@pattern-js/runtime-node 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +70 -0
  3. package/dist/bin/pattern.d.ts +20 -0
  4. package/dist/bin/pattern.d.ts.map +1 -0
  5. package/dist/bin/pattern.js +365 -0
  6. package/dist/bin/pattern.js.map +1 -0
  7. package/dist/cli.d.ts +21 -0
  8. package/dist/cli.d.ts.map +1 -0
  9. package/dist/cli.js +75 -0
  10. package/dist/cli.js.map +1 -0
  11. package/dist/filesystem.d.ts +38 -0
  12. package/dist/filesystem.d.ts.map +1 -0
  13. package/dist/filesystem.js +59 -0
  14. package/dist/filesystem.js.map +1 -0
  15. package/dist/http.d.ts +94 -0
  16. package/dist/http.d.ts.map +1 -0
  17. package/dist/http.js +799 -0
  18. package/dist/http.js.map +1 -0
  19. package/dist/index.d.ts +21 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +23 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/load/generator.d.ts +19 -0
  24. package/dist/load/generator.d.ts.map +1 -0
  25. package/dist/load/generator.js +94 -0
  26. package/dist/load/generator.js.map +1 -0
  27. package/dist/load/index.d.ts +43 -0
  28. package/dist/load/index.d.ts.map +1 -0
  29. package/dist/load/index.js +87 -0
  30. package/dist/load/index.js.map +1 -0
  31. package/dist/load/recorder.d.ts +36 -0
  32. package/dist/load/recorder.d.ts.map +1 -0
  33. package/dist/load/recorder.js +106 -0
  34. package/dist/load/recorder.js.map +1 -0
  35. package/dist/load/report.d.ts +28 -0
  36. package/dist/load/report.d.ts.map +1 -0
  37. package/dist/load/report.js +84 -0
  38. package/dist/load/report.js.map +1 -0
  39. package/dist/load/types.d.ts +85 -0
  40. package/dist/load/types.d.ts.map +1 -0
  41. package/dist/load/types.js +51 -0
  42. package/dist/load/types.js.map +1 -0
  43. package/dist/mods.d.ts +23 -0
  44. package/dist/mods.d.ts.map +1 -0
  45. package/dist/mods.js +48 -0
  46. package/dist/mods.js.map +1 -0
  47. package/dist/project.d.ts +94 -0
  48. package/dist/project.d.ts.map +1 -0
  49. package/dist/project.js +168 -0
  50. package/dist/project.js.map +1 -0
  51. package/dist/schedule.d.ts +33 -0
  52. package/dist/schedule.d.ts.map +1 -0
  53. package/dist/schedule.js +132 -0
  54. package/dist/schedule.js.map +1 -0
  55. package/dist/trace/index.d.ts +32 -0
  56. package/dist/trace/index.d.ts.map +1 -0
  57. package/dist/trace/index.js +48 -0
  58. package/dist/trace/index.js.map +1 -0
  59. package/dist/trace/memory.d.ts +83 -0
  60. package/dist/trace/memory.d.ts.map +1 -0
  61. package/dist/trace/memory.js +260 -0
  62. package/dist/trace/memory.js.map +1 -0
  63. package/dist/trace/sqlite.d.ts +80 -0
  64. package/dist/trace/sqlite.d.ts.map +1 -0
  65. package/dist/trace/sqlite.js +344 -0
  66. package/dist/trace/sqlite.js.map +1 -0
  67. package/dist/worker/entry.d.ts +16 -0
  68. package/dist/worker/entry.d.ts.map +1 -0
  69. package/dist/worker/entry.js +135 -0
  70. package/dist/worker/entry.js.map +1 -0
  71. package/dist/worker-pool.d.ts +49 -0
  72. package/dist/worker-pool.d.ts.map +1 -0
  73. package/dist/worker-pool.js +203 -0
  74. package/dist/worker-pool.js.map +1 -0
  75. package/dist/ws-registry.d.ts +39 -0
  76. package/dist/ws-registry.d.ts.map +1 -0
  77. package/dist/ws-registry.js +99 -0
  78. package/dist/ws-registry.js.map +1 -0
  79. package/dist/ws.d.ts +72 -0
  80. package/dist/ws.d.ts.map +1 -0
  81. package/dist/ws.js +236 -0
  82. package/dist/ws.js.map +1 -0
  83. package/package.json +60 -0
