@veloxts/router 0.7.9 → 0.8.1
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/CHANGELOG.md +21 -0
- package/GUIDE.md +30 -48
- package/README.md +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +2 -1
- package/dist/openapi/generator.js +5 -1
- package/dist/openapi/schema-converter.d.ts +11 -0
- package/dist/openapi/schema-converter.js +47 -0
- package/dist/procedure/builder.d.ts +1 -1
- package/dist/procedure/builder.js +383 -53
- package/dist/procedure/pipeline-executor.d.ts +69 -0
- package/dist/procedure/pipeline-executor.js +134 -0
- package/dist/procedure/pipeline.d.ts +98 -0
- package/dist/procedure/pipeline.js +50 -0
- package/dist/procedure/types.d.ts +255 -99
- package/dist/resource/index.d.ts +4 -4
- package/dist/resource/index.js +3 -3
- package/dist/resource/instance.d.ts +1 -1
- package/dist/resource/instance.js +1 -1
- package/dist/resource/levels.d.ts +93 -11
- package/dist/resource/levels.js +78 -3
- package/dist/resource/schema.d.ts +2 -0
- package/dist/resource/schema.js +4 -4
- package/dist/resource/tags.d.ts +5 -7
- package/dist/resource/tags.js +1 -1
- package/dist/trpc/adapter.js +5 -0
- package/dist/types.d.ts +172 -18
- package/package.json +8 -8
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline executor for .through() steps
|
|
3
|
+
*
|
|
4
|
+
* Executes pipeline steps in declaration order. Each step's output becomes
|
|
5
|
+
* the next step's input. On failure, runs revert actions for completed
|
|
6
|
+
* steps in reverse order (compensation pattern).
|
|
7
|
+
*
|
|
8
|
+
* When `.transactional()` is combined with `.through()`, the executor
|
|
9
|
+
* implements a two-phase model:
|
|
10
|
+
* - Phase A: DB steps (non-external) run inside the transaction
|
|
11
|
+
* - Phase B: External steps run after the transaction commits
|
|
12
|
+
*
|
|
13
|
+
* @module procedure/pipeline-executor
|
|
14
|
+
*/
|
|
15
|
+
import { ConfigurationError, createLogger } from '@veloxts/core';
|
|
16
|
+
const log = createLogger('router');
|
|
17
|
+
/**
|
|
18
|
+
* Splits pipeline steps into DB (non-external) and external phases
|
|
19
|
+
*
|
|
20
|
+
* Validates the ordering constraint: when transactional, all external steps
|
|
21
|
+
* must come AFTER all DB steps. If an external step appears before a DB step,
|
|
22
|
+
* a ConfigurationError is thrown.
|
|
23
|
+
*
|
|
24
|
+
* @param steps - Pipeline steps to split
|
|
25
|
+
* @returns Object with `dbSteps` and `externalSteps` arrays
|
|
26
|
+
* @throws ConfigurationError if an external step precedes a DB step
|
|
27
|
+
*/
|
|
28
|
+
export function splitPipelineSteps(steps) {
|
|
29
|
+
const dbSteps = [];
|
|
30
|
+
const externalSteps = [];
|
|
31
|
+
let seenExternal = false;
|
|
32
|
+
for (const step of steps) {
|
|
33
|
+
if (step.external) {
|
|
34
|
+
seenExternal = true;
|
|
35
|
+
externalSteps.push(step);
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
if (seenExternal) {
|
|
39
|
+
throw new ConfigurationError(`Pipeline step "${step.name}" (DB) is declared after external step "${externalSteps[externalSteps.length - 1].name}". ` +
|
|
40
|
+
'When using .transactional(), all external steps must come after all DB steps in the .through() declaration.');
|
|
41
|
+
}
|
|
42
|
+
dbSteps.push(step);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return { dbSteps, externalSteps };
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Executes a sequence of pipeline steps
|
|
49
|
+
*
|
|
50
|
+
* Steps run in declaration order. Each step receives the previous step's
|
|
51
|
+
* output as its input (the first step receives the original validated input).
|
|
52
|
+
*
|
|
53
|
+
* If a step fails:
|
|
54
|
+
* 1. Collects all completed steps that have a revertAction
|
|
55
|
+
* 2. Runs their revert handlers in REVERSE order
|
|
56
|
+
* 3. Each revert receives the output of the step being reverted
|
|
57
|
+
* 4. Revert errors are logged but do not suppress the original error
|
|
58
|
+
* 5. Rethrows the original error
|
|
59
|
+
*
|
|
60
|
+
* @param steps - Pipeline steps to execute in order
|
|
61
|
+
* @param input - Initial input (typically the validated procedure input)
|
|
62
|
+
* @param ctx - Request context
|
|
63
|
+
* @returns The output of the last step
|
|
64
|
+
*/
|
|
65
|
+
export async function executePipeline(steps, input, ctx) {
|
|
66
|
+
const completedSteps = [];
|
|
67
|
+
let currentInput = input;
|
|
68
|
+
for (const step of steps) {
|
|
69
|
+
try {
|
|
70
|
+
const output = await step.handler({ input: currentInput, ctx });
|
|
71
|
+
completedSteps.push({ step, output });
|
|
72
|
+
currentInput = output;
|
|
73
|
+
}
|
|
74
|
+
catch (error) {
|
|
75
|
+
// Step failed — run reverts for completed steps in reverse order
|
|
76
|
+
await runReverts(completedSteps, ctx);
|
|
77
|
+
throw error;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return currentInput;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Executes external pipeline steps after a transaction has committed
|
|
84
|
+
*
|
|
85
|
+
* External steps run in declaration order. If a step fails, revert actions
|
|
86
|
+
* are run for all previously completed EXTERNAL steps in reverse order.
|
|
87
|
+
* The DB transaction is already committed — DB changes persist.
|
|
88
|
+
*
|
|
89
|
+
* @param steps - External pipeline steps to execute in order
|
|
90
|
+
* @param input - Input for the first external step (output of last DB step, or original input)
|
|
91
|
+
* @param ctx - Request context (with the ORIGINAL db, not the transactional client)
|
|
92
|
+
* @returns The output of the last external step (ignored by the caller, since handler already ran)
|
|
93
|
+
*/
|
|
94
|
+
export async function executeExternalSteps(steps, input, ctx) {
|
|
95
|
+
const completedSteps = [];
|
|
96
|
+
let currentInput = input;
|
|
97
|
+
for (const step of steps) {
|
|
98
|
+
try {
|
|
99
|
+
const output = await step.handler({ input: currentInput, ctx });
|
|
100
|
+
completedSteps.push({ step, output });
|
|
101
|
+
currentInput = output;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
// External step failed — revert only completed external steps
|
|
105
|
+
await runReverts(completedSteps, ctx);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Runs revert actions for completed steps in reverse order
|
|
112
|
+
*
|
|
113
|
+
* Each revert receives the output of the step it is reverting.
|
|
114
|
+
* Revert errors are logged but never suppress the original error.
|
|
115
|
+
*
|
|
116
|
+
* @param completedSteps - Steps that completed successfully before the failure
|
|
117
|
+
* @param ctx - Request context (forwarded to revert handlers)
|
|
118
|
+
* @internal
|
|
119
|
+
*/
|
|
120
|
+
async function runReverts(completedSteps, ctx) {
|
|
121
|
+
// Process in reverse order
|
|
122
|
+
for (let i = completedSteps.length - 1; i >= 0; i--) {
|
|
123
|
+
const { step, output } = completedSteps[i];
|
|
124
|
+
if (!step.revertAction) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
await step.revertAction.handler({ input: output, ctx });
|
|
129
|
+
}
|
|
130
|
+
catch (revertError) {
|
|
131
|
+
log.error(`Revert "${step.revertAction.name}" for step "${step.name}" failed:`, revertError);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline step and revert action factories
|
|
3
|
+
*
|
|
4
|
+
* Provides `defineStep` and `defineRevert` for building pipeline steps
|
|
5
|
+
* that execute in sequence via `.through()` on the procedure builder.
|
|
6
|
+
* Each step's output becomes the next step's input. External steps run
|
|
7
|
+
* outside DB transactions and can have revert actions for compensation.
|
|
8
|
+
*
|
|
9
|
+
* @module procedure/pipeline
|
|
10
|
+
*/
|
|
11
|
+
import type { BaseContext } from '@veloxts/core';
|
|
12
|
+
/**
|
|
13
|
+
* Options for configuring a pipeline step
|
|
14
|
+
*/
|
|
15
|
+
export interface StepOptions {
|
|
16
|
+
/** Step name used for logging and error reporting */
|
|
17
|
+
readonly name: string;
|
|
18
|
+
/** Whether this step runs outside the DB transaction (default: false) */
|
|
19
|
+
readonly external?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* A revert action that undoes the effect of an external step
|
|
23
|
+
*
|
|
24
|
+
* Revert actions are invoked when a later step in the pipeline fails,
|
|
25
|
+
* allowing compensation for steps that ran outside the DB transaction.
|
|
26
|
+
*/
|
|
27
|
+
export interface RevertAction<TInput = unknown> {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
readonly handler: (params: {
|
|
30
|
+
input: TInput;
|
|
31
|
+
ctx: BaseContext;
|
|
32
|
+
}) => void | Promise<void>;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* A single step in a procedure pipeline
|
|
36
|
+
*
|
|
37
|
+
* Steps execute in order, each one's output becoming the next one's input.
|
|
38
|
+
* External steps run outside DB transactions and may have revert actions
|
|
39
|
+
* for compensation when a later step fails.
|
|
40
|
+
*/
|
|
41
|
+
export interface PipelineStep<TInput = unknown, TOutput = unknown> {
|
|
42
|
+
readonly name: string;
|
|
43
|
+
readonly external: boolean;
|
|
44
|
+
readonly handler: (params: {
|
|
45
|
+
input: TInput;
|
|
46
|
+
ctx: BaseContext;
|
|
47
|
+
}) => TOutput | Promise<TOutput>;
|
|
48
|
+
readonly revertAction?: RevertAction;
|
|
49
|
+
/** Attach a revert action to this step, returning a new step (immutable) */
|
|
50
|
+
onRevert(revert: RevertAction): PipelineStep<TInput, TOutput>;
|
|
51
|
+
}
|
|
52
|
+
/** Handler function signature for pipeline steps */
|
|
53
|
+
type StepHandler<TInput, TOutput> = (params: {
|
|
54
|
+
input: TInput;
|
|
55
|
+
ctx: BaseContext;
|
|
56
|
+
}) => TOutput | Promise<TOutput>;
|
|
57
|
+
/**
|
|
58
|
+
* Define a pipeline step with a string name (external defaults to false)
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```typescript
|
|
62
|
+
* const validate = defineStep('validateInventory', async ({ input, ctx }) => {
|
|
63
|
+
* return { ...input, validated: true };
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
export declare function defineStep<TInput = unknown, TOutput = unknown>(name: string, handler: StepHandler<TInput, TOutput>): PipelineStep<TInput, TOutput>;
|
|
68
|
+
/**
|
|
69
|
+
* Define a pipeline step with options (name + external flag)
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* const charge = defineStep(
|
|
74
|
+
* { name: 'chargePayment', external: true },
|
|
75
|
+
* async ({ input, ctx }) => {
|
|
76
|
+
* return { ...input, chargeId: 'ch_123' };
|
|
77
|
+
* },
|
|
78
|
+
* );
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare function defineStep<TInput = unknown, TOutput = unknown>(options: StepOptions, handler: StepHandler<TInput, TOutput>): PipelineStep<TInput, TOutput>;
|
|
82
|
+
/**
|
|
83
|
+
* Define a revert action for compensating an external pipeline step
|
|
84
|
+
*
|
|
85
|
+
* Revert handlers receive the same `{ input, ctx }` shape but return void.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```typescript
|
|
89
|
+
* const refund = defineRevert('refundPayment', async ({ input, ctx }) => {
|
|
90
|
+
* await gateway.refund(input.chargeId);
|
|
91
|
+
* });
|
|
92
|
+
* ```
|
|
93
|
+
*/
|
|
94
|
+
export declare function defineRevert<TInput = unknown>(name: string, handler: (params: {
|
|
95
|
+
input: TInput;
|
|
96
|
+
ctx: BaseContext;
|
|
97
|
+
}) => void | Promise<void>): RevertAction<TInput>;
|
|
98
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline step and revert action factories
|
|
3
|
+
*
|
|
4
|
+
* Provides `defineStep` and `defineRevert` for building pipeline steps
|
|
5
|
+
* that execute in sequence via `.through()` on the procedure builder.
|
|
6
|
+
* Each step's output becomes the next step's input. External steps run
|
|
7
|
+
* outside DB transactions and can have revert actions for compensation.
|
|
8
|
+
*
|
|
9
|
+
* @module procedure/pipeline
|
|
10
|
+
*/
|
|
11
|
+
/** @internal */
|
|
12
|
+
export function defineStep(nameOrOptions, handler) {
|
|
13
|
+
const resolved = typeof nameOrOptions === 'string'
|
|
14
|
+
? { name: nameOrOptions, external: false }
|
|
15
|
+
: { name: nameOrOptions.name, external: nameOrOptions.external ?? false };
|
|
16
|
+
return createStep(resolved.name, resolved.external, handler, undefined);
|
|
17
|
+
}
|
|
18
|
+
// ============================================================================
|
|
19
|
+
// defineRevert
|
|
20
|
+
// ============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Define a revert action for compensating an external pipeline step
|
|
23
|
+
*
|
|
24
|
+
* Revert handlers receive the same `{ input, ctx }` shape but return void.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```typescript
|
|
28
|
+
* const refund = defineRevert('refundPayment', async ({ input, ctx }) => {
|
|
29
|
+
* await gateway.refund(input.chargeId);
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function defineRevert(name, handler) {
|
|
34
|
+
return { name, handler };
|
|
35
|
+
}
|
|
36
|
+
// ============================================================================
|
|
37
|
+
// Internal helpers
|
|
38
|
+
// ============================================================================
|
|
39
|
+
/** @internal Create a PipelineStep object with onRevert method */
|
|
40
|
+
function createStep(name, external, handler, revertAction) {
|
|
41
|
+
return {
|
|
42
|
+
name,
|
|
43
|
+
external,
|
|
44
|
+
handler,
|
|
45
|
+
revertAction,
|
|
46
|
+
onRevert(revert) {
|
|
47
|
+
return createStep(name, external, handler, revert);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|