@shware/workflow 9.0.0 → 9.0.2

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.
@@ -10,8 +10,10 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
10
10
  * timeouts, goal and `performed(... within)` windows inside the workflow), so
11
11
  * the journey keeps its shape, just faster. What does not scale: wall-clock
12
12
  * `timeWindow`s (they are times of day, not durations). Segment definitions
13
- * live in the store rather than the workflow body; `scaleSegmentWindows`
14
- * scales them as they are read, so a segment's `within` keeps pace too.
13
+ * and trigger routes live in the store rather than the workflow body;
14
+ * `scaleSegmentWindows` scales them as they are read — one segment for an
15
+ * event-driven evaluation, all of them for the scheduled sweep, a route's
16
+ * profile gate at ingest — so a segment's `within` keeps pace everywhere.
15
17
  *
16
18
  * The walk follows the IR's own structure — the fields that hold a Duration
17
19
  * are known — so a message prop or an event payload that happens to look like
@@ -105,20 +107,40 @@ function scale(duration, factor) {
105
107
  };
106
108
  }
107
109
  /**
108
- * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
109
- * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
110
- * shrink with the journeys that reference it, and with the segment triggers it routes. Every
111
- * other method is the store's own.
110
+ * A store whose segment definitions come back scaled by `factor` — what the journey host leases
111
+ * in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`) shrink
112
+ * with the journeys that reference it, and with the segment triggers it routes. Every read that
113
+ * hands out a stored condition is scaled: `getSegmentCondition` (event-driven evaluation),
114
+ * `listSegments` (the scheduled sweep, which narrows candidates in SQL and evaluates with those
115
+ * definitions) and `findTriggers` (the profile gate an event trigger checks before it starts a
116
+ * journey, which `scaleDurations` scales inside the journey too). Every other method is the
117
+ * store's own.
112
118
  */
113
119
  function scaleSegmentWindows(store, factor) {
114
120
  if (!(factor > 0) || factor === 1) return store;
115
121
  return new Proxy(store, { get(target, property) {
116
- if (property === "getSegmentCondition") return async (name) => {
117
- const condition = await target.getSegmentCondition(name);
118
- return condition === void 0 ? void 0 : scaleCondition(condition, factor);
119
- };
120
122
  const value = Reflect.get(target, property, target);
121
- return typeof value === "function" ? value.bind(target) : value;
123
+ if (typeof value !== "function") return value;
124
+ switch (property) {
125
+ case "getSegmentCondition": return async (name) => {
126
+ const condition = await target.getSegmentCondition(name);
127
+ return condition === void 0 ? void 0 : scaleCondition(condition, factor);
128
+ };
129
+ case "listSegments": return async () => {
130
+ return (await value.call(target)).map(({ id, condition }) => ({
131
+ id,
132
+ condition: scaleCondition(condition, factor)
133
+ }));
134
+ };
135
+ case "findTriggers": return async (event) => {
136
+ const scaled = (route) => ({
137
+ ...route,
138
+ filter: route.filter === null ? null : scaleCondition(route.filter, factor)
139
+ });
140
+ return (await value.call(target, event)).map(scaled);
141
+ };
142
+ default: return value.bind(target);
143
+ }
122
144
  } });
123
145
  }
124
146
  exports.scaleCondition = scaleCondition;
