Diagram
100%

Drag to pan · Scroll to zoom · + / − to zoom · 0 to fit · Esc to close

Skip to content

16. Semantic Composition and Catalogs ​

The advanced Blueprint style is not a larger constructor call. It is a small domain language made from reusable Dart types.

Reusable facet types keep shared meaning while letting each feature add its own details.

Create semantic facet families ​

dart
abstract class GovernedFacet extends Facet {
  const GovernedFacet({
    required super.name,
    required super.fields,
    super.actions,
  });
}

final class CleaningFacet extends GovernedFacet {
  const CleaningFacet({super.actions})
    : super(
        name: 'cleaning',
        fields: const [CleaningStatus()],
      );

  @override
  CleaningFacet replacingActions(List<Action> next) =>
      CleaningFacet(actions: next);
}

final class CleaningStatus extends TextField {
  const CleaningStatus() : super('status');
}

The subtype expresses a reusable semantic family while retaining the ordinary Facet contract. Overriding replacingActions preserves the subtype when assembly merges capture defaults and lifted rules.

Let assembly lift shared rules ​

Rules may be declared at Blueprint, module, entity, or action scope. Assembly lifts broader rules onto mutating actions without changing their phase:

dart
const tenantRule = Rule(
  id: 'common.tenant.required',
  kind: RuleKind.access,
  phase: RulePhase.beforeCommit,
  test: Not(
    IsNull<String>(FieldValue<String>(FieldOf<String>(TenantIdField()))),
  ),
);

const blueprint = Blueprint(
  name: 'operations',
  version: '1.0.0',
  rules: [tenantRule],
  modules: [opsModule],
);

The effective action retains its local rules plus the lifted rule and their provenance. There is one rule vocabulary, not separate global, module, entity, and action rule systems.

Close executable Dart types explicitly ​

Studio and runtime inspection need to know which concrete semantic subtypes an application supports:

dart
final types = BlueprintTypeCatalog(
  modules: const [
    BlueprintModuleType<Module>(key: 'core.module'),
  ],
  entities: const [
    BlueprintEntityType<Entity>(key: 'core.entity'),
  ],
  facets: const [
    BlueprintFacetType<IdentityFacet>(key: 'core.identity'),
    BlueprintFacetType<CleaningFacet>(key: 'ops.cleaning'),
  ],
  fields: const [
    BlueprintFieldType<CodeField>(key: 'core.code'),
    BlueprintFieldType<CleaningStatus>(key: 'ops.cleaning_status'),
  ],
  actions: const [
    BlueprintActionType<Action>(key: 'core.action'),
  ],
);

final unsupported = types.unsupportedTypes(blueprint);

Catalog keys are persisted identities. Matching is intentionally by exact runtime type: registering Facet does not silently authorize every custom subclass.

Keep catalogs distinct ​

Two catalog concepts serve different purposes:

CatalogContainsPurpose
BlueprintTypeCatalogsupported Dart declaration typesprove the runtime understands authored types
BlueprintCatalogportable definition documentssearch, select, and compose reusable models in Studio

Do not combine them. A definition catalog does not contain constructors, and a type catalog does not contain reusable business modules.

Checkpoint ​

Create CleaningFacet and CalibrationFacet as GovernedFacet subtypes. Contribute calibration from a second module through an EntityDescriptor, then prove that the assembled entity carries both facets and the type catalog reports no unsupported declarations.

Next: Executable entity queries.