@voyantjs/workflows 0.28.3 → 0.30.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.
Files changed (59) hide show
  1. package/dist/driver.d.ts +237 -0
  2. package/dist/driver.d.ts.map +1 -0
  3. package/dist/driver.js +53 -0
  4. package/dist/events/compile.d.ts +34 -0
  5. package/dist/events/compile.d.ts.map +1 -0
  6. package/dist/events/compile.js +204 -0
  7. package/dist/events/index.d.ts +8 -0
  8. package/dist/events/index.d.ts.map +1 -0
  9. package/dist/events/index.js +11 -0
  10. package/dist/events/input-mapper.d.ts +24 -0
  11. package/dist/events/input-mapper.d.ts.map +1 -0
  12. package/dist/events/input-mapper.js +169 -0
  13. package/dist/events/manifest-builder.d.ts +32 -0
  14. package/dist/events/manifest-builder.d.ts.map +1 -0
  15. package/dist/events/manifest-builder.js +66 -0
  16. package/dist/events/payload-hash.d.ts +46 -0
  17. package/dist/events/payload-hash.d.ts.map +1 -0
  18. package/dist/events/payload-hash.js +98 -0
  19. package/dist/events/predicate.d.ts +77 -0
  20. package/dist/events/predicate.d.ts.map +1 -0
  21. package/dist/events/predicate.js +347 -0
  22. package/dist/events/registry.d.ts +37 -0
  23. package/dist/events/registry.d.ts.map +1 -0
  24. package/dist/events/registry.js +47 -0
  25. package/dist/handler/index.d.ts +8 -0
  26. package/dist/handler/index.d.ts.map +1 -1
  27. package/dist/handler/index.js +1 -0
  28. package/dist/http-ingest.d.ts +54 -0
  29. package/dist/http-ingest.d.ts.map +1 -0
  30. package/dist/http-ingest.js +214 -0
  31. package/dist/protocol/index.d.ts +17 -2
  32. package/dist/protocol/index.d.ts.map +1 -1
  33. package/dist/runtime/ctx.d.ts +9 -0
  34. package/dist/runtime/ctx.d.ts.map +1 -1
  35. package/dist/runtime/ctx.js +17 -0
  36. package/dist/runtime/executor.d.ts +7 -0
  37. package/dist/runtime/executor.d.ts.map +1 -1
  38. package/dist/runtime/executor.js +1 -0
  39. package/dist/trigger.d.ts +28 -14
  40. package/dist/trigger.d.ts.map +1 -1
  41. package/dist/trigger.js +4 -4
  42. package/dist/workflow.d.ts +10 -0
  43. package/dist/workflow.d.ts.map +1 -1
  44. package/package.json +14 -2
  45. package/src/driver.ts +277 -0
  46. package/src/events/compile.ts +268 -0
  47. package/src/events/index.ts +42 -0
  48. package/src/events/input-mapper.ts +201 -0
  49. package/src/events/manifest-builder.ts +97 -0
  50. package/src/events/payload-hash.ts +110 -0
  51. package/src/events/predicate.ts +390 -0
  52. package/src/events/registry.ts +88 -0
  53. package/src/handler/index.ts +9 -0
  54. package/src/http-ingest.ts +299 -0
  55. package/src/protocol/index.ts +17 -2
  56. package/src/runtime/ctx.ts +29 -0
  57. package/src/runtime/executor.ts +8 -0
  58. package/src/trigger.ts +31 -15
  59. package/src/workflow.ts +11 -0
