@pikku/core 0.12.134 → 0.12.136
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 +15 -0
- package/dist/analytics/analytics.types.d.ts +20 -0
- package/dist/analytics/anonymous-analytics-identity.d.ts +2 -0
- package/dist/analytics/anonymous-analytics-identity.js +2 -0
- package/dist/analytics/compose-analytics-identity.d.ts +2 -0
- package/dist/analytics/compose-analytics-identity.js +2 -0
- package/dist/analytics/cookie-analytics-identity.d.ts +2 -0
- package/dist/analytics/cookie-analytics-identity.js +2 -0
- package/dist/analytics/define-analytics-events.d.ts +3 -0
- package/dist/analytics/define-analytics-events.js +2 -0
- package/dist/analytics/fan-out-analytics.d.ts +2 -0
- package/dist/analytics/fan-out-analytics.js +2 -0
- package/dist/analytics/logger-analytics-service.d.ts +2 -0
- package/dist/analytics/logger-analytics-service.js +2 -0
- package/dist/analytics/mint-cookie.d.ts +8 -1
- package/dist/analytics/mint-cookie.js +8 -4
- package/dist/function/compensation-name.d.ts +4 -0
- package/dist/function/compensation-name.js +6 -0
- package/dist/function/function-meta.types.d.ts +7 -0
- package/dist/function/function-runner.js +17 -0
- package/dist/function/functions.types.d.ts +18 -0
- package/dist/function/functions.types.js +11 -0
- package/dist/middleware/require-origin.d.ts +2 -0
- package/dist/middleware/require-origin.js +2 -0
- package/dist/permissions.d.ts +5 -0
- package/dist/permissions.js +5 -0
- package/dist/testing/service-tests/queued-workflow-harness.d.ts +61 -0
- package/dist/testing/service-tests/queued-workflow-harness.js +151 -0
- package/dist/testing/service-tests/workflow-compensation-queued-tests.d.ts +11 -0
- package/dist/testing/service-tests/workflow-compensation-queued-tests.js +683 -0
- package/dist/testing/service-tests.d.ts +4 -0
- package/dist/testing/service-tests.js +4 -0
- package/dist/utils/hmac.d.ts +0 -33
- package/dist/utils/hmac.js +0 -61
- package/dist/utils.d.ts +7 -0
- package/dist/utils.js +16 -0
- package/dist/wirings/flag/define-feature-flags.d.ts +1 -13
- package/dist/wirings/flag/define-feature-flags.js +1 -13
- package/dist/wirings/http/http-runner.js +1 -3
- package/dist/wirings/rpc/rpc-runner.js +21 -1
- package/dist/wirings/trigger/webhook-source-runner.d.ts +2 -2
- package/dist/wirings/trigger/webhook-source-runner.js +15 -15
- package/dist/wirings/trigger/webhook-source.types.d.ts +9 -12
- package/dist/wirings/workflow/dsl/workflow-dsl.types.d.ts +38 -6
- package/dist/wirings/workflow/graph/graph-node.d.ts +2 -1
- package/dist/wirings/workflow/graph/graph-node.js +2 -1
- package/dist/wirings/workflow/graph/graph-runner.d.ts +1 -1
- package/dist/wirings/workflow/graph/graph-runner.js +104 -67
- package/dist/wirings/workflow/graph/graph-validation.js +2 -2
- package/dist/wirings/workflow/graph/workflow-graph.types.d.ts +20 -1
- package/dist/wirings/workflow/index.d.ts +1 -1
- package/dist/wirings/workflow/pikku-workflow-service.d.ts +13 -0
- package/dist/wirings/workflow/pikku-workflow-service.js +113 -143
- package/dist/wirings/workflow/run-timeline.d.ts +2 -0
- package/dist/wirings/workflow/run-timeline.js +3 -0
- package/dist/wirings/workflow/workflow-child-step.d.ts +17 -0
- package/dist/wirings/workflow/workflow-child-step.js +31 -0
- package/dist/wirings/workflow/workflow-compensation.d.ts +68 -0
- package/dist/wirings/workflow/workflow-compensation.js +282 -0
- package/dist/wirings/workflow/workflow-constants.d.ts +5 -0
- package/dist/wirings/workflow/workflow-constants.js +9 -0
- package/dist/wirings/workflow/workflow-dsl-pass.d.ts +12 -0
- package/dist/wirings/workflow/workflow-dsl-pass.js +75 -0
- package/dist/wirings/workflow/workflow-queue-routing.js +3 -3
- package/dist/wirings/workflow/workflow-run-status.js +13 -1
- package/dist/wirings/workflow/workflow-status-stream.js +2 -0
- package/dist/wirings/workflow/workflow-unwind-plan.d.ts +38 -0
- package/dist/wirings/workflow/workflow-unwind-plan.js +120 -0
- package/dist/wirings/workflow/workflow.types.d.ts +9 -2
- package/knowledge/decisions/internals/workflow-step-compensation-runs-as-its-own-durable-step.md +21 -18
- package/package.json +1 -1
- package/src/public-surface.json +1 -1
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { pikkuState, resetPikkuState } from '../../pikku-state.js';
|
|
2
|
+
import { addWorkflow } from '../../wirings/workflow/dsl/workflow-runner.js';
|
|
3
|
+
/**
|
|
4
|
+
* Drives a workflow the way a deployment does: every orchestration and every
|
|
5
|
+
* `workflowQueued` step goes through a queue, and a pump plays the workers.
|
|
6
|
+
* Delays are ignored, so a retry runs on the next pass.
|
|
7
|
+
*/
|
|
8
|
+
export class QueuedWorkflowHarness {
|
|
9
|
+
handlers = {};
|
|
10
|
+
log = [];
|
|
11
|
+
contexts = [];
|
|
12
|
+
recovering = [];
|
|
13
|
+
queue = [];
|
|
14
|
+
jobsRun = [];
|
|
15
|
+
ws;
|
|
16
|
+
/** Return true to drop the job instead of running it, simulating a worker that never ran. */
|
|
17
|
+
dropWhen;
|
|
18
|
+
/** Make `queue.add` throw for a job, simulating the broker being unreachable. */
|
|
19
|
+
rejectAddWhen;
|
|
20
|
+
constructor(service) {
|
|
21
|
+
resetPikkuState();
|
|
22
|
+
this.ws = service;
|
|
23
|
+
const queueService = {
|
|
24
|
+
add: async (queue, data, options) => {
|
|
25
|
+
if (this.rejectAddWhen?.(queue, data)) {
|
|
26
|
+
throw new Error('broker unreachable');
|
|
27
|
+
}
|
|
28
|
+
this.queue.push({ queue, data, attempts: options?.attempts ?? 1 });
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
pikkuState(null, 'package', 'singletonServices', {
|
|
32
|
+
logger: { error() { }, info() { }, warn() { }, debug() { } },
|
|
33
|
+
queueService,
|
|
34
|
+
workflowService: this.ws,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
rpc = {
|
|
38
|
+
rpcWithWire: async (rpcName, data, wire) => {
|
|
39
|
+
const isCompensation = rpcName.endsWith(':compensate');
|
|
40
|
+
const base = isCompensation
|
|
41
|
+
? rpcName.slice(0, -':compensate'.length)
|
|
42
|
+
: rpcName;
|
|
43
|
+
const handler = this.handlers[base];
|
|
44
|
+
this.contexts.push({
|
|
45
|
+
rpc: rpcName,
|
|
46
|
+
context: wire.workflow?.compensatingFor,
|
|
47
|
+
});
|
|
48
|
+
if (isCompensation) {
|
|
49
|
+
this.log.push(`undo:${base}`);
|
|
50
|
+
return handler.compensate(data, wire.workflow.compensatingFor);
|
|
51
|
+
}
|
|
52
|
+
this.log.push(`do:${base}`);
|
|
53
|
+
if (wire.graph?.recoveringFrom) {
|
|
54
|
+
this.recovering.push({ rpc: base, from: wire.graph.recoveringFrom });
|
|
55
|
+
}
|
|
56
|
+
return handler.forward(data, wire);
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
register(name, handler) {
|
|
60
|
+
this.handlers[name] = handler;
|
|
61
|
+
pikkuState(null, 'rpc', 'meta')[name] = name;
|
|
62
|
+
pikkuState(null, 'function', 'meta')[name] = {
|
|
63
|
+
name,
|
|
64
|
+
pikkuFuncId: name,
|
|
65
|
+
sessionless: true,
|
|
66
|
+
permissions: [],
|
|
67
|
+
workflowQueued: handler.queued !== false,
|
|
68
|
+
...(handler.compensate ? { compensate: true } : {}),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
defineDsl(name, body) {
|
|
72
|
+
pikkuState(null, 'workflows', 'meta')[name] = {
|
|
73
|
+
name,
|
|
74
|
+
pikkuFuncId: name,
|
|
75
|
+
source: 'dsl',
|
|
76
|
+
graphHash: `${name}-hash`,
|
|
77
|
+
};
|
|
78
|
+
pikkuState(null, 'function', 'meta')[name] = {
|
|
79
|
+
name,
|
|
80
|
+
sessionless: true,
|
|
81
|
+
permissions: [],
|
|
82
|
+
};
|
|
83
|
+
addWorkflow(name, {
|
|
84
|
+
func: async (_services, input, wire) => body(wire.workflow, input),
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
defineGraph(name, entry, nodes) {
|
|
88
|
+
pikkuState(null, 'workflows', 'meta')[name] = {
|
|
89
|
+
name,
|
|
90
|
+
pikkuFuncId: name,
|
|
91
|
+
source: 'graph',
|
|
92
|
+
entryNodeIds: [entry],
|
|
93
|
+
graphHash: `${name}-hash`,
|
|
94
|
+
nodes: Object.fromEntries(Object.entries(nodes).map(([id, node]) => [
|
|
95
|
+
id,
|
|
96
|
+
{ nodeId: id, rpcName: id, retries: 0, ...node },
|
|
97
|
+
])),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
async start(name, input = {}) {
|
|
101
|
+
const { runId } = await this.ws.startWorkflow(name, input, { type: 'test' }, this.rpc);
|
|
102
|
+
await this.pump();
|
|
103
|
+
return runId;
|
|
104
|
+
}
|
|
105
|
+
/** Run jobs until the queue is empty. */
|
|
106
|
+
async pump(limit = 500) {
|
|
107
|
+
for (let i = 0; i < limit && this.queue.length > 0; i++) {
|
|
108
|
+
const job = this.queue.shift();
|
|
109
|
+
if (this.dropWhen?.(job))
|
|
110
|
+
continue;
|
|
111
|
+
this.jobsRun.push({ queue: job.queue, step: job.data.stepName });
|
|
112
|
+
try {
|
|
113
|
+
if (job.data.stepName !== undefined) {
|
|
114
|
+
await this.ws.executeWorkflowStep(job.data.runId, job.data.stepName, job.data.rpcName, job.data.data, this.rpc);
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
await this.ws.orchestrateWorkflow(job.data.runId, this.rpc);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
catch {
|
|
121
|
+
job.attempt = (job.attempt ?? 1) + 1;
|
|
122
|
+
if (job.attempt <= (job.attempts ?? 1))
|
|
123
|
+
this.queue.push(job);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (this.queue.length > 0)
|
|
127
|
+
throw new Error('queue did not drain');
|
|
128
|
+
}
|
|
129
|
+
async run(runId) {
|
|
130
|
+
return (await this.ws.getRun(runId));
|
|
131
|
+
}
|
|
132
|
+
async steps(runId) {
|
|
133
|
+
return this.ws.getRunSteps(runId);
|
|
134
|
+
}
|
|
135
|
+
get undone() {
|
|
136
|
+
return this.log.filter((l) => l.startsWith('undo'));
|
|
137
|
+
}
|
|
138
|
+
ok(output = {}) {
|
|
139
|
+
return { forward: async () => output };
|
|
140
|
+
}
|
|
141
|
+
boom(message = 'boom') {
|
|
142
|
+
return {
|
|
143
|
+
forward: async () => {
|
|
144
|
+
throw new Error(message);
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
undoable(output = {}) {
|
|
149
|
+
return { ...this.ok(output), compensate: async () => { } };
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { PikkuWorkflowService } from '../../wirings/workflow/pikku-workflow-service.js';
|
|
2
|
+
/**
|
|
3
|
+
* Conformance suite for saga compensation and graph `recover` on the queued
|
|
4
|
+
* path: every orchestration and step goes through a queue and is played back by
|
|
5
|
+
* a pump, as in a deployment. Each test gets a fresh service.
|
|
6
|
+
*/
|
|
7
|
+
export interface QueuedCompensationOptions {
|
|
8
|
+
/** False for a runtime where one run owns its storage and cannot start a child run. */
|
|
9
|
+
childWorkflows?: boolean;
|
|
10
|
+
}
|
|
11
|
+
export declare const defineWorkflowCompensationQueuedTests: (name: string, create: () => Promise<PikkuWorkflowService>, options?: QueuedCompensationOptions) => void;
|