@@ -0,0 +1,84 @@
1
+ /**
2
+ * Stage statistics + the terminal report. Latency percentiles come from the
3
+ * SCHEDULED-time deltas (queueing included); the flight recording, when
4
+ * present, is printed beneath each stage so the bottleneck reads next to the
5
+ * symptom.
6
+ */
7
+ const pct = (sorted, p) => {
8
+ if (!sorted.length)
9
+ return 0;
10
+ const i = Math.min(sorted.length - 1, Math.floor((p / 100) * sorted.length));
11
+ return Math.round(sorted[i] * 100) / 100;
12
+ };
13
+ export function summarize(stage, samples, flight) {
14
+ const lat = samples.map((s) => s.endedAt - s.scheduledAt).sort((a, b) => a - b);
15
+ const ok = samples.filter((s) => s.ok).length;
16
+ const span = samples.length ? (Math.max(...samples.map((s) => s.endedAt)) - Math.min(...samples.map((s) => s.scheduledAt))) / 1000 : 0;
17
+ const byStatus = {};
18
+ for (const s of samples) {
19
+ const key = s.status === 0 ? "ERR" : String(s.status);
20
+ byStatus[key] = (byStatus[key] ?? 0) + 1;
21
+ }
22
+ return {
23
+ stage,
24
+ requests: samples.length,
25
+ ok,
26
+ errors: samples.length - ok,
27
+ achievedRate: span > 0 ? Math.round(samples.length / span) : samples.length,
28
+ p50: pct(lat, 50),
29
+ p90: pct(lat, 90),
30
+ p99: pct(lat, 99),
31
+ max: Math.round((lat.at(-1) ?? 0) * 100) / 100,
32
+ mean: lat.length ? Math.round((lat.reduce((s, v) => s + v, 0) / lat.length) * 100) / 100 : 0,
33
+ bytesTotal: samples.reduce((s, v) => s + v.bytes, 0),
34
+ byStatus,
35
+ flight,
36
+ };
37
+ }
38
+ const c = {
39
+ bold: (s) => `\x1b[1m${s}\x1b[0m`,
40
+ dim: (s) => `\x1b[2m${s}\x1b[0m`,
41
+ red: (s) => `\x1b[31m${s}\x1b[0m`,
42
+ green: (s) => `\x1b[32m${s}\x1b[0m`,
43
+ cyan: (s) => `\x1b[36m${s}\x1b[0m`,
44
+ yellow: (s) => `\x1b[33m${s}\x1b[0m`,
45
+ };
46
+ const ms = (n) => `${n}ms`;
47
+ export function printStage(s) {
48
+ const errRate = s.requests ? s.errors / s.requests : 0;
49
+ const head = `${c.bold(`${s.stage.rate}/s`)} ${c.dim(`for ${s.stage.durationMs / 1000}s`)} → ${s.requests} reqs, ${c.bold(`${s.achievedRate}/s`)} achieved`;
50
+ console.log(`\n ${head}`);
51
+ const statusStr = Object.entries(s.byStatus)
52
+ .map(([k, v]) => (k.startsWith("2") ? c.green(`${k}×${v}`) : c.red(`${k}×${v}`)))
53
+ .join(" ");
54
+ console.log(` ${c.dim("status:")} ${statusStr}${errRate > 0.01 ? c.red(` (${(errRate * 100).toFixed(1)}% errors)`) : ""}`);
55
+ console.log(` ${c.dim("latency:")} p50 ${c.cyan(ms(s.p50))} p90 ${c.cyan(ms(s.p90))} p99 ${c.yellow(ms(s.p99))} max ${ms(s.max)} ${c.dim(`mean ${ms(s.mean)}`)}`);
56
+ if (s.flight && s.flight.spans > 0) {
57
+ const f = s.flight;
58
+ console.log(` ${c.dim("engine:")} ${f.runs} runs, peak concurrency ${c.bold(String(f.maxConcurrency))}${f.runErrors ? c.red(`, ${f.runErrors} run errors`) : ""}`);
59
+ const top = f.ops.slice(0, 5);
60
+ const totalOp = f.ops.reduce((sum, o) => sum + o.totalMs, 0) || 1;
61
+ console.log(` ${c.dim("where the time went (by op, total span ms):")}`);
62
+ for (const o of top) {
63
+ const share = Math.round((o.totalMs / totalOp) * 100);
64
+ const bar = "█".repeat(Math.max(1, Math.round(share / 5)));
65
+ console.log(` ${c.cyan(o.op.padEnd(26))} ${c.dim(`${bar} ${share}%`)} ${o.count}× · p99 ${ms(o.p99)}${o.errors ? c.red(` · ${o.errors} err`) : ""}`);
66
+ }
67
+ }
68
+ }
69
+ /** The saturation verdict: the last stage whose p99 + error rate stayed sane. */
70
+ export function printSweepVerdict(stages, p99BudgetMs) {
71
+ const knee = stages.find((s) => s.p99 > p99BudgetMs || s.errors / Math.max(1, s.requests) > 0.02);
72
+ const sustainable = knee ? stages[stages.indexOf(knee) - 1] : stages.at(-1);
73
+ console.log(`\n ${c.bold("Saturation sweep")}`);
74
+ if (sustainable) {
75
+ console.log(` max sustainable ≈ ${c.green(c.bold(`${sustainable.achievedRate}/s`))} ${c.dim(`(p99 ${ms(sustainable.p99)}, ${(sustainable.errors / Math.max(1, sustainable.requests) * 100).toFixed(1)}% err)`)}`);
76
+ }
77
+ if (knee) {
78
+ console.log(` knee at ${c.yellow(`${knee.stage.rate}/s`)}: ${c.dim(`p99 jumped to ${ms(knee.p99)}`)}${knee.flight ? c.dim(`, top op ${knee.flight.ops[0]?.op ?? "?"}`) : ""}`);
79
+ }
80
+ else {
81
+ console.log(` ${c.dim(`no knee within the swept range — budget p99 ${ms(p99BudgetMs)} never exceeded`)}`);
82
+ }
83
+ }
84
+ //# sourceMappingURL=report.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../../src/load/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,85 @@
1
+ /**
2
+ * @pattern-js/runtime-node — load-testing scenario schema (`pattern load`).
3
+ *
4
+ * A scenario is DATA (like everything in Pattern): versioned, diffable,
5
+ * committable. v1 fires HTTP requests at a workflow app under an OPEN-LOOP
6
+ * arrival schedule (requests launch at a fixed rate regardless of how many are
7
+ * in flight — closed-loop concurrency self-throttles and lies about capacity).
8
+ * The differentiator is the engine FLIGHT RECORDER: when the app boots
9
+ * in-process, a trace sink attributes client latency to per-node span time, so
10
+ * the report says *where* the pressure is, not just that there is some.
11
+ */
12
+ import { z } from "@pattern-js/core";
13
+ /** One request shape the generator can fire (chosen by weight). */
14
+ export declare const LoadRequestSchema: z.ZodObject<{
15
+ weight: z.ZodDefault<z.ZodNumber>;
16
+ method: z.ZodDefault<z.ZodString>;
17
+ path: z.ZodString;
18
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
19
+ body: z.ZodOptional<z.ZodUnknown>;
20
+ label: z.ZodOptional<z.ZodString>;
21
+ }, z.core.$strip>;
22
+ export type LoadRequest = z.infer<typeof LoadRequestSchema>;
23
+ /** A constant-rate phase: `rate` requests/second for `durationMs`. */
24
+ export declare const LoadStageSchema: z.ZodObject<{
25
+ rate: z.ZodNumber;
26
+ durationMs: z.ZodNumber;
27
+ }, z.core.$strip>;
28
+ export type LoadStage = z.infer<typeof LoadStageSchema>;
29
+ export declare const LoadScenarioSchema: z.ZodObject<{
30
+ version: z.ZodDefault<z.ZodLiteral<1>>;
31
+ entry: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodLiteral<false>]>>;
32
+ baseUrl: z.ZodOptional<z.ZodString>;
33
+ warmup: z.ZodOptional<z.ZodObject<{
34
+ rate: z.ZodNumber;
35
+ durationMs: z.ZodNumber;
36
+ }, z.core.$strip>>;
37
+ stages: z.ZodDefault<z.ZodArray<z.ZodObject<{
38
+ rate: z.ZodNumber;
39
+ durationMs: z.ZodNumber;
40
+ }, z.core.$strip>>>;
41
+ requests: z.ZodArray<z.ZodObject<{
42
+ weight: z.ZodDefault<z.ZodNumber>;
43
+ method: z.ZodDefault<z.ZodString>;
44
+ path: z.ZodString;
45
+ headers: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
46
+ body: z.ZodOptional<z.ZodUnknown>;
47
+ label: z.ZodOptional<z.ZodString>;
48
+ }, z.core.$strip>>;
49
+ maxInflight: z.ZodDefault<z.ZodNumber>;
50
+ }, z.core.$strip>;
51
+ export type LoadScenario = z.infer<typeof LoadScenarioSchema>;
52
+ /** One completed request observation. */
53
+ export interface RequestSample {
54
+ /** When this request was SCHEDULED to fire (open-loop reference). */
55
+ scheduledAt: number;
56
+ /** When it actually went out (scheduledAt + scheduling lag). */
57
+ sentAt: number;
58
+ endedAt: number;
59
+ status: number;
60
+ ok: boolean;
61
+ bytes: number;
62
+ label: string;
63
+ error?: string;
64
+ }
65
+ /** Per-op rollup from the flight recorder (one window). */
66
+ export interface OpStat {
67
+ op: string;
68
+ count: number;
69
+ totalMs: number;
70
+ selfMs: number;
71
+ p50: number;
72
+ p99: number;
73
+ errors: number;
74
+ }
75
+ /** What the recorder observed during a window. */
76
+ export interface FlightRecording {
77
+ spans: number;
78
+ runs: number;
79
+ runErrors: number;
80
+ maxConcurrency: number;
81
+ ops: OpStat[];
82
+ /** Wall time the window covered (ms). */
83
+ windowMs: number;
84
+ }
85
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/load/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,kBAAkB,CAAC;AAErC,mEAAmE;AACnE,eAAO,MAAM,iBAAiB;;;;;;;iBAW5B,CAAC;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,iBAAiB,CAAC,CAAC;AAE5D,sEAAsE;AACtE,eAAO,MAAM,eAAe;;;iBAG1B,CAAC;AACH,MAAM,MAAM,SAAS,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC;AAExD,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;iBAmB7B,CAAC;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAE9D,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,WAAW,EAAE,MAAM,CAAC;IACpB,gEAAgE;IAChE,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,2DAA2D;AAC3D,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kDAAkD;AAClD,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,MAAM,EAAE,CAAC;IACd,yCAAyC;IACzC,QAAQ,EAAE,MAAM,CAAC;CAClB"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * @pattern-js/runtime-node — load-testing scenario schema (`pattern load`).
3
+ *
4
+ * A scenario is DATA (like everything in Pattern): versioned, diffable,
5
+ * committable. v1 fires HTTP requests at a workflow app under an OPEN-LOOP
6
+ * arrival schedule (requests launch at a fixed rate regardless of how many are
7
+ * in flight — closed-loop concurrency self-throttles and lies about capacity).
8
+ * The differentiator is the engine FLIGHT RECORDER: when the app boots
9
+ * in-process, a trace sink attributes client latency to per-node span time, so
10
+ * the report says *where* the pressure is, not just that there is some.
11
+ */
12
+ import { z } from "@pattern-js/core";
13
+ /** One request shape the generator can fire (chosen by weight). */
14
+ export const LoadRequestSchema = z.object({
15
+ /** Relative pick weight among requests (default 1). */
16
+ weight: z.number().positive().default(1),
17
+ method: z.string().default("GET"),
18
+ /** Path or absolute URL. Relative paths resolve against the target base URL. */
19
+ path: z.string(),
20
+ headers: z.record(z.string(), z.string()).optional(),
21
+ /** Request body — a string sent as-is, or an object sent as JSON. */
22
+ body: z.unknown().optional(),
23
+ /** A label for the report (defaults to "METHOD path"). */
24
+ label: z.string().optional(),
25
+ });
26
+ /** A constant-rate phase: `rate` requests/second for `durationMs`. */
27
+ export const LoadStageSchema = z.object({
28
+ rate: z.number().positive(),
29
+ durationMs: z.number().int().positive(),
30
+ });
31
+ export const LoadScenarioSchema = z.object({
32
+ version: z.literal(1).default(1),
33
+ /**
34
+ * Project entry to BOOT in-process (default "src/index.ts" when present).
35
+ * Booting in-process is what unlocks the flight recorder. Set `false` to
36
+ * skip booting and fire at an already-running `baseUrl` (client metrics
37
+ * only — no span attribution).
38
+ */
39
+ entry: z.union([z.string(), z.literal(false)]).optional(),
40
+ /** Where to fire. Defaults to the booted host's first port. */
41
+ baseUrl: z.string().optional(),
42
+ /** A gentle phase before measurement (JIT warmup, pool spin-up). */
43
+ warmup: LoadStageSchema.optional(),
44
+ /** The measured phases, run in order. */
45
+ stages: z.array(LoadStageSchema).default([{ rate: 50, durationMs: 10_000 }]),
46
+ /** The request mix. */
47
+ requests: z.array(LoadRequestSchema).min(1),
48
+ /** Safety cap on simultaneously in-flight requests (default 10k). */
49
+ maxInflight: z.number().int().positive().default(10_000),
50
+ });
51
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/load/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,kBAAkB,CAAC;AAErC,mEAAmE;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,CAAC,MAAM,CAAC;IACxC,uDAAuD;IACvD,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,CAAC,CAAC;IACxC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC;IACjC,gFAAgF;IAChF,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,OAAO,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,QAAQ,EAAE;IACpD,qEAAqE;IACrE,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE;IAC5B,0DAA0D;IAC1D,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAC7B,CAAC,CAAC;AAGH,sEAAsE;AACtE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,CAAC,MAAM,CAAC;IACtC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC3B,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;CACxC,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,OAAO,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC;IAChC;;;;;OAKG;IACH,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IACzD,+DAA+D;IAC/D,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;IAC9B,oEAAoE;IACpE,MAAM,EAAE,eAAe,CAAC,QAAQ,EAAE;IAClC,yCAAyC;IACzC,MAAM,EAAE,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;IAC5E,uBAAuB;IACvB,QAAQ,EAAE,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC3C,qEAAqE;IACrE,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC;CACzD,CAAC,CAAC"}
package/dist/mods.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ /**
2
+ * @pattern-js/runtime-node — mod loading (§13, §16/14).
3
+ *
4
+ * Loads "mods" (plugins) and installs them into an engine. A mod is any module
5
+ * that exports a `PatternMod` (default export, or named `mod`). Three sources,
6
+ * all the same mechanism:
7
+ *
8
+ * - **1st-party** mods in this monorepo (`@pattern-js/mod-*`) — a bare specifier.
9
+ * - **3rd-party** mods published to npm — a bare specifier (an npm dependency).
10
+ * - **app-local** mods — a relative path (`./mods/foo.ts`), resolved against a
11
+ * base dir (the app root / config location).
12
+ *
13
+ * Mods may contribute ops, workflows, auth providers, and hooks; bringing a
14
+ * frontend app is a future field on `PatternMod`.
15
+ */
16
+ import type { Engine, PatternMod } from "@pattern-js/core";
17
+ export interface LoadModsOptions {
18
+ /** Base dir for resolving relative (app-local) mod specifiers. Default cwd. */
19
+ baseDir?: string;
20
+ }
21
+ /** Dynamically import each specifier and install the exported mod. */
22
+ export declare function loadMods(engine: Engine, specifiers: string[], opts?: LoadModsOptions): Promise<PatternMod[]>;
23
+ //# sourceMappingURL=mods.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mods.d.ts","sourceRoot":"","sources":["../src/mods.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAE3D,MAAM,WAAW,eAAe;IAC9B,+EAA+E;IAC/E,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAUD,sEAAsE;AACtE,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,MAAM,EAAE,EACpB,IAAI,GAAE,eAAoB,GACzB,OAAO,CAAC,UAAU,EAAE,CAAC,CAqBvB"}
package/dist/mods.js ADDED
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @pattern-js/runtime-node — mod loading (§13, §16/14).
3
+ *
4
+ * Loads "mods" (plugins) and installs them into an engine. A mod is any module
5
+ * that exports a `PatternMod` (default export, or named `mod`). Three sources,
6
+ * all the same mechanism:
7
+ *
8
+ * - **1st-party** mods in this monorepo (`@pattern-js/mod-*`) — a bare specifier.
9
+ * - **3rd-party** mods published to npm — a bare specifier (an npm dependency).
10
+ * - **app-local** mods — a relative path (`./mods/foo.ts`), resolved against a
11
+ * base dir (the app root / config location).
12
+ *
13
+ * Mods may contribute ops, workflows, auth providers, and hooks; bringing a
14
+ * frontend app is a future field on `PatternMod`.
15
+ */
16
+ import { pathToFileURL } from "node:url";
17
+ import { resolve, isAbsolute } from "node:path";
18
+ /** Resolve a mod specifier to something `import()` accepts. */
19
+ function resolveSpecifier(spec, baseDir) {
20
+ if (spec.startsWith(".") || isAbsolute(spec)) {
21
+ return pathToFileURL(resolve(baseDir, spec)).href;
22
+ }
23
+ return spec; // bare npm/workspace specifier
24
+ }
25
+ /** Dynamically import each specifier and install the exported mod. */
26
+ export async function loadMods(engine, specifiers, opts = {}) {
27
+ const baseDir = opts.baseDir ?? process.cwd();
28
+ const loaded = [];
29
+ for (const spec of specifiers) {
30
+ const mod = await import(resolveSpecifier(spec, baseDir));
31
+ const def = mod.default ?? mod.mod;
32
+ if (!def || !def.name) {
33
+ throw new Error(`"${spec}" does not export a PatternMod (default export or named "mod")`);
34
+ }
35
+ // Await the mod's async `setup` — `start()` must observe installed mods.
36
+ // `ready` is deferred: it runs once ALL mods are in (see below).
37
+ await engine.useAsync(def, { deferReady: true });
38
+ loaded.push(def);
39
+ }
40
+ // Second phase: every mod is installed (all ops registered) — now run the
41
+ // `ready` hooks. This is what lets the admin's control plane bootstrap
42
+ // stored workflows that use ops from mods listed *after* it in the config.
43
+ for (const def of loaded) {
44
+ await def.ready?.(engine);
45
+ }
46
+ return loaded;
47
+ }
48
+ //# sourceMappingURL=mods.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mods.js","sourceRoot":"","sources":["../src/mods.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,UAAU,EAAE,MAAM,WAAW,CAAC;AAQhD,+DAA+D;AAC/D,SAAS,gBAAgB,CAAC,IAAY,EAAE,OAAe;IACrD,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;QAC7C,OAAO,aAAa,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC;IACpD,CAAC;IACD,OAAO,IAAI,CAAC,CAAC,+BAA+B;AAC9C,CAAC;AAED,sEAAsE;AACtE,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,MAAc,EACd,UAAoB,EACpB,OAAwB,EAAE;IAE1B,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC9C,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;QAC9B,MAAM,GAAG,GAAG,MAAM,MAAM,CAAC,gBAAgB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAC1D,MAAM,GAAG,GAA2B,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,GAAG,CAAC;QAC3D,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;YACtB,MAAM,IAAI,KAAK,CAAC,IAAI,IAAI,gEAAgE,CAAC,CAAC;QAC5F,CAAC;QACD,yEAAyE;QACzE,iEAAiE;QACjE,MAAM,MAAM,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACnB,CAAC;IACD,0EAA0E;IAC1E,uEAAuE;IACvE,2EAA2E;IAC3E,KAAK,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QACzB,MAAM,GAAG,CAAC,KAAK,EAAE,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,94 @@
1
+ /**
2
+ * @pattern-js/runtime-node — project loader.
3
+ *
4
+ * A Pattern app is configured declaratively (`pattern.config.json`): which mods
5
+ * to load and where the workflow JSON files live. `loadProject` builds an engine,
6
+ * installs the mods (so their ops are available), loads every workflow `.json`
7
+ * from disk, and returns the engine plus a ready HTTP host.
8
+ *
9
+ * This is the seam for the runtime-dynamic future: workflows are data on disk
10
+ * now, but the same `engine.registerWorkflow` / `updateWorkflow` /
11
+ * `unregisterWorkflow` calls accept workflows from a DB or an admin API later —
12
+ * the HTTP host re-derives its routes live on every change.
13
+ */
14
+ import { Engine, type Workflow } from "@pattern-js/core";
15
+ import { HttpHost } from "./http.js";
16
+ import { WsHost } from "./ws.js";
17
+ export interface PatternConfig {
18
+ /** Mod specifiers: npm packages and/or app-local paths ("./mods/foo.ts"). */
19
+ mods?: string[];
20
+ /** Directory of workflow `.json` files. Default "./workflows". */
21
+ workflows?: string;
22
+ /** HTTP host defaults. */
23
+ http?: {
24
+ port?: number;
25
+ host?: string;
26
+ };
27
+ /**
28
+ * Off-loop execution for workflows flagged `offload`. A worker-thread pool
29
+ * runs those runs so their CPU-heavy compute can't stall the host event loop;
30
+ * everything else stays inline. `number` = pool size; the object form also
31
+ * picks which mods each worker loads (default: this project's `mods`). Omit
32
+ * to keep everything inline — an `offload` flag then degrades to a no-op.
33
+ *
34
+ * Exclude heavy / host-only mods from the worker set via the `{ mods }` form
35
+ * — e.g. drop `@pattern-js/mod-admin` so the admin's control-plane setup doesn't
36
+ * run in every worker (workers execute domain ops, not the admin).
37
+ */
38
+ workers?: number | {
39
+ size?: number;
40
+ mods?: string[];
41
+ };
42
+ /**
43
+ * WebSocket upgrades on the HTTP servers. Default true: `boundary.ws.*`
44
+ * trigger workflows bind declaratively (live, like HTTP routes) and
45
+ * authenticated clients can subscribe to `core.ws.notify` pushes even with
46
+ * no WS workflows at all. Set false to refuse upgrades entirely.
47
+ */
48
+ ws?: boolean | {
49
+ path?: string;
50
+ };
51
+ /**
52
+ * Run telemetry persistence. Durable SQLite by default so runs survive
53
+ * restarts and any process writing the same DB (host, workers, `pattern run`)
54
+ * shows up in the admin. `persist: false` keeps it in-memory (ephemeral);
55
+ * `path` overrides the default `<project>/.pattern/traces.db`. Degrades to
56
+ * in-memory automatically when `node:sqlite` is unavailable.
57
+ */
58
+ trace?: {
59
+ persist?: boolean;
60
+ path?: string;
61
+ capacity?: number;
62
+ };
63
+ }
64
+ /** Identity helper for authoring `pattern.config.ts` with type-checking. */
65
+ export declare function defineConfig(config: PatternConfig): PatternConfig;
66
+ export interface LoadedProject {
67
+ engine: Engine;
68
+ http: HttpHost;
69
+ /** The auto-wired WS host (absent when `config.ws === false`). */
70
+ ws?: WsHost;
71
+ config: PatternConfig;
72
+ /** Start the HTTP host (opens a server per declared port). */
73
+ start: () => Promise<{
74
+ ports: number[];
75
+ close: () => Promise<void>;
76
+ }>;
77
+ }
78
+ /**
79
+ * Load `.env` from the project dir into process.env (already-set variables
80
+ * win — the real environment outranks the file, dotenv-style). Hand-rolled
81
+ * on purpose: KEY=VALUE lines, # comments, optional single/double quotes.
82
+ * This is where PATTERN_VAULT_KEY / OPENAI_API_KEY live in dev.
83
+ */
84
+ export declare function loadDotEnv(baseDir: string, file?: string): void;
85
+ /** Read & parse every `*.json` workflow in a directory. */
86
+ export declare function loadWorkflowDir(dir: string): Promise<Workflow[]>;
87
+ /**
88
+ * Build a project from a `PatternConfig` (or a path to `pattern.config.json`).
89
+ * Mods load first (they register ops), then workflows are registered.
90
+ */
91
+ export declare function loadProject(configOrPath?: PatternConfig | string, opts?: {
92
+ engine?: Engine;
93
+ }): Promise<LoadedProject>;
94
+ //# sourceMappingURL=project.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.d.ts","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAKH,OAAO,EAAE,MAAM,EAAgC,KAAK,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAGvF,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAIjC,MAAM,WAAW,aAAa;IAC5B,6EAA6E;IAC7E,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,IAAI,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACxC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;IACtD;;;;;OAKG;IACH,EAAE,CAAC,EAAE,OAAO,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjC;;;;;;OAMG;IACH,KAAK,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,OAAO,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED,4EAA4E;AAC5E,wBAAgB,YAAY,CAAC,MAAM,EAAE,aAAa,GAAG,aAAa,CAEjE;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,QAAQ,CAAC;IACf,kEAAkE;IAClE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,aAAa,CAAC;IACtB,8DAA8D;IAC9D,KAAK,EAAE,MAAM,OAAO,CAAC;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;KAAE,CAAC,CAAC;CACvE;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,SAAS,GAAG,IAAI,CAoB/D;AAED,2DAA2D;AAC3D,wBAAsB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,QAAQ,EAAE,CAAC,CActE;AAED;;;GAGG;AACH,wBAAsB,WAAW,CAC/B,YAAY,GAAE,aAAa,GAAG,MAA8B,EAC5D,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,OAAO,CAAC,aAAa,CAAC,CA8FxB"}
@@ -0,0 +1,168 @@
1
+ /**
2
+ * @pattern-js/runtime-node — project loader.
3
+ *
4
+ * A Pattern app is configured declaratively (`pattern.config.json`): which mods
5
+ * to load and where the workflow JSON files live. `loadProject` builds an engine,
6
+ * installs the mods (so their ops are available), loads every workflow `.json`
7
+ * from disk, and returns the engine plus a ready HTTP host.
8
+ *
9
+ * This is the seam for the runtime-dynamic future: workflows are data on disk
10
+ * now, but the same `engine.registerWorkflow` / `updateWorkflow` /
11
+ * `unregisterWorkflow` calls accept workflows from a DB or an admin API later —
12
+ * the HTTP host re-derives its routes live on every change.
13
+ */
14
+ import { readFile, readdir } from "node:fs/promises";
15
+ import { existsSync, readFileSync } from "node:fs";
16
+ import { dirname, join, resolve } from "node:path";
17
+ import { Engine, TRACE_STORE } from "@pattern-js/core";
18
+ import { loadMods } from "./mods.js";
19
+ import { createTraceStore } from "./trace/index.js";
20
+ import { HttpHost } from "./http.js";
21
+ import { WsHost } from "./ws.js";
22
+ import { NodeConnectionRegistry } from "./ws-registry.js";
23
+ import { WorkerPoolTransport } from "./worker-pool.js";
24
+ /** Identity helper for authoring `pattern.config.ts` with type-checking. */
25
+ export function defineConfig(config) {
26
+ return config;
27
+ }
28
+ /**
29
+ * Load `.env` from the project dir into process.env (already-set variables
30
+ * win — the real environment outranks the file, dotenv-style). Hand-rolled
31
+ * on purpose: KEY=VALUE lines, # comments, optional single/double quotes.
32
+ * This is where PATTERN_VAULT_KEY / OPENAI_API_KEY live in dev.
33
+ */
34
+ export function loadDotEnv(baseDir, file = ".env") {
35
+ const path = resolve(baseDir, file);
36
+ if (!existsSync(path))
37
+ return;
38
+ const text = readFileSync(path, "utf8");
39
+ for (const rawLine of text.split("\n")) {
40
+ const line = rawLine.trim();
41
+ if (!line || line.startsWith("#"))
42
+ continue;
43
+ const eq = line.indexOf("=");
44
+ if (eq <= 0)
45
+ continue;
46
+ const key = line.slice(0, eq).trim().replace(/^export\s+/, "");
47
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))
48
+ continue;
49
+ let value = line.slice(eq + 1).trim();
50
+ if ((value.startsWith('"') && value.endsWith('"')) ||
51
+ (value.startsWith("'") && value.endsWith("'"))) {
52
+ value = value.slice(1, -1);
53
+ }
54
+ if (process.env[key] === undefined)
55
+ process.env[key] = value;
56
+ }
57
+ }
58
+ /** Read & parse every `*.json` workflow in a directory. */
59
+ export async function loadWorkflowDir(dir) {
60
+ if (!existsSync(dir))
61
+ return [];
62
+ const entries = await readdir(dir, { withFileTypes: true });
63
+ const workflows = [];
64
+ for (const entry of entries) {
65
+ if (!entry.isFile() || !entry.name.endsWith(".json"))
66
+ continue;
67
+ const text = await readFile(join(dir, entry.name), "utf8");
68
+ try {
69
+ workflows.push(JSON.parse(text));
70
+ }
71
+ catch (err) {
72
+ throw new Error(`failed to parse workflow ${entry.name}: ${err.message}`);
73
+ }
74
+ }
75
+ return workflows;
76
+ }
77
+ /**
78
+ * Build a project from a `PatternConfig` (or a path to `pattern.config.json`).
79
+ * Mods load first (they register ops), then workflows are registered.
80
+ */
81
+ export async function loadProject(configOrPath = "pattern.config.json", opts = {}) {
82
+ let config;
83
+ let baseDir;
84
+ if (typeof configOrPath === "string") {
85
+ const configPath = resolve(process.cwd(), configOrPath);
86
+ baseDir = dirname(configPath);
87
+ config = existsSync(configPath) ? JSON.parse(await readFile(configPath, "utf8")) : {};
88
+ }
89
+ else {
90
+ config = configOrPath;
91
+ baseDir = process.cwd();
92
+ }
93
+ // `.env` first (existing env wins), so mods' setup — the vault reading
94
+ // PATTERN_VAULT_KEY, agents reading OPENAI_API_KEY — and `$env` config
95
+ // interpolation all see it, whatever entry point launched us.
96
+ loadDotEnv(baseDir);
97
+ // Off-loop pool for `offload`-flagged workflows (opt-in via `workers`). Built
98
+ // here so it can be handed to the engine as `offloadTransport`; when the
99
+ // caller brought their own engine, they own its transports (so we skip it).
100
+ let offloadPool;
101
+ let traceStore;
102
+ // Inject process.env so workflow config can use `$env` / `${VAR}` references.
103
+ // The node connection registry up-front means `core.ws.*` ops (notify,
104
+ // broadcast…) reach the same sockets the WS host accepts.
105
+ const engine = opts.engine ?? new Engine({ env: process.env, connections: new NodeConnectionRegistry() });
106
+ if (!opts.engine && config.workers !== undefined) {
107
+ const w = typeof config.workers === "number" ? { size: config.workers } : config.workers;
108
+ // Forward each worker's trace into this engine's sink so offloaded runs land
109
+ // in the admin's Runs view (and the rest of the trace surfaces) like inline
110
+ // ones, tagged with which worker ran them.
111
+ offloadPool = new WorkerPoolTransport({
112
+ size: w.size,
113
+ mods: w.mods ?? config.mods,
114
+ onTrace: (evt) => engine.ingestTrace(evt),
115
+ });
116
+ engine.setOffloadTransport(offloadPool);
117
+ }
118
+ // Durable run telemetry. Created here (before the mods' setup) and provided as
119
+ // a service so the admin reads it instead of spinning up its own in-memory
120
+ // store — runs persist across restarts and any process writing the same DB
121
+ // shows up. Off only when the caller brought their own engine (they own it).
122
+ if (!opts.engine && config.trace?.persist !== false) {
123
+ traceStore = await createTraceStore({
124
+ kind: "sqlite",
125
+ path: config.trace?.path ?? resolve(baseDir, ".pattern/traces.db"),
126
+ capacity: config.trace?.capacity,
127
+ });
128
+ engine.onTrace(traceStore);
129
+ engine.provideService(TRACE_STORE, traceStore);
130
+ }
131
+ if (config.mods?.length) {
132
+ await loadMods(engine, config.mods, { baseDir });
133
+ }
134
+ const wfDir = resolve(baseDir, config.workflows ?? "workflows");
135
+ for (const wf of await loadWorkflowDir(wfDir)) {
136
+ // Async: runs the resolve phase for any boundary config ports (e.g. a route
137
+ // port fed by core.env). Plain `$env` config still works synchronously.
138
+ await engine.registerWorkflowAsync(wf);
139
+ }
140
+ const http = new HttpHost(engine, { defaultPort: config.http?.port, host: config.http?.host });
141
+ // WS rides the same servers (auto mode: boundary.ws.* workflows bind live;
142
+ // bare connections still serve as the notification channel). One WsHost
143
+ // attaches to every port the route reconciler opens, now or later.
144
+ let ws;
145
+ if (config.ws !== false) {
146
+ ws = new WsHost(engine, typeof config.ws === "object" ? { path: config.ws.path } : {});
147
+ const attached = new Set();
148
+ http.onServer((server) => {
149
+ if (attached.has(server))
150
+ return;
151
+ attached.add(server);
152
+ ws.attach(server);
153
+ });
154
+ }
155
+ return {
156
+ engine,
157
+ http,
158
+ ws,
159
+ config,
160
+ start: async () => {
161
+ const { ports, close } = await http.start();
162
+ // The returned close tears down the worker pool too (idempotent with
163
+ // engine.close(), which also owns it). No pool → a no-op.
164
+ return { ports, close: async () => void (await close(), await offloadPool?.close(), await traceStore?.close()) };
165
+ },
166
+ };
167
+ }
168
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"project.js","sourceRoot":"","sources":["../src/project.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AACrD,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,WAAW,EAAkC,MAAM,kBAAkB,CAAC;AACvF,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACjC,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;AAC1D,OAAO,EAAE,mBAAmB,EAAE,MAAM,kBAAkB,CAAC;AAsCvD,4EAA4E;AAC5E,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,MAAM,CAAC;AAChB,CAAC;AAYD;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,IAAI,GAAG,MAAM;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IACpC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO;IAC9B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACxC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAC5C,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAC7B,IAAI,EAAE,IAAI,CAAC;YAAE,SAAS;QACtB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAC/D,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,GAAG,CAAC;YAAE,SAAS;QACpD,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QACtC,IACE,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC;YAC9C,CAAC,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,KAAK,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,EAC9C,CAAC;YACD,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;QAC7B,CAAC;QACD,IAAI,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,SAAS;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;IAC/D,CAAC;AACH,CAAC;AAED,2DAA2D;AAC3D,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,GAAW;IAC/C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAChC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,MAAM,SAAS,GAAe,EAAE,CAAC;IACjC,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC5B,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,SAAS;QAC/D,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QAC3D,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAa,CAAC,CAAC;QAC/C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,KAAK,CAAC,4BAA4B,KAAK,CAAC,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QACvF,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,eAAuC,qBAAqB,EAC5D,OAA4B,EAAE;IAE9B,IAAI,MAAqB,CAAC;IAC1B,IAAI,OAAe,CAAC;IACpB,IAAI,OAAO,YAAY,KAAK,QAAQ,EAAE,CAAC;QACrC,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,YAAY,CAAC,CAAC;QACxD,OAAO,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;QAC9B,MAAM,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAmB,CAAC,CAAC,CAAC,EAAE,CAAC;IAC3G,CAAC;SAAM,CAAC;QACN,MAAM,GAAG,YAAY,CAAC;QACtB,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAC1B,CAAC;IAED,uEAAuE;IACvE,uEAAuE;IACvE,8DAA8D;IAC9D,UAAU,CAAC,OAAO,CAAC,CAAC;IAEpB,8EAA8E;IAC9E,yEAAyE;IACzE,4EAA4E;IAC5E,IAAI,WAA4C,CAAC;IACjD,IAAI,UAAkC,CAAC;IAEvC,8EAA8E;IAC9E,uEAAuE;IACvE,0DAA0D;IAC1D,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,MAAM,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,WAAW,EAAE,IAAI,sBAAsB,EAAE,EAAE,CAAC,CAAC;IAE1G,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACjD,MAAM,CAAC,GAAG,OAAO,MAAM,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC;QACzF,6EAA6E;QAC7E,4EAA4E;QAC5E,2CAA2C;QAC3C,WAAW,GAAG,IAAI,mBAAmB,CAAC;YACpC,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,IAAI,EAAE,CAAC,CAAC,IAAI,IAAI,MAAM,CAAC,IAAI;YAC3B,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC;SAC1C,CAAC,CAAC;QACH,MAAM,CAAC,mBAAmB,CAAC,WAAW,CAAC,CAAC;IAC1C,CAAC;IAED,+EAA+E;IAC/E,2EAA2E;IAC3E,2EAA2E;IAC3E,6EAA6E;IAC7E,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,KAAK,EAAE,OAAO,KAAK,KAAK,EAAE,CAAC;QACpD,UAAU,GAAG,MAAM,gBAAgB,CAAC;YAClC,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,MAAM,CAAC,KAAK,EAAE,IAAI,IAAI,OAAO,CAAC,OAAO,EAAE,oBAAoB,CAAC;YAClE,QAAQ,EAAE,MAAM,CAAC,KAAK,EAAE,QAAQ;SACjC,CAAC,CAAC;QACH,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QAC3B,MAAM,CAAC,cAAc,CAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IACjD,CAAC;IAED,IAAI,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC;QACxB,MAAM,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACnD,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,SAAS,IAAI,WAAW,CAAC,CAAC;IAChE,KAAK,MAAM,EAAE,IAAI,MAAM,eAAe,CAAC,KAAK,CAAC,EAAE,CAAC;QAC9C,4EAA4E;QAC5E,wEAAwE;QACxE,MAAM,MAAM,CAAC,qBAAqB,CAAC,EAAE,CAAC,CAAC;IACzC,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,QAAQ,CAAC,MAAM,EAAE,EAAE,WAAW,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAE/F,2EAA2E;IAC3E,wEAAwE;IACxE,mEAAmE;IACnE,IAAI,EAAsB,CAAC;IAC3B,IAAI,MAAM,CAAC,EAAE,KAAK,KAAK,EAAE,CAAC;QACxB,EAAE,GAAG,IAAI,MAAM,CAAC,MAAM,EAAE,OAAO,MAAM,CAAC,EAAE,KAAK,QAAQ,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACvF,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAW,CAAC;QACpC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,EAAE,EAAE;YACvB,IAAI,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,OAAO;YACjC,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;YACrB,EAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC;IAED,OAAO;QACL,MAAM;QACN,IAAI;QACJ,EAAE;QACF,MAAM;QACN,KAAK,EAAE,KAAK,IAAI,EAAE;YAChB,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YAC5C,qEAAqE;YACrE,0DAA0D;YAC1D,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,KAAK,EAAE,EAAE,MAAM,WAAW,EAAE,KAAK,EAAE,EAAE,MAAM,UAAU,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC;QACnH,CAAC;KACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * @pattern-js/runtime-node — schedule host (§7, §12).
3
+ *
4
+ * Fires `boundary.schedule` triggers on an interval or a 5-field cron. Results
5
+ * are discarded or traced. Cron is evaluated once per minute against the local
6
+ * clock (no external dependency).
7
+ */
8
+ import type { Engine, Workflow } from "@pattern-js/core";
9
+ /** Parse "m h dom mon dow" into per-field matchers. `*`, ranges, lists, steps. */
10
+ declare function cronMatcher(expr: string): (d: Date) => boolean;
11
+ export declare class ScheduleHost {
12
+ private readonly engine;
13
+ /** Timers keyed by workflow id, so a redeploy/remove reconciles cleanly. */
14
+ private timers;
15
+ private unsubscribe?;
16
+ constructor(engine: Engine);
17
+ /**
18
+ * Schedule every `boundary.schedule` trigger. With no argument it watches the
19
+ * engine and **reacts to workflow changes at runtime** — a workflow deployed,
20
+ * updated, or removed (e.g. from an admin) re-reconciles its timers live.
21
+ * Passing an explicit list disables the live watch (one-shot scheduling).
22
+ */
23
+ start(workflows?: Workflow[]): this;
24
+ private scheduleWorkflow;
25
+ private clearWorkflow;
26
+ private fire;
27
+ private everyInterval;
28
+ private everyCron;
29
+ stop(): void;
30
+ }
31
+ export declare function createScheduleHost(engine: Engine): ScheduleHost;
32
+ export { cronMatcher };
33
+ //# sourceMappingURL=schedule.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.d.ts","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAMzD,kFAAkF;AAClF,iBAAS,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,IAAI,KAAK,OAAO,CAiBvD;AAoBD,qBAAa,YAAY;IAKX,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAuC;IACrD,OAAO,CAAC,WAAW,CAAC,CAAa;gBAEJ,MAAM,EAAE,MAAM;IAE3C;;;;;OAKG;IACH,KAAK,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,GAAG,IAAI;IAanC,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,aAAa;IAKrB,OAAO,CAAC,IAAI;IAMZ,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,SAAS;IAgBjB,IAAI,IAAI,IAAI;CAMb;AAED,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,YAAY,CAE/D;AAED,OAAO,EAAE,WAAW,EAAE,CAAC"}