@fabricorg/platform-host 1.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +33 -0
- package/README.md +27 -3
- package/dist/index.cjs +338 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +144 -4
- package/dist/index.d.ts +144 -4
- package/dist/index.js +336 -3
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -14,6 +14,12 @@ var RecoverableFinalizationError = class extends Error {
|
|
|
14
14
|
name = "RecoverableFinalizationError";
|
|
15
15
|
};
|
|
16
16
|
function createGovernedActionHost(options) {
|
|
17
|
+
if (options.outbox) {
|
|
18
|
+
const outboxStore = asOutboxStore(options.store);
|
|
19
|
+
if (!outboxStore || !asAtomicMutationStore(options.store) || outboxStore.transactionalOutbox !== true) {
|
|
20
|
+
throw new Error("Outbox egress requires a store with transactionalOutbox and transactionWithEvents capabilities.");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
17
23
|
const adapters = new AdapterRegistry();
|
|
18
24
|
for (const adapter of options.adapters ?? []) adapters.register(adapter);
|
|
19
25
|
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
@@ -320,6 +326,9 @@ function createGovernedActionHost(options) {
|
|
|
320
326
|
let domainEvents = [];
|
|
321
327
|
try {
|
|
322
328
|
const runHandler = async (db, transaction) => {
|
|
329
|
+
if (options.outbox && !transaction?.appendEventWithOutbox) {
|
|
330
|
+
throw new Error("Outbox egress requires transactionWithEvents to provide appendEventWithOutbox before handler execution.");
|
|
331
|
+
}
|
|
323
332
|
const handlerResult = action.handler ? await action.handler(
|
|
324
333
|
{
|
|
325
334
|
actionInvocationId,
|
|
@@ -338,6 +347,13 @@ function createGovernedActionHost(options) {
|
|
|
338
347
|
throw new Error(handlerResult.error ?? "Action handler failed");
|
|
339
348
|
}
|
|
340
349
|
const handlerData = handlerResult.data ?? {};
|
|
350
|
+
if (action.resultSchema) {
|
|
351
|
+
const publicResult = withoutPrivateHostFields(handlerData, eventResultFields);
|
|
352
|
+
const parsedResult = action.resultSchema.safeParse(publicResult);
|
|
353
|
+
if (!parsedResult.success) {
|
|
354
|
+
throw new Error(`Action result validation failed at ${parsedResult.error.issues.map((issue) => issue.path.map(String).join(".") || "result").join(", ")}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
341
357
|
const events = extractEvents(handlerData);
|
|
342
358
|
const undeclaredEvent = events.find(
|
|
343
359
|
(event) => !action.emitsEvents.includes(event.eventType)
|
|
@@ -678,7 +694,30 @@ function createGovernedActionHost(options) {
|
|
|
678
694
|
correlationId: invocation.correlationId,
|
|
679
695
|
...invocation.causationId ? { causationId: invocation.causationId } : {}
|
|
680
696
|
};
|
|
681
|
-
|
|
697
|
+
if (options.outbox) {
|
|
698
|
+
const hostLifecycleEvent = (/* @__PURE__ */ new Set(["AdapterInvocationStarted", "AdapterInvocationSucceeded", "AdapterInvocationFailed", "ComplianceBlocked"])).has(envelope.eventType);
|
|
699
|
+
const shouldPublish = options.outbox.shouldPublish?.(envelope) ?? !hostLifecycleEvent;
|
|
700
|
+
if (!shouldPublish) {
|
|
701
|
+
await (transaction ?? options.store).appendEvent(envelope);
|
|
702
|
+
return;
|
|
703
|
+
}
|
|
704
|
+
const traceContext = options.outbox.traceContext?.(envelope);
|
|
705
|
+
const metadata = {
|
|
706
|
+
producerModuleVersion: options.outbox.producerModuleVersion(invocation.actionId, invocation.actionVersion),
|
|
707
|
+
payloadClassification: options.outbox.classifyPayload(envelope),
|
|
708
|
+
...traceContext ? { traceContext } : {}
|
|
709
|
+
};
|
|
710
|
+
if (transaction) {
|
|
711
|
+
if (!transaction.appendEventWithOutbox) throw new Error("Outbox egress requires an atomic transaction with appendEventWithOutbox.");
|
|
712
|
+
await transaction.appendEventWithOutbox(envelope, metadata);
|
|
713
|
+
} else {
|
|
714
|
+
const outboxStore = asOutboxStore(options.store);
|
|
715
|
+
if (!outboxStore) throw new Error("Outbox egress requires an outbox-capable Platform Host store.");
|
|
716
|
+
await outboxStore.appendEventWithOutbox(envelope, metadata);
|
|
717
|
+
}
|
|
718
|
+
} else {
|
|
719
|
+
await (transaction ?? options.store).appendEvent(envelope);
|
|
720
|
+
}
|
|
682
721
|
}
|
|
683
722
|
async function fail(invocation, status, error) {
|
|
684
723
|
await options.store.updateActionInvocation(
|
|
@@ -715,6 +754,10 @@ function asAtomicMutationStore(store) {
|
|
|
715
754
|
const candidate = store;
|
|
716
755
|
return typeof candidate.transactionWithEvents === "function" ? store : void 0;
|
|
717
756
|
}
|
|
757
|
+
function asOutboxStore(store) {
|
|
758
|
+
const candidate = store;
|
|
759
|
+
return typeof candidate.appendEventWithOutbox === "function" && typeof candidate.claimOutbox === "function" ? store : void 0;
|
|
760
|
+
}
|
|
718
761
|
function asGovernanceStore(store) {
|
|
719
762
|
const candidate = store;
|
|
720
763
|
return typeof candidate.recordMutationGovernance === "function" && typeof candidate.appendPolicyObligations === "function" && typeof candidate.appendExecutionAttestation === "function" ? store : void 0;
|
|
@@ -771,12 +814,83 @@ function lifecycleId(prefix, invocationId, key) {
|
|
|
771
814
|
return `${prefix}_${invocationId}_${safeKey}`;
|
|
772
815
|
}
|
|
773
816
|
|
|
817
|
+
// src/outbox.ts
|
|
818
|
+
function toEnterpriseEventEnvelope(event, metadata) {
|
|
819
|
+
return {
|
|
820
|
+
eventId: event.id,
|
|
821
|
+
eventType: event.eventType,
|
|
822
|
+
eventSchemaVersion: event.eventSchemaVersion,
|
|
823
|
+
tenantId: event.tenantId,
|
|
824
|
+
spaceId: event.spaceId,
|
|
825
|
+
subjectType: event.subjectType,
|
|
826
|
+
subjectId: event.subjectId,
|
|
827
|
+
sequence: event.sequence,
|
|
828
|
+
...event.actionInvocationId ? { actionInvocationId: event.actionInvocationId } : {},
|
|
829
|
+
correlationId: event.correlationId,
|
|
830
|
+
...event.causationId ? { causationId: event.causationId } : {},
|
|
831
|
+
occurredAt: event.occurredAt,
|
|
832
|
+
recordedAt: event.recordedAt,
|
|
833
|
+
producerModuleVersion: metadata.producerModuleVersion,
|
|
834
|
+
payloadClassification: metadata.payloadClassification,
|
|
835
|
+
...metadata.traceContext ? { traceContext: { ...metadata.traceContext } } : {},
|
|
836
|
+
payload: event.payload
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
async function runOutboxRelayCycle(options) {
|
|
840
|
+
const now = options.now ?? (() => /* @__PURE__ */ new Date());
|
|
841
|
+
const maxAttempts = options.maxAttempts ?? 10;
|
|
842
|
+
const records = await options.store.claimOutbox({
|
|
843
|
+
workerId: options.workerId,
|
|
844
|
+
leaseDurationMs: options.leaseDurationMs ?? 3e4,
|
|
845
|
+
limit: options.batchSize ?? 100,
|
|
846
|
+
now: now()
|
|
847
|
+
});
|
|
848
|
+
const result = { claimed: records.length, published: 0, failed: 0, deadLettered: 0 };
|
|
849
|
+
for (const record of records) {
|
|
850
|
+
try {
|
|
851
|
+
await options.publisher.publish(record.event);
|
|
852
|
+
try {
|
|
853
|
+
await options.store.markOutboxPublished(record.id, options.workerId, now());
|
|
854
|
+
} catch {
|
|
855
|
+
result.failed += 1;
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
result.published += 1;
|
|
859
|
+
} catch {
|
|
860
|
+
const deadLetter = record.attemptCount >= maxAttempts;
|
|
861
|
+
try {
|
|
862
|
+
await options.store.markOutboxFailed({
|
|
863
|
+
id: record.id,
|
|
864
|
+
workerId: options.workerId,
|
|
865
|
+
error: "Event publisher failed",
|
|
866
|
+
availableAt: new Date(now().getTime() + (options.retryDelayMs?.(record.attemptCount) ?? 1e3)),
|
|
867
|
+
deadLetter
|
|
868
|
+
});
|
|
869
|
+
} catch {
|
|
870
|
+
}
|
|
871
|
+
result.failed += 1;
|
|
872
|
+
if (deadLetter) result.deadLettered += 1;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
return result;
|
|
876
|
+
}
|
|
877
|
+
function cloneOutboxRecord(record) {
|
|
878
|
+
return {
|
|
879
|
+
...record,
|
|
880
|
+
event: { ...record.event, ...record.event.traceContext ? { traceContext: { ...record.event.traceContext } } : {} }
|
|
881
|
+
};
|
|
882
|
+
}
|
|
883
|
+
|
|
774
884
|
// src/memory-store.ts
|
|
775
885
|
var MemoryPlatformHostStore = class {
|
|
776
|
-
constructor(db) {
|
|
886
|
+
constructor(db, transactionProvider) {
|
|
777
887
|
this.db = db;
|
|
888
|
+
this.transactionalOutbox = transactionProvider !== void 0;
|
|
889
|
+
if (transactionProvider) this.transactionWithEvents = (run) => this.runTransactionWithEvents(run, transactionProvider);
|
|
778
890
|
}
|
|
779
891
|
db;
|
|
892
|
+
transactionalOutbox;
|
|
893
|
+
transactionTail = Promise.resolve();
|
|
780
894
|
invocations = [];
|
|
781
895
|
policyEvaluations = [];
|
|
782
896
|
adapterInvocations = [];
|
|
@@ -784,9 +898,54 @@ var MemoryPlatformHostStore = class {
|
|
|
784
898
|
policyObligations = [];
|
|
785
899
|
executionAttestations = [];
|
|
786
900
|
externalReconciliations = [];
|
|
901
|
+
outbox = [];
|
|
787
902
|
async transaction(run) {
|
|
788
903
|
return run(this.db);
|
|
789
904
|
}
|
|
905
|
+
async runTransactionWithEvents(run, transactionProvider) {
|
|
906
|
+
let release;
|
|
907
|
+
const previous = this.transactionTail;
|
|
908
|
+
this.transactionTail = new Promise((resolve) => {
|
|
909
|
+
release = resolve;
|
|
910
|
+
});
|
|
911
|
+
await previous;
|
|
912
|
+
let domainSnapshot;
|
|
913
|
+
let snapshotCreated = false;
|
|
914
|
+
try {
|
|
915
|
+
domainSnapshot = transactionProvider.snapshot(this.db);
|
|
916
|
+
snapshotCreated = true;
|
|
917
|
+
const pendingEvents = [];
|
|
918
|
+
const pendingUpdates = [];
|
|
919
|
+
const appendPending = async (event, metadata) => {
|
|
920
|
+
if (this.events.some((candidate) => candidate.id === event.id) || pendingEvents.some((candidate) => candidate.event.id === event.id)) return;
|
|
921
|
+
pendingEvents.push({ event, ...metadata ? { metadata } : {} });
|
|
922
|
+
};
|
|
923
|
+
const result = await run({
|
|
924
|
+
db: this.db,
|
|
925
|
+
appendEvent: (event) => appendPending(event),
|
|
926
|
+
appendEventWithOutbox: (event, metadata) => appendPending(event, metadata),
|
|
927
|
+
nextEventSequence: async (tenantId, spaceId) => (await this.listEvents(tenantId, spaceId)).length + pendingEvents.filter((candidate) => candidate.event.tenantId === tenantId && candidate.event.spaceId === spaceId).length + 1,
|
|
928
|
+
listEvents: async (tenantId, spaceId) => [...await this.listEvents(tenantId, spaceId), ...pendingEvents.map((candidate) => candidate.event).filter((event) => event.tenantId === tenantId && event.spaceId === spaceId)],
|
|
929
|
+
updateActionInvocation: async (id, tenantId, spaceId, patch) => {
|
|
930
|
+
pendingUpdates.push({ id, tenantId, spaceId, patch });
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
for (const update of pendingUpdates) {
|
|
934
|
+
if (!await this.getActionInvocation(update.id, update.tenantId, update.spaceId)) throw new Error(`ActionInvocation not found: ${update.id}`);
|
|
935
|
+
}
|
|
936
|
+
for (const pending of pendingEvents) {
|
|
937
|
+
if (pending.metadata) await this.appendEventWithOutbox(pending.event, pending.metadata);
|
|
938
|
+
else await this.appendEvent(pending.event);
|
|
939
|
+
}
|
|
940
|
+
for (const update of pendingUpdates) await this.updateActionInvocation(update.id, update.tenantId, update.spaceId, update.patch);
|
|
941
|
+
return result;
|
|
942
|
+
} catch (error) {
|
|
943
|
+
if (snapshotCreated) transactionProvider.restore(this.db, domainSnapshot);
|
|
944
|
+
throw error;
|
|
945
|
+
} finally {
|
|
946
|
+
release();
|
|
947
|
+
}
|
|
948
|
+
}
|
|
790
949
|
async createActionInvocation(input) {
|
|
791
950
|
if (input.idempotencyKey) {
|
|
792
951
|
const existing = this.invocations.find(
|
|
@@ -902,6 +1061,53 @@ var MemoryPlatformHostStore = class {
|
|
|
902
1061
|
if (this.events.some((candidate) => candidate.id === event.id)) return;
|
|
903
1062
|
this.events.push(event);
|
|
904
1063
|
}
|
|
1064
|
+
async appendEventWithOutbox(event, metadata) {
|
|
1065
|
+
if (this.events.some((candidate) => candidate.id === event.id)) return;
|
|
1066
|
+
const createdAt = new Date(event.recordedAt);
|
|
1067
|
+
this.events.push(event);
|
|
1068
|
+
this.outbox.push({
|
|
1069
|
+
id: event.id,
|
|
1070
|
+
tenantId: event.tenantId,
|
|
1071
|
+
spaceId: event.spaceId,
|
|
1072
|
+
event: toEnterpriseEventEnvelope(event, metadata),
|
|
1073
|
+
status: "pending",
|
|
1074
|
+
attemptCount: 0,
|
|
1075
|
+
availableAt: createdAt,
|
|
1076
|
+
createdAt
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
async claimOutbox(input) {
|
|
1080
|
+
const current = input.now ?? /* @__PURE__ */ new Date();
|
|
1081
|
+
return this.outbox.filter((record) => record.status === "pending" && record.availableAt <= current && (!record.leaseExpiresAt || record.leaseExpiresAt <= current) && (!input.tenantId || record.tenantId === input.tenantId) && (!input.spaceId || record.spaceId === input.spaceId)).sort((left, right) => left.createdAt.getTime() - right.createdAt.getTime() || left.id.localeCompare(right.id)).slice(0, Math.max(1, Math.min(input.limit ?? 100, 1e3))).map((record) => {
|
|
1082
|
+
record.leaseOwner = input.workerId;
|
|
1083
|
+
record.leaseExpiresAt = new Date(current.getTime() + input.leaseDurationMs);
|
|
1084
|
+
record.attemptCount += 1;
|
|
1085
|
+
return cloneOutboxRecord(record);
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
async markOutboxPublished(id, workerId, publishedAt) {
|
|
1089
|
+
const record = this.requireLeasedOutbox(id, workerId);
|
|
1090
|
+
record.status = "published";
|
|
1091
|
+
record.publishedAt = publishedAt;
|
|
1092
|
+
delete record.leaseOwner;
|
|
1093
|
+
delete record.leaseExpiresAt;
|
|
1094
|
+
}
|
|
1095
|
+
async markOutboxFailed(input) {
|
|
1096
|
+
const record = this.requireLeasedOutbox(input.id, input.workerId);
|
|
1097
|
+
record.status = input.deadLetter ? "dead_letter" : "pending";
|
|
1098
|
+
record.lastError = input.error;
|
|
1099
|
+
record.availableAt = input.availableAt;
|
|
1100
|
+
delete record.leaseOwner;
|
|
1101
|
+
delete record.leaseExpiresAt;
|
|
1102
|
+
}
|
|
1103
|
+
async listOutbox(input = {}) {
|
|
1104
|
+
return this.outbox.filter((record) => (!input.tenantId || record.tenantId === input.tenantId) && (!input.spaceId || record.spaceId === input.spaceId) && (!input.statuses?.length || input.statuses.includes(record.status))).map(cloneOutboxRecord);
|
|
1105
|
+
}
|
|
1106
|
+
requireLeasedOutbox(id, workerId) {
|
|
1107
|
+
const record = this.outbox.find((candidate) => candidate.id === id);
|
|
1108
|
+
if (!record || record.leaseOwner !== workerId) throw new Error(`Outbox record ${id} is not leased by ${workerId}.`);
|
|
1109
|
+
return record;
|
|
1110
|
+
}
|
|
905
1111
|
async nextEventSequence(tenantId, spaceId) {
|
|
906
1112
|
return this.events.filter((event) => event.tenantId === tenantId && event.spaceId === spaceId).length + 1;
|
|
907
1113
|
}
|
|
@@ -945,12 +1151,14 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
945
1151
|
constructor(db, sql, transactionProvider) {
|
|
946
1152
|
this.db = db;
|
|
947
1153
|
this.sql = sql;
|
|
1154
|
+
this.transactionalOutbox = transactionProvider !== void 0;
|
|
948
1155
|
if (transactionProvider) {
|
|
949
1156
|
this.transactionWithEvents = async (run) => transactionProvider.run(async ({ db: db2, sql: sql2 }) => {
|
|
950
1157
|
const scoped = new _PostgresPlatformHostStore(db2, sql2);
|
|
951
1158
|
return run({
|
|
952
1159
|
db: db2,
|
|
953
1160
|
appendEvent: (event) => scoped.appendEvent(event),
|
|
1161
|
+
appendEventWithOutbox: (event, metadata) => scoped.appendEventWithOutbox(event, metadata),
|
|
954
1162
|
nextEventSequence: (tenantId, spaceId) => scoped.nextEventSequence(tenantId, spaceId),
|
|
955
1163
|
listEvents: (tenantId, spaceId) => scoped.listEvents(tenantId, spaceId),
|
|
956
1164
|
updateActionInvocation: (id, tenantId, spaceId, patch) => scoped.updateActionInvocation(id, tenantId, spaceId, patch)
|
|
@@ -961,6 +1169,7 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
961
1169
|
db;
|
|
962
1170
|
sql;
|
|
963
1171
|
transactionWithEvents;
|
|
1172
|
+
transactionalOutbox;
|
|
964
1173
|
async ensureSchema() {
|
|
965
1174
|
await this.sql.query(`
|
|
966
1175
|
CREATE SCHEMA IF NOT EXISTS fabric_platform;
|
|
@@ -1044,6 +1253,15 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1044
1253
|
CREATE INDEX IF NOT EXISTS asset_events_subject_idx
|
|
1045
1254
|
ON fabric_platform.asset_events
|
|
1046
1255
|
(tenant_id, space_id, subject_type, subject_id, sequence);
|
|
1256
|
+
CREATE TABLE IF NOT EXISTS fabric_platform.event_outbox (
|
|
1257
|
+
id text PRIMARY KEY, tenant_id text NOT NULL, space_id text NOT NULL,
|
|
1258
|
+
event jsonb NOT NULL, status text NOT NULL DEFAULT 'pending',
|
|
1259
|
+
attempt_count integer NOT NULL DEFAULT 0, available_at timestamptz NOT NULL,
|
|
1260
|
+
lease_owner text, lease_expires_at timestamptz, last_error text,
|
|
1261
|
+
created_at timestamptz NOT NULL, published_at timestamptz
|
|
1262
|
+
);
|
|
1263
|
+
CREATE INDEX IF NOT EXISTS event_outbox_claim_idx
|
|
1264
|
+
ON fabric_platform.event_outbox (status, available_at, lease_expires_at, created_at);
|
|
1047
1265
|
`);
|
|
1048
1266
|
}
|
|
1049
1267
|
async transaction(run) {
|
|
@@ -1326,6 +1544,86 @@ var PostgresPlatformHostStore = class _PostgresPlatformHostStore {
|
|
|
1326
1544
|
]
|
|
1327
1545
|
);
|
|
1328
1546
|
}
|
|
1547
|
+
async appendEventWithOutbox(event, metadata) {
|
|
1548
|
+
const envelope = toEnterpriseEventEnvelope(event, metadata);
|
|
1549
|
+
await this.sql.query(
|
|
1550
|
+
`WITH inserted_event AS (
|
|
1551
|
+
INSERT INTO fabric_platform.asset_events
|
|
1552
|
+
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
1553
|
+
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
1554
|
+
correlation_id,causation_id)
|
|
1555
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16)
|
|
1556
|
+
ON CONFLICT (id) DO NOTHING RETURNING id
|
|
1557
|
+
)
|
|
1558
|
+
INSERT INTO fabric_platform.event_outbox
|
|
1559
|
+
(id,tenant_id,space_id,event,status,attempt_count,available_at,created_at)
|
|
1560
|
+
SELECT $1,$2,$3,$17::jsonb,'pending',0,$14,$14 FROM inserted_event
|
|
1561
|
+
ON CONFLICT (id) DO NOTHING`,
|
|
1562
|
+
[
|
|
1563
|
+
event.id,
|
|
1564
|
+
event.tenantId,
|
|
1565
|
+
event.spaceId,
|
|
1566
|
+
event.eventType,
|
|
1567
|
+
event.eventSchemaVersion,
|
|
1568
|
+
event.subjectType,
|
|
1569
|
+
event.subjectId,
|
|
1570
|
+
event.actorId,
|
|
1571
|
+
event.actorType,
|
|
1572
|
+
event.actionInvocationId ?? null,
|
|
1573
|
+
JSON.stringify(event.payload),
|
|
1574
|
+
event.sequence,
|
|
1575
|
+
event.occurredAt,
|
|
1576
|
+
event.recordedAt,
|
|
1577
|
+
event.correlationId,
|
|
1578
|
+
event.causationId ?? null,
|
|
1579
|
+
JSON.stringify(envelope)
|
|
1580
|
+
]
|
|
1581
|
+
);
|
|
1582
|
+
}
|
|
1583
|
+
async claimOutbox(input) {
|
|
1584
|
+
const current = input.now ?? /* @__PURE__ */ new Date();
|
|
1585
|
+
const leaseExpiresAt = new Date(current.getTime() + input.leaseDurationMs);
|
|
1586
|
+
const result = await this.sql.query(
|
|
1587
|
+
`WITH claimable AS (
|
|
1588
|
+
SELECT id FROM fabric_platform.event_outbox
|
|
1589
|
+
WHERE status='pending' AND available_at <= $1
|
|
1590
|
+
AND (lease_expires_at IS NULL OR lease_expires_at <= $1)
|
|
1591
|
+
AND ($2::text IS NULL OR tenant_id=$2)
|
|
1592
|
+
AND ($3::text IS NULL OR space_id=$3)
|
|
1593
|
+
ORDER BY created_at,id FOR UPDATE SKIP LOCKED LIMIT $4
|
|
1594
|
+
)
|
|
1595
|
+
UPDATE fabric_platform.event_outbox AS item
|
|
1596
|
+
SET lease_owner=$5, lease_expires_at=$6, attempt_count=attempt_count+1
|
|
1597
|
+
FROM claimable WHERE item.id=claimable.id RETURNING item.*`,
|
|
1598
|
+
[current, input.tenantId ?? null, input.spaceId ?? null, Math.max(1, Math.min(input.limit ?? 100, 1e3)), input.workerId, leaseExpiresAt]
|
|
1599
|
+
);
|
|
1600
|
+
return result.rows.map(toOutboxRecord);
|
|
1601
|
+
}
|
|
1602
|
+
async markOutboxPublished(id, workerId, publishedAt) {
|
|
1603
|
+
const result = await this.sql.query(
|
|
1604
|
+
`UPDATE fabric_platform.event_outbox SET status='published',published_at=$3,
|
|
1605
|
+
lease_owner=NULL,lease_expires_at=NULL WHERE id=$1 AND lease_owner=$2 RETURNING id`,
|
|
1606
|
+
[id, workerId, publishedAt]
|
|
1607
|
+
);
|
|
1608
|
+
if (result.rows.length === 0) throw new Error(`Outbox record ${id} is not leased by ${workerId}.`);
|
|
1609
|
+
}
|
|
1610
|
+
async markOutboxFailed(input) {
|
|
1611
|
+
const result = await this.sql.query(
|
|
1612
|
+
`UPDATE fabric_platform.event_outbox SET status=$3,last_error=$4,available_at=$5,
|
|
1613
|
+
lease_owner=NULL,lease_expires_at=NULL WHERE id=$1 AND lease_owner=$2 RETURNING id`,
|
|
1614
|
+
[input.id, input.workerId, input.deadLetter ? "dead_letter" : "pending", input.error, input.availableAt]
|
|
1615
|
+
);
|
|
1616
|
+
if (result.rows.length === 0) throw new Error(`Outbox record ${input.id} is not leased by ${input.workerId}.`);
|
|
1617
|
+
}
|
|
1618
|
+
async listOutbox(input = {}) {
|
|
1619
|
+
const result = await this.sql.query(
|
|
1620
|
+
`SELECT * FROM fabric_platform.event_outbox
|
|
1621
|
+
WHERE ($1::text IS NULL OR tenant_id=$1) AND ($2::text IS NULL OR space_id=$2)
|
|
1622
|
+
AND ($3::text[] IS NULL OR status=ANY($3::text[])) ORDER BY created_at,id`,
|
|
1623
|
+
[input.tenantId ?? null, input.spaceId ?? null, input.statuses?.length ? input.statuses : null]
|
|
1624
|
+
);
|
|
1625
|
+
return result.rows.map(toOutboxRecord);
|
|
1626
|
+
}
|
|
1329
1627
|
async nextEventSequence(tenantId, spaceId) {
|
|
1330
1628
|
const result = await this.sql.query(
|
|
1331
1629
|
`INSERT INTO fabric_platform.event_sequences (tenant_id,space_id,next_sequence)
|
|
@@ -1514,6 +1812,41 @@ function toEventRecord(row) {
|
|
|
1514
1812
|
...row.causation_id ? { causationId: String(row.causation_id) } : {}
|
|
1515
1813
|
};
|
|
1516
1814
|
}
|
|
1815
|
+
function toOutboxRecord(row) {
|
|
1816
|
+
const event = row.event;
|
|
1817
|
+
return {
|
|
1818
|
+
id: String(row.id),
|
|
1819
|
+
tenantId: String(row.tenant_id),
|
|
1820
|
+
spaceId: String(row.space_id),
|
|
1821
|
+
event: {
|
|
1822
|
+
eventId: String(event.eventId),
|
|
1823
|
+
eventType: String(event.eventType),
|
|
1824
|
+
eventSchemaVersion: Number(event.eventSchemaVersion),
|
|
1825
|
+
tenantId: String(event.tenantId),
|
|
1826
|
+
spaceId: String(event.spaceId),
|
|
1827
|
+
subjectType: String(event.subjectType),
|
|
1828
|
+
subjectId: String(event.subjectId),
|
|
1829
|
+
sequence: Number(event.sequence),
|
|
1830
|
+
...event.actionInvocationId ? { actionInvocationId: String(event.actionInvocationId) } : {},
|
|
1831
|
+
correlationId: String(event.correlationId),
|
|
1832
|
+
...event.causationId ? { causationId: String(event.causationId) } : {},
|
|
1833
|
+
occurredAt: new Date(event.occurredAt),
|
|
1834
|
+
recordedAt: new Date(event.recordedAt),
|
|
1835
|
+
producerModuleVersion: String(event.producerModuleVersion),
|
|
1836
|
+
payloadClassification: String(event.payloadClassification),
|
|
1837
|
+
...event.traceContext ? { traceContext: event.traceContext } : {},
|
|
1838
|
+
payload: event.payload
|
|
1839
|
+
},
|
|
1840
|
+
status: String(row.status),
|
|
1841
|
+
attemptCount: Number(row.attempt_count),
|
|
1842
|
+
availableAt: new Date(row.available_at),
|
|
1843
|
+
...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
|
|
1844
|
+
...row.lease_expires_at ? { leaseExpiresAt: new Date(row.lease_expires_at) } : {},
|
|
1845
|
+
...row.last_error ? { lastError: String(row.last_error) } : {},
|
|
1846
|
+
createdAt: new Date(row.created_at),
|
|
1847
|
+
...row.published_at ? { publishedAt: new Date(row.published_at) } : {}
|
|
1848
|
+
};
|
|
1849
|
+
}
|
|
1517
1850
|
|
|
1518
1851
|
// src/worker.ts
|
|
1519
1852
|
var DEFAULT_BATCH_SIZE = 10;
|
|
@@ -1583,6 +1916,6 @@ async function abortableDelay(milliseconds, signal) {
|
|
|
1583
1916
|
});
|
|
1584
1917
|
}
|
|
1585
1918
|
|
|
1586
|
-
export { MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, PostgresPlatformHostStore, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
|
|
1919
|
+
export { MemoryPlatformHostStore, PLATFORM_HOST_CONTRACT_VERSION, PostgresPlatformHostStore, cloneOutboxRecord, createGovernedActionHost, createStoreBackedActionDispatcher, runOutboxRelayCycle, runPlatformActionWorker, runPlatformActionWorkerCycle, toEnterpriseEventEnvelope };
|
|
1587
1920
|
//# sourceMappingURL=index.js.map
|
|
1588
1921
|
//# sourceMappingURL=index.js.map
|