@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.
- package/dist/actions/index.d.ts +3 -1
- package/dist/actions/index.js +1 -57
- package/dist/actions/index.js.map +1 -1
- package/dist/adapters/index.js +1 -99
- package/dist/adapters/index.js.map +1 -1
- package/dist/chunk-3A6FU3KV.js +67 -0
- package/dist/chunk-3A6FU3KV.js.map +1 -0
- package/dist/chunk-3F6FKQZA.js +89 -0
- package/dist/chunk-3F6FKQZA.js.map +1 -0
- package/dist/chunk-3ZW5USXF.js +101 -0
- package/dist/chunk-3ZW5USXF.js.map +1 -0
- package/dist/chunk-4YQOGV7H.js +55 -0
- package/dist/chunk-4YQOGV7H.js.map +1 -0
- package/dist/chunk-6FMW4NF6.js +59 -0
- package/dist/chunk-6FMW4NF6.js.map +1 -0
- package/dist/chunk-I35B576X.js +30 -0
- package/dist/chunk-I35B576X.js.map +1 -0
- package/dist/chunk-IFMZPLM4.js +27 -0
- package/dist/chunk-IFMZPLM4.js.map +1 -0
- package/dist/chunk-JSTGOT6P.js +181 -0
- package/dist/chunk-JSTGOT6P.js.map +1 -0
- package/dist/chunk-KOC4A7A4.js +544 -0
- package/dist/chunk-KOC4A7A4.js.map +1 -0
- package/dist/chunk-N24NLCZ6.js +6 -0
- package/dist/chunk-N24NLCZ6.js.map +1 -0
- package/dist/chunk-OZLZHISS.js +3 -0
- package/dist/chunk-OZLZHISS.js.map +1 -0
- package/dist/chunk-PXMKCAQI.js +187 -0
- package/dist/chunk-PXMKCAQI.js.map +1 -0
- package/dist/chunk-RALMUSOX.js +55 -0
- package/dist/chunk-RALMUSOX.js.map +1 -0
- package/dist/displays/index.js +1 -87
- package/dist/displays/index.js.map +1 -1
- package/dist/events/index.js +1 -53
- package/dist/events/index.js.map +1 -1
- package/dist/evidence/index.js +1 -1
- package/dist/ids/index.js +1 -53
- package/dist/ids/index.js.map +1 -1
- package/dist/index.js +13 -1355
- package/dist/index.js.map +1 -1
- package/dist/modules/index.js +7 -373
- package/dist/modules/index.js.map +1 -1
- package/dist/objects/index.js +1 -28
- package/dist/objects/index.js.map +1 -1
- package/dist/policies/index.js +1 -542
- package/dist/policies/index.js.map +1 -1
- package/dist/privacy/index.js +1 -4
- package/dist/privacy/index.js.map +1 -1
- package/dist/projections/index.js +1 -179
- package/dist/projections/index.js.map +1 -1
- package/dist/state-machines/index.js +1 -65
- package/dist/state-machines/index.js.map +1 -1
- package/dist/testing/index.js +2 -56
- package/dist/testing/index.js.map +1 -1
- package/package.json +111 -112
package/dist/actions/index.d.ts
CHANGED
|
@@ -64,7 +64,7 @@ interface ActionDefinition<TDb = unknown> {
|
|
|
64
64
|
namespace: string;
|
|
65
65
|
version: number;
|
|
66
66
|
schema: SchemaValidator;
|
|
67
|
-
handler
|
|
67
|
+
handler?: ActionHandler<TDb>;
|
|
68
68
|
requiredRoles?: string[];
|
|
69
69
|
requiredPermissions?: string[];
|
|
70
70
|
policies?: `${string}.v${number}`[];
|
|
@@ -84,6 +84,8 @@ interface SagaContext {
|
|
|
84
84
|
tenantId: string;
|
|
85
85
|
spaceId: string;
|
|
86
86
|
parameters: unknown;
|
|
87
|
+
sleep?: (delayMs: number) => Promise<void>;
|
|
88
|
+
query?: <T = unknown>(model: string, operation: string, args: unknown) => Promise<T>;
|
|
87
89
|
}
|
|
88
90
|
interface SagaChildActionInput {
|
|
89
91
|
suffix: string;
|
package/dist/actions/index.js
CHANGED
|
@@ -1,59 +1,3 @@
|
|
|
1
|
-
|
|
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 };
|
|
1
|
+
export { ACTOR_TYPES, assertActionId, assertActorType, getRegisteredActionIds, isActorType, isRegisteredActionId, registerAction, registerActionIfMissing, resolveAction, resolveRegisteredAction } from '../chunk-6FMW4NF6.js';
|
|
58
2
|
//# sourceMappingURL=index.js.map
|
|
59
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":[
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/dist/adapters/index.js
CHANGED
|
@@ -1,101 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
function adapterKey(adapterType, operation) {
|
|
3
|
-
return `${adapterType}:${operation}`;
|
|
4
|
-
}
|
|
5
|
-
var MissingAdapterRegistrationError = class extends Error {
|
|
6
|
-
constructor(adapterType, operation) {
|
|
7
|
-
super(`No adapter registered for adapterType "${adapterType}" and operation "${operation}"`);
|
|
8
|
-
this.name = "MissingAdapterRegistrationError";
|
|
9
|
-
}
|
|
10
|
-
};
|
|
11
|
-
var AdapterRegistry = class {
|
|
12
|
-
adapters = /* @__PURE__ */ new Map();
|
|
13
|
-
register(adapter) {
|
|
14
|
-
const key = adapterKey(adapter.adapterType, adapter.operation);
|
|
15
|
-
if (this.adapters.has(key)) {
|
|
16
|
-
throw new Error(
|
|
17
|
-
`Adapter already registered for adapterType "${adapter.adapterType}" and operation "${adapter.operation}"`
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
this.adapters.set(key, adapter);
|
|
21
|
-
}
|
|
22
|
-
resolve(adapterType, operation) {
|
|
23
|
-
return this.adapters.get(adapterKey(adapterType, operation));
|
|
24
|
-
}
|
|
25
|
-
require(adapterType, operation) {
|
|
26
|
-
const adapter = this.resolve(adapterType, operation);
|
|
27
|
-
if (!adapter) {
|
|
28
|
-
throw new MissingAdapterRegistrationError(adapterType, operation);
|
|
29
|
-
}
|
|
30
|
-
return adapter;
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
var DEFAULT_RETRY_POLICY = {
|
|
34
|
-
maxAttempts: 3,
|
|
35
|
-
initialDelayMs: 100,
|
|
36
|
-
backoffMultiplier: 2,
|
|
37
|
-
maxDelayMs: 1e3
|
|
38
|
-
};
|
|
39
|
-
var NO_RETRY_POLICY = {
|
|
40
|
-
idempotent: false,
|
|
41
|
-
maxAttempts: 1,
|
|
42
|
-
initialDelayMs: 0,
|
|
43
|
-
backoffMultiplier: 1,
|
|
44
|
-
maxDelayMs: 0
|
|
45
|
-
};
|
|
46
|
-
function resolveAdapterRetryPolicy(policy, defaultIdempotent) {
|
|
47
|
-
const idempotent = policy?.idempotent ?? defaultIdempotent;
|
|
48
|
-
if (!idempotent) {
|
|
49
|
-
return NO_RETRY_POLICY;
|
|
50
|
-
}
|
|
51
|
-
return {
|
|
52
|
-
idempotent: true,
|
|
53
|
-
maxAttempts: Math.max(1, Math.min(policy?.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts, 5)),
|
|
54
|
-
initialDelayMs: Math.max(0, policy?.initialDelayMs ?? DEFAULT_RETRY_POLICY.initialDelayMs),
|
|
55
|
-
backoffMultiplier: Math.max(
|
|
56
|
-
1,
|
|
57
|
-
policy?.backoffMultiplier ?? DEFAULT_RETRY_POLICY.backoffMultiplier
|
|
58
|
-
),
|
|
59
|
-
maxDelayMs: Math.max(0, policy?.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs)
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
function getRetryDelayMs(attempt, policy) {
|
|
63
|
-
const exponentialDelay = policy.initialDelayMs * policy.backoffMultiplier ** Math.max(0, attempt - 1);
|
|
64
|
-
return Math.min(exponentialDelay, policy.maxDelayMs);
|
|
65
|
-
}
|
|
66
|
-
async function executeWithAdapterRetry(options) {
|
|
67
|
-
const policy = resolveAdapterRetryPolicy(options.policy, options.defaultIdempotent);
|
|
68
|
-
const sleep = options.sleep ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
69
|
-
let lastError;
|
|
70
|
-
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
71
|
-
try {
|
|
72
|
-
const result = await options.execute(attempt, policy.maxAttempts);
|
|
73
|
-
if (options.isSuccessful(result)) {
|
|
74
|
-
await options.onAttempt?.({ attempt, result, willRetry: false });
|
|
75
|
-
return result;
|
|
76
|
-
}
|
|
77
|
-
lastError = options.getError(result) ?? "Adapter returned unsuccessful result";
|
|
78
|
-
const willRetry = policy.idempotent && attempt < policy.maxAttempts;
|
|
79
|
-
const nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : void 0;
|
|
80
|
-
await options.onAttempt?.({ attempt, result, error: lastError, willRetry, nextDelayMs });
|
|
81
|
-
if (!willRetry) {
|
|
82
|
-
return result;
|
|
83
|
-
}
|
|
84
|
-
await sleep(nextDelayMs ?? 0);
|
|
85
|
-
} catch (error) {
|
|
86
|
-
lastError = error instanceof Error ? error.message : String(error);
|
|
87
|
-
const willRetry = policy.idempotent && attempt < policy.maxAttempts;
|
|
88
|
-
const nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : void 0;
|
|
89
|
-
await options.onAttempt?.({ attempt, error: lastError, willRetry, nextDelayMs });
|
|
90
|
-
if (!willRetry) {
|
|
91
|
-
throw error;
|
|
92
|
-
}
|
|
93
|
-
await sleep(nextDelayMs ?? 0);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
throw new Error(lastError ?? "Adapter retry exhausted");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
export { AdapterRegistry, MissingAdapterRegistrationError, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy };
|
|
1
|
+
export { AdapterRegistry, MissingAdapterRegistrationError, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy } from '../chunk-3ZW5USXF.js';
|
|
100
2
|
//# sourceMappingURL=index.js.map
|
|
101
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../adapters/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["export interface AdapterExecutionContext {\n\ttenantId: string;\n\tspaceId: string;\n\tactionInvocationId: string;\n\tadapterInvocationId: string;\n\tcorrelationId: string;\n\tcausationId?: string;\n\tattempt: number;\n\tmaxAttempts: number;\n}\n\nexport interface AdapterExecutionResult {\n\tsuccess: boolean;\n\tcorrelationId?: string;\n\terror?: string;\n}\n\nexport type AdapterExecutor = (\n\tinput: Record<string, unknown>,\n\tcontext: AdapterExecutionContext,\n) => Promise<AdapterExecutionResult>;\n\nexport interface AdapterRetryPolicy {\n\t/** External calls must explicitly opt into retry by being idempotent. */\n\tidempotent: boolean;\n\t/** Total attempts including the first call. Non-idempotent policies are forced to 1. */\n\tmaxAttempts?: number;\n\t/** First backoff delay in milliseconds. */\n\tinitialDelayMs?: number;\n\t/** Exponential multiplier applied after each failed attempt. */\n\tbackoffMultiplier?: number;\n\t/** Upper bound for any single retry delay in milliseconds. */\n\tmaxDelayMs?: number;\n}\n\nexport interface ResolvedAdapterRetryPolicy {\n\tidempotent: boolean;\n\tmaxAttempts: number;\n\tinitialDelayMs: number;\n\tbackoffMultiplier: number;\n\tmaxDelayMs: number;\n}\n\nexport interface AdapterCircuitBreakerConfig {\n\tmode: \"monitor\" | \"enforce\";\n\tfailureThreshold: number;\n\twindowMs: number;\n\tcooldownMs: number;\n\tminimumSamples?: number;\n}\n\nexport interface AdapterImplementation {\n\tadapterType: string;\n\toperation: string;\n\tvendor: string;\n\t/** Whether this adapter operation can be safely retried by default. */\n\tidempotent: boolean;\n\tretryPolicy?: AdapterRetryPolicy;\n\t/** Defaults to monitor-only; enforce mode must be explicitly configured. */\n\tcircuitBreaker?: AdapterCircuitBreakerConfig;\n\texecute: AdapterExecutor;\n}\n\nexport interface AdapterAttempt<T> {\n\tattempt: number;\n\tresult?: T;\n\terror?: string;\n\twillRetry: boolean;\n\tnextDelayMs?: number;\n}\n\nexport interface ExecuteWithRetryOptions<T> {\n\tpolicy?: AdapterRetryPolicy;\n\tdefaultIdempotent: boolean;\n\texecute: (attempt: number, maxAttempts: number) => Promise<T>;\n\tisSuccessful: (result: T) => boolean;\n\tgetError: (result: T) => string | undefined;\n\tsleep?: (delayMs: number) => Promise<void>;\n\tonAttempt?: (attempt: AdapterAttempt<T>) => Promise<void> | void;\n}\n\nfunction adapterKey(adapterType: string, operation: string): string {\n\treturn `${adapterType}:${operation}`;\n}\n\nexport class MissingAdapterRegistrationError extends Error {\n\tconstructor(adapterType: string, operation: string) {\n\t\tsuper(`No adapter registered for adapterType \"${adapterType}\" and operation \"${operation}\"`);\n\t\tthis.name = \"MissingAdapterRegistrationError\";\n\t}\n}\n\nexport class AdapterRegistry {\n\tprivate readonly adapters = new Map<string, AdapterImplementation>();\n\n\tregister(adapter: AdapterImplementation): void {\n\t\tconst key = adapterKey(adapter.adapterType, adapter.operation);\n\t\tif (this.adapters.has(key)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Adapter already registered for adapterType \"${adapter.adapterType}\" and operation \"${adapter.operation}\"`,\n\t\t\t);\n\t\t}\n\n\t\tthis.adapters.set(key, adapter);\n\t}\n\n\tresolve(adapterType: string, operation: string): AdapterImplementation | undefined {\n\t\treturn this.adapters.get(adapterKey(adapterType, operation));\n\t}\n\n\trequire(adapterType: string, operation: string): AdapterImplementation {\n\t\tconst adapter = this.resolve(adapterType, operation);\n\t\tif (!adapter) {\n\t\t\tthrow new MissingAdapterRegistrationError(adapterType, operation);\n\t\t}\n\n\t\treturn adapter;\n\t}\n}\n\nconst DEFAULT_RETRY_POLICY: ResolvedAdapterRetryPolicy = {\n\tidempotent: true,\n\tmaxAttempts: 3,\n\tinitialDelayMs: 100,\n\tbackoffMultiplier: 2,\n\tmaxDelayMs: 1_000,\n};\n\nconst NO_RETRY_POLICY: ResolvedAdapterRetryPolicy = {\n\tidempotent: false,\n\tmaxAttempts: 1,\n\tinitialDelayMs: 0,\n\tbackoffMultiplier: 1,\n\tmaxDelayMs: 0,\n};\n\nexport function resolveAdapterRetryPolicy(\n\tpolicy: AdapterRetryPolicy | undefined,\n\tdefaultIdempotent: boolean,\n): ResolvedAdapterRetryPolicy {\n\tconst idempotent = policy?.idempotent ?? defaultIdempotent;\n\n\tif (!idempotent) {\n\t\treturn NO_RETRY_POLICY;\n\t}\n\n\treturn {\n\t\tidempotent: true,\n\t\tmaxAttempts: Math.max(1, Math.min(policy?.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts, 5)),\n\t\tinitialDelayMs: Math.max(0, policy?.initialDelayMs ?? DEFAULT_RETRY_POLICY.initialDelayMs),\n\t\tbackoffMultiplier: Math.max(\n\t\t\t1,\n\t\t\tpolicy?.backoffMultiplier ?? DEFAULT_RETRY_POLICY.backoffMultiplier,\n\t\t),\n\t\tmaxDelayMs: Math.max(0, policy?.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs),\n\t};\n}\n\nexport function getRetryDelayMs(attempt: number, policy: ResolvedAdapterRetryPolicy): number {\n\tconst exponentialDelay =\n\t\tpolicy.initialDelayMs * policy.backoffMultiplier ** Math.max(0, attempt - 1);\n\n\treturn Math.min(exponentialDelay, policy.maxDelayMs);\n}\n\nexport async function executeWithAdapterRetry<T>(options: ExecuteWithRetryOptions<T>): Promise<T> {\n\tconst policy = resolveAdapterRetryPolicy(options.policy, options.defaultIdempotent);\n\tconst sleep =\n\t\toptions.sleep ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\tlet lastError: string | undefined;\n\n\tfor (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {\n\t\ttry {\n\t\t\tconst result = await options.execute(attempt, policy.maxAttempts);\n\t\t\tif (options.isSuccessful(result)) {\n\t\t\t\tawait options.onAttempt?.({ attempt, result, willRetry: false });\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tlastError = options.getError(result) ?? \"Adapter returned unsuccessful result\";\n\t\t\tconst willRetry = policy.idempotent && attempt < policy.maxAttempts;\n\t\t\tconst nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : undefined;\n\t\t\tawait options.onAttempt?.({ attempt, result, error: lastError, willRetry, nextDelayMs });\n\n\t\t\tif (!willRetry) {\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tawait sleep(nextDelayMs ?? 0);\n\t\t} catch (error) {\n\t\t\tlastError = error instanceof Error ? error.message : String(error);\n\t\t\tconst willRetry = policy.idempotent && attempt < policy.maxAttempts;\n\t\t\tconst nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : undefined;\n\t\t\tawait options.onAttempt?.({ attempt, error: lastError, willRetry, nextDelayMs });\n\n\t\t\tif (!willRetry) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tawait sleep(nextDelayMs ?? 0);\n\t\t}\n\t}\n\n\tthrow new Error(lastError ?? \"Adapter retry exhausted\");\n}\n"]}
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// state-machines/engine.ts
|
|
2
|
+
var registry = /* @__PURE__ */ new Map();
|
|
3
|
+
function registerStateMachine(def) {
|
|
4
|
+
if (registry.has(def.entityType)) {
|
|
5
|
+
throw new Error(`State machine for entity type "${def.entityType}" is already registered.`);
|
|
6
|
+
}
|
|
7
|
+
registry.set(def.entityType, def);
|
|
8
|
+
}
|
|
9
|
+
function registerStateMachineIfMissing(def) {
|
|
10
|
+
if (!registry.has(def.entityType)) {
|
|
11
|
+
registerStateMachine(def);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function resolveStateMachine(entityType) {
|
|
15
|
+
return registry.get(entityType);
|
|
16
|
+
}
|
|
17
|
+
function validateTransition(entityType, from, to, actionId) {
|
|
18
|
+
const sm = registry.get(entityType);
|
|
19
|
+
if (!sm) {
|
|
20
|
+
return {
|
|
21
|
+
valid: false,
|
|
22
|
+
error: `No state machine registered for entity type "${entityType}".`
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const fromDef = sm.states[from];
|
|
26
|
+
if (!fromDef) {
|
|
27
|
+
return {
|
|
28
|
+
valid: false,
|
|
29
|
+
error: `State "${from}" does not exist in state machine for entity type "${entityType}".`
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const toDef = sm.states[to];
|
|
33
|
+
if (!toDef) {
|
|
34
|
+
return {
|
|
35
|
+
valid: false,
|
|
36
|
+
error: `State "${to}" does not exist in state machine for entity type "${entityType}".`
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
const transitionExists = sm.transitions.some(
|
|
40
|
+
(t) => t.from === from && t.to === to && t.causedByAction === actionId
|
|
41
|
+
);
|
|
42
|
+
if (!transitionExists) {
|
|
43
|
+
return {
|
|
44
|
+
valid: false,
|
|
45
|
+
error: `Transition from "${from}" to "${to}" via action "${actionId}" is not defined for entity type "${entityType}".`
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
return { valid: true };
|
|
49
|
+
}
|
|
50
|
+
function getAllowedTransitions(entityType, from) {
|
|
51
|
+
const sm = registry.get(entityType);
|
|
52
|
+
if (!sm) {
|
|
53
|
+
return [];
|
|
54
|
+
}
|
|
55
|
+
return sm.transitions.filter((t) => t.from === from).map((t) => ({ to: t.to, causedByAction: t.causedByAction }));
|
|
56
|
+
}
|
|
57
|
+
function getEntityStateClass(entityType, state) {
|
|
58
|
+
const sm = registry.get(entityType);
|
|
59
|
+
if (!sm) {
|
|
60
|
+
return void 0;
|
|
61
|
+
}
|
|
62
|
+
return sm.states[state]?.stateClass;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export { getAllowedTransitions, getEntityStateClass, registerStateMachine, registerStateMachineIfMissing, resolveStateMachine, validateTransition };
|
|
66
|
+
//# sourceMappingURL=chunk-3A6FU3KV.js.map
|
|
67
|
+
//# sourceMappingURL=chunk-3A6FU3KV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../state-machines/engine.ts"],"names":[],"mappings":"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","file":"chunk-3A6FU3KV.js","sourcesContent":["import type { StateClass, StateDefinition } from \"./index\";\n\nexport interface StateMachineDefinition {\n\tentityType: string;\n\tstates: Record<string, StateDefinition>;\n\ttransitions: Array<{\n\t\tfrom: string;\n\t\tto: string;\n\t\tcausedByAction: string;\n\t\tguard?: (ctx: TransitionContext) => boolean | Promise<boolean>;\n\t}>;\n}\n\nexport type StateMachineDatabaseClient = any;\n\nexport interface TransitionContext<TDb = StateMachineDatabaseClient> {\n\tentity: Record<string, unknown>;\n\tactionInvocationId: string;\n\tactorId: string;\n\tparameters: unknown;\n\tdb: TDb;\n}\n\nconst registry = new Map<string, StateMachineDefinition>();\n\n/**\n * Register a state-machine definition for an entity type.\n * Throws if a definition for this entity type is already registered.\n */\nexport function registerStateMachine(def: StateMachineDefinition): void {\n\tif (registry.has(def.entityType)) {\n\t\tthrow new Error(`State machine for entity type \"${def.entityType}\" is already registered.`);\n\t}\n\tregistry.set(def.entityType, def);\n}\n\n/**\n * Register a state-machine definition only if one for this entity type\n * is not already registered. Idempotent — safe to call multiple times.\n */\nexport function registerStateMachineIfMissing(def: StateMachineDefinition): void {\n\tif (!registry.has(def.entityType)) {\n\t\tregisterStateMachine(def);\n\t}\n}\n\n/**\n * Resolve a previously registered state-machine definition by entity type.\n */\nexport function resolveStateMachine(entityType: string): StateMachineDefinition | undefined {\n\treturn registry.get(entityType);\n}\n\n/**\n * Validate whether a transition is allowed for the given entity type,\n * from state, to state, and action ID.\n */\nexport function validateTransition(\n\tentityType: string,\n\tfrom: string,\n\tto: string,\n\tactionId: string,\n): { valid: boolean; error?: string } {\n\tconst sm = registry.get(entityType);\n\tif (!sm) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `No state machine registered for entity type \"${entityType}\".`,\n\t\t};\n\t}\n\n\tconst fromDef = sm.states[from];\n\tif (!fromDef) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `State \"${from}\" does not exist in state machine for entity type \"${entityType}\".`,\n\t\t};\n\t}\n\n\tconst toDef = sm.states[to];\n\tif (!toDef) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `State \"${to}\" does not exist in state machine for entity type \"${entityType}\".`,\n\t\t};\n\t}\n\n\tconst transitionExists = sm.transitions.some(\n\t\t(t) => t.from === from && t.to === to && t.causedByAction === actionId,\n\t);\n\tif (!transitionExists) {\n\t\treturn {\n\t\t\tvalid: false,\n\t\t\terror: `Transition from \"${from}\" to \"${to}\" via action \"${actionId}\" is not defined for entity type \"${entityType}\".`,\n\t\t};\n\t}\n\n\treturn { valid: true };\n}\n\n/**\n * Get all allowed transitions from a given state for an entity type.\n */\nexport function getAllowedTransitions(\n\tentityType: string,\n\tfrom: string,\n): Array<{ to: string; causedByAction: string }> {\n\tconst sm = registry.get(entityType);\n\tif (!sm) {\n\t\treturn [];\n\t}\n\treturn sm.transitions\n\t\t.filter((t) => t.from === from)\n\t\t.map((t) => ({ to: t.to, causedByAction: t.causedByAction }));\n}\n\n/**\n * Get the StateClass of a given state for an entity type.\n */\nexport function getEntityStateClass(entityType: string, state: string): StateClass | undefined {\n\tconst sm = registry.get(entityType);\n\tif (!sm) {\n\t\treturn undefined;\n\t}\n\treturn sm.states[state]?.stateClass;\n}\n"]}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
|
|
3
|
+
// displays/index.ts
|
|
4
|
+
var registry = /* @__PURE__ */ new Map();
|
|
5
|
+
function registerDisplayRenderer(eventType, renderer) {
|
|
6
|
+
registry.set(eventType, renderer);
|
|
7
|
+
}
|
|
8
|
+
function resolveDisplayRenderer(eventType) {
|
|
9
|
+
return registry.get(eventType);
|
|
10
|
+
}
|
|
11
|
+
function denormalizeDisplaySnapshot(payload) {
|
|
12
|
+
if (payload !== null && typeof payload === "object" && "_displaySnapshot" in payload && payload._displaySnapshot !== null && typeof payload._displaySnapshot === "object" && "title" in payload._displaySnapshot) {
|
|
13
|
+
return payload._displaySnapshot;
|
|
14
|
+
}
|
|
15
|
+
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) {
|
|
16
|
+
const snapshot = payload._projectionSnapshot;
|
|
17
|
+
return {
|
|
18
|
+
...snapshot.display,
|
|
19
|
+
_cachedDisplay: {
|
|
20
|
+
source: "projection_snapshot",
|
|
21
|
+
cursor: snapshot.eventCursor,
|
|
22
|
+
checksum: snapshot.checksum,
|
|
23
|
+
generatedAt: snapshot.updatedAt
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return void 0;
|
|
28
|
+
}
|
|
29
|
+
async function renderEventDisplay(eventType, payload, ctx) {
|
|
30
|
+
const snapshot = denormalizeDisplaySnapshot(payload);
|
|
31
|
+
if (snapshot) {
|
|
32
|
+
return snapshot;
|
|
33
|
+
}
|
|
34
|
+
const renderer = resolveDisplayRenderer(eventType);
|
|
35
|
+
if (!renderer) {
|
|
36
|
+
return {
|
|
37
|
+
title: eventType,
|
|
38
|
+
subtitle: "No display renderer registered for this event type."
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
return await renderer(payload, ctx);
|
|
42
|
+
}
|
|
43
|
+
async function _runInlineTests() {
|
|
44
|
+
const testEventType = "__TestEvent__";
|
|
45
|
+
registerDisplayRenderer(testEventType, (payload) => ({
|
|
46
|
+
title: `Test: ${payload.name}`,
|
|
47
|
+
subtitle: "registered renderer"
|
|
48
|
+
}));
|
|
49
|
+
const renderer = resolveDisplayRenderer(testEventType);
|
|
50
|
+
assert.ok(renderer, "resolveDisplayRenderer should return the registered renderer");
|
|
51
|
+
const result = await renderEventDisplay(testEventType, { name: "Alice" });
|
|
52
|
+
assert.strictEqual(result.title, "Test: Alice");
|
|
53
|
+
assert.strictEqual(result.subtitle, "registered renderer");
|
|
54
|
+
const snapshot = {
|
|
55
|
+
title: "Snapshot Title",
|
|
56
|
+
subtitle: "snapshot subtitle",
|
|
57
|
+
stateLabel: "archived",
|
|
58
|
+
primaryFacts: [{ label: "ID", value: "123" }]
|
|
59
|
+
};
|
|
60
|
+
const snapshotResult = await renderEventDisplay(testEventType, {
|
|
61
|
+
_displaySnapshot: snapshot
|
|
62
|
+
});
|
|
63
|
+
assert.deepStrictEqual(snapshotResult, snapshot);
|
|
64
|
+
const fallback = await renderEventDisplay("__UnknownEvent__", {});
|
|
65
|
+
assert.strictEqual(fallback.title, "__UnknownEvent__");
|
|
66
|
+
assert.strictEqual(fallback.subtitle, "No display renderer registered for this event type.");
|
|
67
|
+
registerDisplayRenderer("__AsyncEvent__", async (_payload, ctx) => {
|
|
68
|
+
return {
|
|
69
|
+
title: `Async: ${ctx?.tenantId ?? "no-ctx"}`
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
const asyncResult = await renderEventDisplay(
|
|
73
|
+
"__AsyncEvent__",
|
|
74
|
+
{},
|
|
75
|
+
{ tenantId: "t-1", spaceId: "s-1", db: {} }
|
|
76
|
+
);
|
|
77
|
+
assert.strictEqual(asyncResult.title, "Async: t-1");
|
|
78
|
+
console.log("\u2705 display engine inline tests passed");
|
|
79
|
+
}
|
|
80
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
81
|
+
_runInlineTests().catch((err) => {
|
|
82
|
+
console.error("\u274C display engine inline tests failed", err);
|
|
83
|
+
process.exit(1);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { denormalizeDisplaySnapshot, registerDisplayRenderer, renderEventDisplay, resolveDisplayRenderer };
|
|
88
|
+
//# sourceMappingURL=chunk-3F6FKQZA.js.map
|
|
89
|
+
//# sourceMappingURL=chunk-3F6FKQZA.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../displays/index.ts"],"names":[],"mappings":"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","file":"chunk-3F6FKQZA.js","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"]}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// adapters/index.ts
|
|
2
|
+
function adapterKey(adapterType, operation) {
|
|
3
|
+
return `${adapterType}:${operation}`;
|
|
4
|
+
}
|
|
5
|
+
var MissingAdapterRegistrationError = class extends Error {
|
|
6
|
+
constructor(adapterType, operation) {
|
|
7
|
+
super(`No adapter registered for adapterType "${adapterType}" and operation "${operation}"`);
|
|
8
|
+
this.name = "MissingAdapterRegistrationError";
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
var AdapterRegistry = class {
|
|
12
|
+
adapters = /* @__PURE__ */ new Map();
|
|
13
|
+
register(adapter) {
|
|
14
|
+
const key = adapterKey(adapter.adapterType, adapter.operation);
|
|
15
|
+
if (this.adapters.has(key)) {
|
|
16
|
+
throw new Error(
|
|
17
|
+
`Adapter already registered for adapterType "${adapter.adapterType}" and operation "${adapter.operation}"`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
this.adapters.set(key, adapter);
|
|
21
|
+
}
|
|
22
|
+
resolve(adapterType, operation) {
|
|
23
|
+
return this.adapters.get(adapterKey(adapterType, operation));
|
|
24
|
+
}
|
|
25
|
+
require(adapterType, operation) {
|
|
26
|
+
const adapter = this.resolve(adapterType, operation);
|
|
27
|
+
if (!adapter) {
|
|
28
|
+
throw new MissingAdapterRegistrationError(adapterType, operation);
|
|
29
|
+
}
|
|
30
|
+
return adapter;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
var DEFAULT_RETRY_POLICY = {
|
|
34
|
+
maxAttempts: 3,
|
|
35
|
+
initialDelayMs: 100,
|
|
36
|
+
backoffMultiplier: 2,
|
|
37
|
+
maxDelayMs: 1e3
|
|
38
|
+
};
|
|
39
|
+
var NO_RETRY_POLICY = {
|
|
40
|
+
idempotent: false,
|
|
41
|
+
maxAttempts: 1,
|
|
42
|
+
initialDelayMs: 0,
|
|
43
|
+
backoffMultiplier: 1,
|
|
44
|
+
maxDelayMs: 0
|
|
45
|
+
};
|
|
46
|
+
function resolveAdapterRetryPolicy(policy, defaultIdempotent) {
|
|
47
|
+
const idempotent = policy?.idempotent ?? defaultIdempotent;
|
|
48
|
+
if (!idempotent) {
|
|
49
|
+
return NO_RETRY_POLICY;
|
|
50
|
+
}
|
|
51
|
+
return {
|
|
52
|
+
idempotent: true,
|
|
53
|
+
maxAttempts: Math.max(1, Math.min(policy?.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts, 5)),
|
|
54
|
+
initialDelayMs: Math.max(0, policy?.initialDelayMs ?? DEFAULT_RETRY_POLICY.initialDelayMs),
|
|
55
|
+
backoffMultiplier: Math.max(
|
|
56
|
+
1,
|
|
57
|
+
policy?.backoffMultiplier ?? DEFAULT_RETRY_POLICY.backoffMultiplier
|
|
58
|
+
),
|
|
59
|
+
maxDelayMs: Math.max(0, policy?.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function getRetryDelayMs(attempt, policy) {
|
|
63
|
+
const exponentialDelay = policy.initialDelayMs * policy.backoffMultiplier ** Math.max(0, attempt - 1);
|
|
64
|
+
return Math.min(exponentialDelay, policy.maxDelayMs);
|
|
65
|
+
}
|
|
66
|
+
async function executeWithAdapterRetry(options) {
|
|
67
|
+
const policy = resolveAdapterRetryPolicy(options.policy, options.defaultIdempotent);
|
|
68
|
+
const sleep = options.sleep ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));
|
|
69
|
+
let lastError;
|
|
70
|
+
for (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {
|
|
71
|
+
try {
|
|
72
|
+
const result = await options.execute(attempt, policy.maxAttempts);
|
|
73
|
+
if (options.isSuccessful(result)) {
|
|
74
|
+
await options.onAttempt?.({ attempt, result, willRetry: false });
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
lastError = options.getError(result) ?? "Adapter returned unsuccessful result";
|
|
78
|
+
const willRetry = policy.idempotent && attempt < policy.maxAttempts;
|
|
79
|
+
const nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : void 0;
|
|
80
|
+
await options.onAttempt?.({ attempt, result, error: lastError, willRetry, nextDelayMs });
|
|
81
|
+
if (!willRetry) {
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
await sleep(nextDelayMs ?? 0);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
87
|
+
const willRetry = policy.idempotent && attempt < policy.maxAttempts;
|
|
88
|
+
const nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : void 0;
|
|
89
|
+
await options.onAttempt?.({ attempt, error: lastError, willRetry, nextDelayMs });
|
|
90
|
+
if (!willRetry) {
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
await sleep(nextDelayMs ?? 0);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
throw new Error(lastError ?? "Adapter retry exhausted");
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { AdapterRegistry, MissingAdapterRegistrationError, executeWithAdapterRetry, getRetryDelayMs, resolveAdapterRetryPolicy };
|
|
100
|
+
//# sourceMappingURL=chunk-3ZW5USXF.js.map
|
|
101
|
+
//# sourceMappingURL=chunk-3ZW5USXF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../adapters/index.ts"],"names":[],"mappings":"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","file":"chunk-3ZW5USXF.js","sourcesContent":["export interface AdapterExecutionContext {\n\ttenantId: string;\n\tspaceId: string;\n\tactionInvocationId: string;\n\tadapterInvocationId: string;\n\tcorrelationId: string;\n\tcausationId?: string;\n\tattempt: number;\n\tmaxAttempts: number;\n}\n\nexport interface AdapterExecutionResult {\n\tsuccess: boolean;\n\tcorrelationId?: string;\n\terror?: string;\n}\n\nexport type AdapterExecutor = (\n\tinput: Record<string, unknown>,\n\tcontext: AdapterExecutionContext,\n) => Promise<AdapterExecutionResult>;\n\nexport interface AdapterRetryPolicy {\n\t/** External calls must explicitly opt into retry by being idempotent. */\n\tidempotent: boolean;\n\t/** Total attempts including the first call. Non-idempotent policies are forced to 1. */\n\tmaxAttempts?: number;\n\t/** First backoff delay in milliseconds. */\n\tinitialDelayMs?: number;\n\t/** Exponential multiplier applied after each failed attempt. */\n\tbackoffMultiplier?: number;\n\t/** Upper bound for any single retry delay in milliseconds. */\n\tmaxDelayMs?: number;\n}\n\nexport interface ResolvedAdapterRetryPolicy {\n\tidempotent: boolean;\n\tmaxAttempts: number;\n\tinitialDelayMs: number;\n\tbackoffMultiplier: number;\n\tmaxDelayMs: number;\n}\n\nexport interface AdapterCircuitBreakerConfig {\n\tmode: \"monitor\" | \"enforce\";\n\tfailureThreshold: number;\n\twindowMs: number;\n\tcooldownMs: number;\n\tminimumSamples?: number;\n}\n\nexport interface AdapterImplementation {\n\tadapterType: string;\n\toperation: string;\n\tvendor: string;\n\t/** Whether this adapter operation can be safely retried by default. */\n\tidempotent: boolean;\n\tretryPolicy?: AdapterRetryPolicy;\n\t/** Defaults to monitor-only; enforce mode must be explicitly configured. */\n\tcircuitBreaker?: AdapterCircuitBreakerConfig;\n\texecute: AdapterExecutor;\n}\n\nexport interface AdapterAttempt<T> {\n\tattempt: number;\n\tresult?: T;\n\terror?: string;\n\twillRetry: boolean;\n\tnextDelayMs?: number;\n}\n\nexport interface ExecuteWithRetryOptions<T> {\n\tpolicy?: AdapterRetryPolicy;\n\tdefaultIdempotent: boolean;\n\texecute: (attempt: number, maxAttempts: number) => Promise<T>;\n\tisSuccessful: (result: T) => boolean;\n\tgetError: (result: T) => string | undefined;\n\tsleep?: (delayMs: number) => Promise<void>;\n\tonAttempt?: (attempt: AdapterAttempt<T>) => Promise<void> | void;\n}\n\nfunction adapterKey(adapterType: string, operation: string): string {\n\treturn `${adapterType}:${operation}`;\n}\n\nexport class MissingAdapterRegistrationError extends Error {\n\tconstructor(adapterType: string, operation: string) {\n\t\tsuper(`No adapter registered for adapterType \"${adapterType}\" and operation \"${operation}\"`);\n\t\tthis.name = \"MissingAdapterRegistrationError\";\n\t}\n}\n\nexport class AdapterRegistry {\n\tprivate readonly adapters = new Map<string, AdapterImplementation>();\n\n\tregister(adapter: AdapterImplementation): void {\n\t\tconst key = adapterKey(adapter.adapterType, adapter.operation);\n\t\tif (this.adapters.has(key)) {\n\t\t\tthrow new Error(\n\t\t\t\t`Adapter already registered for adapterType \"${adapter.adapterType}\" and operation \"${adapter.operation}\"`,\n\t\t\t);\n\t\t}\n\n\t\tthis.adapters.set(key, adapter);\n\t}\n\n\tresolve(adapterType: string, operation: string): AdapterImplementation | undefined {\n\t\treturn this.adapters.get(adapterKey(adapterType, operation));\n\t}\n\n\trequire(adapterType: string, operation: string): AdapterImplementation {\n\t\tconst adapter = this.resolve(adapterType, operation);\n\t\tif (!adapter) {\n\t\t\tthrow new MissingAdapterRegistrationError(adapterType, operation);\n\t\t}\n\n\t\treturn adapter;\n\t}\n}\n\nconst DEFAULT_RETRY_POLICY: ResolvedAdapterRetryPolicy = {\n\tidempotent: true,\n\tmaxAttempts: 3,\n\tinitialDelayMs: 100,\n\tbackoffMultiplier: 2,\n\tmaxDelayMs: 1_000,\n};\n\nconst NO_RETRY_POLICY: ResolvedAdapterRetryPolicy = {\n\tidempotent: false,\n\tmaxAttempts: 1,\n\tinitialDelayMs: 0,\n\tbackoffMultiplier: 1,\n\tmaxDelayMs: 0,\n};\n\nexport function resolveAdapterRetryPolicy(\n\tpolicy: AdapterRetryPolicy | undefined,\n\tdefaultIdempotent: boolean,\n): ResolvedAdapterRetryPolicy {\n\tconst idempotent = policy?.idempotent ?? defaultIdempotent;\n\n\tif (!idempotent) {\n\t\treturn NO_RETRY_POLICY;\n\t}\n\n\treturn {\n\t\tidempotent: true,\n\t\tmaxAttempts: Math.max(1, Math.min(policy?.maxAttempts ?? DEFAULT_RETRY_POLICY.maxAttempts, 5)),\n\t\tinitialDelayMs: Math.max(0, policy?.initialDelayMs ?? DEFAULT_RETRY_POLICY.initialDelayMs),\n\t\tbackoffMultiplier: Math.max(\n\t\t\t1,\n\t\t\tpolicy?.backoffMultiplier ?? DEFAULT_RETRY_POLICY.backoffMultiplier,\n\t\t),\n\t\tmaxDelayMs: Math.max(0, policy?.maxDelayMs ?? DEFAULT_RETRY_POLICY.maxDelayMs),\n\t};\n}\n\nexport function getRetryDelayMs(attempt: number, policy: ResolvedAdapterRetryPolicy): number {\n\tconst exponentialDelay =\n\t\tpolicy.initialDelayMs * policy.backoffMultiplier ** Math.max(0, attempt - 1);\n\n\treturn Math.min(exponentialDelay, policy.maxDelayMs);\n}\n\nexport async function executeWithAdapterRetry<T>(options: ExecuteWithRetryOptions<T>): Promise<T> {\n\tconst policy = resolveAdapterRetryPolicy(options.policy, options.defaultIdempotent);\n\tconst sleep =\n\t\toptions.sleep ?? ((delayMs) => new Promise((resolve) => setTimeout(resolve, delayMs)));\n\tlet lastError: string | undefined;\n\n\tfor (let attempt = 1; attempt <= policy.maxAttempts; attempt++) {\n\t\ttry {\n\t\t\tconst result = await options.execute(attempt, policy.maxAttempts);\n\t\t\tif (options.isSuccessful(result)) {\n\t\t\t\tawait options.onAttempt?.({ attempt, result, willRetry: false });\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tlastError = options.getError(result) ?? \"Adapter returned unsuccessful result\";\n\t\t\tconst willRetry = policy.idempotent && attempt < policy.maxAttempts;\n\t\t\tconst nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : undefined;\n\t\t\tawait options.onAttempt?.({ attempt, result, error: lastError, willRetry, nextDelayMs });\n\n\t\t\tif (!willRetry) {\n\t\t\t\treturn result;\n\t\t\t}\n\n\t\t\tawait sleep(nextDelayMs ?? 0);\n\t\t} catch (error) {\n\t\t\tlastError = error instanceof Error ? error.message : String(error);\n\t\t\tconst willRetry = policy.idempotent && attempt < policy.maxAttempts;\n\t\t\tconst nextDelayMs = willRetry ? getRetryDelayMs(attempt, policy) : undefined;\n\t\t\tawait options.onAttempt?.({ attempt, error: lastError, willRetry, nextDelayMs });\n\n\t\t\tif (!willRetry) {\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tawait sleep(nextDelayMs ?? 0);\n\t\t}\n\t}\n\n\tthrow new Error(lastError ?? \"Adapter retry exhausted\");\n}\n"]}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// events/index.ts
|
|
2
|
+
var subjectTypeRegistry = /* @__PURE__ */ new Set([
|
|
3
|
+
"ActionInvocation",
|
|
4
|
+
"PolicyEvaluation",
|
|
5
|
+
"AdapterInvocation"
|
|
6
|
+
]);
|
|
7
|
+
var eventTypeRegistry = /* @__PURE__ */ new Set([
|
|
8
|
+
"ComplianceBlocked",
|
|
9
|
+
"StateTransitioned",
|
|
10
|
+
"AdapterInvocationStarted",
|
|
11
|
+
"AdapterInvocationSucceeded",
|
|
12
|
+
"AdapterInvocationFailed",
|
|
13
|
+
"WebhookReceived"
|
|
14
|
+
]);
|
|
15
|
+
var dynamicSubjectTypes = /* @__PURE__ */ new Set();
|
|
16
|
+
var dynamicEventTypes = /* @__PURE__ */ new Set();
|
|
17
|
+
function registerSubjectType(type) {
|
|
18
|
+
dynamicSubjectTypes.add(type);
|
|
19
|
+
}
|
|
20
|
+
function isValidSubjectType(type) {
|
|
21
|
+
return subjectTypeRegistry.has(type) || dynamicSubjectTypes.has(type);
|
|
22
|
+
}
|
|
23
|
+
function getRegisteredSubjectTypes() {
|
|
24
|
+
return Array.from(/* @__PURE__ */ new Set([...subjectTypeRegistry, ...dynamicSubjectTypes]));
|
|
25
|
+
}
|
|
26
|
+
function registerEventType(type) {
|
|
27
|
+
dynamicEventTypes.add(type);
|
|
28
|
+
}
|
|
29
|
+
function isValidEventType(type) {
|
|
30
|
+
return eventTypeRegistry.has(type) || dynamicEventTypes.has(type);
|
|
31
|
+
}
|
|
32
|
+
function getRegisteredEventTypes() {
|
|
33
|
+
return Array.from(/* @__PURE__ */ new Set([...eventTypeRegistry, ...dynamicEventTypes]));
|
|
34
|
+
}
|
|
35
|
+
function createEventTypeRegistry(initialEventTypes = []) {
|
|
36
|
+
const eventTypes = new Set(initialEventTypes);
|
|
37
|
+
return {
|
|
38
|
+
register(eventType) {
|
|
39
|
+
if (!eventType) {
|
|
40
|
+
throw new Error("Event type cannot be empty");
|
|
41
|
+
}
|
|
42
|
+
eventTypes.add(eventType);
|
|
43
|
+
},
|
|
44
|
+
has(eventType) {
|
|
45
|
+
return eventTypes.has(eventType);
|
|
46
|
+
},
|
|
47
|
+
list() {
|
|
48
|
+
return Array.from(eventTypes).sort();
|
|
49
|
+
}
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export { createEventTypeRegistry, getRegisteredEventTypes, getRegisteredSubjectTypes, isValidEventType, isValidSubjectType, registerEventType, registerSubjectType };
|
|
54
|
+
//# sourceMappingURL=chunk-4YQOGV7H.js.map
|
|
55
|
+
//# sourceMappingURL=chunk-4YQOGV7H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../events/index.ts"],"names":[],"mappings":";AAmBA,IAAM,mBAAA,uBAA0B,GAAA,CAAY;AAAA,EAC3C,kBAAA;AAAA,EACA,kBAAA;AAAA,EACA;AACD,CAAC,CAAA;AAED,IAAM,iBAAA,uBAAwB,GAAA,CAAY;AAAA,EACzC,mBAAA;AAAA,EACA,mBAAA;AAAA,EACA,0BAAA;AAAA,EACA,4BAAA;AAAA,EACA,yBAAA;AAAA,EACA;AACD,CAAC,CAAA;AAED,IAAM,mBAAA,uBAA0B,GAAA,EAAY;AAC5C,IAAM,iBAAA,uBAAwB,GAAA,EAAY;AAMnC,SAAS,oBAAoB,IAAA,EAAoB;AACvD,EAAA,mBAAA,CAAoB,IAAI,IAAI,CAAA;AAC7B;AAEO,SAAS,mBAAmB,IAAA,EAAuB;AACzD,EAAA,OAAO,oBAAoB,GAAA,CAAI,IAAI,CAAA,IAAK,mBAAA,CAAoB,IAAI,IAAI,CAAA;AACrE;AAEO,SAAS,yBAAA,GAAsC;AACrD,EAAA,OAAO,KAAA,CAAM,IAAA,iBAAK,IAAI,GAAA,CAAI,CAAC,GAAG,mBAAA,EAAqB,GAAG,mBAAmB,CAAC,CAAC,CAAA;AAC5E;AAEO,SAAS,kBAAkB,IAAA,EAAoB;AACrD,EAAA,iBAAA,CAAkB,IAAI,IAAI,CAAA;AAC3B;AAEO,SAAS,iBAAiB,IAAA,EAAuB;AACvD,EAAA,OAAO,kBAAkB,GAAA,CAAI,IAAI,CAAA,IAAK,iBAAA,CAAkB,IAAI,IAAI,CAAA;AACjE;AAEO,SAAS,uBAAA,GAAoC;AACnD,EAAA,OAAO,KAAA,CAAM,IAAA,iBAAK,IAAI,GAAA,CAAI,CAAC,GAAG,iBAAA,EAAmB,GAAG,iBAAiB,CAAC,CAAC,CAAA;AACxE;AAQO,SAAS,uBAAA,CAAwB,iBAAA,GAA8B,EAAC,EAAsB;AAC5F,EAAA,MAAM,UAAA,GAAa,IAAI,GAAA,CAAI,iBAAiB,CAAA;AAC5C,EAAA,OAAO;AAAA,IACN,SAAS,SAAA,EAAW;AACnB,MAAA,IAAI,CAAC,SAAA,EAAW;AACf,QAAA,MAAM,IAAI,MAAM,4BAA4B,CAAA;AAAA,MAC7C;AACA,MAAA,UAAA,CAAW,IAAI,SAAS,CAAA;AAAA,IACzB,CAAA;AAAA,IACA,IAAI,SAAA,EAAW;AACd,MAAA,OAAO,UAAA,CAAW,IAAI,SAAS,CAAA;AAAA,IAChC,CAAA;AAAA,IACA,IAAA,GAAO;AACN,MAAA,OAAO,KAAA,CAAM,IAAA,CAAK,UAAU,CAAA,CAAE,IAAA,EAAK;AAAA,IACpC;AAAA,GACD;AACD","file":"chunk-4YQOGV7H.js","sourcesContent":["export interface AssetEventEnvelope<TPayload = unknown> {\n\tid: string;\n\ttenantId: string;\n\tspaceId: string;\n\teventType: string;\n\teventSchemaVersion: number;\n\tsubjectType: string;\n\tsubjectId: string;\n\tactorId: string;\n\tactorType: string;\n\tactionInvocationId?: string;\n\tpayload: TPayload;\n\tsequence: number;\n\toccurredAt: Date;\n\trecordedAt: Date;\n\tcorrelationId: string;\n\tcausationId?: string;\n}\n\nconst subjectTypeRegistry = new Set<string>([\n\t\"ActionInvocation\",\n\t\"PolicyEvaluation\",\n\t\"AdapterInvocation\",\n]);\n\nconst eventTypeRegistry = new Set<string>([\n\t\"ComplianceBlocked\",\n\t\"StateTransitioned\",\n\t\"AdapterInvocationStarted\",\n\t\"AdapterInvocationSucceeded\",\n\t\"AdapterInvocationFailed\",\n\t\"WebhookReceived\",\n]);\n\nconst dynamicSubjectTypes = new Set<string>();\nconst dynamicEventTypes = new Set<string>();\n\nexport type AssetEventSubjectType = string;\nexport type AssetEventType = string;\nexport type AssetEventActorType = \"natural_person\" | \"agent\" | \"adapter\" | \"system\" | \"service_account\" | \"external_system\" | \"integration\";\n\nexport function registerSubjectType(type: string): void {\n\tdynamicSubjectTypes.add(type);\n}\n\nexport function isValidSubjectType(type: string): boolean {\n\treturn subjectTypeRegistry.has(type) || dynamicSubjectTypes.has(type);\n}\n\nexport function getRegisteredSubjectTypes(): string[] {\n\treturn Array.from(new Set([...subjectTypeRegistry, ...dynamicSubjectTypes]));\n}\n\nexport function registerEventType(type: string): void {\n\tdynamicEventTypes.add(type);\n}\n\nexport function isValidEventType(type: string): boolean {\n\treturn eventTypeRegistry.has(type) || dynamicEventTypes.has(type);\n}\n\nexport function getRegisteredEventTypes(): string[] {\n\treturn Array.from(new Set([...eventTypeRegistry, ...dynamicEventTypes]));\n}\n\nexport interface EventTypeRegistry {\n\tregister(eventType: string): void;\n\thas(eventType: string): boolean;\n\tlist(): string[];\n}\n\nexport function createEventTypeRegistry(initialEventTypes: string[] = []): EventTypeRegistry {\n\tconst eventTypes = new Set(initialEventTypes);\n\treturn {\n\t\tregister(eventType) {\n\t\t\tif (!eventType) {\n\t\t\t\tthrow new Error(\"Event type cannot be empty\");\n\t\t\t}\n\t\t\teventTypes.add(eventType);\n\t\t},\n\t\thas(eventType) {\n\t\t\treturn eventTypes.has(eventType);\n\t\t},\n\t\tlist() {\n\t\t\treturn Array.from(eventTypes).sort();\n\t\t},\n\t};\n}\n"]}
|