Skip to content

Compose Products and Blocks

Compose Products from public Blocks and assign one owner to shared records before installing consumers.

Use this guide when you need to compose Blocks and share records across Workspace. Give each installed definition one owner. Consumers depend on that owner.

Before you start

Before composing Products and Blocks, name the work, its users and their shared records. Inspect existing source before adding definitions. Workspace owns the release and install lifecycle.

Choose an owner

A Block lists the resource handles it owns and can contain child Blocks. Export it as a BlockContribution to reuse it. A Product adds versioning and installation around one root Block, and publication fixes the publisher afterward. A child Block has no separate release or install.

NeedComposition
Reuse code in another assemblyImport or copy a public Block and retain its origin.
Share the same installed records and behaviorGive one Product ownership and add exact dependencies from consumers.
Use entity-defining code in Products installed togetherKeep those definitions in one owner. Importing them into both consumers duplicates ownership.

A second screen alone does not require another Product. A shared foundation earns an owner when named consumers need the same records or behavior.

Compose the root

Import the real capability Block and compose the Product with composeProduct. Publication metadata is not a source input. Preview and publish fix it.

import { composeProduct } from '@merulan/cdk/product';
import { followThrough } from './follow-through/block.ts';

export const composition = composeProduct({
  identity: { key: 'sales', version: '1.0.0', name: 'Sales' },
  install: { namespacePolicy: 'defaulted', duplicatePolicy: 'fail' },
  root: { id: 'sales', name: 'Sales', children: [followThrough] },
});
export const product = composition.source;

A Workspace selects exact releases and supplies declared configuration. Consumers pin the shared owner's exact release digest. A copy of the owner's schema is not a dependency.

Map the work

APQC classifies the work a capability supports. A Sales Product can map to APQC 3.5, with Blocks for its included processes. A category does not require every process beneath it.

State the correspondence, scope and construct evidence. Publish only mapping claims that the pinned adapter accepts. A shared schema need not claim to perform a whole business process. Other frameworks can classify the same Product.

Keep dependencies acyclic

A Shared Context Product can own people, companies, Meetings and participation. Sales owns sales records and Meeting-to-sales links. Projects owns work records and Meeting-to-Project links. Both depend on Shared Context. The foundation has no dependency back to either consumer.

Keep inference with the consumer that reads its inputs. A Pipeline using sales records to shortlist Projects can stay in Sales. Projects can still own the Project relationship definition.

When it fails

workspace.structure.orphan_block

The Block has no owning path or runtime import. Restore its import beneath the owner. A type-only import does not establish ownership.

workspace.closure.conflict

The exact dependency closure is incoherent. Check release coordinates and digests, then compile the full closure again.

Next steps

Extract a shared Product when a second consumer changes the boundary. Check syntax in the CDK reference.