@fabricorg/platform 0.1.0 → 0.1.2

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 (55) hide show
  1. package/dist/actions/index.d.ts +3 -1
  2. package/dist/actions/index.js +1 -57
  3. package/dist/actions/index.js.map +1 -1
  4. package/dist/adapters/index.js +1 -99
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/chunk-3A6FU3KV.js +67 -0
  7. package/dist/chunk-3A6FU3KV.js.map +1 -0
  8. package/dist/chunk-3F6FKQZA.js +89 -0
  9. package/dist/chunk-3F6FKQZA.js.map +1 -0
  10. package/dist/chunk-3ZW5USXF.js +101 -0
  11. package/dist/chunk-3ZW5USXF.js.map +1 -0
  12. package/dist/chunk-4YQOGV7H.js +55 -0
  13. package/dist/chunk-4YQOGV7H.js.map +1 -0
  14. package/dist/chunk-6FMW4NF6.js +59 -0
  15. package/dist/chunk-6FMW4NF6.js.map +1 -0
  16. package/dist/chunk-I35B576X.js +30 -0
  17. package/dist/chunk-I35B576X.js.map +1 -0
  18. package/dist/chunk-IFMZPLM4.js +27 -0
  19. package/dist/chunk-IFMZPLM4.js.map +1 -0
  20. package/dist/chunk-JSTGOT6P.js +181 -0
  21. package/dist/chunk-JSTGOT6P.js.map +1 -0
  22. package/dist/chunk-KOC4A7A4.js +544 -0
  23. package/dist/chunk-KOC4A7A4.js.map +1 -0
  24. package/dist/chunk-N24NLCZ6.js +6 -0
  25. package/dist/chunk-N24NLCZ6.js.map +1 -0
  26. package/dist/chunk-OZLZHISS.js +3 -0
  27. package/dist/chunk-OZLZHISS.js.map +1 -0
  28. package/dist/chunk-PXMKCAQI.js +187 -0
  29. package/dist/chunk-PXMKCAQI.js.map +1 -0
  30. package/dist/chunk-RALMUSOX.js +55 -0
  31. package/dist/chunk-RALMUSOX.js.map +1 -0
  32. package/dist/displays/index.js +1 -87
  33. package/dist/displays/index.js.map +1 -1
  34. package/dist/events/index.js +1 -53
  35. package/dist/events/index.js.map +1 -1
  36. package/dist/evidence/index.js +1 -1
  37. package/dist/ids/index.js +1 -53
  38. package/dist/ids/index.js.map +1 -1
  39. package/dist/index.js +13 -1355
  40. package/dist/index.js.map +1 -1
  41. package/dist/modules/index.js +7 -373
  42. package/dist/modules/index.js.map +1 -1
  43. package/dist/objects/index.js +1 -28
  44. package/dist/objects/index.js.map +1 -1
  45. package/dist/policies/index.js +1 -542
  46. package/dist/policies/index.js.map +1 -1
  47. package/dist/privacy/index.js +1 -4
  48. package/dist/privacy/index.js.map +1 -1
  49. package/dist/projections/index.js +1 -179
  50. package/dist/projections/index.js.map +1 -1
  51. package/dist/state-machines/index.js +1 -65
  52. package/dist/state-machines/index.js.map +1 -1
  53. package/dist/testing/index.js +2 -56
  54. package/dist/testing/index.js.map +1 -1
  55. package/package.json +111 -112
