@stackra/pipeline 0.1.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 +21 -0
- package/README.md +312 -0
- package/dist/index.d.mts +447 -0
- package/dist/index.d.ts +447 -0
- package/dist/index.js +491 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +488 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +74 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var container = require('@stackra/container');
|
|
4
|
+
var contracts = require('@stackra/contracts');
|
|
5
|
+
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
8
|
+
|
|
9
|
+
// src/errors/pipeline.error.ts
|
|
10
|
+
var PipelineError = class _PipelineError extends Error {
|
|
11
|
+
static {
|
|
12
|
+
__name(this, "PipelineError");
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Machine-readable error code for programmatic handling.
|
|
16
|
+
*/
|
|
17
|
+
code;
|
|
18
|
+
/**
|
|
19
|
+
* The original error that caused this pipeline error, if any.
|
|
20
|
+
*/
|
|
21
|
+
cause;
|
|
22
|
+
/**
|
|
23
|
+
* @param message - Human-readable description of what went wrong
|
|
24
|
+
* @param code - Machine-readable error code
|
|
25
|
+
* @param cause - The original error that triggered this pipeline error
|
|
26
|
+
*/
|
|
27
|
+
constructor(message, code = "PIPELINE_ERROR", cause) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.name = "PipelineError";
|
|
30
|
+
this.code = code;
|
|
31
|
+
this.cause = cause;
|
|
32
|
+
Object.setPrototypeOf(this, _PipelineError.prototype);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
// src/services/pipeline.service.ts
|
|
37
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
38
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
39
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
40
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
41
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
42
|
+
}
|
|
43
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
44
|
+
function _ts_metadata(k, v) {
|
|
45
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
46
|
+
}
|
|
47
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
48
|
+
function _ts_param(paramIndex, decorator) {
|
|
49
|
+
return function(target, key) {
|
|
50
|
+
decorator(target, key, paramIndex);
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
__name(_ts_param, "_ts_param");
|
|
54
|
+
var DEFAULT_METHOD = "handle";
|
|
55
|
+
exports.Pipeline = class Pipeline {
|
|
56
|
+
static {
|
|
57
|
+
__name(this, "Pipeline");
|
|
58
|
+
}
|
|
59
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
60
|
+
// Private State
|
|
61
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
62
|
+
/** The value flowing through the pipeline. */
|
|
63
|
+
passable;
|
|
64
|
+
/** The ordered list of pipes to execute. */
|
|
65
|
+
pipes = [];
|
|
66
|
+
/** The method name to call on pipe objects. */
|
|
67
|
+
method = DEFAULT_METHOD;
|
|
68
|
+
/** Optional callback invoked after pipeline execution. */
|
|
69
|
+
finallyCallback;
|
|
70
|
+
/** Optional DI container for resolving string pipes. */
|
|
71
|
+
container;
|
|
72
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
73
|
+
// Constructor
|
|
74
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
75
|
+
/**
|
|
76
|
+
* @param container - Optional application context for resolving string-based pipes.
|
|
77
|
+
* When provided, string pipes are resolved via `container.get(pipeName)`.
|
|
78
|
+
* When omitted, string pipes will throw a PipelineError.
|
|
79
|
+
*/
|
|
80
|
+
constructor(container) {
|
|
81
|
+
this.container = container ?? void 0;
|
|
82
|
+
}
|
|
83
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
84
|
+
// Fluent API
|
|
85
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
86
|
+
/**
|
|
87
|
+
* Set the passable object being sent through the pipeline.
|
|
88
|
+
*
|
|
89
|
+
* @param passable - The value to send through the pipes
|
|
90
|
+
* @returns This pipeline instance for chaining
|
|
91
|
+
*/
|
|
92
|
+
send(passable) {
|
|
93
|
+
this.passable = passable;
|
|
94
|
+
return this;
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Set the array of pipes (replaces any existing pipes).
|
|
98
|
+
*
|
|
99
|
+
* @param pipes - Array of pipe definitions to process the passable through
|
|
100
|
+
* @returns This pipeline instance for chaining
|
|
101
|
+
*/
|
|
102
|
+
through(pipes) {
|
|
103
|
+
this.pipes = [
|
|
104
|
+
...pipes
|
|
105
|
+
];
|
|
106
|
+
return this;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Append additional pipes to the pipeline.
|
|
110
|
+
*
|
|
111
|
+
* Unlike `through()` which replaces all pipes, `pipe()` adds to the
|
|
112
|
+
* existing chain. Useful for conditionally extending a pipeline.
|
|
113
|
+
*
|
|
114
|
+
* @param pipes - One or more pipe definitions to append
|
|
115
|
+
* @returns This pipeline instance for chaining
|
|
116
|
+
*/
|
|
117
|
+
pipe(...pipes) {
|
|
118
|
+
this.pipes.push(...pipes);
|
|
119
|
+
return this;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Change the handler method name called on pipe objects.
|
|
123
|
+
*
|
|
124
|
+
* By default, the pipeline calls `handle(passable, next)` on pipe objects.
|
|
125
|
+
* Use `via()` to call a different method (e.g., `process`, `transform`).
|
|
126
|
+
*
|
|
127
|
+
* @param method - The method name to invoke on pipe objects
|
|
128
|
+
* @returns This pipeline instance for chaining
|
|
129
|
+
*/
|
|
130
|
+
via(method) {
|
|
131
|
+
this.method = method;
|
|
132
|
+
return this;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Register a finally callback invoked after pipeline execution.
|
|
136
|
+
*
|
|
137
|
+
* The callback receives the passable after all pipes and the destination
|
|
138
|
+
* have processed it. Useful for cleanup, logging, or telemetry.
|
|
139
|
+
*
|
|
140
|
+
* @param callback - Function to call with the passable after execution
|
|
141
|
+
* @returns This pipeline instance for chaining
|
|
142
|
+
*/
|
|
143
|
+
finally(callback) {
|
|
144
|
+
this.finallyCallback = callback;
|
|
145
|
+
return this;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Execute the pipeline with a final destination closure.
|
|
149
|
+
*
|
|
150
|
+
* The destination is the innermost function — it receives the passable
|
|
151
|
+
* after all pipes have processed it. The pipeline is built using
|
|
152
|
+
* `reduceRight` (equivalent to Laravel's `array_reduce(array_reverse(...))`).
|
|
153
|
+
*
|
|
154
|
+
* @typeParam R - The return type of the destination
|
|
155
|
+
* @param destination - Final handler that produces the pipeline's return value
|
|
156
|
+
* @returns The result of the destination function
|
|
157
|
+
* @throws {PipelineError} When a pipe cannot be resolved or executed
|
|
158
|
+
*/
|
|
159
|
+
then(destination) {
|
|
160
|
+
const pipeline = this.pipes.reduceRight((next, pipe) => {
|
|
161
|
+
return (passable) => {
|
|
162
|
+
return this.carry(pipe, passable, next);
|
|
163
|
+
};
|
|
164
|
+
}, this.prepareDestination(destination));
|
|
165
|
+
const result = pipeline(this.passable);
|
|
166
|
+
if (this.finallyCallback) {
|
|
167
|
+
this.finallyCallback(this.passable);
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Execute the pipeline and return the passable as-is.
|
|
173
|
+
*
|
|
174
|
+
* Equivalent to `then((passable) => passable)`. Useful when the pipeline
|
|
175
|
+
* transforms the passable in place and no final destination is needed.
|
|
176
|
+
*
|
|
177
|
+
* @returns The passable after all pipes have processed it
|
|
178
|
+
*/
|
|
179
|
+
thenReturn() {
|
|
180
|
+
return this.then((passable) => passable);
|
|
181
|
+
}
|
|
182
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
183
|
+
// Private Helpers
|
|
184
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
185
|
+
/**
|
|
186
|
+
* Wrap the destination in a closure matching the pipeline's signature.
|
|
187
|
+
*
|
|
188
|
+
* @param destination - The final handler
|
|
189
|
+
* @returns A closure compatible with the reduce chain
|
|
190
|
+
*/
|
|
191
|
+
prepareDestination(destination) {
|
|
192
|
+
return (passable) => {
|
|
193
|
+
return destination(passable);
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Invoke a single pipe with the passable and next callback.
|
|
198
|
+
*
|
|
199
|
+
* Resolves the pipe type and delegates to the appropriate handler:
|
|
200
|
+
* - Function → called directly
|
|
201
|
+
* - String → resolved from container, method called
|
|
202
|
+
* - Array (tuple) → first element resolved, rest are params
|
|
203
|
+
* - Object → method called directly
|
|
204
|
+
*
|
|
205
|
+
* @param pipe - The pipe to invoke
|
|
206
|
+
* @param passable - The current passable value
|
|
207
|
+
* @param next - The next function in the chain
|
|
208
|
+
* @returns The result of invoking the pipe
|
|
209
|
+
* @throws {PipelineError} When the pipe cannot be resolved or invoked
|
|
210
|
+
*/
|
|
211
|
+
carry(pipe, passable, next) {
|
|
212
|
+
try {
|
|
213
|
+
if (Array.isArray(pipe)) {
|
|
214
|
+
return this.handleTuplePipe(pipe, passable, next);
|
|
215
|
+
}
|
|
216
|
+
if (typeof pipe === "function") {
|
|
217
|
+
return pipe(passable, next);
|
|
218
|
+
}
|
|
219
|
+
if (typeof pipe === "string") {
|
|
220
|
+
return this.handleStringPipe(pipe, passable, next, []);
|
|
221
|
+
}
|
|
222
|
+
if (typeof pipe === "object" && pipe !== null) {
|
|
223
|
+
return this.handleObjectPipe(pipe, passable, next, []);
|
|
224
|
+
}
|
|
225
|
+
throw new PipelineError(`Invalid pipe type: ${typeof pipe}. Expected function, string, object, or tuple.`, "INVALID_PIPE_TYPE");
|
|
226
|
+
} catch (error) {
|
|
227
|
+
if (error instanceof PipelineError) {
|
|
228
|
+
throw error;
|
|
229
|
+
}
|
|
230
|
+
const err = error;
|
|
231
|
+
throw new PipelineError(`Pipe execution failed: ${err.message}`, "PIPE_EXECUTION_FAILED", err);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Handle a tuple pipe by extracting the pipe and its parameters.
|
|
236
|
+
*
|
|
237
|
+
* @param tuple - The [pipe, ...params] tuple
|
|
238
|
+
* @param passable - The current passable
|
|
239
|
+
* @param next - The next function
|
|
240
|
+
* @returns The result of invoking the pipe
|
|
241
|
+
*/
|
|
242
|
+
handleTuplePipe(tuple, passable, next) {
|
|
243
|
+
const [pipeEntry, ...params] = tuple;
|
|
244
|
+
if (typeof pipeEntry === "function") {
|
|
245
|
+
return pipeEntry(passable, next);
|
|
246
|
+
}
|
|
247
|
+
if (typeof pipeEntry === "string") {
|
|
248
|
+
return this.handleStringPipe(pipeEntry, passable, next, params);
|
|
249
|
+
}
|
|
250
|
+
if (typeof pipeEntry === "object" && pipeEntry !== null) {
|
|
251
|
+
return this.handleObjectPipe(pipeEntry, passable, next, params);
|
|
252
|
+
}
|
|
253
|
+
throw new PipelineError(`Invalid pipe entry in tuple: ${typeof pipeEntry}.`, "INVALID_PIPE_ENTRY");
|
|
254
|
+
}
|
|
255
|
+
/**
|
|
256
|
+
* Resolve a string pipe from the container and invoke its handler method.
|
|
257
|
+
*
|
|
258
|
+
* @param name - The service name/token to resolve
|
|
259
|
+
* @param passable - The current passable
|
|
260
|
+
* @param next - The next function
|
|
261
|
+
* @param params - Additional parameters to pass to the handler
|
|
262
|
+
* @returns The result of invoking the resolved pipe
|
|
263
|
+
* @throws {PipelineError} When no container is available or resolution fails
|
|
264
|
+
*/
|
|
265
|
+
handleStringPipe(name, passable, next, params) {
|
|
266
|
+
if (!this.container) {
|
|
267
|
+
throw new PipelineError(`Cannot resolve pipe "${name}": no DI container available. Provide a container to the Pipeline constructor or use object/function pipes.`, "NO_CONTAINER");
|
|
268
|
+
}
|
|
269
|
+
let pipeInstance;
|
|
270
|
+
try {
|
|
271
|
+
pipeInstance = this.container.get(name);
|
|
272
|
+
} catch (error) {
|
|
273
|
+
const err = error;
|
|
274
|
+
throw new PipelineError(`Failed to resolve pipe "${name}" from the container: ${err.message}`, "PIPE_RESOLUTION_FAILED", err);
|
|
275
|
+
}
|
|
276
|
+
if (!pipeInstance || typeof pipeInstance !== "object") {
|
|
277
|
+
throw new PipelineError(`Resolved pipe "${name}" is not an object. Got: ${typeof pipeInstance}`, "INVALID_RESOLVED_PIPE");
|
|
278
|
+
}
|
|
279
|
+
return this.handleObjectPipe(pipeInstance, passable, next, params);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Invoke a handler method on a pipe object instance.
|
|
283
|
+
*
|
|
284
|
+
* @param pipe - The pipe object instance
|
|
285
|
+
* @param passable - The current passable
|
|
286
|
+
* @param next - The next function
|
|
287
|
+
* @param params - Additional parameters to pass after passable and next
|
|
288
|
+
* @returns The result of invoking the method
|
|
289
|
+
* @throws {PipelineError} When the method does not exist on the pipe
|
|
290
|
+
*/
|
|
291
|
+
handleObjectPipe(pipe, passable, next, params) {
|
|
292
|
+
const method = this.method;
|
|
293
|
+
const handler = pipe[method];
|
|
294
|
+
if (typeof handler !== "function") {
|
|
295
|
+
const pipeName = pipe.constructor?.name ?? "anonymous";
|
|
296
|
+
throw new PipelineError(`Pipe "${pipeName}" does not have a "${method}" method.`, "METHOD_NOT_FOUND");
|
|
297
|
+
}
|
|
298
|
+
return handler.call(pipe, passable, next, ...params);
|
|
299
|
+
}
|
|
300
|
+
};
|
|
301
|
+
exports.Pipeline = _ts_decorate([
|
|
302
|
+
container.Injectable(),
|
|
303
|
+
_ts_param(0, container.Optional()),
|
|
304
|
+
_ts_param(0, container.Inject(contracts.APPLICATION)),
|
|
305
|
+
_ts_metadata("design:type", Function),
|
|
306
|
+
_ts_metadata("design:paramtypes", [
|
|
307
|
+
typeof IApplication === "undefined" ? Object : IApplication
|
|
308
|
+
])
|
|
309
|
+
], exports.Pipeline);
|
|
310
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
311
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
312
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
313
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
314
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
315
|
+
}
|
|
316
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
317
|
+
function _ts_metadata2(k, v) {
|
|
318
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
319
|
+
}
|
|
320
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
321
|
+
function _ts_param2(paramIndex, decorator) {
|
|
322
|
+
return function(target, key) {
|
|
323
|
+
decorator(target, key, paramIndex);
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
__name(_ts_param2, "_ts_param");
|
|
327
|
+
exports.PipelineHub = class PipelineHub {
|
|
328
|
+
static {
|
|
329
|
+
__name(this, "PipelineHub");
|
|
330
|
+
}
|
|
331
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
332
|
+
// Private State
|
|
333
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
334
|
+
/** Registry of named pipeline definitions. */
|
|
335
|
+
pipelines = /* @__PURE__ */ new Map();
|
|
336
|
+
/** Default pipeline definition (fallback when no name specified). */
|
|
337
|
+
defaultPipeline;
|
|
338
|
+
/** Optional DI container for creating Pipeline instances. */
|
|
339
|
+
container;
|
|
340
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
341
|
+
// Constructor
|
|
342
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
343
|
+
/**
|
|
344
|
+
* @param container - Optional application context passed to created Pipeline instances
|
|
345
|
+
*/
|
|
346
|
+
constructor(container) {
|
|
347
|
+
this.container = container ?? void 0;
|
|
348
|
+
}
|
|
349
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
350
|
+
// Public API
|
|
351
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
352
|
+
/**
|
|
353
|
+
* Register the default pipeline definition.
|
|
354
|
+
*
|
|
355
|
+
* The default pipeline is used when `pipe()` is called without a name.
|
|
356
|
+
*
|
|
357
|
+
* @param callback - Pipeline definition that configures and executes a pipeline
|
|
358
|
+
*/
|
|
359
|
+
defaults(callback) {
|
|
360
|
+
this.defaultPipeline = callback;
|
|
361
|
+
}
|
|
362
|
+
/**
|
|
363
|
+
* Register a named pipeline definition.
|
|
364
|
+
*
|
|
365
|
+
* @param name - Unique name for this pipeline preset
|
|
366
|
+
* @param callback - Pipeline definition that configures and executes a pipeline
|
|
367
|
+
* @throws {PipelineError} When the name is empty
|
|
368
|
+
*/
|
|
369
|
+
pipeline(name, callback) {
|
|
370
|
+
if (!name) {
|
|
371
|
+
throw new PipelineError("Pipeline name cannot be empty.", "INVALID_PIPELINE_NAME");
|
|
372
|
+
}
|
|
373
|
+
this.pipelines.set(name, callback);
|
|
374
|
+
}
|
|
375
|
+
/**
|
|
376
|
+
* Execute a named (or default) pipeline with the given passable.
|
|
377
|
+
*
|
|
378
|
+
* Creates a fresh Pipeline instance, passes it along with the passable
|
|
379
|
+
* to the registered definition, and returns the result.
|
|
380
|
+
*
|
|
381
|
+
* @typeParam T - The type of the passable value
|
|
382
|
+
* @param passable - The value to send through the pipeline
|
|
383
|
+
* @param pipelineName - Optional name of the pipeline preset to execute.
|
|
384
|
+
* Falls back to the default pipeline if omitted.
|
|
385
|
+
* @returns The result of the pipeline execution
|
|
386
|
+
* @throws {PipelineError} When the named pipeline or default is not registered
|
|
387
|
+
*/
|
|
388
|
+
pipe(passable, pipelineName) {
|
|
389
|
+
const definition = this.resolveDefinition(pipelineName);
|
|
390
|
+
const pipeline = new exports.Pipeline(this.container);
|
|
391
|
+
return definition(pipeline, passable);
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Check if a named pipeline definition exists in the registry.
|
|
395
|
+
*
|
|
396
|
+
* @param name - The pipeline name to check
|
|
397
|
+
* @returns True if the pipeline is registered
|
|
398
|
+
*/
|
|
399
|
+
has(name) {
|
|
400
|
+
return this.pipelines.has(name);
|
|
401
|
+
}
|
|
402
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
403
|
+
// Private Helpers
|
|
404
|
+
// ══════════════════════════════════════════════════════════════════════════════
|
|
405
|
+
/**
|
|
406
|
+
* Resolve a pipeline definition by name or fall back to the default.
|
|
407
|
+
*
|
|
408
|
+
* @param name - Optional pipeline name
|
|
409
|
+
* @returns The resolved pipeline definition
|
|
410
|
+
* @throws {PipelineError} When the definition cannot be found
|
|
411
|
+
*/
|
|
412
|
+
resolveDefinition(name) {
|
|
413
|
+
if (name) {
|
|
414
|
+
const definition = this.pipelines.get(name);
|
|
415
|
+
if (!definition) {
|
|
416
|
+
throw new PipelineError(`Pipeline "${name}" is not registered in the hub.`, "PIPELINE_NOT_FOUND");
|
|
417
|
+
}
|
|
418
|
+
return definition;
|
|
419
|
+
}
|
|
420
|
+
if (!this.defaultPipeline) {
|
|
421
|
+
throw new PipelineError("No default pipeline is registered. Call hub.defaults() or provide a pipeline name.", "NO_DEFAULT_PIPELINE");
|
|
422
|
+
}
|
|
423
|
+
return this.defaultPipeline;
|
|
424
|
+
}
|
|
425
|
+
};
|
|
426
|
+
exports.PipelineHub = _ts_decorate2([
|
|
427
|
+
container.Injectable(),
|
|
428
|
+
_ts_param2(0, container.Optional()),
|
|
429
|
+
_ts_param2(0, container.Inject(contracts.APPLICATION)),
|
|
430
|
+
_ts_metadata2("design:type", Function),
|
|
431
|
+
_ts_metadata2("design:paramtypes", [
|
|
432
|
+
typeof IApplication === "undefined" ? Object : IApplication
|
|
433
|
+
])
|
|
434
|
+
], exports.PipelineHub);
|
|
435
|
+
|
|
436
|
+
// src/constants/pipeline.constant.ts
|
|
437
|
+
var PIPELINE_FACTORY = /* @__PURE__ */ Symbol.for("PIPELINE_FACTORY");
|
|
438
|
+
|
|
439
|
+
// src/pipeline.module.ts
|
|
440
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
441
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
442
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
443
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
444
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
445
|
+
}
|
|
446
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
447
|
+
exports.PipelineModule = class _PipelineModule {
|
|
448
|
+
static {
|
|
449
|
+
__name(this, "PipelineModule");
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* Register the pipeline module globally.
|
|
453
|
+
*
|
|
454
|
+
* @returns Dynamic module definition with PipelineHub and factory
|
|
455
|
+
*/
|
|
456
|
+
static forRoot() {
|
|
457
|
+
return {
|
|
458
|
+
module: _PipelineModule,
|
|
459
|
+
global: true,
|
|
460
|
+
providers: [
|
|
461
|
+
// PipelineHub singleton
|
|
462
|
+
exports.PipelineHub,
|
|
463
|
+
// Pipeline factory — creates fresh instances on demand
|
|
464
|
+
{
|
|
465
|
+
provide: PIPELINE_FACTORY,
|
|
466
|
+
useFactory: /* @__PURE__ */ __name((app) => {
|
|
467
|
+
return () => new exports.Pipeline(app);
|
|
468
|
+
}, "useFactory"),
|
|
469
|
+
inject: [
|
|
470
|
+
{
|
|
471
|
+
token: contracts.APPLICATION,
|
|
472
|
+
optional: true
|
|
473
|
+
}
|
|
474
|
+
]
|
|
475
|
+
}
|
|
476
|
+
],
|
|
477
|
+
exports: [
|
|
478
|
+
exports.PipelineHub,
|
|
479
|
+
PIPELINE_FACTORY
|
|
480
|
+
]
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
exports.PipelineModule = _ts_decorate3([
|
|
485
|
+
container.Module({})
|
|
486
|
+
], exports.PipelineModule);
|
|
487
|
+
|
|
488
|
+
exports.PIPELINE_FACTORY = PIPELINE_FACTORY;
|
|
489
|
+
exports.PipelineError = PipelineError;
|
|
490
|
+
//# sourceMappingURL=index.js.map
|
|
491
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/errors/pipeline.error.ts","../src/services/pipeline.service.ts","../src/services/pipeline-hub.service.ts","../src/constants/pipeline.constant.ts","../src/pipeline.module.ts"],"names":["PipelineError","Error","code","cause","message","name","Object","setPrototypeOf","prototype","DEFAULT_METHOD","Pipeline","passable","pipes","method","finallyCallback","container","undefined","send","through","pipe","push","via","finally","callback","then","destination","pipeline","reduceRight","next","carry","prepareDestination","result","thenReturn","Array","isArray","handleTuplePipe","handleStringPipe","handleObjectPipe","error","err","tuple","pipeEntry","params","pipeInstance","get","handler","pipeName","call","PipelineHub","pipelines","Map","defaultPipeline","defaults","set","pipelineName","definition","resolveDefinition","has","PIPELINE_FACTORY","Symbol","for","PipelineModule","forRoot","module","global","providers","provide","useFactory","app","inject","token","APPLICATION","optional","exports"],"mappings":"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","file":"index.js","sourcesContent":["/**\n * @file pipeline.error.ts\n * @module @stackra/pipeline/errors\n * @description Pipeline-specific error class for all pipeline operation failures.\n * Thrown when a pipe cannot be resolved, the pipeline is misconfigured,\n * or a pipe throws during execution.\n */\n\n// ════════════════════════════════════════════════════════════════════════════════\n// Error Class\n// ════════════════════════════════════════════════════════════════════════════════\n\n/**\n * Error thrown when a pipeline operation fails.\n *\n * Covers scenarios such as:\n * - Pipe resolution failure (string pipe not found in container)\n * - Invalid pipe type provided\n * - Pipeline method not found on a pipe object\n * - Pipe execution failure (wrapped original error)\n *\n * @example\n * ```typescript\n * throw new PipelineError(\n * 'Pipe \"ValidateInput\" could not be resolved from the container.',\n * 'PIPE_RESOLUTION_FAILED',\n * );\n * ```\n */\nexport class PipelineError extends Error {\n /**\n * Machine-readable error code for programmatic handling.\n */\n public readonly code: string;\n\n /**\n * The original error that caused this pipeline error, if any.\n */\n public override readonly cause?: Error;\n\n /**\n * @param message - Human-readable description of what went wrong\n * @param code - Machine-readable error code\n * @param cause - The original error that triggered this pipeline error\n */\n public constructor(message: string, code: string = 'PIPELINE_ERROR', cause?: Error) {\n super(message);\n this.name = 'PipelineError';\n this.code = code;\n this.cause = cause;\n Object.setPrototypeOf(this, PipelineError.prototype);\n }\n}\n","/**\n * @file pipeline.service.ts\n * @module @stackra/pipeline/services\n * @description Laravel-style middleware Pipeline with fluent API and DI container integration.\n * Implements the classic array_reduce(reverse(pipes), carry(), destination) pattern\n * for composing middleware chains with type safety.\n */\n\nimport { Injectable, Optional, Inject } from '@stackra/container';\nimport { APPLICATION } from '@stackra/contracts';\nimport type { IApplication } from '@stackra/contracts';\n\nimport type { PipeType, PipeTuple } from '../interfaces';\nimport { PipelineError } from '../errors';\n\n// ════════════════════════════════════════════════════════════════════════════════\n// Constants\n// ════════════════════════════════════════════════════════════════════════════════\n\n/** Default method name called on pipe objects. */\nconst DEFAULT_METHOD = 'handle';\n\n// ════════════════════════════════════════════════════════════════════════════════\n// Pipeline Class\n// ════════════════════════════════════════════════════════════════════════════════\n\n/**\n * Laravel-style middleware pipeline with fluent API.\n *\n * Composes a chain of pipes (middleware) that process a passable value\n * sequentially. Each pipe can inspect, modify, or short-circuit the passable\n * before passing it to the next pipe in the chain.\n *\n * The pipeline supports four pipe types:\n * - **Closure** — `(passable, next) => result`\n * - **String** — resolved from the DI container by name\n * - **Object** — instance with a handler method (default: `handle`)\n * - **Tuple** — `[pipe, ...params]` for parameterized pipes\n *\n * Core algorithm (mirrors Laravel):\n * ```\n * pipeline = array_reduce(reverse(pipes), carry(), prepareDestination(destination))\n * return pipeline(passable)\n * ```\n *\n * @typeParam TPassable - The type of value flowing through the pipeline\n * @typeParam TReturn - The type returned by the pipeline's `then()` method\n *\n * @example\n * ```typescript\n * const result = new Pipeline<Request, Response>(container)\n * .send(request)\n * .through([AuthMiddleware, LoggingMiddleware])\n * .then((req) => handleRequest(req));\n * ```\n */\n@Injectable()\nexport class Pipeline<TPassable = unknown, TReturn = TPassable> {\n // ══════════════════════════════════════════════════════════════════════════════\n // Private State\n // ══════════════════════════════════════════════════════════════════════════════\n\n /** The value flowing through the pipeline. */\n private passable!: TPassable;\n\n /** The ordered list of pipes to execute. */\n private pipes: PipeType[] = [];\n\n /** The method name to call on pipe objects. */\n private method: string = DEFAULT_METHOD;\n\n /** Optional callback invoked after pipeline execution. */\n private finallyCallback?: (passable: TPassable) => void;\n\n /** Optional DI container for resolving string pipes. */\n private readonly container?: IApplication;\n\n // ══════════════════════════════════════════════════════════════════════════════\n // Constructor\n // ══════════════════════════════════════════════════════════════════════════════\n\n /**\n * @param container - Optional application context for resolving string-based pipes.\n * When provided, string pipes are resolved via `container.get(pipeName)`.\n * When omitted, string pipes will throw a PipelineError.\n */\n public constructor(@Optional() @Inject(APPLICATION) container?: IApplication) {\n this.container = container ?? undefined;\n }\n\n // ══════════════════════════════════════════════════════════════════════════════\n // Fluent API\n // ══════════════════════════════════════════════════════════════════════════════\n\n /**\n * Set the passable object being sent through the pipeline.\n *\n * @param passable - The value to send through the pipes\n * @returns This pipeline instance for chaining\n */\n public send(passable: TPassable): this {\n this.passable = passable;\n return this;\n }\n\n /**\n * Set the array of pipes (replaces any existing pipes).\n *\n * @param pipes - Array of pipe definitions to process the passable through\n * @returns This pipeline instance for chaining\n */\n public through(pipes: PipeType[]): this {\n this.pipes = [...pipes];\n return this;\n }\n\n /**\n * Append additional pipes to the pipeline.\n *\n * Unlike `through()` which replaces all pipes, `pipe()` adds to the\n * existing chain. Useful for conditionally extending a pipeline.\n *\n * @param pipes - One or more pipe definitions to append\n * @returns This pipeline instance for chaining\n */\n public pipe(...pipes: PipeType[]): this {\n this.pipes.push(...pipes);\n return this;\n }\n\n /**\n * Change the handler method name called on pipe objects.\n *\n * By default, the pipeline calls `handle(passable, next)` on pipe objects.\n * Use `via()` to call a different method (e.g., `process`, `transform`).\n *\n * @param method - The method name to invoke on pipe objects\n * @returns This pipeline instance for chaining\n */\n public via(method: string): this {\n this.method = method;\n return this;\n }\n\n /**\n * Register a finally callback invoked after pipeline execution.\n *\n * The callback receives the passable after all pipes and the destination\n * have processed it. Useful for cleanup, logging, or telemetry.\n *\n * @param callback - Function to call with the passable after execution\n * @returns This pipeline instance for chaining\n */\n public finally(callback: (passable: TPassable) => void): this {\n this.finallyCallback = callback;\n return this;\n }\n\n /**\n * Execute the pipeline with a final destination closure.\n *\n * The destination is the innermost function — it receives the passable\n * after all pipes have processed it. The pipeline is built using\n * `reduceRight` (equivalent to Laravel's `array_reduce(array_reverse(...))`).\n *\n * @typeParam R - The return type of the destination\n * @param destination - Final handler that produces the pipeline's return value\n * @returns The result of the destination function\n * @throws {PipelineError} When a pipe cannot be resolved or executed\n */\n public then<R = TReturn>(destination: (passable: TPassable) => R): R {\n const pipeline = this.pipes.reduceRight<(passable: TPassable) => R>((next, pipe) => {\n return (passable: TPassable): R => {\n return this.carry(pipe, passable, next as (passable: TPassable) => unknown) as R;\n };\n }, this.prepareDestination(destination));\n\n const result = pipeline(this.passable);\n\n if (this.finallyCallback) {\n this.finallyCallback(this.passable);\n }\n\n return result;\n }\n\n /**\n * Execute the pipeline and return the passable as-is.\n *\n * Equivalent to `then((passable) => passable)`. Useful when the pipeline\n * transforms the passable in place and no final destination is needed.\n *\n * @returns The passable after all pipes have processed it\n */\n public thenReturn(): TPassable {\n return this.then((passable) => passable) as unknown as TPassable;\n }\n\n // ══════════════════════════════════════════════════════════════════════════════\n // Private Helpers\n // ══════════════════════════════════════════════════════════════════════════════\n\n /**\n * Wrap the destination in a closure matching the pipeline's signature.\n *\n * @param destination - The final handler\n * @returns A closure compatible with the reduce chain\n */\n private prepareDestination<R>(\n destination: (passable: TPassable) => R\n ): (passable: TPassable) => R {\n return (passable: TPassable): R => {\n return destination(passable);\n };\n }\n\n /**\n * Invoke a single pipe with the passable and next callback.\n *\n * Resolves the pipe type and delegates to the appropriate handler:\n * - Function → called directly\n * - String → resolved from container, method called\n * - Array (tuple) → first element resolved, rest are params\n * - Object → method called directly\n *\n * @param pipe - The pipe to invoke\n * @param passable - The current passable value\n * @param next - The next function in the chain\n * @returns The result of invoking the pipe\n * @throws {PipelineError} When the pipe cannot be resolved or invoked\n */\n private carry(\n pipe: PipeType,\n passable: TPassable,\n next: (passable: TPassable) => unknown\n ): unknown {\n try {\n // Tuple pipe: [pipe, ...params]\n if (Array.isArray(pipe)) {\n return this.handleTuplePipe(pipe as PipeTuple, passable, next);\n }\n\n // Function pipe\n if (typeof pipe === 'function') {\n return (pipe as (passable: TPassable, next: (passable: TPassable) => unknown) => unknown)(\n passable,\n next\n );\n }\n\n // String pipe (container resolution)\n if (typeof pipe === 'string') {\n return this.handleStringPipe(pipe, passable, next, []);\n }\n\n // Object pipe (instance with handler method)\n if (typeof pipe === 'object' && pipe !== null) {\n return this.handleObjectPipe(pipe, passable, next, []);\n }\n\n throw new PipelineError(\n `Invalid pipe type: ${typeof pipe}. Expected function, string, object, or tuple.`,\n 'INVALID_PIPE_TYPE'\n );\n } catch (error: unknown) {\n if (error instanceof PipelineError) {\n throw error;\n }\n const err = error as Error;\n throw new PipelineError(\n `Pipe execution failed: ${err.message}`,\n 'PIPE_EXECUTION_FAILED',\n err\n );\n }\n }\n\n /**\n * Handle a tuple pipe by extracting the pipe and its parameters.\n *\n * @param tuple - The [pipe, ...params] tuple\n * @param passable - The current passable\n * @param next - The next function\n * @returns The result of invoking the pipe\n */\n private handleTuplePipe(\n tuple: PipeTuple,\n passable: TPassable,\n next: (passable: TPassable) => unknown\n ): unknown {\n const [pipeEntry, ...params] = tuple;\n\n if (typeof pipeEntry === 'function') {\n return (\n pipeEntry as (passable: TPassable, next: (passable: TPassable) => unknown) => unknown\n )(passable, next);\n }\n\n if (typeof pipeEntry === 'string') {\n return this.handleStringPipe(pipeEntry, passable, next, params);\n }\n\n if (typeof pipeEntry === 'object' && pipeEntry !== null) {\n return this.handleObjectPipe(pipeEntry, passable, next, params);\n }\n\n throw new PipelineError(\n `Invalid pipe entry in tuple: ${typeof pipeEntry}.`,\n 'INVALID_PIPE_ENTRY'\n );\n }\n\n /**\n * Resolve a string pipe from the container and invoke its handler method.\n *\n * @param name - The service name/token to resolve\n * @param passable - The current passable\n * @param next - The next function\n * @param params - Additional parameters to pass to the handler\n * @returns The result of invoking the resolved pipe\n * @throws {PipelineError} When no container is available or resolution fails\n */\n private handleStringPipe(\n name: string,\n passable: TPassable,\n next: (passable: TPassable) => unknown,\n params: unknown[]\n ): unknown {\n if (!this.container) {\n throw new PipelineError(\n `Cannot resolve pipe \"${name}\": no DI container available. ` +\n 'Provide a container to the Pipeline constructor or use object/function pipes.',\n 'NO_CONTAINER'\n );\n }\n\n let pipeInstance: unknown;\n try {\n pipeInstance = this.container.get(name);\n } catch (error: unknown) {\n const err = error as Error;\n throw new PipelineError(\n `Failed to resolve pipe \"${name}\" from the container: ${err.message}`,\n 'PIPE_RESOLUTION_FAILED',\n err\n );\n }\n\n if (!pipeInstance || typeof pipeInstance !== 'object') {\n throw new PipelineError(\n `Resolved pipe \"${name}\" is not an object. Got: ${typeof pipeInstance}`,\n 'INVALID_RESOLVED_PIPE'\n );\n }\n\n return this.handleObjectPipe(pipeInstance, passable, next, params);\n }\n\n /**\n * Invoke a handler method on a pipe object instance.\n *\n * @param pipe - The pipe object instance\n * @param passable - The current passable\n * @param next - The next function\n * @param params - Additional parameters to pass after passable and next\n * @returns The result of invoking the method\n * @throws {PipelineError} When the method does not exist on the pipe\n */\n private handleObjectPipe(\n pipe: object,\n passable: TPassable,\n next: (passable: TPassable) => unknown,\n params: unknown[]\n ): unknown {\n const method = this.method;\n const handler = (pipe as Record<string, unknown>)[method];\n\n if (typeof handler !== 'function') {\n const pipeName = pipe.constructor?.name ?? 'anonymous';\n throw new PipelineError(\n `Pipe \"${pipeName}\" does not have a \"${method}\" method.`,\n 'METHOD_NOT_FOUND'\n );\n }\n\n return (handler as (...args: unknown[]) => unknown).call(pipe, passable, next, ...params);\n }\n}\n","/**\n * @file pipeline-hub.service.ts\n * @module @stackra/pipeline/services\n * @description Named pipeline presets registry (Hub).\n * Allows registering named pipeline definitions and executing them by name.\n * Equivalent to Laravel's Pipeline Hub concept for reusable pipeline configurations.\n */\n\nimport { Injectable, Optional, Inject } from '@stackra/container';\nimport { APPLICATION } from '@stackra/contracts';\nimport type { IApplication } from '@stackra/contracts';\n\nimport type { PipelineDefinition } from '../interfaces';\nimport { Pipeline } from './pipeline.service';\nimport { PipelineError } from '../errors';\n\n// ════════════════════════════════════════════════════════════════════════════════\n// Hub Class\n// ════════════════════════════════════════════════════════════════════════════════\n\n/**\n * Named pipeline presets registry.\n *\n * The Hub stores named pipeline definitions that can be executed by name.\n * This enables reusable pipeline configurations that multiple consumers\n * can invoke without knowing the pipe chain details.\n *\n * A `defaults` pipeline is used as the fallback when no specific pipeline\n * name is provided to `pipe()`.\n *\n * @example\n * ```typescript\n * const hub = new PipelineHub(container);\n *\n * hub.pipeline('order-validation', (pipeline, passable) =>\n * pipeline\n * .send(passable)\n * .through([ValidateStock, ValidatePayment, ValidateAddress])\n * .thenReturn()\n * );\n *\n * const result = hub.pipe(orderData, 'order-validation');\n * ```\n */\n@Injectable()\nexport class PipelineHub {\n // ══════════════════════════════════════════════════════════════════════════════\n // Private State\n // ══════════════════════════════════════════════════════════════════════════════\n\n /** Registry of named pipeline definitions. */\n private readonly pipelines: Map<string, PipelineDefinition> = new Map();\n\n /** Default pipeline definition (fallback when no name specified). */\n private defaultPipeline?: PipelineDefinition;\n\n /** Optional DI container for creating Pipeline instances. */\n private readonly container?: IApplication;\n\n // ══════════════════════════════════════════════════════════════════════════════\n // Constructor\n // ══════════════════════════════════════════════════════════════════════════════\n\n /**\n * @param container - Optional application context passed to created Pipeline instances\n */\n public constructor(@Optional() @Inject(APPLICATION) container?: IApplication) {\n this.container = container ?? undefined;\n }\n\n // ══════════════════════════════════════════════════════════════════════════════\n // Public API\n // ══════════════════════════════════════════════════════════════════════════════\n\n /**\n * Register the default pipeline definition.\n *\n * The default pipeline is used when `pipe()` is called without a name.\n *\n * @param callback - Pipeline definition that configures and executes a pipeline\n */\n public defaults(callback: PipelineDefinition): void {\n this.defaultPipeline = callback;\n }\n\n /**\n * Register a named pipeline definition.\n *\n * @param name - Unique name for this pipeline preset\n * @param callback - Pipeline definition that configures and executes a pipeline\n * @throws {PipelineError} When the name is empty\n */\n public pipeline(name: string, callback: PipelineDefinition): void {\n if (!name) {\n throw new PipelineError('Pipeline name cannot be empty.', 'INVALID_PIPELINE_NAME');\n }\n this.pipelines.set(name, callback);\n }\n\n /**\n * Execute a named (or default) pipeline with the given passable.\n *\n * Creates a fresh Pipeline instance, passes it along with the passable\n * to the registered definition, and returns the result.\n *\n * @typeParam T - The type of the passable value\n * @param passable - The value to send through the pipeline\n * @param pipelineName - Optional name of the pipeline preset to execute.\n * Falls back to the default pipeline if omitted.\n * @returns The result of the pipeline execution\n * @throws {PipelineError} When the named pipeline or default is not registered\n */\n public pipe<T>(passable: T, pipelineName?: string): unknown {\n const definition = this.resolveDefinition(pipelineName);\n const pipeline = new Pipeline(this.container);\n\n return definition(pipeline, passable);\n }\n\n /**\n * Check if a named pipeline definition exists in the registry.\n *\n * @param name - The pipeline name to check\n * @returns True if the pipeline is registered\n */\n public has(name: string): boolean {\n return this.pipelines.has(name);\n }\n\n // ══════════════════════════════════════════════════════════════════════════════\n // Private Helpers\n // ══════════════════════════════════════════════════════════════════════════════\n\n /**\n * Resolve a pipeline definition by name or fall back to the default.\n *\n * @param name - Optional pipeline name\n * @returns The resolved pipeline definition\n * @throws {PipelineError} When the definition cannot be found\n */\n private resolveDefinition(name?: string): PipelineDefinition {\n if (name) {\n const definition = this.pipelines.get(name);\n if (!definition) {\n throw new PipelineError(\n `Pipeline \"${name}\" is not registered in the hub.`,\n 'PIPELINE_NOT_FOUND'\n );\n }\n return definition;\n }\n\n if (!this.defaultPipeline) {\n throw new PipelineError(\n 'No default pipeline is registered. Call hub.defaults() or provide a pipeline name.',\n 'NO_DEFAULT_PIPELINE'\n );\n }\n\n return this.defaultPipeline;\n }\n}\n","/**\n * @file pipeline.constant.ts\n * @module @stackra/pipeline/constants\n * @description DI tokens and constants for the pipeline module.\n */\n\n// ════════════════════════════════════════════════════════════════════════════════\n// DI Tokens\n// ════════════════════════════════════════════════════════════════════════════════\n\n/**\n * DI token for the pipeline factory function.\n *\n * Inject this token to obtain a factory that creates fresh Pipeline instances.\n * Each call to the factory produces a new Pipeline with no residual state.\n *\n * @example\n * ```typescript\n * @Inject(PIPELINE_FACTORY) private readonly createPipeline: PipelineFactory\n * ```\n */\nexport const PIPELINE_FACTORY = Symbol.for('PIPELINE_FACTORY');\n","/**\n * @file pipeline.module.ts\n * @module @stackra/pipeline\n * @description DI module for the pipeline system.\n * Registers the PipelineHub as a global singleton and provides a\n * PIPELINE_FACTORY token for creating fresh Pipeline instances on demand.\n *\n * The Pipeline class itself is NOT a singleton — each usage creates a new\n * instance via the factory. This ensures no state leaks between usages.\n */\n\nimport { Module } from '@stackra/container';\nimport type { DynamicModule } from '@stackra/contracts';\nimport { APPLICATION } from '@stackra/contracts';\nimport type { IApplication } from '@stackra/contracts';\n\nimport { PipelineHub } from './services';\nimport { Pipeline } from './services';\nimport { PIPELINE_FACTORY } from './constants';\n\n// ════════════════════════════════════════════════════════════════════════════════\n// Module\n// ════════════════════════════════════════════════════════════════════════════════\n\n/**\n * Pipeline DI module.\n *\n * Provides:\n * - `PipelineHub` — global singleton for named pipeline presets\n * - `PIPELINE_FACTORY` — factory function for creating fresh Pipeline instances\n *\n * The Pipeline is intentionally NOT registered as a singleton. Each call to\n * the factory produces a new instance with clean state. This mirrors Laravel's\n * pattern where `app(Pipeline::class)` always returns a fresh pipeline.\n *\n * @example\n * ```typescript\n * import { PipelineModule } from '@stackra/pipeline';\n *\n * @Module({\n * imports: [PipelineModule.forRoot()],\n * })\n * export class AppModule {}\n * ```\n *\n * @example\n * ```typescript\n * // Injecting the factory in a service\n * @Injectable()\n * class OrderService {\n * constructor(\n * @Inject(PIPELINE_FACTORY) private readonly createPipeline: PipelineFactory,\n * ) {}\n *\n * processOrder(order: Order): Order {\n * return this.createPipeline<Order>()\n * .send(order)\n * .through([ValidateOrder, CalculateTax, ApplyDiscount])\n * .thenReturn();\n * }\n * }\n * ```\n */\n@Module({})\nexport class PipelineModule {\n /**\n * Register the pipeline module globally.\n *\n * @returns Dynamic module definition with PipelineHub and factory\n */\n public static forRoot(): DynamicModule {\n return {\n module: PipelineModule,\n global: true,\n providers: [\n // PipelineHub singleton\n PipelineHub,\n\n // Pipeline factory — creates fresh instances on demand\n {\n provide: PIPELINE_FACTORY,\n useFactory: (app?: IApplication) => {\n return <T>(): Pipeline<T> => new Pipeline<T>(app);\n },\n inject: [{ token: APPLICATION, optional: true }],\n },\n ],\n exports: [PipelineHub, PIPELINE_FACTORY],\n };\n }\n}\n"]}
|