autotel 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/auto.cjs +2 -2
- package/dist/auto.js +1 -1
- package/dist/correlation-id.cjs +1 -1
- package/dist/correlation-id.js +1 -1
- package/dist/decorators.cjs +1 -1
- package/dist/decorators.js +1 -1
- package/dist/{event-Dlqr4ZNL.cjs → event-BhHREDJk.cjs} +3 -3
- package/dist/{event-Dlqr4ZNL.cjs.map → event-BhHREDJk.cjs.map} +1 -1
- package/dist/{event-_58ryBjh.js → event-ByBTV9M2.js} +3 -3
- package/dist/{event-_58ryBjh.js.map → event-ByBTV9M2.js.map} +1 -1
- package/dist/event.cjs +1 -1
- package/dist/event.js +1 -1
- package/dist/{functional-BGkT8J-h.js → functional-DtI0u4vx.js} +19 -19
- package/dist/functional-DtI0u4vx.js.map +1 -0
- package/dist/{functional-C4CzoVrX.cjs → functional-zpzNLhky.cjs} +4 -4
- package/dist/{functional-C4CzoVrX.cjs.map → functional-zpzNLhky.cjs.map} +1 -1
- package/dist/functional.cjs +1 -1
- package/dist/functional.js +1 -1
- package/dist/http.cjs +1 -1
- package/dist/http.js +1 -1
- package/dist/index.cjs +5 -5
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +5 -5
- package/dist/{init-DJQOdVlN.d.ts → init-B7u-DjxM.d.ts} +57 -2
- package/dist/init-B7u-DjxM.d.ts.map +1 -0
- package/dist/{init-DvapOXCc.cjs → init-BX7AmFRl.cjs} +40 -21
- package/dist/init-BX7AmFRl.cjs.map +1 -0
- package/dist/{init-Ch6t7MNI.js → init-D-jnNMix.js} +39 -20
- package/dist/init-D-jnNMix.js.map +1 -0
- package/dist/{init-CNp-ee80.d.cts → init-DSrRmVnz.d.cts} +57 -2
- package/dist/init-DSrRmVnz.d.cts.map +1 -0
- package/dist/instrumentation.cjs +1 -1
- package/dist/instrumentation.js +1 -1
- package/dist/logger-D3Ej3DII.js +446 -0
- package/dist/logger-D3Ej3DII.js.map +1 -0
- package/dist/logger-thMPLpOG.cjs +487 -0
- package/dist/logger-thMPLpOG.cjs.map +1 -0
- package/dist/logger.cjs +8 -236
- package/dist/logger.js +2 -204
- package/dist/messaging.cjs +1 -1
- package/dist/messaging.js +1 -1
- package/dist/semantic-helpers.cjs +1 -1
- package/dist/semantic-helpers.js +1 -1
- package/dist/{track-3HY4NGV-.cjs → track-D59FfpL0.cjs} +2 -2
- package/dist/{track-3HY4NGV-.cjs.map → track-D59FfpL0.cjs.map} +1 -1
- package/dist/{track-nsKVy-pj.js → track-wc0HafS_.js} +6 -6
- package/dist/track-wc0HafS_.js.map +1 -0
- package/dist/webhook.cjs +1 -1
- package/dist/webhook.js +1 -1
- package/dist/workflow-distributed.cjs +1 -1
- package/dist/workflow-distributed.js +1 -1
- package/dist/workflow.cjs +1 -1
- package/dist/workflow.js +1 -1
- package/dist/{yaml-config-B3dQ82GR.cjs → yaml-config-Ck2uB0Dp.cjs} +2 -1
- package/dist/yaml-config-Ck2uB0Dp.cjs.map +1 -0
- package/dist/yaml-config.cjs +1 -1
- package/dist/yaml-config.d.cts +7 -1
- package/dist/yaml-config.d.cts.map +1 -1
- package/dist/yaml-config.d.ts +7 -1
- package/dist/yaml-config.d.ts.map +1 -1
- package/dist/yaml-config.js +1 -0
- package/dist/yaml-config.js.map +1 -1
- package/package.json +1 -2
- package/skills/autotel-core/SKILL.md +2 -0
- package/skills/autotel-instrumentation/SKILL.md +25 -0
- package/skills/debug-missing-spans/SKILL.md +3 -1
- package/skills/migrate-to-autotel/SKILL.md +24 -23
- package/skills/review-otel-patterns/SKILL.md +5 -4
- package/dist/functional-BGkT8J-h.js.map +0 -1
- package/dist/init-CNp-ee80.d.cts.map +0 -1
- package/dist/init-Ch6t7MNI.js.map +0 -1
- package/dist/init-DJQOdVlN.d.ts.map +0 -1
- package/dist/init-DvapOXCc.cjs.map +0 -1
- package/dist/logger.cjs.map +0 -1
- package/dist/logger.js.map +0 -1
- package/dist/track-nsKVy-pj.js.map +0 -1
- package/dist/yaml-config-B3dQ82GR.cjs.map +0 -1
- package/src/attribute-redacting-processor.test.ts +0 -763
- package/src/attribute-redacting-processor.ts +0 -621
- package/src/attributes/attachers.ts +0 -161
- package/src/attributes/builders.ts +0 -529
- package/src/attributes/domains.ts +0 -42
- package/src/attributes/index.ts +0 -81
- package/src/attributes/registry.ts +0 -323
- package/src/attributes/types.ts +0 -211
- package/src/attributes/utils.ts +0 -64
- package/src/attributes/validators.ts +0 -266
- package/src/attributes.test.ts +0 -292
- package/src/auto.ts +0 -67
- package/src/autotel-logger.test.ts +0 -548
- package/src/autotel-logger.ts +0 -364
- package/src/baggage-span-processor.test.ts +0 -202
- package/src/baggage-span-processor.ts +0 -100
- package/src/business-baggage.test.ts +0 -500
- package/src/business-baggage.ts +0 -669
- package/src/circuit-breaker.test.ts +0 -341
- package/src/circuit-breaker.ts +0 -184
- package/src/config.test.ts +0 -94
- package/src/config.ts +0 -172
- package/src/correlated-events.test.ts +0 -151
- package/src/correlated-events.ts +0 -47
- package/src/correlation-id.test.ts +0 -163
- package/src/correlation-id.ts +0 -206
- package/src/db.test.ts +0 -252
- package/src/db.ts +0 -447
- package/src/decorators.test.ts +0 -153
- package/src/decorators.ts +0 -188
- package/src/define-event.test.ts +0 -41
- package/src/define-event.ts +0 -58
- package/src/devtools.ts +0 -60
- package/src/drain-pipeline.test.ts +0 -68
- package/src/drain-pipeline.ts +0 -199
- package/src/drain-toolkit.test.ts +0 -113
- package/src/drain-toolkit.ts +0 -129
- package/src/enricher-toolkit.test.ts +0 -67
- package/src/enricher-toolkit.ts +0 -79
- package/src/enrichers.test.ts +0 -150
- package/src/enrichers.ts +0 -145
- package/src/env-config.test.ts +0 -323
- package/src/env-config.ts +0 -309
- package/src/error-catalog.test.ts +0 -133
- package/src/error-catalog.ts +0 -262
- package/src/event-queue.test.ts +0 -864
- package/src/event-queue.ts +0 -699
- package/src/event-subscriber.ts +0 -262
- package/src/event-testing.ts +0 -197
- package/src/event.test.ts +0 -1104
- package/src/event.ts +0 -988
- package/src/events-config.ts +0 -235
- package/src/exporters.ts +0 -165
- package/src/filtering-span-processor.test.ts +0 -281
- package/src/filtering-span-processor.ts +0 -111
- package/src/flatten-attributes.test.ts +0 -76
- package/src/flatten-attributes.ts +0 -80
- package/src/functional.strict-types.typecheck.ts +0 -53
- package/src/functional.test.ts +0 -1464
- package/src/functional.ts +0 -2539
- package/src/functional.types.test.ts +0 -135
- package/src/hook.mjs +0 -15
- package/src/http.test.ts +0 -485
- package/src/http.ts +0 -424
- package/src/index.ts +0 -433
- package/src/init-auto-redactor.test.ts +0 -53
- package/src/init-redactor.test.ts +0 -8
- package/src/init.customization.test.ts +0 -594
- package/src/init.integrations.test.ts +0 -399
- package/src/init.openllmetry.test.ts +0 -194
- package/src/init.protocol.test.ts +0 -215
- package/src/init.ts +0 -2312
- package/src/instrumentation.test.ts +0 -108
- package/src/instrumentation.ts +0 -319
- package/src/logger.test.ts +0 -125
- package/src/logger.ts +0 -341
- package/src/messaging-adapters.test.ts +0 -595
- package/src/messaging-adapters.ts +0 -583
- package/src/messaging-testing.test.ts +0 -573
- package/src/messaging-testing.ts +0 -935
- package/src/messaging.test.ts +0 -1646
- package/src/messaging.ts +0 -2245
- package/src/metric-helpers.ts +0 -47
- package/src/metric-testing.ts +0 -197
- package/src/metric.ts +0 -446
- package/src/metrics.test.ts +0 -241
- package/src/node-require.ts +0 -123
- package/src/operation-context.ts +0 -93
- package/src/parse-error.test.ts +0 -73
- package/src/parse-error.ts +0 -112
- package/src/posthog-logs.test.ts +0 -115
- package/src/posthog-logs.ts +0 -77
- package/src/pretty-console-exporter.test.ts +0 -545
- package/src/pretty-console-exporter.ts +0 -413
- package/src/pretty-log-formatter.test.ts +0 -123
- package/src/pretty-log-formatter.ts +0 -210
- package/src/processors/canonical-log-line-processor.test.ts +0 -523
- package/src/processors/canonical-log-line-processor.ts +0 -396
- package/src/processors.ts +0 -152
- package/src/rate-limiter.test.ts +0 -199
- package/src/rate-limiter.ts +0 -98
- package/src/redact-values.test.ts +0 -90
- package/src/redact-values.ts +0 -34
- package/src/register.ts +0 -37
- package/src/request-logger.test.ts +0 -545
- package/src/request-logger.ts +0 -342
- package/src/sampling.test.ts +0 -1060
- package/src/sampling.ts +0 -737
- package/src/security-schema.test.ts +0 -45
- package/src/security-schema.ts +0 -107
- package/src/semantic-conventions.ts +0 -15
- package/src/semantic-helpers.test.ts +0 -226
- package/src/semantic-helpers.ts +0 -438
- package/src/shutdown.test.ts +0 -364
- package/src/shutdown.ts +0 -246
- package/src/span-name-normalizer.test.ts +0 -377
- package/src/span-name-normalizer.ts +0 -213
- package/src/stable-hash.ts +0 -27
- package/src/structured-error.test.ts +0 -191
- package/src/structured-error.ts +0 -157
- package/src/stub.integration.test.ts +0 -361
- package/src/tail-sampling-processor.test.ts +0 -230
- package/src/tail-sampling-processor.ts +0 -55
- package/src/test-span-collector.test.ts +0 -234
- package/src/test-span-collector.ts +0 -150
- package/src/testing.ts +0 -705
- package/src/trace-context.test.ts +0 -73
- package/src/trace-context.ts +0 -567
- package/src/trace-helpers.new.test.ts +0 -278
- package/src/trace-helpers.test.ts +0 -290
- package/src/trace-helpers.ts +0 -710
- package/src/trace-hybrid.test.ts +0 -42
- package/src/trace-hybrid.ts +0 -37
- package/src/tracer-provider.test.ts +0 -183
- package/src/tracer-provider.ts +0 -266
- package/src/track.test.ts +0 -154
- package/src/track.ts +0 -216
- package/src/validate.test.ts +0 -287
- package/src/validate.ts +0 -307
- package/src/validation-attributes.ts +0 -43
- package/src/validation.test.ts +0 -330
- package/src/validation.ts +0 -246
- package/src/variable-name-inference.test.ts +0 -178
- package/src/variable-name-inference.ts +0 -242
- package/src/webhook.test.ts +0 -649
- package/src/webhook.ts +0 -637
- package/src/workflow-distributed.test.ts +0 -786
- package/src/workflow-distributed.ts +0 -916
- package/src/workflow.async-safety.integration.test.ts +0 -345
- package/src/workflow.test.ts +0 -647
- package/src/workflow.ts +0 -810
- package/src/yaml-config.test.ts +0 -337
- package/src/yaml-config.ts +0 -342
package/src/workflow.test.ts
DELETED
|
@@ -1,647 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeEach, vi } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
traceWorkflow,
|
|
4
|
-
traceStep,
|
|
5
|
-
getCurrentWorkflowContext,
|
|
6
|
-
isInWorkflow,
|
|
7
|
-
type WorkflowConfig,
|
|
8
|
-
type WorkflowContext,
|
|
9
|
-
type StepContext,
|
|
10
|
-
} from './workflow';
|
|
11
|
-
|
|
12
|
-
// Mock the functional trace
|
|
13
|
-
vi.mock('./functional', () => ({
|
|
14
|
-
trace: vi.fn((name, factory) => {
|
|
15
|
-
return (...args: unknown[]) => {
|
|
16
|
-
const mockCtx = createMockTraceContext();
|
|
17
|
-
const fn = factory(mockCtx);
|
|
18
|
-
return fn(...args);
|
|
19
|
-
};
|
|
20
|
-
}),
|
|
21
|
-
}));
|
|
22
|
-
|
|
23
|
-
// Mock trace-helpers
|
|
24
|
-
vi.mock('./trace-helpers', () => ({
|
|
25
|
-
getActiveSpan: vi.fn(() => ({
|
|
26
|
-
spanContext: () => ({
|
|
27
|
-
traceId: '00000000000000000000000000000001',
|
|
28
|
-
spanId: '0000000000000001',
|
|
29
|
-
traceFlags: 1,
|
|
30
|
-
}),
|
|
31
|
-
})),
|
|
32
|
-
finalizeSpan: vi.fn(),
|
|
33
|
-
}));
|
|
34
|
-
|
|
35
|
-
function createMockTraceContext() {
|
|
36
|
-
const attributes: Record<string, unknown> = {};
|
|
37
|
-
const events: Array<{ name: string; attributes?: Record<string, unknown> }> =
|
|
38
|
-
[];
|
|
39
|
-
const links: unknown[] = [];
|
|
40
|
-
|
|
41
|
-
return {
|
|
42
|
-
setAttribute: vi.fn((key, value) => {
|
|
43
|
-
attributes[key] = value;
|
|
44
|
-
}),
|
|
45
|
-
setAttributes: vi.fn((attrs) => {
|
|
46
|
-
Object.assign(attributes, attrs);
|
|
47
|
-
}),
|
|
48
|
-
addEvent: vi.fn((name, attrs) => {
|
|
49
|
-
events.push({ name, attributes: attrs });
|
|
50
|
-
}),
|
|
51
|
-
addLink: vi.fn((link) => {
|
|
52
|
-
links.push(link);
|
|
53
|
-
}),
|
|
54
|
-
addLinks: vi.fn((newLinks) => {
|
|
55
|
-
links.push(...newLinks);
|
|
56
|
-
}),
|
|
57
|
-
setStatus: vi.fn(),
|
|
58
|
-
getAttributes: () => attributes,
|
|
59
|
-
getEvents: () => events,
|
|
60
|
-
getLinks: () => links,
|
|
61
|
-
};
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
describe('Workflow Helpers', () => {
|
|
65
|
-
beforeEach(() => {
|
|
66
|
-
vi.clearAllMocks();
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
describe('traceWorkflow', () => {
|
|
70
|
-
it('should create a workflow function', async () => {
|
|
71
|
-
const config: WorkflowConfig = {
|
|
72
|
-
name: 'TestWorkflow',
|
|
73
|
-
workflowId: 'wf-123',
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const workflow = traceWorkflow(config)(
|
|
77
|
-
(_ctx) => async (input: string) => {
|
|
78
|
-
return `processed: ${input}`;
|
|
79
|
-
},
|
|
80
|
-
);
|
|
81
|
-
|
|
82
|
-
const result = await workflow('test-input');
|
|
83
|
-
expect(result).toBe('processed: test-input');
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('should generate workflow ID from function', async () => {
|
|
87
|
-
const config: WorkflowConfig<[{ id: string }]> = {
|
|
88
|
-
name: 'OrderWorkflow',
|
|
89
|
-
workflowId: (order) => order.id,
|
|
90
|
-
};
|
|
91
|
-
|
|
92
|
-
const workflow = traceWorkflow(config)(
|
|
93
|
-
(_ctx) => async (order: { id: string }) => {
|
|
94
|
-
return { processed: true, orderId: order.id };
|
|
95
|
-
},
|
|
96
|
-
);
|
|
97
|
-
|
|
98
|
-
const result = await workflow({ id: 'order-456' });
|
|
99
|
-
expect(result).toEqual({ processed: true, orderId: 'order-456' });
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
it('should support workflow version', () => {
|
|
103
|
-
const config: WorkflowConfig = {
|
|
104
|
-
name: 'VersionedWorkflow',
|
|
105
|
-
workflowId: 'wf-1',
|
|
106
|
-
version: '2.0.0',
|
|
107
|
-
};
|
|
108
|
-
|
|
109
|
-
const workflow = traceWorkflow(config)((_ctx) => async () => {
|
|
110
|
-
return true;
|
|
111
|
-
});
|
|
112
|
-
|
|
113
|
-
expect(workflow).toBeDefined();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it('should support custom attributes', async () => {
|
|
117
|
-
const config: WorkflowConfig = {
|
|
118
|
-
name: 'AttributedWorkflow',
|
|
119
|
-
workflowId: 'wf-1',
|
|
120
|
-
attributes: {
|
|
121
|
-
'workflow.type': 'fulfillment',
|
|
122
|
-
'workflow.priority': 'high',
|
|
123
|
-
},
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const workflow = traceWorkflow(config)((_ctx) => async () => {
|
|
127
|
-
return true;
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
await workflow();
|
|
131
|
-
expect(workflow).toBeDefined();
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
it('should call onComplete callback on success', async () => {
|
|
135
|
-
const onComplete = vi.fn();
|
|
136
|
-
const config: WorkflowConfig = {
|
|
137
|
-
name: 'SuccessWorkflow',
|
|
138
|
-
workflowId: 'wf-1',
|
|
139
|
-
onComplete,
|
|
140
|
-
};
|
|
141
|
-
|
|
142
|
-
const workflow = traceWorkflow(config)((_ctx) => async () => {
|
|
143
|
-
return { success: true };
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
await workflow();
|
|
147
|
-
expect(onComplete).toHaveBeenCalled();
|
|
148
|
-
});
|
|
149
|
-
|
|
150
|
-
it('should call onFailed callback on error', async () => {
|
|
151
|
-
const onFailed = vi.fn();
|
|
152
|
-
const testError = new Error('Workflow failed');
|
|
153
|
-
|
|
154
|
-
const config: WorkflowConfig = {
|
|
155
|
-
name: 'FailedWorkflow',
|
|
156
|
-
workflowId: 'wf-1',
|
|
157
|
-
onFailed,
|
|
158
|
-
};
|
|
159
|
-
|
|
160
|
-
const workflow = traceWorkflow(config)((_ctx) => async () => {
|
|
161
|
-
throw testError;
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
await expect(workflow()).rejects.toThrow('Workflow failed');
|
|
165
|
-
expect(onFailed).toHaveBeenCalledWith(expect.anything(), testError);
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('should call onCompensating when compensations exist', async () => {
|
|
169
|
-
const onCompensating = vi.fn();
|
|
170
|
-
const config: WorkflowConfig = {
|
|
171
|
-
name: 'CompensatingWorkflow',
|
|
172
|
-
workflowId: 'wf-1',
|
|
173
|
-
onCompensating,
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const workflow = traceWorkflow(config)((ctx) => async () => {
|
|
177
|
-
// Register a compensation and mark step as completed
|
|
178
|
-
ctx.registerCompensation('step1', async () => {});
|
|
179
|
-
ctx.completeStep('step1');
|
|
180
|
-
throw new Error('Trigger compensation');
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
await expect(workflow()).rejects.toThrow();
|
|
184
|
-
// Note: In mock environment, onCompensating may not be called
|
|
185
|
-
// because the actual workflow state is mocked
|
|
186
|
-
expect(workflow).toBeDefined();
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
describe('WorkflowContext', () => {
|
|
191
|
-
it('getWorkflowId should return the workflow ID', async () => {
|
|
192
|
-
const workflow = traceWorkflow({
|
|
193
|
-
name: 'Test',
|
|
194
|
-
workflowId: 'my-workflow-123',
|
|
195
|
-
})((ctx) => async () => {
|
|
196
|
-
return ctx.getWorkflowId();
|
|
197
|
-
});
|
|
198
|
-
|
|
199
|
-
const result = await workflow();
|
|
200
|
-
expect(result).toBe('my-workflow-123');
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
it('getWorkflowName should return the workflow name', async () => {
|
|
204
|
-
const workflow = traceWorkflow({
|
|
205
|
-
name: 'MyTestWorkflow',
|
|
206
|
-
workflowId: 'wf-1',
|
|
207
|
-
})((ctx) => async () => {
|
|
208
|
-
return ctx.getWorkflowName();
|
|
209
|
-
});
|
|
210
|
-
|
|
211
|
-
const result = await workflow();
|
|
212
|
-
expect(result).toBe('MyTestWorkflow');
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
it('getStatus should return current status', async () => {
|
|
216
|
-
const workflow = traceWorkflow({
|
|
217
|
-
name: 'StatusWorkflow',
|
|
218
|
-
workflowId: 'wf-1',
|
|
219
|
-
})((ctx) => async () => {
|
|
220
|
-
return ctx.getStatus();
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
const result = await workflow();
|
|
224
|
-
expect(result).toBe('running');
|
|
225
|
-
});
|
|
226
|
-
|
|
227
|
-
it('getCompletedSteps should return list of completed steps', async () => {
|
|
228
|
-
// Note: Due to mocking, the internal state isn't fully simulated
|
|
229
|
-
// This test verifies the method exists and is callable
|
|
230
|
-
const workflow = traceWorkflow({
|
|
231
|
-
name: 'StepsWorkflow',
|
|
232
|
-
workflowId: 'wf-1',
|
|
233
|
-
})((ctx) => async () => {
|
|
234
|
-
ctx.completeStep('step1');
|
|
235
|
-
ctx.completeStep('step2');
|
|
236
|
-
const steps = ctx.getCompletedSteps();
|
|
237
|
-
// In mocked environment, steps tracking may not work
|
|
238
|
-
expect(Array.isArray(steps)).toBe(true);
|
|
239
|
-
return steps;
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
await workflow();
|
|
243
|
-
});
|
|
244
|
-
|
|
245
|
-
it('registerCompensation should store compensation handler', async () => {
|
|
246
|
-
const compensation = vi.fn();
|
|
247
|
-
|
|
248
|
-
const workflow = traceWorkflow({
|
|
249
|
-
name: 'CompWorkflow',
|
|
250
|
-
workflowId: 'wf-1',
|
|
251
|
-
})((ctx) => async () => {
|
|
252
|
-
ctx.registerCompensation('myStep', compensation);
|
|
253
|
-
ctx.completeStep('myStep');
|
|
254
|
-
throw new Error('trigger');
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
try {
|
|
258
|
-
await workflow();
|
|
259
|
-
} catch {
|
|
260
|
-
// Expected
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
// Note: In mocked environment, compensation may not be called
|
|
264
|
-
// because the internal WeakMap state is bypassed by mocks
|
|
265
|
-
expect(workflow).toBeDefined();
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
it('setWorkflowStatus should update status', async () => {
|
|
269
|
-
let finalStatus = '';
|
|
270
|
-
|
|
271
|
-
const workflow = traceWorkflow({
|
|
272
|
-
name: 'StatusWorkflow',
|
|
273
|
-
workflowId: 'wf-1',
|
|
274
|
-
onComplete: (ctx) => {
|
|
275
|
-
finalStatus = ctx.getStatus();
|
|
276
|
-
},
|
|
277
|
-
})((_ctx) => async () => {
|
|
278
|
-
return true;
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
await workflow();
|
|
282
|
-
expect(finalStatus).toBe('completed');
|
|
283
|
-
});
|
|
284
|
-
});
|
|
285
|
-
|
|
286
|
-
describe('traceStep', () => {
|
|
287
|
-
it('should create a step function', async () => {
|
|
288
|
-
const step = traceStep({
|
|
289
|
-
name: 'TestStep',
|
|
290
|
-
})(async (input: string) => {
|
|
291
|
-
return `step result: ${input}`;
|
|
292
|
-
});
|
|
293
|
-
|
|
294
|
-
const result = await step('test');
|
|
295
|
-
expect(result).toBe('step result: test');
|
|
296
|
-
});
|
|
297
|
-
|
|
298
|
-
it('should support step description', () => {
|
|
299
|
-
const step = traceStep({
|
|
300
|
-
name: 'DescribedStep',
|
|
301
|
-
description: 'This step validates the order',
|
|
302
|
-
})(async () => true);
|
|
303
|
-
|
|
304
|
-
expect(step).toBeDefined();
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
it('should support explicit step index', () => {
|
|
308
|
-
const step = traceStep({
|
|
309
|
-
name: 'IndexedStep',
|
|
310
|
-
index: 5,
|
|
311
|
-
})(async () => true);
|
|
312
|
-
|
|
313
|
-
expect(step).toBeDefined();
|
|
314
|
-
});
|
|
315
|
-
|
|
316
|
-
it('should support custom attributes', () => {
|
|
317
|
-
const step = traceStep({
|
|
318
|
-
name: 'AttributedStep',
|
|
319
|
-
attributes: {
|
|
320
|
-
'step.type': 'validation',
|
|
321
|
-
'step.critical': true,
|
|
322
|
-
},
|
|
323
|
-
})(async () => true);
|
|
324
|
-
|
|
325
|
-
expect(step).toBeDefined();
|
|
326
|
-
});
|
|
327
|
-
|
|
328
|
-
it('should support idempotent flag', () => {
|
|
329
|
-
const step = traceStep({
|
|
330
|
-
name: 'IdempotentStep',
|
|
331
|
-
idempotent: true,
|
|
332
|
-
})(async () => true);
|
|
333
|
-
|
|
334
|
-
expect(step).toBeDefined();
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it('should support retry configuration', async () => {
|
|
338
|
-
let attempts = 0;
|
|
339
|
-
|
|
340
|
-
const step = traceStep({
|
|
341
|
-
name: 'RetryStep',
|
|
342
|
-
retry: {
|
|
343
|
-
maxAttempts: 3,
|
|
344
|
-
backoffMs: 10,
|
|
345
|
-
},
|
|
346
|
-
})(async () => {
|
|
347
|
-
attempts++;
|
|
348
|
-
if (attempts < 3) {
|
|
349
|
-
throw new Error('Retry needed');
|
|
350
|
-
}
|
|
351
|
-
return true;
|
|
352
|
-
});
|
|
353
|
-
|
|
354
|
-
const result = await step();
|
|
355
|
-
expect(result).toBe(true);
|
|
356
|
-
expect(attempts).toBe(3);
|
|
357
|
-
});
|
|
358
|
-
|
|
359
|
-
it('should call onComplete callback on success', async () => {
|
|
360
|
-
const onComplete = vi.fn();
|
|
361
|
-
|
|
362
|
-
const step = traceStep({
|
|
363
|
-
name: 'SuccessStep',
|
|
364
|
-
onComplete,
|
|
365
|
-
})(async () => 'done');
|
|
366
|
-
|
|
367
|
-
await step();
|
|
368
|
-
expect(onComplete).toHaveBeenCalled();
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('should call onFailed callback on error', async () => {
|
|
372
|
-
const onFailed = vi.fn();
|
|
373
|
-
const testError = new Error('Step failed');
|
|
374
|
-
|
|
375
|
-
const step = traceStep({
|
|
376
|
-
name: 'FailedStep',
|
|
377
|
-
onFailed,
|
|
378
|
-
})(async () => {
|
|
379
|
-
throw testError;
|
|
380
|
-
});
|
|
381
|
-
|
|
382
|
-
await expect(step()).rejects.toThrow('Step failed');
|
|
383
|
-
expect(onFailed).toHaveBeenCalled();
|
|
384
|
-
});
|
|
385
|
-
|
|
386
|
-
it('should support compensation handler', () => {
|
|
387
|
-
const compensate = vi.fn();
|
|
388
|
-
|
|
389
|
-
const step = traceStep({
|
|
390
|
-
name: 'CompensableStep',
|
|
391
|
-
compensate,
|
|
392
|
-
})(async () => true);
|
|
393
|
-
|
|
394
|
-
expect(step).toBeDefined();
|
|
395
|
-
});
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
describe('StepContext', () => {
|
|
399
|
-
it('getStepName should return step name', async () => {
|
|
400
|
-
// Note: Testing context methods requires mocking the internal context creation
|
|
401
|
-
const step = traceStep({
|
|
402
|
-
name: 'NamedStep',
|
|
403
|
-
})(async () => 'result');
|
|
404
|
-
|
|
405
|
-
const result = await step();
|
|
406
|
-
expect(result).toBe('result');
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
it('skip should mark step as skipped', async () => {
|
|
410
|
-
const step = traceStep({
|
|
411
|
-
name: 'SkippableStep',
|
|
412
|
-
})(async function (this: StepContext) {
|
|
413
|
-
// Skip would be called via context
|
|
414
|
-
return 'skipped';
|
|
415
|
-
});
|
|
416
|
-
|
|
417
|
-
expect(step).toBeDefined();
|
|
418
|
-
});
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
describe('Step linking', () => {
|
|
422
|
-
it('should support linkToPrevious option', () => {
|
|
423
|
-
const step = traceStep({
|
|
424
|
-
name: 'LinkedStep',
|
|
425
|
-
linkToPrevious: true,
|
|
426
|
-
})(async () => true);
|
|
427
|
-
|
|
428
|
-
expect(step).toBeDefined();
|
|
429
|
-
});
|
|
430
|
-
|
|
431
|
-
it('should support linkTo specific step', () => {
|
|
432
|
-
const step = traceStep({
|
|
433
|
-
name: 'DependentStep',
|
|
434
|
-
linkTo: 'ValidateOrder',
|
|
435
|
-
})(async () => true);
|
|
436
|
-
|
|
437
|
-
expect(step).toBeDefined();
|
|
438
|
-
});
|
|
439
|
-
|
|
440
|
-
it('should support linkTo multiple steps', () => {
|
|
441
|
-
const step = traceStep({
|
|
442
|
-
name: 'MultiDependentStep',
|
|
443
|
-
linkTo: ['ValidateOrder', 'CheckInventory'],
|
|
444
|
-
})(async () => true);
|
|
445
|
-
|
|
446
|
-
expect(step).toBeDefined();
|
|
447
|
-
});
|
|
448
|
-
});
|
|
449
|
-
|
|
450
|
-
describe('getCurrentWorkflowContext', () => {
|
|
451
|
-
it('should return null outside workflow', () => {
|
|
452
|
-
const ctx = getCurrentWorkflowContext();
|
|
453
|
-
expect(ctx).toBeNull();
|
|
454
|
-
});
|
|
455
|
-
|
|
456
|
-
it('should return context inside workflow', async () => {
|
|
457
|
-
let insideCtx: WorkflowContext | null = null;
|
|
458
|
-
|
|
459
|
-
const workflow = traceWorkflow({
|
|
460
|
-
name: 'ContextWorkflow',
|
|
461
|
-
workflowId: 'wf-1',
|
|
462
|
-
})((_ctx) => async () => {
|
|
463
|
-
insideCtx = getCurrentWorkflowContext();
|
|
464
|
-
return true;
|
|
465
|
-
});
|
|
466
|
-
|
|
467
|
-
await workflow();
|
|
468
|
-
expect(insideCtx).not.toBeNull();
|
|
469
|
-
});
|
|
470
|
-
});
|
|
471
|
-
|
|
472
|
-
describe('isInWorkflow', () => {
|
|
473
|
-
it('should return false outside workflow', () => {
|
|
474
|
-
expect(isInWorkflow()).toBe(false);
|
|
475
|
-
});
|
|
476
|
-
|
|
477
|
-
it('should return true inside workflow', async () => {
|
|
478
|
-
let insideWorkflow = false;
|
|
479
|
-
|
|
480
|
-
const workflow = traceWorkflow({
|
|
481
|
-
name: 'CheckWorkflow',
|
|
482
|
-
workflowId: 'wf-1',
|
|
483
|
-
})((_ctx) => async () => {
|
|
484
|
-
insideWorkflow = isInWorkflow();
|
|
485
|
-
return true;
|
|
486
|
-
});
|
|
487
|
-
|
|
488
|
-
await workflow();
|
|
489
|
-
expect(insideWorkflow).toBe(true);
|
|
490
|
-
});
|
|
491
|
-
});
|
|
492
|
-
|
|
493
|
-
describe('Workflow status transitions', () => {
|
|
494
|
-
it('should transition from running to completed on success', async () => {
|
|
495
|
-
const statuses: string[] = [];
|
|
496
|
-
|
|
497
|
-
const workflow = traceWorkflow({
|
|
498
|
-
name: 'StatusWorkflow',
|
|
499
|
-
workflowId: 'wf-1',
|
|
500
|
-
onComplete: (ctx) => statuses.push(ctx.getStatus()),
|
|
501
|
-
})((ctx) => async () => {
|
|
502
|
-
statuses.push(ctx.getStatus());
|
|
503
|
-
return true;
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
await workflow();
|
|
507
|
-
expect(statuses).toContain('running');
|
|
508
|
-
expect(statuses).toContain('completed');
|
|
509
|
-
});
|
|
510
|
-
|
|
511
|
-
it('should transition from running to failed on error', async () => {
|
|
512
|
-
const statuses: string[] = [];
|
|
513
|
-
|
|
514
|
-
const workflow = traceWorkflow({
|
|
515
|
-
name: 'FailWorkflow',
|
|
516
|
-
workflowId: 'wf-1',
|
|
517
|
-
onFailed: (ctx) => statuses.push(ctx.getStatus()),
|
|
518
|
-
})((ctx) => async () => {
|
|
519
|
-
statuses.push(ctx.getStatus());
|
|
520
|
-
throw new Error('fail');
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
try {
|
|
524
|
-
await workflow();
|
|
525
|
-
} catch {
|
|
526
|
-
// Expected
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
expect(statuses).toContain('running');
|
|
530
|
-
expect(statuses).toContain('failed');
|
|
531
|
-
});
|
|
532
|
-
|
|
533
|
-
it('should transition to compensating when compensations exist', async () => {
|
|
534
|
-
// Note: In mocked environment, the internal state tracking is bypassed
|
|
535
|
-
// This test verifies the workflow handles errors correctly
|
|
536
|
-
const workflow = traceWorkflow({
|
|
537
|
-
name: 'CompWorkflow',
|
|
538
|
-
workflowId: 'wf-1',
|
|
539
|
-
onCompensating: () => {},
|
|
540
|
-
})((ctx) => async () => {
|
|
541
|
-
ctx.registerCompensation('step1', async () => {});
|
|
542
|
-
ctx.completeStep('step1');
|
|
543
|
-
throw new Error('trigger');
|
|
544
|
-
});
|
|
545
|
-
|
|
546
|
-
await expect(workflow()).rejects.toThrow('trigger');
|
|
547
|
-
});
|
|
548
|
-
});
|
|
549
|
-
|
|
550
|
-
describe('Compensation execution', () => {
|
|
551
|
-
it('should execute compensations in reverse order', async () => {
|
|
552
|
-
// Note: In mocked environment, compensation execution is bypassed
|
|
553
|
-
// This test verifies the structure of the compensation pattern
|
|
554
|
-
const workflow = traceWorkflow({
|
|
555
|
-
name: 'ReverseCompWorkflow',
|
|
556
|
-
workflowId: 'wf-1',
|
|
557
|
-
})((ctx) => async () => {
|
|
558
|
-
ctx.registerCompensation('step1', async () => {});
|
|
559
|
-
ctx.completeStep('step1');
|
|
560
|
-
|
|
561
|
-
ctx.registerCompensation('step2', async () => {});
|
|
562
|
-
ctx.completeStep('step2');
|
|
563
|
-
|
|
564
|
-
ctx.registerCompensation('step3', async () => {});
|
|
565
|
-
ctx.completeStep('step3');
|
|
566
|
-
|
|
567
|
-
throw new Error('trigger compensations');
|
|
568
|
-
});
|
|
569
|
-
|
|
570
|
-
await expect(workflow()).rejects.toThrow('trigger compensations');
|
|
571
|
-
});
|
|
572
|
-
|
|
573
|
-
it('should only compensate completed steps', async () => {
|
|
574
|
-
// Note: In mocked environment, compensation logic is bypassed
|
|
575
|
-
// This test verifies the pattern can be set up correctly
|
|
576
|
-
const workflow = traceWorkflow({
|
|
577
|
-
name: 'PartialCompWorkflow',
|
|
578
|
-
workflowId: 'wf-1',
|
|
579
|
-
})((ctx) => async () => {
|
|
580
|
-
ctx.registerCompensation('step1', async () => {});
|
|
581
|
-
ctx.completeStep('step1');
|
|
582
|
-
|
|
583
|
-
ctx.registerCompensation('step2', async () => {});
|
|
584
|
-
// step2 NOT completed
|
|
585
|
-
|
|
586
|
-
throw new Error('trigger');
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
await expect(workflow()).rejects.toThrow('trigger');
|
|
590
|
-
});
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
describe('Retry mechanism', () => {
|
|
594
|
-
it('should retry step on failure', async () => {
|
|
595
|
-
let attempts = 0;
|
|
596
|
-
|
|
597
|
-
const step = traceStep({
|
|
598
|
-
name: 'RetryStep',
|
|
599
|
-
retry: { maxAttempts: 3 },
|
|
600
|
-
})(async () => {
|
|
601
|
-
attempts++;
|
|
602
|
-
if (attempts < 2) throw new Error('retry');
|
|
603
|
-
return 'success';
|
|
604
|
-
});
|
|
605
|
-
|
|
606
|
-
const result = await step();
|
|
607
|
-
expect(result).toBe('success');
|
|
608
|
-
expect(attempts).toBe(2);
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
it('should fail after max retries exceeded', async () => {
|
|
612
|
-
let attempts = 0;
|
|
613
|
-
|
|
614
|
-
const step = traceStep({
|
|
615
|
-
name: 'FailRetryStep',
|
|
616
|
-
retry: { maxAttempts: 3 },
|
|
617
|
-
})(async () => {
|
|
618
|
-
attempts++;
|
|
619
|
-
throw new Error('always fail');
|
|
620
|
-
});
|
|
621
|
-
|
|
622
|
-
await expect(step()).rejects.toThrow('always fail');
|
|
623
|
-
expect(attempts).toBe(3);
|
|
624
|
-
});
|
|
625
|
-
|
|
626
|
-
it('should apply backoff between retries', async () => {
|
|
627
|
-
const timestamps: number[] = [];
|
|
628
|
-
|
|
629
|
-
const step = traceStep({
|
|
630
|
-
name: 'BackoffStep',
|
|
631
|
-
retry: { maxAttempts: 3, backoffMs: 50 },
|
|
632
|
-
})(async () => {
|
|
633
|
-
timestamps.push(Date.now());
|
|
634
|
-
if (timestamps.length < 3) throw new Error('retry');
|
|
635
|
-
return 'done';
|
|
636
|
-
});
|
|
637
|
-
|
|
638
|
-
await step();
|
|
639
|
-
|
|
640
|
-
// Check that there was some delay between attempts
|
|
641
|
-
if (timestamps.length >= 2) {
|
|
642
|
-
const delay = timestamps[1] - timestamps[0];
|
|
643
|
-
expect(delay).toBeGreaterThanOrEqual(40); // Allow some variance
|
|
644
|
-
}
|
|
645
|
-
});
|
|
646
|
-
});
|
|
647
|
-
});
|