@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 +447 -0
- package/dist/chunk-GHWTXTUB.js +93 -0
- package/dist/chunk-GHWTXTUB.js.map +1 -0
- package/dist/chunk-LPCHUZQE.cjs +97 -0
- package/dist/chunk-LPCHUZQE.cjs.map +1 -0
- package/dist/define-pipeline-BNKM59ML.d.cts +52 -0
- package/dist/define-pipeline-BNKM59ML.d.ts +52 -0
- package/dist/index.cjs +39 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +17 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -0
- package/dist/nestjs/index.cjs +40 -0
- package/dist/nestjs/index.cjs.map +1 -0
- package/dist/nestjs/index.d.cts +19 -0
- package/dist/nestjs/index.d.ts +19 -0
- package/dist/nestjs/index.js +38 -0
- package/dist/nestjs/index.js.map +1 -0
- package/package.json +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
# @backendkit-labs/pipeline
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@backendkit-labs/pipeline)
|
|
4
|
+
[](https://github.com/backendkit-dev/backendkit-monorepo/actions/workflows/ci.yml)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](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":";;;;;;;;;;;;;;AAaO,IAAM,WAAN,MAA2C;AAAA,EAC/B,UAAyC,EAAC;AAAA,EAC1C,OAAA;AAAA,EAEjB,WAAA,CAAY,OAAA,GAA6C,EAAC,EAAG;AAC3D,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AAAA,EAEA,KAAK,IAAA,EAA4C;AAC/C,IAAA,IAAA,CAAK,QAAQ,IAAA,CAAK;AAAA,MAChB,QAAA,EAAU,IAAA;AAAA,MACV,IAAA,EAAU,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,WAAA,CAAY;AAAA,KAC7C,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAA,CAAO,WAAuC,IAAA,EAA4C;AACxF,IAAA,IAAA,CAAK,QAAQ,IAAA,CAAK;AAAA,MAChB,QAAA,EAAW,IAAA;AAAA,MACX,IAAA,EAAW,IAAA,CAAK,QAAA,IAAY,IAAA,CAAK,WAAA,CAAY,IAAA;AAAA,MAC7C;AAAA,KACD,CAAA;AACD,IAAA,OAAO,IAAA;AAAA,EACT;AAAA,EAEA,MAAM,IAAI,UAAA,EAAoE;AAC5E,IAAA,MAAM,KAAA,GAAQ,KAAK,GAAA,EAAI;AACvB,IAAA,MAAM,EAAE,OAAO,eAAA,EAAiB,MAAA,EAAQ,gBAAgB,OAAA,EAAS,UAAA,KAAe,IAAA,CAAK,OAAA;AACrF,IAAA,MAAM,gBAA+C,EAAC;AACtD,IAAA,MAAM,WAA+C,EAAC;AACtD,IAAA,IAAM,GAAA,GAAM,UAAA;AAEZ,IAAA,KAAA,MAAW,EAAE,QAAA,EAAU,IAAA,EAAM,SAAA,EAAU,IAAK,KAAK,OAAA,EAAS;AACxD,MAAA,IAAI,SAAA,IAAa,CAAC,SAAA,CAAU,GAAG,CAAA,EAAG;AAElC,MAAA,MAAM,MAAA,GAAS,MAAM,GAAG,CAAA;AACxB,MAAA,MAAM,SAAA,GAAa,KAAK,GAAA,EAAI;AAC5B,MAAA,MAAM,UAAA,GAAa,MAAM,QAAA,CAAS,MAAA,CAAO,GAAG,CAAA;AAC5C,MAAA,MAAM,MAAA,GAAa,IAAA,CAAK,GAAA,EAAI,GAAI,SAAA;AAEhC,MAAA,IAAI,CAAC,WAAW,EAAA,EAAI;AAClB,QAAA,MAAM,OAAA,GAAU,IAAA,EAAM,UAAA,CAAW,KAAK,CAAA;AACtC,QAAA,QAAA,CAAS,KAAK,EAAE,IAAA,EAAM,MAAM,KAAA,EAAO,UAAA,CAAW,OAAO,CAAA;AAErD,QAAA,IAAI,SAAS,eAAA,EAAiB;AAC5B,UAAA,OAAO;AAAA,YACL,EAAA,EAAO,KAAA;AAAA,YACP,KAAA,EAAO;AAAA,cACL,IAAA;AAAA,cACA,UAAA,EAAe,IAAA;AAAA,cACf,OAAe,UAAA,CAAW,KAAA;AAAA,cAC1B,aAAA,EAAe,CAAC,GAAG,aAAa,CAAA;AAAA,cAChC,UAAA,EAAe,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA;AAAA,cAC5B;AAAA;AACF,WACF;AAAA,QACF;AAGA,QAAA,aAAA,CAAc,KAAK,IAAI,CAAA;AACvB,QAAA;AAAA,MACF;AAEA,MAAA,GAAA,GAAM,UAAA,CAAW,KAAA;AACjB,MAAA,aAAA,CAAc,KAAK,IAAI,CAAA;AACvB,MAAA,MAAM,cAAA,GAAiB,IAAA,EAAM,GAAA,EAAK,MAAM,CAAA;AAAA,IAC1C;AAEA,IAAA,IAAI,QAAA,CAAS,SAAS,CAAA,EAAG;AACvB,MAAA,OAAO;AAAA,QACL,EAAA,EAAO,KAAA;AAAA,QACP,KAAA,EAAO;AAAA,UACL,IAAA;AAAA,UACA,UAAA,EAAe,QAAA,CAAS,CAAC,CAAA,CAAE,IAAA;AAAA,UAC3B,KAAA,EAAe,QAAA,CAAS,CAAC,CAAA,CAAE,KAAA;AAAA,UAC3B,aAAA,EAAe,CAAC,GAAG,aAAa,CAAA;AAAA,UAChC,UAAA,EAAe,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA;AAAA,UAC5B;AAAA;AACF,OACF;AAAA,IACF;AAEA,IAAA,MAAM,UAAA,GAAa,IAAA,CAAK,GAAA,EAAI,GAAI,KAAA;AAChC,IAAA,MAAM,UAAA,GAAa,KAAK,UAAU,CAAA;AAClC,IAAA,OAAO,EAAE,EAAA,EAAI,IAAA,EAAM,KAAA,EAAO,GAAA,EAAK,eAAe,CAAC,GAAG,aAAa,CAAA,EAAG,UAAA,EAAW;AAAA,EAC/E;AACF;AAEO,SAAS,SACd,OAAA,EAC4B;AAC5B,EAAA,OAAO,IAAI,SAAS,OAAO,CAAA;AAC7B","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"]}
|
package/dist/index.d.cts
ADDED
|
@@ -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 };
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|