@protoyard/flows 0.0.0-stage → 1.1.1

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 (47) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -2
  3. package/dist/index.d.ts +2 -0
  4. package/dist/index.js +2 -0
  5. package/dist/model/conditions.d.ts +75 -0
  6. package/dist/model/conditions.js +297 -0
  7. package/dist/model/data.d.ts +14 -0
  8. package/dist/model/data.js +73 -0
  9. package/dist/model/flow.d.ts +53 -0
  10. package/dist/model/flow.js +93 -0
  11. package/dist/model/index.d.ts +17 -0
  12. package/dist/model/index.js +17 -0
  13. package/dist/model/limits.d.ts +32 -0
  14. package/dist/model/limits.js +32 -0
  15. package/dist/model/ops.d.ts +122 -0
  16. package/dist/model/ops.js +311 -0
  17. package/dist/model/recipes.d.ts +12 -0
  18. package/dist/model/recipes.js +209 -0
  19. package/dist/model/refs.d.ts +30 -0
  20. package/dist/model/refs.js +48 -0
  21. package/dist/model/steps.d.ts +276 -0
  22. package/dist/model/steps.js +206 -0
  23. package/dist/model/text.d.ts +30 -0
  24. package/dist/model/text.js +33 -0
  25. package/dist/model/triggers.d.ts +42 -0
  26. package/dist/model/triggers.js +68 -0
  27. package/dist/model/validate.d.ts +19 -0
  28. package/dist/model/validate.js +207 -0
  29. package/dist/model/walk.d.ts +40 -0
  30. package/dist/model/walk.js +68 -0
  31. package/dist/runtime/clock.d.ts +12 -0
  32. package/dist/runtime/clock.js +40 -0
  33. package/dist/runtime/engine.d.ts +15 -0
  34. package/dist/runtime/engine.js +578 -0
  35. package/dist/runtime/index.d.ts +11 -0
  36. package/dist/runtime/index.js +10 -0
  37. package/dist/runtime/keeps.d.ts +8 -0
  38. package/dist/runtime/keeps.js +33 -0
  39. package/dist/runtime/outbox.d.ts +8 -0
  40. package/dist/runtime/outbox.js +27 -0
  41. package/dist/runtime/seed.d.ts +7 -0
  42. package/dist/runtime/seed.js +45 -0
  43. package/dist/runtime/signals.d.ts +4 -0
  44. package/dist/runtime/signals.js +13 -0
  45. package/dist/runtime/types.d.ts +127 -0
  46. package/dist/runtime/types.js +5 -0
  47. package/package.json +62 -3
