Quickstart
Install @fabricorg/platform, declare a module, register it, and invoke your first action — end to end.
This walks through a complete, runnable example: define a vertical module, register it with the platform, invoke an action through the policy → state machine → handler → event → projection pipeline, and project events into a read model.
The full source for this walkthrough is in examples/minimal-vertical — clone the repo and run pnpm --filter @fabricorg/example-minimal-vertical start to see it in action.
1. Install
pnpm add @fabricorg/platform
# or
npm install @fabricorg/platformThe platform has zero runtime dependencies. You bring your own database client and bind it via the TDb type parameter.
Requires Node 20+. Supports both ESM (import) and CommonJS (require).
dotnet add package FabricOrg.Platform.Abstractions
dotnet add package FabricOrg.Platform
dotnet add package FabricOrg.Platform.DependencyInjection # optional, for DI wiringThe platform targets .NET Standard 2.1 and .NET 8.0. You bring your own database client and bind it via the TDb type parameter or DI.
2. Declare an action
An ActionDefinition is a single mutation entry point — schema, handler, policies it must clear, events it emits, and (optionally) the state-machine transition it represents.
import type { ActionContext, ActionDefinition } from "@fabricorg/platform/actions";
interface SubmitOrderParams {
orderId: string;
total: number;
}
const submitOrderAction: ActionDefinition = {
actionId: "order.submit",
namespace: "order",
version: 1,
schema: {
parse: (input) => input as SubmitOrderParams,
safeParse: (input) => ({ success: true, data: input as SubmitOrderParams }),
},
policies: ["order.large_order_requires_approval.v1"],
emitsEvents: ["OrderSubmitted"],
idempotent: false,
mutatesDomain: true,
stateMachine: {
entityType: "Order",
targetState: "submitted",
getEntityId: (params) => (params as SubmitOrderParams).orderId,
},
handler: async (_ctx: ActionContext, params) => {
const { orderId } = params as SubmitOrderParams;
// Real verticals would write through ctx.db here.
return { success: true, data: { orderId, status: "submitted" } };
},
};using FabricOrg.Platform.Actions;
public record SubmitOrderParams(string OrderId, decimal Total);
public class SubmitOrderAction : ActionDefinition<SubmitOrderParams>
{
public SubmitOrderAction()
{
ActionId = new ActionId("order", "submit");
Namespace = "order";
Version = 1;
Policies = [new PolicyId("order", "large_order_requires_approval", 1)];
EmitsEvents = ["OrderSubmitted"];
Idempotent = false;
MutatesDomain = true;
StateMachine = new StateMachineBinding
{
EntityType = "Order",
TargetState = "submitted",
GetEntityId = (params) => params.OrderId,
};
}
public override async Task<ActionResult> HandlerAsync(
IActionContext ctx,
SubmitOrderParams params,
CancellationToken ct)
{
// Real verticals would write through ctx.Db here.
return ActionResult.Success(new { params.OrderId, Status = "submitted" });
}
}For real schemas, use Zod (TypeScript) or FluentValidation (C#) — both implement the SchemaValidator interface natively.
3. Declare a state machine
import type { StateMachineDefinition } from "@fabricorg/platform/state-machines";
const orderStateMachine: StateMachineDefinition = {
entityType: "Order",
states: {
draft: { id: "draft", label: "Draft", stateClass: "initial" },
submitted: { id: "submitted", label: "Submitted", stateClass: "active" },
delivered: { id: "delivered", label: "Delivered", stateClass: "terminal" },
},
transitions: [
{ from: "draft", to: "submitted", causedByAction: "order.submit" },
{ from: "submitted", to: "delivered", causedByAction: "order.deliver" },
],
};using FabricOrg.Platform.StateMachines;
var orderStateMachine = new StateMachineDefinition
{
EntityType = "Order",
States = new Dictionary<string, StateDefinition>
{
["draft"] = new StateDefinition("draft", "Draft", StateClass.Initial),
["submitted"] = new StateDefinition("submitted", "Submitted", StateClass.Active),
["delivered"] = new StateDefinition("delivered", "Delivered", StateClass.Terminal),
},
Transitions = new List<StateTransition>
{
new StateTransition("draft", "submitted", new ActionId("order", "submit")),
new StateTransition("submitted", "delivered", new ActionId("order", "deliver")),
}
};4. Declare a policy
import type { PolicyContext, PolicyEvaluator } from "@fabricorg/platform/policies";
const largeOrderPolicy: PolicyEvaluator = {
policyId: "order.large_order_requires_approval.v1",
version: 1,
evaluate: async (ctx: PolicyContext) => {
const { total } = ctx.parameters as { total: number };
if (total > 10_000) {
return {
policyId: "order.large_order_requires_approval.v1",
policyVersion: 1,
result: "block",
reason: "over $10,000 requires approval",
};
}
return {
policyId: "order.large_order_requires_approval.v1",
policyVersion: 1,
result: "pass",
};
},
};using FabricOrg.Platform.Policies;
public class LargeOrderPolicy : IPolicyEvaluator
{
public PolicyId PolicyId => new PolicyId("order", "large_order_requires_approval", 1);
public int Version => 1;
public async Task<PolicyEvaluationResult> EvaluateAsync(IPolicyContext ctx, CancellationToken ct)
{
var total = (decimal)(ctx.Parameters["total"] ?? 0);
if (total > 10_000)
{
return new PolicyEvaluationResult(
new PolicyId("order", "large_order_requires_approval", 1),
1,
PolicyOutcome.Block,
reason: "over $10,000 requires approval");
}
return new PolicyEvaluationResult(
new PolicyId("order", "large_order_requires_approval", 1),
1,
PolicyOutcome.Pass);
}
}5. Compose into a module
A FabricModule is the manifest your vertical ships. It declares object types, event types, actions, policies, and state machines — and the platform wires them all into its registries when you call registerFabricModules.
import type { FabricModule } from "@fabricorg/platform/modules";
const orderModule: FabricModule = {
namespace: "order",
version: "1.0.0",
objectTypes: ["Order"],
subjectTypes: ["Order"],
eventTypes: ["OrderSubmitted"],
actions: [submitOrderAction],
policies: [largeOrderPolicy],
stateMachines: [orderStateMachine],
};using FabricOrg.Platform.Modules;
var orderModule = new FabricModule
{
Namespace = "order",
Version = "1.0.0",
ObjectTypes = ["Order"],
SubjectTypes = ["Order"],
EventTypes = ["OrderSubmitted"],
Actions = [new SubmitOrderAction()],
Policies = [new LargeOrderPolicy()],
StateMachines = [orderStateMachine],
};6. Register at boot
import { registerFabricModules } from "@fabricorg/platform/modules";
registerFabricModules([orderModule]);using FabricOrg.Platform.Registries;
var registry = new ModuleRegistry();
registry.Register(orderModule);That's it. After this call:
ordernamespace is reservedOrderobject type +OrderSubmittedevent type are registered- The
order.submitaction,order.large_order_requires_approval.v1policy, andOrderstate machine are all wired into the live platform registries
What happens when you invoke
@fabricorg/platform defines the contracts and the pipeline shape — but not a worker. You wire it into your runtime:
invokeAction
→ ActionInvocation row created (status: pending)
→ evaluatePolicies (largeOrderPolicy runs; if total > 10k → block)
→ state-machine validation (verifies draft → submitted is allowed)
→ handler runs in transaction (your ctx.db work happens here)
→ AssetEvent appended (OrderSubmitted)
→ adapter steps (none on this action — skipped)
→ ActionInvocation status: completedThe runnable example in examples/minimal-vertical/src/index.ts ships a minimal in-process orchestrator that does exactly this — a great starting point for understanding the pipeline before plugging in your durable runtime (Temporal, queue worker, in-process executor, etc.).
What to read next
- Mental model — the seven concepts you need.
- Platform reference → architecture — go deep on ontology, agent-native design, and the mutation pipeline.
- Patterns → vertical extensions — design checklist for a new module.
- Reference → examples — runnable snippets for each pattern.
- Agent HITL — routing agent actions through human approval.
- Privacy engine — redacting sensitive data.