@fabricorg/platform 0.1.2 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (70) hide show
  1. package/README.md +105 -61
  2. package/dist/actions/index.cjs +48 -0
  3. package/dist/actions/index.cjs.map +1 -0
  4. package/dist/actions/index.d.cts +116 -0
  5. package/dist/adapters/index.cjs +28 -0
  6. package/dist/adapters/index.cjs.map +1 -0
  7. package/dist/adapters/index.d.cts +83 -0
  8. package/dist/chunk-7KJHGAZY.cjs +99 -0
  9. package/dist/chunk-7KJHGAZY.cjs.map +1 -0
  10. package/dist/chunk-GB2PSZ33.cjs +74 -0
  11. package/dist/chunk-GB2PSZ33.cjs.map +1 -0
  12. package/dist/chunk-JAXDEK5Z.cjs +63 -0
  13. package/dist/chunk-JAXDEK5Z.cjs.map +1 -0
  14. package/dist/chunk-KQEBNUZI.cjs +70 -0
  15. package/dist/chunk-KQEBNUZI.cjs.map +1 -0
  16. package/dist/chunk-LHCSVEFT.cjs +8 -0
  17. package/dist/chunk-LHCSVEFT.cjs.map +1 -0
  18. package/dist/chunk-LYX73U3P.cjs +195 -0
  19. package/dist/chunk-LYX73U3P.cjs.map +1 -0
  20. package/dist/chunk-LZSE6QGQ.cjs +60 -0
  21. package/dist/chunk-LZSE6QGQ.cjs.map +1 -0
  22. package/dist/chunk-NPVIFZQV.cjs +107 -0
  23. package/dist/chunk-NPVIFZQV.cjs.map +1 -0
  24. package/dist/chunk-PG5LYTUX.cjs +4 -0
  25. package/dist/chunk-PG5LYTUX.cjs.map +1 -0
  26. package/dist/chunk-Q3WYIWCN.cjs +183 -0
  27. package/dist/chunk-Q3WYIWCN.cjs.map +1 -0
  28. package/dist/chunk-QETQGAXJ.cjs +35 -0
  29. package/dist/chunk-QETQGAXJ.cjs.map +1 -0
  30. package/dist/chunk-SDXDHVTV.cjs +555 -0
  31. package/dist/chunk-SDXDHVTV.cjs.map +1 -0
  32. package/dist/chunk-YZKUEC2J.cjs +31 -0
  33. package/dist/chunk-YZKUEC2J.cjs.map +1 -0
  34. package/dist/displays/index.cjs +24 -0
  35. package/dist/displays/index.cjs.map +1 -0
  36. package/dist/displays/index.d.cts +34 -0
  37. package/dist/events/index.cjs +36 -0
  38. package/dist/events/index.cjs.map +1 -0
  39. package/dist/events/index.d.cts +35 -0
  40. package/dist/evidence/index.cjs +6 -0
  41. package/dist/evidence/index.cjs.map +1 -0
  42. package/dist/evidence/index.d.cts +43 -0
  43. package/dist/ids/index.cjs +24 -0
  44. package/dist/ids/index.cjs.map +1 -0
  45. package/dist/ids/index.d.cts +19 -0
  46. package/dist/index.cjs +268 -0
  47. package/dist/index.cjs.map +1 -0
  48. package/dist/index.d.cts +13 -0
  49. package/dist/modules/index.cjs +42 -0
  50. package/dist/modules/index.cjs.map +1 -0
  51. package/dist/modules/index.d.cts +73 -0
  52. package/dist/objects/index.cjs +24 -0
  53. package/dist/objects/index.cjs.map +1 -0
  54. package/dist/objects/index.d.cts +7 -0
  55. package/dist/policies/index.cjs +48 -0
  56. package/dist/policies/index.cjs.map +1 -0
  57. package/dist/policies/index.d.cts +143 -0
  58. package/dist/privacy/index.cjs +12 -0
  59. package/dist/privacy/index.cjs.map +1 -0
  60. package/dist/privacy/index.d.cts +77 -0
  61. package/dist/projections/index.cjs +12 -0
  62. package/dist/projections/index.cjs.map +1 -0
  63. package/dist/projections/index.d.cts +52 -0
  64. package/dist/state-machines/index.cjs +32 -0
  65. package/dist/state-machines/index.cjs.map +1 -0
  66. package/dist/state-machines/index.d.cts +60 -0
  67. package/dist/testing/index.cjs +21 -0
  68. package/dist/testing/index.cjs.map +1 -0
  69. package/dist/testing/index.d.cts +19 -0
  70. package/package.json +196 -111
package/README.md CHANGED
@@ -1,104 +1,148 @@
1
1
  # @fabricorg/platform
2
2
 
3
- Generic runtime for ontology-based business applications. Provides the action lifecycle, policy dispatch, state machine validation, adapter retry/circuit-breaker, event append, and ID generation that any vertical (lending, legal, healthcare, insurance, etc.) can build on.
3
+ A portable runtime for ontology-based, agent-native business applications. Zero dependencies. Bring your own database client.
4
4
 
5
- ## Portability Scope
5
+ ```bash
6
+ npm install @fabricorg/platform
7
+ ```
6
8
 
7
- **`@fabricorg/platform` is portable.** It has zero runtime dependencies, no database client, no ORM, and no vertical vocabulary. Every contract is parameterized by `TDb` so consumers bind their own persistence client.
9
+ ## Hello, action
8
10
 
9
- **Vertical packages (e.g. `@repo/lending`) are NOT portable.** They bind `TDb` to a concrete client (e.g. `Prisma.TransactionClient`), reference vertical entity types, and assume a specific database schema. To run a vertical against a different ORM, fork the vertical — not the platform.
11
+ ```ts
12
+ import { z } from "zod";
13
+ import type { ActionDefinition, ActionContext } from "@fabricorg/platform/actions";
14
+
15
+ const greet: ActionDefinition<unknown> = {
16
+ id: "demo.greet",
17
+ schema: z.object({ name: z.string() }),
18
+ handler: async (ctx: ActionContext<unknown>, params: { name: string }) => {
19
+ return { greeting: `Hello, ${params.name}!` };
20
+ },
21
+ };
22
+ ```
10
23
 