@@ -0,0 +1,68 @@
1
+ /** The arms of a step, empty for anything but a branch. */
2
+ export function childArms(step) {
3
+ if (step.type !== 'branch')
4
+ return [];
5
+ return [
6
+ { branch: 'then', steps: step.then },
7
+ { branch: 'otherwise', steps: step.otherwise },
8
+ ];
9
+ }
10
+ /** Visits every step in document (pre-)order, which is also run order. */
11
+ export function walkSteps(steps, visit, parent = null) {
12
+ const base = parent ? [...parent.path, parent.branch] : ['steps'];
13
+ steps.forEach((step, index) => {
14
+ const location = {
15
+ step,
16
+ path: [...base, index],
17
+ parentId: parent?.id ?? null,
18
+ branch: parent?.branch ?? null,
19
+ index,
20
+ depth: parent ? parent.depth + 1 : 0,
21
+ arms: parent ? [...parent.arms, `${parent.id}:${parent.branch}`] : [],
22
+ };
23
+ visit(location);
24
+ for (const arm of childArms(step)) {
25
+ walkSteps(arm.steps, visit, {
26
+ id: step.id,
27
+ branch: arm.branch,
28
+ path: location.path,
29
+ depth: location.depth,
30
+ arms: location.arms,
31
+ });
32
+ }
33
+ });
34
+ }
35
+ export function listSteps(steps) {
36
+ const out = [];
37
+ walkSteps(steps, (location) => out.push(location));
38
+ return out;
39
+ }
40
+ export function findStep(steps, stepId) {
41
+ return listSteps(steps).find((location) => location.step.id === stepId) ?? null;
42
+ }
43
+ /**
44
+ * True when `earlier` has certainly run by the time `later` starts: it comes
45
+ * first in run order and is not inside a branch arm that `later` is outside of.
46
+ */
47
+ export function runsBefore(earlier, later, order) {
48
+ const a = order.get(earlier.step.id);
49
+ const b = order.get(later.step.id);
50
+ if (a === undefined || b === undefined || a >= b)
51
+ return false;
52
+ return earlier.arms.every((arm, i) => later.arms[i] === arm);
53
+ }
54
+ /** The steps whose output a step may reference. */
55
+ export function stepsBefore(steps, stepId) {
56
+ const all = listSteps(steps);
57
+ const order = new Map(all.map((location, i) => [location.step.id, i]));
58
+ const target = all.find((location) => location.step.id === stepId);
59
+ if (!target)
60
+ return [];
61
+ return all.filter((location) => runsBefore(location, target, order)).map((l) => l.step);
62
+ }
63
+ /** `['steps', 0, 'then', 1]` → `steps[0].then[1]`. */
64
+ export function formatPath(path) {
65
+ return path
66
+ .map((segment, i) => typeof segment === 'number' ? `[${segment}]` : i === 0 ? segment : `.${segment}`)
67
+ .join('');
68
+ }
@@ -0,0 +1,12 @@
1
+ /** Injected simulated clock for the mock flow runtime. */
2
+ export interface MockClock {
3
+ nowMs(): number;
4
+ nowIso(): string;
5
+ /** Advance by whole minutes (waits and delays). */
6
+ advanceMinutes(minutes: number): void;
7
+ set(ms: number): void;
8
+ }
9
+ export declare function toEpochMs(value: string | number | Date | undefined, fallback: number): number;
10
+ /** Fixed default so tests without `now` still match across machines. */
11
+ export declare const DEFAULT_RUNTIME_NOW_MS: number;
12
+ export declare function createMockClock(initial?: string | number | Date): MockClock;
@@ -0,0 +1,40 @@
1
+ /** Injected simulated clock for the mock flow runtime. */
2
+ export function toEpochMs(value, fallback) {
3
+ if (value === undefined)
4
+ return fallback;
5
+ if (typeof value === 'number') {
6
+ if (!Number.isFinite(value))
7
+ throw new RangeError(`Invalid clock value: ${String(value)}`);
8
+ return value;
9
+ }
10
+ if (value instanceof Date) {
11
+ const ms = value.getTime();
12
+ if (Number.isNaN(ms))
13
+ throw new RangeError('Invalid clock Date');
14
+ return ms;
15
+ }
16
+ const ms = Date.parse(value);
17
+ if (Number.isNaN(ms))
18
+ throw new RangeError(`Invalid clock ISO: ${value}`);
19
+ return ms;
20
+ }
21
+ /** Fixed default so tests without `now` still match across machines. */
22
+ export const DEFAULT_RUNTIME_NOW_MS = Date.parse('2026-03-15T10:00:00.000Z');
23
+ export function createMockClock(initial) {
24
+ let ms = toEpochMs(initial, DEFAULT_RUNTIME_NOW_MS);
25
+ return {
26
+ nowMs: () => ms,
27
+ nowIso: () => new Date(ms).toISOString(),
28
+ advanceMinutes(minutes) {
29
+ if (!Number.isFinite(minutes) || minutes < 0) {
30
+ throw new RangeError(`Invalid advance minutes: ${String(minutes)}`);
31
+ }
32
+ ms += Math.floor(minutes) * 60_000;
33
+ },
34
+ set(next) {
35
+ if (!Number.isFinite(next))
36
+ throw new RangeError(`Invalid clock set: ${String(next)}`);
37
+ ms = next;
38
+ },
39
+ };
40
+ }
@@ -0,0 +1,15 @@
1
+ import { type Flow } from '../model/index.ts';
2
+ import type { FlowRunResult, FlowRuntimeOptions } from './types.ts';
3
+ /**
4
+ * Run a flow once against a mock context. Pure with respect to the inputs:
5
+ * the same flow, options and clock produce identical results. Does not mutate
6
+ * the input flow or the caller's keeps maps.
7
+ */
8
+ export declare function runFlow(flow: Flow, options?: FlowRuntimeOptions): FlowRunResult;
9
+ /**
10
+ * Convenience: create a reusable runner bound to default options (people,
11
+ * tools, seed). Each `run` call still takes a fresh flow and overrides.
12
+ */
13
+ export declare function createFlowRuntime(defaults?: FlowRuntimeOptions): {
14
+ run(flow: Flow, overrides?: FlowRuntimeOptions): FlowRunResult;
15
+ };