@hyperfixation/workflows 0.1.4 → 0.1.6
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/define-flow.js +35 -2
- package/package.json +4 -4
package/dist/define-flow.js
CHANGED
|
@@ -20,6 +20,8 @@ export class UnknownQueue extends Error {
|
|
|
20
20
|
/** Logged when an attempt finds the run already on a later one; it runs nothing. */
|
|
21
21
|
export const SUPERSEDED_MARKER = "hf-run: superseded attempt, the flow was not run";
|
|
22
22
|
const flows = new Map();
|
|
23
|
+
/** Per name, what `fingerprint` made of the definition that got in first. */
|
|
24
|
+
const fingerprints = new Map();
|
|
23
25
|
/** The one source of a queue name for an enqueue, and of a flow name for `runs.start`. */
|
|
24
26
|
export function definedFlows() {
|
|
25
27
|
return flows;
|
|
@@ -33,8 +35,18 @@ export function definedFlows() {
|
|
|
33
35
|
* The DBOS registration itself happens only in a worker process; see the comment on it.
|
|
34
36
|
*/
|
|
35
37
|
export function defineFlow(name, fn, options) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
// A second call with the *same* definition is one definition reaching here twice, not two
|
|
39
|
+
// flows fighting over a name: Next instantiates the app's `src/flows/*.ts` once per module
|
|
40
|
+
// layer — the rsc page layer and the server-action layer of one authenticated request — while
|
|
41
|
+
// this package stays external and singular, so the registry sees both. That is the first
|
|
42
|
+
// instance's flow, already registered with DBOS if this is a worker; hand it back rather than
|
|
43
|
+
// registering it again. Two different definitions of one name are still a collision.
|
|
44
|
+
const existing = flows.get(name);
|
|
45
|
+
if (existing !== undefined) {
|
|
46
|
+
if (fingerprints.get(name) !== fingerprint(fn, options))
|
|
47
|
+
throw new DuplicateFlow(name);
|
|
48
|
+
return existing;
|
|
49
|
+
}
|
|
38
50
|
if (!QUEUES.some((queue) => queue.name === options.queue)) {
|
|
39
51
|
throw new UnknownQueue(name, options.queue);
|
|
40
52
|
}
|
|
@@ -76,8 +88,29 @@ export function defineFlow(name, fn, options) {
|
|
|
76
88
|
const workflow = process.env.HF_PROCESS === WORKER_PROCESS ? DBOS.registerWorkflow(body, { name }) : body;
|
|
77
89
|
const flow = { name, queue: options.queue, workflow };
|
|
78
90
|
flows.set(name, flow);
|
|
91
|
+
fingerprints.set(name, fingerprint(fn, options));
|
|
79
92
|
return flow;
|
|
80
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* What makes two definitions of a name the same definition. Two module copies of one app file
|
|
96
|
+
* produce distinct function objects with identical source, so identity is useless here and the
|
|
97
|
+
* text is what there is; a closed-over value that differs between the copies is invisible to it,
|
|
98
|
+
* which is the known limit of the check.
|
|
99
|
+
*/
|
|
100
|
+
function fingerprint(fn, options) {
|
|
101
|
+
// Length-prefixed, because a function's source can contain whatever a separator would be.
|
|
102
|
+
const json = stableJson(options);
|
|
103
|
+
return `${json.length}:${json}${fn.toString()}`;
|
|
104
|
+
}
|
|
105
|
+
/** `JSON.stringify` with object keys sorted, so key order is not part of the comparison. */
|
|
106
|
+
function stableJson(value) {
|
|
107
|
+
if (typeof value !== "object" || value === null)
|
|
108
|
+
return JSON.stringify(value) ?? "undefined";
|
|
109
|
+
if (Array.isArray(value))
|
|
110
|
+
return `[${value.map(stableJson).join(",")}]`;
|
|
111
|
+
const entries = Object.entries(value).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
112
|
+
return `{${entries.map(([key, inner]) => `${JSON.stringify(key)}:${stableJson(inner)}`).join(",")}}`;
|
|
113
|
+
}
|
|
81
114
|
function messageOf(error) {
|
|
82
115
|
return error instanceof Error ? `${error.name}: ${error.message}` : String(error);
|
|
83
116
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hyperfixation/workflows",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"description": "startWorker, defineFlow and the run lifecycle, the step wrapper, approvals, actions, queues, and the reconciler",
|
|
6
6
|
"repository": {
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
28
|
"@dbos-inc/dbos-sdk": "4.27.6",
|
|
29
|
-
"@hyperfixation/db": "0.1.
|
|
29
|
+
"@hyperfixation/db": "0.1.6",
|
|
30
30
|
"@langfuse/otel": "5.11.1",
|
|
31
31
|
"@opentelemetry/api": "1.9.1",
|
|
32
32
|
"@opentelemetry/context-async-hooks": "2.11.0",
|
|
@@ -39,8 +39,8 @@
|
|
|
39
39
|
"zod": "4.6.5"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
|
-
"@hyperfixation/eslint-config": "0.1.
|
|
43
|
-
"@hyperfixation/testing": "0.1.
|
|
42
|
+
"@hyperfixation/eslint-config": "0.1.6",
|
|
43
|
+
"@hyperfixation/testing": "0.1.6",
|
|
44
44
|
"@microsoft/api-extractor": "^7.59.1",
|
|
45
45
|
"@sentry/node": "^10.74.0",
|
|
46
46
|
"@types/pg": "^8.23.1",
|