11
- The platform is enforced clean by `boundary.test.ts`, which forbids:
12
- - `@repo/database`, `@repo/ontology`, `@repo/adapters` imports
13
- - the literal `Prisma`, the field name `prisma:`, and the type default `TDb = any`
14
- - any vertical vocabulary (`lending.`, `borrower`, `lender`, `LeadApplication`, `Offer`, `Vehicle`, `Party`, `Obligation`, `auto-refi`, etc.)
15
- - any "register default" hook (`registerDefaultPolicies`, etc.)
24
+ That's the smallest unit. From here, you compose actions into a `FabricModule<TDb>`, register the module at startup, and call `invokeAction("demo.greet", ...)` from any of three call paths (authenticated user, signed token, or system/agent). Every call passes through the same pipeline: **policy → state machine → handler → adapters → events → projections.**
16
25
 
17
- ## Subpath Exports
26
+ For a complete walkthrough see https://platform.fabric.pro/docs/getting-started/quickstart.
18
27
 
19
- | Path | Contents |
20
- |---|---|
21
- | `@fabricorg/platform` | re-exports everything below |
22
- | `@fabricorg/platform/actions` | `ActionDefinition<TDb>`, `ActionContext<TDb>`, `ActorType`, `assertActorType`, `SagaImplementation`, `SchemaValidator` |
23
- | `@fabricorg/platform/adapters` | `AdapterRegistry`, `AdapterImplementation`, retry/circuit-breaker primitives |
24
- | `@fabricorg/platform/events` | `AssetEventEnvelope`, `EventTypeRegistry` |
25
- | `@fabricorg/platform/ids` | `createFabricId`, `FABRIC_ID_PREFIXES`, `parseFabricId` |
26
- | `@fabricorg/platform/modules` | `FabricModule<TDb>`, `ModuleRegistry`, `createModuleRegistry`, `registerFabricModules` |
27
- | `@fabricorg/platform/policies` | `PolicyEvaluator<TDb>`, `PolicyContext<TDb>`, code/data/hybrid evaluation engine |
28
- | `@fabricorg/platform/state-machines` | `StateMachineDefinition`, `validateTransition`, engine |
29
- | `@fabricorg/platform/projections` | replay/snapshot framework |
30
- | `@fabricorg/platform/displays` | `DisplayRendererRegistration`, display engine |
31
- | `@fabricorg/platform/privacy` | redaction, anonymization-pipeline, data-classification contracts |
32
- | `@fabricorg/platform/evidence` | compliance evidence packet envelope |
33
- | `@fabricorg/platform/objects` | shared object-type contracts |
34
- | `@fabricorg/platform/testing` | test fixtures (in-memory adapter registry, fake ActionContext, deterministic IDs) |
28
+ ## Why use this
35
29
 
36
- ## Action Lifecycle
30
+ You're building a governed business application — not a CRUD app. You need:
37
31
 
38
- Every domain mutation flows through a single platform pipeline:
32
+ - A single, auditable entry point for every mutation
33
+ - Policies that gate every change before it happens
34
+ - Events as evidence (not retrofitted logs)
35
+ - Agents and humans treated identically by policy
36
+ - A vertical-specific ontology layered on a portable runtime
39
37
 
40
- 1. `evaluatePolicies` dispatch each policy ID via `evaluatePolicyDefinitions` (code/data/hybrid). Aggregate `block` halts the invocation.
41
- 2. **If `actionDef.kind === "saga"`** — dispatch to a registered saga implementation that orchestrates child actions and returns a `SagaCompletion`.
42
- 3. **Otherwise** — build an `ActionContext<TDb>`, run `actionDef.handler(ctx, params)` inside a single transaction. The handler parses its own input via its declared schema (single source of truth — handlers honor their schema contract). State-machine binding validates transitions before the handler runs. ZodError thrown by handler parsing is translated to `validation_failed` invocation status.
43
- 4. `executeAdapters` — for each `actionDef.adapterSteps`, look up the implementation in the `AdapterRegistry` and run via `executeWithAdapterRetry` (idempotency-aware retry, exponential backoff, circuit breaker, dead-letter queue on terminal failure).
44
- 5. `completeActionInvocation` — mark completed/failed and emit telemetry.
38
+ That's what this package gives you. Nothing more.
45
39
 
46
- `validateActionParameters` exists as a standalone activity for preview/dry-run paths but is **not** part of the execute pipeline — parameter validation happens once, in the handler, via its own schema.
40
+ ## What's in the box
47
41
 
48
- ## Module Contract
42
+ | Subpath | What you get |
43
+ |---|---|
44
+ | `@fabricorg/platform` | Re-exports everything below |
45
+ | `@fabricorg/platform/actions` | `ActionDefinition`, `ActionContext`, `SagaImplementation` |
46
+ | `@fabricorg/platform/policies` | `PolicyEvaluator`, `PolicyContext`, code/data/hybrid evaluation |
47
+ | `@fabricorg/platform/state-machines` | `StateMachineDefinition`, `validateTransition` |
48
+ | `@fabricorg/platform/events` | `AssetEventEnvelope`, `EventTypeRegistry` |
49
+ | `@fabricorg/platform/projections` | Replay/snapshot framework |
50
+ | `@fabricorg/platform/adapters` | `AdapterRegistry`, retry + circuit-breaker |
51
+ | `@fabricorg/platform/modules` | `FabricModule<TDb>`, `registerFabricModules` |
52
+ | `@fabricorg/platform/ids` | `createFabricId`, `FABRIC_ID_PREFIXES` |
53
+ | `@fabricorg/platform/displays` | `DisplayRendererRegistration` |
54
+ | `@fabricorg/platform/privacy` | Redaction + data classification |
55
+ | `@fabricorg/platform/evidence` | Compliance evidence packets |
56
+ | `@fabricorg/platform/objects` | Shared object-type contracts |
57
+ | `@fabricorg/platform/testing` | In-memory adapter registry, fake `ActionContext`, deterministic IDs |
49
58
 
50
- Verticals declare modules as `FabricModule<TDb>`:
59
+ ## Composing a vertical
60
+
61
+ Verticals declare a `FabricModule<TDb>` that registers their actions, policies, state machines, events, and adapters with the platform:
51
62
 
