apcore-js 0.18.0 → 0.20.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 (241) hide show
  1. package/README.md +112 -9
  2. package/dist/acl-handlers.d.ts +14 -0
  3. package/dist/acl-handlers.d.ts.map +1 -1
  4. package/dist/acl-handlers.js +37 -4
  5. package/dist/acl-handlers.js.map +1 -1
  6. package/dist/acl.d.ts +22 -1
  7. package/dist/acl.d.ts.map +1 -1
  8. package/dist/acl.js +90 -34
  9. package/dist/acl.js.map +1 -1
  10. package/dist/async-task.d.ts +70 -16
  11. package/dist/async-task.d.ts.map +1 -1
  12. package/dist/async-task.js +212 -72
  13. package/dist/async-task.js.map +1 -1
  14. package/dist/bindings.d.ts.map +1 -1
  15. package/dist/bindings.js +113 -11
  16. package/dist/bindings.js.map +1 -1
  17. package/dist/builtin-steps.d.ts +33 -8
  18. package/dist/builtin-steps.d.ts.map +1 -1
  19. package/dist/builtin-steps.js +119 -47
  20. package/dist/builtin-steps.js.map +1 -1
  21. package/dist/client.d.ts +1 -0
  22. package/dist/client.d.ts.map +1 -1
  23. package/dist/client.js.map +1 -1
  24. package/dist/config.d.ts +38 -0
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +163 -33
  27. package/dist/config.js.map +1 -1
  28. package/dist/context.d.ts +34 -7
  29. package/dist/context.d.ts.map +1 -1
  30. package/dist/context.js +108 -40
  31. package/dist/context.js.map +1 -1
  32. package/dist/decorator.d.ts +3 -0
  33. package/dist/decorator.d.ts.map +1 -1
  34. package/dist/decorator.js +3 -0
  35. package/dist/decorator.js.map +1 -1
  36. package/dist/errors.d.ts +88 -2
  37. package/dist/errors.d.ts.map +1 -1
  38. package/dist/errors.js +231 -56
  39. package/dist/errors.js.map +1 -1
  40. package/dist/events/circuit-breaker.d.ts +45 -0
  41. package/dist/events/circuit-breaker.d.ts.map +1 -0
  42. package/dist/events/circuit-breaker.js +115 -0
  43. package/dist/events/circuit-breaker.js.map +1 -0
  44. package/dist/events/emitter.d.ts +24 -1
  45. package/dist/events/emitter.d.ts.map +1 -1
  46. package/dist/events/emitter.js +86 -12
  47. package/dist/events/emitter.js.map +1 -1
  48. package/dist/events/index.d.ts +4 -2
  49. package/dist/events/index.d.ts.map +1 -1
  50. package/dist/events/index.js +3 -2
  51. package/dist/events/index.js.map +1 -1
  52. package/dist/events/subscribers.d.ts +33 -1
  53. package/dist/events/subscribers.d.ts.map +1 -1
  54. package/dist/events/subscribers.js +124 -1
  55. package/dist/events/subscribers.js.map +1 -1
  56. package/dist/executor.d.ts +14 -3
  57. package/dist/executor.d.ts.map +1 -1
  58. package/dist/executor.js +155 -48
  59. package/dist/executor.js.map +1 -1
  60. package/dist/generated/version.d.ts +1 -1
  61. package/dist/generated/version.js +1 -1
  62. package/dist/index.d.ts +47 -25
  63. package/dist/index.d.ts.map +1 -1
  64. package/dist/index.js +35 -18
  65. package/dist/index.js.map +1 -1
  66. package/dist/middleware/base.d.ts +25 -3
  67. package/dist/middleware/base.d.ts.map +1 -1
  68. package/dist/middleware/base.js +24 -0
  69. package/dist/middleware/base.js.map +1 -1
  70. package/dist/middleware/circuit-breaker.d.ts +54 -0
  71. package/dist/middleware/circuit-breaker.d.ts.map +1 -0
  72. package/dist/middleware/circuit-breaker.js +168 -0
  73. package/dist/middleware/circuit-breaker.js.map +1 -0
  74. package/dist/middleware/context-namespace.d.ts +30 -0
  75. package/dist/middleware/context-namespace.d.ts.map +1 -0
  76. package/dist/middleware/context-namespace.js +38 -0
  77. package/dist/middleware/context-namespace.js.map +1 -0
  78. package/dist/middleware/index.d.ts +8 -2
  79. package/dist/middleware/index.d.ts.map +1 -1
  80. package/dist/middleware/index.js +5 -2
  81. package/dist/middleware/index.js.map +1 -1
  82. package/dist/middleware/logging.d.ts +6 -0
  83. package/dist/middleware/logging.d.ts.map +1 -1
  84. package/dist/middleware/logging.js +13 -3
  85. package/dist/middleware/logging.js.map +1 -1
  86. package/dist/middleware/manager.d.ts +11 -4
  87. package/dist/middleware/manager.d.ts.map +1 -1
  88. package/dist/middleware/manager.js +26 -9
  89. package/dist/middleware/manager.js.map +1 -1
  90. package/dist/middleware/platform-notify.d.ts +8 -4
  91. package/dist/middleware/platform-notify.d.ts.map +1 -1
  92. package/dist/middleware/platform-notify.js +15 -7
  93. package/dist/middleware/platform-notify.js.map +1 -1
  94. package/dist/middleware/retry.d.ts +16 -7
  95. package/dist/middleware/retry.d.ts.map +1 -1
  96. package/dist/middleware/retry.js +21 -15
  97. package/dist/middleware/retry.js.map +1 -1
  98. package/dist/middleware/tracing.d.ts +50 -0
  99. package/dist/middleware/tracing.d.ts.map +1 -0
  100. package/dist/middleware/tracing.js +89 -0
  101. package/dist/middleware/tracing.js.map +1 -0
  102. package/dist/observability/batch-span-processor.d.ts +48 -0
  103. package/dist/observability/batch-span-processor.d.ts.map +1 -0
  104. package/dist/observability/batch-span-processor.js +89 -0
  105. package/dist/observability/batch-span-processor.js.map +1 -0
  106. package/dist/observability/context-logger.d.ts +54 -1
  107. package/dist/observability/context-logger.d.ts.map +1 -1
  108. package/dist/observability/context-logger.js +287 -10
  109. package/dist/observability/context-logger.js.map +1 -1
  110. package/dist/observability/error-history.d.ts +36 -7
  111. package/dist/observability/error-history.d.ts.map +1 -1
  112. package/dist/observability/error-history.js +169 -50
  113. package/dist/observability/error-history.js.map +1 -1
  114. package/dist/observability/index.d.ts +16 -5
  115. package/dist/observability/index.d.ts.map +1 -1
  116. package/dist/observability/index.js +8 -3
  117. package/dist/observability/index.js.map +1 -1
  118. package/dist/observability/metrics-utils.d.ts.map +1 -1
  119. package/dist/observability/metrics-utils.js +3 -5
  120. package/dist/observability/metrics-utils.js.map +1 -1
  121. package/dist/observability/metrics.d.ts +15 -1
  122. package/dist/observability/metrics.d.ts.map +1 -1
  123. package/dist/observability/metrics.js +37 -3
  124. package/dist/observability/metrics.js.map +1 -1
  125. package/dist/observability/prometheus-exporter.d.ts +37 -0
  126. package/dist/observability/prometheus-exporter.d.ts.map +1 -0
  127. package/dist/observability/prometheus-exporter.js +135 -0
  128. package/dist/observability/prometheus-exporter.js.map +1 -0
  129. package/dist/observability/storage.d.ts +43 -0
  130. package/dist/observability/storage.d.ts.map +1 -0
  131. package/dist/observability/storage.js +58 -0
  132. package/dist/observability/storage.js.map +1 -0
  133. package/dist/observability/store.d.ts +29 -0
  134. package/dist/observability/store.d.ts.map +1 -0
  135. package/dist/observability/store.js +36 -0
  136. package/dist/observability/store.js.map +1 -0
  137. package/dist/observability/tracing.d.ts +2 -0
  138. package/dist/observability/tracing.d.ts.map +1 -1
  139. package/dist/observability/tracing.js +12 -2
  140. package/dist/observability/tracing.js.map +1 -1
  141. package/dist/observability/usage-exporter.d.ts +58 -0
  142. package/dist/observability/usage-exporter.d.ts.map +1 -0
  143. package/dist/observability/usage-exporter.js +86 -0
  144. package/dist/observability/usage-exporter.js.map +1 -0
  145. package/dist/observability/usage.d.ts +18 -1
  146. package/dist/observability/usage.d.ts.map +1 -1
  147. package/dist/observability/usage.js +35 -4
  148. package/dist/observability/usage.js.map +1 -1
  149. package/dist/pipeline-config.d.ts +24 -7
  150. package/dist/pipeline-config.d.ts.map +1 -1
  151. package/dist/pipeline-config.js +113 -19
  152. package/dist/pipeline-config.js.map +1 -1
  153. package/dist/pipeline.d.ts +123 -2
  154. package/dist/pipeline.d.ts.map +1 -1
  155. package/dist/pipeline.js +249 -50
  156. package/dist/pipeline.js.map +1 -1
  157. package/dist/registry/conflicts.d.ts +2 -2
  158. package/dist/registry/conflicts.d.ts.map +1 -1
  159. package/dist/registry/conflicts.js +10 -11
  160. package/dist/registry/conflicts.js.map +1 -1
  161. package/dist/registry/dependencies.d.ts +1 -1
  162. package/dist/registry/dependencies.d.ts.map +1 -1
  163. package/dist/registry/dependencies.js +69 -20
  164. package/dist/registry/dependencies.js.map +1 -1
  165. package/dist/registry/index.d.ts +2 -0
  166. package/dist/registry/index.d.ts.map +1 -1
  167. package/dist/registry/index.js +1 -0
  168. package/dist/registry/index.js.map +1 -1
  169. package/dist/registry/multi-class.d.ts +57 -0
  170. package/dist/registry/multi-class.d.ts.map +1 -0
  171. package/dist/registry/multi-class.js +120 -0
  172. package/dist/registry/multi-class.js.map +1 -0
  173. package/dist/registry/registry.d.ts +99 -4
  174. package/dist/registry/registry.d.ts.map +1 -1
  175. package/dist/registry/registry.js +291 -33
  176. package/dist/registry/registry.js.map +1 -1
  177. package/dist/registry/scanner.d.ts.map +1 -1
  178. package/dist/registry/scanner.js +6 -0
  179. package/dist/registry/scanner.js.map +1 -1
  180. package/dist/registry/version.d.ts +1 -0
  181. package/dist/registry/version.d.ts.map +1 -1
  182. package/dist/registry/version.js +33 -4
  183. package/dist/registry/version.js.map +1 -1
  184. package/dist/schema/constants.d.ts +9 -0
  185. package/dist/schema/constants.d.ts.map +1 -0
  186. package/dist/schema/constants.js +9 -0
  187. package/dist/schema/constants.js.map +1 -0
  188. package/dist/schema/extractor.d.ts +69 -0
  189. package/dist/schema/extractor.d.ts.map +1 -0
  190. package/dist/schema/extractor.js +142 -0
  191. package/dist/schema/extractor.js.map +1 -0
  192. package/dist/schema/index.d.ts +3 -1
  193. package/dist/schema/index.d.ts.map +1 -1
  194. package/dist/schema/index.js +2 -1
  195. package/dist/schema/index.js.map +1 -1
  196. package/dist/schema/loader.d.ts +27 -3
  197. package/dist/schema/loader.d.ts.map +1 -1
  198. package/dist/schema/loader.js +137 -32
  199. package/dist/schema/loader.js.map +1 -1
  200. package/dist/schema/ref-resolver.d.ts.map +1 -1
  201. package/dist/schema/ref-resolver.js +10 -1
  202. package/dist/schema/ref-resolver.js.map +1 -1
  203. package/dist/schema/types.d.ts +4 -0
  204. package/dist/schema/types.d.ts.map +1 -1
  205. package/dist/schema/types.js.map +1 -1
  206. package/dist/schema/validator.d.ts +9 -0
  207. package/dist/schema/validator.d.ts.map +1 -1
  208. package/dist/schema/validator.js +153 -4
  209. package/dist/schema/validator.js.map +1 -1
  210. package/dist/sys-modules/audit.d.ts +50 -0
  211. package/dist/sys-modules/audit.d.ts.map +1 -0
  212. package/dist/sys-modules/audit.js +89 -0
  213. package/dist/sys-modules/audit.js.map +1 -0
  214. package/dist/sys-modules/control.d.ts +32 -4
  215. package/dist/sys-modules/control.d.ts.map +1 -1
  216. package/dist/sys-modules/control.js +197 -23
  217. package/dist/sys-modules/control.js.map +1 -1
  218. package/dist/sys-modules/index.d.ts +7 -2
  219. package/dist/sys-modules/index.d.ts.map +1 -1
  220. package/dist/sys-modules/index.js +3 -1
  221. package/dist/sys-modules/index.js.map +1 -1
  222. package/dist/sys-modules/overrides.d.ts +58 -0
  223. package/dist/sys-modules/overrides.d.ts.map +1 -0
  224. package/dist/sys-modules/overrides.js +106 -0
  225. package/dist/sys-modules/overrides.js.map +1 -0
  226. package/dist/sys-modules/registration.d.ts +18 -1
  227. package/dist/sys-modules/registration.d.ts.map +1 -1
  228. package/dist/sys-modules/registration.js +115 -11
  229. package/dist/sys-modules/registration.js.map +1 -1
  230. package/dist/sys-modules/toggle.d.ts +7 -2
  231. package/dist/sys-modules/toggle.d.ts.map +1 -1
  232. package/dist/sys-modules/toggle.js +61 -5
  233. package/dist/sys-modules/toggle.js.map +1 -1
  234. package/dist/trace-context.d.ts +47 -9
  235. package/dist/trace-context.d.ts.map +1 -1
  236. package/dist/trace-context.js +139 -16
  237. package/dist/trace-context.js.map +1 -1
  238. package/dist/utils/index.d.ts.map +1 -1
  239. package/dist/utils/index.js +2 -1
  240. package/dist/utils/index.js.map +1 -1
  241. package/package.json +1 -1