@@ -0,0 +1,59 @@
1
+ // actions/index.ts
2
+ var ACTOR_TYPES = [
3
+ "natural_person",
4
+ "agent",
5
+ "system",
6
+ "service_account",
7
+ "external_system",
8
+ "integration"
9
+ ];
10
+ function isActorType(value) {
11
+ return ACTOR_TYPES.includes(value);
12
+ }
13
+ function assertActorType(value) {
14
+ if (!isActorType(value)) {
15
+ throw new Error(`Invalid actorType: ${value}`);
16
+ }
17
+ }
18
+ var registry = /* @__PURE__ */ new Map();
19
+ function assertActionId(actionId) {
20
+ if (!/^[a-z][a-z0-9-]*\.[a-z][a-z0-9_]*$/.test(actionId)) {
21
+ throw new Error(`Invalid action ID: ${actionId}`);
22
+ }
23
+ }
24
+ function registerAction(definition) {
25
+ assertActionId(definition.actionId);
26
+ if (registry.has(definition.actionId)) {
27
+ throw new Error(`Action "${definition.actionId}" is already registered`);
28
+ }
29
+ if (definition.mutatesDomain && definition.emitsEvents.length === 0) {
30
+ throw new Error(
31
+ `Action "${definition.actionId}" mutates domain but emits no events. Every mutating action must emit at least one AssetEvent.`
32
+ );
33
+ }
34
+ registry.set(definition.actionId, definition);
35
+ }
36
+ function resolveAction(actionId) {
37
+ return registry.get(actionId);
38
+ }
39
+ function isRegisteredActionId(actionId) {
40
+ return registry.has(actionId);
41
+ }
42
+ function resolveRegisteredAction(actionId) {
43
+ if (!isRegisteredActionId(actionId)) {
44
+ return void 0;
45
+ }
46
+ return resolveAction(actionId);
47
+ }
48
+ function getRegisteredActionIds() {
49
+ return Array.from(registry.keys());
50
+ }
51
+ function registerActionIfMissing(definition) {
52
+ if (!registry.has(definition.actionId)) {
53
+ registerAction(definition);
54
+ }
55
+ }
56
+
57
+ export { ACTOR_TYPES, assertActionId, assertActorType, getRegisteredActionIds, isActorType, isRegisteredActionId, registerAction, registerActionIfMissing, resolveAction, resolveRegisteredAction };
58
+ //# sourceMappingURL=chunk-6FMW4NF6.js.map
59
+ //# sourceMappingURL=chunk-6FMW4NF6.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../actions/index.ts"],"names":[],"mappings":";AAwBO,IAAM,WAAA,GAAoC;AAAA,EAChD,gBAAA;AAAA,EACA,OAAA;AAAA,EACA,QAAA;AAAA,EACA,iBAAA;AAAA,EACA,iBAAA;AAAA,EACA;AACD;AAEO,SAAS,YAAY,KAAA,EAAmC;AAC9D,EAAA,OAAQ,WAAA,CAAkC,SAAS,KAAK,CAAA;AACzD;AAEO,SAAS,gBAAgB,KAAA,EAA2C;AAC1E,EAAA,IAAI,CAAC,WAAA,CAAY,KAAK,CAAA,EAAG;AACxB,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,KAAK,CAAA,CAAE,CAAA;AAAA,EAC9C;AACD;AAsHA,IAAM,QAAA,uBAAe,GAAA,EAAgC;AAE9C,SAAS,eAAe,QAAA,EAAgD;AAC9E,EAAA,IAAI,CAAC,oCAAA,CAAqC,IAAA,CAAK,QAAQ,CAAA,EAAG;AACzD,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,mBAAA,EAAsB,QAAQ,CAAA,CAAE,CAAA;AAAA,EACjD;AACD;AAEO,SAAS,eAAe,UAAA,EAAoC;AAClE,EAAA,cAAA,CAAe,WAAW,QAAQ,CAAA;AAElC,EAAA,IAAI,QAAA,CAAS,GAAA,CAAI,UAAA,CAAW,QAAQ,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,QAAA,EAAW,UAAA,CAAW,QAAQ,CAAA,uBAAA,CAAyB,CAAA;AAAA,EACxE;AAEA,EAAA,IAAI,UAAA,CAAW,aAAA,IAAiB,UAAA,CAAW,WAAA,CAAY,WAAW,CAAA,EAAG;AACpE,IAAA,MAAM,IAAI,KAAA;AAAA,MACT,CAAA,QAAA,EAAW,WAAW,QAAQ,CAAA,8FAAA;AAAA,KAE/B;AAAA,EACD;AAEA,EAAA,QAAA,CAAS,GAAA,CAAI,UAAA,CAAW,QAAA,EAAU,UAAU,CAAA;AAC7C;AAEO,SAAS,cAAc,QAAA,EAAkD;AAC/E,EAAA,OAAO,QAAA,CAAS,IAAI,QAAQ,CAAA;AAC7B;AAEO,SAAS,qBAAqB,QAAA,EAAwC;AAC5E,EAAA,OAAO,QAAA,CAAS,IAAI,QAAoB,CAAA;AACzC;AAEO,SAAS,wBAAwB,QAAA,EAAgD;AACvF,EAAA,IAAI,CAAC,oBAAA,CAAqB,QAAQ,CAAA,EAAG;AACpC,IAAA,OAAO,MAAA;AAAA,EACR;AAEA,EAAA,OAAO,cAAc,QAAQ,CAAA;AAC9B;AAEO,SAAS,sBAAA,GAAqC;AACpD,EAAA,OAAO,KAAA,CAAM,IAAA,CAAK,QAAA,CAAS,IAAA,EAAM,CAAA;AAClC;AAEO,SAAS,wBAAwB,UAAA,EAAoC;AAC3E,EAAA,IAAI,CAAC,QAAA,CAAS,GAAA,CAAI,UAAA,CAAW,QAAQ,CAAA,EAAG;AACvC,IAAA,cAAA,CAAe,UAAU,CAAA;AAAA,EAC1B;AACD","file":"chunk-6FMW4NF6.js","sourcesContent":["export type ActionId = `${string}.${string}`;\n\nexport interface SchemaParseIssue {\n\tpath: PropertyKey[];\n\tmessage: string;\n}\n\nexport type SchemaParseResult<T = unknown> =\n\t| { success: true; data: T }\n\t| { success: false; error: { issues: SchemaParseIssue[] } };\n\nexport interface SchemaValidator<T = unknown> {\n\tparse(input: unknown): T;\n\tsafeParse(input: unknown): SchemaParseResult<T>;\n}\n\nexport type ActorType =\n\t| \"natural_person\"\n\t| \"agent\"\n\t| \"system\"\n\t| \"service_account\"\n\t| \"external_system\"\n\t| \"integration\";\n\nexport const ACTOR_TYPES: readonly ActorType[] = [\n\t\"natural_person\",\n\t\"agent\",\n\t\"system\",\n\t\"service_account\",\n\t\"external_system\",\n\t\"integration\",\n] as const;\n\nexport function isActorType(value: string): value is ActorType {\n\treturn (ACTOR_TYPES as readonly string[]).includes(value);\n}\n\nexport function assertActorType(value: string): asserts value is ActorType {\n\tif (!isActorType(value)) {\n\t\tthrow new Error(`Invalid actorType: ${value}`);\n\t}\n}\n\nexport type ActionStatus =\n\t| \"pending\"\n\t| \"running\"\n\t| \"completed\"\n\t| \"failed\"\n\t| \"blocked_by_policy\"\n\t| \"waiting_for_approval\"\n\t| \"validation_failed\";\n\nexport interface FabricRuntimeServices {\n\treadonly [serviceName: string]: unknown;\n}\n\nexport interface ActionContext<TDb = unknown> {\n\tactionInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\tactorId: string;\n\tactorType: ActorType;\n\tcorrelationId: string;\n\tcausationId?: string;\n\tdb: TDb;\n\tservices?: FabricRuntimeServices;\n}\n\nexport interface ActionResult {\n\tsuccess: boolean;\n\tdata?: Record<string, unknown>;\n\terror?: string;\n}\n\nexport type ActionHandler<TDb = unknown> = (\n\tctx: ActionContext<TDb>,\n\tparams: unknown,\n) => Promise<ActionResult>;\n\nexport interface AdapterStepRetryPolicy {\n\tidempotent: boolean;\n\tmaxAttempts?: number;\n\tinitialDelayMs?: number;\n\tbackoffMultiplier?: number;\n\tmaxDelayMs?: number;\n}\n\nexport interface AdapterStep {\n\tadapterType: string;\n\toperation: string;\n\tretryPolicy?: AdapterStepRetryPolicy;\n\tgetInput: (\n\t\tparams: unknown,\n\t\thandlerResult: Record<string, unknown>,\n\t) => Record<string, unknown> | undefined;\n}\n\nexport interface ActionStateMachineBinding {\n\tentityType: string;\n\ttargetState: string | ((params: unknown) => string);\n\tgetEntityId: (params: unknown) => string | undefined;\n}\n\nexport interface ActionDefinition<TDb = unknown> {\n\tactionId: ActionId;\n\tnamespace: string;\n\tversion: number;\n\tschema: SchemaValidator;\n\thandler?: ActionHandler<TDb>;\n\trequiredRoles?: string[];\n\trequiredPermissions?: string[];\n\tpolicies?: `${string}.v${number}`[];\n\temitsEvents: string[];\n\tidempotent: boolean;\n\tmutatesDomain: boolean;\n\tstateMachine?: ActionStateMachineBinding;\n\tadapterSteps?: AdapterStep[];\n\t/**\n\t * If \"saga\", the action is a multi-step orchestration. The worker dispatches\n\t * to a registered saga implementation instead of running `handler` directly.\n\t */\n\tkind?: \"atomic\" | \"saga\";\n}\n\nexport interface SagaContext {\n\tactionInvocationId: string;\n\ttenantId: string;\n\tspaceId: string;\n\tparameters: unknown;\n\tsleep?: (delayMs: number) => Promise<void>;\n\tquery?: <T = unknown>(model: string, operation: string, args: unknown) => Promise<T>;\n}\n\nexport interface SagaChildActionInput {\n\tsuffix: string;\n\tactionId: string;\n\tparameters: Record<string, unknown>;\n}\n\nexport type SagaRunChild = (input: SagaChildActionInput) => Promise<Record<string, unknown>>;\n\nexport interface SagaCompletionEvent {\n\teventType: string;\n\tsubjectType: string;\n\tsubjectId: string;\n\tpayload: unknown;\n\teventSchemaVersion?: number;\n}\n\nexport interface SagaCompletion {\n\tresultData: Record<string, unknown>;\n\tevent?: SagaCompletionEvent;\n}\n\nexport type SagaImplementation = (\n\tctx: SagaContext,\n\trunChild: SagaRunChild,\n) => Promise<SagaCompletion>;\n\nconst registry = new Map<ActionId, ActionDefinition>();\n\nexport function assertActionId(actionId: string): asserts actionId is ActionId {\n\tif (!/^[a-z][a-z0-9-]*\\.[a-z][a-z0-9_]*$/.test(actionId)) {\n\t\tthrow new Error(`Invalid action ID: ${actionId}`);\n\t}\n}\n\nexport function registerAction(definition: ActionDefinition): void {\n\tassertActionId(definition.actionId);\n\n\tif (registry.has(definition.actionId)) {\n\t\tthrow new Error(`Action \"${definition.actionId}\" is already registered`);\n\t}\n\n\tif (definition.mutatesDomain && definition.emitsEvents.length === 0) {\n\t\tthrow new Error(\n\t\t\t`Action \"${definition.actionId}\" mutates domain but emits no events. ` +\n\t\t\t\t`Every mutating action must emit at least one AssetEvent.`,\n\t\t);\n\t}\n\n\tregistry.set(definition.actionId, definition);\n}\n\nexport function resolveAction(actionId: ActionId): ActionDefinition | undefined {\n\treturn registry.get(actionId);\n}\n\nexport function isRegisteredActionId(actionId: string): actionId is ActionId {\n\treturn registry.has(actionId as ActionId);\n}\n\nexport function resolveRegisteredAction(actionId: string): ActionDefinition | undefined {\n\tif (!isRegisteredActionId(actionId)) {\n\t\treturn undefined;\n\t}\n\n\treturn resolveAction(actionId);\n}\n\nexport function getRegisteredActionIds(): ActionId[] {\n\treturn Array.from(registry.keys());\n}\n\nexport function registerActionIfMissing(definition: ActionDefinition): void {\n\tif (!registry.has(definition.actionId)) {\n\t\tregisterAction(definition);\n\t}\n}\n"]}
@@ -0,0 +1,30 @@
1
+ // objects/index.ts
2
+ var objectTypeRegistry = /* @__PURE__ */ new Set([
3
+ "AssetEvent",
4
+ "ActionInvocation",
5
+ "PolicyEvaluation",
6
+ "ApprovalRequest",
7
+ "ConsentRecord",
8
+ "AgentRun"
9
+ ]);
10
+ function assertObjectType(type) {
11
+ if (!/^[A-Z][a-zA-Z0-9]*$/.test(type)) {
12
+ throw new Error(
13
+ `Invalid object type: "${type}". Object types must be PascalCase alphanumeric.`
14
+ );
15
+ }
16
+ }
17
+ function registerObjectType(type) {
18
+ assertObjectType(type);
19
+ objectTypeRegistry.add(type);
20
+ }
21
+ function isValidObjectType(type) {
22
+ return objectTypeRegistry.has(type);
23
+ }
24
+ function getRegisteredObjectTypes() {
25
+ return Array.from(objectTypeRegistry);
26
+ }
27
+
28
+ export { assertObjectType, getRegisteredObjectTypes, isValidObjectType, registerObjectType };
29
+ //# sourceMappingURL=chunk-I35B576X.js.map
30
+ //# sourceMappingURL=chunk-I35B576X.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../objects/index.ts"],"names":[],"mappings":";AAAA,IAAM,kBAAA,uBAAyB,GAAA,CAAY;AAAA,EAC1C,YAAA;AAAA,EACA,kBAAA;AAAA,EACA,kBAAA;AAAA,EACA,iBAAA;AAAA,EACA,eAAA;AAAA,EACA;AACD,CAAC,CAAA;AAIM,SAAS,iBAAiB,IAAA,EAAgD;AAChF,EAAA,IAAI,CAAC,qBAAA,CAAsB,IAAA,CAAK,IAAI,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,KAAA;AAAA,MACT,yBAAyB,IAAI,CAAA,gDAAA;AAAA,KAC9B;AAAA,EACD;AACD;AAEO,SAAS,mBAAmB,IAAA,EAAoB;AACtD,EAAA,gBAAA,CAAiB,IAAI,CAAA;AACrB,EAAA,kBAAA,CAAmB,IAAI,IAAI,CAAA;AAC5B;AAEO,SAAS,kBAAkB,IAAA,EAAuB;AACxD,EAAA,OAAO,kBAAA,CAAmB,IAAI,IAAI,CAAA;AACnC;AAEO,SAAS,wBAAA,GAAqC;AACpD,EAAA,OAAO,KAAA,CAAM,KAAK,kBAAkB,CAAA;AACrC","file":"chunk-I35B576X.js","sourcesContent":["const objectTypeRegistry = new Set<string>([\n\t\"AssetEvent\",\n\t\"ActionInvocation\",\n\t\"PolicyEvaluation\",\n\t\"ApprovalRequest\",\n\t\"ConsentRecord\",\n\t\"AgentRun\",\n]);\n\nexport type FabricObjectType = string;\n\nexport function assertObjectType(type: string): asserts type is FabricObjectType {\n\tif (!/^[A-Z][a-zA-Z0-9]*$/.test(type)) {\n\t\tthrow new Error(\n\t\t\t`Invalid object type: \"${type}\". Object types must be PascalCase alphanumeric.`,\n\t\t);\n\t}\n}\n\nexport function registerObjectType(type: string): void {\n\tassertObjectType(type);\n\tobjectTypeRegistry.add(type);\n}\n\nexport function isValidObjectType(type: string): boolean {\n\treturn objectTypeRegistry.has(type);\n}\n\nexport function getRegisteredObjectTypes(): string[] {\n\treturn Array.from(objectTypeRegistry);\n}\n"]}
@@ -0,0 +1,27 @@
1
+ import { AdapterRegistry } from './chunk-3ZW5USXF.js';
2
+
3
+ // testing/index.ts
4
+ function createFakeActionContext(options = {}) {
5
+ return {
6
+ actionInvocationId: options.actionInvocationId ?? "act_test",
7
+ tenantId: options.tenantId ?? "tenant-test",
8
+ spaceId: options.spaceId ?? "space-test",
9
+ actorId: options.actorId ?? "actor-test",
10
+ actorType: options.actorType ?? "system",
11
+ correlationId: options.correlationId ?? "corr-test",
12
+ causationId: options.causationId,
13
+ db: options.db ?? {},
14
+ services: options.services ?? {}
15
+ };
16
+ }
17
+ function createInMemoryAdapterRegistry() {
18
+ return new AdapterRegistry();
19
+ }
20
+ function createDeterministicIdFactory(prefix = "test") {
21
+ let sequence = 0;
22
+ return () => `${prefix}_${String(++sequence).padStart(6, "0")}`;
23
+ }
24
+
25
+ export { createDeterministicIdFactory, createFakeActionContext, createInMemoryAdapterRegistry };
26
+ //# sourceMappingURL=chunk-IFMZPLM4.js.map
27
+ //# sourceMappingURL=chunk-IFMZPLM4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../testing/index.ts"],"names":[],"mappings":";;;AAeO,SAAS,uBAAA,CACf,OAAA,GAAyC,EAAC,EACrB;AACrB,EAAA,OAAO;AAAA,IACN,kBAAA,EAAoB,QAAQ,kBAAA,IAAsB,UAAA;AAAA,IAClD,QAAA,EAAU,QAAQ,QAAA,IAAY,aAAA;AAAA,IAC9B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,OAAA,EAAS,QAAQ,OAAA,IAAW,YAAA;AAAA,IAC5B,SAAA,EAAW,QAAQ,SAAA,IAAa,QAAA;AAAA,IAChC,aAAA,EAAe,QAAQ,aAAA,IAAiB,WAAA;AAAA,IACxC,aAAa,OAAA,CAAQ,WAAA;AAAA,IACrB,EAAA,EAAI,OAAA,CAAQ,EAAA,IAAO,EAAC;AAAA,IACpB,QAAA,EAAU,OAAA,CAAQ,QAAA,IAAY;AAAC,GAChC;AACD;AAEO,SAAS,6BAAA,GAAiD;AAChE,EAAA,OAAO,IAAI,eAAA,EAAgB;AAC5B;AAEO,SAAS,4BAAA,CAA6B,SAAS,MAAA,EAAsB;AAC3E,EAAA,IAAI,QAAA,GAAW,CAAA;AACf,EAAA,OAAO,MAAM,CAAA,EAAG,MAAM,CAAA,CAAA,EAAI,MAAA,CAAO,EAAE,QAAQ,CAAA,CAAE,QAAA,CAAS,CAAA,EAAG,GAAG,CAAC,CAAA,CAAA;AAC9D","file":"chunk-IFMZPLM4.js","sourcesContent":["import { AdapterRegistry } from \"../adapters\";\nimport type { ActionContext, ActorType } from \"../actions\";\n\nexport interface FakeActionContextOptions<TDb = Record<string, never>> {\n\tactionInvocationId?: string;\n\ttenantId?: string;\n\tspaceId?: string;\n\tactorId?: string;\n\tactorType?: ActorType;\n\tcorrelationId?: string;\n\tcausationId?: string;\n\tdb?: TDb;\n\tservices?: Record<string, unknown>;\n}\n\nexport function createFakeActionContext<TDb = Record<string, never>>(\n\toptions: FakeActionContextOptions<TDb> = {},\n): ActionContext<TDb> {\n\treturn {\n\t\tactionInvocationId: options.actionInvocationId ?? \"act_test\",\n\t\ttenantId: options.tenantId ?? \"tenant-test\",\n\t\tspaceId: options.spaceId ?? \"space-test\",\n\t\tactorId: options.actorId ?? \"actor-test\",\n\t\tactorType: options.actorType ?? \"system\",\n\t\tcorrelationId: options.correlationId ?? \"corr-test\",\n\t\tcausationId: options.causationId,\n\t\tdb: options.db ?? ({} as TDb),\n\t\tservices: options.services ?? {},\n\t};\n}\n\nexport function createInMemoryAdapterRegistry(): AdapterRegistry {\n\treturn new AdapterRegistry();\n}\n\nexport function createDeterministicIdFactory(prefix = \"test\"): () => string {\n\tlet sequence = 0;\n\treturn () => `${prefix}_${String(++sequence).padStart(6, \"0\")}`;\n}\n"]}
@@ -0,0 +1,181 @@
1
+ // projections/index.ts
2
+ async function replayEvents({
3
+ events,
4
+ scope,
5
+ initialState,
6
+ snapshot,
7
+ applyEvent
8
+ }) {
9
+ const warnings = [];
10
+ const scopedEvents = filterEventsByScope(await collectEvents(events), scope, warnings);
11
+ const orderedEvents = sortEvents(scopedEvents, isSubjectScoped(scope));
12
+ const uniqueEvents = removeDuplicateEvents(orderedEvents, warnings);
13
+ const resumableEvents = applyCursor(uniqueEvents, snapshot, isSubjectScoped(scope), warnings);
14
+ let state = snapshot?.snapshotData ?? initialState;
15
+ const appliedEvents = [];
16
+ let eventCursor = snapshot?.eventCursor ?? null;
17
+ let eventSequence = snapshot?.eventSequence ?? 0;
18
+ for (const event of resumableEvents) {
19
+ state = await applyEvent(state, event);
20
+ appliedEvents.push(event);
21
+ eventCursor = event.id;
22
+ eventSequence = event.sequence;
23
+ }
24
+ return {
25
+ state,
26
+ appliedEvents,
27
+ warnings,
28
+ eventCursor,
29
+ eventSequence
30
+ };
31
+ }
32
+ async function collectEvents(events) {
33
+ const collected = [];
34
+ if (Symbol.asyncIterator in events) {
35
+ for await (const event of events) {
36
+ collected.push(event);
37
+ }
38
+ return collected;
39
+ }
40
+ for (const event of events) {
41
+ collected.push(event);
42
+ }
43
+ return collected;
44
+ }
45
+ function filterEventsByScope(events, scope, warnings) {
46
+ if (scope.subjectType && !scope.subjectId || !scope.subjectType && scope.subjectId) {
47
+ warnings.push({
48
+ code: "subject_scope_incomplete",
49
+ message: "Both subjectType and subjectId are required for subject-scoped replay.",
50
+ subjectType: scope.subjectType,
51
+ subjectId: scope.subjectId
52
+ });
53
+ }
54
+ return events.filter((event) => {
55
+ if (event.tenantId !== scope.tenantId || event.spaceId !== scope.spaceId) {
56
+ return false;
57
+ }
58
+ if (scope.subjectType && event.subjectType !== scope.subjectType) {
59
+ return false;
60
+ }
61
+ if (scope.subjectId && event.subjectId !== scope.subjectId) {
62
+ return false;
63
+ }
64
+ return true;
65
+ });
66
+ }
67
+ function removeDuplicateEvents(events, warnings) {
68
+ const seenEventIds = /* @__PURE__ */ new Set();
69
+ const uniqueEvents = [];
70
+ for (const event of events) {
71
+ if (seenEventIds.has(event.id)) {
72
+ warnings.push({
73
+ code: "duplicate_event",
74
+ message: `Duplicate event ${event.id} ignored during replay.`,
75
+ eventId: event.id,
76
+ subjectType: event.subjectType,
77
+ subjectId: event.subjectId
78
+ });
79
+ continue;
80
+ }
81
+ seenEventIds.add(event.id);
82
+ uniqueEvents.push(event);
83
+ }
84
+ recordMissingSequenceWarnings(uniqueEvents, warnings);
85
+ return uniqueEvents;
86
+ }
87
+ function recordMissingSequenceWarnings(events, warnings) {
88
+ const lastSequenceBySubject = /* @__PURE__ */ new Map();
89
+ for (const event of events) {
90
+ const subjectKey = `${event.subjectType}:${event.subjectId}`;
91
+ const previousSequence = lastSequenceBySubject.get(subjectKey);
92
+ const expectedSequence = previousSequence === void 0 ? event.sequence : previousSequence + 1;
93
+ if (previousSequence !== void 0 && event.sequence > expectedSequence) {
94
+ warnings.push({
95
+ code: "missing_sequence",
96
+ message: `Missing event sequence for ${subjectKey}: expected ${expectedSequence}, received ${event.sequence}.`,
97
+ eventId: event.id,
98
+ subjectType: event.subjectType,
99
+ subjectId: event.subjectId,
100
+ expectedSequence,
101
+ actualSequence: event.sequence
102
+ });
103
+ }
104
+ lastSequenceBySubject.set(subjectKey, Math.max(previousSequence ?? event.sequence, event.sequence));
105
+ }
106
+ }
107
+ function applyCursor(events, snapshot, subjectScoped, warnings) {
108
+ const eventCursor = snapshot?.eventCursor;
109
+ if (eventCursor) {
110
+ const cursorIndex = events.findIndex((event) => event.id === eventCursor);
111
+ if (cursorIndex >= 0) {
112
+ return events.slice(cursorIndex + 1);
113
+ }
114
+ warnings.push({
115
+ code: "cursor_not_found",
116
+ message: `Snapshot cursor ${eventCursor} was not found in the scoped event stream.`,
117
+ eventId: eventCursor
118
+ });
119
+ }
120
+ if (subjectScoped && snapshot?.eventSequence !== void 0 && snapshot.eventSequence !== null) {
121
+ return events.filter((event) => event.sequence > snapshot.eventSequence);
122
+ }
123
+ return events;
124
+ }
125
+ function sortEvents(events, subjectScoped) {
126
+ return [...events].sort(
127
+ (first, second) => subjectScoped ? compareSubjectEvents(first, second) : compareGlobalEvents(first, second)
128
+ );
129
+ }
130
+ function compareSubjectEvents(first, second) {
131
+ return compareValues(
132
+ [first.sequence, toTimestamp(first.recordedAt), toTimestamp(first.occurredAt), first.id],
133
+ [second.sequence, toTimestamp(second.recordedAt), toTimestamp(second.occurredAt), second.id]
134
+ );
135
+ }
136
+ function compareGlobalEvents(first, second) {
137
+ return compareValues(
138
+ [
139
+ toTimestamp(first.recordedAt),
140
+ toTimestamp(first.occurredAt),
141
+ first.actionInvocationId,
142
+ first.correlationId,
143
+ first.subjectType,
144
+ first.subjectId,
145
+ first.sequence,
146
+ first.id
147
+ ],
148
+ [
149
+ toTimestamp(second.recordedAt),
150
+ toTimestamp(second.occurredAt),
151
+ second.actionInvocationId,
152
+ second.correlationId,
153
+ second.subjectType,
154
+ second.subjectId,
155
+ second.sequence,
156
+ second.id
157
+ ]
158
+ );
159
+ }
160
+ function compareValues(firstValues, secondValues) {
161
+ for (const [index, firstValue] of firstValues.entries()) {
162
+ const secondValue = secondValues[index];
163
+ if (firstValue < secondValue) {
164
+ return -1;
165
+ }
166
+ if (firstValue > secondValue) {
167
+ return 1;
168
+ }
169
+ }
170
+ return 0;
171
+ }
172
+ function isSubjectScoped(scope) {
173
+ return Boolean(scope.subjectType && scope.subjectId);
174
+ }
175
+ function toTimestamp(value) {
176
+ return value instanceof Date ? value.getTime() : new Date(value).getTime();
177
+ }
178
+
179
+ export { replayEvents };
180
+ //# sourceMappingURL=chunk-JSTGOT6P.js.map
181
+ //# sourceMappingURL=chunk-JSTGOT6P.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../projections/index.ts"],"names":[],"mappings":"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","file":"chunk-JSTGOT6P.js","sourcesContent":["export interface ReplayableAssetEvent<Payload = unknown> {\n\tid: string;\n\ttenantId: string;\n\tspaceId: string;\n\teventType: string;\n\tsubjectType: string;\n\tsubjectId: string;\n\tpayload: Payload;\n\tsequence: number;\n\toccurredAt: Date | string;\n\trecordedAt: Date | string;\n\tactionInvocationId: string;\n\tcorrelationId: string;\n}\n\nexport interface ReplayScope {\n\ttenantId: string;\n\tspaceId: string;\n\tsubjectType?: string;\n\tsubjectId?: string;\n}\n\nexport interface ReplaySnapshot<State = unknown> {\n\tsnapshotData: State;\n\teventCursor?: string | null;\n\teventSequence?: number | null;\n}\n\nexport type ReplayWarningCode =\n\t| \"cursor_not_found\"\n\t| \"duplicate_event\"\n\t| \"missing_sequence\"\n\t| \"subject_scope_incomplete\";\n\nexport interface ReplayWarning {\n\tcode: ReplayWarningCode;\n\tmessage: string;\n\teventId?: string;\n\tsubjectType?: string;\n\tsubjectId?: string;\n\texpectedSequence?: number;\n\tactualSequence?: number;\n}\n\nexport interface ReplayEventsOptions<State, Event extends ReplayableAssetEvent = ReplayableAssetEvent> {\n\tevents: Iterable<Event> | AsyncIterable<Event>;\n\tscope: ReplayScope;\n\tinitialState: State;\n\tsnapshot?: ReplaySnapshot<State> | null;\n\tapplyEvent: (state: State, event: Event) => State | Promise<State>;\n}\n\nexport interface ReplayEventsResult<State, Event extends ReplayableAssetEvent = ReplayableAssetEvent> {\n\tstate: State;\n\tappliedEvents: Event[];\n\twarnings: ReplayWarning[];\n\teventCursor: string | null;\n\teventSequence: number;\n}\n\nexport async function replayEvents<State, Event extends ReplayableAssetEvent = ReplayableAssetEvent>({\n\tevents,\n\tscope,\n\tinitialState,\n\tsnapshot,\n\tapplyEvent,\n}: ReplayEventsOptions<State, Event>): Promise<ReplayEventsResult<State, Event>> {\n\tconst warnings: ReplayWarning[] = [];\n\tconst scopedEvents = filterEventsByScope(await collectEvents(events), scope, warnings);\n\tconst orderedEvents = sortEvents(scopedEvents, isSubjectScoped(scope));\n\tconst uniqueEvents = removeDuplicateEvents(orderedEvents, warnings);\n\tconst resumableEvents = applyCursor(uniqueEvents, snapshot, isSubjectScoped(scope), warnings);\n\n\tlet state = snapshot?.snapshotData ?? initialState;\n\tconst appliedEvents: Event[] = [];\n\tlet eventCursor = snapshot?.eventCursor ?? null;\n\tlet eventSequence = snapshot?.eventSequence ?? 0;\n\n\tfor (const event of resumableEvents) {\n\t\tstate = await applyEvent(state, event);\n\t\tappliedEvents.push(event);\n\t\teventCursor = event.id;\n\t\teventSequence = event.sequence;\n\t}\n\n\treturn {\n\t\tstate,\n\t\tappliedEvents,\n\t\twarnings,\n\t\teventCursor,\n\t\teventSequence,\n\t};\n}\n\nasync function collectEvents<Event extends ReplayableAssetEvent>(\n\tevents: Iterable<Event> | AsyncIterable<Event>,\n): Promise<Event[]> {\n\tconst collected: Event[] = [];\n\n\tif (Symbol.asyncIterator in events) {\n\t\tfor await (const event of events) {\n\t\t\tcollected.push(event);\n\t\t}\n\n\t\treturn collected;\n\t}\n\n\tfor (const event of events) {\n\t\tcollected.push(event);\n\t}\n\n\treturn collected;\n}\n\nfunction filterEventsByScope<Event extends ReplayableAssetEvent>(\n\tevents: Event[],\n\tscope: ReplayScope,\n\twarnings: ReplayWarning[],\n): Event[] {\n\tif ((scope.subjectType && !scope.subjectId) || (!scope.subjectType && scope.subjectId)) {\n\t\twarnings.push({\n\t\t\tcode: \"subject_scope_incomplete\",\n\t\t\tmessage: \"Both subjectType and subjectId are required for subject-scoped replay.\",\n\t\t\tsubjectType: scope.subjectType,\n\t\t\tsubjectId: scope.subjectId,\n\t\t});\n\t}\n\n\treturn events.filter((event) => {\n\t\tif (event.tenantId !== scope.tenantId || event.spaceId !== scope.spaceId) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (scope.subjectType && event.subjectType !== scope.subjectType) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (scope.subjectId && event.subjectId !== scope.subjectId) {\n\t\t\treturn false;\n\t\t}\n\n\t\treturn true;\n\t});\n}\n\nfunction removeDuplicateEvents<Event extends ReplayableAssetEvent>(\n\tevents: Event[],\n\twarnings: ReplayWarning[],\n): Event[] {\n\tconst seenEventIds = new Set<string>();\n\tconst uniqueEvents: Event[] = [];\n\n\tfor (const event of events) {\n\t\tif (seenEventIds.has(event.id)) {\n\t\t\twarnings.push({\n\t\t\t\tcode: \"duplicate_event\",\n\t\t\t\tmessage: `Duplicate event ${event.id} ignored during replay.`,\n\t\t\t\teventId: event.id,\n\t\t\t\tsubjectType: event.subjectType,\n\t\t\t\tsubjectId: event.subjectId,\n\t\t\t});\n\t\t\tcontinue;\n\t\t}\n\n\t\tseenEventIds.add(event.id);\n\t\tuniqueEvents.push(event);\n\t}\n\n\trecordMissingSequenceWarnings(uniqueEvents, warnings);\n\n\treturn uniqueEvents;\n}\n\nfunction recordMissingSequenceWarnings<Event extends ReplayableAssetEvent>(\n\tevents: Event[],\n\twarnings: ReplayWarning[],\n): void {\n\tconst lastSequenceBySubject = new Map<string, number>();\n\n\tfor (const event of events) {\n\t\tconst subjectKey = `${event.subjectType}:${event.subjectId}`;\n\t\tconst previousSequence = lastSequenceBySubject.get(subjectKey);\n\t\tconst expectedSequence = previousSequence === undefined ? event.sequence : previousSequence + 1;\n\n\t\tif (previousSequence !== undefined && event.sequence > expectedSequence) {\n\t\t\twarnings.push({\n\t\t\t\tcode: \"missing_sequence\",\n\t\t\t\tmessage: `Missing event sequence for ${subjectKey}: expected ${expectedSequence}, received ${event.sequence}.`,\n\t\t\t\teventId: event.id,\n\t\t\t\tsubjectType: event.subjectType,\n\t\t\t\tsubjectId: event.subjectId,\n\t\t\t\texpectedSequence,\n\t\t\t\tactualSequence: event.sequence,\n\t\t\t});\n\t\t}\n\n\t\tlastSequenceBySubject.set(subjectKey, Math.max(previousSequence ?? event.sequence, event.sequence));\n\t}\n}\n\nfunction applyCursor<Event extends ReplayableAssetEvent>(\n\tevents: Event[],\n\tsnapshot: ReplaySnapshot | null | undefined,\n\tsubjectScoped: boolean,\n\twarnings: ReplayWarning[],\n): Event[] {\n\tconst eventCursor = snapshot?.eventCursor;\n\n\tif (eventCursor) {\n\t\tconst cursorIndex = events.findIndex((event) => event.id === eventCursor);\n\n\t\tif (cursorIndex >= 0) {\n\t\t\treturn events.slice(cursorIndex + 1);\n\t\t}\n\n\t\twarnings.push({\n\t\t\tcode: \"cursor_not_found\",\n\t\t\tmessage: `Snapshot cursor ${eventCursor} was not found in the scoped event stream.`,\n\t\t\teventId: eventCursor,\n\t\t});\n\t}\n\n\tif (subjectScoped && snapshot?.eventSequence !== undefined && snapshot.eventSequence !== null) {\n\t\treturn events.filter((event) => event.sequence > snapshot.eventSequence!);\n\t}\n\n\treturn events;\n}\n\nfunction sortEvents<Event extends ReplayableAssetEvent>(events: Event[], subjectScoped: boolean): Event[] {\n\treturn [...events].sort((first, second) =>\n\t\tsubjectScoped ? compareSubjectEvents(first, second) : compareGlobalEvents(first, second),\n\t);\n}\n\nfunction compareSubjectEvents(first: ReplayableAssetEvent, second: ReplayableAssetEvent): number {\n\treturn compareValues(\n\t\t[first.sequence, toTimestamp(first.recordedAt), toTimestamp(first.occurredAt), first.id],\n\t\t[second.sequence, toTimestamp(second.recordedAt), toTimestamp(second.occurredAt), second.id],\n\t);\n}\n\nfunction compareGlobalEvents(first: ReplayableAssetEvent, second: ReplayableAssetEvent): number {\n\treturn compareValues(\n\t\t[\n\t\t\ttoTimestamp(first.recordedAt),\n\t\t\ttoTimestamp(first.occurredAt),\n\t\t\tfirst.actionInvocationId,\n\t\t\tfirst.correlationId,\n\t\t\tfirst.subjectType,\n\t\t\tfirst.subjectId,\n\t\t\tfirst.sequence,\n\t\t\tfirst.id,\n\t\t],\n\t\t[\n\t\t\ttoTimestamp(second.recordedAt),\n\t\t\ttoTimestamp(second.occurredAt),\n\t\t\tsecond.actionInvocationId,\n\t\t\tsecond.correlationId,\n\t\t\tsecond.subjectType,\n\t\t\tsecond.subjectId,\n\t\t\tsecond.sequence,\n\t\t\tsecond.id,\n\t\t],\n\t);\n}\n\nfunction compareValues(firstValues: Array<number | string>, secondValues: Array<number | string>): number {\n\tfor (const [index, firstValue] of firstValues.entries()) {\n\t\tconst secondValue = secondValues[index];\n\n\t\tif (firstValue < secondValue) {\n\t\t\treturn -1;\n\t\t}\n\n\t\tif (firstValue > secondValue) {\n\t\t\treturn 1;\n\t\t}\n\t}\n\n\treturn 0;\n}\n\nfunction isSubjectScoped(scope: ReplayScope): boolean {\n\treturn Boolean(scope.subjectType && scope.subjectId);\n}\n\nfunction toTimestamp(value: Date | string): number {\n\treturn value instanceof Date ? value.getTime() : new Date(value).getTime();\n}\n"]}