TechFabricTechFabricPlatform
Getting started

Quickstart

Scaffold a capability, run a mutation through Platform Host, and serve its projection through ProjectionHost.

This path uses the real runtime interfaces. The local profile swaps PostgreSQL for memory adapters, but it does not replace Host with a hand-written orchestrator.

The complete source is examples/minimal-vertical.

1. Install the runtime

pnpm add @fabricorg/platform @fabricorg/platform-host @fabricorg/projection-host
pnpm add -D @fabricorg/fabric-gen

Node 20 or newer is required. All three runtime packages support ESM and CommonJS.

For C#, a non-production port exists as FabricOrg.Platform; only the TypeScript host is supported in production, and the C# port trails it on execution-contract enforcement. Use packages and their native Host guide.

2. Start a capability

pnpm exec fabric-gen init capability order
cd order
pnpm exec fabric-gen export --module module.ts --out fabric.manifest.json
pnpm exec fabric-gen --input fabric.manifest.json --out generated

The initializer writes module.ts and a README, and never overwrites an existing file. module.ts is the source of truth: it is TypeScript, so the shape of every action, object type, and event is checked as you write it. The scaffold is a working capability with one object type, one event, and one action that declares all six execution dimensions, so the three commands above succeed before you have changed anything.

fabric-gen export is the seam between the two halves of the loop. It imports the module, normalizes it into fabric.manifest.json, and compiles that manifest immediately, so a mistake is reported against the module you just wrote rather than inside a generator two steps later. Commit both the manifest and generated/, and keep them honest in CI with --check.

3. Author the executable module

module.ts from the scaffold is already one. This is what it holds: an action that validates parameters, enters one state transition, and emits the event that makes the mutation auditable.

const submitOrder: ActionDefinition = {
  actionId: "order.submit",
  namespace: "order",
  version: 1,
  kind: "atomic",
  schema: submitOrderSchema,
  policies: ["order.large_order_requires_approval.v1"],
  emitsEvents: ["OrderSubmitted"],
  idempotent: true,
  mutatesDomain: true,
  stateMachine: {
    entityType: "Order",
    targetState: "submitted",
    getEntityId: (parameters) => parameters.orderId,
  },
  handler: async (_context, parameters) => {
    const input = parameters as SubmitOrder;
    return {
      success: true,
      data: {
        orderId: input.orderId,
        _events: [{
          eventType: "OrderSubmitted",
          subjectType: "Order",
          subjectId: input.orderId,
          payload: { ...input, toState: "submitted" },
        }],
      },
    };
  },
};

const orderModule: FabricModule = {
  namespace: "order",
  version: "1.0.0",
  objectTypes: ["Order"],
  subjectTypes: ["Order"],
  eventTypes: [{ eventType: "OrderSubmitted", schemaVersion: 1 }],
  actions: [submitOrder],
  policies: [largeOrderPolicy],
  stateMachines: [orderStateMachine],
  views: [submittedOrdersView],
};

The full example includes the parameter schema, policy, state machine, and replay-safe view.

4. Compose the actual hosts

const registry = createModuleRegistry([orderModule]);
const store = new MemoryPlatformHostStore({});
const eventSource = new InMemoryProjectionEventSource();

const host = createGovernedActionHost({
  registry,
  store,
  authorization: {
    checkEntitlement: async () => true,
    authorize: async () => true,
  },
});

const projectionHost = createProjectionHost({
  views: [submittedOrdersView],
  eventSource,
  snapshotStore: new InMemoryProjectionSnapshotStore(),
  authorize: () => ({ allowed: true, decisionId: "local-read-policy" }),
});

The allow decisions and memory adapters are explicit local-development choices. Production derives actor and tenant context from authentication, supplies real policy adapters, and uses PostgreSQL or Lakebase stores.

5. Submit, then project

const result = await host.submitAction({
  actionId: "order.submit",
  parameters: { orderId: "order-1", total: 99 },
  tenantId: serverSession.tenantId,
  spaceId: "orders",
  actorId: serverSession.actorId,
  actorType: "natural_person",
  idempotencyKey: "order-1:submit",
});

for (const event of await store.listEvents(serverSession.tenantId, "orders")) {
  eventSource.append(event as ReplayableAssetEvent);
}

const readModel = await projectionHost.project({
  view: { name: "order/submitted", version: "1" },
  scope: { tenantId: serverSession.tenantId, spaceId: "orders" },
  actor: { id: serverSession.actorId, type: "natural_person" },
  consistency: "current",
});

No caller invokes a handler, writes a domain table, or folds events directly. Mutations enter through Platform Host. Reads enter through ProjectionHost.

Run the example:

pnpm --filter @fabricorg/example-minimal-vertical start

It completes one order, serves the projection, and proves a second order is blocked by policy.

6. Move to an approved application

The governed portfolio example shows the next step:

  • one shared capability adopted by lending and field-service applications;
  • independent compiler artifact identities;
  • Assembly v2 lockfiles and content-bound component packs;
  • promoted experience releases with exact authorization grants;
  • runtime evidence containing the assembly and initiating release digests;
  • memory and PostgreSQL profiles; and
  • complete, distinct vertical state-machine lifecycles.

Continue with Production adoption before accepting external traffic.

On this page