@databehave/openapi 0.0.0-stage → 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.
Files changed (68) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/LICENSE +21 -0
  3. package/README.md +93 -2
  4. package/dist/catalog.d.ts +51 -0
  5. package/dist/catalog.d.ts.map +1 -0
  6. package/dist/catalog.js +90 -0
  7. package/dist/catalog.js.map +1 -0
  8. package/dist/index.d.ts +11 -0
  9. package/dist/index.d.ts.map +1 -0
  10. package/dist/index.js +9 -0
  11. package/dist/index.js.map +1 -0
  12. package/dist/middleware/request-validation.d.ts +65 -0
  13. package/dist/middleware/request-validation.d.ts.map +1 -0
  14. package/dist/middleware/request-validation.js +353 -0
  15. package/dist/middleware/request-validation.js.map +1 -0
  16. package/dist/mock-mode-plugin.d.ts +5 -0
  17. package/dist/mock-mode-plugin.d.ts.map +1 -0
  18. package/dist/mock-mode-plugin.js +20 -0
  19. package/dist/mock-mode-plugin.js.map +1 -0
  20. package/dist/mock-mode.d.ts +76 -0
  21. package/dist/mock-mode.d.ts.map +1 -0
  22. package/dist/mock-mode.js +93 -0
  23. package/dist/mock-mode.js.map +1 -0
  24. package/dist/openapi/auto-schema.d.ts +3 -0
  25. package/dist/openapi/auto-schema.d.ts.map +1 -0
  26. package/dist/openapi/auto-schema.js +10 -0
  27. package/dist/openapi/auto-schema.js.map +1 -0
  28. package/dist/openapi/generate.d.ts +8 -0
  29. package/dist/openapi/generate.d.ts.map +1 -0
  30. package/dist/openapi/generate.js +121 -0
  31. package/dist/openapi/generate.js.map +1 -0
  32. package/dist/openapi/loader.d.ts +18 -0
  33. package/dist/openapi/loader.d.ts.map +1 -0
  34. package/dist/openapi/loader.js +32 -0
  35. package/dist/openapi/loader.js.map +1 -0
  36. package/dist/openapi/seed.d.ts +17 -0
  37. package/dist/openapi/seed.d.ts.map +1 -0
  38. package/dist/openapi/seed.js +58 -0
  39. package/dist/openapi/seed.js.map +1 -0
  40. package/dist/openapi/translate.d.ts +14 -0
  41. package/dist/openapi/translate.d.ts.map +1 -0
  42. package/dist/openapi/translate.js +312 -0
  43. package/dist/openapi/translate.js.map +1 -0
  44. package/dist/openapi/walker.d.ts +37 -0
  45. package/dist/openapi/walker.d.ts.map +1 -0
  46. package/dist/openapi/walker.js +2 -0
  47. package/dist/openapi/walker.js.map +1 -0
  48. package/dist/placeholder.d.ts +7 -0
  49. package/dist/placeholder.d.ts.map +1 -0
  50. package/dist/placeholder.js +17 -0
  51. package/dist/placeholder.js.map +1 -0
  52. package/dist/plugin.d.ts +10 -0
  53. package/dist/plugin.d.ts.map +1 -0
  54. package/dist/plugin.js +43 -0
  55. package/dist/plugin.js.map +1 -0
  56. package/dist/request-validation-plugin.d.ts +8 -0
  57. package/dist/request-validation-plugin.d.ts.map +1 -0
  58. package/dist/request-validation-plugin.js +24 -0
  59. package/dist/request-validation-plugin.js.map +1 -0
  60. package/dist/schema.d.ts +18 -0
  61. package/dist/schema.d.ts.map +1 -0
  62. package/dist/schema.js +26 -0
  63. package/dist/schema.js.map +1 -0
  64. package/dist/validation/validate.d.ts +40 -0
  65. package/dist/validation/validate.d.ts.map +1 -0
  66. package/dist/validation/validate.js +378 -0
  67. package/dist/validation/validate.js.map +1 -0
  68. package/package.json +55 -3
