@fabricorg/platform-host 0.1.0

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/index.cjs ADDED
@@ -0,0 +1,698 @@
1
+ 'use strict';
2
+
3
+ var platform = require('@fabricorg/platform');
4
+
5
+ // src/host.ts
6
+ var DEFAULT_EXTRACT_EVENTS = (data) => {
7
+ const value = data._events;
8
+ return Array.isArray(value) ? value : [];
9
+ };
10
+ function createGovernedActionHost(options) {
11
+ const adapters = new platform.AdapterRegistry();
12
+ for (const adapter of options.adapters ?? []) adapters.register(adapter);
13
+ const now = options.now ?? (() => /* @__PURE__ */ new Date());
14
+ const extractEvents = options.extractEvents ?? DEFAULT_EXTRACT_EVENTS;
15
+ async function submitAction(input) {
16
+ const action = platform.resolveAction(input.actionId);
17
+ if (!action) throw new Error(`Unknown action: ${input.actionId}`);
18
+ const authorizationInput = toAuthorizationInput(action, input);
19
+ if (!await options.authorization.checkEntitlement(authorizationInput)) {
20
+ throw new Error(`Module "${action.namespace}" is not enabled for tenant ${input.tenantId}`);
21
+ }
22
+ if (!await options.authorization.authorize(authorizationInput)) {
23
+ throw new Error(`Actor ${input.actorId} is not authorized for action ${input.actionId}`);
24
+ }
25
+ const actionInvocationId = platform.createFabricId("act");
26
+ const correlationId = input.correlationId ?? platform.createFabricId("corr");
27
+ const workflowId = `action-invocation-${actionInvocationId}`;
28
+ await options.store.createActionInvocation({
29
+ id: actionInvocationId,
30
+ tenantId: input.tenantId,
31
+ spaceId: input.spaceId,
32
+ actionId: input.actionId,
33
+ actionVersion: action.version,
34
+ actorId: input.actorId,
35
+ actorType: input.actorType,
36
+ status: "pending",
37
+ parameters: input.parameters,
38
+ result: {},
39
+ correlationId,
40
+ ...input.causationId ? { causationId: input.causationId } : {}
41
+ });
42
+ if (options.dispatcher) {
43
+ try {
44
+ const dispatched = await options.dispatcher.dispatch({
45
+ actionInvocationId,
46
+ tenantId: input.tenantId,
47
+ spaceId: input.spaceId,
48
+ workflowId
49
+ });
50
+ return {
51
+ actionInvocationId,
52
+ status: "pending",
53
+ workflowId: dispatched.workflowId,
54
+ ...dispatched.runId ? { runId: dispatched.runId } : {}
55
+ };
56
+ } catch (error) {
57
+ const message = errorMessage(error);
58
+ await options.store.updateActionInvocation(
59
+ actionInvocationId,
60
+ input.tenantId,
61
+ input.spaceId,
62
+ { status: "failed", error: message }
63
+ );
64
+ throw error;
65
+ }
66
+ }
67
+ const executed = await executeInvocation(
68
+ actionInvocationId,
69
+ input.tenantId,
70
+ input.spaceId
71
+ );
72
+ return { ...executed, workflowId };
73
+ }
74
+ async function executeInvocation(actionInvocationId, tenantId, spaceId) {
75
+ const invocation = await options.store.getActionInvocation(
76
+ actionInvocationId,
77
+ tenantId,
78
+ spaceId
79
+ );
80
+ if (!invocation) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
81
+ if (isTerminal(invocation.status)) {
82
+ return {
83
+ actionInvocationId,
84
+ status: invocation.status,
85
+ result: invocation.result,
86
+ ...invocation.error ? { error: invocation.error } : {}
87
+ };
88
+ }
89
+ const action = platform.resolveAction(invocation.actionId);
90
+ if (!action) {
91
+ return fail(invocation, "failed", `Unknown action: ${invocation.actionId}`);
92
+ }
93
+ await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
94
+ status: "running"
95
+ });
96
+ const parsed = action.schema.safeParse(invocation.parameters);
97
+ if (!parsed.success) {
98
+ const message = parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
99
+ return fail(invocation, "validation_failed", message);
100
+ }
101
+ const authorizationInput = toAuthorizationInput(action, invocation);
102
+ const definitions = options.resolvePolicies ? await options.resolvePolicies({
103
+ ...authorizationInput,
104
+ declaredPolicyIds: action.policies ?? []
105
+ }) : declaredPolicies(action.policies ?? []);
106
+ const outcomes = await platform.evaluatePolicyDefinitions(definitions, {
107
+ tenantId,
108
+ spaceId,
109
+ actionInvocationId,
110
+ actionId: action.actionId,
111
+ actorId: invocation.actorId,
112
+ actorType: invocation.actorType,
113
+ parameters: parsed.data,
114
+ db: options.store.db,
115
+ services: options.services,
116
+ mode: "execute"
117
+ });
118
+ for (const outcome of outcomes) {
119
+ await options.store.appendPolicyEvaluation({
120
+ id: platform.createFabricId("pol"),
121
+ actionInvocationId,
122
+ tenantId,
123
+ spaceId,
124
+ outcome,
125
+ createdAt: now()
126
+ });
127
+ }
128
+ const aggregate = platform.aggregatePolicyOutcomes(outcomes);
129
+ if (aggregate?.result === "block") {
130
+ const reason = aggregate.reason ?? `Blocked by policy ${aggregate.policyId}`;
131
+ await appendEvent(invocation, {
132
+ eventType: "ComplianceBlocked",
133
+ subjectType: "ActionInvocation",
134
+ subjectId: actionInvocationId,
135
+ payload: { actionId: action.actionId, policyId: aggregate.policyId, reason }
136
+ });
137
+ return fail(invocation, "blocked_by_policy", reason);
138
+ }
139
+ const binding = action.stateMachine;
140
+ if (binding) {
141
+ const entityId = binding.getEntityId(parsed.data);
142
+ const currentState = entityId ? await options.store.getEntityState(
143
+ tenantId,
144
+ spaceId,
145
+ binding.entityType,
146
+ entityId
147
+ ) ?? initialState(binding.entityType) : initialState(binding.entityType);
148
+ const targetState = typeof binding.targetState === "function" ? binding.targetState(parsed.data) : binding.targetState;
149
+ const transition = platform.validateTransition(
150
+ binding.entityType,
151
+ currentState,
152
+ targetState,
153
+ action.actionId
154
+ );
155
+ if (!transition.valid) {
156
+ return fail(invocation, "failed", transition.error ?? "Invalid state transition");
157
+ }
158
+ }
159
+ let data;
160
+ let domainEvents = [];
161
+ try {
162
+ const handlerResult = await options.store.transaction(
163
+ (db) => action.handler ? action.handler(
164
+ {
165
+ actionInvocationId,
166
+ tenantId,
167
+ spaceId,
168
+ actorId: invocation.actorId,
169
+ actorType: invocation.actorType,
170
+ correlationId: invocation.correlationId,
171
+ ...invocation.causationId ? { causationId: invocation.causationId } : {},
172
+ db,
173
+ services: options.services
174
+ },
175
+ parsed.data
176
+ ) : Promise.resolve({ success: true, data: {} })
177
+ );
178
+ if (!handlerResult.success) {
179
+ return fail(invocation, "failed", handlerResult.error ?? "Action handler failed");
180
+ }
181
+ data = handlerResult.data ?? {};
182
+ domainEvents = extractEvents(data);
183
+ if (action.eventPhase !== "after_adapters") {
184
+ for (const event of domainEvents) await appendEvent(invocation, event);
185
+ }
186
+ } catch (error) {
187
+ return fail(invocation, "failed", errorMessage(error));
188
+ }
189
+ for (const step of action.adapterSteps ?? []) {
190
+ const input = step.getInput(parsed.data, data);
191
+ if (!input) continue;
192
+ const adapter = adapters.require(step.adapterType, step.operation);
193
+ const adapterInvocationId = platform.createFabricId("adp");
194
+ const adapterEventSubject = options.adapterEventSubject?.(
195
+ action.actionId,
196
+ parsed.data,
197
+ data
198
+ ) ?? { subjectType: "AdapterInvocation", subjectId: adapterInvocationId };
199
+ const recordedInput = options.redactAdapterInput ? options.redactAdapterInput(
200
+ action.actionId,
201
+ step.adapterType,
202
+ step.operation,
203
+ input
204
+ ) : input;
205
+ const startedAt = now();
206
+ const record = {
207
+ id: adapterInvocationId,
208
+ actionInvocationId,
209
+ tenantId,
210
+ spaceId,
211
+ adapterType: step.adapterType,
212
+ operation: step.operation,
213
+ vendor: adapter.vendor,
214
+ status: "running",
215
+ input: recordedInput,
216
+ attempt: 1,
217
+ createdAt: startedAt,
218
+ updatedAt: startedAt
219
+ };
220
+ await options.store.createAdapterInvocation(record);
221
+ await appendEvent(invocation, {
222
+ eventType: "AdapterInvocationStarted",
223
+ subjectType: adapterEventSubject.subjectType,
224
+ subjectId: adapterEventSubject.subjectId,
225
+ payload: { adapterType: step.adapterType, operation: step.operation }
226
+ });
227
+ try {
228
+ const result2 = await platform.executeWithAdapterRetry({
229
+ policy: step.retryPolicy ?? adapter.retryPolicy,
230
+ defaultIdempotent: adapter.idempotent,
231
+ execute: async (attempt, maxAttempts) => {
232
+ await options.store.updateAdapterInvocation(adapterInvocationId, {
233
+ attempt,
234
+ updatedAt: now()
235
+ });
236
+ return adapter.execute(input, {
237
+ tenantId,
238
+ spaceId,
239
+ actionInvocationId,
240
+ adapterInvocationId,
241
+ correlationId: invocation.correlationId,
242
+ ...invocation.causationId ? { causationId: invocation.causationId } : {},
243
+ attempt,
244
+ maxAttempts
245
+ });
246
+ },
247
+ isSuccessful: (result3) => result3.success,
248
+ getError: (result3) => result3.error
249
+ });
250
+ if (!result2.success) {
251
+ await options.store.updateAdapterInvocation(adapterInvocationId, {
252
+ status: "failed",
253
+ error: result2.error ?? "Adapter failed",
254
+ updatedAt: now()
255
+ });
256
+ await appendEvent(invocation, {
257
+ eventType: "AdapterInvocationFailed",
258
+ subjectType: adapterEventSubject.subjectType,
259
+ subjectId: adapterEventSubject.subjectId,
260
+ payload: { adapterType: step.adapterType, operation: step.operation }
261
+ });
262
+ return fail(invocation, "failed", result2.error ?? "Adapter failed");
263
+ }
264
+ const resultRecord = result2;
265
+ const output = isRecord(resultRecord.output) ? resultRecord.output : Object.fromEntries(
266
+ Object.entries(resultRecord).filter(
267
+ ([key]) => key !== "success" && key !== "error"
268
+ )
269
+ );
270
+ await options.store.updateAdapterInvocation(adapterInvocationId, {
271
+ status: "succeeded",
272
+ output,
273
+ updatedAt: now()
274
+ });
275
+ await appendEvent(invocation, {
276
+ eventType: "AdapterInvocationSucceeded",
277
+ subjectType: adapterEventSubject.subjectType,
278
+ subjectId: adapterEventSubject.subjectId,
279
+ payload: {
280
+ adapterType: step.adapterType,
281
+ operation: step.operation,
282
+ input: recordedInput,
283
+ output
284
+ }
285
+ });
286
+ } catch (error) {
287
+ const message = errorMessage(error);
288
+ await options.store.updateAdapterInvocation(adapterInvocationId, {
289
+ status: "failed",
290
+ error: message,
291
+ updatedAt: now()
292
+ });
293
+ return fail(invocation, "failed", message);
294
+ }
295
+ }
296
+ if (action.eventPhase === "after_adapters") {
297
+ for (const event of domainEvents) await appendEvent(invocation, event);
298
+ }
299
+ const result = withoutPrivateHostFields(data);
300
+ await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
301
+ status: "completed",
302
+ result
303
+ });
304
+ return { actionInvocationId, status: "completed", result };
305
+ }
306
+ async function appendEvent(invocation, event) {
307
+ const timestamp = now();
308
+ const envelope = {
309
+ id: platform.createFabricId("evt"),
310
+ tenantId: invocation.tenantId,
311
+ spaceId: invocation.spaceId,
312
+ eventType: event.eventType,
313
+ eventSchemaVersion: event.eventSchemaVersion ?? 1,
314
+ subjectType: event.subjectType,
315
+ subjectId: event.subjectId,
316
+ actorId: invocation.actorId,
317
+ actorType: invocation.actorType,
318
+ actionInvocationId: invocation.id,
319
+ payload: event.payload,
320
+ sequence: await options.store.nextEventSequence(
321
+ invocation.tenantId,
322
+ invocation.spaceId
323
+ ),
324
+ occurredAt: timestamp,
325
+ recordedAt: timestamp,
326
+ correlationId: invocation.correlationId,
327
+ ...invocation.causationId ? { causationId: invocation.causationId } : {}
328
+ };
329
+ await options.store.appendEvent(envelope);
330
+ }
331
+ async function fail(invocation, status, error) {
332
+ await options.store.updateActionInvocation(
333
+ invocation.id,
334
+ invocation.tenantId,
335
+ invocation.spaceId,
336
+ { status, error }
337
+ );
338
+ return { actionInvocationId: invocation.id, status, error };
339
+ }
340
+ return { submitAction, executeInvocation };
341
+ }
342
+ function toAuthorizationInput(action, input) {
343
+ return {
344
+ actionId: action.actionId,
345
+ namespace: action.namespace,
346
+ requiredPermissions: action.requiredPermissions ?? [],
347
+ requiredRoles: action.requiredRoles ?? [],
348
+ tenantId: input.tenantId,
349
+ spaceId: input.spaceId,
350
+ actorId: input.actorId,
351
+ actorType: input.actorType
352
+ };
353
+ }
354
+ function declaredPolicies(policyIds) {
355
+ return policyIds.map((policyId) => ({
356
+ policyId,
357
+ policyVersion: Number(policyId.split(".v").at(-1) ?? 1),
358
+ kind: "code",
359
+ codeEvaluatorPolicyId: policyId
360
+ }));
361
+ }
362
+ function initialState(entityType) {
363
+ const machine = platform.resolveStateMachine(entityType);
364
+ return Object.values(machine?.states ?? {}).find((state) => state.stateClass === "initial")?.id ?? "none";
365
+ }
366
+ function isTerminal(status) {
367
+ return ["completed", "failed", "blocked_by_policy", "validation_failed"].includes(status);
368
+ }
369
+ function withoutPrivateHostFields(data) {
370
+ const { _events: _ignored, ...result } = data;
371
+ return result;
372
+ }
373
+ function errorMessage(error) {
374
+ return error instanceof Error ? error.message : String(error);
375
+ }
376
+ function isRecord(value) {
377
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
378
+ }
379
+
380
+ // src/memory-store.ts
381
+ var MemoryPlatformHostStore = class {
382
+ constructor(db) {
383
+ this.db = db;
384
+ }
385
+ db;
386
+ invocations = [];
387
+ policyEvaluations = [];
388
+ adapterInvocations = [];
389
+ events = [];
390
+ async transaction(run) {
391
+ return run(this.db);
392
+ }
393
+ async createActionInvocation(input) {
394
+ const now = /* @__PURE__ */ new Date();
395
+ const record = { ...input, createdAt: now, updatedAt: now };
396
+ this.invocations.push(record);
397
+ return record;
398
+ }
399
+ async getActionInvocation(id, tenantId, spaceId) {
400
+ return this.invocations.find(
401
+ (record) => record.id === id && record.tenantId === tenantId && record.spaceId === spaceId
402
+ );
403
+ }
404
+ async updateActionInvocation(id, tenantId, spaceId, patch) {
405
+ const record = await this.getActionInvocation(id, tenantId, spaceId);
406
+ if (!record) throw new Error(`ActionInvocation not found: ${id}`);
407
+ Object.assign(record, patch, { updatedAt: /* @__PURE__ */ new Date() });
408
+ }
409
+ async appendPolicyEvaluation(record) {
410
+ this.policyEvaluations.push(record);
411
+ }
412
+ async createAdapterInvocation(record) {
413
+ this.adapterInvocations.push(record);
414
+ }
415
+ async updateAdapterInvocation(id, patch) {
416
+ const record = this.adapterInvocations.find((candidate) => candidate.id === id);
417
+ if (!record) throw new Error(`AdapterInvocation not found: ${id}`);
418
+ Object.assign(record, patch);
419
+ }
420
+ async appendEvent(event) {
421
+ this.events.push(event);
422
+ }
423
+ async nextEventSequence(tenantId, spaceId) {
424
+ return this.events.filter((event) => event.tenantId === tenantId && event.spaceId === spaceId).length + 1;
425
+ }
426
+ async getEntityState(tenantId, spaceId, entityType, entityId) {
427
+ let state;
428
+ for (const event of this.events) {
429
+ if (event.tenantId !== tenantId || event.spaceId !== spaceId || event.subjectType !== entityType || event.subjectId !== entityId) {
430
+ continue;
431
+ }
432
+ const candidate = event.payload?.toState;
433
+ if (typeof candidate === "string") state = candidate;
434
+ }
435
+ return state;
436
+ }
437
+ };
438
+
439
+ // src/postgres-store.ts
440
+ var PostgresPlatformHostStore = class {
441
+ constructor(db, sql) {
442
+ this.db = db;
443
+ this.sql = sql;
444
+ }
445
+ db;
446
+ sql;
447
+ async ensureSchema() {
448
+ await this.sql.query(`
449
+ CREATE SCHEMA IF NOT EXISTS fabric_platform;
450
+ CREATE TABLE IF NOT EXISTS fabric_platform.action_invocations (
451
+ id text PRIMARY KEY, tenant_id text NOT NULL, space_id text NOT NULL,
452
+ action_id text NOT NULL, action_version integer NOT NULL,
453
+ actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
454
+ parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
455
+ correlation_id text NOT NULL, causation_id text, error text,
456
+ created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL
457
+ );
458
+ CREATE TABLE IF NOT EXISTS fabric_platform.policy_evaluations (
459
+ id text PRIMARY KEY, action_invocation_id text NOT NULL,
460
+ tenant_id text NOT NULL, space_id text NOT NULL, outcome jsonb NOT NULL,
461
+ created_at timestamptz NOT NULL
462
+ );
463
+ CREATE TABLE IF NOT EXISTS fabric_platform.adapter_invocations (
464
+ id text PRIMARY KEY, action_invocation_id text NOT NULL,
465
+ tenant_id text NOT NULL, space_id text NOT NULL, adapter_type text NOT NULL,
466
+ operation text NOT NULL, vendor text NOT NULL, status text NOT NULL,
467
+ input jsonb NOT NULL, output jsonb, error text, attempt integer NOT NULL,
468
+ created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL
469
+ );
470
+ CREATE TABLE IF NOT EXISTS fabric_platform.event_sequences (
471
+ tenant_id text NOT NULL, space_id text NOT NULL, next_sequence bigint NOT NULL,
472
+ PRIMARY KEY (tenant_id, space_id)
473
+ );
474
+ CREATE TABLE IF NOT EXISTS fabric_platform.asset_events (
475
+ id text PRIMARY KEY, tenant_id text NOT NULL, space_id text NOT NULL,
476
+ event_type text NOT NULL, event_schema_version integer NOT NULL,
477
+ subject_type text NOT NULL, subject_id text NOT NULL,
478
+ actor_id text NOT NULL, actor_type text NOT NULL, action_invocation_id text,
479
+ payload jsonb NOT NULL, sequence bigint NOT NULL,
480
+ occurred_at timestamptz NOT NULL, recorded_at timestamptz NOT NULL,
481
+ correlation_id text NOT NULL, causation_id text,
482
+ UNIQUE (tenant_id, space_id, sequence)
483
+ );
484
+ CREATE INDEX IF NOT EXISTS asset_events_subject_idx
485
+ ON fabric_platform.asset_events
486
+ (tenant_id, space_id, subject_type, subject_id, sequence);
487
+ `);
488
+ }
489
+ async transaction(run) {
490
+ return run(this.db);
491
+ }
492
+ async createActionInvocation(input) {
493
+ const now = /* @__PURE__ */ new Date();
494
+ await this.sql.query(
495
+ `INSERT INTO fabric_platform.action_invocations
496
+ (id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
497
+ parameters,result,correlation_id,causation_id,error,created_at,updated_at)
498
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$14)`,
499
+ [
500
+ input.id,
501
+ input.tenantId,
502
+ input.spaceId,
503
+ input.actionId,
504
+ input.actionVersion,
505
+ input.actorId,
506
+ input.actorType,
507
+ input.status,
508
+ JSON.stringify(input.parameters),
509
+ JSON.stringify(input.result),
510
+ input.correlationId,
511
+ input.causationId ?? null,
512
+ input.error ?? null,
513
+ now
514
+ ]
515
+ );
516
+ return { ...input, createdAt: now, updatedAt: now };
517
+ }
518
+ async getActionInvocation(id, tenantId, spaceId) {
519
+ const result = await this.sql.query(
520
+ `SELECT * FROM fabric_platform.action_invocations
521
+ WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
522
+ [id, tenantId, spaceId]
523
+ );
524
+ return result.rows[0] ? toActionRecord(result.rows[0]) : void 0;
525
+ }
526
+ async updateActionInvocation(id, tenantId, spaceId, patch) {
527
+ await this.sql.query(
528
+ `UPDATE fabric_platform.action_invocations SET
529
+ status=COALESCE($4,status), result=COALESCE($5::jsonb,result),
530
+ error=CASE WHEN $6::boolean THEN $7 ELSE error END, updated_at=now()
531
+ WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
532
+ [
533
+ id,
534
+ tenantId,
535
+ spaceId,
536
+ patch.status ?? null,
537
+ patch.result === void 0 ? null : JSON.stringify(patch.result),
538
+ Object.hasOwn(patch, "error"),
539
+ patch.error ?? null
540
+ ]
541
+ );
542
+ }
543
+ async appendPolicyEvaluation(record) {
544
+ await this.sql.query(
545
+ `INSERT INTO fabric_platform.policy_evaluations
546
+ (id,action_invocation_id,tenant_id,space_id,outcome,created_at)
547
+ VALUES ($1,$2,$3,$4,$5::jsonb,$6)`,
548
+ [
549
+ record.id,
550
+ record.actionInvocationId,
551
+ record.tenantId,
552
+ record.spaceId,
553
+ JSON.stringify(record.outcome),
554
+ record.createdAt
555
+ ]
556
+ );
557
+ }
558
+ async createAdapterInvocation(record) {
559
+ await this.sql.query(
560
+ `INSERT INTO fabric_platform.adapter_invocations
561
+ (id,action_invocation_id,tenant_id,space_id,adapter_type,operation,vendor,status,
562
+ input,output,error,attempt,created_at,updated_at)
563
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14)`,
564
+ [
565
+ record.id,
566
+ record.actionInvocationId,
567
+ record.tenantId,
568
+ record.spaceId,
569
+ record.adapterType,
570
+ record.operation,
571
+ record.vendor,
572
+ record.status,
573
+ JSON.stringify(record.input),
574
+ record.output ? JSON.stringify(record.output) : null,
575
+ record.error ?? null,
576
+ record.attempt,
577
+ record.createdAt,
578
+ record.updatedAt
579
+ ]
580
+ );
581
+ }
582
+ async updateAdapterInvocation(id, patch) {
583
+ await this.sql.query(
584
+ `UPDATE fabric_platform.adapter_invocations SET
585
+ status=COALESCE($2,status), output=COALESCE($3::jsonb,output),
586
+ error=CASE WHEN $4::boolean THEN $5 ELSE error END,
587
+ attempt=COALESCE($6,attempt), updated_at=COALESCE($7,now()) WHERE id=$1`,
588
+ [
589
+ id,
590
+ patch.status ?? null,
591
+ patch.output === void 0 ? null : JSON.stringify(patch.output),
592
+ Object.hasOwn(patch, "error"),
593
+ patch.error ?? null,
594
+ patch.attempt ?? null,
595
+ patch.updatedAt ?? null
596
+ ]
597
+ );
598
+ }
599
+ async appendEvent(event) {
600
+ await this.sql.query(
601
+ `INSERT INTO fabric_platform.asset_events
602
+ (id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
603
+ actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
604
+ correlation_id,causation_id)
605
+ VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16)`,
606
+ [
607
+ event.id,
608
+ event.tenantId,
609
+ event.spaceId,
610
+ event.eventType,
611
+ event.eventSchemaVersion,
612
+ event.subjectType,
613
+ event.subjectId,
614
+ event.actorId,
615
+ event.actorType,
616
+ event.actionInvocationId ?? null,
617
+ JSON.stringify(event.payload),
618
+ event.sequence,
619
+ event.occurredAt,
620
+ event.recordedAt,
621
+ event.correlationId,
622
+ event.causationId ?? null
623
+ ]
624
+ );
625
+ }
626
+ async nextEventSequence(tenantId, spaceId) {
627
+ const result = await this.sql.query(
628
+ `INSERT INTO fabric_platform.event_sequences (tenant_id,space_id,next_sequence)
629
+ VALUES ($1,$2,1) ON CONFLICT (tenant_id,space_id) DO UPDATE
630
+ SET next_sequence=fabric_platform.event_sequences.next_sequence+1
631
+ RETURNING next_sequence`,
632
+ [tenantId, spaceId]
633
+ );
634
+ return Number(result.rows[0].next_sequence);
635
+ }
636
+ async getEntityState(tenantId, spaceId, entityType, entityId) {
637
+ const result = await this.sql.query(
638
+ `SELECT payload->>'toState' AS state FROM fabric_platform.asset_events
639
+ WHERE tenant_id=$1 AND space_id=$2 AND subject_type=$3 AND subject_id=$4
640
+ AND payload ? 'toState' ORDER BY sequence DESC LIMIT 1`,
641
+ [tenantId, spaceId, entityType, entityId]
642
+ );
643
+ return result.rows[0]?.state;
644
+ }
645
+ async listEvents(tenantId, spaceId) {
646
+ const result = await this.sql.query(
647
+ `SELECT * FROM fabric_platform.asset_events
648
+ WHERE tenant_id=$1 AND space_id=$2 ORDER BY sequence`,
649
+ [tenantId, spaceId]
650
+ );
651
+ return result.rows.map(toEventRecord);
652
+ }
653
+ };
654
+ function toActionRecord(row) {
655
+ return {
656
+ id: String(row.id),
657
+ tenantId: String(row.tenant_id),
658
+ spaceId: String(row.space_id),
659
+ actionId: String(row.action_id),
660
+ actionVersion: Number(row.action_version),
661
+ actorId: String(row.actor_id),
662
+ actorType: String(row.actor_type),
663
+ status: String(row.status),
664
+ parameters: row.parameters,
665
+ result: row.result,
666
+ correlationId: String(row.correlation_id),
667
+ ...row.causation_id ? { causationId: String(row.causation_id) } : {},
668
+ ...row.error ? { error: String(row.error) } : {},
669
+ createdAt: new Date(row.created_at),
670
+ updatedAt: new Date(row.updated_at)
671
+ };
672
+ }
673
+ function toEventRecord(row) {
674
+ return {
675
+ id: String(row.id),
676
+ tenantId: String(row.tenant_id),
677
+ spaceId: String(row.space_id),
678
+ eventType: String(row.event_type),
679
+ eventSchemaVersion: Number(row.event_schema_version),
680
+ subjectType: String(row.subject_type),
681
+ subjectId: String(row.subject_id),
682
+ actorId: String(row.actor_id),
683
+ actorType: String(row.actor_type),
684
+ ...row.action_invocation_id ? { actionInvocationId: String(row.action_invocation_id) } : {},
685
+ payload: row.payload,
686
+ sequence: Number(row.sequence),
687
+ occurredAt: new Date(row.occurred_at),
688
+ recordedAt: new Date(row.recorded_at),
689
+ correlationId: String(row.correlation_id),
690
+ ...row.causation_id ? { causationId: String(row.causation_id) } : {}
691
+ };
692
+ }
693
+
694
+ exports.MemoryPlatformHostStore = MemoryPlatformHostStore;
695
+ exports.PostgresPlatformHostStore = PostgresPlatformHostStore;
696
+ exports.createGovernedActionHost = createGovernedActionHost;
697
+ //# sourceMappingURL=index.cjs.map
698
+ //# sourceMappingURL=index.cjs.map