@fiducian/temporal 0.4.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/dist/index.d.ts +44 -0
- package/dist/index.js +93 -0
- package/dist/index.test.d.ts +1 -0
- package/dist/index.test.js +33 -0
- package/package.json +34 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { StoryContext, TraceEvent } from "@fiducian/conductor";
|
|
2
|
+
import type { FiduciaTrace } from "@fiducian/runtime";
|
|
3
|
+
export declare const TEMPORAL_HEADER_CONTEXT = "fiducia-context";
|
|
4
|
+
export declare const TEMPORAL_HEADER_WORKFLOW = "fiducia-workflow-id";
|
|
5
|
+
export interface WorkflowStep {
|
|
6
|
+
from: string;
|
|
7
|
+
to: string;
|
|
8
|
+
payload?: Record<string, unknown>;
|
|
9
|
+
op?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface TemporalHeaders {
|
|
12
|
+
[TEMPORAL_HEADER_CONTEXT]?: string;
|
|
13
|
+
[TEMPORAL_HEADER_WORKFLOW]?: string;
|
|
14
|
+
[key: string]: string | undefined;
|
|
15
|
+
}
|
|
16
|
+
export declare class FiduciaTemporal {
|
|
17
|
+
readonly ct: FiduciaTrace;
|
|
18
|
+
readonly workflowType: string;
|
|
19
|
+
constructor(ct: FiduciaTrace, workflowType: string);
|
|
20
|
+
contextForWorkflowRun(workflowId: string): StoryContext;
|
|
21
|
+
encodeHeaders(ctx: StoryContext, workflowId: string): TemporalHeaders;
|
|
22
|
+
decodeHeaders(headers: TemporalHeaders | undefined): StoryContext | undefined;
|
|
23
|
+
emitStep(ctx: StoryContext, step: WorkflowStep): TraceEvent;
|
|
24
|
+
}
|
|
25
|
+
export interface ActivityInterceptorHandlers {
|
|
26
|
+
inbound: {
|
|
27
|
+
execute: (input: unknown, next: (input: unknown) => Promise<unknown>) => Promise<unknown>;
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
export interface ActivityExecutionInput {
|
|
31
|
+
headers?: TemporalHeaders;
|
|
32
|
+
activityType: string;
|
|
33
|
+
args: unknown[];
|
|
34
|
+
}
|
|
35
|
+
export interface TemporalActivityInterceptorOptions {
|
|
36
|
+
bridge: FiduciaTemporal;
|
|
37
|
+
mapActivity: (activityType: string, args: unknown[]) => WorkflowStep | undefined;
|
|
38
|
+
}
|
|
39
|
+
export declare function createActivityInterceptor(options: TemporalActivityInterceptorOptions): ActivityInterceptorHandlers;
|
|
40
|
+
export type CheckoutMode = "bug" | "fix";
|
|
41
|
+
export declare function simulateCheckoutWorkflow(bridge: FiduciaTemporal, workflowId: string, mode: CheckoutMode): Promise<{
|
|
42
|
+
ctx: StoryContext;
|
|
43
|
+
evaluation: ReturnType<FiduciaTrace["evaluateRecorded"]>;
|
|
44
|
+
}>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
export const TEMPORAL_HEADER_CONTEXT = "fiducia-context";
|
|
2
|
+
export const TEMPORAL_HEADER_WORKFLOW = "fiducia-workflow-id";
|
|
3
|
+
export class FiduciaTemporal {
|
|
4
|
+
ct;
|
|
5
|
+
workflowType;
|
|
6
|
+
constructor(ct, workflowType) {
|
|
7
|
+
this.ct = ct;
|
|
8
|
+
this.workflowType = workflowType;
|
|
9
|
+
}
|
|
10
|
+
contextForWorkflowRun(workflowId) {
|
|
11
|
+
return this.ct.conductor.startStoryWithId(`${this.workflowType}:${workflowId}`);
|
|
12
|
+
}
|
|
13
|
+
encodeHeaders(ctx, workflowId) {
|
|
14
|
+
return {
|
|
15
|
+
[TEMPORAL_HEADER_CONTEXT]: ctx.contextId,
|
|
16
|
+
[TEMPORAL_HEADER_WORKFLOW]: workflowId,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
decodeHeaders(headers) {
|
|
20
|
+
if (!headers?.[TEMPORAL_HEADER_CONTEXT])
|
|
21
|
+
return undefined;
|
|
22
|
+
return { contextId: headers[TEMPORAL_HEADER_CONTEXT] };
|
|
23
|
+
}
|
|
24
|
+
emitStep(ctx, step) {
|
|
25
|
+
return this.ct.emit(ctx, {
|
|
26
|
+
from: step.from,
|
|
27
|
+
to: step.to,
|
|
28
|
+
op: step.op ?? "send",
|
|
29
|
+
payload: step.payload ?? {},
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function createActivityInterceptor(options) {
|
|
34
|
+
return {
|
|
35
|
+
inbound: {
|
|
36
|
+
execute: async (input, next) => {
|
|
37
|
+
const execution = input;
|
|
38
|
+
const ctx = options.bridge.decodeHeaders(execution.headers);
|
|
39
|
+
const step = options.mapActivity(execution.activityType, execution.args);
|
|
40
|
+
if (ctx && step) {
|
|
41
|
+
options.bridge.emitStep(ctx, step);
|
|
42
|
+
}
|
|
43
|
+
return next(input);
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export async function simulateCheckoutWorkflow(bridge, workflowId, mode) {
|
|
49
|
+
const ctx = bridge.contextForWorkflowRun(workflowId);
|
|
50
|
+
const headers = bridge.encodeHeaders(ctx, workflowId);
|
|
51
|
+
const runActivity = (activityType, args) => {
|
|
52
|
+
const interceptor = createActivityInterceptor({
|
|
53
|
+
bridge,
|
|
54
|
+
mapActivity: (type, activityArgs) => activityMapping(type, activityArgs),
|
|
55
|
+
});
|
|
56
|
+
return interceptor.inbound.execute({ headers, activityType, args }, async () => undefined);
|
|
57
|
+
};
|
|
58
|
+
await runActivity("PlaceOrder", [{ orderId: "ord-99" }]);
|
|
59
|
+
if (mode === "bug") {
|
|
60
|
+
await runActivity("ChargePayment", [{ orderId: "ord-99", charged: true }]);
|
|
61
|
+
await runActivity("AcceptKitchen", [{ orderId: "ord-99", accepted: true }]);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
await runActivity("AcceptKitchen", [{ orderId: "ord-99", accepted: true }]);
|
|
65
|
+
await runActivity("ChargePayment", [{ orderId: "ord-99", charged: true }]);
|
|
66
|
+
}
|
|
67
|
+
return { ctx, evaluation: bridge.ct.evaluateRecorded() };
|
|
68
|
+
}
|
|
69
|
+
function activityMapping(activityType, args) {
|
|
70
|
+
const record = (args[0] ?? {});
|
|
71
|
+
switch (activityType) {
|
|
72
|
+
case "PlaceOrder":
|
|
73
|
+
return {
|
|
74
|
+
from: "client",
|
|
75
|
+
to: "order",
|
|
76
|
+
payload: { orderId: record.orderId },
|
|
77
|
+
};
|
|
78
|
+
case "AcceptKitchen":
|
|
79
|
+
return {
|
|
80
|
+
from: "order",
|
|
81
|
+
to: "kitchen",
|
|
82
|
+
payload: { orderId: record.orderId, accepted: record.accepted === true },
|
|
83
|
+
};
|
|
84
|
+
case "ChargePayment":
|
|
85
|
+
return {
|
|
86
|
+
from: "order",
|
|
87
|
+
to: "billing",
|
|
88
|
+
payload: { orderId: record.orderId, charged: record.charged === true },
|
|
89
|
+
};
|
|
90
|
+
default:
|
|
91
|
+
return undefined;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import test from "node:test";
|
|
2
|
+
import assert from "node:assert/strict";
|
|
3
|
+
import { FiduciaTrace, parseWaltzFile } from "@fiducian/runtime";
|
|
4
|
+
import { simulateCheckoutWorkflow, FiduciaTemporal } from "./index.js";
|
|
5
|
+
const spec = parseWaltzFile(`
|
|
6
|
+
Omega(
|
|
7
|
+
send client->order{orderId} : true
|
|
8
|
+
;
|
|
9
|
+
send order->kitchen{orderId, accepted} : accepted == true
|
|
10
|
+
;
|
|
11
|
+
send order->billing{orderId, charged} : charged == true
|
|
12
|
+
)
|
|
13
|
+
`);
|
|
14
|
+
test("simulateCheckoutWorkflow fix mode passes", async () => {
|
|
15
|
+
const ct = new FiduciaTrace({ spec });
|
|
16
|
+
const bridge = new FiduciaTemporal(ct, "CheckoutWorkflow");
|
|
17
|
+
const { evaluation } = await simulateCheckoutWorkflow(bridge, "run-1", "fix");
|
|
18
|
+
assert.equal(evaluation.passed, true);
|
|
19
|
+
});
|
|
20
|
+
test("simulateCheckoutWorkflow bug mode fails", async () => {
|
|
21
|
+
const ct = new FiduciaTrace({ spec });
|
|
22
|
+
const bridge = new FiduciaTemporal(ct, "CheckoutWorkflow");
|
|
23
|
+
const { evaluation } = await simulateCheckoutWorkflow(bridge, "run-2", "bug");
|
|
24
|
+
assert.equal(evaluation.passed, false);
|
|
25
|
+
});
|
|
26
|
+
test("FiduciaTemporal encodes headers for activity propagation", () => {
|
|
27
|
+
const ct = new FiduciaTrace({ spec });
|
|
28
|
+
const bridge = new FiduciaTemporal(ct, "CheckoutWorkflow");
|
|
29
|
+
const ctx = bridge.contextForWorkflowRun("wf-1");
|
|
30
|
+
const headers = bridge.encodeHeaders(ctx, "wf-1");
|
|
31
|
+
assert.equal(headers["fiducia-context"], ctx.contextId);
|
|
32
|
+
assert.equal(headers["fiducia-workflow-id"], "wf-1");
|
|
33
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@fiducian/temporal",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "Temporal workflow and activity hooks for FiduciaTrace",
|
|
5
|
+
"license": "AGPL-3.0-or-later",
|
|
6
|
+
"keywords": ["fiducia", "fiducia", "temporal", "workflow", "activities"],
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/thesnmc/Fiducian.git",
|
|
10
|
+
"directory": "trace/packages/temporal"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"files": ["dist"],
|
|
16
|
+
"type": "module",
|
|
17
|
+
"main": "./dist/index.js",
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"exports": {
|
|
20
|
+
".": {
|
|
21
|
+
"types": "./dist/index.d.ts",
|
|
22
|
+
"import": "./dist/index.js"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"@fiducian/conductor": "0.4.0",
|
|
27
|
+
"@fiducian/runtime": "0.4.0"
|
|
28
|
+
},
|
|
29
|
+
"scripts": {
|
|
30
|
+
"build": "tsc -p tsconfig.json",
|
|
31
|
+
"test": "node --test dist/**/*.test.js",
|
|
32
|
+
"prepublishOnly": "npm run build"
|
|
33
|
+
}
|
|
34
|
+
}
|