@@ -1 +1 @@
1
- {"version":3,"file":"time-scale.cjs","names":[],"sources":["../../src/engine/time-scale.ts"],"sourcesContent":["import type { ConditionIR, DurationIR, NodeIR, WorkflowIR } from '../ir';\n\n/**\n * Debug time scale: divide every duration in a workflow's IR by a factor so a\n * journey written in days plays out in seconds — with a factor of 3600, a\n * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`\n * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.\n *\n * Everything that is a Duration scales together (delays, random delays, wait\n * timeouts, goal and `performed(... within)` windows inside the workflow), so\n * the journey keeps its shape, just faster. What does not scale: wall-clock\n * `timeWindow`s (they are times of day, not durations). Segment definitions\n * live in the store rather than the workflow body; `scaleSegmentWindows`\n * scales them as they are read, so a segment's `within` keeps pace too.\n *\n * The walk follows the IR's own structure — the fields that hold a Duration\n * are known — so a message prop or an event payload that happens to look like\n * one (`{ value, ms }`) is left alone. The source text is kept so the studio\n * still shows what was written.\n */\nexport function scaleDurations(ir: WorkflowIR, factor: number): WorkflowIR {\n if (!(factor > 0) || factor === 1) return ir;\n return {\n ...ir,\n trigger:\n ir.trigger.type === 'event'\n ? {\n ...ir.trigger,\n ...(ir.trigger.filter === undefined\n ? {}\n : { filter: scaleCondition(ir.trigger.filter, factor) }),\n }\n : ir.trigger,\n ...(ir.goal === undefined\n ? {}\n : {\n goal: {\n ...ir.goal,\n condition: scaleCondition(ir.goal.condition, factor),\n ...(ir.goal.within === undefined ? {} : { within: scale(ir.goal.within, factor) }),\n },\n }),\n ...(ir.exitWhen === undefined ? {} : { exitWhen: scaleCondition(ir.exitWhen, factor) }),\n flow: scaleFlow(ir.flow, factor),\n };\n}\n\n/** A condition with its `performed(... within)` windows divided by `factor`. */\nexport function scaleCondition(condition: ConditionIR, factor: number): ConditionIR {\n if (!(factor > 0) || factor === 1) return condition;\n switch (condition.type) {\n case 'and':\n case 'or':\n return {\n ...condition,\n conditions: condition.conditions.map((child) => scaleCondition(child, factor)),\n };\n case 'not':\n return { ...condition, condition: scaleCondition(condition.condition, factor) };\n case 'performed':\n return condition.within === undefined\n ? condition\n : { ...condition, within: scale(condition.within, factor) };\n default:\n return condition;\n }\n}\n\nfunction scaleFlow(nodes: readonly NodeIR[], factor: number): NodeIR[] {\n return nodes.map((node) => scaleNode(node, factor));\n}\n\nfunction scaleNode(node: NodeIR, factor: number): NodeIR {\n switch (node.type) {\n case 'delay':\n return { ...node, duration: scale(node.duration, factor) };\n case 'random_delay':\n return { ...node, min: scale(node.min, factor), max: scale(node.max, factor) };\n case 'wait_until':\n return {\n ...node,\n condition: scaleCondition(node.condition, factor),\n timeout: scale(node.timeout, factor),\n onTimeout: Array.isArray(node.onTimeout)\n ? scaleFlow(node.onTimeout, factor)\n : node.onTimeout,\n };\n case 'branch':\n return {\n ...node,\n cases: node.cases.map((branch) => ({\n ...branch,\n condition: scaleCondition(branch.condition, factor),\n flow: scaleFlow(branch.flow, factor),\n })),\n ...(node.otherwise === undefined ? {} : { otherwise: scaleFlow(node.otherwise, factor) }),\n };\n case 'filter':\n return { ...node, condition: scaleCondition(node.condition, factor) };\n case 'cohort':\n return {\n ...node,\n arms: node.arms.map((arm) => ({ ...arm, flow: scaleFlow(arm.flow, factor) })),\n };\n default:\n return node;\n }\n}\n\nfunction scale(duration: DurationIR, factor: number): DurationIR {\n return { value: duration.value, ms: Math.max(0, Math.round(duration.ms / factor)) };\n}\n\n/**\n * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads\n * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)\n * shrink with the journeys that reference it, and with the segment triggers it routes. Every\n * other method is the store's own.\n */\nexport function scaleSegmentWindows<\n S extends { getSegmentCondition(name: string): Promise<ConditionIR | undefined> },\n>(store: S, factor: number): S {\n if (!(factor > 0) || factor === 1) return store;\n return new Proxy(store, {\n get(target, property) {\n if (property === 'getSegmentCondition') {\n return async (name: string) => {\n const condition = await target.getSegmentCondition(name);\n return condition === undefined ? undefined : scaleCondition(condition, factor);\n };\n }\n const value: unknown = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,eAAe,IAAgB,QAA4B;CACzE,IAAI,EAAE,SAAS,MAAM,WAAW,GAAG,OAAO;CAC1C,OAAO;EACL,GAAG;EACH,SACE,GAAG,QAAQ,SAAS,UAChB;GACE,GAAG,GAAG;GACN,GAAI,GAAG,QAAQ,WAAW,KAAA,IACtB,CAAC,IACD,EAAE,QAAQ,eAAe,GAAG,QAAQ,QAAQ,MAAM,EAAE;EAC1D,IACA,GAAG;EACT,GAAI,GAAG,SAAS,KAAA,IACZ,CAAC,IACD,EACE,MAAM;GACJ,GAAG,GAAG;GACN,WAAW,eAAe,GAAG,KAAK,WAAW,MAAM;GACnD,GAAI,GAAG,KAAK,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,MAAM,GAAG,KAAK,QAAQ,MAAM,EAAE;EAClF,EACF;EACJ,GAAI,GAAG,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,eAAe,GAAG,UAAU,MAAM,EAAE;EACrF,MAAM,UAAU,GAAG,MAAM,MAAM;CACjC;AACF;;AAGA,SAAgB,eAAe,WAAwB,QAA6B;CAClF,IAAI,EAAE,SAAS,MAAM,WAAW,GAAG,OAAO;CAC1C,QAAQ,UAAU,MAAlB;EACE,KAAK;EACL,KAAK,MACH,OAAO;GACL,GAAG;GACH,YAAY,UAAU,WAAW,KAAK,UAAU,eAAe,OAAO,MAAM,CAAC;EAC/E;EACF,KAAK,OACH,OAAO;GAAE,GAAG;GAAW,WAAW,eAAe,UAAU,WAAW,MAAM;EAAE;EAChF,KAAK,aACH,OAAO,UAAU,WAAW,KAAA,IACxB,YACA;GAAE,GAAG;GAAW,QAAQ,MAAM,UAAU,QAAQ,MAAM;EAAE;EAC9D,SACE,OAAO;CACX;AACF;AAEA,SAAS,UAAU,OAA0B,QAA0B;CACrE,OAAO,MAAM,KAAK,SAAS,UAAU,MAAM,MAAM,CAAC;AACpD;AAEA,SAAS,UAAU,MAAc,QAAwB;CACvD,QAAQ,KAAK,MAAb;EACE,KAAK,SACH,OAAO;GAAE,GAAG;GAAM,UAAU,MAAM,KAAK,UAAU,MAAM;EAAE;EAC3D,KAAK,gBACH,OAAO;GAAE,GAAG;GAAM,KAAK,MAAM,KAAK,KAAK,MAAM;GAAG,KAAK,MAAM,KAAK,KAAK,MAAM;EAAE;EAC/E,KAAK,cACH,OAAO;GACL,GAAG;GACH,WAAW,eAAe,KAAK,WAAW,MAAM;GAChD,SAAS,MAAM,KAAK,SAAS,MAAM;GACnC,WAAW,MAAM,QAAQ,KAAK,SAAS,IACnC,UAAU,KAAK,WAAW,MAAM,IAChC,KAAK;EACX;EACF,KAAK,UACH,OAAO;GACL,GAAG;GACH,OAAO,KAAK,MAAM,KAAK,YAAY;IACjC,GAAG;IACH,WAAW,eAAe,OAAO,WAAW,MAAM;IAClD,MAAM,UAAU,OAAO,MAAM,MAAM;GACrC,EAAE;GACF,GAAI,KAAK,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,UAAU,KAAK,WAAW,MAAM,EAAE;EACzF;EACF,KAAK,UACH,OAAO;GAAE,GAAG;GAAM,WAAW,eAAe,KAAK,WAAW,MAAM;EAAE;EACtE,KAAK,UACH,OAAO;GACL,GAAG;GACH,MAAM,KAAK,KAAK,KAAK,SAAS;IAAE,GAAG;IAAK,MAAM,UAAU,IAAI,MAAM,MAAM;GAAE,EAAE;EAC9E;EACF,SACE,OAAO;CACX;AACF;AAEA,SAAS,MAAM,UAAsB,QAA4B;CAC/D,OAAO;EAAE,OAAO,SAAS;EAAO,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,KAAK,MAAM,CAAC;CAAE;AACpF;;;;;;;AAQA,SAAgB,oBAEd,OAAU,QAAmB;CAC7B,IAAI,EAAE,SAAS,MAAM,WAAW,GAAG,OAAO;CAC1C,OAAO,IAAI,MAAM,OAAO,EACtB,IAAI,QAAQ,UAAU;EACpB,IAAI,aAAa,uBACf,OAAO,OAAO,SAAiB;GAC7B,MAAM,YAAY,MAAM,OAAO,oBAAoB,IAAI;GACvD,OAAO,cAAc,KAAA,IAAY,KAAA,IAAY,eAAe,WAAW,MAAM;EAC/E;EAEF,MAAM,QAAiB,QAAQ,IAAI,QAAQ,UAAU,MAAM;EAC3D,OAAO,OAAO,UAAU,aAAa,MAAM,KAAK,MAAM,IAAI;CAC5D,EACF,CAAC;AACH"}
1
+ {"version":3,"file":"time-scale.cjs","names":[],"sources":["../../src/engine/time-scale.ts"],"sourcesContent":["import type { ConditionIR, DurationIR, NodeIR, WorkflowIR } from '../ir';\n\n/**\n * Debug time scale: divide every duration in a workflow's IR by a factor so a\n * journey written in days plays out in seconds — with a factor of 3600, a\n * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`\n * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.\n *\n * Everything that is a Duration scales together (delays, random delays, wait\n * timeouts, goal and `performed(... within)` windows inside the workflow), so\n * the journey keeps its shape, just faster. What does not scale: wall-clock\n * `timeWindow`s (they are times of day, not durations). Segment definitions\n * and trigger routes live in the store rather than the workflow body;\n * `scaleSegmentWindows` scales them as they are read — one segment for an\n * event-driven evaluation, all of them for the scheduled sweep, a route's\n * profile gate at ingest — so a segment's `within` keeps pace everywhere.\n *\n * The walk follows the IR's own structure — the fields that hold a Duration\n * are known — so a message prop or an event payload that happens to look like\n * one (`{ value, ms }`) is left alone. The source text is kept so the studio\n * still shows what was written.\n */\nexport function scaleDurations(ir: WorkflowIR, factor: number): WorkflowIR {\n if (!(factor > 0) || factor === 1) return ir;\n return {\n ...ir,\n trigger:\n ir.trigger.type === 'event'\n ? {\n ...ir.trigger,\n ...(ir.trigger.filter === undefined\n ? {}\n : { filter: scaleCondition(ir.trigger.filter, factor) }),\n }\n : ir.trigger,\n ...(ir.goal === undefined\n ? {}\n : {\n goal: {\n ...ir.goal,\n condition: scaleCondition(ir.goal.condition, factor),\n ...(ir.goal.within === undefined ? {} : { within: scale(ir.goal.within, factor) }),\n },\n }),\n ...(ir.exitWhen === undefined ? {} : { exitWhen: scaleCondition(ir.exitWhen, factor) }),\n flow: scaleFlow(ir.flow, factor),\n };\n}\n\n/** A condition with its `performed(... within)` windows divided by `factor`. */\nexport function scaleCondition(condition: ConditionIR, factor: number): ConditionIR {\n if (!(factor > 0) || factor === 1) return condition;\n switch (condition.type) {\n case 'and':\n case 'or':\n return {\n ...condition,\n conditions: condition.conditions.map((child) => scaleCondition(child, factor)),\n };\n case 'not':\n return { ...condition, condition: scaleCondition(condition.condition, factor) };\n case 'performed':\n return condition.within === undefined\n ? condition\n : { ...condition, within: scale(condition.within, factor) };\n default:\n return condition;\n }\n}\n\nfunction scaleFlow(nodes: readonly NodeIR[], factor: number): NodeIR[] {\n return nodes.map((node) => scaleNode(node, factor));\n}\n\nfunction scaleNode(node: NodeIR, factor: number): NodeIR {\n switch (node.type) {\n case 'delay':\n return { ...node, duration: scale(node.duration, factor) };\n case 'random_delay':\n return { ...node, min: scale(node.min, factor), max: scale(node.max, factor) };\n case 'wait_until':\n return {\n ...node,\n condition: scaleCondition(node.condition, factor),\n timeout: scale(node.timeout, factor),\n onTimeout: Array.isArray(node.onTimeout)\n ? scaleFlow(node.onTimeout, factor)\n : node.onTimeout,\n };\n case 'branch':\n return {\n ...node,\n cases: node.cases.map((branch) => ({\n ...branch,\n condition: scaleCondition(branch.condition, factor),\n flow: scaleFlow(branch.flow, factor),\n })),\n ...(node.otherwise === undefined ? {} : { otherwise: scaleFlow(node.otherwise, factor) }),\n };\n case 'filter':\n return { ...node, condition: scaleCondition(node.condition, factor) };\n case 'cohort':\n return {\n ...node,\n arms: node.arms.map((arm) => ({ ...arm, flow: scaleFlow(arm.flow, factor) })),\n };\n default:\n return node;\n }\n}\n\nfunction scale(duration: DurationIR, factor: number): DurationIR {\n return { value: duration.value, ms: Math.max(0, Math.round(duration.ms / factor)) };\n}\n\n/**\n * A store whose segment definitions come back scaled by `factor` — what the journey host leases\n * in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`) shrink\n * with the journeys that reference it, and with the segment triggers it routes. Every read that\n * hands out a stored condition is scaled: `getSegmentCondition` (event-driven evaluation),\n * `listSegments` (the scheduled sweep, which narrows candidates in SQL and evaluates with those\n * definitions) and `findTriggers` (the profile gate an event trigger checks before it starts a\n * journey, which `scaleDurations` scales inside the journey too). Every other method is the\n * store's own.\n */\nexport function scaleSegmentWindows<\n S extends { getSegmentCondition(name: string): Promise<ConditionIR | undefined> },\n>(store: S, factor: number): S {\n if (!(factor > 0) || factor === 1) return store;\n return new Proxy(store, {\n get(target, property) {\n const value: unknown = Reflect.get(target, property, target);\n if (typeof value !== 'function') return value;\n switch (property) {\n case 'getSegmentCondition':\n return async (name: string) => {\n const condition = await target.getSegmentCondition(name);\n return condition === undefined ? undefined : scaleCondition(condition, factor);\n };\n case 'listSegments':\n return async () => {\n const segments = (await value.call(target)) as { id: string; condition: ConditionIR }[];\n return segments.map(({ id, condition }) => ({\n id,\n condition: scaleCondition(condition, factor),\n }));\n };\n case 'findTriggers':\n return async (event: string) => {\n // Only the profile gate: `where` is payload-only, so it holds no window to scale\n const scaled = (route: { filter: ConditionIR | null }) => ({\n ...route,\n filter: route.filter === null ? null : scaleCondition(route.filter, factor),\n });\n return ((await value.call(target, event)) as { filter: ConditionIR | null }[]).map(\n scaled\n );\n };\n default:\n return value.bind(target);\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -9,8 +9,10 @@ import { ConditionIR, WorkflowIR } from "../ir.cjs";
9
9
  * timeouts, goal and `performed(... within)` windows inside the workflow), so
10
10
  * the journey keeps its shape, just faster. What does not scale: wall-clock
11
11
  * `timeWindow`s (they are times of day, not durations). Segment definitions
12
- * live in the store rather than the workflow body; `scaleSegmentWindows`
13
- * scales them as they are read, so a segment's `within` keeps pace too.
12
+ * and trigger routes live in the store rather than the workflow body;
13
+ * `scaleSegmentWindows` scales them as they are read — one segment for an
14
+ * event-driven evaluation, all of them for the scheduled sweep, a route's
15
+ * profile gate at ingest — so a segment's `within` keeps pace everywhere.
14
16
  *
15
17
  * The walk follows the IR's own structure — the fields that hold a Duration
16
18
  * are known — so a message prop or an event payload that happens to look like
@@ -21,10 +23,14 @@ export declare function scaleDurations(ir: WorkflowIR, factor: number): Workflow
21
23
  /** A condition with its `performed(... within)` windows divided by `factor`. */
22
24
  export declare function scaleCondition(condition: ConditionIR, factor: number): ConditionIR;
23
25
  /**
24
- * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
25
- * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
26
- * shrink with the journeys that reference it, and with the segment triggers it routes. Every
27
- * other method is the store's own.
26
+ * A store whose segment definitions come back scaled by `factor` — what the journey host leases
27
+ * in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`) shrink
28
+ * with the journeys that reference it, and with the segment triggers it routes. Every read that
29
+ * hands out a stored condition is scaled: `getSegmentCondition` (event-driven evaluation),
30
+ * `listSegments` (the scheduled sweep, which narrows candidates in SQL and evaluates with those
31
+ * definitions) and `findTriggers` (the profile gate an event trigger checks before it starts a
32
+ * journey, which `scaleDurations` scales inside the journey too). Every other method is the
33
+ * store's own.
28
34
  */
29
35
  export declare function scaleSegmentWindows<S extends {
30
36
  getSegmentCondition(name: string): Promise<ConditionIR | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"time-scale.d.cts","names":[],"sources":["../../src/engine/time-scale.ts"],"mappings":";;;;;;;;;;;;;;;;;;;wBAoBgB,eAAe,IAAI,YAAY,iBAAiB;;wBA4BhD,eAAe,WAAW,aAAa,iBAAiB;;;;;;;wBAuExD,oBACd;EAAY,oBAAoB,eAAe,QAAQ;GACvD,OAAO,GAAG,iBAAiB"}
1
+ {"version":3,"file":"time-scale.d.cts","names":[],"sources":["../../src/engine/time-scale.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;wBAsBgB,eAAe,IAAI,YAAY,iBAAiB;;wBA4BhD,eAAe,WAAW,aAAa,iBAAiB;;;;;;;;;;;wBA2ExD,oBACd;EAAY,oBAAoB,eAAe,QAAQ;GACvD,OAAO,GAAG,iBAAiB"}
@@ -9,8 +9,10 @@ import { ConditionIR, WorkflowIR } from "../ir.mjs";
9
9
  * timeouts, goal and `performed(... within)` windows inside the workflow), so
10
10
  * the journey keeps its shape, just faster. What does not scale: wall-clock
11
11
  * `timeWindow`s (they are times of day, not durations). Segment definitions
12
- * live in the store rather than the workflow body; `scaleSegmentWindows`
13
- * scales them as they are read, so a segment's `within` keeps pace too.
12
+ * and trigger routes live in the store rather than the workflow body;
13
+ * `scaleSegmentWindows` scales them as they are read — one segment for an
14
+ * event-driven evaluation, all of them for the scheduled sweep, a route's
15
+ * profile gate at ingest — so a segment's `within` keeps pace everywhere.
14
16
  *
15
17
  * The walk follows the IR's own structure — the fields that hold a Duration
16
18
  * are known — so a message prop or an event payload that happens to look like
@@ -21,10 +23,14 @@ export declare function scaleDurations(ir: WorkflowIR, factor: number): Workflow
21
23
  /** A condition with its `performed(... within)` windows divided by `factor`. */
22
24
  export declare function scaleCondition(condition: ConditionIR, factor: number): ConditionIR;
23
25
  /**
24
- * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
25
- * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
26
- * shrink with the journeys that reference it, and with the segment triggers it routes. Every
27
- * other method is the store's own.
26
+ * A store whose segment definitions come back scaled by `factor` — what the journey host leases
27
+ * in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`) shrink
28
+ * with the journeys that reference it, and with the segment triggers it routes. Every read that
29
+ * hands out a stored condition is scaled: `getSegmentCondition` (event-driven evaluation),
30
+ * `listSegments` (the scheduled sweep, which narrows candidates in SQL and evaluates with those
31
+ * definitions) and `findTriggers` (the profile gate an event trigger checks before it starts a
32
+ * journey, which `scaleDurations` scales inside the journey too). Every other method is the
33
+ * store's own.
28
34
  */
29
35
  export declare function scaleSegmentWindows<S extends {
30
36
  getSegmentCondition(name: string): Promise<ConditionIR | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"time-scale.d.mts","names":[],"sources":["../../src/engine/time-scale.ts"],"mappings":";;;;;;;;;;;;;;;;;;;wBAoBgB,eAAe,IAAI,YAAY,iBAAiB;;wBA4BhD,eAAe,WAAW,aAAa,iBAAiB;;;;;;;wBAuExD,oBACd;EAAY,oBAAoB,eAAe,QAAQ;GACvD,OAAO,GAAG,iBAAiB"}
1
+ {"version":3,"file":"time-scale.d.mts","names":[],"sources":["../../src/engine/time-scale.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;wBAsBgB,eAAe,IAAI,YAAY,iBAAiB;;wBA4BhD,eAAe,WAAW,aAAa,iBAAiB;;;;;;;;;;;wBA2ExD,oBACd;EAAY,oBAAoB,eAAe,QAAQ;GACvD,OAAO,GAAG,iBAAiB"}
@@ -9,8 +9,10 @@
9
9
  * timeouts, goal and `performed(... within)` windows inside the workflow), so
10
10
  * the journey keeps its shape, just faster. What does not scale: wall-clock
11
11
  * `timeWindow`s (they are times of day, not durations). Segment definitions
12
- * live in the store rather than the workflow body; `scaleSegmentWindows`
13
- * scales them as they are read, so a segment's `within` keeps pace too.
12
+ * and trigger routes live in the store rather than the workflow body;
13
+ * `scaleSegmentWindows` scales them as they are read — one segment for an
14
+ * event-driven evaluation, all of them for the scheduled sweep, a route's
15
+ * profile gate at ingest — so a segment's `within` keeps pace everywhere.
14
16
  *
15
17
  * The walk follows the IR's own structure — the fields that hold a Duration
16
18
  * are known — so a message prop or an event payload that happens to look like
@@ -104,20 +106,40 @@ function scale(duration, factor) {
104
106
  };
105
107
  }
106
108
  /**
107
- * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads
108
- * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)
109
- * shrink with the journeys that reference it, and with the segment triggers it routes. Every
110
- * other method is the store's own.
109
+ * A store whose segment definitions come back scaled by `factor` — what the journey host leases
110
+ * in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`) shrink
111
+ * with the journeys that reference it, and with the segment triggers it routes. Every read that
112
+ * hands out a stored condition is scaled: `getSegmentCondition` (event-driven evaluation),
113
+ * `listSegments` (the scheduled sweep, which narrows candidates in SQL and evaluates with those
114
+ * definitions) and `findTriggers` (the profile gate an event trigger checks before it starts a
115
+ * journey, which `scaleDurations` scales inside the journey too). Every other method is the
116
+ * store's own.
111
117
  */
112
118
  function scaleSegmentWindows(store, factor) {
113
119
  if (!(factor > 0) || factor === 1) return store;
114
120
  return new Proxy(store, { get(target, property) {
115
- if (property === "getSegmentCondition") return async (name) => {
116
- const condition = await target.getSegmentCondition(name);
117
- return condition === void 0 ? void 0 : scaleCondition(condition, factor);
118
- };
119
121
  const value = Reflect.get(target, property, target);
120
- return typeof value === "function" ? value.bind(target) : value;
122
+ if (typeof value !== "function") return value;
123
+ switch (property) {
124
+ case "getSegmentCondition": return async (name) => {
125
+ const condition = await target.getSegmentCondition(name);
126
+ return condition === void 0 ? void 0 : scaleCondition(condition, factor);
127
+ };
128
+ case "listSegments": return async () => {
129
+ return (await value.call(target)).map(({ id, condition }) => ({
130
+ id,
131
+ condition: scaleCondition(condition, factor)
132
+ }));
133
+ };
134
+ case "findTriggers": return async (event) => {
135
+ const scaled = (route) => ({
136
+ ...route,
137
+ filter: route.filter === null ? null : scaleCondition(route.filter, factor)
138
+ });
139
+ return (await value.call(target, event)).map(scaled);
140
+ };
141
+ default: return value.bind(target);
142
+ }
121
143
  } });
122
144
  }
123
145
  export { scaleCondition, scaleDurations, scaleSegmentWindows };
@@ -1 +1 @@
1
- {"version":3,"file":"time-scale.mjs","names":[],"sources":["../../src/engine/time-scale.ts"],"sourcesContent":["import type { ConditionIR, DurationIR, NodeIR, WorkflowIR } from '../ir';\n\n/**\n * Debug time scale: divide every duration in a workflow's IR by a factor so a\n * journey written in days plays out in seconds — with a factor of 3600, a\n * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`\n * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.\n *\n * Everything that is a Duration scales together (delays, random delays, wait\n * timeouts, goal and `performed(... within)` windows inside the workflow), so\n * the journey keeps its shape, just faster. What does not scale: wall-clock\n * `timeWindow`s (they are times of day, not durations). Segment definitions\n * live in the store rather than the workflow body; `scaleSegmentWindows`\n * scales them as they are read, so a segment's `within` keeps pace too.\n *\n * The walk follows the IR's own structure — the fields that hold a Duration\n * are known — so a message prop or an event payload that happens to look like\n * one (`{ value, ms }`) is left alone. The source text is kept so the studio\n * still shows what was written.\n */\nexport function scaleDurations(ir: WorkflowIR, factor: number): WorkflowIR {\n if (!(factor > 0) || factor === 1) return ir;\n return {\n ...ir,\n trigger:\n ir.trigger.type === 'event'\n ? {\n ...ir.trigger,\n ...(ir.trigger.filter === undefined\n ? {}\n : { filter: scaleCondition(ir.trigger.filter, factor) }),\n }\n : ir.trigger,\n ...(ir.goal === undefined\n ? {}\n : {\n goal: {\n ...ir.goal,\n condition: scaleCondition(ir.goal.condition, factor),\n ...(ir.goal.within === undefined ? {} : { within: scale(ir.goal.within, factor) }),\n },\n }),\n ...(ir.exitWhen === undefined ? {} : { exitWhen: scaleCondition(ir.exitWhen, factor) }),\n flow: scaleFlow(ir.flow, factor),\n };\n}\n\n/** A condition with its `performed(... within)` windows divided by `factor`. */\nexport function scaleCondition(condition: ConditionIR, factor: number): ConditionIR {\n if (!(factor > 0) || factor === 1) return condition;\n switch (condition.type) {\n case 'and':\n case 'or':\n return {\n ...condition,\n conditions: condition.conditions.map((child) => scaleCondition(child, factor)),\n };\n case 'not':\n return { ...condition, condition: scaleCondition(condition.condition, factor) };\n case 'performed':\n return condition.within === undefined\n ? condition\n : { ...condition, within: scale(condition.within, factor) };\n default:\n return condition;\n }\n}\n\nfunction scaleFlow(nodes: readonly NodeIR[], factor: number): NodeIR[] {\n return nodes.map((node) => scaleNode(node, factor));\n}\n\nfunction scaleNode(node: NodeIR, factor: number): NodeIR {\n switch (node.type) {\n case 'delay':\n return { ...node, duration: scale(node.duration, factor) };\n case 'random_delay':\n return { ...node, min: scale(node.min, factor), max: scale(node.max, factor) };\n case 'wait_until':\n return {\n ...node,\n condition: scaleCondition(node.condition, factor),\n timeout: scale(node.timeout, factor),\n onTimeout: Array.isArray(node.onTimeout)\n ? scaleFlow(node.onTimeout, factor)\n : node.onTimeout,\n };\n case 'branch':\n return {\n ...node,\n cases: node.cases.map((branch) => ({\n ...branch,\n condition: scaleCondition(branch.condition, factor),\n flow: scaleFlow(branch.flow, factor),\n })),\n ...(node.otherwise === undefined ? {} : { otherwise: scaleFlow(node.otherwise, factor) }),\n };\n case 'filter':\n return { ...node, condition: scaleCondition(node.condition, factor) };\n case 'cohort':\n return {\n ...node,\n arms: node.arms.map((arm) => ({ ...arm, flow: scaleFlow(arm.flow, factor) })),\n };\n default:\n return node;\n }\n}\n\nfunction scale(duration: DurationIR, factor: number): DurationIR {\n return { value: duration.value, ms: Math.max(0, Math.round(duration.ms / factor)) };\n}\n\n/**\n * A store whose segment definitions come back scaled by `factor` — what journeyWorker reads\n * through in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`)\n * shrink with the journeys that reference it, and with the segment triggers it routes. Every\n * other method is the store's own.\n */\nexport function scaleSegmentWindows<\n S extends { getSegmentCondition(name: string): Promise<ConditionIR | undefined> },\n>(store: S, factor: number): S {\n if (!(factor > 0) || factor === 1) return store;\n return new Proxy(store, {\n get(target, property) {\n if (property === 'getSegmentCondition') {\n return async (name: string) => {\n const condition = await target.getSegmentCondition(name);\n return condition === undefined ? undefined : scaleCondition(condition, factor);\n };\n }\n const value: unknown = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"time-scale.mjs","names":[],"sources":["../../src/engine/time-scale.ts"],"sourcesContent":["import type { ConditionIR, DurationIR, NodeIR, WorkflowIR } from '../ir';\n\n/**\n * Debug time scale: divide every duration in a workflow's IR by a factor so a\n * journey written in days plays out in seconds — with a factor of 3600, a\n * `delay('2 days')` sleeps 48 s, a `waitUntil(..., { timeout: '3 days' })`\n * gives up after 72 s, and a goal `within: '30 days'` closes after 12 min.\n *\n * Everything that is a Duration scales together (delays, random delays, wait\n * timeouts, goal and `performed(... within)` windows inside the workflow), so\n * the journey keeps its shape, just faster. What does not scale: wall-clock\n * `timeWindow`s (they are times of day, not durations). Segment definitions\n * and trigger routes live in the store rather than the workflow body;\n * `scaleSegmentWindows` scales them as they are read — one segment for an\n * event-driven evaluation, all of them for the scheduled sweep, a route's\n * profile gate at ingest — so a segment's `within` keeps pace everywhere.\n *\n * The walk follows the IR's own structure — the fields that hold a Duration\n * are known — so a message prop or an event payload that happens to look like\n * one (`{ value, ms }`) is left alone. The source text is kept so the studio\n * still shows what was written.\n */\nexport function scaleDurations(ir: WorkflowIR, factor: number): WorkflowIR {\n if (!(factor > 0) || factor === 1) return ir;\n return {\n ...ir,\n trigger:\n ir.trigger.type === 'event'\n ? {\n ...ir.trigger,\n ...(ir.trigger.filter === undefined\n ? {}\n : { filter: scaleCondition(ir.trigger.filter, factor) }),\n }\n : ir.trigger,\n ...(ir.goal === undefined\n ? {}\n : {\n goal: {\n ...ir.goal,\n condition: scaleCondition(ir.goal.condition, factor),\n ...(ir.goal.within === undefined ? {} : { within: scale(ir.goal.within, factor) }),\n },\n }),\n ...(ir.exitWhen === undefined ? {} : { exitWhen: scaleCondition(ir.exitWhen, factor) }),\n flow: scaleFlow(ir.flow, factor),\n };\n}\n\n/** A condition with its `performed(... within)` windows divided by `factor`. */\nexport function scaleCondition(condition: ConditionIR, factor: number): ConditionIR {\n if (!(factor > 0) || factor === 1) return condition;\n switch (condition.type) {\n case 'and':\n case 'or':\n return {\n ...condition,\n conditions: condition.conditions.map((child) => scaleCondition(child, factor)),\n };\n case 'not':\n return { ...condition, condition: scaleCondition(condition.condition, factor) };\n case 'performed':\n return condition.within === undefined\n ? condition\n : { ...condition, within: scale(condition.within, factor) };\n default:\n return condition;\n }\n}\n\nfunction scaleFlow(nodes: readonly NodeIR[], factor: number): NodeIR[] {\n return nodes.map((node) => scaleNode(node, factor));\n}\n\nfunction scaleNode(node: NodeIR, factor: number): NodeIR {\n switch (node.type) {\n case 'delay':\n return { ...node, duration: scale(node.duration, factor) };\n case 'random_delay':\n return { ...node, min: scale(node.min, factor), max: scale(node.max, factor) };\n case 'wait_until':\n return {\n ...node,\n condition: scaleCondition(node.condition, factor),\n timeout: scale(node.timeout, factor),\n onTimeout: Array.isArray(node.onTimeout)\n ? scaleFlow(node.onTimeout, factor)\n : node.onTimeout,\n };\n case 'branch':\n return {\n ...node,\n cases: node.cases.map((branch) => ({\n ...branch,\n condition: scaleCondition(branch.condition, factor),\n flow: scaleFlow(branch.flow, factor),\n })),\n ...(node.otherwise === undefined ? {} : { otherwise: scaleFlow(node.otherwise, factor) }),\n };\n case 'filter':\n return { ...node, condition: scaleCondition(node.condition, factor) };\n case 'cohort':\n return {\n ...node,\n arms: node.arms.map((arm) => ({ ...arm, flow: scaleFlow(arm.flow, factor) })),\n };\n default:\n return node;\n }\n}\n\nfunction scale(duration: DurationIR, factor: number): DurationIR {\n return { value: duration.value, ms: Math.max(0, Math.round(duration.ms / factor)) };\n}\n\n/**\n * A store whose segment definitions come back scaled by `factor` — what the journey host leases\n * in fast-forward, so a segment's windows (`performed(e.login, { within: '7 days' })`) shrink\n * with the journeys that reference it, and with the segment triggers it routes. Every read that\n * hands out a stored condition is scaled: `getSegmentCondition` (event-driven evaluation),\n * `listSegments` (the scheduled sweep, which narrows candidates in SQL and evaluates with those\n * definitions) and `findTriggers` (the profile gate an event trigger checks before it starts a\n * journey, which `scaleDurations` scales inside the journey too). Every other method is the\n * store's own.\n */\nexport function scaleSegmentWindows<\n S extends { getSegmentCondition(name: string): Promise<ConditionIR | undefined> },\n>(store: S, factor: number): S {\n if (!(factor > 0) || factor === 1) return store;\n return new Proxy(store, {\n get(target, property) {\n const value: unknown = Reflect.get(target, property, target);\n if (typeof value !== 'function') return value;\n switch (property) {\n case 'getSegmentCondition':\n return async (name: string) => {\n const condition = await target.getSegmentCondition(name);\n return condition === undefined ? undefined : scaleCondition(condition, factor);\n };\n case 'listSegments':\n return async () => {\n const segments = (await value.call(target)) as { id: string; condition: ConditionIR }[];\n return segments.map(({ id, condition }) => ({\n id,\n condition: scaleCondition(condition, factor),\n }));\n };\n case 'findTriggers':\n return async (event: string) => {\n // Only the profile gate: `where` is payload-only, so it holds no window to scale\n const scaled = (route: { filter: ConditionIR | null }) => ({\n ...route,\n filter: route.filter === null ? null : scaleCondition(route.filter, factor),\n });\n return ((await value.call(target, event)) as { filter: ConditionIR | null }[]).map(\n scaled\n );\n };\n default:\n return value.bind(target);\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -9,10 +9,19 @@ postgres = require_runtime.__toESM(postgres, 1);
9
9
  * (`postgres` is an optional peer dependency; import this subpath only when
10
10
  * you use it). On Cloudflare the connection string is Hyperdrive's.
11
11
  */
12
+ /**
13
+ * A JS array as a Postgres array literal (`{"a","b"}`), one level deep. `sql.unsafe` sends an
14
+ * array parameter as `String(array)` (`a,b`, and `''` for an empty one), which `$1::text[]`
15
+ * rejects with "malformed array literal"; the literal form parses whatever the column type.
16
+ */
17
+ function pgArrayLiteral(items) {
18
+ const element = (item) => item === null || item === void 0 ? "NULL" : `"${String(item).replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
19
+ return `{${items.map(element).join(",")}}`;
20
+ }
12
21
  /** postgres.js → the store's minimal client. Inside `begin` every statement runs on the transaction client; the store never nests, so an inner `transaction` continues on the same one. */
13
22
  function postgresJsClient(sql, inTransaction = false) {
14
23
  return {
15
- query: (text, params = []) => sql.unsafe(text, params),
24
+ query: (text, params = []) => sql.unsafe(text, params.map((param) => Array.isArray(param) ? pgArrayLiteral(param) : param)),
16
25
  transaction: (fn) => inTransaction ? fn(postgresJsClient(sql, true)) : sql.begin((tx) => fn(postgresJsClient(tx, true)))
17
26
  };
18
27
  }
@@ -35,6 +44,7 @@ function postgresJsStore(connectionString, options = {}) {
35
44
  close: () => sql.end({ timeout: 5 })
36
45
  };
37
46
  }
47
+ exports.pgArrayLiteral = pgArrayLiteral;
38
48
  exports.postgresJsClient = postgresJsClient;
39
49
  exports.postgresJsStore = postgresJsStore;
40
50
 
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-js.cjs","names":["PostgresJourneyStore"],"sources":["../src/postgres-js.ts"],"sourcesContent":["import postgres from 'postgres';\nimport type { StoreLease } from './store/index';\nimport {\n PostgresJourneyStore,\n type PostgresJourneyStoreOptions,\n type SqlClientLike,\n} from './store/postgres';\n\n/**\n * `@shware/workflow/postgres-js` — the Postgres store over postgres.js\n * (`postgres` is an optional peer dependency; import this subpath only when\n * you use it). On Cloudflare the connection string is Hyperdrive's.\n */\n\n/** postgres.js → the store's minimal client. Inside `begin` every statement runs on the transaction client; the store never nests, so an inner `transaction` continues on the same one. */\nexport function postgresJsClient(\n sql: postgres.Sql | postgres.TransactionSql,\n inTransaction = false\n): SqlClientLike {\n return {\n query: <T>(text: string, params: readonly unknown[] = []) =>\n sql.unsafe(text, params as never) as unknown as Promise<T[]>,\n transaction: <T>(fn: (tx: SqlClientLike) => Promise<T>) =>\n inTransaction\n ? fn(postgresJsClient(sql, true))\n : ((sql as postgres.Sql).begin((tx) => fn(postgresJsClient(tx, true))) as Promise<T>),\n };\n}\n\nexport interface PostgresJsStoreOptions extends PostgresJourneyStoreOptions {\n /**\n * postgres.js options. Defaults suit a Worker with Hyperdrive: one\n * connection per request/run, closed after 2 s idle, no type fetch on\n * connect, prepared statements (Hyperdrive caches them).\n */\n connection?: postgres.Options<Record<string, never>>;\n}\n\n/**\n * Open a Postgres-backed store for one request or workflow run. The lease's\n * `close` ends the client — a Worker cannot share a socket across requests,\n * and Hyperdrive keeps the real pool next to the database.\n */\nexport function postgresJsStore(\n connectionString: string,\n options: PostgresJsStoreOptions = {}\n): Required<StoreLease> {\n const { connection, ...storeOptions } = options;\n const sql = postgres(connectionString, {\n max: 1,\n // A journey run lives for days: between steps it sleeps or waits, and a run holding its\n // connection through that holds one per sleeping instance — hundreds of them exhaust the\n // database. Idle connections close and the next query opens a fresh one.\n idle_timeout: 2,\n fetch_types: false,\n prepare: true,\n ...connection,\n });\n return {\n store: new PostgresJourneyStore(postgresJsClient(sql), storeOptions),\n close: () => sql.end({ timeout: 5 }),\n };\n}\n"],"mappings":";;;;;;;;;;;;AAeA,SAAgB,iBACd,KACA,gBAAgB,OACD;CACf,OAAO;EACL,QAAW,MAAc,SAA6B,CAAC,MACrD,IAAI,OAAO,MAAM,MAAe;EAClC,cAAiB,OACf,gBACI,GAAG,iBAAiB,KAAK,IAAI,CAAC,IAC5B,IAAqB,OAAO,OAAO,GAAG,iBAAiB,IAAI,IAAI,CAAC,CAAC;CAC3E;AACF;;;;;;AAgBA,SAAgB,gBACd,kBACA,UAAkC,CAAC,GACb;CACtB,MAAM,EAAE,YAAY,GAAG,iBAAiB;CACxC,MAAM,OAAA,GAAM,SAAA,QAAA,CAAS,kBAAkB;EACrC,KAAK;EAIL,cAAc;EACd,aAAa;EACb,SAAS;EACT,GAAG;CACL,CAAC;CACD,OAAO;EACL,OAAO,IAAIA,uBAAAA,qBAAqB,iBAAiB,GAAG,GAAG,YAAY;EACnE,aAAa,IAAI,IAAI,EAAE,SAAS,EAAE,CAAC;CACrC;AACF"}
1
+ {"version":3,"file":"postgres-js.cjs","names":["PostgresJourneyStore"],"sources":["../src/postgres-js.ts"],"sourcesContent":["import postgres from 'postgres';\nimport type { StoreLease } from './store/index';\nimport {\n PostgresJourneyStore,\n type PostgresJourneyStoreOptions,\n type SqlClientLike,\n} from './store/postgres';\n\n/**\n * `@shware/workflow/postgres-js` — the Postgres store over postgres.js\n * (`postgres` is an optional peer dependency; import this subpath only when\n * you use it). On Cloudflare the connection string is Hyperdrive's.\n */\n\n/**\n * A JS array as a Postgres array literal (`{\"a\",\"b\"}`), one level deep. `sql.unsafe` sends an\n * array parameter as `String(array)` (`a,b`, and `''` for an empty one), which `$1::text[]`\n * rejects with \"malformed array literal\"; the literal form parses whatever the column type.\n */\nexport function pgArrayLiteral(\n items: readonly (string | number | bigint | boolean | null | undefined)[]\n): string {\n const element = (item: (typeof items)[number]) =>\n item === null || item === undefined\n ? 'NULL'\n : `\"${String(item).replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"')}\"`;\n return `{${items.map(element).join(',')}}`;\n}\n\n/** postgres.js → the store's minimal client. Inside `begin` every statement runs on the transaction client; the store never nests, so an inner `transaction` continues on the same one. */\nexport function postgresJsClient(\n sql: postgres.Sql | postgres.TransactionSql,\n inTransaction = false\n): SqlClientLike {\n return {\n query: <T>(text: string, params: readonly unknown[] = []) =>\n sql.unsafe(\n text,\n params.map((param) =>\n Array.isArray(param)\n ? pgArrayLiteral(param as Parameters<typeof pgArrayLiteral>[0])\n : param\n ) as never\n ) as unknown as Promise<T[]>,\n transaction: <T>(fn: (tx: SqlClientLike) => Promise<T>) =>\n inTransaction\n ? fn(postgresJsClient(sql, true))\n : ((sql as postgres.Sql).begin((tx) => fn(postgresJsClient(tx, true))) as Promise<T>),\n };\n}\n\nexport interface PostgresJsStoreOptions extends PostgresJourneyStoreOptions {\n /**\n * postgres.js options. Defaults suit a Worker with Hyperdrive: one\n * connection per request/run, closed after 2 s idle, no type fetch on\n * connect, prepared statements (Hyperdrive caches them).\n */\n connection?: postgres.Options<Record<string, never>>;\n}\n\n/**\n * Open a Postgres-backed store for one request or workflow run. The lease's\n * `close` ends the client — a Worker cannot share a socket across requests,\n * and Hyperdrive keeps the real pool next to the database.\n */\nexport function postgresJsStore(\n connectionString: string,\n options: PostgresJsStoreOptions = {}\n): Required<StoreLease> {\n const { connection, ...storeOptions } = options;\n const sql = postgres(connectionString, {\n max: 1,\n // A journey run lives for days: between steps it sleeps or waits, and a run holding its\n // connection through that holds one per sleeping instance — hundreds of them exhaust the\n // database. Idle connections close and the next query opens a fresh one.\n idle_timeout: 2,\n fetch_types: false,\n prepare: true,\n ...connection,\n });\n return {\n store: new PostgresJourneyStore(postgresJsClient(sql), storeOptions),\n close: () => sql.end({ timeout: 5 }),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAmBA,SAAgB,eACd,OACQ;CACR,MAAM,WAAW,SACf,SAAS,QAAQ,SAAS,KAAA,IACtB,SACA,IAAI,OAAO,IAAI,CAAC,CAAC,WAAW,MAAM,MAAM,CAAC,CAAC,WAAW,MAAK,MAAK,EAAE;CACvE,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE;AAC1C;;AAGA,SAAgB,iBACd,KACA,gBAAgB,OACD;CACf,OAAO;EACL,QAAW,MAAc,SAA6B,CAAC,MACrD,IAAI,OACF,MACA,OAAO,KAAK,UACV,MAAM,QAAQ,KAAK,IACf,eAAe,KAA6C,IAC5D,KACN,CACF;EACF,cAAiB,OACf,gBACI,GAAG,iBAAiB,KAAK,IAAI,CAAC,IAC5B,IAAqB,OAAO,OAAO,GAAG,iBAAiB,IAAI,IAAI,CAAC,CAAC;CAC3E;AACF;;;;;;AAgBA,SAAgB,gBACd,kBACA,UAAkC,CAAC,GACb;CACtB,MAAM,EAAE,YAAY,GAAG,iBAAiB;CACxC,MAAM,OAAA,GAAM,SAAA,QAAA,CAAS,kBAAkB;EACrC,KAAK;EAIL,cAAc;EACd,aAAa;EACb,SAAS;EACT,GAAG;CACL,CAAC;CACD,OAAO;EACL,OAAO,IAAIA,uBAAAA,qBAAqB,iBAAiB,GAAG,GAAG,YAAY;EACnE,aAAa,IAAI,IAAI,EAAE,SAAS,EAAE,CAAC;CACrC;AACF"}
@@ -7,6 +7,12 @@ import postgres from "postgres";
7
7
  * (`postgres` is an optional peer dependency; import this subpath only when
8
8
  * you use it). On Cloudflare the connection string is Hyperdrive's.
9
9
  */
10
+ /**
11
+ * A JS array as a Postgres array literal (`{"a","b"}`), one level deep. `sql.unsafe` sends an
12
+ * array parameter as `String(array)` (`a,b`, and `''` for an empty one), which `$1::text[]`
13
+ * rejects with "malformed array literal"; the literal form parses whatever the column type.
14
+ */
15
+ export declare function pgArrayLiteral(items: readonly (string | number | bigint | boolean | null | undefined)[]): string;
10
16
  /** postgres.js → the store's minimal client. Inside `begin` every statement runs on the transaction client; the store never nests, so an inner `transaction` continues on the same one. */
11
17
  export declare function postgresJsClient(sql: postgres.Sql | postgres.TransactionSql, inTransaction?: boolean): SqlClientLike;
12
18
  export interface PostgresJsStoreOptions extends PostgresJourneyStoreOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-js.d.cts","names":[],"sources":["../src/postgres-js.ts"],"mappings":";;;;;;;;;;wBAegB,iBACd,KAAK,SAAS,MAAM,SAAS,gBAC7B,0BACC;iBAWc,+BAA+B;;;;;;EAM9C,aAAa,SAAS,QAAQ;;;;;;;wBAQhB,gBACd,0BACA,UAAS,yBACR,SAAS"}
1
+ {"version":3,"file":"postgres-js.d.cts","names":[],"sources":["../src/postgres-js.ts"],"mappings":";;;;;;;;;;;;;;wBAmBgB,eACd;;wBAUc,iBACd,KAAK,SAAS,MAAM,SAAS,gBAC7B,0BACC;iBAkBc,+BAA+B;;;;;;EAM9C,aAAa,SAAS,QAAQ;;;;;;;wBAQhB,gBACd,0BACA,UAAS,yBACR,SAAS"}
@@ -7,6 +7,12 @@ import postgres from "postgres";
7
7
  * (`postgres` is an optional peer dependency; import this subpath only when
8
8
  * you use it). On Cloudflare the connection string is Hyperdrive's.
9
9
  */
10
+ /**
11
+ * A JS array as a Postgres array literal (`{"a","b"}`), one level deep. `sql.unsafe` sends an
12
+ * array parameter as `String(array)` (`a,b`, and `''` for an empty one), which `$1::text[]`
13
+ * rejects with "malformed array literal"; the literal form parses whatever the column type.
14
+ */
15
+ export declare function pgArrayLiteral(items: readonly (string | number | bigint | boolean | null | undefined)[]): string;
10
16
  /** postgres.js → the store's minimal client. Inside `begin` every statement runs on the transaction client; the store never nests, so an inner `transaction` continues on the same one. */
11
17
  export declare function postgresJsClient(sql: postgres.Sql | postgres.TransactionSql, inTransaction?: boolean): SqlClientLike;
12
18
  export interface PostgresJsStoreOptions extends PostgresJourneyStoreOptions {
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-js.d.mts","names":[],"sources":["../src/postgres-js.ts"],"mappings":";;;;;;;;;;wBAegB,iBACd,KAAK,SAAS,MAAM,SAAS,gBAC7B,0BACC;iBAWc,+BAA+B;;;;;;EAM9C,aAAa,SAAS,QAAQ;;;;;;;wBAQhB,gBACd,0BACA,UAAS,yBACR,SAAS"}
1
+ {"version":3,"file":"postgres-js.d.mts","names":[],"sources":["../src/postgres-js.ts"],"mappings":";;;;;;;;;;;;;;wBAmBgB,eACd;;wBAUc,iBACd,KAAK,SAAS,MAAM,SAAS,gBAC7B,0BACC;iBAkBc,+BAA+B;;;;;;EAM9C,aAAa,SAAS,QAAQ;;;;;;;wBAQhB,gBACd,0BACA,UAAS,yBACR,SAAS"}
@@ -6,10 +6,19 @@ import postgres from "postgres";
6
6
  * (`postgres` is an optional peer dependency; import this subpath only when
7
7
  * you use it). On Cloudflare the connection string is Hyperdrive's.
8
8
  */
9
+ /**
10
+ * A JS array as a Postgres array literal (`{"a","b"}`), one level deep. `sql.unsafe` sends an
11
+ * array parameter as `String(array)` (`a,b`, and `''` for an empty one), which `$1::text[]`
12
+ * rejects with "malformed array literal"; the literal form parses whatever the column type.
13
+ */
14
+ function pgArrayLiteral(items) {
15
+ const element = (item) => item === null || item === void 0 ? "NULL" : `"${String(item).replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
16
+ return `{${items.map(element).join(",")}}`;
17
+ }
9
18
  /** postgres.js → the store's minimal client. Inside `begin` every statement runs on the transaction client; the store never nests, so an inner `transaction` continues on the same one. */
10
19
  function postgresJsClient(sql, inTransaction = false) {
11
20
  return {
12
- query: (text, params = []) => sql.unsafe(text, params),
21
+ query: (text, params = []) => sql.unsafe(text, params.map((param) => Array.isArray(param) ? pgArrayLiteral(param) : param)),
13
22
  transaction: (fn) => inTransaction ? fn(postgresJsClient(sql, true)) : sql.begin((tx) => fn(postgresJsClient(tx, true)))
14
23
  };
15
24
  }
@@ -32,6 +41,6 @@ function postgresJsStore(connectionString, options = {}) {
32
41
  close: () => sql.end({ timeout: 5 })
33
42
  };
34
43
  }
35
- export { postgresJsClient, postgresJsStore };
44
+ export { pgArrayLiteral, postgresJsClient, postgresJsStore };
36
45
 
37
46
  //# sourceMappingURL=postgres-js.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-js.mjs","names":[],"sources":["../src/postgres-js.ts"],"sourcesContent":["import postgres from 'postgres';\nimport type { StoreLease } from './store/index';\nimport {\n PostgresJourneyStore,\n type PostgresJourneyStoreOptions,\n type SqlClientLike,\n} from './store/postgres';\n\n/**\n * `@shware/workflow/postgres-js` — the Postgres store over postgres.js\n * (`postgres` is an optional peer dependency; import this subpath only when\n * you use it). On Cloudflare the connection string is Hyperdrive's.\n */\n\n/** postgres.js → the store's minimal client. Inside `begin` every statement runs on the transaction client; the store never nests, so an inner `transaction` continues on the same one. */\nexport function postgresJsClient(\n sql: postgres.Sql | postgres.TransactionSql,\n inTransaction = false\n): SqlClientLike {\n return {\n query: <T>(text: string, params: readonly unknown[] = []) =>\n sql.unsafe(text, params as never) as unknown as Promise<T[]>,\n transaction: <T>(fn: (tx: SqlClientLike) => Promise<T>) =>\n inTransaction\n ? fn(postgresJsClient(sql, true))\n : ((sql as postgres.Sql).begin((tx) => fn(postgresJsClient(tx, true))) as Promise<T>),\n };\n}\n\nexport interface PostgresJsStoreOptions extends PostgresJourneyStoreOptions {\n /**\n * postgres.js options. Defaults suit a Worker with Hyperdrive: one\n * connection per request/run, closed after 2 s idle, no type fetch on\n * connect, prepared statements (Hyperdrive caches them).\n */\n connection?: postgres.Options<Record<string, never>>;\n}\n\n/**\n * Open a Postgres-backed store for one request or workflow run. The lease's\n * `close` ends the client — a Worker cannot share a socket across requests,\n * and Hyperdrive keeps the real pool next to the database.\n */\nexport function postgresJsStore(\n connectionString: string,\n options: PostgresJsStoreOptions = {}\n): Required<StoreLease> {\n const { connection, ...storeOptions } = options;\n const sql = postgres(connectionString, {\n max: 1,\n // A journey run lives for days: between steps it sleeps or waits, and a run holding its\n // connection through that holds one per sleeping instance — hundreds of them exhaust the\n // database. Idle connections close and the next query opens a fresh one.\n idle_timeout: 2,\n fetch_types: false,\n prepare: true,\n ...connection,\n });\n return {\n store: new PostgresJourneyStore(postgresJsClient(sql), storeOptions),\n close: () => sql.end({ timeout: 5 }),\n };\n}\n"],"mappings":";;;;;;;;;AAeA,SAAgB,iBACd,KACA,gBAAgB,OACD;CACf,OAAO;EACL,QAAW,MAAc,SAA6B,CAAC,MACrD,IAAI,OAAO,MAAM,MAAe;EAClC,cAAiB,OACf,gBACI,GAAG,iBAAiB,KAAK,IAAI,CAAC,IAC5B,IAAqB,OAAO,OAAO,GAAG,iBAAiB,IAAI,IAAI,CAAC,CAAC;CAC3E;AACF;;;;;;AAgBA,SAAgB,gBACd,kBACA,UAAkC,CAAC,GACb;CACtB,MAAM,EAAE,YAAY,GAAG,iBAAiB;CACxC,MAAM,MAAM,SAAS,kBAAkB;EACrC,KAAK;EAIL,cAAc;EACd,aAAa;EACb,SAAS;EACT,GAAG;CACL,CAAC;CACD,OAAO;EACL,OAAO,IAAI,qBAAqB,iBAAiB,GAAG,GAAG,YAAY;EACnE,aAAa,IAAI,IAAI,EAAE,SAAS,EAAE,CAAC;CACrC;AACF"}
1
+ {"version":3,"file":"postgres-js.mjs","names":[],"sources":["../src/postgres-js.ts"],"sourcesContent":["import postgres from 'postgres';\nimport type { StoreLease } from './store/index';\nimport {\n PostgresJourneyStore,\n type PostgresJourneyStoreOptions,\n type SqlClientLike,\n} from './store/postgres';\n\n/**\n * `@shware/workflow/postgres-js` — the Postgres store over postgres.js\n * (`postgres` is an optional peer dependency; import this subpath only when\n * you use it). On Cloudflare the connection string is Hyperdrive's.\n */\n\n/**\n * A JS array as a Postgres array literal (`{\"a\",\"b\"}`), one level deep. `sql.unsafe` sends an\n * array parameter as `String(array)` (`a,b`, and `''` for an empty one), which `$1::text[]`\n * rejects with \"malformed array literal\"; the literal form parses whatever the column type.\n */\nexport function pgArrayLiteral(\n items: readonly (string | number | bigint | boolean | null | undefined)[]\n): string {\n const element = (item: (typeof items)[number]) =>\n item === null || item === undefined\n ? 'NULL'\n : `\"${String(item).replaceAll('\\\\', '\\\\\\\\').replaceAll('\"', '\\\\\"')}\"`;\n return `{${items.map(element).join(',')}}`;\n}\n\n/** postgres.js → the store's minimal client. Inside `begin` every statement runs on the transaction client; the store never nests, so an inner `transaction` continues on the same one. */\nexport function postgresJsClient(\n sql: postgres.Sql | postgres.TransactionSql,\n inTransaction = false\n): SqlClientLike {\n return {\n query: <T>(text: string, params: readonly unknown[] = []) =>\n sql.unsafe(\n text,\n params.map((param) =>\n Array.isArray(param)\n ? pgArrayLiteral(param as Parameters<typeof pgArrayLiteral>[0])\n : param\n ) as never\n ) as unknown as Promise<T[]>,\n transaction: <T>(fn: (tx: SqlClientLike) => Promise<T>) =>\n inTransaction\n ? fn(postgresJsClient(sql, true))\n : ((sql as postgres.Sql).begin((tx) => fn(postgresJsClient(tx, true))) as Promise<T>),\n };\n}\n\nexport interface PostgresJsStoreOptions extends PostgresJourneyStoreOptions {\n /**\n * postgres.js options. Defaults suit a Worker with Hyperdrive: one\n * connection per request/run, closed after 2 s idle, no type fetch on\n * connect, prepared statements (Hyperdrive caches them).\n */\n connection?: postgres.Options<Record<string, never>>;\n}\n\n/**\n * Open a Postgres-backed store for one request or workflow run. The lease's\n * `close` ends the client — a Worker cannot share a socket across requests,\n * and Hyperdrive keeps the real pool next to the database.\n */\nexport function postgresJsStore(\n connectionString: string,\n options: PostgresJsStoreOptions = {}\n): Required<StoreLease> {\n const { connection, ...storeOptions } = options;\n const sql = postgres(connectionString, {\n max: 1,\n // A journey run lives for days: between steps it sleeps or waits, and a run holding its\n // connection through that holds one per sleeping instance — hundreds of them exhaust the\n // database. Idle connections close and the next query opens a fresh one.\n idle_timeout: 2,\n fetch_types: false,\n prepare: true,\n ...connection,\n });\n return {\n store: new PostgresJourneyStore(postgresJsClient(sql), storeOptions),\n close: () => sql.end({ timeout: 5 }),\n };\n}\n"],"mappings":";;;;;;;;;;;;;AAmBA,SAAgB,eACd,OACQ;CACR,MAAM,WAAW,SACf,SAAS,QAAQ,SAAS,KAAA,IACtB,SACA,IAAI,OAAO,IAAI,CAAC,CAAC,WAAW,MAAM,MAAM,CAAC,CAAC,WAAW,MAAK,MAAK,EAAE;CACvE,OAAO,IAAI,MAAM,IAAI,OAAO,CAAC,CAAC,KAAK,GAAG,EAAE;AAC1C;;AAGA,SAAgB,iBACd,KACA,gBAAgB,OACD;CACf,OAAO;EACL,QAAW,MAAc,SAA6B,CAAC,MACrD,IAAI,OACF,MACA,OAAO,KAAK,UACV,MAAM,QAAQ,KAAK,IACf,eAAe,KAA6C,IAC5D,KACN,CACF;EACF,cAAiB,OACf,gBACI,GAAG,iBAAiB,KAAK,IAAI,CAAC,IAC5B,IAAqB,OAAO,OAAO,GAAG,iBAAiB,IAAI,IAAI,CAAC,CAAC;CAC3E;AACF;;;;;;AAgBA,SAAgB,gBACd,kBACA,UAAkC,CAAC,GACb;CACtB,MAAM,EAAE,YAAY,GAAG,iBAAiB;CACxC,MAAM,MAAM,SAAS,kBAAkB;EACrC,KAAK;EAIL,cAAc;EACd,aAAa;EACb,SAAS;EACT,GAAG;CACL,CAAC;CACD,OAAO;EACL,OAAO,IAAI,qBAAqB,iBAAiB,GAAG,GAAG,YAAY;EACnE,aAAa,IAAI,IAAI,EAAE,SAAS,EAAE,CAAC;CACrC;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"sql.cjs","names":["Buffer"],"sources":["../../src/store/sql.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\n\n/**\n * The smallest SQL client this store needs: parameterized queries (`$1`, `$2`,\n * …) and a transaction scope. It is deliberately not postgres.js, pg or\n * drizzle — the app wraps whichever it already uses in a few lines:\n *\n * const client: SqlClientLike = {\n * query: (text, params) => sql.unsafe(text, params), // postgres.js\n * transaction: (fn) => sql.begin((tx) => fn(wrap(tx))),\n * };\n *\n * jsonb columns may come back parsed (postgres.js, pg) or as strings (some\n * drivers); both are accepted.\n */\nexport interface SqlClientLike {\n query<T = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<T[]>;\n transaction<T>(fn: (tx: SqlClientLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * UUID v7 (time-ordered) for event rows, generated here so the store has no\n * dependency: 48-bit ms timestamp, version 7, RFC 4122 variant, 74 random bits.\n */\nexport function uuidv7(now = Date.now()): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n bytes[0] = (now / 2 ** 40) & 0xff;\n bytes[1] = (now / 2 ** 32) & 0xff;\n bytes[2] = (now / 2 ** 24) & 0xff;\n bytes[3] = (now / 2 ** 16) & 0xff;\n bytes[4] = (now / 2 ** 8) & 0xff;\n bytes[5] = now & 0xff;\n bytes[6] = (bytes[6] & 0x0f) | 0x70;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Buffer.from(bytes).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n"],"mappings":";;;;;;;AAwBA,SAAgB,OAAO,MAAM,KAAK,IAAI,GAAW;CAC/C,MAAM,wBAAQ,IAAI,WAAW,EAAE;CAC/B,OAAO,gBAAgB,KAAK;CAC5B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,MAAU;CAC5B,MAAM,KAAK,MAAM;CACjB,MAAM,KAAM,MAAM,KAAK,KAAQ;CAC/B,MAAM,KAAM,MAAM,KAAK,KAAQ;CAC/B,MAAM,MAAMA,YAAAA,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,KAAK;CAC7C,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,EAAE;AACzG"}
1
+ {"version":3,"file":"sql.cjs","names":["Buffer"],"sources":["../../src/store/sql.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\n\n/**\n * The smallest SQL client this store needs: parameterized queries (`$1`, `$2`,\n * …) and a transaction scope. It is deliberately not postgres.js, pg or\n * drizzle — the app wraps whichever it already uses in a few lines:\n *\n * const client: SqlClientLike = {\n * query: (text, params) => pg.query(text, params).then((r) => r.rows), // pg\n * transaction: (fn) => withTransaction((tx) => fn(wrap(tx))),\n * };\n *\n * For postgres.js use `postgresJsClient` (`@shware/workflow/postgres-js`): its\n * `unsafe` sends a JS array parameter as `String(array)`, which the store's\n * `= ANY($1::text[])` queries reject, so that wrapper writes arrays as Postgres\n * array literals. jsonb columns may come back parsed (postgres.js, pg) or as\n * strings (some drivers); both are accepted.\n */\nexport interface SqlClientLike {\n query<T = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<T[]>;\n transaction<T>(fn: (tx: SqlClientLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * UUID v7 (time-ordered) for event rows, generated here so the store has no\n * dependency: 48-bit ms timestamp, version 7, RFC 4122 variant, 74 random bits.\n */\nexport function uuidv7(now = Date.now()): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n bytes[0] = (now / 2 ** 40) & 0xff;\n bytes[1] = (now / 2 ** 32) & 0xff;\n bytes[2] = (now / 2 ** 24) & 0xff;\n bytes[3] = (now / 2 ** 16) & 0xff;\n bytes[4] = (now / 2 ** 8) & 0xff;\n bytes[5] = now & 0xff;\n bytes[6] = (bytes[6] & 0x0f) | 0x70;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Buffer.from(bytes).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n"],"mappings":";;;;;;;AA2BA,SAAgB,OAAO,MAAM,KAAK,IAAI,GAAW;CAC/C,MAAM,wBAAQ,IAAI,WAAW,EAAE;CAC/B,OAAO,gBAAgB,KAAK;CAC5B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,MAAU;CAC5B,MAAM,KAAK,MAAM;CACjB,MAAM,KAAM,MAAM,KAAK,KAAQ;CAC/B,MAAM,KAAM,MAAM,KAAK,KAAQ;CAC/B,MAAM,MAAMA,YAAAA,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,KAAK;CAC7C,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,EAAE;AACzG"}
@@ -4,12 +4,15 @@
4
4
  * drizzle — the app wraps whichever it already uses in a few lines:
5
5
  *
6
6
  * const client: SqlClientLike = {
7
- * query: (text, params) => sql.unsafe(text, params), // postgres.js
8
- * transaction: (fn) => sql.begin((tx) => fn(wrap(tx))),
7
+ * query: (text, params) => pg.query(text, params).then((r) => r.rows), // pg
8
+ * transaction: (fn) => withTransaction((tx) => fn(wrap(tx))),
9
9
  * };
10
10
  *
11
- * jsonb columns may come back parsed (postgres.js, pg) or as strings (some
12
- * drivers); both are accepted.
11
+ * For postgres.js use `postgresJsClient` (`@shware/workflow/postgres-js`): its
12
+ * `unsafe` sends a JS array parameter as `String(array)`, which the store's
13
+ * `= ANY($1::text[])` queries reject, so that wrapper writes arrays as Postgres
14
+ * array literals. jsonb columns may come back parsed (postgres.js, pg) or as
15
+ * strings (some drivers); both are accepted.
13
16
  */
14
17
  export interface SqlClientLike {
15
18
  query<T = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<T[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"sql.d.cts","names":[],"sources":["../../src/store/sql.ts"],"mappings":";;;;;;;;;;;;;iBAeiB;EACf,MAAM,IAAI,yBAAyB,cAAc,8BAA8B,QAAQ;EACvF,YAAY,GAAG,KAAK,IAAI,kBAAkB,QAAQ,KAAK,QAAQ;;;;;;wBAOjD,OAAO"}
1
+ {"version":3,"file":"sql.d.cts","names":[],"sources":["../../src/store/sql.ts"],"mappings":";;;;;;;;;;;;;;;;iBAkBiB;EACf,MAAM,IAAI,yBAAyB,cAAc,8BAA8B,QAAQ;EACvF,YAAY,GAAG,KAAK,IAAI,kBAAkB,QAAQ,KAAK,QAAQ;;;;;;wBAOjD,OAAO"}
@@ -4,12 +4,15 @@
4
4
  * drizzle — the app wraps whichever it already uses in a few lines:
5
5
  *
6
6
  * const client: SqlClientLike = {
7
- * query: (text, params) => sql.unsafe(text, params), // postgres.js
8
- * transaction: (fn) => sql.begin((tx) => fn(wrap(tx))),
7
+ * query: (text, params) => pg.query(text, params).then((r) => r.rows), // pg
8
+ * transaction: (fn) => withTransaction((tx) => fn(wrap(tx))),
9
9
  * };
10
10
  *
11
- * jsonb columns may come back parsed (postgres.js, pg) or as strings (some
12
- * drivers); both are accepted.
11
+ * For postgres.js use `postgresJsClient` (`@shware/workflow/postgres-js`): its
12
+ * `unsafe` sends a JS array parameter as `String(array)`, which the store's
13
+ * `= ANY($1::text[])` queries reject, so that wrapper writes arrays as Postgres
14
+ * array literals. jsonb columns may come back parsed (postgres.js, pg) or as
15
+ * strings (some drivers); both are accepted.
13
16
  */
14
17
  export interface SqlClientLike {
15
18
  query<T = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<T[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"sql.d.mts","names":[],"sources":["../../src/store/sql.ts"],"mappings":";;;;;;;;;;;;;iBAeiB;EACf,MAAM,IAAI,yBAAyB,cAAc,8BAA8B,QAAQ;EACvF,YAAY,GAAG,KAAK,IAAI,kBAAkB,QAAQ,KAAK,QAAQ;;;;;;wBAOjD,OAAO"}
1
+ {"version":3,"file":"sql.d.mts","names":[],"sources":["../../src/store/sql.ts"],"mappings":";;;;;;;;;;;;;;;;iBAkBiB;EACf,MAAM,IAAI,yBAAyB,cAAc,8BAA8B,QAAQ;EACvF,YAAY,GAAG,KAAK,IAAI,kBAAkB,QAAQ,KAAK,QAAQ;;;;;;wBAOjD,OAAO"}
@@ -1 +1 @@
1
- {"version":3,"file":"sql.mjs","names":[],"sources":["../../src/store/sql.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\n\n/**\n * The smallest SQL client this store needs: parameterized queries (`$1`, `$2`,\n * …) and a transaction scope. It is deliberately not postgres.js, pg or\n * drizzle — the app wraps whichever it already uses in a few lines:\n *\n * const client: SqlClientLike = {\n * query: (text, params) => sql.unsafe(text, params), // postgres.js\n * transaction: (fn) => sql.begin((tx) => fn(wrap(tx))),\n * };\n *\n * jsonb columns may come back parsed (postgres.js, pg) or as strings (some\n * drivers); both are accepted.\n */\nexport interface SqlClientLike {\n query<T = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<T[]>;\n transaction<T>(fn: (tx: SqlClientLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * UUID v7 (time-ordered) for event rows, generated here so the store has no\n * dependency: 48-bit ms timestamp, version 7, RFC 4122 variant, 74 random bits.\n */\nexport function uuidv7(now = Date.now()): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n bytes[0] = (now / 2 ** 40) & 0xff;\n bytes[1] = (now / 2 ** 32) & 0xff;\n bytes[2] = (now / 2 ** 24) & 0xff;\n bytes[3] = (now / 2 ** 16) & 0xff;\n bytes[4] = (now / 2 ** 8) & 0xff;\n bytes[5] = now & 0xff;\n bytes[6] = (bytes[6] & 0x0f) | 0x70;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Buffer.from(bytes).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n"],"mappings":";;;;;;AAwBA,SAAgB,OAAO,MAAM,KAAK,IAAI,GAAW;CAC/C,MAAM,wBAAQ,IAAI,WAAW,EAAE;CAC/B,OAAO,gBAAgB,KAAK;CAC5B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,MAAU;CAC5B,MAAM,KAAK,MAAM;CACjB,MAAM,KAAM,MAAM,KAAK,KAAQ;CAC/B,MAAM,KAAM,MAAM,KAAK,KAAQ;CAC/B,MAAM,MAAM,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,KAAK;CAC7C,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,EAAE;AACzG"}
1
+ {"version":3,"file":"sql.mjs","names":[],"sources":["../../src/store/sql.ts"],"sourcesContent":["import { Buffer } from 'node:buffer';\n\n/**\n * The smallest SQL client this store needs: parameterized queries (`$1`, `$2`,\n * …) and a transaction scope. It is deliberately not postgres.js, pg or\n * drizzle — the app wraps whichever it already uses in a few lines:\n *\n * const client: SqlClientLike = {\n * query: (text, params) => pg.query(text, params).then((r) => r.rows), // pg\n * transaction: (fn) => withTransaction((tx) => fn(wrap(tx))),\n * };\n *\n * For postgres.js use `postgresJsClient` (`@shware/workflow/postgres-js`): its\n * `unsafe` sends a JS array parameter as `String(array)`, which the store's\n * `= ANY($1::text[])` queries reject, so that wrapper writes arrays as Postgres\n * array literals. jsonb columns may come back parsed (postgres.js, pg) or as\n * strings (some drivers); both are accepted.\n */\nexport interface SqlClientLike {\n query<T = Record<string, unknown>>(text: string, params?: readonly unknown[]): Promise<T[]>;\n transaction<T>(fn: (tx: SqlClientLike) => Promise<T>): Promise<T>;\n}\n\n/**\n * UUID v7 (time-ordered) for event rows, generated here so the store has no\n * dependency: 48-bit ms timestamp, version 7, RFC 4122 variant, 74 random bits.\n */\nexport function uuidv7(now = Date.now()): string {\n const bytes = new Uint8Array(16);\n crypto.getRandomValues(bytes);\n bytes[0] = (now / 2 ** 40) & 0xff;\n bytes[1] = (now / 2 ** 32) & 0xff;\n bytes[2] = (now / 2 ** 24) & 0xff;\n bytes[3] = (now / 2 ** 16) & 0xff;\n bytes[4] = (now / 2 ** 8) & 0xff;\n bytes[5] = now & 0xff;\n bytes[6] = (bytes[6] & 0x0f) | 0x70;\n bytes[8] = (bytes[8] & 0x3f) | 0x80;\n const hex = Buffer.from(bytes).toString('hex');\n return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;\n}\n"],"mappings":";;;;;;AA2BA,SAAgB,OAAO,MAAM,KAAK,IAAI,GAAW;CAC/C,MAAM,wBAAQ,IAAI,WAAW,EAAE;CAC/B,OAAO,gBAAgB,KAAK;CAC5B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,KAAK,KAAM;CAC7B,MAAM,KAAM,MAAM,MAAU;CAC5B,MAAM,KAAK,MAAM;CACjB,MAAM,KAAM,MAAM,KAAK,KAAQ;CAC/B,MAAM,KAAM,MAAM,KAAK,KAAQ;CAC/B,MAAM,MAAM,OAAO,KAAK,KAAK,CAAC,CAAC,SAAS,KAAK;CAC7C,OAAO,GAAG,IAAI,MAAM,GAAG,CAAC,EAAE,GAAG,IAAI,MAAM,GAAG,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,IAAI,MAAM,EAAE;AACzG"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shware/workflow",
3
- "version": "9.0.0",
3
+ "version": "9.0.2",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/ShwareHQ/shware-sdk.git"