@db-lyon/flowkit 0.13.0 → 0.15.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 (53) hide show
  1. package/README.md +387 -386
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/flow/runner.d.ts +12 -4
  4. package/dist/flow/runner.d.ts.map +1 -1
  5. package/dist/flow/runner.js +27 -12
  6. package/dist/flow/runner.js.map +1 -1
  7. package/dist/guard/index.d.ts +7 -0
  8. package/dist/guard/index.d.ts.map +1 -0
  9. package/dist/guard/index.js +5 -0
  10. package/dist/guard/index.js.map +1 -0
  11. package/dist/guard/pipeline.d.ts +22 -0
  12. package/dist/guard/pipeline.d.ts.map +1 -0
  13. package/dist/guard/pipeline.js +45 -0
  14. package/dist/guard/pipeline.js.map +1 -0
  15. package/dist/guard/registry.d.ts +20 -0
  16. package/dist/guard/registry.d.ts.map +1 -0
  17. package/dist/guard/registry.js +33 -0
  18. package/dist/guard/registry.js.map +1 -0
  19. package/dist/guard/task-guards.d.ts +89 -0
  20. package/dist/guard/task-guards.d.ts.map +1 -0
  21. package/dist/guard/task-guards.js +97 -0
  22. package/dist/guard/task-guards.js.map +1 -0
  23. package/dist/guard/types.d.ts +49 -0
  24. package/dist/guard/types.d.ts.map +1 -0
  25. package/dist/guard/types.js +38 -0
  26. package/dist/guard/types.js.map +1 -0
  27. package/dist/index.d.ts +8 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +6 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/task/base-task.d.ts +65 -3
  32. package/dist/task/base-task.d.ts.map +1 -1
  33. package/dist/task/base-task.js +51 -5
  34. package/dist/task/base-task.js.map +1 -1
  35. package/dist/task/index.d.ts +2 -2
  36. package/dist/task/index.d.ts.map +1 -1
  37. package/dist/task/index.js +1 -1
  38. package/dist/task/index.js.map +1 -1
  39. package/dist/task/registry.d.ts +11 -3
  40. package/dist/task/registry.d.ts.map +1 -1
  41. package/dist/task/registry.js +11 -3
  42. package/dist/task/registry.js.map +1 -1
  43. package/docs/ai-agents.md +368 -368
  44. package/docs/api-reference.md +606 -458
  45. package/docs/configuration.md +347 -347
  46. package/docs/custom-tasks.md +271 -258
  47. package/docs/guards.md +133 -0
  48. package/docs/releases.md +59 -0
  49. package/package.json +59 -53
  50. package/dist/flow/references.d.ts +0 -39
  51. package/dist/flow/references.d.ts.map +0 -1
  52. package/dist/flow/references.js +0 -102
  53. package/dist/flow/references.js.map +0 -1