52
63
  ```ts
53
64
  import type { FabricModule } from "@fabricorg/platform/modules";
54
- import type { Prisma } from "@repo/database";
65
+ import { registerFabricModules } from "@fabricorg/platform/modules";
55
66
 
56
- export const lendingModule: FabricModule<Prisma.TransactionClient> = {
57
- namespace: "lending",
67
+ const myModule: FabricModule<MyDbClient> = {
68
+ namespace: "demo",
58
69
  version: "1.0.0",
59
- objectTypes: ["Vehicle", "Party", "Location", "Document"],
60
- actions: [...],
61
- policies: [...],
62
- stateMachines: [...],
63
- displayRenderers: [...],
70
+ objectTypes: ["Widget"],
71
+ actions: [greet, /* ... */],
72
+ policies: [/* ... */],
73
+ stateMachines: [/* ... */],
74
+ displayRenderers: [],
64
75
  };
76
+
77
+ // At app startup:
78
+ registerFabricModules([myModule]);
65
79
  ```
66
80
 
67
- Apps compose modules explicitly at startup:
81
+ The platform never auto-loads a module. The host app composes them explicitly no implicit defaults, no globals, no surprises.
68
82
 
69
- ```ts
70
- import { registerFabricModules } from "@fabricorg/platform/modules";
71
- import { lendingModule } from "@repo/lending";
72
- import { messagingModule } from "@repo/messaging";
83
+ ## The action lifecycle
73
84
 
74
- registerFabricModules([messagingModule, lendingModule]);
75
- ```
85
+ Every `invokeAction(id, params)` call goes through:
86
+
87
+ 1. **`evaluatePolicies`** — every policy registered for the action runs (code/data/hybrid). A single `block` halts the invocation.
88
+ 2. **State-machine validation** — if the action's target entity is bound to a state machine, the requested transition is validated before the handler runs.
89
+ 3. **Handler** — `actionDef.handler(ctx, params)` runs inside a single `TDb` transaction. Handler parses its own input via its `schema` (single source of truth).
90
+ 4. **Adapters** — for each `actionDef.adapterSteps`, the registered implementation runs via `executeWithAdapterRetry` (idempotent retry, exponential backoff, circuit breaker).
91
+ 5. **Events** — domain events are appended; projections derive read models on next replay.
92
+ 6. **Completion** — invocation status is marked, telemetry emitted.
76
93
 
77
- There are no implicit/default modules. The platform never auto-loads a vertical. Each vertical owns its full domain ontology — there is no "shared core ontology" package; cross-industry governance object types (`AssetEvent`, `ActionInvocation`, `PolicyEvaluation`, `ApprovalRequest`, `ConsentRecord`, `AgentRun`) are pre-registered by the platform itself.
94
+ `validateActionParameters` is also exposed for preview/dry-run paths but is **not** part of the execute pipeline.
78
95
 
79
- ## Saga Actions
96
+ ## Saga actions
80
97
 
81
- Multi-step orchestrations declare `kind: "saga"` on the `ActionDefinition` and ship a `SagaImplementation` alongside:
98
+ Multi-step orchestrations declare `kind: "saga"` on the `ActionDefinition` and ship a `SagaImplementation`:
82
99
 
83
100
  ```ts
84
101
  import type { SagaImplementation } from "@fabricorg/platform/actions";
85
102
 
86
103
  export const ingestSaga: SagaImplementation = async (ctx, runChild) => {
87
- const child = await runChild({
104
+ const step1 = await runChild({
88
105
  suffix: "step-1",
89
- actionId: "lending.receive_lead_payload",
90
- parameters: { ... },
106
+ actionId: "demo.receive_payload",
107
+ parameters: { /* ... */ },
91
108
  });
92
- // ... orchestrate further child actions ...
109
+ // orchestrate further child actions...
93
110
  return {
94
- resultData: { ... },
95
- event: { eventType: "...", subjectType: "...", subjectId: "...", payload: ... },
111
+ resultData: { /* ... */ },
112
+ event: { eventType: "...", subjectType: "...", subjectId: "...", payload: {} },
96
113
  };
97
114
  };
98
115
  ```
99
116
 
100
- The host app's saga registry maps action IDs to implementations. The worker dispatches generically — no platform code or worker code references vertical action IDs.
117
+ Your worker maps action IDs to implementations. The platform dispatches generically — no platform code references vertical action IDs.
118
+
119
+ ## Portability boundary
120
+
121
+ The `boundary.test.ts` in this package fails CI if any of these leak in:
122
+
123
+ - `@repo/database`, `@repo/ontology`, `@repo/adapters` imports
124
+ - the literal `Prisma`, the field name `prisma:`, or the type default `TDb = any`
125
+ - vertical vocabulary (`lending.`, `borrower`, `lender`, `Vehicle`, `Party`, `Obligation`, `auto-refi`, etc.)
126
+ - "register default" hooks (`registerDefaultPolicies`, etc.)
127
+
128
+ This is what keeps the platform portable across verticals and consumers.
129
+
130
+ ## Compatibility
131
+
132
+ - **Runtime:** Node 20+
133
+ - **Module formats:** ESM and CommonJS (dual format from v0.2.0+)
134
+ - **TypeScript:** 5.0+, `moduleResolution: node` or `node16`/`nodenext`
101
135
 
102
136
  ## Versioning
103
137
 
104
138
  `0.x` while contracts evolve. `1.0` only after at least two materially different verticals validate the API.
