@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.
- package/README.md +105 -61
- package/dist/actions/index.cjs +48 -0
- package/dist/actions/index.cjs.map +1 -0
- package/dist/actions/index.d.cts +116 -0
- package/dist/adapters/index.cjs +28 -0
- package/dist/adapters/index.cjs.map +1 -0
- package/dist/adapters/index.d.cts +83 -0
- package/dist/chunk-7KJHGAZY.cjs +99 -0
- package/dist/chunk-7KJHGAZY.cjs.map +1 -0
- package/dist/chunk-GB2PSZ33.cjs +74 -0
- package/dist/chunk-GB2PSZ33.cjs.map +1 -0
- package/dist/chunk-JAXDEK5Z.cjs +63 -0
- package/dist/chunk-JAXDEK5Z.cjs.map +1 -0
- package/dist/chunk-KQEBNUZI.cjs +70 -0
- package/dist/chunk-KQEBNUZI.cjs.map +1 -0
- package/dist/chunk-LHCSVEFT.cjs +8 -0
- package/dist/chunk-LHCSVEFT.cjs.map +1 -0
- package/dist/chunk-LYX73U3P.cjs +195 -0
- package/dist/chunk-LYX73U3P.cjs.map +1 -0
- package/dist/chunk-LZSE6QGQ.cjs +60 -0
- package/dist/chunk-LZSE6QGQ.cjs.map +1 -0
- package/dist/chunk-NPVIFZQV.cjs +107 -0
- package/dist/chunk-NPVIFZQV.cjs.map +1 -0
- package/dist/chunk-PG5LYTUX.cjs +4 -0
- package/dist/chunk-PG5LYTUX.cjs.map +1 -0
- package/dist/chunk-Q3WYIWCN.cjs +183 -0
- package/dist/chunk-Q3WYIWCN.cjs.map +1 -0
- package/dist/chunk-QETQGAXJ.cjs +35 -0
- package/dist/chunk-QETQGAXJ.cjs.map +1 -0
- package/dist/chunk-SDXDHVTV.cjs +555 -0
- package/dist/chunk-SDXDHVTV.cjs.map +1 -0
- package/dist/chunk-YZKUEC2J.cjs +31 -0
- package/dist/chunk-YZKUEC2J.cjs.map +1 -0
- package/dist/displays/index.cjs +24 -0
- package/dist/displays/index.cjs.map +1 -0
- package/dist/displays/index.d.cts +34 -0
- package/dist/events/index.cjs +36 -0
- package/dist/events/index.cjs.map +1 -0
- package/dist/events/index.d.cts +35 -0
- package/dist/evidence/index.cjs +6 -0
- package/dist/evidence/index.cjs.map +1 -0
- package/dist/evidence/index.d.cts +43 -0
- package/dist/ids/index.cjs +24 -0
- package/dist/ids/index.cjs.map +1 -0
- package/dist/ids/index.d.cts +19 -0
- package/dist/index.cjs +268 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +13 -0
- package/dist/modules/index.cjs +42 -0
- package/dist/modules/index.cjs.map +1 -0
- package/dist/modules/index.d.cts +73 -0
- package/dist/objects/index.cjs +24 -0
- package/dist/objects/index.cjs.map +1 -0
- package/dist/objects/index.d.cts +7 -0
- package/dist/policies/index.cjs +48 -0
- package/dist/policies/index.cjs.map +1 -0
- package/dist/policies/index.d.cts +143 -0
- package/dist/privacy/index.cjs +12 -0
- package/dist/privacy/index.cjs.map +1 -0
- package/dist/privacy/index.d.cts +77 -0
- package/dist/projections/index.cjs +12 -0
- package/dist/projections/index.cjs.map +1 -0
- package/dist/projections/index.d.cts +52 -0
- package/dist/state-machines/index.cjs +32 -0
- package/dist/state-machines/index.cjs.map +1 -0
- package/dist/state-machines/index.d.cts +60 -0
- package/dist/testing/index.cjs +21 -0
- package/dist/testing/index.cjs.map +1 -0
- package/dist/testing/index.d.cts +19 -0
- package/package.json +196 -111
package/README.md
CHANGED
|
@@ -1,104 +1,148 @@
|
|
|
1
1
|
# @fabricorg/platform
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A portable runtime for ontology-based, agent-native business applications. Zero dependencies. Bring your own database client.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
```bash
|
|
6
|
+
npm install @fabricorg/platform
|
|
7
|
+
```
|
|
6
8
|
|
|
7
|
-
|
|
9
|
+
## Hello, action
|
|
8
10
|
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
26
|
+
For a complete walkthrough see https://platform.fabric.pro/docs/getting-started/quickstart.
|
|
18
27
|
|
|
19
|
-
|
|
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
|
-
|
|
30
|
+
You're building a governed business application — not a CRUD app. You need:
|
|
37
31
|
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
+
## What's in the box
|
|
47
41
|
|
|
48
|
-
|
|
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
|
-
|
|
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
|
|
65
|
+
import { registerFabricModules } from "@fabricorg/platform/modules";
|
|
55
66
|
|
|
56
|
-
|
|
57
|
-
namespace: "
|
|
67
|
+
const myModule: FabricModule<MyDbClient> = {
|
|
68
|
+
namespace: "demo",
|
|
58
69
|
version: "1.0.0",
|
|
59
|
-
objectTypes: ["
|
|
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
|
-
|
|
81
|
+
The platform never auto-loads a module. The host app composes them explicitly — no implicit defaults, no globals, no surprises.
|
|
68
82
|
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
94
|
+
`validateActionParameters` is also exposed for preview/dry-run paths but is **not** part of the execute pipeline.
|
|
78
95
|
|
|
79
|
-
## Saga
|
|
96
|
+
## Saga actions
|
|
80
97
|
|
|
81
|
-
Multi-step orchestrations declare `kind: "saga"` on the `ActionDefinition` and ship a `SagaImplementation
|
|
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
|
|
104
|
+
const step1 = await runChild({
|
|
88
105
|
suffix: "step-1",
|
|
89
|
-
actionId: "
|
|
90
|
-
parameters: { ... },
|
|
106
|
+
actionId: "demo.receive_payload",
|
|
107
|
+
parameters: { /* ... */ },
|
|
91
108
|
});
|
|
92
|
-
//
|
|
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
|
-
|
|
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":"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","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"]}
|