expediate 1.0.5 → 1.0.6

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.
Files changed (73) hide show
  1. package/CHANGELOG.md +138 -0
  2. package/CONTRIBUTING.md +150 -0
  3. package/README.md +278 -779
  4. package/dist/apis.d.ts +372 -12
  5. package/dist/apis.d.ts.map +1 -1
  6. package/dist/apis.js +483 -65
  7. package/dist/apis.js.map +1 -1
  8. package/dist/cjs/index.js +2290 -807
  9. package/dist/git.d.ts +1 -1
  10. package/dist/git.d.ts.map +1 -1
  11. package/dist/git.js +5 -5
  12. package/dist/git.js.map +1 -1
  13. package/dist/http-objects.d.ts +26 -0
  14. package/dist/http-objects.d.ts.map +1 -0
  15. package/dist/http-objects.js +588 -0
  16. package/dist/http-objects.js.map +1 -0
  17. package/dist/index.d.ts +6 -5
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +2 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/jwt-auth.d.ts +11 -0
  22. package/dist/jwt-auth.d.ts.map +1 -1
  23. package/dist/jwt-auth.js +9 -9
  24. package/dist/jwt-auth.js.map +1 -1
  25. package/dist/middleware.js +2 -2
  26. package/dist/middleware.js.map +1 -1
  27. package/dist/mimetypes.json +882 -1
  28. package/dist/misc.d.ts +161 -25
  29. package/dist/misc.d.ts.map +1 -1
  30. package/dist/misc.js +228 -80
  31. package/dist/misc.js.map +1 -1
  32. package/dist/openapi.d.ts +156 -13
  33. package/dist/openapi.d.ts.map +1 -1
  34. package/dist/openapi.js +214 -71
  35. package/dist/openapi.js.map +1 -1
  36. package/dist/router-types.d.ts +760 -0
  37. package/dist/router-types.d.ts.map +1 -0
  38. package/dist/router-types.js +23 -0
  39. package/dist/router-types.js.map +1 -0
  40. package/dist/router.d.ts +7 -530
  41. package/dist/router.d.ts.map +1 -1
  42. package/dist/router.js +128 -375
  43. package/dist/router.js.map +1 -1
  44. package/dist/static.d.ts +2 -2
  45. package/dist/static.d.ts.map +1 -1
  46. package/dist/static.js +77 -22
  47. package/dist/static.js.map +1 -1
  48. package/docs/THREAT_MODEL.md +52 -0
  49. package/docs/api-builder-v2-design.md +644 -0
  50. package/docs/api-builder-v3-design.md +397 -0
  51. package/docs/api-builder.md +454 -0
  52. package/docs/benchmark.md +27 -0
  53. package/docs/body-parsing.md +223 -0
  54. package/docs/errors.md +359 -0
  55. package/docs/expediate.png +0 -0
  56. package/docs/git.md +139 -0
  57. package/docs/jwt-auth.md +251 -0
  58. package/docs/logo.svg +12 -0
  59. package/docs/middleware.md +264 -0
  60. package/docs/openapi.md +180 -0
  61. package/docs/router.md +356 -0
  62. package/docs/static.md +128 -0
  63. package/docs/wiki.json +123 -0
  64. package/package.json +30 -8
  65. package/dist/cjs/apis.js +0 -327
  66. package/dist/cjs/git.js +0 -293
  67. package/dist/cjs/jwt-auth.js +0 -532
  68. package/dist/cjs/middleware.js +0 -511
  69. package/dist/cjs/mimetypes.json +0 -1
  70. package/dist/cjs/misc.js +0 -787
  71. package/dist/cjs/openapi.js +0 -485
  72. package/dist/cjs/router.js +0 -898
  73. package/dist/cjs/static.js +0 -669
package/dist/apis.d.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { RouterRequest, RouterResponse, Router } from './router.js';
1
+ import type { RouterRequest, RouterResponse, Router, Middleware } from './router.js';
2
2
  import './openapi.js';