139
+
140
+ See [`CHANGELOG.md`](./CHANGELOG.md) for release history.
141
+
142
+ ## Documentation
143
+
144
+ Full reference docs at https://platform.fabric.pro
145
+
146
+ ## License
147
+
148
+ MIT
@@ -0,0 +1,48 @@
1
+ 'use strict';
2
+
3
+ var chunkKQEBNUZI_cjs = require('../chunk-KQEBNUZI.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "ACTOR_TYPES", {
8
+ enumerable: true,
9
+ get: function () { return chunkKQEBNUZI_cjs.ACTOR_TYPES; }
10
+ });
11
+ Object.defineProperty(exports, "assertActionId", {
12
+ enumerable: true,
13
+ get: function () { return chunkKQEBNUZI_cjs.assertActionId; }
14
+ });
15
+ Object.defineProperty(exports, "assertActorType", {
16
+ enumerable: true,
17
+ get: function () { return chunkKQEBNUZI_cjs.assertActorType; }
18
+ });
19
+ Object.defineProperty(exports, "getRegisteredActionIds", {
20
+ enumerable: true,
21
+ get: function () { return chunkKQEBNUZI_cjs.getRegisteredActionIds; }
22
+ });
23
+ Object.defineProperty(exports, "isActorType", {
24
+ enumerable: true,
25
+ get: function () { return chunkKQEBNUZI_cjs.isActorType; }
26
+ });
27
+ Object.defineProperty(exports, "isRegisteredActionId", {
28
+ enumerable: true,
29
+ get: function () { return chunkKQEBNUZI_cjs.isRegisteredActionId; }
30
+ });
31
+ Object.defineProperty(exports, "registerAction", {
32
+ enumerable: true,
33
+ get: function () { return chunkKQEBNUZI_cjs.registerAction; }
34
+ });
35
+ Object.defineProperty(exports, "registerActionIfMissing", {
36
+ enumerable: true,
37
+ get: function () { return chunkKQEBNUZI_cjs.registerActionIfMissing; }
38
+ });
39
+ Object.defineProperty(exports, "resolveAction", {
40
+ enumerable: true,
41
+ get: function () { return chunkKQEBNUZI_cjs.resolveAction; }
42
+ });
43
+ Object.defineProperty(exports, "resolveRegisteredAction", {
44
+ enumerable: true,
45
+ get: function () { return chunkKQEBNUZI_cjs.resolveRegisteredAction; }
46
+ });
47
+ //# sourceMappingURL=index.cjs.map
48
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs"}
@@ -0,0 +1,116 @@
1
+ type ActionId = `${string}.${string}`;
2
+ interface SchemaParseIssue {
3
+ path: PropertyKey[];
4
+ message: string;
5
+ }
6
+ type SchemaParseResult<T = unknown> = {
7
+ success: true;
8
+ data: T;
9
+ } | {
10
+ success: false;
11
+ error: {
12
+ issues: SchemaParseIssue[];
13
+ };
14
+ };
15
+ interface SchemaValidator<T = unknown> {
16
+ parse(input: unknown): T;
17
+ safeParse(input: unknown): SchemaParseResult<T>;
18
+ }
19
+ type ActorType = "natural_person" | "agent" | "system" | "service_account" | "external_system" | "integration";
20
+ declare const ACTOR_TYPES: readonly ActorType[];
21
+ declare function isActorType(value: string): value is ActorType;
22
+ declare function assertActorType(value: string): asserts value is ActorType;
23
+ type ActionStatus = "pending" | "running" | "completed" | "failed" | "blocked_by_policy" | "waiting_for_approval" | "validation_failed";
24
+ interface FabricRuntimeServices {
25
+ readonly [serviceName: string]: unknown;
26
+ }
27
+ interface ActionContext<TDb = unknown> {
28
+ actionInvocationId: string;
29
+ tenantId: string;
30
+ spaceId: string;
31
+ actorId: string;
32
+ actorType: ActorType;
33
+ correlationId: string;
34
+ causationId?: string;
35
+ db: TDb;
36
+ services?: FabricRuntimeServices;
37
+ }
38
+ interface ActionResult {
39
+ success: boolean;
40
+ data?: Record<string, unknown>;
41
+ error?: string;
42
+ }
43
+ type ActionHandler<TDb = unknown> = (ctx: ActionContext<TDb>, params: unknown) => Promise<ActionResult>;
44
+ interface AdapterStepRetryPolicy {
45
+ idempotent: boolean;
46
+ maxAttempts?: number;
47
+ initialDelayMs?: number;
48
+ backoffMultiplier?: number;
49
+ maxDelayMs?: number;
50
+ }
51
+ interface AdapterStep {
52
+ adapterType: string;
53
+ operation: string;
54
+ retryPolicy?: AdapterStepRetryPolicy;
55
+ getInput: (params: unknown, handlerResult: Record<string, unknown>) => Record<string, unknown> | undefined;
56
+ }
57
+ interface ActionStateMachineBinding {
58
+ entityType: string;
59
+ targetState: string | ((params: unknown) => string);
60
+ getEntityId: (params: unknown) => string | undefined;
61
+ }
62
+ interface ActionDefinition<TDb = unknown> {
63
+ actionId: ActionId;
64
+ namespace: string;
65
+ version: number;
66
+ schema: SchemaValidator;
67
+ handler?: ActionHandler<TDb>;
68
+ requiredRoles?: string[];
69
+ requiredPermissions?: string[];
70
+ policies?: `${string}.v${number}`[];
71
+ emitsEvents: string[];
72
+ idempotent: boolean;
73
+ mutatesDomain: boolean;
74
+ stateMachine?: ActionStateMachineBinding;
75
+ adapterSteps?: AdapterStep[];
76
+ /**
77
+ * If "saga", the action is a multi-step orchestration. The worker dispatches
78
+ * to a registered saga implementation instead of running `handler` directly.
79
+ */
80
+ kind?: "atomic" | "saga";
81
+ }
82
+ interface SagaContext {
83
+ actionInvocationId: string;
84
+ tenantId: string;
85
+ spaceId: string;
86
+ parameters: unknown;
87
+ sleep?: (delayMs: number) => Promise<void>;
88
+ query?: <T = unknown>(model: string, operation: string, args: unknown) => Promise<T>;
89
+ }
90
+ interface SagaChildActionInput {
91
+ suffix: string;
92
+ actionId: string;
93
+ parameters: Record<string, unknown>;
94
+ }
95
+ type SagaRunChild = (input: SagaChildActionInput) => Promise<Record<string, unknown>>;
96
+ interface SagaCompletionEvent {
97
+ eventType: string;
98
+ subjectType: string;
99
+ subjectId: string;
100
+ payload: unknown;
101
+ eventSchemaVersion?: number;
102
+ }
103
+ interface SagaCompletion {
104
+ resultData: Record<string, unknown>;
105
+ event?: SagaCompletionEvent;
106
+ }
107
+ type SagaImplementation = (ctx: SagaContext, runChild: SagaRunChild) => Promise<SagaCompletion>;
108
+ declare function assertActionId(actionId: string): asserts actionId is ActionId;
109
+ declare function registerAction(definition: ActionDefinition): void;
110
+ declare function resolveAction(actionId: ActionId): ActionDefinition | undefined;
111
+ declare function isRegisteredActionId(actionId: string): actionId is ActionId;
112
+ declare function resolveRegisteredAction(actionId: string): ActionDefinition | undefined;
113
+ declare function getRegisteredActionIds(): ActionId[];
114
+ declare function registerActionIfMissing(definition: ActionDefinition): void;
115
+
116
+ export { ACTOR_TYPES, type ActionContext, type ActionDefinition, type ActionHandler, type ActionId, type ActionResult, type ActionStateMachineBinding, type ActionStatus, type ActorType, type AdapterStep, type AdapterStepRetryPolicy, type FabricRuntimeServices, type SagaChildActionInput, type SagaCompletion, type SagaCompletionEvent, type SagaContext, type SagaImplementation, type SagaRunChild, type SchemaParseIssue, type SchemaParseResult, type SchemaValidator, assertActionId, assertActorType, getRegisteredActionIds, isActorType, isRegisteredActionId, registerAction, registerActionIfMissing, resolveAction, resolveRegisteredAction };
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ var chunkNPVIFZQV_cjs = require('../chunk-NPVIFZQV.cjs');
4
+
5
+
6
+
7
+ Object.defineProperty(exports, "AdapterRegistry", {
8
+ enumerable: true,
9
+ get: function () { return chunkNPVIFZQV_cjs.AdapterRegistry; }
10
+ });
11
+ Object.defineProperty(exports, "MissingAdapterRegistrationError", {
12
+ enumerable: true,
13
+ get: function () { return chunkNPVIFZQV_cjs.MissingAdapterRegistrationError; }
14
+ });
15
+ Object.defineProperty(exports, "executeWithAdapterRetry", {
16
+ enumerable: true,
17
+ get: function () { return chunkNPVIFZQV_cjs.executeWithAdapterRetry; }
18
+ });
19
+ Object.defineProperty(exports, "getRetryDelayMs", {
20
+ enumerable: true,
21
+ get: function () { return chunkNPVIFZQV_cjs.getRetryDelayMs; }
22
+ });
23
+ Object.defineProperty(exports, "resolveAdapterRetryPolicy", {
24
+ enumerable: true,
25
+ get: function () { return chunkNPVIFZQV_cjs.resolveAdapterRetryPolicy; }
26
+ });
27
+ //# sourceMappingURL=index.cjs.map
28
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"index.cjs"}
@@ -0,0 +1,83 @@
1
+ interface AdapterExecutionContext {
2
+ tenantId: string;
3
+ spaceId: string;
4
+ actionInvocationId: string;
5
+ adapterInvocationId: string;
6
+ correlationId: string;
7
+ causationId?: string;
8
+ attempt: number;
9
+ maxAttempts: number;
10
+ }
11
+ interface AdapterExecutionResult {
12
+ success: boolean;
13
+ correlationId?: string;
14
+ error?: string;
15
+ }
16
+ type AdapterExecutor = (input: Record<string, unknown>, context: AdapterExecutionContext) => Promise<AdapterExecutionResult>;
17
+ interface AdapterRetryPolicy {
18
+ /** External calls must explicitly opt into retry by being idempotent. */
19
+ idempotent: boolean;
20
+ /** Total attempts including the first call. Non-idempotent policies are forced to 1. */
21
+ maxAttempts?: number;
22
+ /** First backoff delay in milliseconds. */
23
+ initialDelayMs?: number;
24
+ /** Exponential multiplier applied after each failed attempt. */
25
+ backoffMultiplier?: number;
26
+ /** Upper bound for any single retry delay in milliseconds. */
27
+ maxDelayMs?: number;
28
+ }
29
+ interface ResolvedAdapterRetryPolicy {
30
+ idempotent: boolean;
31
+ maxAttempts: number;
32
+ initialDelayMs: number;
33
+ backoffMultiplier: number;
34
+ maxDelayMs: number;
35
+ }
36
+ interface AdapterCircuitBreakerConfig {
37
+ mode: "monitor" | "enforce";
38
+ failureThreshold: number;
39
+ windowMs: number;
40
+ cooldownMs: number;
41
+ minimumSamples?: number;
42
+ }
43
+ interface AdapterImplementation {
44
+ adapterType: string;
45
+ operation: string;
46
+ vendor: string;
47
+ /** Whether this adapter operation can be safely retried by default. */
48
+ idempotent: boolean;
49
+ retryPolicy?: AdapterRetryPolicy;
50
+ /** Defaults to monitor-only; enforce mode must be explicitly configured. */
51
+ circuitBreaker?: AdapterCircuitBreakerConfig;
52
+ execute: AdapterExecutor;
53
+ }
54
+ interface AdapterAttempt<T> {
55
+ attempt: number;
56
+ result?: T;
57
+ error?: string;
58
+ willRetry: boolean;
59
+ nextDelayMs?: number;
60
+ }
61
+ interface ExecuteWithRetryOptions<T> {
62
+ policy?: AdapterRetryPolicy;
63
+ defaultIdempotent: boolean;
64
+ execute: (attempt: number, maxAttempts: number) => Promise<T>;
65
+ isSuccessful: (result: T) => boolean;
66
+ getError: (result: T) => string | undefined;
67
+ sleep?: (delayMs: number) => Promise<void>;
68
+ onAttempt?: (attempt: AdapterAttempt<T>) => Promise<void> | void;
69
+ }
70
+ declare class MissingAdapterRegistrationError extends Error {
71
+ constructor(adapterType: string, operation: string);
72
+ }
73
+ declare class AdapterRegistry {
74
+ private readonly adapters;
75
+ register(adapter: AdapterImplementation): void;
76
+ resolve(adapterType: string, operation: string): AdapterImplementation | undefined;
77
+ require(adapterType: string, operation: string): AdapterImplementation;
78
+ }
79
+ declare function resolveAdapterRetryPolicy(policy: AdapterRetryPolicy | undefined, defaultIdempotent: boolean): ResolvedAdapterRetryPolicy;
80
+ declare function getRetryDelayMs(attempt: number, policy: ResolvedAdapterRetryPolicy): number;
81
+ declare function executeWithAdapterRetry<T>(options: ExecuteWithRetryOptions<T>): Promise<T>;
82
+
83
+ export { type AdapterAttempt, type AdapterCircuitBreakerConfig, type AdapterExecutionContext, type AdapterExecutionResult, type AdapterExecutor, type AdapterImplementation, AdapterRegistry, type AdapterRetryPolicy, type ExecuteWithRetryOptions, MissingAdapterRegistrationError, type ResolvedAdapterRetryPolicy, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy };
@@ -0,0 +1,99 @@
1
+ 'use strict';
2
+
3
+ var assert = require('assert');
4
+
5
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
6
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
7
+
8
+ var assert__default = /*#__PURE__*/_interopDefault(assert);
9
+
10
+ // displays/index.ts
11
+ var registry = /* @__PURE__ */ new Map();
12
+ function registerDisplayRenderer(eventType, renderer) {
13
+ registry.set(eventType, renderer);
14
+ }
15
+ function resolveDisplayRenderer(eventType) {
16
+ return registry.get(eventType);
17
+ }
18
+ function denormalizeDisplaySnapshot(payload) {
19
+ if (payload !== null && typeof payload === "object" && "_displaySnapshot" in payload && payload._displaySnapshot !== null && typeof payload._displaySnapshot === "object" && "title" in payload._displaySnapshot) {
20
+ return payload._displaySnapshot;
21
+ }
22
+ if (payload !== null && typeof payload === "object" && "_projectionSnapshot" in payload && payload._projectionSnapshot !== null && typeof payload._projectionSnapshot === "object" && "display" in payload._projectionSnapshot && payload._projectionSnapshot.display !== null && typeof payload._projectionSnapshot.display === "object" && "title" in payload._projectionSnapshot.display) {
23
+ const snapshot = payload._projectionSnapshot;
24
+ return {
25
+ ...snapshot.display,
26
+ _cachedDisplay: {
27
+ source: "projection_snapshot",
28
+ cursor: snapshot.eventCursor,
29
+ checksum: snapshot.checksum,
30
+ generatedAt: snapshot.updatedAt
31
+ }
32
+ };
33
+ }
34
+ return void 0;
35
+ }
36
+ async function renderEventDisplay(eventType, payload, ctx) {
37
+ const snapshot = denormalizeDisplaySnapshot(payload);
38
+ if (snapshot) {
39
+ return snapshot;
40
+ }
41
+ const renderer = resolveDisplayRenderer(eventType);
42
+ if (!renderer) {
43
+ return {
44
+ title: eventType,
45
+ subtitle: "No display renderer registered for this event type."
46
+ };
47
+ }
48
+ return await renderer(payload, ctx);
49
+ }
50
+ async function _runInlineTests() {
51
+ const testEventType = "__TestEvent__";
52
+ registerDisplayRenderer(testEventType, (payload) => ({
53
+ title: `Test: ${payload.name}`,
54
+ subtitle: "registered renderer"
55
+ }));
56
+ const renderer = resolveDisplayRenderer(testEventType);
57
+ assert__default.default.ok(renderer, "resolveDisplayRenderer should return the registered renderer");
58
+ const result = await renderEventDisplay(testEventType, { name: "Alice" });
59
+ assert__default.default.strictEqual(result.title, "Test: Alice");
60
+ assert__default.default.strictEqual(result.subtitle, "registered renderer");
61
+ const snapshot = {
62
+ title: "Snapshot Title",
63
+ subtitle: "snapshot subtitle",
64
+ stateLabel: "archived",
65
+ primaryFacts: [{ label: "ID", value: "123" }]
66
+ };
67
+ const snapshotResult = await renderEventDisplay(testEventType, {
68
+ _displaySnapshot: snapshot
69
+ });
70
+ assert__default.default.deepStrictEqual(snapshotResult, snapshot);
71
+ const fallback = await renderEventDisplay("__UnknownEvent__", {});
72
+ assert__default.default.strictEqual(fallback.title, "__UnknownEvent__");
73
+ assert__default.default.strictEqual(fallback.subtitle, "No display renderer registered for this event type.");
74
+ registerDisplayRenderer("__AsyncEvent__", async (_payload, ctx) => {
75
+ return {
76
+ title: `Async: ${ctx?.tenantId ?? "no-ctx"}`
77
+ };
78
+ });
79
+ const asyncResult = await renderEventDisplay(
80
+ "__AsyncEvent__",
81
+ {},
82
+ { tenantId: "t-1", spaceId: "s-1", db: {} }
83
+ );
84
+ assert__default.default.strictEqual(asyncResult.title, "Async: t-1");
85
+ console.log("\u2705 display engine inline tests passed");
86
+ }
87
+ if ((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('chunk-7KJHGAZY.cjs', document.baseURI).href)) === `file://${process.argv[1]}`) {
88
+ _runInlineTests().catch((err) => {
89
+ console.error("\u274C display engine inline tests failed", err);
90
+ process.exit(1);
91
+ });
92
+ }
93
+
94
+ exports.denormalizeDisplaySnapshot = denormalizeDisplaySnapshot;
95
+ exports.registerDisplayRenderer = registerDisplayRenderer;
96
+ exports.renderEventDisplay = renderEventDisplay;
97
+ exports.resolveDisplayRenderer = resolveDisplayRenderer;
98
+ //# sourceMappingURL=chunk-7KJHGAZY.cjs.map
99
+ //# sourceMappingURL=chunk-7KJHGAZY.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../displays/index.ts"],"names":["assert"],"mappings":";;;;;;;;;;AAoCA,IAAM,QAAA,uBAAe,GAAA,EAA6B;AAE3C,SAAS,uBAAA,CAAwB,WAAmB,QAAA,EAAiC;AAC3F,EAAA,QAAA,CAAS,GAAA,CAAI,WAAW,QAAQ,CAAA;AACjC;AAEO,SAAS,uBAAuB,SAAA,EAAgD;AACtF,EAAA,OAAO,QAAA,CAAS,IAAI,SAAS,CAAA;AAC9B;AAEO,SAAS,2BAA2B,OAAA,EAAuD;AACjG,EAAA,IACC,YAAY,IAAA,IACZ,OAAO,OAAA,KAAY,QAAA,IACnB,sBAAsB,OAAA,IACtB,OAAA,CAAQ,gBAAA,KAAqB,IAAA,IAC7B,OAAO,OAAA,CAAQ,gBAAA,KAAqB,QAAA,IACpC,OAAA,IAAW,QAAQ,gBAAA,EAClB;AACD,IAAA,OAAO,OAAA,CAAQ,gBAAA;AAAA,EAChB;AAEA,EAAA,IACC,OAAA,KAAY,IAAA,IACZ,OAAO,OAAA,KAAY,QAAA,IACnB,qBAAA,IAAyB,OAAA,IACzB,OAAA,CAAQ,mBAAA,KAAwB,IAAA,IAChC,OAAO,OAAA,CAAQ,mBAAA,KAAwB,QAAA,IACvC,SAAA,IAAa,OAAA,CAAQ,mBAAA,IACrB,OAAA,CAAQ,mBAAA,CAAoB,OAAA,KAAY,IAAA,IACxC,OAAO,OAAA,CAAQ,mBAAA,CAAoB,OAAA,KAAY,QAAA,IAC/C,OAAA,IAAW,OAAA,CAAQ,oBAAoB,OAAA,EACtC;AACD,IAAA,MAAM,WAAW,OAAA,CAAQ,mBAAA;AAMzB,IAAA,OAAO;AAAA,MACN,GAAG,QAAA,CAAS,OAAA;AAAA,MACZ,cAAA,EAAgB;AAAA,QACf,MAAA,EAAQ,qBAAA;AAAA,QACR,QAAQ,QAAA,CAAS,WAAA;AAAA,QACjB,UAAU,QAAA,CAAS,QAAA;AAAA,QACnB,aAAa,QAAA,CAAS;AAAA;AACvB,KACD;AAAA,EACD;AACA,EAAA,OAAO,MAAA;AACR;AAEA,eAAsB,kBAAA,CACrB,SAAA,EACA,OAAA,EACA,GAAA,EACmC;AACnC,EAAA,MAAM,QAAA,GAAW,2BAA2B,OAAO,CAAA;AACnD,EAAA,IAAI,QAAA,EAAU;AACb,IAAA,OAAO,QAAA;AAAA,EACR;AAEA,EAAA,MAAM,QAAA,GAAW,uBAAuB,SAAS,CAAA;AACjD,EAAA,IAAI,CAAC,QAAA,EAAU;AACd,IAAA,OAAO;AAAA,MACN,KAAA,EAAO,SAAA;AAAA,MACP,QAAA,EAAU;AAAA,KACX;AAAA,EACD;AAEA,EAAA,OAAO,MAAM,QAAA,CAAS,OAAA,EAAS,GAAG,CAAA;AACnC;AAGA,eAAe,eAAA,GAAiC;AAC/C,EAAA,MAAM,aAAA,GAAgB,eAAA;AAGtB,EAAA,uBAAA,CAAwB,aAAA,EAAe,CAAC,OAAA,MAAa;AAAA,IACpD,KAAA,EAAO,CAAA,MAAA,EAAU,OAAA,CAA6B,IAAI,CAAA,CAAA;AAAA,IAClD,QAAA,EAAU;AAAA,GACX,CAAE,CAAA;AAEF,EAAA,MAAM,QAAA,GAAW,uBAAuB,aAAa,CAAA;AACrD,EAAAA,uBAAA,CAAO,EAAA,CAAG,UAAU,8DAA8D,CAAA;AAElF,EAAA,MAAM,SAAS,MAAM,kBAAA,CAAmB,eAAe,EAAE,IAAA,EAAM,SAAS,CAAA;AACxE,EAAAA,uBAAA,CAAO,WAAA,CAAY,MAAA,CAAO,KAAA,EAAO,aAAa,CAAA;AAC9C,EAAAA,uBAAA,CAAO,WAAA,CAAY,MAAA,CAAO,QAAA,EAAU,qBAAqB,CAAA;AAGzD,EAAA,MAAM,QAAA,GAA8B;AAAA,IACnC,KAAA,EAAO,gBAAA;AAAA,IACP,QAAA,EAAU,mBAAA;AAAA,IACV,UAAA,EAAY,UAAA;AAAA,IACZ,cAAc,CAAC,EAAE,OAAO,IAAA,EAAM,KAAA,EAAO,OAAO;AAAA,GAC7C;AACA,EAAA,MAAM,cAAA,GAAiB,MAAM,kBAAA,CAAmB,aAAA,EAAe;AAAA,IAC9D,gBAAA,EAAkB;AAAA,GAClB,CAAA;AACD,EAAAA,uBAAA,CAAO,eAAA,CAAgB,gBAAgB,QAAQ,CAAA;AAG/C,EAAA,MAAM,QAAA,GAAW,MAAM,kBAAA,CAAmB,kBAAA,EAAoB,EAAE,CAAA;AAChE,EAAAA,uBAAA,CAAO,WAAA,CAAY,QAAA,CAAS,KAAA,EAAO,kBAAkB,CAAA;AACrD,EAAAA,uBAAA,CAAO,WAAA,CAAY,QAAA,CAAS,QAAA,EAAU,qDAAqD,CAAA;AAG3F,EAAA,uBAAA,CAAwB,gBAAA,EAAkB,OAAO,QAAA,EAAU,GAAA,KAAQ;AAClE,IAAA,OAAO;AAAA,MACN,KAAA,EAAO,CAAA,OAAA,EAAU,GAAA,EAAK,QAAA,IAAY,QAAQ,CAAA;AAAA,KAC3C;AAAA,EACD,CAAC,CAAA;AACD,EAAA,MAAM,cAAc,MAAM,kBAAA;AAAA,IACzB,gBAAA;AAAA,IACA,EAAC;AAAA,IACD,EAAE,QAAA,EAAU,KAAA,EAAO,SAAS,KAAA,EAAO,EAAA,EAAI,EAAC;AAAE,GAC3C;AACA,EAAAA,uBAAA,CAAO,WAAA,CAAY,WAAA,CAAY,KAAA,EAAO,YAAY,CAAA;AAElD,EAAA,OAAA,CAAQ,IAAI,2CAAsC,CAAA;AACnD;AAEA,IAAI,oQAAY,KAAQ,CAAA,OAAA,EAAU,QAAQ,IAAA,CAAK,CAAC,CAAC,CAAA,CAAA,EAAI;AACpD,EAAA,eAAA,EAAgB,CAAE,KAAA,CAAM,CAAC,GAAA,KAAQ;AAChC,IAAA,OAAA,CAAQ,KAAA,CAAM,6CAAwC,GAAG,CAAA;AACzD,IAAA,OAAA,CAAQ,KAAK,CAAC,CAAA;AAAA,EACf,CAAC,CAAA;AACF","file":"chunk-7KJHGAZY.cjs","sourcesContent":["import assert from \"node:assert\";\n\nexport interface DisplayDescriptor {\n\ttitle: string;\n\tsubtitle?: string;\n\tstateLabel?: string;\n\tprimaryFacts?: Array<{ label: string; value: string }>;\n}\n\nexport interface CachedDisplayDescriptor extends DisplayDescriptor {\n\t_cachedDisplay?: {\n\t\tsource: \"event_payload\" | \"projection_snapshot\";\n\t\tcursor?: string | null;\n\t\tchecksum?: string;\n\t\tgeneratedAt?: string;\n\t};\n}\n\nexport type DisplayDatabaseClient = any;\n\nexport interface DisplayContext<TDb = DisplayDatabaseClient> {\n\ttenantId: string;\n\tspaceId: string;\n\tdb: TDb;\n}\n\nexport type DisplayRenderer = (\n\tpayload: unknown,\n\tctx?: DisplayContext,\n) => DisplayDescriptor | Promise<DisplayDescriptor>;\n\nexport interface DisplayRendererRegistration {\n\teventType: string;\n\trenderer: DisplayRenderer;\n}\n\nconst registry = new Map<string, DisplayRenderer>();\n\nexport function registerDisplayRenderer(eventType: string, renderer: DisplayRenderer): void {\n\tregistry.set(eventType, renderer);\n}\n\nexport function resolveDisplayRenderer(eventType: string): DisplayRenderer | undefined {\n\treturn registry.get(eventType);\n}\n\nexport function denormalizeDisplaySnapshot(payload: unknown): CachedDisplayDescriptor | undefined {\n\tif (\n\t\tpayload !== null &&\n\t\ttypeof payload === \"object\" &&\n\t\t\"_displaySnapshot\" in payload &&\n\t\tpayload._displaySnapshot !== null &&\n\t\ttypeof payload._displaySnapshot === \"object\" &&\n\t\t\"title\" in payload._displaySnapshot\n\t) {\n\t\treturn payload._displaySnapshot as CachedDisplayDescriptor;\n\t}\n\n\tif (\n\t\tpayload !== null &&\n\t\ttypeof payload === \"object\" &&\n\t\t\"_projectionSnapshot\" in payload &&\n\t\tpayload._projectionSnapshot !== null &&\n\t\ttypeof payload._projectionSnapshot === \"object\" &&\n\t\t\"display\" in payload._projectionSnapshot &&\n\t\tpayload._projectionSnapshot.display !== null &&\n\t\ttypeof payload._projectionSnapshot.display === \"object\" &&\n\t\t\"title\" in payload._projectionSnapshot.display\n\t) {\n\t\tconst snapshot = payload._projectionSnapshot as {\n\t\t\tdisplay: DisplayDescriptor;\n\t\t\teventCursor?: string | null;\n\t\t\tchecksum?: string;\n\t\t\tupdatedAt?: string;\n\t\t};\n\t\treturn {\n\t\t\t...snapshot.display,\n\t\t\t_cachedDisplay: {\n\t\t\t\tsource: \"projection_snapshot\",\n\t\t\t\tcursor: snapshot.eventCursor,\n\t\t\t\tchecksum: snapshot.checksum,\n\t\t\t\tgeneratedAt: snapshot.updatedAt,\n\t\t\t},\n\t\t};\n\t}\n\treturn undefined;\n}\n\nexport async function renderEventDisplay(\n\teventType: string,\n\tpayload: unknown,\n\tctx?: DisplayContext,\n): Promise<CachedDisplayDescriptor> {\n\tconst snapshot = denormalizeDisplaySnapshot(payload);\n\tif (snapshot) {\n\t\treturn snapshot;\n\t}\n\n\tconst renderer = resolveDisplayRenderer(eventType);\n\tif (!renderer) {\n\t\treturn {\n\t\t\ttitle: eventType,\n\t\t\tsubtitle: \"No display renderer registered for this event type.\",\n\t\t};\n\t}\n\n\treturn await renderer(payload, ctx);\n}\n\n// Inline test: verify registry round-trip, snapshot denormalization, and fallback.\nasync function _runInlineTests(): Promise<void> {\n\tconst testEventType = \"__TestEvent__\";\n\n\t// 1. Registry round-trip\n\tregisterDisplayRenderer(testEventType, (payload) => ({\n\t\ttitle: `Test: ${(payload as { name: string }).name}`,\n\t\tsubtitle: \"registered renderer\",\n\t}));\n\n\tconst renderer = resolveDisplayRenderer(testEventType);\n\tassert.ok(renderer, \"resolveDisplayRenderer should return the registered renderer\");\n\n\tconst result = await renderEventDisplay(testEventType, { name: \"Alice\" });\n\tassert.strictEqual(result.title, \"Test: Alice\");\n\tassert.strictEqual(result.subtitle, \"registered renderer\");\n\n\t// 2. Snapshot denormalization takes precedence over renderer\n\tconst snapshot: DisplayDescriptor = {\n\t\ttitle: \"Snapshot Title\",\n\t\tsubtitle: \"snapshot subtitle\",\n\t\tstateLabel: \"archived\",\n\t\tprimaryFacts: [{ label: \"ID\", value: \"123\" }],\n\t};\n\tconst snapshotResult = await renderEventDisplay(testEventType, {\n\t\t_displaySnapshot: snapshot,\n\t});\n\tassert.deepStrictEqual(snapshotResult, snapshot);\n\n\t// 3. Fallback for unregistered event type\n\tconst fallback = await renderEventDisplay(\"__UnknownEvent__\", {});\n\tassert.strictEqual(fallback.title, \"__UnknownEvent__\");\n\tassert.strictEqual(fallback.subtitle, \"No display renderer registered for this event type.\");\n\n\t// 4. Async renderer with context\n\tregisterDisplayRenderer(\"__AsyncEvent__\", async (_payload, ctx) => {\n\t\treturn {\n\t\t\ttitle: `Async: ${ctx?.tenantId ?? \"no-ctx\"}`,\n\t\t};\n\t});\n\tconst asyncResult = await renderEventDisplay(\n\t\t\"__AsyncEvent__\",\n\t\t{},\n\t\t{ tenantId: \"t-1\", spaceId: \"s-1\", db: {} },\n\t);\n\tassert.strictEqual(asyncResult.title, \"Async: t-1\");\n\n\tconsole.log(\"✅ display engine inline tests passed\");\n}\n\nif (import.meta.url === `file://${process.argv[1]}`) {\n\t_runInlineTests().catch((err) => {\n\t\tconsole.error(\"❌ display engine inline tests failed\", err);\n\t\tprocess.exit(1);\n\t});\n}\n"]}