@@ -0,0 +1,59 @@
1
+ # Release notes
2
+
3
+ ## 0.15.0
4
+
5
+ Flowkit now exposes a generic execution-lifecycle contract on every task:
6
+
7
+ ```typescript
8
+ type ExecutionPhase =
9
+ | 'task'
10
+ | 'on_start'
11
+ | 'on_success'
12
+ | 'on_failure'
13
+ | 'finally'
14
+ | 'rollback';
15
+
16
+ // Inside a task:
17
+ protected get executionPhase(): ExecutionPhase;
18
+ ```
19
+
20
+ The runner derives a fresh context for each invocation. Ordinary work,
21
+ including nested flows, direct task calls, task-to-task calls, agents, and
22
+ tools, receives `task`; hooks and rollback invocations receive their matching
23
+ phase. A nested flow or sub-agent cannot carry its caller's phase into its own
24
+ ordinary work.
25
+
26
+ This is additive for hosts, including at the type level. `executionPhase` is
27
+ **optional** on `TaskContext`, so a host context interface built on it stays
28
+ constructible without naming a phase:
29
+
30
+ ```typescript
31
+ interface FlowContext extends TaskContext, ToolContext {}
32
+ const ctx: FlowContext = { bridge, project }; // still compiles
33
+ ```
34
+
35
+ Making the field required would have broken every downstream context type that
36
+ extends `TaskContext`, which is why it is not. Inside a task, read
37
+ `this.executionPhase` rather than `this.ctx.executionPhase`: the accessor is
38
+ typed `ExecutionPhase` with no `undefined`, because Flowkit resolves the value
39
+ before any task observes it. `ResolvedTaskContext` names that resolved shape for
40
+ code that needs the type directly.
41
+
42
+ `TaskRegistry.create()` accepts host-style context without an `executionPhase`
43
+ and derives `task` before construction. Code that calls `registry.resolve()` and
44
+ directly instantiates the returned constructor gets the same defaulting from
45
+ `BaseTask`, so it does not need to supply a phase either.
46
+
47
+ Guard hosts are unaffected: `DiscoverTaskGuardsOptions.contextFor` returns
48
+ `TaskContext`, unchanged from 0.14.0.
49
+
50
+ The context a host passes is no longer copied when it already carries a phase,
51
+ which is every path through `FlowRunner`. A host may therefore pass a class
52
+ instance or service object as its context and keep its prototype methods and
53
+ object identity; previously each invocation shallow-copied it. `Flowkit` exports
54
+ `resolveTaskContext` and `DEFAULT_EXECUTION_PHASE` for hosts that construct
55
+ tasks themselves and want the same normalization.
56
+
57
+ A string `when:` condition now receives the phase of the step it gates, so a
58
+ `finally` hook gating on `context.executionPhase` sees `'finally'` rather than
59
+ the runner's seed value.
package/package.json CHANGED
@@ -1,53 +1,59 @@
1
- {
2
- "name": "@db-lyon/flowkit",
3
- "version": "0.13.0",
4
- "description": "YAML-configured task and flow orchestration engine",
5
- "type": "module",
6
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
- "exports": {
9
- ".": "./dist/index.js",
10
- "./config": "./dist/config/index.js",
11
- "./task": "./dist/task/index.js",
12
- "./flow": "./dist/flow/index.js",
13
- "./dag": "./dist/dag/index.js"
14
- },
15
- "files": [
16
- "/dist",
17
- "/docs"
18
- ],
19
- "scripts": {
20
- "build": "tsc -b",
21
- "prepublishOnly": "tsc -b",
22
- "test": "vitest run",
23
- "test:package-api": "npm run build && tsc -p test/tsconfig.public-api.json",
24
- "test:watch": "vitest"
25
- },
26
- "keywords": [
27
- "task",
28
- "flow",
29
- "orchestration",
30
- "yaml",
31
- "pipeline",
32
- "engine"
33
- ],
34
- "author": "David Lyon",
35
- "license": "MIT",
36
- "repository": {
37
- "type": "git",
38
- "url": "https://github.com/db-lyon/flowkit.git"
39
- },
40
- "dependencies": {
41
- "js-yaml": "^4",
42
- "zod": "^3"
43
- },
44
- "devDependencies": {
45
- "@types/js-yaml": "^4",
46
- "@types/node": "^20",
47
- "typescript": "^5",
48
- "vitest": "^2"
49
- },
50
- "engines": {
51
- "node": ">=20"
52
- }
53
- }
1
+ {
2
+ "name": "@db-lyon/flowkit",
3
+ "version": "0.15.0",
4
+ "description": "YAML-configured task and flow orchestration engine",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": "./dist/index.js",
10
+ "./config": "./dist/config/index.js",
11
+ "./task": "./dist/task/index.js",
12
+ "./flow": "./dist/flow/index.js",
13
+ "./dag": "./dist/dag/index.js",
14
+ "./guard": "./dist/guard/index.js"
15
+ },
16
+ "files": [
17
+ "/dist",
18
+ "/docs"
19
+ ],
20
+ "scripts": {
21
+ "build": "tsc -b",
22
+ "prepublishOnly": "tsc -b",
23
+ "test": "vitest run",
24
+ "test:package-api": "npm run build && tsc -p test/tsconfig.public-api.json",
25
+ "test:types": "tsc -p test/tsconfig.json",
26
+ "test:watch": "vitest"
27
+ },
28
+ "keywords": [
29
+ "task",
30
+ "flow",
31
+ "orchestration",
32
+ "yaml",
33
+ "pipeline",
34
+ "engine"
35
+ ],
36
+ "author": "David Lyon",
37
+ "license": "MIT",
38
+ "repository": {
39
+ "type": "git",
40
+ "url": "git+https://github.com/db-lyon/flowkit.git"
41
+ },
42
+ "dependencies": {
43
+ "js-yaml": "^4",
44
+ "zod": "^3"
45
+ },
46
+ "devDependencies": {
47
+ "@types/js-yaml": "^4",
48
+ "@types/node": "^20",
49
+ "typescript": "^5",
50
+ "vitest": "^3.2.7"
51
+ },
52
+ "overrides": {
53
+ "postcss": "8.5.26",
54
+ "vite": "6.4.3"
55
+ },
56
+ "engines": {
57
+ "node": ">=20"
58
+ }
59
+ }
@@ -1,39 +0,0 @@
1
- /**
2
- * Reference resolution for flow option values.
3
- *
4
- * Option values may contain `${<namespace>.<path>}` references:
5
- *
6
- * ${steps.<id>.<path>} → value from a previously completed step
7
- * ${error.<path>} → error info, only inside on_failure / finally hooks
8
- * ${<ns>.<path>} → value from a host-supplied namespace in `namespaces`
9
- * (e.g. ${project.package.namespace}, ${org.username},
10
- * ${env.HOME}) — the resolver is namespace-extensible,
11
- * symmetric with the pluggable `conditionEvaluator`.
12
- *
13
- * For `steps`, `<id>` is a step number ("3") or a task name ("level.place_actor");
14
- * task names match the most recently completed step with that name, longest-prefix
15
- * wins. A reference filling the entire string yields the raw value (preserving
16
- * object/array/number types); embedded references are stringified. A reference to
17
- * an UNREGISTERED namespace is left untouched (so non-reference `${...}` survives).
18
- */
19
- export interface ReferenceableStep {
20
- stepNumber: number;
21
- name: string;
22
- result?: {
23
- data?: unknown;
24
- };
25
- }
26
- export interface ReferenceContext {
27
- steps: ReferenceableStep[];
28
- /** Present inside on_failure / finally hooks. */
29
- error?: {
30
- message: string;
31
- name: string;
32
- stack?: string;
33
- step?: string;
34
- };
35
- /** Host-supplied reference namespaces, e.g. { project, org, env }. */
36
- namespaces?: Record<string, unknown>;
37
- }
38
- export declare function resolveReferences<T>(value: T, ctx: ReferenceContext): T;
39
- //# sourceMappingURL=references.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"references.d.ts","sourceRoot":"","sources":["../../src/flow/references.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE;QAAE,IAAI,CAAC,EAAE,OAAO,CAAA;KAAE,CAAC;CAC7B;AAED,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,iDAAiD;IACjD,KAAK,CAAC,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACzE,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAQD,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,gBAAgB,GAAG,CAAC,CAcvE"}
@@ -1,102 +0,0 @@
1
- /**
2
- * Reference resolution for flow option values.
3
- *
4
- * Option values may contain `${<namespace>.<path>}` references:
5
- *
6
- * ${steps.<id>.<path>} → value from a previously completed step
7
- * ${error.<path>} → error info, only inside on_failure / finally hooks
8
- * ${<ns>.<path>} → value from a host-supplied namespace in `namespaces`
9
- * (e.g. ${project.package.namespace}, ${org.username},
10
- * ${env.HOME}) — the resolver is namespace-extensible,
11
- * symmetric with the pluggable `conditionEvaluator`.
12
- *
13
- * For `steps`, `<id>` is a step number ("3") or a task name ("level.place_actor");
14
- * task names match the most recently completed step with that name, longest-prefix
15
- * wins. A reference filling the entire string yields the raw value (preserving
16
- * object/array/number types); embedded references are stringified. A reference to
17
- * an UNREGISTERED namespace is left untouched (so non-reference `${...}` survives).
18
- */
19
- /** Returned by resolveRef when the namespace is unregistered — leave the text literal. */
20
- const LITERAL = Symbol('literal');
21
- const WHOLE_VALUE = /^\$\{(\w+)\.([^}]+)\}$/;
22
- const EMBEDDED = /\$\{(\w+)\.([^}]+)\}/g;
23
- export function resolveReferences(value, ctx) {
24
- if (value == null)
25
- return value;
26
- if (typeof value === 'string')
27
- return resolveString(value, ctx);
28
- if (Array.isArray(value)) {
29
- return value.map((v) => resolveReferences(v, ctx));
30
- }
31
- if (typeof value === 'object') {
32
- const out = {};
33
- for (const [k, v] of Object.entries(value)) {
34
- out[k] = resolveReferences(v, ctx);
35
- }
36
- return out;
37
- }
38
- return value;
39
- }
40
- function resolveString(str, ctx) {
41
- const whole = str.match(WHOLE_VALUE);
42
- if (whole) {
43
- const v = resolveRef(whole[1], whole[2], ctx);
44
- return v === LITERAL ? str : v;
45
- }
46
- if (!str.includes('${'))
47
- return str;
48
- return str.replace(EMBEDDED, (match, ns, ref) => {
49
- const v = resolveRef(ns, ref, ctx);
50
- if (v === LITERAL)
51
- return match; // unregistered namespace — leave untouched
52
- if (v == null)
53
- return '';
54
- if (typeof v === 'object')
55
- return JSON.stringify(v);
56
- return String(v);
57
- });
58
- }
59
- function resolveRef(namespace, ref, ctx) {
60
- if (namespace === 'error') {
61
- if (!ctx.error) {
62
- throw new Error(`\${error.${ref}} referenced outside on_failure/finally — error context not available`);
63
- }
64
- return getPath(ctx.error, ref.split('.'));
65
- }
66
- if (namespace === 'steps') {
67
- const segments = ref.split('.');
68
- for (let i = segments.length; i >= 1; i--) {
69
- const idCandidate = segments.slice(0, i).join('.');
70
- const match = findStep(idCandidate, ctx.steps);
71
- if (match)
72
- return getPath(match.result?.data, segments.slice(i));
73
- }
74
- throw new Error(`Unresolvable step reference: \${steps.${ref}} — no completed step matches "${segments[0]}"`);
75
- }
76
- // Host-supplied namespace (project / org / env / ...). Missing path → undefined.
77
- if (ctx.namespaces && namespace in ctx.namespaces) {
78
- return getPath(ctx.namespaces[namespace], ref.split('.'));
79
- }
80
- // Unregistered namespace — not a reference we own; leave it literal.
81
- return LITERAL;
82
- }
83
- function findStep(id, steps) {
84
- if (/^\d+$/.test(id)) {
85
- return steps.find((s) => String(s.stepNumber) === id);
86
- }
87
- for (let i = steps.length - 1; i >= 0; i--) {
88
- if (steps[i].name === id)
89
- return steps[i];
90
- }
91
- return undefined;
92
- }
93
- function getPath(obj, segments) {
94
- let cur = obj;
95
- for (const seg of segments) {
96
- if (cur == null || typeof cur !== 'object')
97
- return undefined;
98
- cur = cur[seg];
99
- }
100
- return cur;
101
- }
102
- //# sourceMappingURL=references.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"references.js","sourceRoot":"","sources":["../../src/flow/references.ts"],"names":[],"mappings":"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"}