3
- import type { SpecOptions, SpecFormat, OpenApiDocument } from './openapi.js';
3
+ import type { SpecOptions, SpecFormat, OpenApiDocument, OperationMeta, JsonSchema } from './openapi.js';
4
4
  /**
5
5
  * Context object passed as the first argument to every service method handler.
6
6
  *
@@ -12,14 +12,19 @@ import type { SpecOptions, SpecFormat, OpenApiDocument } from './openapi.js';
12
12
  * ```ts
13
13
  * GET: {
14
14
  * '/items/:id': function (ctx: ApiContext) {
15
- * const id = ctx.query.route.id; // ':id' from the path pattern
15
+ * const id = ctx.params.id; // ':id' from the path pattern
16
16
  * const fmt = ctx.query.url.format; // '?format=json' from the query string
17
17
  * return getItem(id, fmt);
18
18
  * },
19
19
  * }
20
20
  * ```
21
+ *
22
+ * @template TUser - The shape of the authenticated user payload (e.g.
23
+ * `TokenPayload` from the JWT plugin). Defaults to
24
+ * `unknown`, forcing explicit typing for safe access.
25
+ * @template TState - The shape of the guard-produced `state` bag.
21
26
  */
22
- export interface ApiContext {
27
+ export interface ApiContext<TUser = unknown, TState = Record<string, unknown>> {
23
28
  /** Route and URL query parameters, separated by origin. */
24
29
  query: {
25
30
  /**
@@ -37,6 +42,12 @@ export interface ApiContext {
37
42
  */
38
43
  url: Record<string, string | string[]>;
39
44
  };
45
+ /**
46
+ * Shorthand alias for {@link ApiContext.query}.route — the named parameters
47
+ * captured from the route pattern. This is the dominant access pattern;
48
+ * the namespaced form remains available for collision cases.
49
+ */
50
+ params: Record<string, string>;
40
51
  /**
41
52
  * The request path as seen by this API router, after any prefix stripping
42
53
  * performed by a parent `use()` mount.
@@ -49,7 +60,15 @@ export interface ApiContext {
49
60
  * `req.user` before the service method is called. `undefined` when no
50
61
  * authentication data has been attached.
51
62
  */
52
- user?: any;
63
+ user?: TUser;
64
+ /**
65
+ * Values produced by guards (loaded resources, resolved roles, …).
66
+ *
67
+ * Each guard that returns an object has that object shallow-merged into
68
+ * this bag before the next guard (or the handler) runs. Starts as `{}`
69
+ * for every request.
70
+ */
71
+ state: TState;
53
72
  }
54
73
  /**
55
74
  * An API error thrown (or rejected) by a service method.
@@ -101,16 +120,182 @@ export type ServiceInstance = Record<string, unknown> & {
101
120
  * Methods declared here are copied onto the instance object, bound to `this`,
102
121
  * so they can call each other and read/write instance state naturally.
103
122
  */
104
- export type ServiceMethods<TInstance extends ServiceInstance = ServiceInstance> = {
105
- [name: string]: (this: TInstance, ...args: unknown[]) => unknown;
106
- };
123
+ export type ServiceMethods<TInstance extends ServiceInstance = ServiceInstance> = Record<string, (this: TInstance, ...args: unknown[]) => unknown>;
107
124
  /**
108
125
  * A route map: keys are Express-style path patterns, values are handler
109
126
  * functions that are invoked with `this` bound to the service instance.
110
127
  */
111
- export type RouteMap<TInstance extends ServiceInstance = ServiceInstance> = {
112
- [path: string]: ServiceMethod<TInstance>;
113
- };
128
+ export type RouteMap<TInstance extends ServiceInstance = ServiceInstance> = Record<string, ServiceMethod<TInstance>>;
129
+ /**
130
+ * A pre-handler hook running in the `ctx` world.
131
+ *
132
+ * Guards attach at three levels — API (`ServiceDefinition.guards`),
133
+ * controller (`ControllerDefinition.guards`), and route
134
+ * (`OperationMeta.guards` via `describe()`) — and run outermost-first:
135
+ *
136
+ * ```
137
+ * auth.authenticate → auth.check → api guards → controller guards → route guards → handler
138
+ * ```
139
+ *
140
+ * A guard may:
141
+ * - **throw / reject** an {@link ApiError} → translated to an HTTP error response;
142
+ * - **return an object** → shallow-merged into `ctx.state`;
143
+ * - **return void** → pure check.
144
+ *
145
+ * Guards are loosely typed on purpose (`ctx.user` is `any` here); declare
146
+ * `ApiContext<TUser, TState>` explicitly in handlers for strict typing.
147
+ *
148
+ * @example
149
+ * ```ts
150
+ * const requireAdmin: Guard = (ctx) => {
151
+ * if (!ctx.user?.isAdmin) throw { status: 403, message: 'Admin access required' };
152
+ * return { admin: true }; // → ctx.state.admin
153
+ * };
154
+ * ```
155
+ */
156
+ export type Guard = (ctx: ApiContext<any, Record<string, unknown>>, req: RouterRequest) => void | Record<string, unknown> | Promise<void | Record<string, unknown>>;
157
+ /**
158
+ * A group of routes sharing a path prefix, default OpenAPI tags, guards, and
159
+ * a default permission requirement.
160
+ *
161
+ * Controllers are *route organisation*, not isolation boundaries: handlers in
162
+ * every controller run with `this` bound to the same service instance (the
163
+ * instance lifecycle — `scope` / `data` / `setup` / `methods` — stays at the
164
+ * {@link ServiceDefinition} level).
165
+ *
166
+ * @template TInstance - The shape of the service's state object.
167
+ */
168
+ export interface ControllerDefinition<TInstance extends ServiceInstance = ServiceInstance> {
169
+ /** Path prefix prepended to every route in this controller (may contain params). */
170
+ prefix?: string;
171
+ /** Default OpenAPI tags applied to routes that do not declare their own. */
172
+ tags?: string[];
173
+ /** Guards run before every handler of this controller (see {@link Guard}). */
174
+ guards?: Guard[];
175
+ /**
176
+ * Default permission requirement for every route of this controller.
177
+ * Route-level `OperationMeta.permission` overrides it. When set, the
178
+ * pipeline runs `auth.check(ctx, required)` before the guards.
179
+ */
180
+ permission?: string | string[];
181
+ /** Route handlers for `GET` requests (paths relative to `prefix`). */
182
+ GET?: RouteMap<TInstance>;
183
+ /** Route handlers for `POST` requests (paths relative to `prefix`). */
184
+ POST?: RouteMap<TInstance>;
185
+ /** Route handlers for `PUT` requests (paths relative to `prefix`). */
186
+ PUT?: RouteMap<TInstance>;
187
+ /** Route handlers for `DELETE` requests (paths relative to `prefix`). */
188
+ DELETE?: RouteMap<TInstance>;
189
+ /** Route handlers for `PATCH` requests (paths relative to `prefix`). */
190
+ PATCH?: RouteMap<TInstance>;
191
+ }
192
+ /**
193
+ * Identity helper for type inference and discoverability when declaring a
194
+ * {@link ControllerDefinition} in its own file.
195
+ *
196
+ * @example
197
+ * ```ts
198
+ * export const wikiController = defineController({
199
+ * prefix: '/p/:proj/wiki',
200
+ * tags: ['Wiki'],
201
+ * permission: 'wiki.read',
202
+ * GET: { '/tree': (ctx) => listPages(ctx.params.proj) },
203
+ * });
204
+ * ```
205
+ */
206
+ export declare function defineController<TInstance extends ServiceInstance = ServiceInstance>(c: ControllerDefinition<TInstance>): ControllerDefinition<TInstance>;
207
+ /**
208
+ * Authentication / authorization binding connecting an auth layer (typically
209
+ * the JWT plugin) to the API Builder pipeline.
210
+ *
211
+ * @example
212
+ * ```ts
213
+ * const jwt = createJwtPlugin({ accessTokenSecret: SECRET });
214
+ * const api = apiBuilder({
215
+ * auth: { authenticate: jwt.authenticate }, // default check() reads ctx.user.permissions
216
+ * controllers: [ ... ],
217
+ * });
218
+ * ```
219
+ *
220
+ * @template TUser - The shape of the authenticated user payload.
221
+ */
222
+ export interface AuthBinding<TUser = unknown> {
223
+ /**
224
+ * Router middleware run before any guard or handler — typically
225
+ * `jwtPlugin.authenticate`. Registered by `apiBuilder` on its internal
226
+ * router, so the client no longer wires it per-mount.
227
+ */
228
+ authenticate?: Middleware;
229
+ /**
230
+ * Enforce a permission requirement for the current request.
231
+ *
232
+ * Default implementation: require `ctx.user` (else `401`) and check that
233
+ * `ctx.user.permissions` contains **all** required entries (else `403`) —
234
+ * i.e. the exact semantics of `jwtPlugin.requirePermission`, but in the
235
+ * `ctx` world. Override for resource-scoped models (per-project roles,
236
+ * ownership, …); the override may load resources and share them through
237
+ * `ctx.state`.
238
+ *
239
+ * Failure is signalled by throwing / rejecting an {@link ApiError}.
240
+ */
241
+ check?: (ctx: ApiContext<TUser>, required: string[]) => void | Promise<void>;
242
+ /**
243
+ * OpenAPI security scheme emitted into `components.securitySchemes.bearerAuth`
244
+ * when at least one route declares a `permission`.
245
+ *
246
+ * Default: `{ type: 'http', scheme: 'bearer', bearerFormat: 'JWT' }`.
247
+ */
248
+ scheme?: Record<string, unknown>;
249
+ /**
250
+ * Name of the vendor extension listing the required permissions on each
251
+ * secured operation in the generated OpenAPI document.
252
+ *
253
+ * Default: `'x-required-permissions'`.
254
+ */
255
+ permissionsExtension?: string;
256
+ }
257
+ /**
258
+ * Validation controls for {@link apiBuilder}.
259
+ *
260
+ * Used both as the factory's optional second argument and as the object form of
261
+ * the {@link ServiceDefinition.validate} field. When passed as the second
262
+ * argument it is authoritative and overrides `service.validate`:
263
+ *
264
+ * ```ts
265
+ * apiBuilder(service); // follows service.validate
266
+ * apiBuilder(service, {}); // validate requests (default), not responses
267
+ * apiBuilder(service, { validateRequests: false }); // validate nothing
268
+ * apiBuilder(service, { validateResponses: true }); // requests + responses (500 on mismatch)
269
+ * apiBuilder(service, { validateResponses: 'warn' }); // requests + responses (log only, no 500)
270
+ * ```
271
+ */
272
+ export interface ApiBuilderOptions {
273
+ /**
274
+ * Validate incoming request bodies against each route's declared
275
+ * `OperationMeta.requestBody` schema. Failures produce `400` with
276
+ * `{ message, fieldErrors }`.
277
+ *
278
+ * @default true — pass `false` to cancel the incoming-data check.
279
+ */
280
+ validateRequests?: boolean;
281
+ /**
282
+ * Validate each handler's return value against the route's declared
283
+ * `OperationMeta.responses['200']` schema before it is sent.
284
+ *
285
+ * - `true` — a mismatch is a server-contract breach: the off-spec body is
286
+ * **not** sent; instead a `500` with `{ message, fieldErrors }` is returned.
287
+ * - `'warn'` — a mismatch is logged server-side via `console.warn` and the
288
+ * response is sent unchanged (handy in development).
289
+ * - `false` — no response checking.
290
+ *
291
+ * Only truthy returns (sent as `200` JSON) are checked; falsy returns
292
+ * (`201 No Content`) and routes without a declared `200` response schema are
293
+ * skipped.
294
+ *
295
+ * @default false
296
+ */
297
+ validateResponses?: boolean | 'warn';
298
+ }
114
299
  /**
115
300
  * Extra methods attached to the router returned by {@link apiBuilder}.
116
301
  *
@@ -219,6 +404,65 @@ export interface ServiceDefinition<TInstance extends ServiceInstance = ServiceIn
219
404
  * objects to trigger HTTP error responses.
220
405
  */
221
406
  methods?: ServiceMethods<TInstance>;
407
+ /**
408
+ * Sub-controllers merged into this API.
409
+ *
410
+ * Each controller's routes are rewritten to `joinPath(prefix, path)`, then
411
+ * all routes of all controllers (plus the root-level route maps below) are
412
+ * concatenated and sorted by specificity **globally**. A duplicate
413
+ * `(verb, joined path)` pair across controllers **throws at build time**.
414
+ *
415
+ * All controllers share the single service instance lifecycle declared at
416
+ * this level — controllers organise routes, they do not isolate state.
417
+ */
418
+ controllers?: ControllerDefinition<TInstance>[];
419
+ /** Guards run before every handler of the whole API (see {@link Guard}). */
420
+ guards?: Guard[];
421
+ /** Authentication / authorization binding (see {@link AuthBinding}). */
422
+ auth?: AuthBinding;
423
+ /**
424
+ * Enable runtime validation of declared schemas.
425
+ *
426
+ * - `true` — validate request bodies against each route's
427
+ * `OperationMeta.requestBody` schema (`400` with `{ message, fieldErrors }`
428
+ * on failure).
429
+ * - An {@link ApiBuilderOptions} object — fine-grained control over request
430
+ * and response validation.
431
+ *
432
+ * Overridden when an {@link ApiBuilderOptions} object is passed as the second
433
+ * argument to {@link apiBuilder}.
434
+ */
435
+ validate?: boolean | ApiBuilderOptions;
436
+ /**
437
+ * Reusable JSON Schema components, shared by request validation and spec
438
+ * generation. `$ref: '#/components/schemas/Name'` references in operation
439
+ * metadata are resolved against this map by the validator, and the map is
440
+ * merged into `components.schemas` of the generated OpenAPI document
441
+ * (taking precedence over `SpecOptions.schemas`).
442
+ */
443
+ schemas?: Record<string, JsonSchema>;
444
+ /**
445
+ * Hook invoked whenever a handler, guard, auth check, or validation step
446
+ * throws or rejects — before the default {@link ApiError} → HTTP translation.
447
+ *
448
+ * Use it to log the failure and/or shape a better response:
449
+ * - **Return nothing** (`undefined`) → the error is left untouched and the
450
+ * built-in translation runs (`{ status, message | data }`, else `500`).
451
+ * Ideal for log-only use.
452
+ * - **Return an {@link ApiError}** → that value is sent instead of the
453
+ * original (e.g. to hide internals behind a generic message, or attach a
454
+ * correlation id).
455
+ * - **Throw** → the thrown value is escalated to the surrounding app's error
456
+ * channel (`router.error()` / `onError`) instead of being answered here,
457
+ * letting a process-wide handler take over.
458
+ *
459
+ * @param err - The caught value (thrown or rejected).
460
+ * @param ctx - The {@link ApiContext} for the failing request.
461
+ * @param req - The underlying request.
462
+ * @returns An {@link ApiError} to override the response, or nothing to keep
463
+ * the default translation.
464
+ */
465
+ onError?(err: unknown, ctx: ApiContext<any>, req: RouterRequest): void | ApiError;
222
466
  /** Route handlers for `GET` requests. */
223
467
  GET?: RouteMap<TInstance>;
224
468
  /** Route handlers for `POST` requests. */
@@ -230,6 +474,112 @@ export interface ServiceDefinition<TInstance extends ServiceInstance = ServiceIn
230
474
  /** Route handlers for `PATCH` requests. */
231
475
  PATCH?: RouteMap<TInstance>;
232
476
  }