@@ -32,6 +32,43 @@ export interface StepResult {
32
32
  confidence?: number | null;
33
33
  alternatives?: string[] | null;
34
34
  }
35
+ /** Snapshot passed to runUntil predicates after each step completes (§1.4). */
36
+ export interface PipelineState {
37
+ readonly stepName: string;
38
+ readonly outputs: Record<string, unknown>;
39
+ readonly context: PipelineContext;
40
+ }
41
+ /**
42
+ * Middleware interface for intercepting pipeline step lifecycle (Issue #33 §2.2).
43
+ *
44
+ * Each method is optional. The PipelineEngine invokes them in this order
45
+ * around every step:
46
+ *
47
+ * beforeStep → step.execute → afterStep (success path)
48
+ * beforeStep → step.execute → onStepError (failure path)
49
+ *
50
+ * `onStepError` MAY return a non-null recovery value to suppress the error
51
+ * and continue the pipeline. Returning `null` (or `undefined`) re-raises the
52
+ * original error after every middleware has been consulted. The first
53
+ * middleware to return non-null wins; later middlewares are not invoked.
54
+ *
55
+ * All methods may be sync or async. The engine awaits any thenable return
56
+ * value (mirroring Issue #42's middleware fix) so plain functions returning
57
+ * a Promise are not silently dropped.
58
+ */
59
+ export interface StepMiddleware {
60
+ /** Invoked before a step's `execute()` runs. */
61
+ beforeStep?(stepName: string, state: PipelineState): void | Promise<void>;
62
+ /** Invoked after a step's `execute()` completes successfully. */
63
+ afterStep?(stepName: string, state: PipelineState, result: unknown): void | Promise<void>;
64
+ /**
65
+ * Invoked when a step's `execute()` throws. Return non-null to recover
66
+ * (the engine treats the value as the step's output and continues).
67
+ * Return null/undefined to let the next middleware try, or — if no
68
+ * middleware recovers — to propagate the original error.
69
+ */
70
+ onStepError?(stepName: string, state: PipelineState, error: Error): unknown | null | Promise<unknown | null>;
71
+ }
35
72
  /** Holds all state flowing through the pipeline. */
