@fabricorg/platform-host 0.2.0 → 0.3.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 +14 -0
- package/README.md +11 -0
- package/dist/index.cjs +444 -209
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +64 -2
- package/dist/index.d.ts +64 -2
- package/dist/index.js +442 -210
- package/dist/index.js.map +1 -1
- package/package.json +62 -66
package/dist/index.js
CHANGED
|
@@ -22,9 +22,8 @@ function createGovernedActionHost(options) {
|
|
|
22
22
|
}
|
|
23
23
|
const actionInvocationId = createFabricId("act");
|
|
24
24
|
const correlationId = input.correlationId ?? createFabricId("corr");
|
|
25
|
-
const workflowId = `action-invocation-${actionInvocationId}`;
|
|
26
25
|
const durableParameters = options.redactActionParameters ? options.redactActionParameters(input.actionId, input.parameters) : input.parameters;
|
|
27
|
-
await options.store.createActionInvocation({
|
|
26
|
+
const durableInvocation = await options.store.createActionInvocation({
|
|
28
27
|
id: actionInvocationId,
|
|
29
28
|
tenantId: input.tenantId,
|
|
30
29
|
spaceId: input.spaceId,
|
|
@@ -36,26 +35,37 @@ function createGovernedActionHost(options) {
|
|
|
36
35
|
parameters: durableParameters,
|
|
37
36
|
result: {},
|
|
38
37
|
correlationId,
|
|
39
|
-
...input.causationId ? { causationId: input.causationId } : {}
|
|
38
|
+
...input.causationId ? { causationId: input.causationId } : {},
|
|
39
|
+
...input.idempotencyKey ? { idempotencyKey: input.idempotencyKey } : {}
|
|
40
40
|
});
|
|
41
|
+
const durableWorkflowId = `action-invocation-${durableInvocation.id}`;
|
|
42
|
+
if (durableInvocation.id !== actionInvocationId && isTerminal(durableInvocation.status)) {
|
|
43
|
+
return {
|
|
44
|
+
actionInvocationId: durableInvocation.id,
|
|
45
|
+
status: durableInvocation.status,
|
|
46
|
+
workflowId: durableWorkflowId,
|
|
47
|
+
result: durableInvocation.result,
|
|
48
|
+
...durableInvocation.error ? { error: durableInvocation.error } : {}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
41
51
|
if (options.dispatcher) {
|
|
42
52
|
try {
|
|
43
53
|
const dispatched = await options.dispatcher.dispatch({
|
|
44
|
-
actionInvocationId,
|
|
54
|
+
actionInvocationId: durableInvocation.id,
|
|
45
55
|
tenantId: input.tenantId,
|
|
46
56
|
spaceId: input.spaceId,
|
|
47
|
-
workflowId
|
|
57
|
+
workflowId: durableWorkflowId
|
|
48
58
|
});
|
|
49
59
|
return {
|
|
50
|
-
actionInvocationId,
|
|
51
|
-
status:
|
|
60
|
+
actionInvocationId: durableInvocation.id,
|
|
61
|
+
status: durableInvocation.status,
|
|
52
62
|
workflowId: dispatched.workflowId,
|
|
53
63
|
...dispatched.runId ? { runId: dispatched.runId } : {}
|
|
54
64
|
};
|
|
55
65
|
} catch (error) {
|
|
56
66
|
const message = errorMessage(error);
|
|
57
67
|
await options.store.updateActionInvocation(
|
|
58
|
-
|
|
68
|
+
durableInvocation.id,
|
|
59
69
|
input.tenantId,
|
|
60
70
|
input.spaceId,
|
|
61
71
|
{ status: "failed", error: message }
|
|
@@ -64,11 +74,11 @@ function createGovernedActionHost(options) {
|
|
|
64
74
|
}
|
|
65
75
|
}
|
|
66
76
|
const executed = await executeInvocation(
|
|
67
|
-
|
|
77
|
+
durableInvocation.id,
|
|
68
78
|
input.tenantId,
|
|
69
79
|
input.spaceId
|
|
70
80
|
);
|
|
71
|
-
return { ...executed, workflowId };
|
|
81
|
+
return { ...executed, workflowId: durableWorkflowId };
|
|
72
82
|
}
|
|
73
83
|
async function executeInvocation(actionInvocationId, tenantId, spaceId) {
|
|
74
84
|
const invocation = await options.store.getActionInvocation(
|
|
@@ -89,223 +99,243 @@ function createGovernedActionHost(options) {
|
|
|
89
99
|
if (!action) {
|
|
90
100
|
return fail(invocation, "failed", `Unknown action: ${invocation.actionId}`);
|
|
91
101
|
}
|
|
102
|
+
if (invocation.attemptCount > 1 && !action.idempotent) {
|
|
103
|
+
return fail(
|
|
104
|
+
invocation,
|
|
105
|
+
"failed",
|
|
106
|
+
`Interrupted action ${invocation.actionId} is not declared idempotent; manual reconciliation is required.`
|
|
107
|
+
);
|
|
108
|
+
}
|
|
92
109
|
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
|
|
93
110
|
status: "running"
|
|
94
111
|
});
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
spaceId,
|
|
108
|
-
actionInvocationId,
|
|
109
|
-
actionId: action.actionId,
|
|
110
|
-
actorId: invocation.actorId,
|
|
111
|
-
actorType: invocation.actorType,
|
|
112
|
-
parameters: parsed.data,
|
|
113
|
-
db: options.store.db,
|
|
114
|
-
services: options.services,
|
|
115
|
-
mode: "execute"
|
|
116
|
-
});
|
|
117
|
-
for (const outcome of outcomes) {
|
|
118
|
-
await options.store.appendPolicyEvaluation({
|
|
119
|
-
id: createFabricId("pol"),
|
|
120
|
-
actionInvocationId,
|
|
112
|
+
try {
|
|
113
|
+
const parsed = action.schema.safeParse(invocation.parameters);
|
|
114
|
+
if (!parsed.success) {
|
|
115
|
+
const message = parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
116
|
+
return fail(invocation, "validation_failed", message);
|
|
117
|
+
}
|
|
118
|
+
const authorizationInput = toAuthorizationInput(action, invocation);
|
|
119
|
+
const definitions = options.resolvePolicies ? await options.resolvePolicies({
|
|
120
|
+
...authorizationInput,
|
|
121
|
+
declaredPolicyIds: action.policies ?? []
|
|
122
|
+
}) : declaredPolicies(action.policies ?? []);
|
|
123
|
+
const outcomes = await evaluatePolicyDefinitions(definitions, {
|
|
121
124
|
tenantId,
|
|
122
125
|
spaceId,
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
eventType: "ComplianceBlocked",
|
|
132
|
-
subjectType: "ActionInvocation",
|
|
133
|
-
subjectId: actionInvocationId,
|
|
134
|
-
payload: { actionId: action.actionId, policyId: aggregate.policyId, reason }
|
|
126
|
+
actionInvocationId,
|
|
127
|
+
actionId: action.actionId,
|
|
128
|
+
actorId: invocation.actorId,
|
|
129
|
+
actorType: invocation.actorType,
|
|
130
|
+
parameters: parsed.data,
|
|
131
|
+
db: options.store.db,
|
|
132
|
+
services: options.services,
|
|
133
|
+
mode: "execute"
|
|
135
134
|
});
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
entityId
|
|
146
|
-
) ?? initialState(binding.entityType) : initialState(binding.entityType);
|
|
147
|
-
const targetState = typeof binding.targetState === "function" ? binding.targetState(parsed.data) : binding.targetState;
|
|
148
|
-
const transition = validateTransition(
|
|
149
|
-
binding.entityType,
|
|
150
|
-
currentState,
|
|
151
|
-
targetState,
|
|
152
|
-
action.actionId
|
|
153
|
-
);
|
|
154
|
-
if (!transition.valid) {
|
|
155
|
-
return fail(invocation, "failed", transition.error ?? "Invalid state transition");
|
|
135
|
+
for (const outcome of outcomes) {
|
|
136
|
+
await options.store.appendPolicyEvaluation({
|
|
137
|
+
id: lifecycleId("pol", actionInvocationId, outcome.policyId),
|
|
138
|
+
actionInvocationId,
|
|
139
|
+
tenantId,
|
|
140
|
+
spaceId,
|
|
141
|
+
outcome,
|
|
142
|
+
createdAt: now()
|
|
143
|
+
});
|
|
156
144
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
actorId: invocation.actorId,
|
|
168
|
-
actorType: invocation.actorType,
|
|
169
|
-
correlationId: invocation.correlationId,
|
|
170
|
-
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
171
|
-
db,
|
|
172
|
-
services: options.services
|
|
173
|
-
},
|
|
174
|
-
parsed.data
|
|
175
|
-
) : Promise.resolve({ success: true, data: {} })
|
|
176
|
-
);
|
|
177
|
-
if (!handlerResult.success) {
|
|
178
|
-
return fail(invocation, "failed", handlerResult.error ?? "Action handler failed");
|
|
145
|
+
const aggregate = aggregatePolicyOutcomes(outcomes);
|
|
146
|
+
if (aggregate?.result === "block") {
|
|
147
|
+
const reason = aggregate.reason ?? `Blocked by policy ${aggregate.policyId}`;
|
|
148
|
+
await appendEvent(invocation, {
|
|
149
|
+
eventType: "ComplianceBlocked",
|
|
150
|
+
subjectType: "ActionInvocation",
|
|
151
|
+
subjectId: actionInvocationId,
|
|
152
|
+
payload: { actionId: action.actionId, policyId: aggregate.policyId, reason }
|
|
153
|
+
}, `compliance:${aggregate.policyId}`);
|
|
154
|
+
return fail(invocation, "blocked_by_policy", reason);
|
|
179
155
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
156
|
+
const binding = action.stateMachine;
|
|
157
|
+
if (binding) {
|
|
158
|
+
const entityId = binding.getEntityId(parsed.data);
|
|
159
|
+
const currentState = entityId ? await options.store.getEntityState(
|
|
160
|
+
tenantId,
|
|
161
|
+
spaceId,
|
|
162
|
+
binding.entityType,
|
|
163
|
+
entityId
|
|
164
|
+
) ?? initialState(binding.entityType) : initialState(binding.entityType);
|
|
165
|
+
const targetState = typeof binding.targetState === "function" ? binding.targetState(parsed.data) : binding.targetState;
|
|
166
|
+
const transition = validateTransition(
|
|
167
|
+
binding.entityType,
|
|
168
|
+
currentState,
|
|
169
|
+
targetState,
|
|
170
|
+
action.actionId
|
|
171
|
+
);
|
|
172
|
+
const replayingAppliedTransition = action.idempotent && currentState === targetState;
|
|
173
|
+
if (!transition.valid && !replayingAppliedTransition) {
|
|
174
|
+
return fail(invocation, "failed", transition.error ?? "Invalid state transition");
|
|
175
|
+
}
|
|
184
176
|
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
}
|
|
188
|
-
for (const step of action.adapterSteps ?? []) {
|
|
189
|
-
const input = step.getInput(parsed.data, data);
|
|
190
|
-
if (!input) continue;
|
|
191
|
-
const adapter = adapters.require(step.adapterType, step.operation);
|
|
192
|
-
const adapterInvocationId = createFabricId("adp");
|
|
193
|
-
const adapterEventSubject = options.adapterEventSubject?.(
|
|
194
|
-
action.actionId,
|
|
195
|
-
parsed.data,
|
|
196
|
-
data
|
|
197
|
-
) ?? { subjectType: "AdapterInvocation", subjectId: adapterInvocationId };
|
|
198
|
-
const recordedInput = options.redactAdapterInput ? options.redactAdapterInput(
|
|
199
|
-
action.actionId,
|
|
200
|
-
step.adapterType,
|
|
201
|
-
step.operation,
|
|
202
|
-
input
|
|
203
|
-
) : input;
|
|
204
|
-
const startedAt = now();
|
|
205
|
-
const record = {
|
|
206
|
-
id: adapterInvocationId,
|
|
207
|
-
actionInvocationId,
|
|
208
|
-
tenantId,
|
|
209
|
-
spaceId,
|
|
210
|
-
adapterType: step.adapterType,
|
|
211
|
-
operation: step.operation,
|
|
212
|
-
vendor: adapter.vendor,
|
|
213
|
-
status: "running",
|
|
214
|
-
input: recordedInput,
|
|
215
|
-
attempt: 1,
|
|
216
|
-
createdAt: startedAt,
|
|
217
|
-
updatedAt: startedAt
|
|
218
|
-
};
|
|
219
|
-
await options.store.createAdapterInvocation(record);
|
|
220
|
-
await appendEvent(invocation, {
|
|
221
|
-
eventType: "AdapterInvocationStarted",
|
|
222
|
-
subjectType: adapterEventSubject.subjectType,
|
|
223
|
-
subjectId: adapterEventSubject.subjectId,
|
|
224
|
-
payload: { adapterType: step.adapterType, operation: step.operation }
|
|
225
|
-
});
|
|
177
|
+
let data;
|
|
178
|
+
let domainEvents = [];
|
|
226
179
|
try {
|
|
227
|
-
const
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
await options.store.updateAdapterInvocation(adapterInvocationId, {
|
|
232
|
-
attempt,
|
|
233
|
-
updatedAt: now()
|
|
234
|
-
});
|
|
235
|
-
return adapter.execute(input, {
|
|
180
|
+
const handlerResult = await options.store.transaction(
|
|
181
|
+
(db) => action.handler ? action.handler(
|
|
182
|
+
{
|
|
183
|
+
actionInvocationId,
|
|
236
184
|
tenantId,
|
|
237
185
|
spaceId,
|
|
238
|
-
|
|
239
|
-
|
|
186
|
+
actorId: invocation.actorId,
|
|
187
|
+
actorType: invocation.actorType,
|
|
240
188
|
correlationId: invocation.correlationId,
|
|
241
189
|
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
242
|
-
|
|
243
|
-
|
|
190
|
+
db,
|
|
191
|
+
services: options.services
|
|
192
|
+
},
|
|
193
|
+
parsed.data
|
|
194
|
+
) : Promise.resolve({ success: true, data: {} })
|
|
195
|
+
);
|
|
196
|
+
if (!handlerResult.success) {
|
|
197
|
+
return fail(invocation, "failed", handlerResult.error ?? "Action handler failed");
|
|
198
|
+
}
|
|
199
|
+
data = handlerResult.data ?? {};
|
|
200
|
+
domainEvents = extractEvents(data);
|
|
201
|
+
if (action.eventPhase !== "after_adapters") {
|
|
202
|
+
for (const [index, event] of domainEvents.entries()) {
|
|
203
|
+
await appendEvent(invocation, event, `domain:${index}`);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
} catch (error) {
|
|
207
|
+
return fail(invocation, "failed", errorMessage(error));
|
|
208
|
+
}
|
|
209
|
+
for (const [stepIndex, step] of (action.adapterSteps ?? []).entries()) {
|
|
210
|
+
const input = step.getInput(parsed.data, data);
|
|
211
|
+
if (!input) continue;
|
|
212
|
+
const adapter = adapters.require(step.adapterType, step.operation);
|
|
213
|
+
const adapterInvocationId = lifecycleId("adp", actionInvocationId, String(stepIndex));
|
|
214
|
+
const previousAdapterInvocation = await options.store.getAdapterInvocation(
|
|
215
|
+
adapterInvocationId
|
|
216
|
+
);
|
|
217
|
+
if (previousAdapterInvocation?.status === "succeeded") continue;
|
|
218
|
+
const adapterEventSubject = options.adapterEventSubject?.(
|
|
219
|
+
action.actionId,
|
|
220
|
+
parsed.data,
|
|
221
|
+
data
|
|
222
|
+
) ?? { subjectType: "AdapterInvocation", subjectId: adapterInvocationId };
|
|
223
|
+
const recordedInput = options.redactAdapterInput ? options.redactAdapterInput(
|
|
224
|
+
action.actionId,
|
|
225
|
+
step.adapterType,
|
|
226
|
+
step.operation,
|
|
227
|
+
input
|
|
228
|
+
) : input;
|
|
229
|
+
const startedAt = now();
|
|
230
|
+
const record = {
|
|
231
|
+
id: adapterInvocationId,
|
|
232
|
+
actionInvocationId,
|
|
233
|
+
tenantId,
|
|
234
|
+
spaceId,
|
|
235
|
+
adapterType: step.adapterType,
|
|
236
|
+
operation: step.operation,
|
|
237
|
+
vendor: adapter.vendor,
|
|
238
|
+
status: "running",
|
|
239
|
+
input: recordedInput,
|
|
240
|
+
attempt: 1,
|
|
241
|
+
createdAt: startedAt,
|
|
242
|
+
updatedAt: startedAt
|
|
243
|
+
};
|
|
244
|
+
await options.store.createAdapterInvocation(record);
|
|
245
|
+
await appendEvent(invocation, {
|
|
246
|
+
eventType: "AdapterInvocationStarted",
|
|
247
|
+
subjectType: adapterEventSubject.subjectType,
|
|
248
|
+
subjectId: adapterEventSubject.subjectId,
|
|
249
|
+
payload: { adapterType: step.adapterType, operation: step.operation }
|
|
250
|
+
}, `adapter:${stepIndex}:started`);
|
|
251
|
+
try {
|
|
252
|
+
const result2 = await executeWithAdapterRetry({
|
|
253
|
+
policy: step.retryPolicy ?? adapter.retryPolicy,
|
|
254
|
+
defaultIdempotent: adapter.idempotent,
|
|
255
|
+
execute: async (attempt, maxAttempts) => {
|
|
256
|
+
await options.store.updateAdapterInvocation(adapterInvocationId, {
|
|
257
|
+
attempt,
|
|
258
|
+
updatedAt: now()
|
|
259
|
+
});
|
|
260
|
+
return adapter.execute(input, {
|
|
261
|
+
tenantId,
|
|
262
|
+
spaceId,
|
|
263
|
+
actionInvocationId,
|
|
264
|
+
adapterInvocationId,
|
|
265
|
+
correlationId: invocation.correlationId,
|
|
266
|
+
...invocation.causationId ? { causationId: invocation.causationId } : {},
|
|
267
|
+
attempt,
|
|
268
|
+
maxAttempts
|
|
269
|
+
});
|
|
270
|
+
},
|
|
271
|
+
isSuccessful: (result3) => result3.success,
|
|
272
|
+
getError: (result3) => result3.error
|
|
273
|
+
});
|
|
274
|
+
if (!result2.success) {
|
|
275
|
+
await options.store.updateAdapterInvocation(adapterInvocationId, {
|
|
276
|
+
status: "failed",
|
|
277
|
+
error: result2.error ?? "Adapter failed",
|
|
278
|
+
updatedAt: now()
|
|
244
279
|
});
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
280
|
+
await appendEvent(invocation, {
|
|
281
|
+
eventType: "AdapterInvocationFailed",
|
|
282
|
+
subjectType: adapterEventSubject.subjectType,
|
|
283
|
+
subjectId: adapterEventSubject.subjectId,
|
|
284
|
+
payload: { adapterType: step.adapterType, operation: step.operation }
|
|
285
|
+
}, `adapter:${stepIndex}:failed`);
|
|
286
|
+
return fail(invocation, "failed", result2.error ?? "Adapter failed");
|
|
287
|
+
}
|
|
288
|
+
const resultRecord = result2;
|
|
289
|
+
const output = isRecord(resultRecord.output) ? resultRecord.output : Object.fromEntries(
|
|
290
|
+
Object.entries(resultRecord).filter(
|
|
291
|
+
([key]) => key !== "success" && key !== "error"
|
|
292
|
+
)
|
|
293
|
+
);
|
|
250
294
|
await options.store.updateAdapterInvocation(adapterInvocationId, {
|
|
251
|
-
status: "
|
|
252
|
-
|
|
295
|
+
status: "succeeded",
|
|
296
|
+
output,
|
|
253
297
|
updatedAt: now()
|
|
254
298
|
});
|
|
255
299
|
await appendEvent(invocation, {
|
|
256
|
-
eventType: "
|
|
300
|
+
eventType: "AdapterInvocationSucceeded",
|
|
257
301
|
subjectType: adapterEventSubject.subjectType,
|
|
258
302
|
subjectId: adapterEventSubject.subjectId,
|
|
259
|
-
payload: {
|
|
303
|
+
payload: {
|
|
304
|
+
adapterType: step.adapterType,
|
|
305
|
+
operation: step.operation,
|
|
306
|
+
input: recordedInput,
|
|
307
|
+
output
|
|
308
|
+
}
|
|
309
|
+
}, `adapter:${stepIndex}:succeeded`);
|
|
310
|
+
} catch (error) {
|
|
311
|
+
const message = errorMessage(error);
|
|
312
|
+
await options.store.updateAdapterInvocation(adapterInvocationId, {
|
|
313
|
+
status: "failed",
|
|
314
|
+
error: message,
|
|
315
|
+
updatedAt: now()
|
|
260
316
|
});
|
|
261
|
-
return fail(invocation, "failed",
|
|
317
|
+
return fail(invocation, "failed", message);
|
|
262
318
|
}
|
|
263
|
-
const resultRecord = result2;
|
|
264
|
-
const output = isRecord(resultRecord.output) ? resultRecord.output : Object.fromEntries(
|
|
265
|
-
Object.entries(resultRecord).filter(
|
|
266
|
-
([key]) => key !== "success" && key !== "error"
|
|
267
|
-
)
|
|
268
|
-
);
|
|
269
|
-
await options.store.updateAdapterInvocation(adapterInvocationId, {
|
|
270
|
-
status: "succeeded",
|
|
271
|
-
output,
|
|
272
|
-
updatedAt: now()
|
|
273
|
-
});
|
|
274
|
-
await appendEvent(invocation, {
|
|
275
|
-
eventType: "AdapterInvocationSucceeded",
|
|
276
|
-
subjectType: adapterEventSubject.subjectType,
|
|
277
|
-
subjectId: adapterEventSubject.subjectId,
|
|
278
|
-
payload: {
|
|
279
|
-
adapterType: step.adapterType,
|
|
280
|
-
operation: step.operation,
|
|
281
|
-
input: recordedInput,
|
|
282
|
-
output
|
|
283
|
-
}
|
|
284
|
-
});
|
|
285
|
-
} catch (error) {
|
|
286
|
-
const message = errorMessage(error);
|
|
287
|
-
await options.store.updateAdapterInvocation(adapterInvocationId, {
|
|
288
|
-
status: "failed",
|
|
289
|
-
error: message,
|
|
290
|
-
updatedAt: now()
|
|
291
|
-
});
|
|
292
|
-
return fail(invocation, "failed", message);
|
|
293
319
|
}
|
|
320
|
+
if (action.eventPhase === "after_adapters") {
|
|
321
|
+
for (const [index, event] of domainEvents.entries()) {
|
|
322
|
+
await appendEvent(invocation, event, `domain:${index}`);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
const result = withoutPrivateHostFields(data);
|
|
326
|
+
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
|
|
327
|
+
status: "completed",
|
|
328
|
+
result
|
|
329
|
+
});
|
|
330
|
+
return { actionInvocationId, status: "completed", result };
|
|
331
|
+
} catch (error) {
|
|
332
|
+
return fail(invocation, "failed", errorMessage(error));
|
|
294
333
|
}
|
|
295
|
-
if (action.eventPhase === "after_adapters") {
|
|
296
|
-
for (const event of domainEvents) await appendEvent(invocation, event);
|
|
297
|
-
}
|
|
298
|
-
const result = withoutPrivateHostFields(data);
|
|
299
|
-
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
|
|
300
|
-
status: "completed",
|
|
301
|
-
result
|
|
302
|
-
});
|
|
303
|
-
return { actionInvocationId, status: "completed", result };
|
|
304
334
|
}
|
|
305
|
-
async function appendEvent(invocation, event) {
|
|
335
|
+
async function appendEvent(invocation, event, deduplicationKey) {
|
|
306
336
|
const timestamp = now();
|
|
307
337
|
const envelope = {
|
|
308
|
-
id:
|
|
338
|
+
id: lifecycleId("evt", invocation.id, deduplicationKey),
|
|
309
339
|
tenantId: invocation.tenantId,
|
|
310
340
|
spaceId: invocation.spaceId,
|
|
311
341
|
eventType: event.eventType,
|
|
@@ -375,6 +405,10 @@ function errorMessage(error) {
|
|
|
375
405
|
function isRecord(value) {
|
|
376
406
|
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
377
407
|
}
|
|
408
|
+
function lifecycleId(prefix, invocationId, key) {
|
|
409
|
+
const safeKey = key.replace(/[^a-zA-Z0-9_-]/g, "_").slice(0, 96);
|
|
410
|
+
return `${prefix}_${invocationId}_${safeKey}`;
|
|
411
|
+
}
|
|
378
412
|
|
|
379
413
|
// src/memory-store.ts
|
|
380
414
|
var MemoryPlatformHostStore = class {
|
|
@@ -390,8 +424,14 @@ var MemoryPlatformHostStore = class {
|
|
|
390
424
|
return run(this.db);
|
|
391
425
|
}
|
|
392
426
|
async createActionInvocation(input) {
|
|
427
|
+
if (input.idempotencyKey) {
|
|
428
|
+
const existing = this.invocations.find(
|
|
429
|
+
(record2) => record2.tenantId === input.tenantId && record2.spaceId === input.spaceId && record2.actionId === input.actionId && record2.idempotencyKey === input.idempotencyKey
|
|
430
|
+
);
|
|
431
|
+
if (existing) return existing;
|
|
432
|
+
}
|
|
393
433
|
const now = /* @__PURE__ */ new Date();
|
|
394
|
-
const record = { ...input, createdAt: now, updatedAt: now };
|
|
434
|
+
const record = { ...input, attemptCount: 0, createdAt: now, updatedAt: now };
|
|
395
435
|
this.invocations.push(record);
|
|
396
436
|
return record;
|
|
397
437
|
}
|
|
@@ -406,17 +446,23 @@ var MemoryPlatformHostStore = class {
|
|
|
406
446
|
Object.assign(record, patch, { updatedAt: /* @__PURE__ */ new Date() });
|
|
407
447
|
}
|
|
408
448
|
async appendPolicyEvaluation(record) {
|
|
449
|
+
if (this.policyEvaluations.some((candidate) => candidate.id === record.id)) return;
|
|
409
450
|
this.policyEvaluations.push(record);
|
|
410
451
|
}
|
|
411
452
|
async createAdapterInvocation(record) {
|
|
453
|
+
if (this.adapterInvocations.some((candidate) => candidate.id === record.id)) return;
|
|
412
454
|
this.adapterInvocations.push(record);
|
|
413
455
|
}
|
|
456
|
+
async getAdapterInvocation(id) {
|
|
457
|
+
return this.adapterInvocations.find((candidate) => candidate.id === id);
|
|
458
|
+
}
|
|
414
459
|
async updateAdapterInvocation(id, patch) {
|
|
415
460
|
const record = this.adapterInvocations.find((candidate) => candidate.id === id);
|
|
416
461
|
if (!record) throw new Error(`AdapterInvocation not found: ${id}`);
|
|
417
462
|
Object.assign(record, patch);
|
|
418
463
|
}
|
|
419
464
|
async appendEvent(event) {
|
|
465
|
+
if (this.events.some((candidate) => candidate.id === event.id)) return;
|
|
420
466
|
this.events.push(event);
|
|
421
467
|
}
|
|
422
468
|
async nextEventSequence(tenantId, spaceId) {
|
|
@@ -433,6 +479,28 @@ var MemoryPlatformHostStore = class {
|
|
|
433
479
|
}
|
|
434
480
|
return state;
|
|
435
481
|
}
|
|
482
|
+
async listActionInvocations(input = {}) {
|
|
483
|
+
return this.invocations.filter(
|
|
484
|
+
(record) => (!input.tenantId || record.tenantId === input.tenantId) && (!input.spaceId || record.spaceId === input.spaceId) && (!input.statuses?.length || input.statuses.includes(record.status)) && (!input.updatedBefore || record.updatedAt < input.updatedBefore)
|
|
485
|
+
).sort((left, right) => left.createdAt.getTime() - right.createdAt.getTime()).slice(0, input.limit ?? 100);
|
|
486
|
+
}
|
|
487
|
+
async claimActionInvocations(input) {
|
|
488
|
+
const current = input.now ?? /* @__PURE__ */ new Date();
|
|
489
|
+
const eligible = this.invocations.filter(
|
|
490
|
+
(record) => (!input.tenantId || record.tenantId === input.tenantId) && (!input.spaceId || record.spaceId === input.spaceId) && (record.status === "pending" || record.status === "running" && (!record.leaseExpiresAt || record.leaseExpiresAt <= current))
|
|
491
|
+
).sort((left, right) => left.createdAt.getTime() - right.createdAt.getTime()).slice(0, input.limit ?? 10);
|
|
492
|
+
for (const record of eligible) {
|
|
493
|
+
record.status = "running";
|
|
494
|
+
record.leaseOwner = input.workerId;
|
|
495
|
+
record.leaseExpiresAt = new Date(current.getTime() + input.leaseDurationMs);
|
|
496
|
+
record.attemptCount += 1;
|
|
497
|
+
record.updatedAt = current;
|
|
498
|
+
}
|
|
499
|
+
return eligible;
|
|
500
|
+
}
|
|
501
|
+
async listEvents(tenantId, spaceId) {
|
|
502
|
+
return this.events.filter((event) => event.tenantId === tenantId && event.spaceId === spaceId).sort((left, right) => left.sequence - right.sequence);
|
|
503
|
+
}
|
|
436
504
|
};
|
|
437
505
|
|
|
438
506
|
// src/postgres-store.ts
|
|
@@ -451,9 +519,22 @@ var PostgresPlatformHostStore = class {
|
|
|
451
519
|
action_id text NOT NULL, action_version integer NOT NULL,
|
|
452
520
|
actor_id text NOT NULL, actor_type text NOT NULL, status text NOT NULL,
|
|
453
521
|
parameters jsonb NOT NULL, result jsonb NOT NULL DEFAULT '{}'::jsonb,
|
|
454
|
-
correlation_id text NOT NULL, causation_id text, error text,
|
|
522
|
+
correlation_id text NOT NULL, causation_id text, idempotency_key text, error text,
|
|
523
|
+
attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
|
|
524
|
+
lease_expires_at timestamptz,
|
|
455
525
|
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL
|
|
456
526
|
);
|
|
527
|
+
ALTER TABLE fabric_platform.action_invocations
|
|
528
|
+
ADD COLUMN IF NOT EXISTS idempotency_key text,
|
|
529
|
+
ADD COLUMN IF NOT EXISTS attempt_count integer NOT NULL DEFAULT 0,
|
|
530
|
+
ADD COLUMN IF NOT EXISTS lease_owner text,
|
|
531
|
+
ADD COLUMN IF NOT EXISTS lease_expires_at timestamptz;
|
|
532
|
+
CREATE UNIQUE INDEX IF NOT EXISTS action_invocations_idempotency_idx
|
|
533
|
+
ON fabric_platform.action_invocations
|
|
534
|
+
(tenant_id, space_id, action_id, idempotency_key)
|
|
535
|
+
WHERE idempotency_key IS NOT NULL;
|
|
536
|
+
CREATE INDEX IF NOT EXISTS action_invocations_worker_idx
|
|
537
|
+
ON fabric_platform.action_invocations (status, lease_expires_at, created_at);
|
|
457
538
|
CREATE TABLE IF NOT EXISTS fabric_platform.policy_evaluations (
|
|
458
539
|
id text PRIMARY KEY, action_invocation_id text NOT NULL,
|
|
459
540
|
tenant_id text NOT NULL, space_id text NOT NULL, outcome jsonb NOT NULL,
|
|
@@ -490,11 +571,14 @@ var PostgresPlatformHostStore = class {
|
|
|
490
571
|
}
|
|
491
572
|
async createActionInvocation(input) {
|
|
492
573
|
const now = /* @__PURE__ */ new Date();
|
|
493
|
-
await this.sql.query(
|
|
574
|
+
const result = await this.sql.query(
|
|
494
575
|
`INSERT INTO fabric_platform.action_invocations
|
|
495
576
|
(id,tenant_id,space_id,action_id,action_version,actor_id,actor_type,status,
|
|
496
|
-
parameters,result,correlation_id,causation_id,error,created_at,updated_at)
|
|
497
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$
|
|
577
|
+
parameters,result,correlation_id,causation_id,idempotency_key,error,created_at,updated_at)
|
|
578
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14,$15,$15)
|
|
579
|
+
ON CONFLICT (tenant_id,space_id,action_id,idempotency_key)
|
|
580
|
+
WHERE idempotency_key IS NOT NULL DO UPDATE SET id=fabric_platform.action_invocations.id
|
|
581
|
+
RETURNING *`,
|
|
498
582
|
[
|
|
499
583
|
input.id,
|
|
500
584
|
input.tenantId,
|
|
@@ -508,11 +592,12 @@ var PostgresPlatformHostStore = class {
|
|
|
508
592
|
JSON.stringify(input.result),
|
|
509
593
|
input.correlationId,
|
|
510
594
|
input.causationId ?? null,
|
|
595
|
+
input.idempotencyKey ?? null,
|
|
511
596
|
input.error ?? null,
|
|
512
597
|
now
|
|
513
598
|
]
|
|
514
599
|
);
|
|
515
|
-
return
|
|
600
|
+
return toActionRecord(result.rows[0]);
|
|
516
601
|
}
|
|
517
602
|
async getActionInvocation(id, tenantId, spaceId) {
|
|
518
603
|
const result = await this.sql.query(
|
|
@@ -526,7 +611,12 @@ var PostgresPlatformHostStore = class {
|
|
|
526
611
|
await this.sql.query(
|
|
527
612
|
`UPDATE fabric_platform.action_invocations SET
|
|
528
613
|
status=COALESCE($4,status), result=COALESCE($5::jsonb,result),
|
|
529
|
-
error=CASE WHEN $6::boolean THEN $7 ELSE error END,
|
|
614
|
+
error=CASE WHEN $6::boolean THEN $7 ELSE error END,
|
|
615
|
+
lease_owner=CASE WHEN $4 IN ('completed','failed','blocked_by_policy','validation_failed')
|
|
616
|
+
THEN NULL ELSE lease_owner END,
|
|
617
|
+
lease_expires_at=CASE WHEN $4 IN ('completed','failed','blocked_by_policy','validation_failed')
|
|
618
|
+
THEN NULL ELSE lease_expires_at END,
|
|
619
|
+
updated_at=now()
|
|
530
620
|
WHERE id=$1 AND tenant_id=$2 AND space_id=$3`,
|
|
531
621
|
[
|
|
532
622
|
id,
|
|
@@ -543,7 +633,7 @@ var PostgresPlatformHostStore = class {
|
|
|
543
633
|
await this.sql.query(
|
|
544
634
|
`INSERT INTO fabric_platform.policy_evaluations
|
|
545
635
|
(id,action_invocation_id,tenant_id,space_id,outcome,created_at)
|
|
546
|
-
VALUES ($1,$2,$3,$4,$5::jsonb,$6)`,
|
|
636
|
+
VALUES ($1,$2,$3,$4,$5::jsonb,$6) ON CONFLICT (id) DO NOTHING`,
|
|
547
637
|
[
|
|
548
638
|
record.id,
|
|
549
639
|
record.actionInvocationId,
|
|
@@ -559,7 +649,8 @@ var PostgresPlatformHostStore = class {
|
|
|
559
649
|
`INSERT INTO fabric_platform.adapter_invocations
|
|
560
650
|
(id,action_invocation_id,tenant_id,space_id,adapter_type,operation,vendor,status,
|
|
561
651
|
input,output,error,attempt,created_at,updated_at)
|
|
562
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14)
|
|
652
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12,$13,$14)
|
|
653
|
+
ON CONFLICT (id) DO NOTHING`,
|
|
563
654
|
[
|
|
564
655
|
record.id,
|
|
565
656
|
record.actionInvocationId,
|
|
@@ -578,6 +669,13 @@ var PostgresPlatformHostStore = class {
|
|
|
578
669
|
]
|
|
579
670
|
);
|
|
580
671
|
}
|
|
672
|
+
async getAdapterInvocation(id) {
|
|
673
|
+
const result = await this.sql.query(
|
|
674
|
+
`SELECT * FROM fabric_platform.adapter_invocations WHERE id=$1`,
|
|
675
|
+
[id]
|
|
676
|
+
);
|
|
677
|
+
return result.rows[0] ? toAdapterRecord(result.rows[0]) : void 0;
|
|
678
|
+
}
|
|
581
679
|
async updateAdapterInvocation(id, patch) {
|
|
582
680
|
await this.sql.query(
|
|
583
681
|
`UPDATE fabric_platform.adapter_invocations SET
|
|
@@ -601,7 +699,8 @@ var PostgresPlatformHostStore = class {
|
|
|
601
699
|
(id,tenant_id,space_id,event_type,event_schema_version,subject_type,subject_id,
|
|
602
700
|
actor_id,actor_type,action_invocation_id,payload,sequence,occurred_at,recorded_at,
|
|
603
701
|
correlation_id,causation_id)
|
|
604
|
-
|
|
702
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11::jsonb,$12,$13,$14,$15,$16)
|
|
703
|
+
ON CONFLICT (id) DO NOTHING`,
|
|
605
704
|
[
|
|
606
705
|
event.id,
|
|
607
706
|
event.tenantId,
|
|
@@ -641,6 +740,57 @@ var PostgresPlatformHostStore = class {
|
|
|
641
740
|
);
|
|
642
741
|
return result.rows[0]?.state;
|
|
643
742
|
}
|
|
743
|
+
async listActionInvocations(input = {}) {
|
|
744
|
+
const conditions = [];
|
|
745
|
+
const values = [];
|
|
746
|
+
const add = (condition, value) => {
|
|
747
|
+
values.push(value);
|
|
748
|
+
conditions.push(condition.replace("?", `$${values.length}`));
|
|
749
|
+
};
|
|
750
|
+
if (input.tenantId) add("tenant_id=?", input.tenantId);
|
|
751
|
+
if (input.spaceId) add("space_id=?", input.spaceId);
|
|
752
|
+
if (input.statuses?.length) add("status = ANY(?::text[])", [...input.statuses]);
|
|
753
|
+
if (input.updatedBefore) add("updated_at<?", input.updatedBefore);
|
|
754
|
+
values.push(Math.max(1, Math.min(input.limit ?? 100, 1e3)));
|
|
755
|
+
const where = conditions.length ? `WHERE ${conditions.join(" AND ")}` : "";
|
|
756
|
+
const result = await this.sql.query(
|
|
757
|
+
`SELECT * FROM fabric_platform.action_invocations ${where}
|
|
758
|
+
ORDER BY created_at LIMIT $${values.length}`,
|
|
759
|
+
values
|
|
760
|
+
);
|
|
761
|
+
return result.rows.map(toActionRecord);
|
|
762
|
+
}
|
|
763
|
+
async claimActionInvocations(input) {
|
|
764
|
+
const current = input.now ?? /* @__PURE__ */ new Date();
|
|
765
|
+
const limit = Math.max(1, Math.min(input.limit ?? 10, 100));
|
|
766
|
+
const leaseExpiresAt = new Date(current.getTime() + input.leaseDurationMs);
|
|
767
|
+
const result = await this.sql.query(
|
|
768
|
+
`WITH claimable AS (
|
|
769
|
+
SELECT id FROM fabric_platform.action_invocations
|
|
770
|
+
WHERE (status='pending' OR
|
|
771
|
+
(status='running' AND (lease_expires_at IS NULL OR lease_expires_at <= $1)))
|
|
772
|
+
AND ($2::text IS NULL OR tenant_id=$2)
|
|
773
|
+
AND ($3::text IS NULL OR space_id=$3)
|
|
774
|
+
ORDER BY created_at
|
|
775
|
+
FOR UPDATE SKIP LOCKED
|
|
776
|
+
LIMIT $4
|
|
777
|
+
)
|
|
778
|
+
UPDATE fabric_platform.action_invocations AS invocation
|
|
779
|
+
SET status='running', lease_owner=$5, lease_expires_at=$6,
|
|
780
|
+
attempt_count=attempt_count+1, updated_at=$1
|
|
781
|
+
FROM claimable WHERE invocation.id=claimable.id
|
|
782
|
+
RETURNING invocation.*`,
|
|
783
|
+
[
|
|
784
|
+
current,
|
|
785
|
+
input.tenantId ?? null,
|
|
786
|
+
input.spaceId ?? null,
|
|
787
|
+
limit,
|
|
788
|
+
input.workerId,
|
|
789
|
+
leaseExpiresAt
|
|
790
|
+
]
|
|
791
|
+
);
|
|
792
|
+
return result.rows.map(toActionRecord);
|
|
793
|
+
}
|
|
644
794
|
async listEvents(tenantId, spaceId) {
|
|
645
795
|
const result = await this.sql.query(
|
|
646
796
|
`SELECT * FROM fabric_platform.asset_events
|
|
@@ -650,6 +800,24 @@ var PostgresPlatformHostStore = class {
|
|
|
650
800
|
return result.rows.map(toEventRecord);
|
|
651
801
|
}
|
|
652
802
|
};
|
|
803
|
+
function toAdapterRecord(row) {
|
|
804
|
+
return {
|
|
805
|
+
id: String(row.id),
|
|
806
|
+
actionInvocationId: String(row.action_invocation_id),
|
|
807
|
+
tenantId: String(row.tenant_id),
|
|
808
|
+
spaceId: String(row.space_id),
|
|
809
|
+
adapterType: String(row.adapter_type),
|
|
810
|
+
operation: String(row.operation),
|
|
811
|
+
vendor: String(row.vendor),
|
|
812
|
+
status: String(row.status),
|
|
813
|
+
input: row.input,
|
|
814
|
+
...row.output ? { output: row.output } : {},
|
|
815
|
+
...row.error ? { error: String(row.error) } : {},
|
|
816
|
+
attempt: Number(row.attempt),
|
|
817
|
+
createdAt: new Date(row.created_at),
|
|
818
|
+
updatedAt: new Date(row.updated_at)
|
|
819
|
+
};
|
|
820
|
+
}
|
|
653
821
|
function toActionRecord(row) {
|
|
654
822
|
return {
|
|
655
823
|
id: String(row.id),
|
|
@@ -664,6 +832,10 @@ function toActionRecord(row) {
|
|
|
664
832
|
result: row.result,
|
|
665
833
|
correlationId: String(row.correlation_id),
|
|
666
834
|
...row.causation_id ? { causationId: String(row.causation_id) } : {},
|
|
835
|
+
...row.idempotency_key ? { idempotencyKey: String(row.idempotency_key) } : {},
|
|
836
|
+
attemptCount: Number(row.attempt_count ?? 0),
|
|
837
|
+
...row.lease_owner ? { leaseOwner: String(row.lease_owner) } : {},
|
|
838
|
+
...row.lease_expires_at ? { leaseExpiresAt: new Date(row.lease_expires_at) } : {},
|
|
667
839
|
...row.error ? { error: String(row.error) } : {},
|
|
668
840
|
createdAt: new Date(row.created_at),
|
|
669
841
|
updatedAt: new Date(row.updated_at)
|
|
@@ -690,6 +862,66 @@ function toEventRecord(row) {
|
|
|
690
862
|
};
|
|
691
863
|
}
|
|
692
864
|
|
|
693
|
-
|
|
865
|
+
// src/worker.ts
|
|
866
|
+
var DEFAULT_BATCH_SIZE = 10;
|
|
867
|
+
var DEFAULT_LEASE_DURATION_MS = 5 * 6e4;
|
|
868
|
+
var DEFAULT_POLL_INTERVAL_MS = 1e3;
|
|
869
|
+
function createStoreBackedActionDispatcher() {
|
|
870
|
+
return {
|
|
871
|
+
dispatch: async (input) => ({ workflowId: input.workflowId })
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
async function runPlatformActionWorkerCycle(options) {
|
|
875
|
+
const claimed = await options.store.claimActionInvocations({
|
|
876
|
+
workerId: options.workerId,
|
|
877
|
+
limit: options.batchSize ?? DEFAULT_BATCH_SIZE,
|
|
878
|
+
leaseDurationMs: options.leaseDurationMs ?? DEFAULT_LEASE_DURATION_MS,
|
|
879
|
+
...options.tenantId ? { tenantId: options.tenantId } : {},
|
|
880
|
+
...options.spaceId ? { spaceId: options.spaceId } : {}
|
|
881
|
+
});
|
|
882
|
+
let completed = 0;
|
|
883
|
+
let failed = 0;
|
|
884
|
+
for (const invocation of claimed) {
|
|
885
|
+
try {
|
|
886
|
+
const result = await options.host.executeInvocation(
|
|
887
|
+
invocation.id,
|
|
888
|
+
invocation.tenantId,
|
|
889
|
+
invocation.spaceId
|
|
890
|
+
);
|
|
891
|
+
if (result.status === "completed") completed += 1;
|
|
892
|
+
else failed += 1;
|
|
893
|
+
} catch (error) {
|
|
894
|
+
failed += 1;
|
|
895
|
+
options.onError?.(error, invocation);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
return { claimed: claimed.length, completed, failed };
|
|
899
|
+
}
|
|
900
|
+
async function runPlatformActionWorker(options) {
|
|
901
|
+
while (!options.signal?.aborted) {
|
|
902
|
+
try {
|
|
903
|
+
await runPlatformActionWorkerCycle(options);
|
|
904
|
+
} catch (error) {
|
|
905
|
+
options.onError?.(error);
|
|
906
|
+
}
|
|
907
|
+
await abortableDelay(options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS, options.signal);
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
async function abortableDelay(milliseconds, signal) {
|
|
911
|
+
if (signal?.aborted) return;
|
|
912
|
+
await new Promise((resolve) => {
|
|
913
|
+
const timeout = setTimeout(resolve, milliseconds);
|
|
914
|
+
signal?.addEventListener(
|
|
915
|
+
"abort",
|
|
916
|
+
() => {
|
|
917
|
+
clearTimeout(timeout);
|
|
918
|
+
resolve();
|
|
919
|
+
},
|
|
920
|
+
{ once: true }
|
|
921
|
+
);
|
|
922
|
+
});
|
|
923
|
+
}
|
|
924
|
+
|
|
925
|
+
export { MemoryPlatformHostStore, PostgresPlatformHostStore, createGovernedActionHost, createStoreBackedActionDispatcher, runPlatformActionWorker, runPlatformActionWorkerCycle };
|
|
694
926
|
//# sourceMappingURL=index.js.map
|
|
695
927
|
//# sourceMappingURL=index.js.map
|