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