package/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ ## [Unreleased]
4
+
5
+ Upcoming package version: **0.1.0**. This section describes planned release contents, not a published release.
6
+
7
+ ### Runtime requirements
8
+
9
+ - Require Node.js 24 or 26; support and test majors 24 and 26. Node.js 18, 20 and 22 are unsupported.
10
+
11
+ ### Added
12
+
13
+ - Explicit operation catalog, selectable response-generator providers, request validation and mock-status plugins.
14
+ - Separate `/schema` adapter receiving builders and a runtime explicitly; the default entry does not import schema.
15
+ - Selected declared success statuses, bodyless status handling and shared response generation for fault injection.
16
+ - Incremental request-body limits, required-body checks and problem-response documentation.
17
+
18
+ See [migration](../../MIGRATION.md) and [release procedures](../../docs/releasing.md).
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ochairo
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,94 @@
1
- # Temporary Holding Version
1
+ # @databehave/openapi
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Requires Node.js 24 or 26; supported majors are 24 and 26. See the
4
+ [plugin author guide](../../docs/plugins.md) and [migration guide](../../MIGRATION.md).
5
+
6
+ Explicit OpenAPI catalog, generation, request validation and mock-status plugins for Databehave.
7
+ The default entry point never imports the schema package at runtime or in its public declarations.
8
+
9
+ ```ts
10
+ import { readFile } from 'node:fs/promises'
11
+ import { createHost } from '@databehave/core'
12
+ import { httpPlugin, nodeHttpPlugin } from '@databehave/server'
13
+ import {
14
+ openapiPlugin, placeholderGeneratorPlugin, requestValidationPlugin, mockModePlugin,
15
+ } from '@databehave/openapi'
16
+
17
+ const host = createHost({ plugins: [
18
+ httpPlugin(),
19
+ placeholderGeneratorPlugin(),
20
+ openapiPlugin({ document: await readFile('./openapi.json', 'utf8') }),
21
+ requestValidationPlugin({ maxBodyBytes: 102400 }),
22
+ mockModePlugin({ allowHeaderOverride: true }),
23
+ nodeHttpPlugin({ port: 3000 }),
24
+ ] })
25
+ await host.start()
26
+ // await host.close() when the application shuts down.
27
+ ```
28
+
29
+ `document` is raw JSON text. Filesystem/network loading belongs to the application; YAML must be converted before
30
+ composition. Parsing and generator compilation happen during preparation, before HTTP materializes routes or opens
31
+ listeners. The operation catalog owns method/path matching and response status selection. Exact paths precede
32
+ parameterized paths; parameterized ties retain document declaration order. HTTP explicit routes replace generated
33
+ routes with the same key.
34
+
35
+ `openapiCapability` provides the parsed document, operations, `find(method, path)` and
36
+ `body(operation, status, request)`. Responses use 200 when declared, otherwise the lowest declared 2xx, defaulting
37
+ to 200 if none is declared. Only exact numeric status keys and `application/json` response schemas generate bodies.
38
+ 204, 205 and 304 have no generated body. Unsupported nonnumeric/default/range statuses do not select a schema.
39
+ GET/HEAD without a schema use `{}`; other methods use `{ success: true, message: null }`.
40
+
41
+ Placeholder generation uses deterministic structural values such as `"string"`, `0` and `false`.
42
+ Unsupported placeholder schemas report `onWalkError` and fall back to a stub. Permissive success schemas report
43
+ `onEmptySchema`. A response is compiled once per operation/status; injection and ordinary routes share its generator.
44
+
45
+ For schema generation, replace `placeholderGeneratorPlugin()` with the optional adapter:
46
+
47
+ ```ts
48
+ import * as builders from '@databehave/schema'
49
+ import { createSchemaRuntime, standardKinds, standardBehaviors } from '@databehave/schema'
50
+ import { schemaGeneratorPlugin } from '@databehave/openapi/schema'
51
+
52
+ const runtime = createSchemaRuntime({ kinds: standardKinds, behaviors: standardBehaviors })
53
+ const provider = schemaGeneratorPlugin({ builders, runtime, seed: 'stable' })
54
+ ```
55
+
56
+ The adapter receives builders and a runtime explicitly. It never resolves another schema implementation internally.
57
+ `seed` accepts a number, `'stable'` (default: operation, sorted query and path parameters), or `'random'` (per request).
58
+ Schema generation rejects unsupported response schemas during startup, including schemas for injected error statuses.
59
+ The translator retains bounds, formats, nullable/optional fields, object/array/union/discriminator shapes, local
60
+ references and examples. Request conformance remains separate: generation is not a validator. Exclusive numeric
61
+ bounds retain the existing inclusive approximation in generation; conformance enforces exclusive bounds exactly.
62
+
63
+ An external provider can provide `openapiGeneratorCapability` using the public core `Plugin` protocol, or a different
64
+ `Capability<OpenApiGenerator>` selected with `openapiPlugin({ document, generator: token })`.
65
+ Its `compile(schema, { document, operation, status })` returns a request body function, optionally asynchronously.
66
+ `failureMode: 'throw'` rejects startup; `'fallback'` emits diagnostics and preserves stub behavior.
67
+ Do not also include a second provider for the same capability.
68
+
69
+ Request validation is opt-in through `requestValidationPlugin()`. It retains body/parameter conformance, parameter
70
+ coercion, local references, size checks and presence checks for declared security schemes. HEAD fallback validates the
71
+ GET contract. Failures produce problem JSON with 400/401/413/415/422; HTTP applies decorators and observes the outcome.
72
+ Security checks establish declared credential presence, not token authenticity.
73
+
74
+ Problem response references: [400](../../docs/errors/malformed-body.md),
75
+ [401](../../docs/errors/unauthorized.md), [413](../../docs/errors/payload-too-large.md),
76
+ [415](../../docs/errors/unsupported-media-type.md), [422](../../docs/errors/request-validation.md).
77
+
78
+ Declared request bodies are read incrementally with a UTF-8 byte limit before JSON parsing. `maxBodyBytes` defaults
79
+ to 102400 and must be a nonnegative safe integer; zero permits no body bytes. Overflow returns 413 before EOF and
80
+ cancels both validation and original request streams; read failures or aborts return 400. Successful validation leaves
81
+ the original body readable by the handler. Required-body presence is checked even without a schema, including declared
82
+ OPTIONS bodies. JSON media declarations still require valid JSON without a schema; non-JSON bodies retain presence
83
+ and size checks. A `pattern` requires a finite nonnegative integer `maxLength` on the same schema node; oversized
84
+ strings report a length violation without executing that regex.
85
+
86
+ `mockModePlugin()` enables status selection by method/path override, path override, then default status. Incoming
87
+ status headers require `allowHeaderOverride: true`. Health paths bypass injection/tagging. A selected numeric status
88
+ uses the same catalog and generator as normal responses; a missing body schema uses `{ error: true, status }`.
89
+
90
+ Migration changes: import these features from `@databehave/openapi`, compose an explicit provider, and pass JSON text
91
+ through `document`. Generated routes now return their selected declared success status (201 stays 201; 204/205 are
92
+ empty), root operations are registered, HEAD/OPTIONS operations are supported, malformed non-object documents are
93
+ rejected early, and error-status generation follows the selected provider. Removed JSONC `enabled`, `locale` and
94
+ `arrayCount` schema knobs are not part of this API; the latter two had no generation effect.
@@ -0,0 +1,51 @@
1
+ import type { Method, MockRequest, RouteKey } from '@databehave/server';
2
+ import type { OasDoc, OasNode } from './openapi/walker.js';
3
+ /** One declared numeric response; permissive or absent schemas have no generator. */
4
+ export interface OpenApiResponse {
5
+ readonly status: number;
6
+ readonly schema?: OasNode;
7
+ }
8
+ /** One parsed operation shared by routes, request conformance and status injection. */
9
+ export interface OpenApiOperation {
10
+ readonly key: RouteKey;
11
+ readonly method: Method;
12
+ readonly path: string;
13
+ readonly sourcePath: string;
14
+ readonly definition: Readonly<Record<string, unknown>>;
15
+ readonly pathParameters: readonly unknown[];
16
+ readonly responses: readonly OpenApiResponse[];
17
+ /** Preferred success schema source: 200, then the smallest declared 2xx. */
18
+ readonly successStatus: number;
19
+ }
20
+ /** Per-request body generator prepared once for a declared operation and status. */
21
+ export type OpenApiBodyGenerator = (request: MockRequest) => unknown;
22
+ /** Compilation input owned by the catalog; providers must not enumerate operations themselves. */
23
+ export interface OpenApiGenerationContext {
24
+ readonly document: OasDoc;
25
+ readonly operation: OpenApiOperation;
26
+ readonly status: number;
27
+ }
28
+ /** Replaceable response generation policy. Schema generation fails startup; placeholders allow stubs. */
29
+ export interface OpenApiGenerator {
30
+ readonly failureMode: 'throw' | 'fallback';
31
+ compile(schema: OasNode, context: OpenApiGenerationContext): OpenApiBodyGenerator | Promise<OpenApiBodyGenerator>;
32
+ }
33
+ /** Parsed catalog and prepared generators, available after the preparation barrier. */
34
+ export interface OpenApiCatalog {
35
+ readonly document: OasDoc;
36
+ readonly operations: readonly OpenApiOperation[];
37
+ find(method: string, path: string): OpenApiOperation | undefined;
38
+ body(operation: OpenApiOperation, status: number, request: MockRequest): unknown;
39
+ }
40
+ export declare const openapiCapability: import("@databehave/core").Capability<OpenApiCatalog>;
41
+ export declare const openapiGeneratorCapability: import("@databehave/core").Capability<OpenApiGenerator>;
42
+ /** Diagnostics for fallback placeholder generation and permissive success schemas. */
43
+ export interface OpenApiDiagnostics {
44
+ readonly onWalkError?: (method: Method, path: string, error: unknown) => void;
45
+ readonly onEmptySchema?: (method: Method, path: string, status: number) => void;
46
+ }
47
+ /** Parse operation identities and status selection exactly once per document. */
48
+ export declare const readOperations: (document: OasDoc) => readonly OpenApiOperation[];
49
+ /** Prepare one immutable catalog, compiling each operation/status only once. */
50
+ export declare const prepareCatalog: (document: OasDoc, generator: OpenApiGenerator, diagnostics?: OpenApiDiagnostics) => Promise<OpenApiCatalog>;
51
+ //# sourceMappingURL=catalog.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAEvE,OAAO,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAE1D,qFAAqF;AACrF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAA;CAC1B;AAED,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,GAAG,EAAE,QAAQ,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;IACtD,QAAQ,CAAC,cAAc,EAAE,SAAS,OAAO,EAAE,CAAA;IAC3C,QAAQ,CAAC,SAAS,EAAE,SAAS,eAAe,EAAE,CAAA;IAC9C,4EAA4E;IAC5E,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAC/B;AAED,oFAAoF;AACpF,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,WAAW,KAAK,OAAO,CAAA;AAEpE,kGAAkG;AAClG,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,yGAAyG;AACzG,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,WAAW,EAAE,OAAO,GAAG,UAAU,CAAA;IAC1C,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE,OAAO,EAAE,wBAAwB,GAAG,oBAAoB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAA;CAClH;AAED,uFAAuF;AACvF,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,gBAAgB,EAAE,CAAA;IAChD,IAAI,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAAA;IAChE,IAAI,CAAC,SAAS,EAAE,gBAAgB,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,WAAW,GAAG,OAAO,CAAA;CACjF;AAED,eAAO,MAAM,iBAAiB,uDAAsD,CAAA;AACpF,eAAO,MAAM,0BAA0B,yDAAkE,CAAA;AAEzG,sFAAsF;AACtF,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;IAC7E,QAAQ,CAAC,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAA;CAChF;AAWD,iFAAiF;AACjF,eAAO,MAAM,cAAc,GAAI,UAAU,MAAM,KAAG,SAAS,gBAAgB,EAiC1E,CAAA;AAED,gFAAgF;AAChF,eAAO,MAAM,cAAc,GACzB,UAAU,MAAM,EAChB,WAAW,gBAAgB,EAC3B,cAAa,kBAAuB,KACnC,OAAO,CAAC,cAAc,CAkCxB,CAAA"}
@@ -0,0 +1,90 @@
1
+ import { capability } from '@databehave/core';
2
+ import { matchPattern, parseRoutePattern } from '@databehave/server';
3
+ export const openapiCapability = capability('databehave.openapi', 1);
4
+ export const openapiGeneratorCapability = capability('databehave.openapi.generator', 1);
5
+ const METHODS = ['get', 'post', 'put', 'delete', 'patch', 'head', 'options'];
6
+ const isObject = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
7
+ const hasShape = (node) => Boolean(node.$ref || node.type || node.enum || node.const !== undefined || node.example !== undefined || node.examples || node.properties ||
8
+ node.items || node.allOf || node.oneOf || node.anyOf);
9
+ /** Parse operation identities and status selection exactly once per document. */
10
+ export const readOperations = (document) => {
11
+ const operations = [];
12
+ for (const [sourcePath, item] of Object.entries(document.paths ?? {})) {
13
+ if (!isObject(item))
14
+ throw new Error(`OpenAPI path item must be an object: ${sourcePath}`);
15
+ const path = sourcePath.split('/').map(segment => /^\{[^{}]+\}$/.test(segment) ? `:${segment.slice(1, -1)}` : segment).join('/');
16
+ if (!path.startsWith('/'))
17
+ throw new Error(`OpenAPI path must start with '/': ${path}`);
18
+ parseRoutePattern(path);
19
+ for (const [methodName, definition] of Object.entries(item)) {
20
+ if (!METHODS.includes(methodName))
21
+ continue;
22
+ if (!isObject(definition))
23
+ throw new Error(`OpenAPI operation must be an object: ${methodName} ${path}`);
24
+ const method = methodName;
25
+ const responses = [];
26
+ const rawResponses = definition.responses;
27
+ if (isObject(rawResponses)) {
28
+ for (const [code, response] of Object.entries(rawResponses)) {
29
+ if (!/^[1-5]\d\d$/.test(code))
30
+ continue;
31
+ const status = Number(code);
32
+ const content = isObject(response) && isObject(response.content) ? response.content : undefined;
33
+ const media = content?.['application/json'];
34
+ const schema = isObject(media) && isObject(media.schema) ? media.schema : undefined;
35
+ responses.push(Object.freeze({ status, ...(schema ? { schema } : {}) }));
36
+ }
37
+ }
38
+ const successStatus = responses.map(r => r.status).filter(status => status >= 200 && status < 300).sort((a, b) => a - b)[0] ?? 200;
39
+ operations.push(Object.freeze({
40
+ key: `${method.toUpperCase()} ${path}`, method, path, sourcePath,
41
+ definition, pathParameters: Array.isArray(item.parameters) ? item.parameters : [],
42
+ responses: Object.freeze(responses), successStatus,
43
+ }));
44
+ }
45
+ }
46
+ // HTTP resolves exact paths before parameterized paths; ties retain declaration order.
47
+ return Object.freeze(operations.sort((a, b) => Number(!parseRoutePattern(a.path).isStatic) - Number(!parseRoutePattern(b.path).isStatic)));
48
+ };
49
+ /** Prepare one immutable catalog, compiling each operation/status only once. */
50
+ export const prepareCatalog = async (document, generator, diagnostics = {}) => {
51
+ const operations = readOperations(document);
52
+ const patterns = new Map(operations.map(operation => [operation, parseRoutePattern(operation.path)]));
53
+ const generators = new Map();
54
+ for (const operation of operations) {
55
+ const responses = new Map();
56
+ for (const { status, schema } of operation.responses) {
57
+ if (!schema || status === 204 || status === 205 || status === 304)
58
+ continue;
59
+ if (!hasShape(schema)) {
60
+ if (status === operation.successStatus)
61
+ diagnostics.onEmptySchema?.(operation.method, operation.path, status);
62
+ if (generator.failureMode === 'fallback')
63
+ continue;
64
+ }
65
+ try {
66
+ responses.set(status, await generator.compile(schema, { document, operation, status }));
67
+ }
68
+ catch (error) {
69
+ if (generator.failureMode === 'throw')
70
+ throw error;
71
+ diagnostics.onWalkError?.(operation.method, operation.path, error);
72
+ }
73
+ }
74
+ generators.set(operation, responses);
75
+ }
76
+ return Object.freeze({
77
+ document,
78
+ operations,
79
+ find(method, path) {
80
+ return operations.find(operation => {
81
+ const pattern = patterns.get(operation);
82
+ return operation.method === method.toLowerCase() && pattern !== undefined && matchPattern(pattern, path) !== null;
83
+ });
84
+ },
85
+ body(operation, status, request) {
86
+ return generators.get(operation)?.get(status)?.(request);
87
+ },
88
+ });
89
+ };
90
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export { openapiPlugin, type OpenApiOptions } from './plugin.js';
2
+ export { placeholderGeneratorPlugin, placeholderGenerator } from './placeholder.js';
3
+ export { requestValidationPlugin, type RequestValidationOptions } from './request-validation-plugin.js';
4
+ export { mockModePlugin, type MockModeOptions } from './mock-mode-plugin.js';
5
+ export { openapiCapability, openapiGeneratorCapability } from './catalog.js';
6
+ export type { OpenApiCatalog, OpenApiOperation, OpenApiResponse, OpenApiGenerator, OpenApiGenerationContext, OpenApiBodyGenerator, OpenApiDiagnostics } from './catalog.js';
7
+ export type { OasDoc, OasNode } from './openapi/walker.js';
8
+ export { parseOpenApi } from './openapi/loader.js';
9
+ export { seedFor, type SeedInput } from './openapi/seed.js';
10
+ export { resolveStatus, type MockModeLogger } from './mock-mode.js';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACnF,OAAO,EAAE,uBAAuB,EAAE,KAAK,wBAAwB,EAAE,MAAM,gCAAgC,CAAA;AACvG,OAAO,EAAE,cAAc,EAAE,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAC5E,YAAY,EAAE,cAAc,EAAE,gBAAgB,EAAE,eAAe,EAAE,gBAAgB,EAAE,wBAAwB,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AAC3K,YAAY,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AAC1D,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,OAAO,EAAE,KAAK,SAAS,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,gBAAgB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,9 @@
1
+ export { openapiPlugin } from './plugin.js';
2
+ export { placeholderGeneratorPlugin, placeholderGenerator } from './placeholder.js';
3
+ export { requestValidationPlugin } from './request-validation-plugin.js';
4
+ export { mockModePlugin } from './mock-mode-plugin.js';
5
+ export { openapiCapability, openapiGeneratorCapability } from './catalog.js';
6
+ export { parseOpenApi } from './openapi/loader.js';
7
+ export { seedFor } from './openapi/seed.js';
8
+ export { resolveStatus } from './mock-mode.js';
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAuB,MAAM,aAAa,CAAA;AAChE,OAAO,EAAE,0BAA0B,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACnF,OAAO,EAAE,uBAAuB,EAAiC,MAAM,gCAAgC,CAAA;AACvG,OAAO,EAAE,cAAc,EAAwB,MAAM,uBAAuB,CAAA;AAC5E,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,MAAM,cAAc,CAAA;AAG5E,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAA;AAClD,OAAO,EAAE,OAAO,EAAkB,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAAE,aAAa,EAAuB,MAAM,gBAAgB,CAAA"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Inbound request-validation middleware (opt-in, zero-runtime-dep).
3
+ *
4
+ * Builds per-route validator descriptors from the OpenAPI document at
5
+ * server-start (build-time errors propagate up through the server
6
+ * boot). Per-request, validates body / path / query / header
7
+ * parameters and security against the schema and emits an RFC 7807
8
+ * `application/problem+json` envelope on violation.
9
+ *
10
+ * Status mapping:
11
+ * 400 — malformed JSON body or interrupted/failed body read
12
+ * 401 — required security scheme not satisfied
13
+ * 413 — request body exceeds the byte limit
14
+ * 415 — request body content-type not declared by the spec
15
+ * 422 — semantic validation failure (one or more `violations`)
16
+ *
17
+ * Body coercion: body validation is strict (no coercion) — mirroring
18
+ * how a real client/server pair speaks JSON. Path / query / header
19
+ * parameters arrive as strings on the wire, so they are coerced
20
+ * (integer / number / boolean) before validation.
21
+ */
22
+ import type { OpenApiCatalog } from '../catalog.js';
23
+ import { type RoutePattern } from '@databehave/server';
24
+ import { type Validator } from '../validation/validate.js';
25
+ /** Prepared OpenAPI ParamSpec metadata. */
26
+ interface ParamSpec {
27
+ readonly name: string;
28
+ readonly in: 'query' | 'path' | 'header';
29
+ readonly required: boolean;
30
+ readonly type: string | null;
31
+ readonly validator: Validator;
32
+ }
33
+ /** Prepared OpenAPI RouteValidator metadata. */
34
+ interface RouteValidator {
35
+ readonly key: string;
36
+ readonly pattern: RoutePattern;
37
+ readonly hasBody: boolean;
38
+ readonly hasJsonBody: boolean;
39
+ readonly bodyOnlyJson: boolean;
40
+ readonly bodyRequired: boolean;
41
+ readonly bodyValidator: Validator | null;
42
+ readonly params: readonly ParamSpec[];
43
+ readonly security: readonly Readonly<Record<string, readonly string[]>>[];
44
+ }
45
+ /** Prepared OpenAPI SecurityScheme metadata. */
46
+ interface SecurityScheme {
47
+ readonly type: 'http' | 'apiKey' | 'oauth2' | 'openIdConnect';
48
+ readonly scheme?: string;
49
+ readonly in?: 'header' | 'query' | 'cookie';
50
+ readonly name?: string;
51
+ }
52
+ /** Prepared conformance checks attached to the authoritative operation catalog. */
53
+ export interface RequestValidationContext {
54
+ readonly catalog: OpenApiCatalog;
55
+ readonly routes: readonly RouteValidator[];
56
+ readonly schemes: ReadonlyMap<string, SecurityScheme>;
57
+ /** Hard cap on declared body size in bytes (UTF-8). Default 100 KB. */
58
+ readonly maxBodyBytes: number;
59
+ }
60
+ export declare const createRequestValidationContext: (catalog: OpenApiCatalog, options?: {
61
+ readonly maxBodyBytes?: number;
62
+ }) => RequestValidationContext;
63
+ export declare const validateRequest: (req: Request, ctx: RequestValidationContext, effectiveMethod?: string) => Promise<Response | null>;
64
+ export {};
65
+ //# sourceMappingURL=request-validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"request-validation.d.ts","sourceRoot":"","sources":["../../src/middleware/request-validation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,EAAmC,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAA;AACvF,OAAO,EAAoB,KAAK,SAAS,EAAkB,MAAM,2BAA2B,CAAA;AA8B5F,2CAA2C;AAC3C,UAAU,SAAS;IACjB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAA;IACxC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC5B,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAA;CAC9B;AAED,gDAAgD;AAChD,UAAU,cAAc;IACtB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,OAAO,EAAE,YAAY,CAAA;IAC9B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAA;IAC9B,QAAQ,CAAC,aAAa,EAAE,SAAS,GAAG,IAAI,CAAA;IACxC,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAA;IACrC,QAAQ,CAAC,QAAQ,EAAE,SAAS,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,CAAC,CAAC,EAAE,CAAA;CAC1E;AAED,gDAAgD;AAChD,UAAU,cAAc;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,eAAe,CAAA;IAC7D,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,EAAE,CAAC,EAAE,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAA;IAC3C,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,mFAAmF;AACnF,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,cAAc,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,SAAS,cAAc,EAAE,CAAA;IAC1C,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;IACrD,uEAAuE;IACvE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAiCD,eAAO,MAAM,8BAA8B,GACzC,SAAS,cAAc,EACvB,UAAS;IAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAA;CAAO,KAC/C,wBAgFF,CAAA;AA6ID,eAAO,MAAM,eAAe,GAC1B,KAAK,OAAO,EACZ,KAAK,wBAAwB,EAC7B,wBAA4B,KAC3B,OAAO,CAAC,QAAQ,GAAG,IAAI,CA2HzB,CAAA"}