@@ -0,0 +1,169 @@
1
+ // Input mapper DSL — projects a workflow input from an event envelope.
2
+ //
3
+ // Each `EventFilterDeclaration` carries an optional `input: InputMapper`. At
4
+ // ingest time, after the predicate matches, the mapper builds the actual
5
+ // input value passed to `driver.trigger(target, input, ...)`. Same path
6
+ // roots as the predicate evaluator (`data`, `metadata`, `name`, `emittedAt`).
7
+ //
8
+ // Variants:
9
+ // * `undefined` → pass through `envelope.data`
10
+ // * `{ passthrough: true }` → explicit pass-through of `envelope.data`
11
+ // * `{ path: string }` → workflow input = the resolved path value
12
+ // * `{ object: {...} }` → build an object by projecting each key;
13
+ // each value is itself an InputMapper or a
14
+ // `PathOrLit` for terminal projections
15
+ //
16
+ // Architecture: docs/architecture/workflows-runtime-architecture.md §13.2.
17
+ import { resolvePath } from "./predicate.js";
18
+ // ---- Public API ----
19
+ /**
20
+ * Project a workflow input from an event envelope. Mirrors the predicate
21
+ * evaluator's no-throw contract — missing paths produce `undefined` in the
22
+ * output, registration-time linting catches structural errors.
23
+ *
24
+ * Throws `InputMapperError` only on unexpected shape errors (the mapper
25
+ * itself was constructed wrong). Drivers catch and surface this as
26
+ * `IngestMatch.status === "skipped"` with reason `"input_projection_error"`.
27
+ */
28
+ export function projectInput(mapper, envelope) {
29
+ if (mapper === undefined)
30
+ return envelope.data;
31
+ if (typeof mapper !== "object" || mapper === null) {
32
+ throw new InputMapperError(`input mapper must be undefined, {passthrough}, {path}, or {object}, got ${typeof mapper}`);
33
+ }
34
+ if ("passthrough" in mapper) {
35
+ if (mapper.passthrough !== true) {
36
+ throw new InputMapperError(`{ passthrough } must be true`);
37
+ }
38
+ return envelope.data;
39
+ }
40
+ if ("path" in mapper) {
41
+ if (typeof mapper.path !== "string" || mapper.path.length === 0) {
42
+ throw new InputMapperError(`{ path } must be a non-empty string`);
43
+ }
44
+ return resolvePath(mapper.path, envelope);
45
+ }
46
+ if ("object" in mapper) {
47
+ if (typeof mapper.object !== "object" || mapper.object === null) {
48
+ throw new InputMapperError(`{ object } must map keys to InputMapper | PathOrLit`);
49
+ }
50
+ const out = {};
51
+ for (const [key, child] of Object.entries(mapper.object)) {
52
+ out[key] = projectChild(child, envelope);
53
+ }
54
+ return out;
55
+ }
56
+ throw new InputMapperError(`input mapper must contain one of passthrough | path | object, got keys: ${Object.keys(mapper).join(", ")}`);
57
+ }
58
+ export function validateInputMapper(mapper) {
59
+ const errors = [];
60
+ walkValidate(mapper, errors, []);
61
+ return { ok: errors.length === 0, errors };
62
+ }
63
+ // ---- Internals ----
64
+ class InputMapperError extends Error {
65
+ constructor(message) {
66
+ super(message);
67
+ this.name = "InputMapperError";
68
+ }
69
+ }
70
+ /**
71
+ * Resolve one entry inside `{ object: { ... } }`. The value is either a
72
+ * nested mapper (recurse) or a `PathOrLit` (terminal projection).
73
+ */
74
+ function projectChild(child, envelope) {
75
+ if (child === undefined)
76
+ return envelope.data;
77
+ if (typeof child !== "object" || child === null) {
78
+ throw new InputMapperError(`nested mapper entry must be an object, got ${typeof child}`);
79
+ }
80
+ // Distinguish PathOrLit (`{ path }` or `{ lit }`) from nested mapper.
81
+ if ("lit" in child) {
82
+ return child.lit;
83
+ }
84
+ // `{ path: "..." }` is shared between PathOrLit and InputMapper — resolve directly.
85
+ if ("path" in child) {
86
+ if (typeof child.path !== "string" || child.path.length === 0) {
87
+ throw new InputMapperError(`nested { path } must be a non-empty string`);
88
+ }
89
+ return resolvePath(child.path, envelope);
90
+ }
91
+ // Otherwise: a nested InputMapper (passthrough / object).
92
+ return projectInput(child, envelope);
93
+ }
94
+ function walkValidate(mapper, errors, path) {
95
+ if (mapper === undefined)
96
+ return;
97
+ if (typeof mapper !== "object" || mapper === null) {
98
+ errors.push(`${pathLabel(path)}: input mapper must be undefined or an object, got ${typeof mapper}`);
99
+ return;
100
+ }
101
+ const keys = Object.keys(mapper);
102
+ if (keys.length === 0) {
103
+ errors.push(`${pathLabel(path)}: input mapper must specify passthrough | path | object`);
104
+ return;
105
+ }
106
+ if ("passthrough" in mapper) {
107
+ if (mapper.passthrough !== true) {
108
+ errors.push(`${pathLabel(path)}: { passthrough } must be true`);
109
+ }
110
+ return;
111
+ }
112
+ if ("path" in mapper) {
113
+ if (typeof mapper.path !== "string" || mapper.path.length === 0) {
114
+ errors.push(`${pathLabel(path)}: { path } must be a non-empty string`);
115
+ return;
116
+ }
117
+ validatePathRoot(mapper.path, errors, path);
118
+ return;
119
+ }
120
+ if ("object" in mapper) {
121
+ if (typeof mapper.object !== "object" || mapper.object === null) {
122
+ errors.push(`${pathLabel(path)}: { object } must map keys to InputMapper | PathOrLit`);
123
+ return;
124
+ }
125
+ for (const [k, child] of Object.entries(mapper.object)) {
126
+ validateChild(child, errors, [...path, k]);
127
+ }
128
+ return;
129
+ }
130
+ errors.push(`${pathLabel(path)}: unknown mapper variant — keys: ${keys.join(", ")}`);
131
+ }
132
+ function validateChild(child, errors, path) {
133
+ if (child === undefined)
134
+ return;
135
+ if (typeof child !== "object" || child === null) {
136
+ errors.push(`${pathLabel(path)}: nested entry must be an object`);
137
+ return;
138
+ }
139
+ if ("lit" in child) {
140
+ const t = typeof child.lit;
141
+ if (t !== "string" &&
142
+ t !== "number" &&
143
+ t !== "boolean" &&
144
+ child.lit !== null) {
145
+ errors.push(`${pathLabel(path)}: { lit } must be string | number | boolean | null`);
146
+ }
147
+ return;
148
+ }
149
+ if ("path" in child) {
150
+ if (typeof child.path !== "string" || child.path.length === 0) {
151
+ errors.push(`${pathLabel(path)}: { path } must be a non-empty string`);
152
+ return;
153
+ }
154
+ validatePathRoot(child.path, errors, path);
155
+ return;
156
+ }
157
+ walkValidate(child, errors, path);
158
+ }
159
+ function validatePathRoot(path, errors, errorPath) {
160
+ // Match the predicate path roots exactly so the two DSLs stay aligned.
161
+ const firstSegment = path.split(".")[0] ?? "";
162
+ const root = firstSegment.split("[")[0] ?? "";
163
+ if (root !== "data" && root !== "metadata" && root !== "name" && root !== "emittedAt") {
164
+ errors.push(`${pathLabel(errorPath)}: path root "${root}" is not one of data | metadata | name | emittedAt`);
165
+ }
166
+ }
167
+ function pathLabel(path) {
168
+ return path.length === 0 ? "(root)" : path.join(".");
169
+ }
@@ -0,0 +1,32 @@
1
+ import type { WorkflowManifest } from "../protocol/index.js";
2
+ import type { EventFilterRuntimeEntry } from "./registry.js";
3
+ export interface BuildManifestArgs {
4
+ /** Project / tenant identifier. Single-tenant runtimes pass `"default"`. */
5
+ projectId?: string;
6
+ /** Deployment environment. */
7
+ environment: "production" | "preview" | "development";
8
+ /** Workflow definitions collected from modules + plugins. */
9
+ workflows: ReadonlyArray<{
10
+ id: string;
11
+ config?: {
12
+ defaultRuntime?: "edge" | "node";
13
+ retry?: unknown;
14
+ timeout?: unknown;
15
+ };
16
+ }>;
17
+ /** Event-filter entries from `getEventFilterRegistry()`. */
18
+ eventFilters: ReadonlyArray<EventFilterRuntimeEntry>;
19
+ /** Wall-clock build time, ms-since-epoch. Defaults to `Date.now()`. */
20
+ builtAt?: number;
21
+ /** Source-code version of the manifest builder. */
22
+ builderVersion?: string;
23
+ }
24
+ /**
25
+ * Build a deterministic `WorkflowManifest`. Same inputs always produce
26
+ * byte-identical output, including `versionId`.
27
+ *
28
+ * Does NOT write the manifest anywhere — that's the driver's
29
+ * `registerManifest(...)` responsibility. This function is pure.
30
+ */
31
+ export declare function buildManifest(args: BuildManifestArgs): Promise<WorkflowManifest>;
32
+ //# sourceMappingURL=manifest-builder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"manifest-builder.d.ts","sourceRoot":"","sources":["../../src/events/manifest-builder.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAEV,gBAAgB,EAEjB,MAAM,sBAAsB,CAAA;AAE7B,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAE5D,MAAM,WAAW,iBAAiB;IAChC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,8BAA8B;IAC9B,WAAW,EAAE,YAAY,GAAG,SAAS,GAAG,aAAa,CAAA;IACrD,6DAA6D;IAC7D,SAAS,EAAE,aAAa,CAAC;QACvB,EAAE,EAAE,MAAM,CAAA;QACV,MAAM,CAAC,EAAE;YAAE,cAAc,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,OAAO,CAAC;YAAC,OAAO,CAAC,EAAE,OAAO,CAAA;SAAE,CAAA;KAClF,CAAC,CAAA;IACF,4DAA4D;IAC5D,YAAY,EAAE,aAAa,CAAC,uBAAuB,CAAC,CAAA;IACpD,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,mDAAmD;IACnD,cAAc,CAAC,EAAE,MAAM,CAAA;CACxB;AAED;;;;;;GAMG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,iBAAiB,GAAG,OAAO,CAAC,gBAAgB,CAAC,CAsDtF"}
@@ -0,0 +1,66 @@
1
+ // Build a `WorkflowManifest` from collected workflow + event-filter entries.
2
+ //
3
+ // Called once at `createApp()` boot (PR4). The resulting manifest is
4
+ // content-addressed: byte-identical inputs produce byte-identical
5
+ // `versionId`s, so concurrent registration calls don't race meaningfully —
6
+ // the second caller sees the same versionId the first did.
7
+ //
8
+ // Architecture: docs/architecture/workflows-runtime-architecture.md §14.1.
9
+ import { canonicalJson, shortHash } from "./payload-hash.js";
10
+ /**
11
+ * Build a deterministic `WorkflowManifest`. Same inputs always produce
12
+ * byte-identical output, including `versionId`.
13
+ *
14
+ * Does NOT write the manifest anywhere — that's the driver's
15
+ * `registerManifest(...)` responsibility. This function is pure.
16
+ */
17
+ export async function buildManifest(args) {
18
+ const builtAt = args.builtAt ?? Date.now();
19
+ const builderVersion = args.builderVersion ?? "@voyantjs/workflows@manifest-builder/v1";
20
+ const projectId = args.projectId ?? "default";
21
+ const workflows = args.workflows
22
+ .map((wf) => ({
23
+ id: wf.id,
24
+ version: "v1",
25
+ steps: [],
26
+ schedules: [],
27
+ defaultRuntime: wf.config?.defaultRuntime ?? "edge",
28
+ hasCompensation: false,
29
+ sourceLocation: { file: "<runtime>", line: 0 },
30
+ }))
31
+ .sort((a, b) => a.id.localeCompare(b.id));
32
+ // Sort filters by id so the canonical form is order-independent.
33
+ const eventFilters = args.eventFilters
34
+ .map((entry) => entry.manifest)
35
+ .sort((a, b) => a.id.localeCompare(b.id));
36
+ const draft = {
37
+ schemaVersion: 1,
38
+ projectId,
39
+ builtAt,
40
+ builderVersion,
41
+ capabilities: ["events:v1"],
42
+ workflows,
43
+ eventFilters,
44
+ bindings: {},
45
+ environments: { production: {}, preview: {}, development: {} },
46
+ };
47
+ // versionId is the cryptographic short hash of the canonical manifest
48
+ // body (excluding builtAt + versionId itself, which are non-load-bearing
49
+ // for content identity).
50
+ const identityBody = {
51
+ schemaVersion: draft.schemaVersion,
52
+ projectId: draft.projectId,
53
+ builderVersion: draft.builderVersion,
54
+ capabilities: draft.capabilities,
55
+ workflows: draft.workflows,
56
+ eventFilters: draft.eventFilters,
57
+ bindings: draft.bindings,
58
+ environments: draft.environments,
59
+ };
60
+ const versionId = await shortHash(identityBody);
61
+ void canonicalJson; // referenced via shortHash; keep the import surface stable
62
+ return {
63
+ ...draft,
64
+ versionId,
65
+ };
66
+ }
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Recursively alphabetize object keys, leaving arrays and primitives intact.
3
+ * `undefined` is converted to `null` so canonical JSON shape is stable
4
+ * (JSON.stringify drops `undefined` from objects).
5
+ */
6
+ export declare function canonicalize(value: unknown): unknown;
7
+ /**
8
+ * Stable canonical JSON string for `value`. Two values that are deeply
9
+ * equal modulo key order produce identical strings; two values that
10
+ * differ in any way produce different strings. Used as the input to
11
+ * `sha256(...)` for content-derived ids.
12
+ */
13
+ export declare function canonicalJson(value: unknown): string;
14
+ /**
15
+ * SHA-256 hex digest of an arbitrary value (canonicalized first). Async
16
+ * — callers await once during manifest build.
17
+ *
18
+ * @returns lowercase hex string, 64 chars long.
19
+ */
20
+ export declare function sha256(value: unknown): Promise<string>;
21
+ /**
22
+ * Short content-derived id, used as `EventFilterManifestEntry.payloadHash`
23
+ * and `WorkflowManifest.versionId`. 16 hex chars (~64 bits) — collision
24
+ * space is fine for human-friendly ids in dashboards/logs; the canonical
25
+ * full hash is `sha256(...)` if you need it.
26
+ */
27
+ export declare function shortHash(value: unknown): Promise<string>;
28
+ /**
29
+ * Derive a stable event id from an envelope when `metadata.eventId` is
30
+ * absent. Mirrors the formula from architecture doc §15.2:
31
+ *
32
+ * `${name}:${emittedAt}:${sha256(canonical(data)).slice(0, 12)}`
33
+ *
34
+ * Same envelope content always produces the same id — concurrent retries
35
+ * of the same external HTTP delivery dedupe at the driver's
36
+ * `${filterId}:${eventId}` idempotency key derivation.
37
+ *
38
+ * Returns a fallback id of the form `evt_<name>_<emittedAt>_<hash12>`
39
+ * (URL-safe; no colons in case the id flows through path segments).
40
+ */
41
+ export declare function deriveStableEventId(envelope: {
42
+ name: string;
43
+ data: unknown;
44
+ emittedAt: string;
45
+ }): Promise<string>;
46
+ //# sourceMappingURL=payload-hash.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payload-hash.d.ts","sourceRoot":"","sources":["../../src/events/payload-hash.ts"],"names":[],"mappings":"AAWA;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAYpD;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AACH,wBAAsB,MAAM,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAK5D;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAG/D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE;IAClD,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,OAAO,CAAA;IACb,SAAS,EAAE,MAAM,CAAA;CAClB,GAAG,OAAO,CAAC,MAAM,CAAC,CAKlB"}
@@ -0,0 +1,98 @@
1
+ // Canonical JSON + SHA-256 helpers used to derive `payloadHash` ids for
2
+ // `EventFilterRuntimeEntry` and `WorkflowManifest.versionId`.
3
+ //
4
+ // Canonicalization recursively alphabetizes object keys before
5
+ // JSON.stringify. SHA-256 uses Web Crypto (`globalThis.crypto.subtle`),
6
+ // available on Node ≥ 19, all modern browsers, and Cloudflare Workers.
7
+ // Async because Web Crypto's digest is async — callers `await` once
8
+ // at registration time.
9
+ //
10
+ // Architecture: docs/architecture/workflows-runtime-architecture.md §13.3.
11
+ /**
12
+ * Recursively alphabetize object keys, leaving arrays and primitives intact.
13
+ * `undefined` is converted to `null` so canonical JSON shape is stable
14
+ * (JSON.stringify drops `undefined` from objects).
15
+ */
16
+ export function canonicalize(value) {
17
+ if (value === undefined)
18
+ return null;
19
+ if (value === null || typeof value !== "object")
20
+ return value;
21
+ if (Array.isArray(value)) {
22
+ return value.map(canonicalize);
23
+ }
24
+ const sorted = {};
25
+ const keys = Object.keys(value).sort();
26
+ for (const k of keys) {
27
+ sorted[k] = canonicalize(value[k]);
28
+ }
29
+ return sorted;
30
+ }
31
+ /**
32
+ * Stable canonical JSON string for `value`. Two values that are deeply
33
+ * equal modulo key order produce identical strings; two values that
34
+ * differ in any way produce different strings. Used as the input to
35
+ * `sha256(...)` for content-derived ids.
36
+ */
37
+ export function canonicalJson(value) {
38
+ return JSON.stringify(canonicalize(value));
39
+ }
40
+ /**
41
+ * SHA-256 hex digest of an arbitrary value (canonicalized first). Async
42
+ * — callers await once during manifest build.
43
+ *
44
+ * @returns lowercase hex string, 64 chars long.
45
+ */
46
+ export async function sha256(value) {
47
+ const text = canonicalJson(value);
48
+ const bytes = new TextEncoder().encode(text);
49
+ const digest = await getCrypto().subtle.digest("SHA-256", bytes);
50
+ return bytesToHex(new Uint8Array(digest));
51
+ }
52
+ /**
53
+ * Short content-derived id, used as `EventFilterManifestEntry.payloadHash`
54
+ * and `WorkflowManifest.versionId`. 16 hex chars (~64 bits) — collision
55
+ * space is fine for human-friendly ids in dashboards/logs; the canonical
56
+ * full hash is `sha256(...)` if you need it.
57
+ */
58
+ export async function shortHash(value) {
59
+ const full = await sha256(value);
60
+ return full.slice(0, 16);
61
+ }
62
+ /**
63
+ * Derive a stable event id from an envelope when `metadata.eventId` is
64
+ * absent. Mirrors the formula from architecture doc §15.2:
65
+ *
66
+ * `${name}:${emittedAt}:${sha256(canonical(data)).slice(0, 12)}`
67
+ *
68
+ * Same envelope content always produces the same id — concurrent retries
69
+ * of the same external HTTP delivery dedupe at the driver's
70
+ * `${filterId}:${eventId}` idempotency key derivation.
71
+ *
72
+ * Returns a fallback id of the form `evt_<name>_<emittedAt>_<hash12>`
73
+ * (URL-safe; no colons in case the id flows through path segments).
74
+ */
75
+ export async function deriveStableEventId(envelope) {
76
+ const dataHash = (await sha256(envelope.data)).slice(0, 12);
77
+ const safeName = envelope.name.replace(/[^a-zA-Z0-9._-]/g, "_");
78
+ const safeAt = envelope.emittedAt.replace(/[^a-zA-Z0-9.]/g, "_");
79
+ return `evt_${safeName}_${safeAt}_${dataHash}`;
80
+ }
81
+ // ---- Internal ----
82
+ function getCrypto() {
83
+ // `globalThis.crypto` is available on Node 19+, Workers, browsers.
84
+ // Any environment older than that needs a polyfill at the consumer level.
85
+ const c = globalThis.crypto;
86
+ if (!c?.subtle) {
87
+ throw new Error("@voyantjs/workflows/events: globalThis.crypto.subtle is required for payload-hash. " +
88
+ "Polyfill via webcrypto on legacy runtimes.");
89
+ }
90
+ return c;
91
+ }
92
+ function bytesToHex(bytes) {
93
+ let out = "";
94
+ for (let i = 0; i < bytes.length; i++) {
95
+ out += (bytes[i] ?? 0).toString(16).padStart(2, "0");
96
+ }
97
+ return out;
98
+ }
@@ -0,0 +1,77 @@
1
+ /** Either a path into the envelope or an inline literal value. */
2
+ export type PathOrLit = {
3
+ path: string;
4
+ } | {
5
+ lit: string | number | boolean | null;
6
+ };
7
+ export type PredicateExpr = {
8
+ eq: [PathOrLit, PathOrLit];
9
+ } | {
10
+ neq: [PathOrLit, PathOrLit];
11
+ } | {
12
+ in: [PathOrLit, PathOrLit[]];
13
+ } | {
14
+ gt: [PathOrLit, PathOrLit];
15
+ } | {
16
+ gte: [PathOrLit, PathOrLit];
17
+ } | {
18
+ lt: [PathOrLit, PathOrLit];
19
+ } | {
20
+ lte: [PathOrLit, PathOrLit];
21
+ } | {
22
+ exists: PathOrLit;
23
+ } | {
24
+ not: PredicateExpr;
25
+ } | {
26
+ and: PredicateExpr[];
27
+ } | {
28
+ or: PredicateExpr[];
29
+ };
30
+ /**
31
+ * Minimal structural envelope the evaluator reads. Matches the standard
32
+ * `EventEnvelope` from `@voyantjs/core`. Declared structurally here so the
33
+ * SDK package stays a leaf.
34
+ */
35
+ export interface PredicateEnvelope<TData = unknown> {
36
+ name: string;
37
+ data: TData;
38
+ metadata?: Record<string, unknown> | undefined;
39
+ emittedAt: string;
40
+ }
41
+ /**
42
+ * Evaluate a predicate against an event envelope. Returns `true` / `false`.
43
+ * Path resolution against missing keys yields `undefined`, which makes
44
+ * comparison ops `false` (not throw). The evaluator never throws on data
45
+ * mismatches — registration-time linting catches structural errors via
46
+ * {@link validatePredicate}.
47
+ *
48
+ * Throws `PredicateEvalError` only on unexpected shape errors (the predicate
49
+ * itself was constructed wrong, e.g. malformed operator). Drivers catch
50
+ * this and surface it as `IngestMatch.status === "skipped"` with reason
51
+ * `"where_eval_error"`.
52
+ */
53
+ export declare function evaluatePredicate(expr: PredicateExpr, envelope: PredicateEnvelope): boolean;
54
+ /**
55
+ * Resolve a path string against an envelope. Public so consumers (input
56
+ * mapper, manifest builder) can share the same path semantics without
57
+ * re-implementing them.
58
+ *
59
+ * Path syntax: dot-separated; `[N]` for array index; missing intermediate
60
+ * keys produce `undefined`. Roots: `data`, `metadata`, `name`, `emittedAt`.
61
+ *
62
+ * Returns `undefined` on any shape mismatch — that's how runtime evaluation
63
+ * stays no-throw.
64
+ */
65
+ export declare function resolvePath(path: string, envelope: PredicateEnvelope): unknown;
66
+ export interface PredicateValidationResult {
67
+ ok: boolean;
68
+ errors: string[];
69
+ }
70
+ /**
71
+ * Static structural check on a `PredicateExpr`. Catches path roots that
72
+ * aren't `data`/`metadata`/`name`/`emittedAt`, type mismatches on
73
+ * comparison operators (number vs string lhs/rhs), and malformed grammars.
74
+ * Surfaced at `trigger.on()` registration so authoring errors fail fast.
75
+ */
76
+ export declare function validatePredicate(expr: PredicateExpr): PredicateValidationResult;
77
+ //# sourceMappingURL=predicate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"predicate.d.ts","sourceRoot":"","sources":["../../src/events/predicate.ts"],"names":[],"mappings":"AAkBA,kEAAkE;AAClE,MAAM,MAAM,SAAS,GAAG;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAA;CAAE,CAAA;AAIpF,MAAM,MAAM,aAAa,GACrB;IAAE,EAAE,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;CAAE,GAC9B;IAAE,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,CAAC,SAAS,EAAE,SAAS,EAAE,CAAC,CAAA;CAAE,GAChC;IAAE,EAAE,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;CAAE,GAC9B;IAAE,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;CAAE,GAC/B;IAAE,EAAE,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;CAAE,GAC9B;IAAE,GAAG,EAAE,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;CAAE,GAC/B;IAAE,MAAM,EAAE,SAAS,CAAA;CAAE,GACrB;IAAE,GAAG,EAAE,aAAa,CAAA;CAAE,GACtB;IAAE,GAAG,EAAE,aAAa,EAAE,CAAA;CAAE,GACxB;IAAE,EAAE,EAAE,aAAa,EAAE,CAAA;CAAE,CAAA;AAI3B;;;;GAIG;AACH,MAAM,WAAW,iBAAiB,CAAC,KAAK,GAAG,OAAO;IAChD,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,KAAK,CAAA;IACX,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAA;IAC9C,SAAS,EAAE,MAAM,CAAA;CAClB;AAID;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAiC3F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,iBAAiB,GAAG,OAAO,CAkC9E;AAID,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,OAAO,CAAA;IACX,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,aAAa,GAAG,yBAAyB,CAIhF"}