36
73
  export interface PipelineContext {
37
74
  moduleId: string;
@@ -51,6 +88,8 @@ export interface PipelineContext {
51
88
  versionHint?: string | null;
52
89
  /** Tracks which middleware ran, enabling on_error recovery chain. */
53
90
  executedMiddlewares?: unknown[];
91
+ /** When set, pipeline halts after the first step where predicate returns true (§1.4). */
92
+ runUntil?: ((state: PipelineState) => boolean) | null;
54
93
  }
55
94
  /** Records execution details for a single step. */
56
95
  export interface StepTrace {
@@ -77,14 +116,43 @@ export interface StrategyInfo {
77
116
  stepNames: string[];
78
117
  description: string;
79
118
  }
119
+ /**
120
+ * Optional configuration for {@link ExecutionStrategy}.
121
+ *
122
+ * `seedProvides` lists pipeline-context fields that are guaranteed to be
123
+ * populated by an external caller before the first step runs. They count
124
+ * as "already provided" during dependency validation (Issue #33 §2.1) so
125
+ * that legitimate sub-strategies — for example the post-stream strategy
126
+ * built from {@link Step}s of a parent strategy — do not raise spurious
127
+ * `PipelineDependencyError`s. Use sparingly; prefer adding the missing
128
+ * upstream step when possible.
129
+ */
130
+ export interface ExecutionStrategyOptions {
131
+ /** Names of pipeline-context fields the caller will pre-populate. */
132
+ readonly seedProvides?: readonly string[];
133
+ }
80
134
  /** An ordered sequence of steps that defines how a module is executed. */
81
135
  export declare class ExecutionStrategy {
82
136
  readonly name: string;
83
137
  private _steps;
84
- constructor(name: string, steps: Step[]);
85
- /** Warn if any step's requires are not provided by a preceding step. */
138
+ private _nameToIdx;
139
+ private readonly _seedProvides;
140
+ constructor(name: string, steps: Step[], options?: ExecutionStrategyOptions);
141
+ /** Rebuild the O(1) name→index map. Call after any mutation (§1.5). */
142
+ private _rebuildIndex;
143
+ /**
144
+ * Fail-fast: throw `PipelineDependencyError` if any step's `requires` are not
145
+ * provided by a preceding step (Issue #33 §2.1).
146
+ *
147
+ * Previously this method emitted a `console.warn` and let construction
148
+ * succeed; that allowed misconfigured strategies to run partway and fail
149
+ * with a confusing runtime error. Throwing at construction surfaces the
150
+ * problem immediately and tells the caller which step / field is missing.
151
+ */
86
152
  private _validateDependencies;
87
153
  get steps(): readonly Step[];
154
+ /** Return the index of a step by name, or undefined if not found. O(1). */
155
+ findStepIndex(name: string): number | undefined;
88
156
  /** Insert a step after the named anchor step. */
89
157
  insertAfter(anchor: string, step: Step): void;
90
158
  /** Insert a step before the named anchor step. */
@@ -93,6 +161,13 @@ export declare class ExecutionStrategy {
93
161
  remove(stepName: string): void;
94
162
  /** Replace a step by name. Raises if the step is not replaceable. */
95
163
  replace(stepName: string, newStep: Step): void;
164
+ /**
165
+ * Replace a step by name using replace semantics (§1.2).
166
+ *
167
+ * Calling configureStep twice with the same stepName always leaves exactly
168
+ * one step at that position — idempotent, never duplicates.
169
+ */
170
+ configureStep(stepName: string, newStep: Step): void;
96
171
  /** Return the ordered list of step names. */
97
172
  stepNames(): string[];
98
173
  /** Return an AI-introspectable description of this strategy. */
@@ -127,8 +202,54 @@ export declare class StepNameDuplicateError extends ModuleError {
127
202
  static readonly DEFAULT_RETRYABLE: boolean | null;
128
203
  constructor(message?: string, options?: ErrorOptions);
129
204
  }
205
+ /**
206
+ * Raised when a pipeline step fails (fail-fast, §1.1).
207
+ *
208
+ * Wraps the original exception from the failing step. When ignore_errors is
209
+ * true on the step, this error is NOT raised — execution continues instead.
210
+ */
211
+ export declare class PipelineStepError extends ModuleError {
212
+ static readonly DEFAULT_RETRYABLE: boolean | null;
213
+ readonly stepName: string;
214
+ readonly pipelineTrace: PipelineTrace | null;
215
+ constructor(stepName: string, cause?: Error | null, trace?: PipelineTrace | null, options?: ErrorOptions);
216
+ }
217
+ /**
218
+ * Raised at strategy construction when a step's declared `requires` are not
219
+ * provided by any preceding step (Issue #33 §2.1).
220
+ *
221
+ * This replaces the previous `console.warn` in `_validateDependencies`. The
222
+ * error names the offending step and the missing fields so the caller can
223
+ * fix the strategy definition before any step runs.
224
+ */
225
+ export declare class PipelineDependencyError extends ModuleError {
226
+ static readonly DEFAULT_RETRYABLE: boolean | null;
227
+ readonly stepName: string;
228
+ readonly missingRequires: string[];
229
+ constructor(stepName: string, missingRequires: string[], options?: ErrorOptions);
230
+ }
231
+ /** Raised when configureStep targets a step name that does not exist. */
232
+ export declare class PipelineStepNotFoundError extends ModuleError {
233
+ static readonly DEFAULT_RETRYABLE: boolean | null;
234
+ readonly stepName: string;
235
+ constructor(stepName?: string, options?: ErrorOptions);
236
+ }
130
237
  /** Executes an ExecutionStrategy against a PipelineContext, returning the final output and a complete execution trace. */
131
238
  export declare class PipelineEngine {
239
+ /**
240
+ * Registered step-lifecycle middlewares (Issue #33 §2.2). Invoked in
241
+ * registration order around every step's execute() call.
242
+ */
243
+ private readonly _stepMiddlewares;
244
+ /** Read-only view of the registered step middlewares. */
245
+ get stepMiddlewares(): readonly StepMiddleware[];
246
+ /**
247
+ * Register a `StepMiddleware` to intercept every step's lifecycle
248
+ * (Issue #33 §2.2). beforeStep / afterStep run in registration order;
249
+ * onStepError is consulted in registration order until one returns a
250
+ * non-null recovery value.
251
+ */
252
+ addStepMiddleware(mw: StepMiddleware): void;
132
253
  /** Run every step in the strategy against ctx, respecting flow-control actions (continue, skip_to, abort). */
133
254
  run(strategy: ExecutionStrategy, ctx: PipelineContext): Promise<[unknown, PipelineTrace]>;
134
255
  }
@@ -1 +1 @@
1
- {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../src/pipeline.ts"],"names":[],"mappings":"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"}
package/dist/pipeline.js CHANGED
@@ -3,16 +3,35 @@
3
3
  */
4
4
  import { ModuleError } from './errors.js';
5
5
  import { matchPattern } from './utils/pattern.js';
6
- // ---------------------------------------------------------------------------
7
- // ExecutionStrategy
8
- // ---------------------------------------------------------------------------
6
+ /**
7
+ * Detect a thenable (Promise-like) without calling `.then`.
8
+ *
9
+ * Mirrors the Issue #42 fix in MiddlewareManager: handlers may not be
10
+ * declared `async` but still return a Promise; treating those values as
11
+ * sync would silently drop their effects.
12
+ */
13
+ function _isThenable(value) {
14
+ return (value != null &&
15
+ (typeof value === 'object' || typeof value === 'function') &&
16
+ typeof value.then === 'function');
17
+ }
18
+ async function _maybeAwait(value) {
19
+ if (_isThenable(value)) {
20
+ return await value;
21
+ }
22
+ return value;
23
+ }
9
24
  /** An ordered sequence of steps that defines how a module is executed. */
10
25
  export class ExecutionStrategy {
11
26
  name;
12
27
  _steps;
13
- constructor(name, steps) {
28
+ _nameToIdx;
29
+ _seedProvides;
30
+ constructor(name, steps, options) {
14
31
  this.name = name;
15
32
  this._steps = [...steps];
33
+ this._nameToIdx = new Map();
34
+ this._seedProvides = new Set(options?.seedProvides ?? []);
16
35
  // Validate unique step names
17
36
  const names = this._steps.map((s) => s.name);
18
37
  const seen = new Set();
@@ -26,18 +45,35 @@ export class ExecutionStrategy {
26
45
  if (dupes.size > 0) {
27
46
  throw new StepNameDuplicateError(`Duplicate step names: ${[...dupes].join(', ')}`);
28
47
  }
48
+ this._rebuildIndex();
29
49
  this._validateDependencies();
30
50
  }
31
- /** Warn if any step's requires are not provided by a preceding step. */
51
+ /** Rebuild the O(1) name→index map. Call after any mutation (§1.5). */
52
+ _rebuildIndex() {
53
+ this._nameToIdx = new Map(this._steps.map((s, i) => [s.name, i]));
54
+ }
55
+ /**
56
+ * Fail-fast: throw `PipelineDependencyError` if any step's `requires` are not
57
+ * provided by a preceding step (Issue #33 §2.1).
58
+ *
59
+ * Previously this method emitted a `console.warn` and let construction
60
+ * succeed; that allowed misconfigured strategies to run partway and fail
61
+ * with a confusing runtime error. Throwing at construction surfaces the
62
+ * problem immediately and tells the caller which step / field is missing.
63
+ */
32
64
  _validateDependencies() {
33
- const provided = new Set();
65
+ const provided = new Set(this._seedProvides);
34
66
  for (const step of this._steps) {
35
67
  const requires = step.requires ?? [];
68
+ const missing = [];
36
69
  for (const req of requires) {
37
70
  if (!provided.has(req)) {
38
- console.warn(`[apcore:pipeline] Step '${step.name}' requires '${req}', but no preceding step provides it. This may cause runtime errors.`);
71
+ missing.push(req);
39
72
  }
40
73
  }
74
+ if (missing.length > 0) {
75
+ throw new PipelineDependencyError(step.name, missing);
76
+ }
41
77
  for (const p of step.provides ?? []) {
42
78
  provided.add(p);
43
79
  }
@@ -46,59 +82,82 @@ export class ExecutionStrategy {
46
82
  get steps() {
47
83
  return this._steps;
48
84
  }
85
+ /** Return the index of a step by name, or undefined if not found. O(1). */
86
+ findStepIndex(name) {
87
+ return this._nameToIdx.get(name);
88
+ }
49
89
  /** Insert a step after the named anchor step. */
50
90
  insertAfter(anchor, step) {
51
- if (this._steps.some((s) => s.name === step.name)) {
91
+ if (this._nameToIdx.has(step.name)) {
52
92
  throw new StepNameDuplicateError(`Step '${step.name}' already exists`);
53
93
  }
54
- for (let i = 0; i < this._steps.length; i++) {
55
- if (this._steps[i].name === anchor) {
56
- this._steps.splice(i + 1, 0, step);
57
- this._validateDependencies();
58
- return;
59
- }
94
+ const anchorIdx = this._nameToIdx.get(anchor);
95
+ if (anchorIdx === undefined) {
96
+ throw new StepNotFoundError(`Anchor step '${anchor}' not found`);
60
97
  }
61
- throw new StepNotFoundError(`Anchor step '${anchor}' not found`);
98
+ this._steps.splice(anchorIdx + 1, 0, step);
99
+ this._rebuildIndex();
100
+ this._validateDependencies();
62
101
  }
63
102
  /** Insert a step before the named anchor step. */
64
103
  insertBefore(anchor, step) {
65
- if (this._steps.some((s) => s.name === step.name)) {
104
+ if (this._nameToIdx.has(step.name)) {
66
105
  throw new StepNameDuplicateError(`Step '${step.name}' already exists`);
67
106
  }
68
- for (let i = 0; i < this._steps.length; i++) {
69
- if (this._steps[i].name === anchor) {
70
- this._steps.splice(i, 0, step);
71
- this._validateDependencies();
72
- return;
73
- }
107
+ const anchorIdx = this._nameToIdx.get(anchor);
108
+ if (anchorIdx === undefined) {
109
+ throw new StepNotFoundError(`Anchor step '${anchor}' not found`);
74
110
  }
75
- throw new StepNotFoundError(`Anchor step '${anchor}' not found`);
111
+ this._steps.splice(anchorIdx, 0, step);
112
+ this._rebuildIndex();
113
+ this._validateDependencies();
76
114
  }
77
115
  /** Remove a step by name. Raises if the step is not removable. */
78
116
  remove(stepName) {
79
- for (let i = 0; i < this._steps.length; i++) {
80
- if (this._steps[i].name === stepName) {
81
- if (!this._steps[i].removable) {
82
- throw new StepNotRemovableError(`Step '${stepName}' is not removable`);
83
- }
84
- this._steps.splice(i, 1);
85
- return;
86
- }
117
+ const idx = this._nameToIdx.get(stepName);
118
+ if (idx === undefined) {
119
+ throw new StepNotFoundError(`Step '${stepName}' not found`);
87
120
  }
88
- throw new StepNotFoundError(`Step '${stepName}' not found`);
121
+ if (!this._steps[idx].removable) {
122
+ throw new StepNotRemovableError(`Step '${stepName}' is not removable`);
123
+ }
124
+ this._steps.splice(idx, 1);
125
+ this._rebuildIndex();
89
126
  }
90
127
  /** Replace a step by name. Raises if the step is not replaceable. */
91
128
  replace(stepName, newStep) {
92
- for (let i = 0; i < this._steps.length; i++) {
93
- if (this._steps[i].name === stepName) {
94
- if (!this._steps[i].replaceable) {
95
- throw new StepNotReplaceableError(`Step '${stepName}' is not replaceable`);
96
- }
97
- this._steps[i] = newStep;
98
- return;
99
- }
129
+ const idx = this._nameToIdx.get(stepName);
130
+ if (idx === undefined) {
131
+ throw new StepNotFoundError(`Step '${stepName}' not found`);
132
+ }
133
+ if (!this._steps[idx].replaceable) {
134
+ throw new StepNotReplaceableError(`Step '${stepName}' is not replaceable`);
135
+ }
136
+ this._steps[idx] = newStep;
137
+ this._rebuildIndex();
138
+ }
139
+ /**
140
+ * Replace a step by name using replace semantics (§1.2).
141
+ *
142
+ * Calling configureStep twice with the same stepName always leaves exactly
143
+ * one step at that position — idempotent, never duplicates.
144
+ */
145
+ configureStep(stepName, newStep) {
146
+ const idx = this._nameToIdx.get(stepName);
147
+ if (idx === undefined) {
148
+ throw new PipelineStepNotFoundError(stepName);
149
+ }
150
+ if (!this._steps[idx].replaceable) {
151
+ throw new StepNotReplaceableError(`Step '${stepName}' is not replaceable`);
100
152
  }
101
- throw new StepNotFoundError(`Step '${stepName}' not found`);
153
+ // Guard: reject if newStep.name already exists at a different position.
154
+ // Allowing it would silently produce duplicate names in _steps and corrupt
155
+ // _nameToIdx (the second entry for that name would shadow the first).
156
+ if (newStep.name !== stepName && this._nameToIdx.has(newStep.name)) {
157
+ throw new StepNameDuplicateError(`Step '${newStep.name}' already exists at a different position`);
158
+ }
159
+ this._steps[idx] = newStep;
160
+ this._rebuildIndex();
102
161
  }
103
162
  /** Return the ordered list of step names. */
104
163
  stepNames() {
@@ -110,7 +169,7 @@ export class ExecutionStrategy {
110
169
  name: this.name,
111
170
  stepCount: this._steps.length,
112
171
  stepNames: this.stepNames(),
113
- description: this.stepNames().join(' \u2192 '),
172
+ description: this.stepNames().join(' '),
114
173
  };
115
174
  }
116
175
  }
@@ -165,11 +224,76 @@ export class StepNameDuplicateError extends ModuleError {
165
224
  this.name = 'StepNameDuplicateError';
166
225
  }
167
226
  }
227
+ /**
228
+ * Raised when a pipeline step fails (fail-fast, §1.1).
229
+ *
230
+ * Wraps the original exception from the failing step. When ignore_errors is
231
+ * true on the step, this error is NOT raised — execution continues instead.
232
+ */
233
+ export class PipelineStepError extends ModuleError {
234
+ static DEFAULT_RETRYABLE = false;
235
+ stepName;
236
+ pipelineTrace;
237
+ constructor(stepName, cause, trace, options) {
238
+ const causeMsg = cause?.message ?? 'unknown error';
239
+ super('PIPELINE_STEP_ERROR', `Pipeline step '${stepName}' failed: ${causeMsg}`, { stepName }, cause ?? undefined, options?.traceId, options?.retryable, options?.aiGuidance, options?.userFixable, options?.suggestion);
240
+ this.name = 'PipelineStepError';
241
+ this.stepName = stepName;
242
+ this.pipelineTrace = trace ?? null;
243
+ }
244
+ }
245
+ /**
246
+ * Raised at strategy construction when a step's declared `requires` are not
247
+ * provided by any preceding step (Issue #33 §2.1).
248
+ *
249
+ * This replaces the previous `console.warn` in `_validateDependencies`. The
250
+ * error names the offending step and the missing fields so the caller can
251
+ * fix the strategy definition before any step runs.
252
+ */
253
+ export class PipelineDependencyError extends ModuleError {
254
+ static DEFAULT_RETRYABLE = false;
255
+ stepName;
256
+ missingRequires;
257
+ constructor(stepName, missingRequires, options) {
258
+ super('PIPELINE_DEPENDENCY_ERROR', `Pipeline step '${stepName}' requires [${missingRequires.join(', ')}], but no preceding step provides ${missingRequires.length === 1 ? 'it' : 'them'}.`, { stepName, missingRequires: [...missingRequires] }, options?.cause, options?.traceId, options?.retryable, options?.aiGuidance, options?.userFixable, options?.suggestion);
259
+ this.name = 'PipelineDependencyError';
260
+ this.stepName = stepName;
261
+ this.missingRequires = [...missingRequires];
262
+ }
263
+ }
264
+ /** Raised when configureStep targets a step name that does not exist. */
265
+ export class PipelineStepNotFoundError extends ModuleError {
266
+ static DEFAULT_RETRYABLE = false;
267
+ stepName;
268
+ constructor(stepName = '', options) {
269
+ super('PIPELINE_STEP_NOT_FOUND', `Pipeline step not found: '${stepName}'`, { stepName }, options?.cause, options?.traceId, options?.retryable, options?.aiGuidance, options?.userFixable, options?.suggestion);
270
+ this.name = 'PipelineStepNotFoundError';
271
+ this.stepName = stepName;
272
+ }
273
+ }
168
274
  // ---------------------------------------------------------------------------
169
275
  // PipelineEngine
170
276
  // ---------------------------------------------------------------------------
171
277
  /** Executes an ExecutionStrategy against a PipelineContext, returning the final output and a complete execution trace. */
172
278
  export class PipelineEngine {
279
+ /**
280
+ * Registered step-lifecycle middlewares (Issue #33 §2.2). Invoked in
281
+ * registration order around every step's execute() call.
282
+ */
283
+ _stepMiddlewares = [];
284
+ /** Read-only view of the registered step middlewares. */
285
+ get stepMiddlewares() {
286
+ return this._stepMiddlewares;
287
+ }
288
+ /**
289
+ * Register a `StepMiddleware` to intercept every step's lifecycle
290
+ * (Issue #33 §2.2). beforeStep / afterStep run in registration order;
291
+ * onStepError is consulted in registration order until one returns a
292
+ * non-null recovery value.
293
+ */
294
+ addStepMiddleware(mw) {
295
+ this._stepMiddlewares.push(mw);
296
+ }
173
297
  /** Run every step in the strategy against ctx, respecting flow-control actions (continue, skip_to, abort). */
174
298
  async run(strategy, ctx) {
175
299
  const pipelineStart = performance.now();
@@ -182,6 +306,7 @@ export class PipelineEngine {
182
306
  success: false,
183
307
  };
184
308
  ctx.trace = trace;
309
+ const stepOutputs = {};
185
310
  let idx = 0;
186
311
  while (idx < steps.length) {
187
312
  const step = steps[idx];
@@ -219,8 +344,19 @@ export class PipelineEngine {
219
344
  idx += 1;
220
345
  continue;
221
346
  }
222
- // ③ Execute with per-step timeout
347
+ // ③ Execute with per-step timeout, wrapped in StepMiddleware hooks (§2.2)
223
348
  const stepStart = performance.now();
349
+ const stepState = {
350
+ stepName: step.name,
351
+ outputs: stepOutputs,
352
+ context: ctx,
353
+ };
354
+ // ③a beforeStep hooks (registration order)
355
+ for (const mw of this._stepMiddlewares) {
356
+ if (mw.beforeStep) {
357
+ await _maybeAwait(mw.beforeStep(step.name, stepState));
358
+ }
359
+ }
224
360
  let result;
225
361
  try {
226
362
  if (stepTimeoutMs > 0) {
@@ -240,14 +376,55 @@ export class PipelineEngine {
240
376
  }
241
377
  catch (exc) {
242
378
  const durationMs = performance.now() - stepStart;
379
+ const cause = exc instanceof Error ? exc : new Error(String(exc));
380
+ // ③b onStepError hooks: first non-null recovery wins (§2.2)
381
+ let recovery = null;
382
+ for (const mw of this._stepMiddlewares) {
383
+ if (!mw.onStepError)
384
+ continue;
385
+ const ret = await _maybeAwait(mw.onStepError(step.name, stepState, cause));
386
+ if (ret != null) {
387
+ recovery = ret;
388
+ break;
389
+ }
390
+ }
391
+ if (recovery != null) {
392
+ // The recovery value is informational — it's surfaced to afterStep
393
+ // hooks and recorded in the trace explanation, but NOT auto-merged
394
+ // into ctx.output. Middlewares that want to publish recovery state
395
+ // into output should mutate ctx.output directly inside onStepError.
396
+ const recoveredResult = {
397
+ action: 'continue',
398
+ explanation: `recovered from: ${cause.message}`,
399
+ };
400
+ trace.steps.push({
401
+ name: step.name,
402
+ durationMs,
403
+ result: recoveredResult,
404
+ skipped: false,
405
+ decisionPoint: false,
406
+ skipReason: 'error_recovered',
407
+ });
408
+ // afterStep hooks still fire on recovery — middlewares treat recovery
409
+ // as a successful (post-step) outcome and may want to record metrics.
410
+ for (const mw of this._stepMiddlewares) {
411
+ if (mw.afterStep) {
412
+ await _maybeAwait(mw.afterStep(step.name, stepState, recovery));
413
+ }
414
+ }
415
+ stepOutputs[step.name] = ctx.output != null ? { ...ctx.output } : null;
416
+ idx += 1;
417
+ continue;
418
+ }
243
419
  // ④ ignore_errors: log and continue
244
420
  if (stepIgnoreErrors) {
421
+ console.warn(`[apcore:pipeline] Step '${step.name}' failed (ignored):`, cause.message);
245
422
  trace.steps.push({
246
423
  name: step.name,
247
424
  durationMs,
248
425
  result: {
249
426
  action: 'continue',
250
- explanation: exc instanceof Error ? exc.message : String(exc),
427
+ explanation: cause.message,
251
428
  },
252
429
  skipped: false,
253
430
  decisionPoint: false,
@@ -256,21 +433,27 @@ export class PipelineEngine {
256
433
  idx += 1;
257
434
  continue;
258
435
  }
259
- // Not ignored: record and raise
436
+ // Fail-fast (§1.1): wrap in PipelineStepError with step name and cause
260
437
  trace.steps.push({
261
438
  name: step.name,
262
439
  durationMs,
263
440
  result: {
264
441
  action: 'abort',
265
- explanation: exc instanceof Error ? exc.message : String(exc),
442
+ explanation: cause.message,
266
443
  },
267
444
  skipped: false,
268
445
  decisionPoint: false,
269
446
  });
270
447
  trace.totalDurationMs = performance.now() - pipelineStart;
271
- throw exc;
448
+ throw new PipelineStepError(step.name, cause, trace);
272
449
  }
273
450
  const durationMs = performance.now() - stepStart;
451
+ // ③c afterStep hooks (registration order, success path)
452
+ for (const mw of this._stepMiddlewares) {
453
+ if (mw.afterStep) {
454
+ await _maybeAwait(mw.afterStep(step.name, stepState, result));
455
+ }
456
+ }
274
457
  // ⑤ Record trace
275
458
  trace.steps.push({
276
459
  name: step.name,
@@ -279,8 +462,23 @@ export class PipelineEngine {
279
462
  skipped: false,
280
463
  decisionPoint: result.confidence != null,
281
464
  });
282
- // ⑥ Handle abort / skip_to
465
+ // ⑥ Handle abort / skip_to / continue
283
466
  if (result.action === 'continue') {
467
+ // Snapshot output for run_until predicates (§1.4)
468
+ stepOutputs[step.name] = ctx.output != null ? { ...ctx.output } : null;
469
+ // ⑦ run_until: evaluate predicate after each successful continue (§1.4)
470
+ if (ctx.runUntil != null) {
471
+ const state = {
472
+ stepName: step.name,
473
+ outputs: stepOutputs,
474
+ context: ctx,
475
+ };
476
+ if (ctx.runUntil(state)) {
477
+ trace.totalDurationMs = performance.now() - pipelineStart;
478
+ trace.success = true;
479
+ return [ctx.output ?? null, trace];
480
+ }
481
+ }
284
482
  idx += 1;
285
483
  }
286
484
  else if (result.action === 'abort') {
@@ -290,8 +488,9 @@ export class PipelineEngine {
290
488
  }
291
489
  else if (result.action === 'skip_to') {
292
490
  const target = result.skipTo ?? '';
293
- const targetIdx = steps.findIndex((s, i) => i > idx && s.name === target);
294
- if (targetIdx === -1) {
491
+ // O(1) step index lookup (§1.5)
492
+ const targetIdx = strategy.findStepIndex(target);
493
+ if (targetIdx === undefined || targetIdx <= idx) {
295
494
  throw new StepNotFoundError(`skip_to target '${target}' not found after step '${step.name}'`);
296
495
  }
297
496
  // Mark skipped steps in trace