477
+ /** The five HTTP verbs supported by `apiBuilder`. */
478
+ declare const VERBS: readonly ["GET", "POST", "PUT", "DELETE", "PATCH"];
479
+ /** One of the five HTTP verbs supported by `apiBuilder`. */
480
+ export type ApiVerb = typeof VERBS[number];
481
+ /**
482
+ * One merged route entry produced by {@link collectRoutes}.
483
+ *
484
+ * Records per-route provenance — effective tags, composed guard chain, and
485
+ * permission requirement — consumed by both the request pipeline
486
+ * (`apiBuilder`) and the spec generator (`openApiSpec`).
487
+ *
488
+ * @template TInstance - The shape of the service's state object.
489
+ */
490
+ export interface CollectedRoute<TInstance extends ServiceInstance = ServiceInstance> {
491
+ /** HTTP verb of the route. */
492
+ verb: ApiVerb;
493
+ /** Full path after joining the controller prefix (Express-style pattern). */
494
+ path: string;
495
+ /** The route handler (possibly `describe()`-wrapped). */
496
+ handler: ServiceMethod<TInstance>;
497
+ /** Operation metadata attached via `describe()`, when present. */
498
+ meta?: OperationMeta;
499
+ /** Effective tags: route-level `meta.tags`, else the controller's `tags`. */
500
+ tags?: string[];
501
+ /** Composed guard chain: API guards, then controller guards, then route guards. */
502
+ guards: Guard[];
503
+ /** Normalised permission requirement (route-level overrides controller-level). */
504
+ permission?: string[];
505
+ /** Human-readable controller identifier used in diagnostics. */
506
+ controller: string;
507
+ }
508
+ /**
509
+ * Join a controller prefix and a route path into a single normalised pattern.
510
+ *
511
+ * Duplicate slashes are collapsed and a trailing slash is stripped (except
512
+ * for the root path), so `joinPath('/p/:proj/wiki', '/')` → `'/p/:proj/wiki'`.
513
+ *
514
+ * Exported for use by `openapi.ts` (multi-definition spec merging); not part
515
+ * of the public package API.
516
+ *
517
+ * @param prefix - The controller prefix (may be empty).
518
+ * @param path - The route path relative to the prefix.
519
+ */
520
+ export declare function joinPath(prefix: string, path: string): string;
521
+ /**
522
+ * Compute the specificity score of a route pattern.
523
+ *
524
+ * `score = (segment count × 100) − (parameter count × 10)`. Higher scores
525
+ * are registered first so that more precise patterns (more segments, fewer
526
+ * parameters) cannot be shadowed by prefix matches.
527
+ *
528
+ * Exported for use by `openapi.ts` (multi-definition spec merging); not part
529
+ * of the public package API.
530
+ */
531
+ export declare function routeScore(path: string): number;
532
+ /**
533
+ * Normalise a `permission` declaration (`string | string[]`) to an array,
534
+ * or `undefined` when absent.
535
+ *
536
+ * Exported for use by `openapi.ts` (multi-definition spec merging); not part
537
+ * of the public package API.
538
+ */
539
+ export declare function normalizePermission(permission: string | string[] | undefined): string[] | undefined;
540
+ /**
541
+ * Build the merged route table for a service definition.
542
+ *
543
+ * The algorithm (see `docs/api-builder-v2-design.md` §4):
544
+ * 1. Normalises the root-level route maps into an anonymous controller
545
+ * (`prefix: ''`) so v1 single-definition services keep working.
546
+ * 2. Rewrites each controller route to `joinPath(prefix, path)`.
547
+ * 3. Concatenates all routes of all controllers, then sorts them by
548
+ * decreasing specificity **globally** (the score is computed on the
549
+ * joined path, so prefix parameters are accounted for).
550
+ * 4. **Throws** on a duplicate `(verb, joined path)` pair, naming both
551
+ * declaring controllers.
552
+ * 5. Records per-route provenance (tags, guards, permission) consumed by
553
+ * both the request pipeline and `openApiSpec()`.
554
+ *
555
+ * Exported for use by `openapi.ts`; not part of the public package API.
556
+ *
557
+ * @param service - The service definition to collect routes from.
558
+ * @returns The merged, globally sorted route table.
559
+ * @throws Error on duplicate `(verb, path)` declarations.
560
+ */
561
+ export declare function collectRoutes<TInstance extends ServiceInstance = ServiceInstance>(service: ServiceDefinition<TInstance>): CollectedRoute<TInstance>[];
562
+ /**
563
+ * Validate a value against a JSON Schema subset, collecting field errors.
564
+ *
565
+ * Supported keywords: `type`, `required`, `properties`, `items`, `enum`,
566
+ * `pattern`, `minLength` / `maxLength`, `minimum` / `maximum`,
567
+ * `additionalProperties`, `allOf` / `anyOf` / `oneOf`, and `$ref` (resolved
568
+ * against `components`, i.e. `ServiceDefinition.schemas`).
569
+ *
570
+ * Field-error paths are dotted (`name`, `address.city`, `tags.0`); errors on
571
+ * the value itself are keyed `'$'`.
572
+ *
573
+ * Exported for testing; not part of the public package API.
574
+ *
575
+ * @param value - The value to validate.
576
+ * @param schema - The schema to validate against.
577
+ * @param components - Reusable schemas for `$ref` resolution.
578
+ * @param path - Current field path (used in recursion; omit at the root).
579
+ * @param errors - Accumulator (used in recursion; omit at the root).
580
+ * @returns A map of field path → first error message (empty when valid).
581
+ */
582
+ export declare function validateSchema(value: unknown, schema: JsonSchema, components?: Record<string, JsonSchema>, path?: string, errors?: Record<string, string>): Record<string, string>;
233
583
  /**
234
584
  * Build an Express-compatible router from a service definition object.
235
585
  *
@@ -275,9 +625,19 @@ export interface ServiceDefinition<TInstance extends ServiceInstance = ServiceIn
275
625
  * as a JSON body.
276
626
  * - Any other thrown value produces `500 Internal Server Error`.
277
627
  *
628
+ * **Validation:**
629
+ * - With no `options`, validation follows the {@link ServiceDefinition.validate}
630
+ * field.
631
+ * - With `options`, request validation defaults **on** (cancel via
632
+ * `{ validateRequests: false }`) and response validation can be enabled with
633
+ * `{ validateResponses: true }` (500 on mismatch) or `{ validateResponses:
634
+ * 'warn' }` (log only). See {@link ApiBuilderOptions}.
635
+ *
278
636
  * @param service - The service definition (see {@link ServiceDefinition}).
637
+ * @param options - Optional validation controls (see {@link ApiBuilderOptions}).
638
+ * When provided, it overrides the legacy `service.validate` field.
279
639
  * @returns A router instance pre-configured with all declared routes.
280
640
  */
281
- export declare function apiBuilder<TInstance extends ServiceInstance = ServiceInstance>(service: ServiceDefinition<TInstance>): ApiRouter;
641
+ export declare function apiBuilder<TInstance extends ServiceInstance = ServiceInstance>(service: ServiceDefinition<TInstance>, options?: ApiBuilderOptions): ApiRouter;
282
642
  export default apiBuilder;
283
643
  //# sourceMappingURL=apis.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"apis.d.ts","sourceRoot":"","sources":["../src/apis.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apis.d.ts","sourceRoot":"","sources":["../src/apis.ts"],"names":[],"mappings":"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"}