@wpkernel/pipeline 0.12.1-beta.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.
- package/LICENSE +287 -0
- package/README.md +110 -0
- package/dist/async-utils.d.ts +51 -0
- package/dist/async-utils.d.ts.map +1 -0
- package/dist/async-utils.js +39 -0
- package/dist/createExtension.d.ts +164 -0
- package/dist/createExtension.d.ts.map +1 -0
- package/dist/createExtension.js +23 -0
- package/dist/createPipeline.d.ts +205 -0
- package/dist/createPipeline.d.ts.map +1 -0
- package/dist/createPipeline.js +447 -0
- package/dist/dependency-graph.d.ts +80 -0
- package/dist/dependency-graph.d.ts.map +1 -0
- package/dist/dependency-graph.js +105 -0
- package/dist/error-factory.d.ts +46 -0
- package/dist/error-factory.d.ts.map +1 -0
- package/dist/error-factory.js +15 -0
- package/dist/executor.d.ts +24 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +40 -0
- package/dist/extensions.d.ts +83 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +88 -0
- package/dist/helper.d.ts +134 -0
- package/dist/helper.d.ts.map +1 -0
- package/dist/helper.js +29 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +31 -0
- package/dist/registration.d.ts +44 -0
- package/dist/registration.d.ts.map +1 -0
- package/dist/registration.js +50 -0
- package/dist/types.d.ts +332 -0
- package/dist/types.d.ts.map +1 -0
- package/package.json +41 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { HelperDescriptor, HelperKind } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* A registered helper with its unique identifier and registration index.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export interface RegisteredHelper<THelper> {
|
|
8
|
+
readonly helper: THelper;
|
|
9
|
+
readonly id: string;
|
|
10
|
+
readonly index: number;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Describes a helper that depends on a missing helper key.
|
|
14
|
+
*
|
|
15
|
+
* @internal
|
|
16
|
+
*/
|
|
17
|
+
export interface MissingDependencyIssue<THelper> {
|
|
18
|
+
readonly dependant: RegisteredHelper<THelper>;
|
|
19
|
+
readonly dependencyKey: string;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Options for dependency graph creation.
|
|
23
|
+
*
|
|
24
|
+
* @internal
|
|
25
|
+
*/
|
|
26
|
+
export interface CreateDependencyGraphOptions<THelper> {
|
|
27
|
+
readonly onMissingDependency?: (issue: MissingDependencyIssue<THelper>) => void;
|
|
28
|
+
readonly onUnresolvedHelpers?: (options: {
|
|
29
|
+
readonly unresolved: RegisteredHelper<THelper>[];
|
|
30
|
+
}) => void;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Creates a unique identifier for a registered helper.
|
|
34
|
+
*
|
|
35
|
+
* Format: `{kind}:{key}#{index}`
|
|
36
|
+
*
|
|
37
|
+
* @param helper - The helper descriptor
|
|
38
|
+
* @param helper.kind
|
|
39
|
+
* @param index - The registration index
|
|
40
|
+
* @param helper.key
|
|
41
|
+
* @returns A unique string identifier
|
|
42
|
+
*
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
export declare function createHelperId(helper: {
|
|
46
|
+
kind: HelperKind;
|
|
47
|
+
key: string;
|
|
48
|
+
}, index: number): string;
|
|
49
|
+
/**
|
|
50
|
+
* Comparator for sorting helpers by priority, key, then registration index.
|
|
51
|
+
*
|
|
52
|
+
* Higher priority comes first. If equal, sorts by key alphabetically.
|
|
53
|
+
* If still equal, sorts by registration index.
|
|
54
|
+
*
|
|
55
|
+
* @param a - First helper entry
|
|
56
|
+
* @param b - Second helper entry
|
|
57
|
+
* @returns Negative if a < b, positive if a > b, zero if equal
|
|
58
|
+
*
|
|
59
|
+
* @internal
|
|
60
|
+
*/
|
|
61
|
+
export declare function compareHelpers<THelper extends HelperDescriptor>(a: RegisteredHelper<THelper>, b: RegisteredHelper<THelper>): number;
|
|
62
|
+
/**
|
|
63
|
+
* Creates a dependency graph and returns the topological order.
|
|
64
|
+
*
|
|
65
|
+
* Validates that all dependencies exist and that there are no circular dependencies.
|
|
66
|
+
* Throws `WPKernelError` if validation fails.
|
|
67
|
+
*
|
|
68
|
+
* @param entries - All registered helpers with their IDs
|
|
69
|
+
* @param options - Optional callbacks for diagnostic reporting
|
|
70
|
+
* @param createError - Error factory function
|
|
71
|
+
* @returns Ordered helpers and the adjacency list
|
|
72
|
+
* @throws {Error} If dependencies are missing or unresolved
|
|
73
|
+
*
|
|
74
|
+
* @internal
|
|
75
|
+
*/
|
|
76
|
+
export declare function createDependencyGraph<THelper extends HelperDescriptor>(entries: RegisteredHelper<THelper>[], options: CreateDependencyGraphOptions<THelper> | undefined, createError: (code: string, message: string) => Error): {
|
|
77
|
+
order: RegisteredHelper<THelper>[];
|
|
78
|
+
adjacency: Map<string, Set<string>>;
|
|
79
|
+
};
|
|
80
|
+
//# sourceMappingURL=dependency-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dependency-graph.d.ts","sourceRoot":"","sources":["../src/dependency-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAE5D;;;;GAIG;AACH,MAAM,WAAW,gBAAgB,CAAC,OAAO;IACxC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAaD;;;;GAIG;AACH,MAAM,WAAW,sBAAsB,CAAC,OAAO;IAC9C,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC,OAAO,CAAC,CAAC;IAC9C,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;CAC/B;AAED;;;;GAIG;AACH,MAAM,WAAW,4BAA4B,CAAC,OAAO;IACpD,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAC9B,KAAK,EAAE,sBAAsB,CAAC,OAAO,CAAC,KAClC,IAAI,CAAC;IACV,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC,OAAO,EAAE;QACxC,QAAQ,CAAC,UAAU,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;KACjD,KAAK,IAAI,CAAC;CACX;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,cAAc,CAC7B,MAAM,EAAE;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,EACzC,KAAK,EAAE,MAAM,GACX,MAAM,CAER;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,OAAO,SAAS,gBAAgB,EAC9D,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAC5B,CAAC,EAAE,gBAAgB,CAAC,OAAO,CAAC,GAC1B,MAAM,CAUR;AA2KD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,SAAS,gBAAgB,EACrE,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,EACpC,OAAO,EAAE,4BAA4B,CAAC,OAAO,CAAC,GAAG,SAAS,EAC1D,WAAW,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,KAAK,GACnD;IACF,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;IACnC,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC;CACpC,CA4CA"}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wpkernel/pipeline
|
|
3
|
+
* @license EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
function k(n, e) {
|
|
6
|
+
return `${n.kind}:${n.key}#${e}`;
|
|
7
|
+
}
|
|
8
|
+
function f(n, e) {
|
|
9
|
+
return n.helper.priority !== e.helper.priority ? e.helper.priority - n.helper.priority : n.helper.key !== e.helper.key ? n.helper.key.localeCompare(e.helper.key) : n.index - e.index;
|
|
10
|
+
}
|
|
11
|
+
function y(n) {
|
|
12
|
+
const e = /* @__PURE__ */ new Map(), o = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map();
|
|
13
|
+
for (const t of n)
|
|
14
|
+
e.set(t.id, /* @__PURE__ */ new Set()), o.set(t.id, 0), r.set(t.id, t);
|
|
15
|
+
return { adjacency: e, indegree: o, entryById: r };
|
|
16
|
+
}
|
|
17
|
+
function a(n, e) {
|
|
18
|
+
const o = [];
|
|
19
|
+
for (const r of n)
|
|
20
|
+
for (const t of r.helper.dependsOn)
|
|
21
|
+
h(
|
|
22
|
+
n,
|
|
23
|
+
e,
|
|
24
|
+
t,
|
|
25
|
+
r.id
|
|
26
|
+
) || o.push({
|
|
27
|
+
dependant: r,
|
|
28
|
+
dependencyKey: t
|
|
29
|
+
});
|
|
30
|
+
return o;
|
|
31
|
+
}
|
|
32
|
+
function h(n, e, o, r) {
|
|
33
|
+
const t = n.filter(
|
|
34
|
+
({ helper: c }) => c.key === o
|
|
35
|
+
);
|
|
36
|
+
if (t.length === 0)
|
|
37
|
+
return !1;
|
|
38
|
+
for (const c of t) {
|
|
39
|
+
const d = e.adjacency.get(c.id);
|
|
40
|
+
if (!d)
|
|
41
|
+
continue;
|
|
42
|
+
d.add(r);
|
|
43
|
+
const i = e.indegree.get(r) ?? 0;
|
|
44
|
+
e.indegree.set(r, i + 1);
|
|
45
|
+
}
|
|
46
|
+
return !0;
|
|
47
|
+
}
|
|
48
|
+
function g(n, e) {
|
|
49
|
+
const o = n.filter(
|
|
50
|
+
(i) => (e.indegree.get(i.id) ?? 0) === 0
|
|
51
|
+
);
|
|
52
|
+
o.sort(f);
|
|
53
|
+
const r = [], t = new Map(e.indegree), c = /* @__PURE__ */ new Set();
|
|
54
|
+
for (; o.length > 0; ) {
|
|
55
|
+
const i = o.shift();
|
|
56
|
+
if (!i)
|
|
57
|
+
break;
|
|
58
|
+
r.push(i), c.add(i.id);
|
|
59
|
+
const u = e.adjacency.get(i.id);
|
|
60
|
+
if (u)
|
|
61
|
+
for (const s of u) {
|
|
62
|
+
const p = (t.get(s) ?? 0) - 1;
|
|
63
|
+
if (t.set(s, p), p !== 0)
|
|
64
|
+
continue;
|
|
65
|
+
const l = e.entryById.get(s);
|
|
66
|
+
l && (o.push(l), o.sort(f));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const d = n.filter((i) => !c.has(i.id));
|
|
70
|
+
return { ordered: r, unresolved: d };
|
|
71
|
+
}
|
|
72
|
+
function w(n, e, o) {
|
|
73
|
+
const r = y(n), t = a(n, r);
|
|
74
|
+
if (t.length > 0) {
|
|
75
|
+
for (const s of t)
|
|
76
|
+
e?.onMissingDependency?.(s);
|
|
77
|
+
const i = /* @__PURE__ */ new Map();
|
|
78
|
+
for (const s of t) {
|
|
79
|
+
const p = s.dependant.helper.key, l = i.get(p) ?? [];
|
|
80
|
+
l.push(s.dependencyKey), i.set(p, l);
|
|
81
|
+
}
|
|
82
|
+
const u = Array.from(i.entries()).map(
|
|
83
|
+
([s, p]) => `"${s}" → [${p.map((l) => `"${l}"`).join(", ")}]`
|
|
84
|
+
).join(", ");
|
|
85
|
+
throw o(
|
|
86
|
+
"ValidationError",
|
|
87
|
+
`Helpers depend on unknown helpers: ${u}.`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const { ordered: c, unresolved: d } = g(n, r);
|
|
91
|
+
if (d.length > 0) {
|
|
92
|
+
e?.onUnresolvedHelpers?.({ unresolved: d });
|
|
93
|
+
const i = d.map((u) => u.helper.key);
|
|
94
|
+
throw o(
|
|
95
|
+
"ValidationError",
|
|
96
|
+
`Detected unresolved pipeline helpers: ${i.join(", ")}.`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
return { order: c, adjacency: r.adjacency };
|
|
100
|
+
}
|
|
101
|
+
export {
|
|
102
|
+
f as compareHelpers,
|
|
103
|
+
w as createDependencyGraph,
|
|
104
|
+
k as createHelperId
|
|
105
|
+
};
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Factory function for creating errors.
|
|
3
|
+
* Allows the pipeline to be framework-agnostic.
|
|
4
|
+
*
|
|
5
|
+
* @param code - Error code (e.g., 'ValidationError', 'RuntimeError')
|
|
6
|
+
* @param message - Error message
|
|
7
|
+
* @returns An Error instance
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const createError = (code: string, message: string) =>
|
|
12
|
+
* new MyCustomError(code, { message });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export type ErrorFactory = (code: string, message: string) => Error;
|
|
16
|
+
/**
|
|
17
|
+
* Default error factory that creates standard Error instances with code property.
|
|
18
|
+
*
|
|
19
|
+
* @param code - Error code
|
|
20
|
+
* @param message - Error message
|
|
21
|
+
* @returns A standard Error with code property and prefixed message
|
|
22
|
+
*/
|
|
23
|
+
export declare function createDefaultError(code: string, message: string): Error;
|
|
24
|
+
/**
|
|
25
|
+
* Creates an error factory that wraps a custom error class.
|
|
26
|
+
*
|
|
27
|
+
* @param ErrorClass - Custom error class constructor
|
|
28
|
+
* @param create
|
|
29
|
+
* @returns An error factory function
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* ```typescript
|
|
33
|
+
* class WPKernelError extends Error {
|
|
34
|
+
* constructor(code: string, options: { message: string }) {
|
|
35
|
+
* super(options.message);
|
|
36
|
+
* this.name = code;
|
|
37
|
+
* }
|
|
38
|
+
* }
|
|
39
|
+
*
|
|
40
|
+
* const createError = createErrorFactory(
|
|
41
|
+
* (code, message) => new WPKernelError(code, { message })
|
|
42
|
+
* );
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function createErrorFactory(create: (code: string, message: string) => Error): ErrorFactory;
|
|
46
|
+
//# sourceMappingURL=error-factory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error-factory.d.ts","sourceRoot":"","sources":["../src/error-factory.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,YAAY,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,KAAK,CAAC;AAEpE;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,KAAK,CAIvE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,KAAK,GAC9C,YAAY,CAEd"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Helper, HelperApplyOptions, HelperKind, MaybePromise, PipelineReporter } from './types';
|
|
2
|
+
import { RegisteredHelper } from './dependency-graph';
|
|
3
|
+
/**
|
|
4
|
+
* Executes an ordered list of helpers sequentially with middleware-style `next()` support.
|
|
5
|
+
*
|
|
6
|
+
* Each helper can optionally call `next()` to explicitly control when the next helper runs.
|
|
7
|
+
* If a helper doesn't call `next()`, execution continues automatically after the helper completes
|
|
8
|
+
* (for async helpers, after the promise resolves).
|
|
9
|
+
*
|
|
10
|
+
* This enables middleware patterns where helpers can:
|
|
11
|
+
* - Pre-process before calling next()
|
|
12
|
+
* - Post-process after next() returns
|
|
13
|
+
* - Control timing of downstream execution via explicit next() calls
|
|
14
|
+
*
|
|
15
|
+
* @param ordered - Topologically sorted helpers to execute
|
|
16
|
+
* @param makeArgs - Factory function to create arguments for each helper
|
|
17
|
+
* @param invoke - Function that invokes a helper with its arguments and next callback
|
|
18
|
+
* @param recordStep - Callback invoked when a helper starts executing (for diagnostics)
|
|
19
|
+
* @returns Set of visited helper IDs
|
|
20
|
+
*
|
|
21
|
+
* @internal
|
|
22
|
+
*/
|
|
23
|
+
export declare function executeHelpers<TContext, TInput, TOutput, TReporter extends PipelineReporter, TKind extends HelperKind, THelper extends Helper<TContext, TInput, TOutput, TReporter, TKind>, TArgs extends HelperApplyOptions<TContext, TInput, TOutput, TReporter>>(ordered: RegisteredHelper<THelper>[], makeArgs: (entry: RegisteredHelper<THelper>) => TArgs, invoke: (helper: THelper, args: TArgs, next: () => MaybePromise<void>) => MaybePromise<void>, recordStep: (entry: RegisteredHelper<THelper>) => void): MaybePromise<Set<string>>;
|
|
24
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,MAAM,EACN,kBAAkB,EAClB,UAAU,EACV,YAAY,EACZ,gBAAgB,EAChB,MAAM,SAAS,CAAC;AACjB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG3D;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAC7B,QAAQ,EACR,MAAM,EACN,OAAO,EACP,SAAS,SAAS,gBAAgB,EAClC,KAAK,SAAS,UAAU,EACxB,OAAO,SAAS,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,EACnE,KAAK,SAAS,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,CAAC,EAEtE,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,EAAE,EACpC,QAAQ,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,KAAK,EACrD,MAAM,EAAE,CACP,MAAM,EAAE,OAAO,EACf,IAAI,EAAE,KAAK,EACX,IAAI,EAAE,MAAM,YAAY,CAAC,IAAI,CAAC,KAC1B,YAAY,CAAC,IAAI,CAAC,EACvB,UAAU,EAAE,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,IAAI,GACpD,YAAY,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CA0E3B"}
|
package/dist/executor.js
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { isPromiseLike as u } from "./async-utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* @wpkernel/pipeline
|
|
4
|
+
* @license EUPL-1.2
|
|
5
|
+
*/
|
|
6
|
+
function g(s, a, v, h) {
|
|
7
|
+
const o = /* @__PURE__ */ new Set();
|
|
8
|
+
function m(t) {
|
|
9
|
+
const e = r(t);
|
|
10
|
+
return u(e) ? Promise.resolve(e).then(() => {
|
|
11
|
+
}) : Promise.resolve();
|
|
12
|
+
}
|
|
13
|
+
function r(t) {
|
|
14
|
+
if (t >= s.length)
|
|
15
|
+
return;
|
|
16
|
+
const e = s[t];
|
|
17
|
+
if (!e || o.has(e.id))
|
|
18
|
+
return r(t + 1);
|
|
19
|
+
o.add(e.id), h(e);
|
|
20
|
+
let i = !1, n;
|
|
21
|
+
const p = a(e), P = () => {
|
|
22
|
+
if (i)
|
|
23
|
+
return n;
|
|
24
|
+
i = !0;
|
|
25
|
+
const l = r(t + 1);
|
|
26
|
+
return n = l, l;
|
|
27
|
+
}, f = v(e.helper, p, P);
|
|
28
|
+
return u(f) ? Promise.resolve(f).then(() => {
|
|
29
|
+
if (!i)
|
|
30
|
+
return m(t + 1);
|
|
31
|
+
if (u(n))
|
|
32
|
+
return n;
|
|
33
|
+
}) : i ? n : r(t + 1);
|
|
34
|
+
}
|
|
35
|
+
const c = r(0);
|
|
36
|
+
return u(c) ? c.then(() => o) : o;
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
g as executeHelpers
|
|
40
|
+
};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { MaybePromise, PipelineExtensionHook, PipelineExtensionHookOptions, PipelineExtensionHookResult, PipelineExtensionRollbackErrorMetadata } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* An extension hook entry with its unique key.
|
|
4
|
+
*
|
|
5
|
+
* @internal
|
|
6
|
+
*/
|
|
7
|
+
export interface ExtensionHookEntry<TContext, TOptions, TArtifact> {
|
|
8
|
+
readonly key: string;
|
|
9
|
+
readonly hook: PipelineExtensionHook<TContext, TOptions, TArtifact>;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The result of executing an extension hook.
|
|
13
|
+
*
|
|
14
|
+
* @internal
|
|
15
|
+
*/
|
|
16
|
+
export interface ExtensionHookExecution<TContext, TOptions, TArtifact> {
|
|
17
|
+
readonly hook: ExtensionHookEntry<TContext, TOptions, TArtifact>;
|
|
18
|
+
readonly result: PipelineExtensionHookResult<TArtifact>;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Arguments passed to the rollback error handler.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
export interface RollbackErrorArgs {
|
|
26
|
+
readonly error: unknown;
|
|
27
|
+
readonly extensionKeys: readonly string[];
|
|
28
|
+
readonly hookSequence: readonly string[];
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Converts an error into a serializable metadata object.
|
|
32
|
+
*
|
|
33
|
+
* Extracts `name`, `message`, `stack`, and `cause` from Error instances.
|
|
34
|
+
* Falls back to a plain message string for non-Error values.
|
|
35
|
+
*
|
|
36
|
+
* @param error - The error to convert
|
|
37
|
+
* @returns Serializable error metadata
|
|
38
|
+
*
|
|
39
|
+
* @internal
|
|
40
|
+
*/
|
|
41
|
+
export declare function createRollbackErrorMetadata(error: unknown): PipelineExtensionRollbackErrorMetadata;
|
|
42
|
+
/**
|
|
43
|
+
* Runs extension hooks sequentially and accumulates their results.
|
|
44
|
+
*
|
|
45
|
+
* Each hook can optionally transform the artifact and return commit/rollback functions.
|
|
46
|
+
* If any hook throws, automatically rolls back all previously executed hooks in reverse order.
|
|
47
|
+
*
|
|
48
|
+
* @param hooks - The extension hooks to run
|
|
49
|
+
* @param options - Context, options, and initial artifact
|
|
50
|
+
* @param onRollbackError - Callback invoked if a rollback itself fails
|
|
51
|
+
* @returns The final artifact and all hook execution results
|
|
52
|
+
*
|
|
53
|
+
* @internal
|
|
54
|
+
*/
|
|
55
|
+
export declare function runExtensionHooks<TContext, TOptions, TArtifact>(hooks: readonly ExtensionHookEntry<TContext, TOptions, TArtifact>[], options: PipelineExtensionHookOptions<TContext, TOptions, TArtifact>, onRollbackError: (args: RollbackErrorArgs) => void): MaybePromise<{
|
|
56
|
+
artifact: TArtifact;
|
|
57
|
+
results: ExtensionHookExecution<TContext, TOptions, TArtifact>[];
|
|
58
|
+
}>;
|
|
59
|
+
/**
|
|
60
|
+
* Commits all extension hook results by invoking their commit functions.
|
|
61
|
+
*
|
|
62
|
+
* Processes commits sequentially in the order hooks were executed.
|
|
63
|
+
*
|
|
64
|
+
* @param results - The hook execution results
|
|
65
|
+
* @returns A promise if any commit is async, otherwise `void`
|
|
66
|
+
*
|
|
67
|
+
* @internal
|
|
68
|
+
*/
|
|
69
|
+
export declare function commitExtensionResults<TContext, TOptions, TArtifact>(results: readonly ExtensionHookExecution<TContext, TOptions, TArtifact>[]): MaybePromise<void>;
|
|
70
|
+
/**
|
|
71
|
+
* Rolls back extension hook results by invoking their rollback functions in reverse order.
|
|
72
|
+
*
|
|
73
|
+
* If a rollback itself fails, calls `onRollbackError` but continues rolling back remaining hooks.
|
|
74
|
+
*
|
|
75
|
+
* @param results - The hook execution results to roll back
|
|
76
|
+
* @param hooks - The original hook entries (for error context)
|
|
77
|
+
* @param onRollbackError - Callback invoked if a rollback fails
|
|
78
|
+
* @returns A promise if any rollback is async, otherwise `void`
|
|
79
|
+
*
|
|
80
|
+
* @internal
|
|
81
|
+
*/
|
|
82
|
+
export declare function rollbackExtensionResults<TContext, TOptions, TArtifact>(results: readonly ExtensionHookExecution<TContext, TOptions, TArtifact>[], hooks: readonly ExtensionHookEntry<TContext, TOptions, TArtifact>[], onRollbackError: (args: RollbackErrorArgs) => void): MaybePromise<void>;
|
|
83
|
+
//# sourceMappingURL=extensions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"extensions.d.ts","sourceRoot":"","sources":["../src/extensions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,YAAY,EACZ,qBAAqB,EACrB,4BAA4B,EAC5B,2BAA2B,EAC3B,sCAAsC,EACtC,MAAM,SAAS,CAAC;AAQjB;;;;GAIG;AACH,MAAM,WAAW,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS;IAChE,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;CACpE;AAED;;;;GAIG;AACH,MAAM,WAAW,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS;IACpE,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IACjE,QAAQ,CAAC,MAAM,EAAE,2BAA2B,CAAC,SAAS,CAAC,CAAC;CACxD;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,aAAa,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1C,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAC;CACzC;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,2BAA2B,CAC1C,KAAK,EAAE,OAAO,GACZ,sCAAsC,CAoBxC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EAC9D,KAAK,EAAE,SAAS,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,EACnE,OAAO,EAAE,4BAA4B,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EACpE,eAAe,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,GAChD,YAAY,CAAC;IACf,QAAQ,EAAE,SAAS,CAAC;IACpB,OAAO,EAAE,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,CAAC;CACjE,CAAC,CAuDD;AAED;;;;;;;;;GASG;AACH,wBAAgB,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EACnE,OAAO,EAAE,SAAS,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,GACvE,YAAY,CAAC,IAAI,CAAC,CAcpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,EACrE,OAAO,EAAE,SAAS,sBAAsB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,EACzE,KAAK,EAAE,SAAS,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,EAAE,EACnE,eAAe,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,GAChD,YAAY,CAAC,IAAI,CAAC,CA2BpB"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { maybeTry as m, maybeThen as l, processSequentially as f, isPromiseLike as k } from "./async-utils.js";
|
|
2
|
+
/**
|
|
3
|
+
* @wpkernel/pipeline
|
|
4
|
+
* @license EUPL-1.2
|
|
5
|
+
*/
|
|
6
|
+
function p(t) {
|
|
7
|
+
if (t instanceof Error) {
|
|
8
|
+
const { name: o, message: r, stack: e } = t, n = t.cause;
|
|
9
|
+
return {
|
|
10
|
+
name: o,
|
|
11
|
+
message: r,
|
|
12
|
+
stack: e,
|
|
13
|
+
cause: n
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
return typeof t == "string" ? {
|
|
17
|
+
message: t
|
|
18
|
+
} : {};
|
|
19
|
+
}
|
|
20
|
+
function v(t, o, r) {
|
|
21
|
+
let e = o.artifact;
|
|
22
|
+
const n = [], u = m(
|
|
23
|
+
() => f(t, (s) => {
|
|
24
|
+
const i = s.hook({
|
|
25
|
+
context: o.context,
|
|
26
|
+
options: o.options,
|
|
27
|
+
artifact: e
|
|
28
|
+
});
|
|
29
|
+
if (k(i))
|
|
30
|
+
return Promise.resolve(i).then((a) => {
|
|
31
|
+
a && (a.artifact !== void 0 && (e = a.artifact), n.push({
|
|
32
|
+
hook: s,
|
|
33
|
+
result: a
|
|
34
|
+
}));
|
|
35
|
+
});
|
|
36
|
+
if (i)
|
|
37
|
+
return i.artifact !== void 0 && (e = i.artifact), void n.push({
|
|
38
|
+
hook: s,
|
|
39
|
+
result: i
|
|
40
|
+
});
|
|
41
|
+
}),
|
|
42
|
+
(s) => l(
|
|
43
|
+
h(n, t, r),
|
|
44
|
+
() => {
|
|
45
|
+
throw s;
|
|
46
|
+
}
|
|
47
|
+
)
|
|
48
|
+
);
|
|
49
|
+
return l(u, () => ({ artifact: e, results: n }));
|
|
50
|
+
}
|
|
51
|
+
function x(t) {
|
|
52
|
+
return f(t, (o) => {
|
|
53
|
+
const r = o.result.commit;
|
|
54
|
+
if (!r)
|
|
55
|
+
return;
|
|
56
|
+
const e = r();
|
|
57
|
+
if (k(e))
|
|
58
|
+
return e.then(() => {
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function h(t, o, r) {
|
|
63
|
+
const e = o.map((c) => c.key), n = e;
|
|
64
|
+
return f(
|
|
65
|
+
[...t].reverse(),
|
|
66
|
+
(c) => {
|
|
67
|
+
const u = c.result.rollback;
|
|
68
|
+
if (u)
|
|
69
|
+
return m(
|
|
70
|
+
() => u(),
|
|
71
|
+
(s) => {
|
|
72
|
+
r({
|
|
73
|
+
error: s,
|
|
74
|
+
extensionKeys: e,
|
|
75
|
+
hookSequence: n
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
},
|
|
80
|
+
"forward"
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
x as commitExtensionResults,
|
|
85
|
+
p as createRollbackErrorMetadata,
|
|
86
|
+
h as rollbackExtensionResults,
|
|
87
|
+
v as runExtensionHooks
|
|
88
|
+
};
|
package/dist/helper.d.ts
ADDED
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { CreateHelperOptions, Helper, HelperKind, PipelineReporter } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Creates a pipeline helper-the fundamental building block of WPKernel's code generation system.
|
|
4
|
+
*
|
|
5
|
+
* ## Overview
|
|
6
|
+
*
|
|
7
|
+
* Helpers are composable, dependency-aware transformation units that power the entire framework:
|
|
8
|
+
* - **CLI package**: Generates PHP resources, actions, blocks, and bindings via helper chains
|
|
9
|
+
* - **PHP Driver**: Transforms PHP AST nodes through fragment helpers
|
|
10
|
+
* - **Core**: Orchestrates resource definitions and action middleware
|
|
11
|
+
*
|
|
12
|
+
* Each helper is a pure, immutable descriptor that declares:
|
|
13
|
+
* - **What it does**: Fragment transformations or artifact building
|
|
14
|
+
* - **When it runs**: Priority ordering and dependency relationships
|
|
15
|
+
* - **How it integrates**: Mode (extend/replace/before/after) and rollback behavior
|
|
16
|
+
*
|
|
17
|
+
* ## Key Concepts
|
|
18
|
+
*
|
|
19
|
+
* ### Helper Kinds
|
|
20
|
+
* - `fragment`: Modifies AST nodes in-place (e.g., add PHP opening tag, inject imports)
|
|
21
|
+
* - `builder`: Produces final artifacts from fragments (e.g., write files, format code)
|
|
22
|
+
*
|
|
23
|
+
* ### Execution Modes
|
|
24
|
+
* - `extend` (default): Add to existing transformations; multiple helpers with same key can coexist
|
|
25
|
+
* - `override`: Only one override helper per key is allowed; prevents duplicate override registrations
|
|
26
|
+
*
|
|
27
|
+
* Note: Mode primarily affects registration validation. For execution ordering, use `priority` and `dependsOn`.
|
|
28
|
+
*
|
|
29
|
+
* ### Dependency Resolution
|
|
30
|
+
* The pipeline automatically:
|
|
31
|
+
* - Topologically sorts helpers based on `dependsOn` declarations
|
|
32
|
+
* - Validates dependency chains and reports missing/circular dependencies
|
|
33
|
+
* - Ensures helpers run in correct order regardless of registration sequence
|
|
34
|
+
*
|
|
35
|
+
* ## Architecture
|
|
36
|
+
*
|
|
37
|
+
* Helpers form directed acyclic graphs (DAGs) where each node represents a transformation
|
|
38
|
+
* and edges represent dependencies. The pipeline executes helpers in topological order,
|
|
39
|
+
* ensuring all dependencies complete before dependent helpers run.
|
|
40
|
+
*
|
|
41
|
+
* This design enables:
|
|
42
|
+
* - **Composability**: Combine helpers from different packages without conflicts
|
|
43
|
+
* - **Extensibility**: Third-party helpers integrate seamlessly via dependency declarations
|
|
44
|
+
* - **Reliability**: Rollback support ensures atomic operations across helper chains
|
|
45
|
+
* - **Observability**: Built-in diagnostics and reporter integration for debugging
|
|
46
|
+
*
|
|
47
|
+
* @param options
|
|
48
|
+
* @category Pipeline
|
|
49
|
+
*
|
|
50
|
+
* @example Basic fragment helper
|
|
51
|
+
* ```typescript
|
|
52
|
+
* import { createHelper } from '@wpkernel/pipeline';
|
|
53
|
+
*
|
|
54
|
+
* // Add PHP opening tag to generated files
|
|
55
|
+
* const addPHPTag = createHelper({
|
|
56
|
+
* key: 'add-php-opening-tag',
|
|
57
|
+
* kind: 'fragment',
|
|
58
|
+
* mode: 'extend',
|
|
59
|
+
* priority: 100, // Run early in pipeline
|
|
60
|
+
* origin: 'wp-kernel-core',
|
|
61
|
+
* apply: ({ fragment }) => {
|
|
62
|
+
* fragment.children.unshift({
|
|
63
|
+
* kind: 'text',
|
|
64
|
+
* text: '<?php\n',
|
|
65
|
+
* });
|
|
66
|
+
* },
|
|
67
|
+
* });
|
|
68
|
+
* ```
|
|
69
|
+
*
|
|
70
|
+
* @example Helper with dependencies
|
|
71
|
+
* ```typescript
|
|
72
|
+
* // This helper depends on namespace detection running first
|
|
73
|
+
* const addNamespaceDeclaration = createHelper({
|
|
74
|
+
* key: 'add-namespace',
|
|
75
|
+
* kind: 'fragment',
|
|
76
|
+
* dependsOn: ['detect-namespace'], // Won't run until this completes
|
|
77
|
+
* apply: ({ fragment, context }) => {
|
|
78
|
+
* const ns = context.detectedNamespace;
|
|
79
|
+
* fragment.children.push({
|
|
80
|
+
* kind: 'namespace',
|
|
81
|
+
* name: ns,
|
|
82
|
+
* });
|
|
83
|
+
* },
|
|
84
|
+
* });
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @example Builder helper with rollback
|
|
88
|
+
* ```typescript
|
|
89
|
+
* import { createPipelineCommit, createPipelineRollback } from '@wpkernel/pipeline';
|
|
90
|
+
*
|
|
91
|
+
* const writeFileHelper = createHelper({
|
|
92
|
+
* key: 'write-file',
|
|
93
|
+
* kind: 'builder',
|
|
94
|
+
* apply: ({ draft, context }) => {
|
|
95
|
+
* const path = context.outputPath;
|
|
96
|
+
* const backup = readFileSync(path, 'utf-8'); // Capture current state
|
|
97
|
+
*
|
|
98
|
+
* writeFileSync(path, draft);
|
|
99
|
+
*
|
|
100
|
+
* return {
|
|
101
|
+
* commit: createPipelineCommit(
|
|
102
|
+
* () => context.reporter.info(`Wrote ${path}`)
|
|
103
|
+
* ),
|
|
104
|
+
* rollback: createPipelineRollback(
|
|
105
|
+
* () => writeFileSync(path, backup), // Restore on error
|
|
106
|
+
* () => context.reporter.warn(`Rolled back ${path}`)
|
|
107
|
+
* ),
|
|
108
|
+
* };
|
|
109
|
+
* },
|
|
110
|
+
* });
|
|
111
|
+
* ```
|
|
112
|
+
*
|
|
113
|
+
* @example Async helper with error handling
|
|
114
|
+
* ```typescript
|
|
115
|
+
* const formatCodeHelper = createHelper({
|
|
116
|
+
* key: 'format-code',
|
|
117
|
+
* kind: 'builder',
|
|
118
|
+
* dependsOn: ['write-file'],
|
|
119
|
+
* apply: async ({ artifact, context }) => {
|
|
120
|
+
* try {
|
|
121
|
+
* const formatted = await prettier.format(artifact, {
|
|
122
|
+
* parser: 'php',
|
|
123
|
+
* });
|
|
124
|
+
* return { artifact: formatted };
|
|
125
|
+
* } catch (error) {
|
|
126
|
+
* context.reporter.error('Formatting failed', { error });
|
|
127
|
+
* throw error;
|
|
128
|
+
* }
|
|
129
|
+
* },
|
|
130
|
+
* });
|
|
131
|
+
* ```
|
|
132
|
+
*/
|
|
133
|
+
export declare function createHelper<TContext, TInput, TOutput, TReporter extends PipelineReporter = PipelineReporter, TKind extends HelperKind = HelperKind>(options: CreateHelperOptions<TContext, TInput, TOutput, TReporter, TKind>): Helper<TContext, TInput, TOutput, TReporter, TKind>;
|
|
134
|
+
//# sourceMappingURL=helper.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper.d.ts","sourceRoot":"","sources":["../src/helper.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,mBAAmB,EACnB,MAAM,EACN,UAAU,EAEV,gBAAgB,EAChB,MAAM,SAAS,CAAC;AAEjB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkIG;AACH,wBAAgB,YAAY,CAC3B,QAAQ,EACR,MAAM,EACN,OAAO,EACP,SAAS,SAAS,gBAAgB,GAAG,gBAAgB,EACrD,KAAK,SAAS,UAAU,GAAG,UAAU,EAErC,OAAO,EAAE,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,GACvE,MAAM,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,CAAC,CA8BrD"}
|
package/dist/helper.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wpkernel/pipeline
|
|
3
|
+
* @license EUPL-1.2
|
|
4
|
+
*/
|
|
5
|
+
function a(e) {
|
|
6
|
+
const {
|
|
7
|
+
key: r,
|
|
8
|
+
kind: n,
|
|
9
|
+
mode: t = "extend",
|
|
10
|
+
priority: p = 0,
|
|
11
|
+
dependsOn: o = [],
|
|
12
|
+
origin: d,
|
|
13
|
+
apply: i
|
|
14
|
+
} = e;
|
|
15
|
+
return Object.freeze({
|
|
16
|
+
key: r,
|
|
17
|
+
kind: n,
|
|
18
|
+
mode: t,
|
|
19
|
+
priority: p,
|
|
20
|
+
dependsOn: Array.from(o),
|
|
21
|
+
origin: d,
|
|
22
|
+
apply(c, s) {
|
|
23
|
+
return i(c, s);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
export {
|
|
28
|
+
a as createHelper
|
|
29
|
+
};
|