@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
|
@@ -9,11 +9,11 @@
|
|
|
9
9
|
*
|
|
10
10
|
* @module procedure/types
|
|
11
11
|
*/
|
|
12
|
-
import type { BaseContext } from '@veloxts/core';
|
|
12
|
+
import type { BaseContext, DomainError } from '@veloxts/core';
|
|
13
13
|
import type { ZodType } from 'zod';
|
|
14
|
-
import type {
|
|
15
|
-
import type {
|
|
16
|
-
import type {
|
|
14
|
+
import type { OutputForLevel, ResourceSchema, TaggedResourceSchema } from '../resource/index.js';
|
|
15
|
+
import type { AfterHandler, CompiledProcedure, GuardLike, MiddlewareFunction, ParentResourceConfig, PolicyActionLike, ProcedureHandler, RestRouteOverride, TransactionalOptions } from '../types.js';
|
|
16
|
+
import type { PipelineStep } from './pipeline.js';
|
|
17
17
|
/**
|
|
18
18
|
* Internal state type that accumulates type information through the builder chain
|
|
19
19
|
*
|
|
@@ -23,14 +23,16 @@ import type { CompiledProcedure, GuardLike, MiddlewareFunction, ParentResourceCo
|
|
|
23
23
|
* @template TInput - The validated input type (unknown if no input schema)
|
|
24
24
|
* @template TOutput - The validated output type (unknown if no output schema)
|
|
25
25
|
* @template TContext - The context type (starts as BaseContext, extended by middleware)
|
|
26
|
+
* @template TErrors - Union of domain error types (defaults to never)
|
|
26
27
|
*/
|
|
27
|
-
export interface ProcedureBuilderState<TInput = unknown, TOutput = unknown, TContext extends BaseContext = BaseContext> {
|
|
28
|
+
export interface ProcedureBuilderState<TInput = unknown, TOutput = unknown, TContext extends BaseContext = BaseContext, TErrors = never> {
|
|
28
29
|
/** Marker for state identification */
|
|
29
30
|
readonly _brand: 'ProcedureBuilderState';
|
|
30
31
|
/** Phantom type holders - not used at runtime */
|
|
31
32
|
readonly _input: TInput;
|
|
32
33
|
readonly _output: TOutput;
|
|
33
34
|
readonly _context: TContext;
|
|
35
|
+
readonly _errors: TErrors;
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
36
38
|
* Constraint for valid input/output schemas
|
|
@@ -50,6 +52,20 @@ export type ValidSchema = ZodType;
|
|
|
50
52
|
export type InferSchemaOutput<T> = T extends {
|
|
51
53
|
parse: (data: unknown) => infer O;
|
|
52
54
|
} ? O : never;
|
|
55
|
+
/**
|
|
56
|
+
* Valid types for `.output()` — Zod schemas, tagged resource views, or untagged resource schemas
|
|
57
|
+
*/
|
|
58
|
+
export type ValidOutputSchema = ValidSchema | TaggedResourceSchema | ResourceSchema;
|
|
59
|
+
/**
|
|
60
|
+
* Infers the output type from a schema passed to `.output()`
|
|
61
|
+
*
|
|
62
|
+
* - Zod schema → uses structural `parse()` matching
|
|
63
|
+
* - Tagged resource view → uses `OutputForLevel` to compute projected fields
|
|
64
|
+
* - Untagged resource schema → falls back to `unknown` (Level 3 branching resolves at runtime)
|
|
65
|
+
*/
|
|
66
|
+
export type InferOutputSchema<T> = T extends TaggedResourceSchema ? OutputForLevel<T> : T extends {
|
|
67
|
+
parse: (data: unknown) => infer O;
|
|
68
|
+
} ? O : unknown;
|
|
53
69
|
/**
|
|
54
70
|
* Fluent procedure builder interface
|
|
55
71
|
*
|
|
@@ -60,6 +76,7 @@ export type InferSchemaOutput<T> = T extends {
|
|
|
60
76
|
* @template TInput - Current input type
|
|
61
77
|
* @template TOutput - Current output type
|
|
62
78
|
* @template TContext - Current context type
|
|
79
|
+
* @template TErrors - Union of domain error types (defaults to never)
|
|
63
80
|
*
|
|
64
81
|
* @example
|
|
65
82
|
* ```typescript
|
|
@@ -71,7 +88,7 @@ export type InferSchemaOutput<T> = T extends {
|
|
|
71
88
|
* })
|
|
72
89
|
* ```
|
|
73
90
|
*/
|
|
74
|
-
export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext extends BaseContext = BaseContext> {
|
|
91
|
+
export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext extends BaseContext = BaseContext, TErrors = never> {
|
|
75
92
|
/**
|
|
76
93
|
* Defines the input validation schema for the procedure
|
|
77
94
|
*
|
|
@@ -92,56 +109,33 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
92
109
|
* // input is now typed as { id: string; name?: string }
|
|
93
110
|
* ```
|
|
94
111
|
*/
|
|
95
|
-
input<TSchema extends ValidSchema>(schema: TSchema): ProcedureBuilder<InferSchemaOutput<TSchema>, TOutput, TContext>;
|
|
112
|
+
input<TSchema extends ValidSchema>(schema: TSchema): ProcedureBuilder<InferSchemaOutput<TSchema>, TOutput, TContext, TErrors>;
|
|
96
113
|
/**
|
|
97
|
-
* Defines the output
|
|
114
|
+
* Defines the output schema for the procedure
|
|
98
115
|
*
|
|
99
|
-
*
|
|
100
|
-
*
|
|
116
|
+
* Accepts two schema variants:
|
|
117
|
+
* 1. **Zod schema** — validates handler return value, all callers see same fields
|
|
118
|
+
* 2. **Tagged resource view** (e.g., `UserSchema.authenticated`) — auto-projects fields by access level
|
|
101
119
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
* @template TSchema - The Zod schema type
|
|
106
|
-
* @param schema - Zod schema for output validation
|
|
120
|
+
* @template TSchema - The Zod or tagged resource schema type
|
|
121
|
+
* @param schema - Zod schema or tagged resource view
|
|
107
122
|
* @returns New builder with updated output type
|
|
108
123
|
*
|
|
109
|
-
* @example
|
|
124
|
+
* @example Zod schema
|
|
110
125
|
* ```typescript
|
|
111
126
|
* procedure()
|
|
112
127
|
* .output(z.object({ id: z.string(), name: z.string() }))
|
|
113
128
|
* .query(handler) // handler must return { id: string; name: string }
|
|
114
129
|
* ```
|
|
115
|
-
*/
|
|
116
|
-
output<TSchema extends ValidSchema>(schema: TSchema): ProcedureBuilder<TInput, InferSchemaOutput<TSchema>, TContext>;
|
|
117
|
-
/**
|
|
118
|
-
* Sets field-level visibility via a resource schema
|
|
119
|
-
*
|
|
120
|
-
* Accepts two resource schema variants:
|
|
121
|
-
* 1. **Tagged resource schema** (e.g., `UserSchema.authenticated`) — explicit field projection by access level
|
|
122
|
-
* 2. **Plain resource schema** (e.g., `UserSchema`) — context-derived field projection from `guardNarrow`
|
|
123
130
|
*
|
|
124
|
-
* @
|
|
125
|
-
* @param schema - Resource schema for field projection
|
|
126
|
-
* @returns New builder with updated output type
|
|
127
|
-
*
|
|
128
|
-
* @example Tagged resource schema — explicit projection level
|
|
131
|
+
* @example Tagged resource view
|
|
129
132
|
* ```typescript
|
|
130
133
|
* procedure()
|
|
131
|
-
* .
|
|
132
|
-
* .expose(UserSchema.authenticated) // returns { id, name, email }
|
|
133
|
-
* .query(handler)
|
|
134
|
-
* ```
|
|
135
|
-
*
|
|
136
|
-
* @example Plain resource schema — derives level from guardNarrow
|
|
137
|
-
* ```typescript
|
|
138
|
-
* procedure()
|
|
139
|
-
* .guardNarrow(authenticatedNarrow)
|
|
140
|
-
* .expose(UserSchema) // auto-projects based on guard's accessLevel
|
|
134
|
+
* .output(UserSchema.authenticated) // auto-projects to { id, name, email }
|
|
141
135
|
* .query(handler)
|
|
142
136
|
* ```
|
|
143
137
|
*/
|
|
144
|
-
|
|
138
|
+
output<TSchema extends ValidOutputSchema>(schema: TSchema): ProcedureBuilder<TInput, InferOutputSchema<TSchema>, TContext, TErrors>;
|
|
145
139
|
/**
|
|
146
140
|
* Adds middleware to the procedure chain
|
|
147
141
|
*
|
|
@@ -171,7 +165,7 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
171
165
|
* })
|
|
172
166
|
* ```
|
|
173
167
|
*/
|
|
174
|
-
use<TNewContext extends BaseContext = TContext>(middleware: MiddlewareFunction<TInput, TContext, TNewContext, TOutput>): ProcedureBuilder<TInput, TOutput, TNewContext>;
|
|
168
|
+
use<TNewContext extends BaseContext = TContext>(middleware: MiddlewareFunction<TInput, TContext, TNewContext, TOutput>): ProcedureBuilder<TInput, TOutput, TNewContext, TErrors>;
|
|
175
169
|
/**
|
|
176
170
|
* Adds an authorization guard to the procedure
|
|
177
171
|
*
|
|
@@ -204,42 +198,7 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
204
198
|
* .mutation(async ({ input, ctx }) => { ... });
|
|
205
199
|
* ```
|
|
206
200
|
*/
|
|
207
|
-
guard<TGuardContext extends Partial<TContext>>(guard: GuardLike<TGuardContext>): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
208
|
-
/**
|
|
209
|
-
* Adds an authorization guard with type narrowing (EXPERIMENTAL)
|
|
210
|
-
*
|
|
211
|
-
* Unlike `.guard()`, this method narrows the context type based on
|
|
212
|
-
* what the guard guarantees. For example, `authenticatedNarrow` narrows
|
|
213
|
-
* `ctx.user` from `User | undefined` to `User`.
|
|
214
|
-
*
|
|
215
|
-
* **EXPERIMENTAL**: This API may change. Consider using middleware
|
|
216
|
-
* for context type extension as the current stable alternative.
|
|
217
|
-
*
|
|
218
|
-
* @template TNarrowedContext - The context type guaranteed by the guard
|
|
219
|
-
* @param guard - Narrowing guard definition with `_narrows` type
|
|
220
|
-
* @returns New builder with narrowed context type
|
|
221
|
-
*
|
|
222
|
-
* @example
|
|
223
|
-
* ```typescript
|
|
224
|
-
* import { authenticatedNarrow, hasRoleNarrow } from '@veloxts/auth';
|
|
225
|
-
*
|
|
226
|
-
* // ctx.user is guaranteed non-null after guard passes
|
|
227
|
-
* procedure()
|
|
228
|
-
* .guardNarrow(authenticatedNarrow)
|
|
229
|
-
* .query(({ ctx }) => {
|
|
230
|
-
* return { email: ctx.user.email }; // No null check needed!
|
|
231
|
-
* });
|
|
232
|
-
*
|
|
233
|
-
* // Chain multiple narrowing guards
|
|
234
|
-
* procedure()
|
|
235
|
-
* .guardNarrow(authenticatedNarrow)
|
|
236
|
-
* .guardNarrow(hasRoleNarrow('admin'))
|
|
237
|
-
* .mutation(({ ctx }) => { ... });
|
|
238
|
-
* ```
|
|
239
|
-
*/
|
|
240
|
-
guardNarrow<TNarrowedContext>(guard: GuardLike<Partial<TContext>> & {
|
|
241
|
-
readonly _narrows: TNarrowedContext;
|
|
242
|
-
}): ProcedureBuilder<TInput, TOutput, TContext & TNarrowedContext>;
|
|
201
|
+
guard<TGuardContext extends Partial<TContext>>(guard: GuardLike<TGuardContext>): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
243
202
|
/**
|
|
244
203
|
* Adds multiple authorization guards at once
|
|
245
204
|
*
|
|
@@ -266,7 +225,62 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
266
225
|
* .mutation(async ({ input, ctx }) => { ... });
|
|
267
226
|
* ```
|
|
268
227
|
*/
|
|
269
|
-
guards<TGuards extends GuardLike<Partial<TContext>>[]>(...guards: TGuards): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
228
|
+
guards<TGuards extends GuardLike<Partial<TContext>>[]>(...guards: TGuards): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
229
|
+
/**
|
|
230
|
+
* Adds a policy action check to the procedure
|
|
231
|
+
*
|
|
232
|
+
* Policy actions are checked during execution after guards but before
|
|
233
|
+
* the pipeline and handler. The policy looks up the resource on the
|
|
234
|
+
* context by lowercase resource name (e.g., `PostPolicy` → `ctx.post`).
|
|
235
|
+
*
|
|
236
|
+
* If the policy check fails, a ForbiddenError is thrown.
|
|
237
|
+
*
|
|
238
|
+
* **Compile-time safety:** When the resource name is a string literal
|
|
239
|
+
* (e.g., from `definePolicy('Post', ...)`), TypeScript enforces that the
|
|
240
|
+
* context contains the resource key. Forgetting `.use(loadPost)` before
|
|
241
|
+
* `.policy(PostPolicy.update)` produces a type error.
|
|
242
|
+
*
|
|
243
|
+
* @param action - Policy action reference (from definePolicy)
|
|
244
|
+
* @returns Same builder (no type changes)
|
|
245
|
+
*
|
|
246
|
+
* @example
|
|
247
|
+
* ```typescript
|
|
248
|
+
* import { PostPolicy } from './policies';
|
|
249
|
+
*
|
|
250
|
+
* procedure()
|
|
251
|
+
* .guard(authenticated)
|
|
252
|
+
* .use(loadPost) // adds { post: Post } to context
|
|
253
|
+
* .policy(PostPolicy.update) // ✓ context has 'post'
|
|
254
|
+
* .mutation(async ({ input, ctx }) => { ... });
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
policy<TResourceName extends string>(action: string extends TResourceName ? PolicyActionLike<unknown, unknown, TResourceName> : Uncapitalize<TResourceName> extends keyof TContext ? PolicyActionLike<unknown, unknown, TResourceName> : PolicyActionLike<unknown, unknown, TResourceName> & {
|
|
258
|
+
_contextMissing: `Add .use() middleware to provide '${Uncapitalize<TResourceName>}' in context before .policy()`;
|
|
259
|
+
}): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
260
|
+
/**
|
|
261
|
+
* Declares domain error classes that this procedure may throw
|
|
262
|
+
*
|
|
263
|
+
* Stores error class references on the compiled procedure for:
|
|
264
|
+
* - OpenAPI error response generation (per-endpoint error schemas)
|
|
265
|
+
* - Client-side error type narrowing (`InferProcedureErrors<T>`)
|
|
266
|
+
*
|
|
267
|
+
* Can be called multiple times — error classes accumulate across calls.
|
|
268
|
+
*
|
|
269
|
+
* @template TDomainErrors - Union of domain error types declared
|
|
270
|
+
* @param errorClasses - Domain error constructors this procedure may throw
|
|
271
|
+
* @returns New builder with updated TErrors union
|
|
272
|
+
*
|
|
273
|
+
* @example
|
|
274
|
+
* ```typescript
|
|
275
|
+
* procedure()
|
|
276
|
+
* .input(z.object({ sku: z.string(), qty: z.number() }))
|
|
277
|
+
* .throws(InsufficientStock, PaymentFailed)
|
|
278
|
+
* .mutation(async ({ input }) => {
|
|
279
|
+
* // handler may throw InsufficientStock or PaymentFailed
|
|
280
|
+
* })
|
|
281
|
+
* ```
|
|
282
|
+
*/
|
|
283
|
+
throws<TDomainErrors extends DomainError<Record<string, unknown>>>(...errorClasses: Array<new (data: Record<string, unknown>) => TDomainErrors>): ProcedureBuilder<TInput, TOutput, TContext, TErrors | TDomainErrors>;
|
|
270
284
|
/**
|
|
271
285
|
* Configures REST route override
|
|
272
286
|
*
|
|
@@ -282,7 +296,7 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
282
296
|
* .rest({ method: 'POST', path: '/users/:id/activate' })
|
|
283
297
|
* ```
|
|
284
298
|
*/
|
|
285
|
-
rest(config: RestRouteOverride): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
299
|
+
rest(config: RestRouteOverride): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
286
300
|
/**
|
|
287
301
|
* Configures the procedure as a webhook endpoint
|
|
288
302
|
*
|
|
@@ -302,7 +316,7 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
302
316
|
* })
|
|
303
317
|
* ```
|
|
304
318
|
*/
|
|
305
|
-
webhook(path: string): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
319
|
+
webhook(path: string): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
306
320
|
/**
|
|
307
321
|
* Marks the procedure as deprecated
|
|
308
322
|
*
|
|
@@ -325,7 +339,7 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
325
339
|
* .query(handler);
|
|
326
340
|
* ```
|
|
327
341
|
*/
|
|
328
|
-
deprecated(message?: string): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
342
|
+
deprecated(message?: string): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
329
343
|
/**
|
|
330
344
|
* Declares a parent resource for nested routes (single level)
|
|
331
345
|
*
|
|
@@ -354,7 +368,7 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
354
368
|
* .query(async ({ input }) => { ... });
|
|
355
369
|
* ```
|
|
356
370
|
*/
|
|
357
|
-
parent(resource: string, param?: string): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
371
|
+
parent(resource: string, param?: string): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
358
372
|
/**
|
|
359
373
|
* Declares multiple parent resources for deeply nested routes
|
|
360
374
|
*
|
|
@@ -385,7 +399,94 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
385
399
|
parents(config: Array<{
|
|
386
400
|
resource: string;
|
|
387
401
|
param?: string;
|
|
388
|
-
}>): ProcedureBuilder<TInput, TOutput, TContext>;
|
|
402
|
+
}>): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
403
|
+
/**
|
|
404
|
+
* Declares a domain event to emit after successful handler execution
|
|
405
|
+
*
|
|
406
|
+
* Events fire AFTER the handler returns its result (and AFTER transaction
|
|
407
|
+
* commit when `.transactional()` is used). Multiple `.emits()` calls
|
|
408
|
+
* accumulate — all declared events are emitted in order.
|
|
409
|
+
*
|
|
410
|
+
* Emission errors are caught and logged; they never fail the request.
|
|
411
|
+
*
|
|
412
|
+
* @template TEventData - The event's data payload type
|
|
413
|
+
* @param eventClass - Domain event class constructor
|
|
414
|
+
* @param mapper - Optional function to transform the handler result into event data.
|
|
415
|
+
* When omitted, the handler result is used directly as event data.
|
|
416
|
+
* @returns Same builder (no type changes)
|
|
417
|
+
*
|
|
418
|
+
* @example With mapper (recommended)
|
|
419
|
+
* ```typescript
|
|
420
|
+
* procedure()
|
|
421
|
+
* .emits(OrderCreated, (result) => ({ orderId: result.id, total: result.total }))
|
|
422
|
+
* .mutation(handler)
|
|
423
|
+
* ```
|
|
424
|
+
*
|
|
425
|
+
* @example Without mapper (result type must match event data type)
|
|
426
|
+
* ```typescript
|
|
427
|
+
* procedure()
|
|
428
|
+
* .emits(OrderCreated)
|
|
429
|
+
* .mutation(handler)
|
|
430
|
+
* ```
|
|
431
|
+
*/
|
|
432
|
+
emits<TEventData extends Record<string, unknown>>(eventClass: {
|
|
433
|
+
new (data: TEventData, options?: {
|
|
434
|
+
correlationId?: string;
|
|
435
|
+
}): unknown;
|
|
436
|
+
readonly name: string;
|
|
437
|
+
}, mapper?: (result: TOutput) => TEventData): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
438
|
+
/**
|
|
439
|
+
* Wraps the handler in a database transaction via `ctx.db.$transaction()`
|
|
440
|
+
*
|
|
441
|
+
* When set, `executeProcedure` replaces `ctx.db` with the transactional
|
|
442
|
+
* client inside the handler. On throw the transaction auto-rollbacks;
|
|
443
|
+
* on return it auto-commits.
|
|
444
|
+
*
|
|
445
|
+
* Gracefully degrades: if `ctx.db` or `ctx.db.$transaction` is missing,
|
|
446
|
+
* the handler runs without transaction wrapping.
|
|
447
|
+
*
|
|
448
|
+
* @param options - Optional isolation level and timeout
|
|
449
|
+
* @returns Same builder (no type changes)
|
|
450
|
+
*
|
|
451
|
+
* @example
|
|
452
|
+
* ```typescript
|
|
453
|
+
* procedure()
|
|
454
|
+
* .input(CreateOrderSchema)
|
|
455
|
+
* .transactional({ isolationLevel: 'Serializable', timeout: 10000 })
|
|
456
|
+
* .mutation(async ({ input, ctx }) => {
|
|
457
|
+
* // ctx.db is the transactional client — all queries share the same tx
|
|
458
|
+
* const order = await ctx.db.order.create({ data: input });
|
|
459
|
+
* await ctx.db.inventory.update({ ... });
|
|
460
|
+
* return order; // auto-commit on success
|
|
461
|
+
* })
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
transactional(options?: TransactionalOptions): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
465
|
+
/**
|
|
466
|
+
* Adds pipeline steps that execute BEFORE the handler
|
|
467
|
+
*
|
|
468
|
+
* Steps run in declaration order. Each step's output becomes the next
|
|
469
|
+
* step's input, and the final step's output is passed to the handler
|
|
470
|
+
* as its `input`. If a step fails, revert actions for previously
|
|
471
|
+
* completed steps run in reverse order (compensation pattern).
|
|
472
|
+
*
|
|
473
|
+
* Can be called multiple times — steps accumulate across calls.
|
|
474
|
+
*
|
|
475
|
+
* @param steps - Pipeline steps to execute before the handler
|
|
476
|
+
* @returns Same builder (no type changes)
|
|
477
|
+
*
|
|
478
|
+
* @example
|
|
479
|
+
* ```typescript
|
|
480
|
+
* procedure()
|
|
481
|
+
* .input(CreateOrderSchema)
|
|
482
|
+
* .through(validateInventory, chargePayment.onRevert(refund))
|
|
483
|
+
* .mutation(async ({ input, ctx }) => {
|
|
484
|
+
* // input has been transformed by pipeline steps
|
|
485
|
+
* return ctx.db.order.create({ data: input });
|
|
486
|
+
* })
|
|
487
|
+
* ```
|
|
488
|
+
*/
|
|
489
|
+
through(...steps: PipelineStep[]): ProcedureBuilder<TInput, TOutput, TContext, TErrors>;
|
|
389
490
|
/**
|
|
390
491
|
* Finalizes the procedure as a query (read-only operation)
|
|
391
492
|
*
|
|
@@ -393,7 +494,7 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
393
494
|
* The handler receives the validated input and context.
|
|
394
495
|
*
|
|
395
496
|
* @param handler - The query handler function
|
|
396
|
-
* @returns
|
|
497
|
+
* @returns PostHandlerBuilder with all CompiledProcedure fields plus .useAfter()
|
|
397
498
|
*
|
|
398
499
|
* @example
|
|
399
500
|
* ```typescript
|
|
@@ -404,15 +505,18 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
404
505
|
* })
|
|
405
506
|
* ```
|
|
406
507
|
*/
|
|
407
|
-
query(handler: ProcedureHandler<TInput, TOutput, TContext>):
|
|
508
|
+
query(handler: ProcedureHandler<TInput, TOutput, TContext> | Record<string, ProcedureHandler<TInput, TOutput, TContext>>): PostHandlerBuilder<TInput, TOutput, TContext, 'query', TErrors>;
|
|
408
509
|
/**
|
|
409
510
|
* Finalizes the procedure as a mutation (write operation)
|
|
410
511
|
*
|
|
411
512
|
* Mutations map to POST/PUT/DELETE in REST and can modify data.
|
|
412
513
|
* The handler receives the validated input and context.
|
|
413
514
|
*
|
|
414
|
-
*
|
|
415
|
-
*
|
|
515
|
+
* In Level 3 branching mode (when `.output()` receives an untagged resource schema),
|
|
516
|
+
* accepts a handler map keyed by `[Schema.level.key]` instead of a single handler.
|
|
517
|
+
*
|
|
518
|
+
* @param handler - The mutation handler function or handler map
|
|
519
|
+
* @returns PostHandlerBuilder with all CompiledProcedure fields plus .useAfter()
|
|
416
520
|
*
|
|
417
521
|
* @example
|
|
418
522
|
* ```typescript
|
|
@@ -423,22 +527,52 @@ export interface ProcedureBuilder<TInput = unknown, TOutput = unknown, TContext
|
|
|
423
527
|
* })
|
|
424
528
|
* ```
|
|
425
529
|
*/
|
|
426
|
-
mutation(handler: ProcedureHandler<TInput, TOutput, TContext>):
|
|
530
|
+
mutation(handler: ProcedureHandler<TInput, TOutput, TContext> | Record<string, ProcedureHandler<TInput, TOutput, TContext>>): PostHandlerBuilder<TInput, TOutput, TContext, 'mutation', TErrors>;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Returned by `.query()` and `.mutation()` — extends CompiledProcedure with `.useAfter()`
|
|
534
|
+
*
|
|
535
|
+
* PostHandlerBuilder has all CompiledProcedure fields (so it's assignable to
|
|
536
|
+
* CompiledProcedure and passes `isCompiledProcedure` checks) plus a `.useAfter()`
|
|
537
|
+
* method for registering post-handler hooks.
|
|
538
|
+
*
|
|
539
|
+
* `.useAfter()` returns another PostHandlerBuilder so hooks can be chained.
|
|
540
|
+
*
|
|
541
|
+
* @template TInput - The validated input type
|
|
542
|
+
* @template TOutput - The handler output type
|
|
543
|
+
* @template TContext - The context type
|
|
544
|
+
* @template TType - The procedure type literal ('query' or 'mutation')
|
|
545
|
+
* @template TErrors - Union of domain error types (defaults to never)
|
|
546
|
+
*
|
|
547
|
+
* @example
|
|
548
|
+
* ```typescript
|
|
549
|
+
* procedure()
|
|
550
|
+
* .input(z.object({ id: z.string() }))
|
|
551
|
+
* .mutation(async ({ input, ctx }) => {
|
|
552
|
+
* return ctx.db.user.delete({ where: { id: input.id } });
|
|
553
|
+
* })
|
|
554
|
+
* .useAfter(({ input, result, ctx }) => {
|
|
555
|
+
* console.log(`Deleted user ${input.id}`);
|
|
556
|
+
* })
|
|
557
|
+
* .useAfter(({ result }) => {
|
|
558
|
+
* invalidateCache(result.id);
|
|
559
|
+
* })
|
|
560
|
+
* ```
|
|
561
|
+
*/
|
|
562
|
+
export interface PostHandlerBuilder<TInput = unknown, TOutput = unknown, TContext extends BaseContext = BaseContext, TType extends 'query' | 'mutation' = 'query' | 'mutation', TErrors = never> extends CompiledProcedure<TInput, TOutput, TContext, TType, TErrors> {
|
|
427
563
|
/**
|
|
428
|
-
*
|
|
564
|
+
* Registers a post-handler hook that runs after successful handler execution
|
|
429
565
|
*
|
|
430
|
-
*
|
|
566
|
+
* Hooks run after events are emitted (if any) and auto-projection is applied.
|
|
567
|
+
* Errors in hooks are caught and logged — they never fail the request.
|
|
568
|
+
* The return value is ignored: hooks cannot modify the result.
|
|
431
569
|
*
|
|
432
|
-
*
|
|
433
|
-
* ```typescript
|
|
434
|
-
* // Before
|
|
435
|
-
* procedure().resource(UserSchema.authenticated).query(handler)
|
|
570
|
+
* Multiple `.useAfter()` calls chain in registration order.
|
|
436
571
|
*
|
|
437
|
-
*
|
|
438
|
-
*
|
|
439
|
-
* ```
|
|
572
|
+
* @param handler - Post-handler hook function
|
|
573
|
+
* @returns New PostHandlerBuilder with the hook appended
|
|
440
574
|
*/
|
|
441
|
-
|
|
575
|
+
useAfter(handler: AfterHandler<TInput, TOutput, TContext>): PostHandlerBuilder<TInput, TOutput, TContext, TType, TErrors>;
|
|
442
576
|
}
|
|
443
577
|
/**
|
|
444
578
|
* Internal runtime state for the procedure builder
|
|
@@ -471,6 +605,28 @@ export interface BuilderRuntimeState {
|
|
|
471
605
|
deprecationMessage?: string;
|
|
472
606
|
/** Whether this procedure is a webhook endpoint (metadata marker) */
|
|
473
607
|
isWebhook?: boolean;
|
|
608
|
+
/** Whether .output() received an untagged resource schema (Level 3 branching mode) */
|
|
609
|
+
branchingMode?: boolean;
|
|
610
|
+
/** Error classes declared via .throws() */
|
|
611
|
+
errorClasses?: Array<new (data: Record<string, unknown>) => unknown>;
|
|
612
|
+
/** Whether the handler should be wrapped in a database transaction */
|
|
613
|
+
transactional?: boolean;
|
|
614
|
+
/** Options for the database transaction (isolation level, timeout) */
|
|
615
|
+
transactionalOptions?: TransactionalOptions;
|
|
616
|
+
/** Domain events to emit after successful handler execution */
|
|
617
|
+
emittedEvents?: Array<{
|
|
618
|
+
eventClass: {
|
|
619
|
+
new (data: Record<string, unknown>, options?: {
|
|
620
|
+
correlationId?: string;
|
|
621
|
+
}): unknown;
|
|
622
|
+
readonly name: string;
|
|
623
|
+
};
|
|
624
|
+
mapper?: (result: unknown) => Record<string, unknown>;
|
|
625
|
+
}>;
|
|
626
|
+
/** Pipeline steps declared via .through() */
|
|
627
|
+
pipelineSteps?: PipelineStep[];
|
|
628
|
+
/** Policy action reference for declarative authorization */
|
|
629
|
+
policyAction?: PolicyActionLike;
|
|
474
630
|
}
|
|
475
631
|
/**
|
|
476
632
|
* Type for the procedures object passed to defineProcedures
|
|
@@ -485,7 +641,7 @@ export interface BuilderRuntimeState {
|
|
|
485
641
|
* The actual type safety is preserved through InferProcedures<T> which captures
|
|
486
642
|
* the concrete types at definition time. This `any` only allows the assignment.
|
|
487
643
|
*/
|
|
488
|
-
export type ProcedureDefinitions = Record<string, CompiledProcedure<any, any, any, any>>;
|
|
644
|
+
export type ProcedureDefinitions = Record<string, CompiledProcedure<any, any, any, any, any>>;
|
|
489
645
|
/**
|
|
490
646
|
* Type helper to preserve procedure types in a collection
|
|
491
647
|
*
|
package/dist/resource/index.d.ts
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*
|
|
40
40
|
* // Authenticated endpoint → returns { id, name, email }
|
|
41
41
|
* getProfile: procedure()
|
|
42
|
-
* .
|
|
42
|
+
* .guard(authenticated)
|
|
43
43
|
* .input(z.object({ id: z.string() }))
|
|
44
44
|
* .query(async ({ input, ctx }) => {
|
|
45
45
|
* const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
*
|
|
49
49
|
* // Admin endpoint → returns { id, name, email, internalNotes }
|
|
50
50
|
* getFullProfile: procedure()
|
|
51
|
-
* .
|
|
51
|
+
* .guard(hasRole('admin'))
|
|
52
52
|
* .input(z.object({ id: z.string() }))
|
|
53
53
|
* .query(async ({ input, ctx }) => {
|
|
54
54
|
* const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
@@ -60,8 +60,8 @@
|
|
|
60
60
|
* @module resource
|
|
61
61
|
*/
|
|
62
62
|
export { Resource, ResourceCollection, resource, resourceCollection } from './instance.js';
|
|
63
|
-
export type { AccessLevelConfig } from './levels.js';
|
|
64
|
-
export { defineAccessLevels } from './levels.js';
|
|
63
|
+
export type { AccessLevelConfig, AccessLevelGuard, AccessLevelGuards } from './levels.js';
|
|
64
|
+
export { defaultAccess, defineAccessLevels } from './levels.js';
|
|
65
65
|
export type { AdminOutput, AnonymousOutput, AuthenticatedOutput, BuilderField, CustomResourceSchemaWithViews, CustomSchemaBuilder, FilterFieldsByLevel, OutputForLevel, OutputForTag, PublicOutput, RelationField, ResourceField, ResourceSchema, ResourceSchemaWithViews, RuntimeField, TaggedResourceSchema, } from './schema.js';
|
|
66
66
|
export { isResourceSchema, isTaggedResourceSchema, ResourceSchemaBuilder, resourceSchema, } from './schema.js';
|
|
67
67
|
export type { AccessLevel, ADMIN, ANONYMOUS, AUTHENTICATED, ContextTag, ExtractTag, HasTag, LevelToTag, PUBLIC, TaggedContext, TagToLevel, WithTag, } from './tags.js';
|
package/dist/resource/index.js
CHANGED
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
*
|
|
40
40
|
* // Authenticated endpoint → returns { id, name, email }
|
|
41
41
|
* getProfile: procedure()
|
|
42
|
-
* .
|
|
42
|
+
* .guard(authenticated)
|
|
43
43
|
* .input(z.object({ id: z.string() }))
|
|
44
44
|
* .query(async ({ input, ctx }) => {
|
|
45
45
|
* const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
*
|
|
49
49
|
* // Admin endpoint → returns { id, name, email, internalNotes }
|
|
50
50
|
* getFullProfile: procedure()
|
|
51
|
-
* .
|
|
51
|
+
* .guard(hasRole('admin'))
|
|
52
52
|
* .input(z.object({ id: z.string() }))
|
|
53
53
|
* .query(async ({ input, ctx }) => {
|
|
54
54
|
* const user = await ctx.db.user.findUnique({ where: { id: input.id } });
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
// ============================================================================
|
|
65
65
|
// Resource instances
|
|
66
66
|
export { Resource, ResourceCollection, resource, resourceCollection } from './instance.js';
|
|
67
|
-
export { defineAccessLevels } from './levels.js';
|
|
67
|
+
export { defaultAccess, defineAccessLevels } from './levels.js';
|
|
68
68
|
// Schema builder
|
|
69
69
|
export { isResourceSchema, isTaggedResourceSchema, ResourceSchemaBuilder, resourceSchema, } from './schema.js';
|
|
70
70
|
// Visibility
|
|
@@ -82,7 +82,7 @@ export declare class Resource<TSchema extends ResourceSchema> {
|
|
|
82
82
|
*
|
|
83
83
|
* @example
|
|
84
84
|
* ```typescript
|
|
85
|
-
* // In a procedure with
|
|
85
|
+
* // In a procedure with guard(authenticated)
|
|
86
86
|
* const profile = resource(user, UserSchema).for(ctx);
|
|
87
87
|
* // Type is automatically inferred based on ctx's tag
|
|
88
88
|
* ```
|
|
@@ -179,7 +179,7 @@ export class Resource {
|
|
|
179
179
|
*
|
|
180
180
|
* @example
|
|
181
181
|
* ```typescript
|
|
182
|
-
* // In a procedure with
|
|
182
|
+
* // In a procedure with guard(authenticated)
|
|
183
183
|
* const profile = resource(user, UserSchema).for(ctx);
|
|
184
184
|
* // Type is automatically inferred based on ctx's tag
|
|
185
185
|
* ```
|