@backendkit-labs/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/README.md ADDED
@@ -0,0 +1,447 @@
1
+ # @backendkit-labs/pipeline
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@backendkit-labs/pipeline?style=flat-square&color=cb3837)](https://www.npmjs.com/package/@backendkit-labs/pipeline)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/backendkit-dev/backendkit-monorepo/ci.yml?style=flat-square&label=CI)](https://github.com/backendkit-dev/backendkit-monorepo/actions/workflows/ci.yml)
5
+ [![License](https://img.shields.io/npm/l/@backendkit-labs/pipeline?style=flat-square)](LICENSE)
6
+ [![Node](https://img.shields.io/node/v/@backendkit-labs/pipeline?style=flat-square)](package.json)
7
+
8
+ > Type-safe async pipeline for Node.js — Chain of Responsibility pattern with stop-on-first / collect-all modes, conditional steps, observability hooks, and optional NestJS integration.
9
+
10
+ Each step in the pipeline receives the current context, transforms it, and returns a typed result. If a step fails, the pipeline can stop immediately or continue collecting all errors — your choice per pipeline.
11
+
12
+ ---
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install @backendkit-labs/pipeline
18
+ ```
19
+
20
+ NestJS peer dependencies (only for the `/nestjs` subpath):
21
+
22
+ ```bash
23
+ npm install @nestjs/common @nestjs/core rxjs
24
+ ```
25
+
26
+ ---
27
+
28
+ ## TypeScript Configuration
29
+
30
+ ### Subpath exports (`/nestjs`)
31
+
32
+ This package uses the `exports` field in `package.json` to expose the `/nestjs` subpath. TypeScript's ability to resolve it depends on the `moduleResolution` setting in your `tsconfig.json`.
33
+
34
+ **Modern resolution (recommended) — no extra config needed:**
35
+
36
+ ```json
37
+ // tsconfig.json
38
+ {
39
+ "compilerOptions": {
40
+ "moduleResolution": "bundler"
41
+ }
42
+ }
43
+ ```
44
+
45
+ `"bundler"`, `"node16"`, and `"nodenext"` all understand the `exports` field natively. This is the recommended setting for any project using a bundler (Webpack, esbuild, Vite) or for NestJS projects on TypeScript ≥ 5.
46
+
47
+ **Legacy resolution (`"node"`) — add `paths` aliases:**
48
+
49
+ NestJS projects generated before ~2024 default to `"moduleResolution": "node"`, which ignores the `exports` field entirely. TypeScript won't find the types for `@backendkit-labs/pipeline/nestjs` unless you add explicit path aliases:
50
+
51
+ ```json
52
+ // tsconfig.json
53
+ {
54
+ "compilerOptions": {
55
+ "moduleResolution": "node",
56
+ "paths": {
57
+ "@backendkit-labs/pipeline/nestjs": [
58
+ "./node_modules/@backendkit-labs/pipeline/dist/nestjs/index"
59
+ ]
60
+ }
61
+ }
62
+ }
63
+ ```
64
+
65
+ > **Why does this happen?** The `"node"` resolver was designed before subpath exports existed. It only knows how to find `main` and `types` at the root of a package — it does not read the `exports` map. The `paths` alias manually points TypeScript to the right `.d.ts` file for the subpath.
66
+ >
67
+ > The `splitting: true` tsup option (which this package uses) and this `paths` configuration solve completely different problems. `splitting` fixes a **runtime** class identity issue — ensuring there is only one copy of a class in memory across both bundles. The `paths` alias fixes a **compile-time** issue — helping TypeScript find the types. Both may be needed in a legacy project.
68
+
69
+ ---
70
+
71
+ ### NestJS decorator support
72
+
73
+ NestJS requires two compiler options to be enabled:
74
+
75
+ ```json
76
+ // tsconfig.json
77
+ {
78
+ "compilerOptions": {
79
+ "experimentalDecorators": true,
80
+ "emitDecoratorMetadata": true
81
+ }
82
+ }
83
+ ```
84
+
85
+ And `reflect-metadata` must be imported once at application startup, before any NestJS module is loaded:
86
+
87
+ ```typescript
88
+ // main.ts
89
+ import 'reflect-metadata';
90
+ import { NestFactory } from '@nestjs/core';
91
+ import { AppModule } from './app.module';
92
+
93
+ async function bootstrap() {
94
+ const app = await NestFactory.create(AppModule);
95
+ await app.listen(3000);
96
+ }
97
+ bootstrap();
98
+ ```
99
+
100
+ > NestJS CLI scaffolds both of these automatically. You only need to check this if you are setting up a project manually or if decorator-related DI errors appear at runtime.
101
+
102
+ ---
103
+
104
+ ## Quick Start — Framework-agnostic
105
+
106
+ ```typescript
107
+ import { pipeline, Ok, Err } from '@backendkit-labs/pipeline';
108
+ import type { PipelineStep, StepResult } from '@backendkit-labs/pipeline';
109
+
110
+ interface OrderCtx {
111
+ productId: string;
112
+ quantity: number;
113
+ stock: number;
114
+ price: number;
115
+ total: number;
116
+ }
117
+
118
+ interface OrderError {
119
+ code: string;
120
+ message: string;
121
+ }
122
+
123
+ class StockStep implements PipelineStep<OrderCtx, OrderError> {
124
+ async handle(ctx: OrderCtx): Promise<StepResult<OrderCtx, OrderError>> {
125
+ if (ctx.stock < ctx.quantity) {
126
+ return Err({ code: 'INSUFFICIENT_STOCK', message: 'Not enough stock' });
127
+ }
128
+ return Ok(ctx);
129
+ }
130
+ }
131
+
132
+ class PricingStep implements PipelineStep<OrderCtx, OrderError> {
133
+ async handle(ctx: OrderCtx): Promise<StepResult<OrderCtx, OrderError>> {
134
+ return Ok({ ...ctx, total: ctx.price * ctx.quantity });
135
+ }
136
+ }
137
+
138
+ // Build and run
139
+ const result = await pipeline<OrderCtx, OrderError>()
140
+ .pipe(new StockStep())
141
+ .pipe(new PricingStep())
142
+ .run({ productId: 'p1', quantity: 2, stock: 10, price: 50, total: 0 });
143
+
144
+ if (result.ok) {
145
+ console.log(result.value.total); // 100
146
+ console.log(result.executedSteps); // ['StockStep', 'PricingStep']
147
+ } else {
148
+ console.log(result.error.failedStep); // 'StockStep'
149
+ console.log(result.error.cause); // { code: 'INSUFFICIENT_STOCK', ... }
150
+ }
151
+ ```
152
+
153
+ ---
154
+
155
+ ## Quick Start — NestJS
156
+
157
+ ```typescript
158
+ // order.pipeline.ts
159
+ import { definePipeline } from '@backendkit-labs/pipeline';
160
+ import type { OrderCtx, OrderError } from './order.types';
161
+
162
+ export const ORDER_PIPELINE = definePipeline<OrderCtx, OrderError>('order');
163
+ ```
164
+
165
+ ```typescript
166
+ // app.module.ts
167
+ import { Module } from '@nestjs/common';
168
+ import { PipelineModule } from '@backendkit-labs/pipeline/nestjs';
169
+ import { ORDER_PIPELINE } from './order.pipeline';
170
+ import { StockStep, PricingStep, NotifyStep } from './steps';
171
+
172
+ @Module({
173
+ imports: [
174
+ PipelineModule.forRoot({
175
+ pipelines: [
176
+ {
177
+ token: ORDER_PIPELINE,
178
+ steps: [StockStep, PricingStep, NotifyStep],
179
+ options: {
180
+ onError: (step, err) => logger.error(`Pipeline failed at ${step}`, err),
181
+ },
182
+ },
183
+ ],
184
+ }),
185
+ ],
186
+ })
187
+ export class AppModule {}
188
+ ```
189
+
190
+ ```typescript
191
+ // order.service.ts
192
+ import { Injectable } from '@nestjs/common';
193
+ import { InjectPipeline } from '@backendkit-labs/pipeline/nestjs';
194
+ import { Pipeline } from '@backendkit-labs/pipeline';
195
+ import { ORDER_PIPELINE } from './order.pipeline';
196
+ import type { OrderCtx, OrderError } from './order.types';
197
+
198
+ @Injectable()
199
+ export class OrderService {
200
+ constructor(
201
+ @InjectPipeline(ORDER_PIPELINE)
202
+ private readonly pipeline: Pipeline<OrderCtx, OrderError>,
203
+ ) {}
204
+
205
+ async processOrder(ctx: OrderCtx) {
206
+ return this.pipeline.run(ctx);
207
+ }
208
+ }
209
+ ```
210
+
211
+ ---
212
+
213
+ ## API
214
+
215
+ ### `pipeline(options?)`
216
+
217
+ Creates a new pipeline builder.
218
+
219
+ ```typescript
220
+ const p = pipeline<TContext, TError>(options?);
221
+ ```
222
+
223
+ #### Options
224
+
225
+ ```typescript
226
+ pipeline<Ctx, Err>({
227
+ // 'stop-on-first' — stop and return on the first failure (default)
228
+ // 'collect-all' — run all steps, accumulate every failure
229
+ mode: 'stop-on-first',
230
+
231
+ onStep(stepName, ctx) {
232
+ logger.debug(`[pipeline] → ${stepName}`);
233
+ },
234
+
235
+ onStepComplete(stepName, ctx, durationMs) {
236
+ metrics.timing(`step.${stepName}`, durationMs);
237
+ },
238
+
239
+ onError(stepName, error) {
240
+ logger.error(`[pipeline] ✗ ${stepName}`, error);
241
+ },
242
+
243
+ onComplete(ctx, durationMs) {
244
+ metrics.timing('pipeline.total', durationMs);
245
+ },
246
+ });
247
+ ```
248
+
249
+ ---
250
+
251
+ ### `.pipe(step)`
252
+
253
+ Adds a step that always runs.
254
+
255
+ ```typescript
256
+ p.pipe(new StockStep())
257
+ .pipe(new PricingStep());
258
+ ```
259
+
260
+ ---
261
+
262
+ ### `.pipeIf(condition, step)`
263
+
264
+ Adds a step that runs only when `condition(ctx)` returns `true`. The condition receives the context **after** all previous steps have transformed it.
265
+
266
+ ```typescript
267
+ p.pipe(new BaseStep())
268
+ .pipeIf(ctx => ctx.hasDiscount, new DiscountStep())
269
+ .pipe(new FinalStep());
270
+ ```
271
+
272
+ ---
273
+
274
+ ### `.run(ctx)`
275
+
276
+ Executes the pipeline and returns a `PipelineRunResult`.
277
+
278
+ ```typescript
279
+ const result = await p.run(initialCtx);
280
+
281
+ // Success
282
+ result.ok // true
283
+ result.value // final context
284
+ result.executedSteps // ['StockStep', 'PricingStep']
285
+ result.durationMs // total duration
286
+
287
+ // Failure
288
+ result.ok // false
289
+ result.error.failedStep // 'StockStep'
290
+ result.error.cause // original typed error
291
+ result.error.executedSteps // steps that ran before the failure
292
+ result.error.durationMs // total duration
293
+ result.error.failures // all failures — one entry for stop-on-first, N for collect-all
294
+ result.error.mode // 'stop-on-first' | 'collect-all'
295
+ ```
296
+
297
+ ---
298
+
299
+ ### `Ok(value)` / `Err(error)`
300
+
301
+ Helpers for returning step results.
302
+
303
+ ```typescript
304
+ import { Ok, Err } from '@backendkit-labs/pipeline';
305
+
306
+ async handle(ctx): Promise<StepResult<Ctx, Err>> {
307
+ if (!valid) return Err({ code: 'INVALID' });
308
+ return Ok({ ...ctx, validated: true });
309
+ }
310
+ ```
311
+
312
+ ---
313
+
314
+ ### `PipelineStep<TContext, TError>`
315
+
316
+ Interface your step classes implement.
317
+
318
+ ```typescript
319
+ import type { PipelineStep, StepResult } from '@backendkit-labs/pipeline';
320
+
321
+ class MyStep implements PipelineStep<Ctx, MyError> {
322
+ // Optional — overrides constructor.name in error reports and hook calls
323
+ readonly stepName = 'MyStep';
324
+
325
+ async handle(ctx: Ctx): Promise<StepResult<Ctx, MyError>> {
326
+ // ...
327
+ }
328
+ }
329
+ ```
330
+
331
+ ---
332
+
333
+ ## Error Modes
334
+
335
+ ### `stop-on-first` (default)
336
+
337
+ Stops at the first failure. Use when later steps depend on earlier ones being successful.
338
+
339
+ ```typescript
340
+ pipeline({ mode: 'stop-on-first' })
341
+ .pipe(new AuthStep()) // if this fails → stop, PaymentStep never runs
342
+ .pipe(new PaymentStep())
343
+ .run(ctx);
344
+ ```
345
+
346
+ ### `collect-all`
347
+
348
+ Runs every step regardless of failures. Use when steps are independent and you want to report all errors at once — e.g., form validation.
349
+
350
+ ```typescript
351
+ pipeline({ mode: 'collect-all' })
352
+ .pipe(new ValidateNameStep())
353
+ .pipe(new ValidateEmailStep())
354
+ .pipe(new ValidatePhoneStep())
355
+ .run(formData);
356
+
357
+ // result.error.failures → [{ step: 'ValidateEmailStep', cause: ... }, { step: 'ValidatePhoneStep', cause: ... }]
358
+ ```
359
+
360
+ ---
361
+
362
+ ## NestJS Integration
363
+
364
+ ### `definePipeline<TContext, TError>(name)`
365
+
366
+ Creates a typed injection token. Define it once and share across module and service.
367
+
368
+ ```typescript
369
+ export const ORDER_PIPELINE = definePipeline<OrderCtx, OrderError>('order');
370
+ // PipelineToken<OrderCtx, OrderError>
371
+ ```
372
+
373
+ ### `PipelineModule.forRoot(options)`
374
+
375
+ Registers pipelines globally. Each step class is resolved via NestJS DI, so steps can inject other services.
376
+
377
+ ```typescript
378
+ PipelineModule.forRoot({
379
+ pipelines: [
380
+ {
381
+ token: ORDER_PIPELINE,
382
+ steps: [StockStep, PricingStep, NotifyStep], // resolved via DI
383
+ options: { mode: 'stop-on-first', onError: ... },
384
+ },
385
+ ],
386
+ })
387
+ ```
388
+
389
+ ### `@InjectPipeline(token)`
390
+
391
+ Parameter decorator for injecting a pipeline into a service.
392
+
393
+ ```typescript
394
+ constructor(
395
+ @InjectPipeline(ORDER_PIPELINE)
396
+ private readonly orderPipeline: Pipeline<OrderCtx, OrderError>,
397
+ ) {}
398
+ ```
399
+
400
+ ---
401
+
402
+ ## Use Cases
403
+
404
+ | Scenario | Mode |
405
+ |---|---|
406
+ | Order processing (stock → payment → notify) | `stop-on-first` |
407
+ | Form / DTO validation (collect all field errors) | `collect-all` |
408
+ | User onboarding (KYC → plan → welcome email) | `stop-on-first` |
409
+ | File processing (validate → scan → compress → upload) | `stop-on-first` |
410
+ | Webhook processing (verify signature → parse → deduplicate → route) | `stop-on-first` |
411
+ | Pricing pipeline (base → volume discount → tax → currency) | `stop-on-first` |
412
+
413
+ ---
414
+
415
+ ## Design Notes
416
+
417
+ ### Context is immutable by convention
418
+
419
+ Each step returns a **new** context object rather than mutating the existing one. This makes each step's input/output explicit and easy to trace in logs.
420
+
421
+ ```typescript
422
+ // Do this
423
+ return Ok({ ...ctx, total: ctx.price * ctx.quantity });
424
+
425
+ // Not this
426
+ ctx.total = ctx.price * ctx.quantity;
427
+ return Ok(ctx);
428
+ ```
429
+
430
+ ### Steps are plain classes
431
+
432
+ Steps don't extend a base class or require special decorators. They just implement `PipelineStep<TContext, TError>`. This makes them easy to test in isolation:
433
+
434
+ ```typescript
435
+ const result = await new StockStep().handle({ stock: 0, quantity: 5, ... });
436
+ expect(result.ok).toBe(false);
437
+ ```
438
+
439
+ ### NestJS DI class identity
440
+
441
+ `PipelineModule.forRoot()` resolves step classes via NestJS DI and wires them into the pipeline at startup. All steps share the same DI context — no class identity issues.
442
+
443
+ ---
444
+
445
+ ## License
446
+
447
+ MIT — [BackendKit Labs](https://github.com/backendkit-dev)
@@ -0,0 +1,93 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result) __defProp(target, key, result);
9
+ return result;
10
+ };
11
+
12
+ // src/core/pipeline.ts
13
+ var Pipeline = class {
14
+ entries = [];
15
+ options;
16
+ constructor(options = {}) {
17
+ this.options = options;
18
+ }
19
+ pipe(step) {
20
+ this.entries.push({
21
+ instance: step,
22
+ name: step.stepName ?? step.constructor.name
23
+ });
24
+ return this;
25
+ }
26
+ pipeIf(condition, step) {
27
+ this.entries.push({
28
+ instance: step,
29
+ name: step.stepName ?? step.constructor.name,
30
+ condition
31
+ });
32
+ return this;
33
+ }
34
+ async run(initialCtx) {
35
+ const start = Date.now();
36
+ const { mode = "stop-on-first", onStep, onStepComplete, onError, onComplete } = this.options;
37
+ const executedSteps = [];
38
+ const failures = [];
39
+ let ctx = initialCtx;
40
+ for (const { instance, name, condition } of this.entries) {
41
+ if (condition && !condition(ctx)) continue;
42
+ await onStep?.(name, ctx);
43
+ const stepStart = Date.now();
44
+ const stepResult = await instance.handle(ctx);
45
+ const stepMs = Date.now() - stepStart;
46
+ if (!stepResult.ok) {
47
+ await onError?.(name, stepResult.error);
48
+ failures.push({ step: name, cause: stepResult.error });
49
+ if (mode === "stop-on-first") {
50
+ return {
51
+ ok: false,
52
+ error: {
53
+ mode,
54
+ failedStep: name,
55
+ cause: stepResult.error,
56
+ executedSteps: [...executedSteps],
57
+ durationMs: Date.now() - start,
58
+ failures
59
+ }
60
+ };
61
+ }
62
+ executedSteps.push(name);
63
+ continue;
64
+ }
65
+ ctx = stepResult.value;
66
+ executedSteps.push(name);
67
+ await onStepComplete?.(name, ctx, stepMs);
68
+ }
69
+ if (failures.length > 0) {
70
+ return {
71
+ ok: false,
72
+ error: {
73
+ mode,
74
+ failedStep: failures[0].step,
75
+ cause: failures[0].cause,
76
+ executedSteps: [...executedSteps],
77
+ durationMs: Date.now() - start,
78
+ failures
79
+ }
80
+ };
81
+ }
82
+ const durationMs = Date.now() - start;
83
+ await onComplete?.(ctx, durationMs);
84
+ return { ok: true, value: ctx, executedSteps: [...executedSteps], durationMs };
85
+ }
86
+ };
87
+ function pipeline(options) {
88
+ return new Pipeline(options);
89
+ }
90
+
91
+ export { Pipeline, __decorateClass, pipeline };
92
+ //# sourceMappingURL=chunk-GHWTXTUB.js.map
93
+ //# sourceMappingURL=chunk-GHWTXTUB.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/pipeline.ts"],"names":[],"mappings":"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","file":"chunk-GHWTXTUB.js","sourcesContent":["import type {\n PipelineOptions,\n PipelineRunResult,\n PipelineStep,\n PipelineStepFailure,\n} from './types.js';\n\ninterface StepEntry<TContext, TError> {\n instance: PipelineStep<TContext, TError>;\n name: string;\n condition?: (ctx: TContext) => boolean;\n}\n\nexport class Pipeline<TContext, TError = unknown> {\n private readonly entries: StepEntry<TContext, TError>[] = [];\n private readonly options: PipelineOptions<TContext, TError>;\n\n constructor(options: PipelineOptions<TContext, TError> = {}) {\n this.options = options;\n }\n\n pipe(step: PipelineStep<TContext, TError>): this {\n this.entries.push({\n instance: step,\n name: step.stepName ?? step.constructor.name,\n });\n return this;\n }\n\n pipeIf(condition: (ctx: TContext) => boolean, step: PipelineStep<TContext, TError>): this {\n this.entries.push({\n instance: step,\n name: step.stepName ?? step.constructor.name,\n condition,\n });\n return this;\n }\n\n async run(initialCtx: TContext): Promise<PipelineRunResult<TContext, TError>> {\n const start = Date.now();\n const { mode = 'stop-on-first', onStep, onStepComplete, onError, onComplete } = this.options;\n const executedSteps: string[] = [];\n const failures: PipelineStepFailure<TError>[] = [];\n let ctx = initialCtx;\n\n for (const { instance, name, condition } of this.entries) {\n if (condition && !condition(ctx)) continue;\n\n await onStep?.(name, ctx);\n const stepStart = Date.now();\n const stepResult = await instance.handle(ctx);\n const stepMs = Date.now() - stepStart;\n\n if (!stepResult.ok) {\n await onError?.(name, stepResult.error);\n failures.push({ step: name, cause: stepResult.error });\n\n if (mode === 'stop-on-first') {\n return {\n ok: false,\n error: {\n mode,\n failedStep: name,\n cause: stepResult.error,\n executedSteps: [...executedSteps],\n durationMs: Date.now() - start,\n failures,\n },\n };\n }\n\n // collect-all: record the failed step and continue\n executedSteps.push(name);\n continue;\n }\n\n ctx = stepResult.value;\n executedSteps.push(name);\n await onStepComplete?.(name, ctx, stepMs);\n }\n\n if (failures.length > 0) {\n return {\n ok: false,\n error: {\n mode,\n failedStep: failures[0].step,\n cause: failures[0].cause,\n executedSteps: [...executedSteps],\n durationMs: Date.now() - start,\n failures,\n },\n };\n }\n\n const durationMs = Date.now() - start;\n await onComplete?.(ctx, durationMs);\n return { ok: true, value: ctx, executedSteps: [...executedSteps], durationMs };\n }\n}\n\nexport function pipeline<TContext, TError = unknown>(\n options?: PipelineOptions<TContext, TError>,\n): Pipeline<TContext, TError> {\n return new Pipeline(options);\n}\n"]}
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __decorateClass = (decorators, target, key, kind) => {
6
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
7
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
8
+ if (decorator = decorators[i])
9
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
10
+ if (kind && result) __defProp(target, key, result);
11
+ return result;
12
+ };
13
+
14
+ // src/core/pipeline.ts
15
+ var Pipeline = class {
16
+ entries = [];
17
+ options;
18
+ constructor(options = {}) {
19
+ this.options = options;
20
+ }
21
+ pipe(step) {
22
+ this.entries.push({
23
+ instance: step,
24
+ name: step.stepName ?? step.constructor.name
25
+ });
26
+ return this;
27
+ }
28
+ pipeIf(condition, step) {
29
+ this.entries.push({
30
+ instance: step,
31
+ name: step.stepName ?? step.constructor.name,
32
+ condition
33
+ });
34
+ return this;
35
+ }
36
+ async run(initialCtx) {
37
+ const start = Date.now();
38
+ const { mode = "stop-on-first", onStep, onStepComplete, onError, onComplete } = this.options;
39
+ const executedSteps = [];
40
+ const failures = [];
41
+ let ctx = initialCtx;
42
+ for (const { instance, name, condition } of this.entries) {
43
+ if (condition && !condition(ctx)) continue;
44
+ await onStep?.(name, ctx);
45
+ const stepStart = Date.now();
46
+ const stepResult = await instance.handle(ctx);
47
+ const stepMs = Date.now() - stepStart;
48
+ if (!stepResult.ok) {
49
+ await onError?.(name, stepResult.error);
50
+ failures.push({ step: name, cause: stepResult.error });
51
+ if (mode === "stop-on-first") {
52
+ return {
53
+ ok: false,
54
+ error: {
55
+ mode,
56
+ failedStep: name,
57
+ cause: stepResult.error,
58
+ executedSteps: [...executedSteps],
59
+ durationMs: Date.now() - start,
60
+ failures
61
+ }
62
+ };
63
+ }
64
+ executedSteps.push(name);
65
+ continue;
66
+ }
67
+ ctx = stepResult.value;
68
+ executedSteps.push(name);
69
+ await onStepComplete?.(name, ctx, stepMs);
70
+ }
71
+ if (failures.length > 0) {
72
+ return {
73
+ ok: false,
74
+ error: {
75
+ mode,
76
+ failedStep: failures[0].step,
77
+ cause: failures[0].cause,
78
+ executedSteps: [...executedSteps],
79
+ durationMs: Date.now() - start,
80
+ failures
81
+ }
82
+ };
83
+ }
84
+ const durationMs = Date.now() - start;
85
+ await onComplete?.(ctx, durationMs);
86
+ return { ok: true, value: ctx, executedSteps: [...executedSteps], durationMs };
87
+ }
88
+ };
89
+ function pipeline(options) {
90
+ return new Pipeline(options);
91
+ }
92
+
93
+ exports.Pipeline = Pipeline;
94
+ exports.__decorateClass = __decorateClass;
95
+ exports.pipeline = pipeline;
96
+ //# sourceMappingURL=chunk-LPCHUZQE.cjs.map
97
+ //# sourceMappingURL=chunk-LPCHUZQE.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/pipeline.ts"],"names":[],"mappings":"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","file":"chunk-LPCHUZQE.cjs","sourcesContent":["import type {\n PipelineOptions,\n PipelineRunResult,\n PipelineStep,\n PipelineStepFailure,\n} from './types.js';\n\ninterface StepEntry<TContext, TError> {\n instance: PipelineStep<TContext, TError>;\n name: string;\n condition?: (ctx: TContext) => boolean;\n}\n\nexport class Pipeline<TContext, TError = unknown> {\n private readonly entries: StepEntry<TContext, TError>[] = [];\n private readonly options: PipelineOptions<TContext, TError>;\n\n constructor(options: PipelineOptions<TContext, TError> = {}) {\n this.options = options;\n }\n\n pipe(step: PipelineStep<TContext, TError>): this {\n this.entries.push({\n instance: step,\n name: step.stepName ?? step.constructor.name,\n });\n return this;\n }\n\n pipeIf(condition: (ctx: TContext) => boolean, step: PipelineStep<TContext, TError>): this {\n this.entries.push({\n instance: step,\n name: step.stepName ?? step.constructor.name,\n condition,\n });\n return this;\n }\n\n async run(initialCtx: TContext): Promise<PipelineRunResult<TContext, TError>> {\n const start = Date.now();\n const { mode = 'stop-on-first', onStep, onStepComplete, onError, onComplete } = this.options;\n const executedSteps: string[] = [];\n const failures: PipelineStepFailure<TError>[] = [];\n let ctx = initialCtx;\n\n for (const { instance, name, condition } of this.entries) {\n if (condition && !condition(ctx)) continue;\n\n await onStep?.(name, ctx);\n const stepStart = Date.now();\n const stepResult = await instance.handle(ctx);\n const stepMs = Date.now() - stepStart;\n\n if (!stepResult.ok) {\n await onError?.(name, stepResult.error);\n failures.push({ step: name, cause: stepResult.error });\n\n if (mode === 'stop-on-first') {\n return {\n ok: false,\n error: {\n mode,\n failedStep: name,\n cause: stepResult.error,\n executedSteps: [...executedSteps],\n durationMs: Date.now() - start,\n failures,\n },\n };\n }\n\n // collect-all: record the failed step and continue\n executedSteps.push(name);\n continue;\n }\n\n ctx = stepResult.value;\n executedSteps.push(name);\n await onStepComplete?.(name, ctx, stepMs);\n }\n\n if (failures.length > 0) {\n return {\n ok: false,\n error: {\n mode,\n failedStep: failures[0].step,\n cause: failures[0].cause,\n executedSteps: [...executedSteps],\n durationMs: Date.now() - start,\n failures,\n },\n };\n }\n\n const durationMs = Date.now() - start;\n await onComplete?.(ctx, durationMs);\n return { ok: true, value: ctx, executedSteps: [...executedSteps], durationMs };\n }\n}\n\nexport function pipeline<TContext, TError = unknown>(\n options?: PipelineOptions<TContext, TError>,\n): Pipeline<TContext, TError> {\n return new Pipeline(options);\n}\n"]}
@@ -0,0 +1,52 @@
1
+ type StepResult<TContext, TError> = {
2
+ readonly ok: true;
3
+ readonly value: TContext;
4
+ } | {
5
+ readonly ok: false;
6
+ readonly error: TError;
7
+ };
8
+ interface PipelineStep<TContext, TError = unknown> {
9
+ /** Optional human-readable name used in error reporting and hooks. Defaults to constructor.name. */
10
+ readonly stepName?: string;
11
+ handle(ctx: TContext): Promise<StepResult<TContext, TError>>;
12
+ }
13
+ type PipelineMode = 'stop-on-first' | 'collect-all';
14
+ interface PipelineStepFailure<TError> {
15
+ readonly step: string;
16
+ readonly cause: TError;
17
+ }
18
+ interface PipelineError<TError> {
19
+ readonly mode: PipelineMode;
20
+ readonly failedStep: string;
21
+ readonly cause: TError;
22
+ readonly executedSteps: string[];
23
+ readonly durationMs: number;
24
+ readonly failures: PipelineStepFailure<TError>[];
25
+ }
26
+ type PipelineRunResult<TContext, TError> = {
27
+ readonly ok: true;
28
+ readonly value: TContext;
29
+ readonly executedSteps: string[];
30
+ readonly durationMs: number;
31
+ } | {
32
+ readonly ok: false;
33
+ readonly error: PipelineError<TError>;
34
+ };
35
+ interface PipelineOptions<TContext, TError> {
36
+ mode?: PipelineMode;
37
+ onStep?: (stepName: string, ctx: TContext) => void | Promise<void>;
38
+ onStepComplete?: (stepName: string, ctx: TContext, durationMs: number) => void | Promise<void>;
39
+ onError?: (stepName: string, error: TError) => void | Promise<void>;
40
+ onComplete?: (ctx: TContext, durationMs: number) => void | Promise<void>;
41
+ }
42
+
43
+ declare class PipelineToken<TContext, TError = unknown> {
44
+ readonly description: string;
45
+ readonly symbol: symbol;
46
+ readonly _ctx: TContext;
47
+ readonly _err: TError;
48
+ constructor(name: string);
49
+ }
50
+ declare function definePipeline<TContext, TError = unknown>(name: string): PipelineToken<TContext, TError>;
51
+
52
+ export { type PipelineOptions as P, type StepResult as S, type PipelineStep as a, type PipelineRunResult as b, type PipelineError as c, type PipelineMode as d, type PipelineStepFailure as e, PipelineToken as f, definePipeline as g };
@@ -0,0 +1,52 @@
1
+ type StepResult<TContext, TError> = {
2
+ readonly ok: true;
3
+ readonly value: TContext;
4
+ } | {
5
+ readonly ok: false;
6
+ readonly error: TError;
7
+ };
8
+ interface PipelineStep<TContext, TError = unknown> {
9
+ /** Optional human-readable name used in error reporting and hooks. Defaults to constructor.name. */
10
+ readonly stepName?: string;
11
+ handle(ctx: TContext): Promise<StepResult<TContext, TError>>;
12
+ }
13
+ type PipelineMode = 'stop-on-first' | 'collect-all';
14
+ interface PipelineStepFailure<TError> {
15
+ readonly step: string;
16
+ readonly cause: TError;
17
+ }
18
+ interface PipelineError<TError> {
19
+ readonly mode: PipelineMode;
20
+ readonly failedStep: string;
21
+ readonly cause: TError;
22
+ readonly executedSteps: string[];
23
+ readonly durationMs: number;
24
+ readonly failures: PipelineStepFailure<TError>[];
25
+ }
26
+ type PipelineRunResult<TContext, TError> = {
27
+ readonly ok: true;
28
+ readonly value: TContext;
29
+ readonly executedSteps: string[];
30
+ readonly durationMs: number;
31
+ } | {
32
+ readonly ok: false;
33
+ readonly error: PipelineError<TError>;
34
+ };
35
+ interface PipelineOptions<TContext, TError> {
36
+ mode?: PipelineMode;
37
+ onStep?: (stepName: string, ctx: TContext) => void | Promise<void>;
38
+ onStepComplete?: (stepName: string, ctx: TContext, durationMs: number) => void | Promise<void>;
39
+ onError?: (stepName: string, error: TError) => void | Promise<void>;
40
+ onComplete?: (ctx: TContext, durationMs: number) => void | Promise<void>;
41
+ }
42
+
43
+ declare class PipelineToken<TContext, TError = unknown> {
44
+ readonly description: string;
45
+ readonly symbol: symbol;
46
+ readonly _ctx: TContext;
47
+ readonly _err: TError;
48
+ constructor(name: string);
49
+ }
50
+ declare function definePipeline<TContext, TError = unknown>(name: string): PipelineToken<TContext, TError>;
51
+
52
+ export { type PipelineOptions as P, type StepResult as S, type PipelineStep as a, type PipelineRunResult as b, type PipelineError as c, type PipelineMode as d, type PipelineStepFailure as e, PipelineToken as f, definePipeline as g };
package/dist/index.cjs ADDED
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ var chunkLPCHUZQE_cjs = require('./chunk-LPCHUZQE.cjs');
4
+
5
+ // src/core/define-pipeline.ts
6
+ var PipelineToken = class {
7
+ description;
8
+ symbol;
9
+ constructor(name) {
10
+ this.description = `Pipeline(${name})`;
11
+ this.symbol = /* @__PURE__ */ Symbol(`Pipeline(${name})`);
12
+ }
13
+ };
14
+ function definePipeline(name) {
15
+ return new PipelineToken(name);
16
+ }
17
+
18
+ // src/core/result.ts
19
+ function Ok(value) {
20
+ return { ok: true, value };
21
+ }
22
+ function Err(error) {
23
+ return { ok: false, error };
24
+ }
25
+
26
+ Object.defineProperty(exports, "Pipeline", {
27
+ enumerable: true,
28
+ get: function () { return chunkLPCHUZQE_cjs.Pipeline; }
29
+ });
30
+ Object.defineProperty(exports, "pipeline", {
31
+ enumerable: true,
32
+ get: function () { return chunkLPCHUZQE_cjs.pipeline; }
33
+ });
34
+ exports.Err = Err;
35
+ exports.Ok = Ok;
36
+ exports.PipelineToken = PipelineToken;
37
+ exports.definePipeline = definePipeline;
38
+ //# sourceMappingURL=index.cjs.map
39
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/define-pipeline.ts","../src/core/result.ts"],"names":[],"mappings":";;;;;AAAO,IAAM,gBAAN,MAAgD;AAAA,EAC5C,WAAA;AAAA,EACA,MAAA;AAAA,EAMT,YAAY,IAAA,EAAc;AACxB,IAAA,IAAA,CAAK,WAAA,GAAc,YAAY,IAAI,CAAA,CAAA,CAAA;AACnC,IAAA,IAAA,CAAK,MAAA,mBAAc,MAAA,CAAO,CAAA,SAAA,EAAY,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,EAC/C;AACF;AAEO,SAAS,eACd,IAAA,EACiC;AACjC,EAAA,OAAO,IAAI,cAAgC,IAAI,CAAA;AACjD;;;AChBO,SAAS,GAAa,KAAA,EAA8C;AACzE,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAM;AAC3B;AAEO,SAAS,IAAY,KAAA,EAA0C;AACpE,EAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAM;AAC5B","file":"index.cjs","sourcesContent":["export class PipelineToken<TContext, TError = unknown> {\n readonly description: string;\n readonly symbol: symbol;\n\n // Phantom brand fields — exist only at the type level, never at runtime\n declare readonly _ctx: TContext;\n declare readonly _err: TError;\n\n constructor(name: string) {\n this.description = `Pipeline(${name})`;\n this.symbol = Symbol(`Pipeline(${name})`);\n }\n}\n\nexport function definePipeline<TContext, TError = unknown>(\n name: string,\n): PipelineToken<TContext, TError> {\n return new PipelineToken<TContext, TError>(name);\n}\n","import type { StepResult } from './types.js';\n\nexport function Ok<TContext>(value: TContext): StepResult<TContext, never> {\n return { ok: true, value };\n}\n\nexport function Err<TError>(error: TError): StepResult<never, TError> {\n return { ok: false, error };\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import { P as PipelineOptions, a as PipelineStep, b as PipelineRunResult, S as StepResult } from './define-pipeline-BNKM59ML.cjs';
2
+ export { c as PipelineError, d as PipelineMode, e as PipelineStepFailure, f as PipelineToken, g as definePipeline } from './define-pipeline-BNKM59ML.cjs';
3
+
4
+ declare class Pipeline<TContext, TError = unknown> {
5
+ private readonly entries;
6
+ private readonly options;
7
+ constructor(options?: PipelineOptions<TContext, TError>);
8
+ pipe(step: PipelineStep<TContext, TError>): this;
9
+ pipeIf(condition: (ctx: TContext) => boolean, step: PipelineStep<TContext, TError>): this;
10
+ run(initialCtx: TContext): Promise<PipelineRunResult<TContext, TError>>;
11
+ }
12
+ declare function pipeline<TContext, TError = unknown>(options?: PipelineOptions<TContext, TError>): Pipeline<TContext, TError>;
13
+
14
+ declare function Ok<TContext>(value: TContext): StepResult<TContext, never>;
15
+ declare function Err<TError>(error: TError): StepResult<never, TError>;
16
+
17
+ export { Err, Ok, Pipeline, PipelineOptions, PipelineRunResult, PipelineStep, StepResult, pipeline };
@@ -0,0 +1,17 @@
1
+ import { P as PipelineOptions, a as PipelineStep, b as PipelineRunResult, S as StepResult } from './define-pipeline-BNKM59ML.js';
2
+ export { c as PipelineError, d as PipelineMode, e as PipelineStepFailure, f as PipelineToken, g as definePipeline } from './define-pipeline-BNKM59ML.js';
3
+
4
+ declare class Pipeline<TContext, TError = unknown> {
5
+ private readonly entries;
6
+ private readonly options;
7
+ constructor(options?: PipelineOptions<TContext, TError>);
8
+ pipe(step: PipelineStep<TContext, TError>): this;
9
+ pipeIf(condition: (ctx: TContext) => boolean, step: PipelineStep<TContext, TError>): this;
10
+ run(initialCtx: TContext): Promise<PipelineRunResult<TContext, TError>>;
11
+ }
12
+ declare function pipeline<TContext, TError = unknown>(options?: PipelineOptions<TContext, TError>): Pipeline<TContext, TError>;
13
+
14
+ declare function Ok<TContext>(value: TContext): StepResult<TContext, never>;
15
+ declare function Err<TError>(error: TError): StepResult<never, TError>;
16
+
17
+ export { Err, Ok, Pipeline, PipelineOptions, PipelineRunResult, PipelineStep, StepResult, pipeline };
package/dist/index.js ADDED
@@ -0,0 +1,26 @@
1
+ export { Pipeline, pipeline } from './chunk-GHWTXTUB.js';
2
+
3
+ // src/core/define-pipeline.ts
4
+ var PipelineToken = class {
5
+ description;
6
+ symbol;
7
+ constructor(name) {
8
+ this.description = `Pipeline(${name})`;
9
+ this.symbol = /* @__PURE__ */ Symbol(`Pipeline(${name})`);
10
+ }
11
+ };
12
+ function definePipeline(name) {
13
+ return new PipelineToken(name);
14
+ }
15
+
16
+ // src/core/result.ts
17
+ function Ok(value) {
18
+ return { ok: true, value };
19
+ }
20
+ function Err(error) {
21
+ return { ok: false, error };
22
+ }
23
+
24
+ export { Err, Ok, PipelineToken, definePipeline };
25
+ //# sourceMappingURL=index.js.map
26
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/define-pipeline.ts","../src/core/result.ts"],"names":[],"mappings":";;;AAAO,IAAM,gBAAN,MAAgD;AAAA,EAC5C,WAAA;AAAA,EACA,MAAA;AAAA,EAMT,YAAY,IAAA,EAAc;AACxB,IAAA,IAAA,CAAK,WAAA,GAAc,YAAY,IAAI,CAAA,CAAA,CAAA;AACnC,IAAA,IAAA,CAAK,MAAA,mBAAc,MAAA,CAAO,CAAA,SAAA,EAAY,IAAI,CAAA,CAAA,CAAG,CAAA;AAAA,EAC/C;AACF;AAEO,SAAS,eACd,IAAA,EACiC;AACjC,EAAA,OAAO,IAAI,cAAgC,IAAI,CAAA;AACjD;;;AChBO,SAAS,GAAa,KAAA,EAA8C;AACzE,EAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAM;AAC3B;AAEO,SAAS,IAAY,KAAA,EAA0C;AACpE,EAAA,OAAO,EAAE,EAAA,EAAI,KAAA,EAAO,KAAA,EAAM;AAC5B","file":"index.js","sourcesContent":["export class PipelineToken<TContext, TError = unknown> {\n readonly description: string;\n readonly symbol: symbol;\n\n // Phantom brand fields — exist only at the type level, never at runtime\n declare readonly _ctx: TContext;\n declare readonly _err: TError;\n\n constructor(name: string) {\n this.description = `Pipeline(${name})`;\n this.symbol = Symbol(`Pipeline(${name})`);\n }\n}\n\nexport function definePipeline<TContext, TError = unknown>(\n name: string,\n): PipelineToken<TContext, TError> {\n return new PipelineToken<TContext, TError>(name);\n}\n","import type { StepResult } from './types.js';\n\nexport function Ok<TContext>(value: TContext): StepResult<TContext, never> {\n return { ok: true, value };\n}\n\nexport function Err<TError>(error: TError): StepResult<never, TError> {\n return { ok: false, error };\n}\n"]}
@@ -0,0 +1,40 @@
1
+ 'use strict';
2
+
3
+ var chunkLPCHUZQE_cjs = require('../chunk-LPCHUZQE.cjs');
4
+ var common = require('@nestjs/common');
5
+
6
+ exports.PipelineModule = class PipelineModule {
7
+ static forRoot(options) {
8
+ const providers = [];
9
+ for (const def of options.pipelines) {
10
+ for (const step of def.steps) {
11
+ providers.push({ provide: step, useClass: step });
12
+ }
13
+ providers.push({
14
+ provide: def.token.symbol,
15
+ useFactory: (...stepInstances) => {
16
+ const p = new chunkLPCHUZQE_cjs.Pipeline(def.options ?? {});
17
+ for (const instance of stepInstances) {
18
+ p.pipe(instance);
19
+ }
20
+ return p;
21
+ },
22
+ inject: def.steps
23
+ });
24
+ }
25
+ return {
26
+ module: exports.PipelineModule,
27
+ providers,
28
+ exports: providers,
29
+ global: true
30
+ };
31
+ }
32
+ };
33
+ exports.PipelineModule = chunkLPCHUZQE_cjs.__decorateClass([
34
+ common.Module({})
35
+ ], exports.PipelineModule);
36
+ var InjectPipeline = (token) => common.Inject(token.symbol);
37
+
38
+ exports.InjectPipeline = InjectPipeline;
39
+ //# sourceMappingURL=index.cjs.map
40
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/nestjs/pipeline.module.ts","../../src/nestjs/pipeline.decorator.ts"],"names":["PipelineModule","Pipeline","__decorateClass","Module","Inject"],"mappings":";;;;;AAMaA,yBAAN,oBAAA,CAAqB;AAAA,EAC1B,OAAO,QAAQ,OAAA,EAA+C;AAC5D,IAAA,MAAM,YAAwB,EAAC;AAE/B,IAAA,KAAA,MAAW,GAAA,IAAO,QAAQ,SAAA,EAAW;AACnC,MAAA,KAAA,MAAW,IAAA,IAAQ,IAAI,KAAA,EAAO;AAC5B,QAAA,SAAA,CAAU,KAAK,EAAE,OAAA,EAAS,IAAA,EAAM,QAAA,EAAU,MAAM,CAAA;AAAA,MAClD;AAEA,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,OAAA,EAAY,IAAI,KAAA,CAAM,MAAA;AAAA,QACtB,UAAA,EAAY,IAAI,aAAA,KAAoD;AAClE,UAAA,MAAM,IAAI,IAAIC,0BAAA,CAAS,GAAA,CAAI,OAAA,IAAW,EAAE,CAAA;AACxC,UAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,YAAA,CAAA,CAAE,KAAK,QAAQ,CAAA;AAAA,UACjB;AACA,UAAA,OAAO,CAAA;AAAA,QACT,CAAA;AAAA,QACA,QAAQ,GAAA,CAAI;AAAA,OACb,CAAA;AAAA,IACH;AAEA,IAAA,OAAO;AAAA,MACL,MAAA,EAAWD,sBAAA;AAAA,MACX,SAAA;AAAA,MACA,OAAA,EAAW,SAAA;AAAA,MACX,MAAA,EAAW;AAAA,KACb;AAAA,EACF;AACF;AA7BaA,sBAAA,GAANE,iCAAA,CAAA;AAAA,EADNC,aAAA,CAAO,EAAE;AAAA,CAAA,EACGH,sBAAA,CAAA;ACHN,IAAM,cAAA,GAAiB,CAC5B,KAAA,KAC8BI,aAAA,CAAO,MAAM,MAAM","file":"index.cjs","sourcesContent":["import { DynamicModule, Module, Provider } from '@nestjs/common';\nimport { Pipeline } from '../core/pipeline.js';\nimport type { PipelineStep } from '../core/types.js';\nimport type { PipelineModuleOptions } from './pipeline.options.js';\n\n@Module({})\nexport class PipelineModule {\n static forRoot(options: PipelineModuleOptions): DynamicModule {\n const providers: Provider[] = [];\n\n for (const def of options.pipelines) {\n for (const step of def.steps) {\n providers.push({ provide: step, useClass: step });\n }\n\n providers.push({\n provide: def.token.symbol,\n useFactory: (...stepInstances: PipelineStep<unknown, unknown>[]) => {\n const p = new Pipeline(def.options ?? {});\n for (const instance of stepInstances) {\n p.pipe(instance);\n }\n return p;\n },\n inject: def.steps,\n });\n }\n\n return {\n module: PipelineModule,\n providers,\n exports: providers,\n global: true,\n };\n }\n}\n","import { Inject } from '@nestjs/common';\nimport type { PipelineToken } from '../core/define-pipeline.js';\n\nexport const InjectPipeline = <TContext, TError>(\n token: PipelineToken<TContext, TError>,\n): ReturnType<typeof Inject> => Inject(token.symbol);\n"]}
@@ -0,0 +1,19 @@
1
+ import { Type, DynamicModule, Inject } from '@nestjs/common';
2
+ import { f as PipelineToken, a as PipelineStep, P as PipelineOptions } from '../define-pipeline-BNKM59ML.cjs';
3
+
4
+ interface PipelineDefinition<TContext = unknown, TError = unknown> {
5
+ token: PipelineToken<TContext, TError>;
6
+ steps: Type<PipelineStep<TContext, TError>>[];
7
+ options?: PipelineOptions<TContext, TError>;
8
+ }
9
+ interface PipelineModuleOptions {
10
+ pipelines: PipelineDefinition[];
11
+ }
12
+
13
+ declare class PipelineModule {
14
+ static forRoot(options: PipelineModuleOptions): DynamicModule;
15
+ }
16
+
17
+ declare const InjectPipeline: <TContext, TError>(token: PipelineToken<TContext, TError>) => ReturnType<typeof Inject>;
18
+
19
+ export { InjectPipeline, type PipelineDefinition, PipelineModule, type PipelineModuleOptions };
@@ -0,0 +1,19 @@
1
+ import { Type, DynamicModule, Inject } from '@nestjs/common';
2
+ import { f as PipelineToken, a as PipelineStep, P as PipelineOptions } from '../define-pipeline-BNKM59ML.js';
3
+
4
+ interface PipelineDefinition<TContext = unknown, TError = unknown> {
5
+ token: PipelineToken<TContext, TError>;
6
+ steps: Type<PipelineStep<TContext, TError>>[];
7
+ options?: PipelineOptions<TContext, TError>;
8
+ }
9
+ interface PipelineModuleOptions {
10
+ pipelines: PipelineDefinition[];
11
+ }
12
+
13
+ declare class PipelineModule {
14
+ static forRoot(options: PipelineModuleOptions): DynamicModule;
15
+ }
16
+
17
+ declare const InjectPipeline: <TContext, TError>(token: PipelineToken<TContext, TError>) => ReturnType<typeof Inject>;
18
+
19
+ export { InjectPipeline, type PipelineDefinition, PipelineModule, type PipelineModuleOptions };
@@ -0,0 +1,38 @@
1
+ import { __decorateClass, Pipeline } from '../chunk-GHWTXTUB.js';
2
+ import { Module, Inject } from '@nestjs/common';
3
+
4
+ var PipelineModule = class {
5
+ static forRoot(options) {
6
+ const providers = [];
7
+ for (const def of options.pipelines) {
8
+ for (const step of def.steps) {
9
+ providers.push({ provide: step, useClass: step });
10
+ }
11
+ providers.push({
12
+ provide: def.token.symbol,
13
+ useFactory: (...stepInstances) => {
14
+ const p = new Pipeline(def.options ?? {});
15
+ for (const instance of stepInstances) {
16
+ p.pipe(instance);
17
+ }
18
+ return p;
19
+ },
20
+ inject: def.steps
21
+ });
22
+ }
23
+ return {
24
+ module: PipelineModule,
25
+ providers,
26
+ exports: providers,
27
+ global: true
28
+ };
29
+ }
30
+ };
31
+ PipelineModule = __decorateClass([
32
+ Module({})
33
+ ], PipelineModule);
34
+ var InjectPipeline = (token) => Inject(token.symbol);
35
+
36
+ export { InjectPipeline, PipelineModule };
37
+ //# sourceMappingURL=index.js.map
38
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/nestjs/pipeline.module.ts","../../src/nestjs/pipeline.decorator.ts"],"names":[],"mappings":";;;AAMO,IAAM,iBAAN,MAAqB;AAAA,EAC1B,OAAO,QAAQ,OAAA,EAA+C;AAC5D,IAAA,MAAM,YAAwB,EAAC;AAE/B,IAAA,KAAA,MAAW,GAAA,IAAO,QAAQ,SAAA,EAAW;AACnC,MAAA,KAAA,MAAW,IAAA,IAAQ,IAAI,KAAA,EAAO;AAC5B,QAAA,SAAA,CAAU,KAAK,EAAE,OAAA,EAAS,IAAA,EAAM,QAAA,EAAU,MAAM,CAAA;AAAA,MAClD;AAEA,MAAA,SAAA,CAAU,IAAA,CAAK;AAAA,QACb,OAAA,EAAY,IAAI,KAAA,CAAM,MAAA;AAAA,QACtB,UAAA,EAAY,IAAI,aAAA,KAAoD;AAClE,UAAA,MAAM,IAAI,IAAI,QAAA,CAAS,GAAA,CAAI,OAAA,IAAW,EAAE,CAAA;AACxC,UAAA,KAAA,MAAW,YAAY,aAAA,EAAe;AACpC,YAAA,CAAA,CAAE,KAAK,QAAQ,CAAA;AAAA,UACjB;AACA,UAAA,OAAO,CAAA;AAAA,QACT,CAAA;AAAA,QACA,QAAQ,GAAA,CAAI;AAAA,OACb,CAAA;AAAA,IACH;AAEA,IAAA,OAAO;AAAA,MACL,MAAA,EAAW,cAAA;AAAA,MACX,SAAA;AAAA,MACA,OAAA,EAAW,SAAA;AAAA,MACX,MAAA,EAAW;AAAA,KACb;AAAA,EACF;AACF;AA7Ba,cAAA,GAAN,eAAA,CAAA;AAAA,EADN,MAAA,CAAO,EAAE;AAAA,CAAA,EACG,cAAA,CAAA;ACHN,IAAM,cAAA,GAAiB,CAC5B,KAAA,KAC8B,MAAA,CAAO,MAAM,MAAM","file":"index.js","sourcesContent":["import { DynamicModule, Module, Provider } from '@nestjs/common';\nimport { Pipeline } from '../core/pipeline.js';\nimport type { PipelineStep } from '../core/types.js';\nimport type { PipelineModuleOptions } from './pipeline.options.js';\n\n@Module({})\nexport class PipelineModule {\n static forRoot(options: PipelineModuleOptions): DynamicModule {\n const providers: Provider[] = [];\n\n for (const def of options.pipelines) {\n for (const step of def.steps) {\n providers.push({ provide: step, useClass: step });\n }\n\n providers.push({\n provide: def.token.symbol,\n useFactory: (...stepInstances: PipelineStep<unknown, unknown>[]) => {\n const p = new Pipeline(def.options ?? {});\n for (const instance of stepInstances) {\n p.pipe(instance);\n }\n return p;\n },\n inject: def.steps,\n });\n }\n\n return {\n module: PipelineModule,\n providers,\n exports: providers,\n global: true,\n };\n }\n}\n","import { Inject } from '@nestjs/common';\nimport type { PipelineToken } from '../core/define-pipeline.js';\n\nexport const InjectPipeline = <TContext, TError>(\n token: PipelineToken<TContext, TError>,\n): ReturnType<typeof Inject> => Inject(token.symbol);\n"]}
package/package.json ADDED
@@ -0,0 +1,71 @@
1
+ {
2
+ "name": "@backendkit-labs/pipeline",
3
+ "version": "0.1.0",
4
+ "description": "Type-safe async pipeline — Chain of Responsibility with stop-on-first / collect-all modes, conditional steps, observability hooks, and optional NestJS integration",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./nestjs": {
16
+ "types": "./dist/nestjs/index.d.ts",
17
+ "import": "./dist/nestjs/index.js",
18
+ "require": "./dist/nestjs/index.cjs"
19
+ }
20
+ },
21
+ "files": ["dist", "README.md", "LICENSE"],
22
+ "scripts": {
23
+ "build": "tsup",
24
+ "dev": "tsup --watch",
25
+ "test": "vitest run",
26
+ "test:watch": "vitest",
27
+ "test:coverage": "vitest run --coverage",
28
+ "typecheck": "tsc --noEmit",
29
+ "lint": "eslint src/",
30
+ "prepublishOnly": "npm run build && npm run test && npm run lint"
31
+ },
32
+ "keywords": [
33
+ "pipeline", "chain-of-responsibility", "middleware", "handler",
34
+ "async", "typescript", "nestjs", "node", "pattern"
35
+ ],
36
+ "author": "Mairon José Cuello Martínez",
37
+ "license": "MIT",
38
+ "homepage": "https://github.com/backendkit-dev/backendkit-monorepo/tree/master/packages/pipeline#readme",
39
+ "repository": {
40
+ "type": "git",
41
+ "url": "git+https://github.com/backendkit-dev/backendkit-monorepo.git",
42
+ "directory": "packages/pipeline"
43
+ },
44
+ "bugs": { "url": "https://github.com/backendkit-dev/backendkit-monorepo/issues" },
45
+ "publishConfig": { "access": "public" },
46
+ "sideEffects": false,
47
+ "engines": { "node": ">=18" },
48
+ "peerDependencies": {
49
+ "@nestjs/common": ">=10.0.0",
50
+ "@nestjs/core": ">=10.0.0",
51
+ "rxjs": ">=7.0.0"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@nestjs/common": { "optional": true },
55
+ "@nestjs/core": { "optional": true },
56
+ "rxjs": { "optional": true }
57
+ },
58
+ "devDependencies": {
59
+ "@eslint/js": "^9.39.4",
60
+ "@nestjs/common": "^10.0.0",
61
+ "@nestjs/core": "^10.0.0",
62
+ "@types/node": "^22.0.0",
63
+ "eslint": "^9.0.0",
64
+ "reflect-metadata": "^0.2.0",
65
+ "rxjs": "^7.8.0",
66
+ "tsup": "^8.0.0",
67
+ "typescript": "^5.5.0",
68
+ "typescript-eslint": "^8.59.3",
69
+ "vitest": "^2.0.0"
70
+ }
71
+ }