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