@databehave/server 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 (162) hide show
  1. package/CHANGELOG.md +28 -0
  2. package/LICENSE +21 -0
  3. package/README.md +87 -0
  4. package/dist/admin/admin-routes.d.ts +42 -0
  5. package/dist/admin/admin-routes.d.ts.map +1 -0
  6. package/dist/admin/admin-routes.js +472 -0
  7. package/dist/admin/admin-routes.js.map +1 -0
  8. package/dist/admin/admin-types.d.ts +170 -0
  9. package/dist/admin/admin-types.d.ts.map +1 -0
  10. package/dist/admin/admin-types.js +30 -0
  11. package/dist/admin/admin-types.js.map +1 -0
  12. package/dist/admin/banner.d.ts +11 -0
  13. package/dist/admin/banner.d.ts.map +1 -0
  14. package/dist/admin/banner.js +5 -0
  15. package/dist/admin/banner.js.map +1 -0
  16. package/dist/admin/header-parser.d.ts +14 -0
  17. package/dist/admin/header-parser.d.ts.map +1 -0
  18. package/dist/admin/header-parser.js +171 -0
  19. package/dist/admin/header-parser.js.map +1 -0
  20. package/dist/admin/inject.d.ts +48 -0
  21. package/dist/admin/inject.d.ts.map +1 -0
  22. package/dist/admin/inject.js +167 -0
  23. package/dist/admin/inject.js.map +1 -0
  24. package/dist/admin/overrides-store.d.ts +18 -0
  25. package/dist/admin/overrides-store.d.ts.map +1 -0
  26. package/dist/admin/overrides-store.js +100 -0
  27. package/dist/admin/overrides-store.js.map +1 -0
  28. package/dist/admin/resolve-cors.d.ts +22 -0
  29. package/dist/admin/resolve-cors.d.ts.map +1 -0
  30. package/dist/admin/resolve-cors.js +28 -0
  31. package/dist/admin/resolve-cors.js.map +1 -0
  32. package/dist/admin/scenarios-store.d.ts +15 -0
  33. package/dist/admin/scenarios-store.d.ts.map +1 -0
  34. package/dist/admin/scenarios-store.js +109 -0
  35. package/dist/admin/scenarios-store.js.map +1 -0
  36. package/dist/admin/ui.css +2 -0
  37. package/dist/admin/ui.html +13 -0
  38. package/dist/admin/ui.js +240 -0
  39. package/dist/bin-helpers.d.ts +46 -0
  40. package/dist/bin-helpers.d.ts.map +1 -0
  41. package/dist/bin-helpers.js +61 -0
  42. package/dist/bin-helpers.js.map +1 -0
  43. package/dist/bin.d.ts +3 -0
  44. package/dist/bin.d.ts.map +1 -0
  45. package/dist/bin.js +57 -0
  46. package/dist/bin.js.map +1 -0
  47. package/dist/cors.d.ts +57 -0
  48. package/dist/cors.d.ts.map +1 -0
  49. package/dist/cors.js +167 -0
  50. package/dist/cors.js.map +1 -0
  51. package/dist/define.d.ts +26 -0
  52. package/dist/define.d.ts.map +1 -0
  53. package/dist/define.js +55 -0
  54. package/dist/define.js.map +1 -0
  55. package/dist/index.d.ts +21 -0
  56. package/dist/index.d.ts.map +1 -0
  57. package/dist/index.js +19 -0
  58. package/dist/index.js.map +1 -0
  59. package/dist/json-config.d.ts +170 -0
  60. package/dist/json-config.d.ts.map +1 -0
  61. package/dist/json-config.js +684 -0
  62. package/dist/json-config.js.map +1 -0
  63. package/dist/jsonc.d.ts +24 -0
  64. package/dist/jsonc.d.ts.map +1 -0
  65. package/dist/jsonc.js +125 -0
  66. package/dist/jsonc.js.map +1 -0
  67. package/dist/log.d.ts +57 -0
  68. package/dist/log.d.ts.map +1 -0
  69. package/dist/log.js +103 -0
  70. package/dist/log.js.map +1 -0
  71. package/dist/middleware/request-validation.d.ts +58 -0
  72. package/dist/middleware/request-validation.d.ts.map +1 -0
  73. package/dist/middleware/request-validation.js +328 -0
  74. package/dist/middleware/request-validation.js.map +1 -0
  75. package/dist/mock-mode.d.ts +95 -0
  76. package/dist/mock-mode.d.ts.map +1 -0
  77. package/dist/mock-mode.js +95 -0
  78. package/dist/mock-mode.js.map +1 -0
  79. package/dist/openapi/auto-schema.d.ts +83 -0
  80. package/dist/openapi/auto-schema.d.ts.map +1 -0
  81. package/dist/openapi/auto-schema.js +232 -0
  82. package/dist/openapi/auto-schema.js.map +1 -0
  83. package/dist/openapi/generate.d.ts +20 -0
  84. package/dist/openapi/generate.d.ts.map +1 -0
  85. package/dist/openapi/generate.js +121 -0
  86. package/dist/openapi/generate.js.map +1 -0
  87. package/dist/openapi/loader.d.ts +18 -0
  88. package/dist/openapi/loader.d.ts.map +1 -0
  89. package/dist/openapi/loader.js +25 -0
  90. package/dist/openapi/loader.js.map +1 -0
  91. package/dist/openapi/register.d.ts +58 -0
  92. package/dist/openapi/register.d.ts.map +1 -0
  93. package/dist/openapi/register.js +166 -0
  94. package/dist/openapi/register.js.map +1 -0
  95. package/dist/openapi/seed.d.ts +16 -0
  96. package/dist/openapi/seed.d.ts.map +1 -0
  97. package/dist/openapi/seed.js +58 -0
  98. package/dist/openapi/seed.js.map +1 -0
  99. package/dist/openapi/translate.d.ts +55 -0
  100. package/dist/openapi/translate.d.ts.map +1 -0
  101. package/dist/openapi/translate.js +319 -0
  102. package/dist/openapi/translate.js.map +1 -0
  103. package/dist/openapi/walker.d.ts +44 -0
  104. package/dist/openapi/walker.d.ts.map +1 -0
  105. package/dist/openapi/walker.js +15 -0
  106. package/dist/openapi/walker.js.map +1 -0
  107. package/dist/request.d.ts +19 -0
  108. package/dist/request.d.ts.map +1 -0
  109. package/dist/request.js +66 -0
  110. package/dist/request.js.map +1 -0
  111. package/dist/response.d.ts +13 -0
  112. package/dist/response.d.ts.map +1 -0
  113. package/dist/response.js +43 -0
  114. package/dist/response.js.map +1 -0
  115. package/dist/route-key.d.ts +60 -0
  116. package/dist/route-key.d.ts.map +1 -0
  117. package/dist/route-key.js +105 -0
  118. package/dist/route-key.js.map +1 -0
  119. package/dist/run.d.ts +27 -0
  120. package/dist/run.d.ts.map +1 -0
  121. package/dist/run.js +87 -0
  122. package/dist/run.js.map +1 -0
  123. package/dist/server.d.ts +60 -0
  124. package/dist/server.d.ts.map +1 -0
  125. package/dist/server.js +497 -0
  126. package/dist/server.js.map +1 -0
  127. package/dist/types.d.ts +274 -0
  128. package/dist/types.d.ts.map +1 -0
  129. package/dist/types.js +9 -0
  130. package/dist/types.js.map +1 -0
  131. package/dist/validation/validate.d.ts +38 -0
  132. package/dist/validation/validate.d.ts.map +1 -0
  133. package/dist/validation/validate.js +372 -0
  134. package/dist/validation/validate.js.map +1 -0
  135. package/docs/admin/error-mode.md +14 -0
  136. package/docs/admin/headers.md +63 -0
  137. package/docs/admin/index.md +111 -0
  138. package/docs/admin/overrides.md +23 -0
  139. package/docs/admin/rest-api.md +90 -0
  140. package/docs/admin/scenarios.md +28 -0
  141. package/docs/cli.md +76 -0
  142. package/docs/config/admin.md +62 -0
  143. package/docs/config/cors.md +72 -0
  144. package/docs/config/endpoints.md +162 -0
  145. package/docs/config/index.md +107 -0
  146. package/docs/config/log.md +43 -0
  147. package/docs/config/mock-mode.md +84 -0
  148. package/docs/config/openapi.md +8 -0
  149. package/docs/config/schema.md +34 -0
  150. package/docs/config/server.md +32 -0
  151. package/docs/config/validation.md +161 -0
  152. package/docs/design.md +517 -0
  153. package/docs/errors.md +192 -0
  154. package/docs/getting-started.md +146 -0
  155. package/docs/index.md +23 -0
  156. package/docs/openapi/auto-schema.md +146 -0
  157. package/docs/openapi/fallback.md +242 -0
  158. package/docs/openapi/keywords.md +78 -0
  159. package/docs/recipes.md +258 -0
  160. package/docs/stability.md +118 -0
  161. package/docs/troubleshooting.md +191 -0
  162. package/package.json +80 -0
@@ -0,0 +1,166 @@
1
+ import { generateFromOasSchema } from './generate.js';
2
+ const SUPPORTED_METHODS = new Set([
3
+ 'get',
4
+ 'post',
5
+ 'put',
6
+ 'delete',
7
+ 'patch',
8
+ ]);
9
+ /**
10
+ * Convert OAS path syntax (`/users/{id}`) into @databehave/server pattern syntax
11
+ * (`/users/:id`). Other `{x}` placeholders not isolated to a single
12
+ * segment are left as-is — the matcher will then reject them at
13
+ * registration time.
14
+ */
15
+ const oasPathToInternal = (oasPath) => oasPath
16
+ .split('/')
17
+ .map((seg) => (seg.startsWith('{') && seg.endsWith('}') ? `:${seg.slice(1, -1)}` : seg))
18
+ .join('/');
19
+ const stubBody = (method) => Object.freeze(method === 'get' ? {} : { success: true, message: null });
20
+ const buildHandler = (body) => {
21
+ // The OAS-only generator is structurally deterministic — same
22
+ // schema → same value — so the body is materialised once at boot
23
+ // and re-served per request.
24
+ return () => ({ json: body });
25
+ };
26
+ const buildStubHandler = (_routePath, method) => {
27
+ const body = stubBody(method);
28
+ return () => ({ json: body });
29
+ };
30
+ /**
31
+ * `true` when an OAS schema node is empty / permissive (`{}`) —
32
+ * i.e. carries no shape information (no `$ref`, `type`, `enum`,
33
+ * `const`, `properties`, `items`, `allOf`, `oneOf`, `anyOf`).
34
+ * Per JSON Schema this matches any JSON value.
35
+ */
36
+ const isEmptyOasSchema = (node) => !node.$ref &&
37
+ !node.type &&
38
+ !node.enum &&
39
+ node.const === undefined &&
40
+ !node.properties &&
41
+ !node.items &&
42
+ !node.allOf &&
43
+ !node.oneOf &&
44
+ !node.anyOf;
45
+ /**
46
+ * Build a per-route per-status JSON body generator map from the OAS
47
+ * `responses` table. Used by mock-mode to synthesize an error body
48
+ * from the schema declared for that status code.
49
+ *
50
+ * Each generator is deterministic: same `(routeKey, status)` always
51
+ * yields the same body. Routes with `:param` segments are matched
52
+ * by exact path key only — runtime path-param substitution is the
53
+ * caller's responsibility.
54
+ *
55
+ * `onWalkError` mirrors `buildOpenApiRoutes` so spec gaps in error
56
+ * responses surface the same way as gaps in success responses.
57
+ */
58
+ export const buildOpenApiResponseGenerators = (doc, options = {}) => {
59
+ const out = new Map();
60
+ const paths = doc.paths;
61
+ if (!paths)
62
+ return out;
63
+ for (const [oasPath, methods] of Object.entries(paths)) {
64
+ if (oasPath === '/')
65
+ continue;
66
+ const kitPath = oasPathToInternal(oasPath);
67
+ for (const [methodName, op] of Object.entries(methods)) {
68
+ const method = methodName.toLowerCase();
69
+ if (!SUPPORTED_METHODS.has(method))
70
+ continue;
71
+ const key = `${method.toUpperCase()} ${kitPath}`;
72
+ const responses = op.responses;
73
+ if (!responses)
74
+ continue;
75
+ const perStatus = new Map();
76
+ for (const [statusStr, resp] of Object.entries(responses)) {
77
+ const status = Number.parseInt(statusStr, 10);
78
+ if (!Number.isFinite(status))
79
+ continue;
80
+ const schemaNode = resp?.content?.['application/json']?.schema;
81
+ if (!schemaNode || typeof schemaNode !== 'object')
82
+ continue;
83
+ if (isEmptyOasSchema(schemaNode)) {
84
+ // Permissive — let mock-mode fall back to its envelope.
85
+ continue;
86
+ }
87
+ try {
88
+ // Pre-generate at boot — the OAS-only generator is
89
+ // structurally deterministic so the body is constant per
90
+ // (route, status).
91
+ const body = generateFromOasSchema(schemaNode, doc);
92
+ perStatus.set(status, () => body);
93
+ }
94
+ catch (err) {
95
+ // Caller's mock-mode fallback envelope will be used; surface
96
+ // the spec gap so it can be fixed.
97
+ options.onWalkError?.(method, kitPath, err);
98
+ }
99
+ }
100
+ if (perStatus.size > 0)
101
+ out.set(key, perStatus);
102
+ }
103
+ }
104
+ return out;
105
+ };
106
+ /**
107
+ * Produce a `Map<RouteKey, Handler>` covering every OAS
108
+ * `paths.<p>.<m>` not in `skip`. Failing schemas fall back to the
109
+ * stub body and report via `onWalkError` (default: ignore).
110
+ */
111
+ export const buildOpenApiRoutes = (doc, options = {}) => {
112
+ const out = new Map();
113
+ const skip = options.skip ?? new Set();
114
+ const paths = doc.paths;
115
+ if (!paths)
116
+ return out;
117
+ for (const [oasPath, methods] of Object.entries(paths)) {
118
+ if (oasPath === '/')
119
+ continue;
120
+ const kitPath = oasPathToInternal(oasPath);
121
+ for (const [methodName, op] of Object.entries(methods)) {
122
+ const method = methodName.toLowerCase();
123
+ if (!SUPPORTED_METHODS.has(method))
124
+ continue;
125
+ const key = `${method.toUpperCase()} ${kitPath}`;
126
+ if (skip.has(key))
127
+ continue;
128
+ // Prefer 200, then the smallest declared 2xx response \u2014 OAS
129
+ // documents for mutating endpoints often only declare 201 / 202
130
+ // / 204, so falling back to those keeps the synthesised handler
131
+ // honest. 204 explicitly means \"no body\" and is handled below.
132
+ const responses = op.responses ?? {};
133
+ const twoXx = Object.keys(responses)
134
+ .map((s) => Number.parseInt(s, 10))
135
+ .filter((n) => Number.isFinite(n) && n >= 200 && n < 300)
136
+ .sort((a, b) => (a === 200 ? -1 : b === 200 ? 1 : a - b));
137
+ const pickedStatus = twoXx[0];
138
+ const pickedKey = pickedStatus !== undefined ? String(pickedStatus) : '200';
139
+ const responseSchema = responses[pickedKey]?.content?.['application/json']?.schema;
140
+ try {
141
+ if (pickedStatus === 204) {
142
+ out.set(key, buildStubHandler(kitPath, method));
143
+ }
144
+ else if (responseSchema && typeof responseSchema === 'object') {
145
+ if (isEmptyOasSchema(responseSchema)) {
146
+ options.onEmptySchema?.(method, kitPath, pickedStatus ?? 200);
147
+ out.set(key, buildStubHandler(kitPath, method));
148
+ }
149
+ else {
150
+ const body = generateFromOasSchema(responseSchema, doc);
151
+ out.set(key, buildHandler(body));
152
+ }
153
+ }
154
+ else {
155
+ out.set(key, buildStubHandler(kitPath, method));
156
+ }
157
+ }
158
+ catch (err) {
159
+ options.onWalkError?.(method, kitPath, err);
160
+ out.set(key, buildStubHandler(kitPath, method));
161
+ }
162
+ }
163
+ }
164
+ return out;
165
+ };
166
+ //# sourceMappingURL=register.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register.js","sourceRoot":"","sources":["../../src/openapi/register.ts"],"names":[],"mappings":"AAsBA,OAAO,EAAE,qBAAqB,EAAE,MAAM,eAAe,CAAA;AAGrD,MAAM,iBAAiB,GAAwB,IAAI,GAAG,CAAS;IAC7D,KAAK;IACL,MAAM;IACN,KAAK;IACL,QAAQ;IACR,OAAO;CACR,CAAC,CAAA;AAEF;;;;;GAKG;AACH,MAAM,iBAAiB,GAAG,CAAC,OAAe,EAAU,EAAE,CACpD,OAAO;KACJ,KAAK,CAAC,GAAG,CAAC;KACV,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;KACvF,IAAI,CAAC,GAAG,CAAC,CAAA;AAEd,MAAM,QAAQ,GAAG,CAAC,MAAc,EAAU,EAAE,CAC1C,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAA;AAEzE,MAAM,YAAY,GAAG,CAAC,IAAa,EAAW,EAAE;IAC9C,8DAA8D;IAC9D,iEAAiE;IACjE,6BAA6B;IAC7B,OAAO,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;AAC/B,CAAC,CAAA;AAED,MAAM,gBAAgB,GAAG,CACvB,UAAkB,EAClB,MAAc,EACL,EAAE;IACX,MAAM,IAAI,GAAG,QAAQ,CAAC,MAAM,CAAC,CAAA;IAC7B,OAAO,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAA;AAC/B,CAAC,CAAA;AAwBD;;;;;GAKG;AACH,MAAM,gBAAgB,GAAG,CAAC,IAAa,EAAW,EAAE,CAClD,CAAC,IAAI,CAAC,IAAI;IACV,CAAC,IAAI,CAAC,IAAI;IACV,CAAC,IAAI,CAAC,IAAI;IACV,IAAI,CAAC,KAAK,KAAK,SAAS;IACxB,CAAC,IAAI,CAAC,UAAU;IAChB,CAAC,IAAI,CAAC,KAAK;IACX,CAAC,IAAI,CAAC,KAAK;IACX,CAAC,IAAI,CAAC,KAAK;IACX,CAAC,IAAI,CAAC,KAAK,CAAA;AAEb;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAC5C,GAAW,EACX,UAAiE,EAAE,EACxB,EAAE;IAC7C,MAAM,GAAG,GAAG,IAAI,GAAG,EAAwC,CAAA;IAC3D,MAAM,KAAK,GAAI,GAA2D,CAAC,KAAK,CAAA;IAChF,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAA;IAEtB,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,IAAI,OAAO,KAAK,GAAG;YAAE,SAAQ;QAC7B,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;QAE1C,KAAK,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,EAAY,CAAA;YACjD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAQ;YAE5C,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,OAAO,EAAc,CAAA;YAC5D,MAAM,SAAS,GAAI,EAA8C,CAAC,SAAS,CAAA;YAC3E,IAAI,CAAC,SAAS;gBAAE,SAAQ;YAExB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAyB,CAAA;YAClD,KAAK,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE,CAAC;gBAC1D,MAAM,MAAM,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,EAAE,EAAE,CAAC,CAAA;gBAC7C,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;oBAAE,SAAQ;gBACtC,MAAM,UAAU,GACd,IAGD,EAAE,OAAO,EAAE,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;gBACxC,IAAI,CAAC,UAAU,IAAI,OAAO,UAAU,KAAK,QAAQ;oBAAE,SAAQ;gBAC3D,IAAI,gBAAgB,CAAC,UAAU,CAAC,EAAE,CAAC;oBACjC,wDAAwD;oBACxD,SAAQ;gBACV,CAAC;gBACD,IAAI,CAAC;oBACH,mDAAmD;oBACnD,yDAAyD;oBACzD,mBAAmB;oBACnB,MAAM,IAAI,GAAG,qBAAqB,CAAC,UAAU,EAAE,GAAG,CAAC,CAAA;oBACnD,SAAS,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;gBACnC,CAAC;gBAAC,OAAO,GAAG,EAAE,CAAC;oBACb,6DAA6D;oBAC7D,mCAAmC;oBACnC,OAAO,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;gBAC7C,CAAC;YACH,CAAC;YACD,IAAI,SAAS,CAAC,IAAI,GAAG,CAAC;gBAAE,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;QACjD,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAChC,GAAW,EACX,UAAgC,EAAE,EACV,EAAE;IAC1B,MAAM,GAAG,GAAG,IAAI,GAAG,EAAqB,CAAA;IACxC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,GAAG,EAAY,CAAA;IAChD,MAAM,KAAK,GAAI,GAA2D,CAAC,KAAK,CAAA;IAChF,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,CAAA;IAEtB,KAAK,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvD,IAAI,OAAO,KAAK,GAAG;YAAE,SAAQ;QAC7B,MAAM,OAAO,GAAG,iBAAiB,CAAC,OAAO,CAAC,CAAA;QAE1C,KAAK,MAAM,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACvD,MAAM,MAAM,GAAG,UAAU,CAAC,WAAW,EAAY,CAAA;YACjD,IAAI,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,CAAC;gBAAE,SAAQ;YAE5C,MAAM,GAAG,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,IAAI,OAAO,EAAc,CAAA;YAC5D,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;gBAAE,SAAQ;YAE3B,iEAAiE;YACjE,gEAAgE;YAChE,gEAAgE;YAChE,iEAAiE;YACjE,MAAM,SAAS,GAAI,EAA8C,CAAC,SAAS,IAAI,EAAE,CAAA;YACjF,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC;iBACjC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;iBAClC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,GAAG,GAAG,CAAC;iBACxD,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAA;YAC3D,MAAM,YAAY,GAAG,KAAK,CAAC,CAAC,CAAC,CAAA;YAC7B,MAAM,SAAS,GAAG,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC,KAAK,CAAA;YAC3E,MAAM,cAAc,GAClB,SAAS,CAAC,SAAS,CAGpB,EAAE,OAAO,EAAE,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAA;YAExC,IAAI,CAAC;gBACH,IAAI,YAAY,KAAK,GAAG,EAAE,CAAC;oBACzB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;gBACjD,CAAC;qBAAM,IAAI,cAAc,IAAI,OAAO,cAAc,KAAK,QAAQ,EAAE,CAAC;oBAChE,IAAI,gBAAgB,CAAC,cAAc,CAAC,EAAE,CAAC;wBACrC,OAAO,CAAC,aAAa,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,YAAY,IAAI,GAAG,CAAC,CAAA;wBAC7D,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;oBACjD,CAAC;yBAAM,CAAC;wBACN,MAAM,IAAI,GAAG,qBAAqB,CAAC,cAAc,EAAE,GAAG,CAAC,CAAA;wBACvD,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC,CAAA;oBAClC,CAAC;gBACH,CAAC;qBAAM,CAAC;oBACN,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;gBACjD,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,CAAC,CAAA;gBAC3C,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,gBAAgB,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;YACjD,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Pure helper for building deterministic databehave seed strings.
3
+ *
4
+ * Seed format: `<endpoint>|<sortedKey>=<val>|...|date=<YYYY-MM-DD>|day=<n>`.
5
+ * Same input → same string → databehave yields byte-identical JSON. Re-exported
6
+ * from `@databehave/server` so consumers can derive their own seeds without
7
+ * reimplementing the format.
8
+ */
9
+ export type SeedInput = {
10
+ endpoint: string;
11
+ from?: string;
12
+ dayOffset?: number;
13
+ extra?: Record<string, unknown>;
14
+ };
15
+ export declare const seedFor: (i: SeedInput) => string;
16
+ //# sourceMappingURL=seed.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.d.ts","sourceRoot":"","sources":["../../src/openapi/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,SAAS,GAAG;IACtB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC,CAAA;AAmCD,eAAO,MAAM,OAAO,GAAI,GAAG,SAAS,KAAG,MAWtC,CAAA"}
@@ -0,0 +1,58 @@
1
+ /**
2
+ * Pure helper for building deterministic databehave seed strings.
3
+ *
4
+ * Seed format: `<endpoint>|<sortedKey>=<val>|...|date=<YYYY-MM-DD>|day=<n>`.
5
+ * Same input → same string → databehave yields byte-identical JSON. Re-exported
6
+ * from `@databehave/server` so consumers can derive their own seeds without
7
+ * reimplementing the format.
8
+ */
9
+ /**
10
+ * Recursively rebuild `v` with object keys sorted so two structurally
11
+ * identical objects with different insertion orders serialise to the
12
+ * same string. Arrays preserve order (semantic), plain objects are
13
+ * key-sorted, primitives are returned as-is.
14
+ */
15
+ const sortKeys = (v) => {
16
+ if (v === null || typeof v !== 'object')
17
+ return v;
18
+ if (Array.isArray(v))
19
+ return v.map(sortKeys);
20
+ const out = {};
21
+ for (const k of Object.keys(v).sort()) {
22
+ out[k] = sortKeys(v[k]);
23
+ }
24
+ return out;
25
+ };
26
+ const stringifyExtra = (v) => {
27
+ if (v === null || v === undefined)
28
+ return String(v);
29
+ const t = typeof v;
30
+ if (t === 'string' || t === 'number' || t === 'boolean' || t === 'bigint') {
31
+ return String(v);
32
+ }
33
+ // Objects / arrays — use JSON so distinct shapes produce distinct
34
+ // seeds. Keys are sorted recursively so insertion-order doesn't
35
+ // perturb the seed. Falls back to `String()` if stringify fails
36
+ // (cycles).
37
+ try {
38
+ return JSON.stringify(sortKeys(v));
39
+ }
40
+ catch {
41
+ return String(v);
42
+ }
43
+ };
44
+ export const seedFor = (i) => {
45
+ const parts = [i.endpoint];
46
+ if (i.extra) {
47
+ const keys = Object.keys(i.extra).sort();
48
+ if (keys.length > 0) {
49
+ parts.push(keys.map((k) => `${k}=${stringifyExtra(i.extra[k])}`).join('|'));
50
+ }
51
+ }
52
+ if (i.from !== undefined)
53
+ parts.push(`date=${i.from}`);
54
+ if (i.dayOffset !== undefined)
55
+ parts.push(`day=${i.dayOffset}`);
56
+ return parts.join('|');
57
+ };
58
+ //# sourceMappingURL=seed.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed.js","sourceRoot":"","sources":["../../src/openapi/seed.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH;;;;;GAKG;AACH,MAAM,QAAQ,GAAG,CAAC,CAAU,EAAW,EAAE;IACvC,IAAI,CAAC,KAAK,IAAI,IAAI,OAAO,CAAC,KAAK,QAAQ;QAAE,OAAO,CAAC,CAAA;IACjD,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC;QAAE,OAAO,CAAC,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAA;IAC5C,MAAM,GAAG,GAA4B,EAAE,CAAA;IACvC,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,CAA4B,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;QACjE,GAAG,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAE,CAA6B,CAAC,CAAC,CAAC,CAAC,CAAA;IACtD,CAAC;IACD,OAAO,GAAG,CAAA;AACZ,CAAC,CAAA;AAED,MAAM,cAAc,GAAG,CAAC,CAAU,EAAU,EAAE;IAC5C,IAAI,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,SAAS;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAA;IACnD,MAAM,CAAC,GAAG,OAAO,CAAC,CAAA;IAClB,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,QAAQ,EAAE,CAAC;QAC1E,OAAO,MAAM,CAAC,CAAC,CAAC,CAAA;IAClB,CAAC;IACD,kEAAkE;IAClE,gEAAgE;IAChE,gEAAgE;IAChE,YAAY;IACZ,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAA;IAClB,CAAC;AACH,CAAC,CAAA;AAED,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAY,EAAU,EAAE;IAC9C,MAAM,KAAK,GAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAA;IACpC,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC;QACZ,MAAM,IAAI,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAA;QACxC,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACpB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,IAAI,cAAc,CAAC,CAAC,CAAC,KAAM,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAA;QAC9E,CAAC;IACH,CAAC;IACD,IAAI,CAAC,CAAC,IAAI,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE,CAAC,CAAA;IACtD,IAAI,CAAC,CAAC,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAA;IAC/D,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;AACxB,CAAC,CAAA"}
@@ -0,0 +1,55 @@
1
+ /**
2
+ * OpenAPI-node → @databehave/schema IR translator.
3
+ *
4
+ * Phase 2C step 2C.a: pure translator + unit tests. NOT yet wired into
5
+ * the OAS dispatch — `register.ts` and `generate.ts` still drive the
6
+ * default OAS-only mock path; this module is staged for the upcoming
7
+ * auto-schema mode where, when the consumer opts in via the JSONC
8
+ * `schema:` field, the server translates each response schema to the
9
+ * databehave IR and hands it to `mock()` for richer fixtures.
10
+ *
11
+ * Genuine-separation invariant: this file MUST NOT statically value-
12
+ * import `@databehave/schema`. The runtime DSL constructors are only
13
+ * loaded via `await import('@databehave/schema')` inside the function
14
+ * body, so OAS-only consumers who never opt in do not pay for the
15
+ * schema package at all. Type-only imports (`import type`) are erased
16
+ * at compile time and are fine.
17
+ *
18
+ * Supported OAS keywords (mirror `validation/validate.ts`):
19
+ * type / enum / required / properties / items / nullable + ["X","null"] /
20
+ * minLength / maxLength / pattern / minimum / maximum /
21
+ * exclusiveMinimum / exclusiveMaximum / minItems / maxItems /
22
+ * format (date | date-time | email | uuid | uri) /
23
+ * $ref (intra-document only) / oneOf / anyOf / allOf (shallow merge) /
24
+ * discriminator (propertyName + mapping) / example / examples / const /
25
+ * additionalProperties (no-op for generation; nested schema is
26
+ * recursively validated so unsupported inner keywords still FAIL LOUD).
27
+ *
28
+ * UNSUPPORTED keywords (FAIL LOUD at translate time, mirroring the
29
+ * validator): if / then / else / dependentSchemas / dependentRequired /
30
+ * unevaluatedProperties / unevaluatedItems / propertyNames /
31
+ * patternProperties / contentEncoding / contentMediaType /
32
+ * remote `http(s)://` $ref.
33
+ *
34
+ * Security guarantees mirror the validator:
35
+ * - Pattern length capped at 1024 chars (ReDoS guard).
36
+ * - `pattern` requires an own-node `maxLength` (ReDoS footgun).
37
+ * - Recursion depth capped at 64 levels.
38
+ * - $ref cycle detection via a build-time visited-pointer set.
39
+ *
40
+ * Precedence (mirrors `generate.ts` ordering, expressed via IR):
41
+ * example > examples[0] > enum[0]/enum > const > $ref >
42
+ * allOf (shallow merge of object branches) > oneOf[0]/union >
43
+ * anyOf[0]/union > type-fallback.
44
+ */
45
+ import type { Schema } from '@databehave/schema';
46
+ import type { OasDoc as OasDocument, OasNode as OasSchemaNode } from './walker.js';
47
+ /**
48
+ * Translate an OpenAPI schema node into a `@databehave/schema` IR
49
+ * Schema instance. Throws at translate time on unsupported keywords,
50
+ * malformed `$ref`s, `$ref` cycles, ReDoS-vulnerable patterns, and
51
+ * schemas that exceed the depth cap. See the file header for the
52
+ * full keyword matrix and security guarantees.
53
+ */
54
+ export declare function translateOasToIR(node: OasSchemaNode, doc: OasDocument): Promise<Schema<unknown>>;
55
+ //# sourceMappingURL=translate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"translate.d.ts","sourceRoot":"","sources":["../../src/openapi/translate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2CG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oBAAoB,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,IAAI,WAAW,EAAE,OAAO,IAAI,aAAa,EAAE,MAAM,aAAa,CAAA;AAgVlF;;;;;;GAMG;AACH,wBAAsB,gBAAgB,CACpC,IAAI,EAAE,aAAa,EACnB,GAAG,EAAE,WAAW,GACf,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAO1B"}
@@ -0,0 +1,319 @@
1
+ const DEPTH_CAP = 64;
2
+ const PATTERN_MAX_LENGTH = 1024;
3
+ const UNSUPPORTED_KEYWORDS = new Set([
4
+ 'if', 'then', 'else',
5
+ 'dependentSchemas', 'dependentRequired',
6
+ 'unevaluatedProperties', 'unevaluatedItems',
7
+ 'contentEncoding', 'contentMediaType',
8
+ 'propertyNames', 'patternProperties',
9
+ ]);
10
+ const FORMAT_MAP = {
11
+ date: 'date',
12
+ 'date-time': 'datetime',
13
+ email: 'email',
14
+ uuid: 'uuid',
15
+ uri: 'url',
16
+ };
17
+ const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
18
+ const buildErr = (pointer, message) => new Error(`@databehave/server/openapi: ${message} (at ${pointer})`);
19
+ const resolveRef = (ref, doc, pointer) => {
20
+ if (ref.includes('://'))
21
+ throw buildErr(pointer, `unsupported $ref: ${ref}`);
22
+ const m = /^#\/components\/schemas\/(.+)$/.exec(ref);
23
+ if (!m)
24
+ throw buildErr(pointer, `unsupported $ref: ${ref}`);
25
+ const name = decodeURIComponent(m[1]);
26
+ const target = doc.components?.schemas?.[name];
27
+ if (!target)
28
+ throw buildErr(pointer, `$ref not found: ${ref}`);
29
+ return target;
30
+ };
31
+ /**
32
+ * Build a databehave Schema from an arbitrary JSON value, used when
33
+ * an OAS node carries a literal `example` / `examples[0]` / `const`
34
+ * payload. Recursively maps objects → `obj({...})` and arrays →
35
+ * `tuple(...)` so the entire value is preserved in the IR.
36
+ */
37
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
+ const literalize = (value, m, pointer, depth) => {
39
+ if (depth > DEPTH_CAP) {
40
+ throw buildErr(pointer, `schema nesting exceeds depth cap of ${DEPTH_CAP}`);
41
+ }
42
+ if (value === null ||
43
+ typeof value === 'string' ||
44
+ typeof value === 'number' ||
45
+ typeof value === 'boolean') {
46
+ return m.literal(value);
47
+ }
48
+ if (Array.isArray(value)) {
49
+ if (value.length === 0)
50
+ return m.tuple();
51
+ return m.tuple(...value.map((v) => literalize(v, m, pointer, depth + 1)));
52
+ }
53
+ if (isObj(value)) {
54
+ const fields = {};
55
+ for (const [k, v] of Object.entries(value)) {
56
+ fields[k] = literalize(v, m, pointer, depth + 1);
57
+ }
58
+ return m.obj(fields);
59
+ }
60
+ throw buildErr(pointer, `cannot literalize value of type ${typeof value}`);
61
+ };
62
+ const enterPtr = (ctx, segment) => ({
63
+ ...ctx,
64
+ pointer: `${ctx.pointer}/${segment}`,
65
+ depth: ctx.depth + 1,
66
+ });
67
+ const pickType = (node) => {
68
+ if (Array.isArray(node.type))
69
+ return node.type.find((t) => t !== 'null');
70
+ return node.type;
71
+ };
72
+ const isNullable = (node) => node.nullable === true || (Array.isArray(node.type) && node.type.includes('null'));
73
+ const buildString = (node, ctx) => {
74
+ if (typeof node.pattern === 'string') {
75
+ if (node.pattern.length > PATTERN_MAX_LENGTH) {
76
+ throw buildErr(ctx.pointer, `pattern length ${node.pattern.length} exceeds cap of ${PATTERN_MAX_LENGTH} (ReDoS guard)`);
77
+ }
78
+ if (typeof node.maxLength !== 'number') {
79
+ throw buildErr(ctx.pointer, 'pattern requires maxLength on the same schema (footgun: ReDoS)');
80
+ }
81
+ try {
82
+ new RegExp(node.pattern);
83
+ }
84
+ catch (err) {
85
+ throw buildErr(ctx.pointer, `invalid pattern: ${err instanceof Error ? err.message : String(err)}`);
86
+ }
87
+ }
88
+ const fmt = typeof node.format === 'string' ? FORMAT_MAP[node.format] : undefined;
89
+ const stringNode = { kind: 'string', format: fmt ?? 'plain' };
90
+ if (typeof node.minLength === 'number')
91
+ stringNode.min = node.minLength;
92
+ if (typeof node.maxLength === 'number')
93
+ stringNode.max = node.maxLength;
94
+ if (typeof node.pattern === 'string')
95
+ stringNode.pattern = node.pattern;
96
+ return new ctx.m.StringSchema(stringNode);
97
+ };
98
+ const buildNumber = (node, ctx, isInt) => {
99
+ // schema's IR has only min/max (inclusive). exclusiveMinimum/Maximum are
100
+ // approximated as inclusive for IR mock-generation purposes — the
101
+ // generated value drift is at most one ulp / one int and acceptable for
102
+ // placeholder fixtures. The validator (validate.ts) still enforces the
103
+ // exact exclusive semantics on real inbound requests.
104
+ const numNode = { kind: 'number', int: isInt };
105
+ if (typeof node.minimum === 'number')
106
+ numNode.min = node.minimum;
107
+ else if (typeof node.exclusiveMinimum === 'number') {
108
+ numNode.min = node.exclusiveMinimum;
109
+ }
110
+ if (typeof node.maximum === 'number')
111
+ numNode.max = node.maximum;
112
+ else if (typeof node.exclusiveMaximum === 'number') {
113
+ numNode.max = node.exclusiveMaximum;
114
+ }
115
+ return new ctx.m.NumberSchema(numNode);
116
+ };
117
+ const buildArray = (node, ctx) => {
118
+ const itemNode = node.items ?? {};
119
+ const itemSchema = translate(itemNode, enterPtr(ctx, 'items'));
120
+ let arr = ctx.m.arr(itemSchema);
121
+ if (typeof node.minItems === 'number')
122
+ arr = arr.min(node.minItems);
123
+ if (typeof node.maxItems === 'number')
124
+ arr = arr.max(node.maxItems);
125
+ return arr;
126
+ };
127
+ const buildObject = (node, ctx) => {
128
+ const required = new Set(Array.isArray(node.required) ? node.required : []);
129
+ const fields = {};
130
+ for (const [k, child] of Object.entries(node.properties ?? {})) {
131
+ const childSchema = translate(child, enterPtr(ctx, `properties/${k}`));
132
+ fields[k] = required.has(k) ? childSchema : childSchema.optional();
133
+ }
134
+ // `additionalProperties` is a no-op for *generation* — we only ever
135
+ // emit the declared `properties` above, so we never invent extra
136
+ // keys regardless of the keyword's value. We still acknowledge it
137
+ // explicitly so the translator's keyword surface mirrors
138
+ // `validation/validate.ts` (which DOES enforce it on inbound bodies).
139
+ const ap = node.additionalProperties;
140
+ if (ap === false) {
141
+ // Strict-extra-keys: aligned with validator behaviour. No-op here.
142
+ }
143
+ else if (ap === true || ap === undefined) {
144
+ // Open object: no-op — generator never invents undeclared keys.
145
+ }
146
+ else if (isObj(ap)) {
147
+ // Schema form: translate-and-discard. We don't use the result for
148
+ // generation (we never emit extra keys), but recursing now means
149
+ // an unsupported nested keyword (e.g. `if`) FAILS LOUD at build
150
+ // time instead of being silently accepted.
151
+ translate(ap, enterPtr(ctx, 'additionalProperties'));
152
+ }
153
+ return ctx.m.obj(fields);
154
+ };
155
+ const buildDiscriminated = (node, ctx, disc) => {
156
+ const branches = {};
157
+ for (const [tag, ref] of Object.entries(disc.mapping)) {
158
+ const childCtx = enterPtr(ctx, `discriminator/mapping/${tag}`);
159
+ if (childCtx.refStack.includes(ref)) {
160
+ throw buildErr(childCtx.pointer, `$ref cycle detected: ${ref}`);
161
+ }
162
+ const target = resolveRef(ref, ctx.doc, childCtx.pointer);
163
+ const branchSchema = translate(target, {
164
+ ...childCtx,
165
+ refStack: [...childCtx.refStack, ref],
166
+ pointer: ref,
167
+ });
168
+ if (!(branchSchema instanceof ctx.m.ObjectSchema)) {
169
+ throw buildErr(childCtx.pointer, `discriminator branch ${JSON.stringify(tag)} must be an object schema`);
170
+ }
171
+ // Force the discriminator field to literal(tag) so `discriminated()`
172
+ // accepts the branch — OAS schemas typically declare it as
173
+ // `type: 'string'` rather than constraining to the tag value.
174
+ const objBranch = branchSchema;
175
+ const newFields = {
176
+ ...objBranch._fields,
177
+ [disc.propertyName]: ctx.m.literal(tag),
178
+ };
179
+ branches[tag] = ctx.m.obj(newFields);
180
+ }
181
+ return ctx.m.discriminated(disc.propertyName, branches);
182
+ };
183
+ const buildAllOfMerge = (node, ctx) => {
184
+ // Mirror generate.ts: shallow-merge object-typed branches into a
185
+ // single obj schema. If any branch isn't object-typed once translated
186
+ // we bail and let the caller fall through to other precedence rules.
187
+ const merged = {};
188
+ let allObjects = true;
189
+ for (let i = 0; i < node.allOf.length; i++) {
190
+ const branch = translate(node.allOf[i], enterPtr(ctx, `allOf/${i}`));
191
+ if (!(branch instanceof ctx.m.ObjectSchema)) {
192
+ allObjects = false;
193
+ break;
194
+ }
195
+ const fields = branch._fields;
196
+ for (const [k, v] of Object.entries(fields))
197
+ merged[k] = v;
198
+ }
199
+ if (!allObjects)
200
+ return null;
201
+ return ctx.m.obj(merged);
202
+ };
203
+ const translate = (node, ctx) => {
204
+ if (ctx.depth > DEPTH_CAP) {
205
+ throw buildErr(ctx.pointer, `schema nesting exceeds depth cap of ${DEPTH_CAP}`);
206
+ }
207
+ if (!isObj(node))
208
+ throw buildErr(ctx.pointer, 'schema node must be an object');
209
+ for (const key of Object.keys(node)) {
210
+ if (UNSUPPORTED_KEYWORDS.has(key)) {
211
+ throw buildErr(ctx.pointer, `unsupported JSON Schema keyword: ${key}`);
212
+ }
213
+ }
214
+ // Precedence (mirrors generate.ts):
215
+ // example > examples[0] > enum > const > $ref > allOf > oneOf > anyOf > type
216
+ if (node.example !== undefined) {
217
+ return literalize(node.example, ctx.m, ctx.pointer, ctx.depth);
218
+ }
219
+ if (Array.isArray(node.examples) && node.examples.length > 0) {
220
+ return literalize(node.examples[0], ctx.m, ctx.pointer, ctx.depth);
221
+ }
222
+ if (Array.isArray(node.enum) && node.enum.length > 0) {
223
+ const allStrNum = node.enum.every((v) => typeof v === 'string' || typeof v === 'number');
224
+ return allStrNum
225
+ ? ctx.m.enum_(node.enum)
226
+ : ctx.m.union(...node.enum.map((v) => literalize(v, ctx.m, ctx.pointer, ctx.depth)));
227
+ }
228
+ if (node.const !== undefined) {
229
+ return literalize(node.const, ctx.m, ctx.pointer, ctx.depth);
230
+ }
231
+ if (typeof node.$ref === 'string') {
232
+ if (ctx.refStack.includes(node.$ref)) {
233
+ throw buildErr(ctx.pointer, `$ref cycle detected: ${[...ctx.refStack, node.$ref].join(' -> ')}`);
234
+ }
235
+ const target = resolveRef(node.$ref, ctx.doc, ctx.pointer);
236
+ const out = translate(target, {
237
+ ...ctx,
238
+ refStack: [...ctx.refStack, node.$ref],
239
+ depth: ctx.depth + 1,
240
+ pointer: node.$ref,
241
+ });
242
+ return isNullable(node) ? out.nullable() : out;
243
+ }
244
+ if (Array.isArray(node.allOf) && node.allOf.length > 0) {
245
+ const merged = buildAllOfMerge(node, ctx);
246
+ if (merged !== null)
247
+ return isNullable(node) ? merged.nullable() : merged;
248
+ }
249
+ const disc = node.discriminator;
250
+ if (Array.isArray(node.oneOf) && node.oneOf.length > 0) {
251
+ if (disc && disc.mapping) {
252
+ const out = buildDiscriminated(node, ctx, { propertyName: disc.propertyName, mapping: disc.mapping });
253
+ return isNullable(node) ? out.nullable() : out;
254
+ }
255
+ const branches = node.oneOf.map((b, i) => translate(b, enterPtr(ctx, `oneOf/${i}`)));
256
+ const out = ctx.m.union(...branches);
257
+ return isNullable(node) ? out.nullable() : out;
258
+ }
259
+ if (Array.isArray(node.anyOf) && node.anyOf.length > 0) {
260
+ const branches = node.anyOf.map((b, i) => translate(b, enterPtr(ctx, `anyOf/${i}`)));
261
+ const out = ctx.m.union(...branches);
262
+ return isNullable(node) ? out.nullable() : out;
263
+ }
264
+ // type-fallback
265
+ let t = pickType(node);
266
+ if (!t && node.properties)
267
+ t = 'object';
268
+ if (!t && node.items !== undefined)
269
+ t = 'array';
270
+ if (!t) {
271
+ // Unknown / open node: best-effort — empty object schema, with
272
+ // nullable applied if requested.
273
+ const empty = ctx.m.obj({});
274
+ return isNullable(node) ? empty.nullable() : empty;
275
+ }
276
+ let built;
277
+ switch (t) {
278
+ case 'string':
279
+ built = buildString(node, ctx);
280
+ break;
281
+ case 'integer':
282
+ built = buildNumber(node, ctx, true);
283
+ break;
284
+ case 'number':
285
+ built = buildNumber(node, ctx, false);
286
+ break;
287
+ case 'boolean':
288
+ built = ctx.m.bool();
289
+ break;
290
+ case 'null':
291
+ built = ctx.m.null_();
292
+ break;
293
+ case 'array':
294
+ built = buildArray(node, ctx);
295
+ break;
296
+ case 'object':
297
+ built = buildObject(node, ctx);
298
+ break;
299
+ default:
300
+ throw buildErr(ctx.pointer, `unsupported OpenAPI type: ${t}`);
301
+ }
302
+ return isNullable(node) ? built.nullable() : built;
303
+ };
304
+ /**
305
+ * Translate an OpenAPI schema node into a `@databehave/schema` IR
306
+ * Schema instance. Throws at translate time on unsupported keywords,
307
+ * malformed `$ref`s, `$ref` cycles, ReDoS-vulnerable patterns, and
308
+ * schemas that exceed the depth cap. See the file header for the
309
+ * full keyword matrix and security guarantees.
310
+ */
311
+ export async function translateOasToIR(node, doc) {
312
+ // Genuine-separation invariant: dynamic import only. Do NOT replace
313
+ // with a static `import { ... } from '@databehave/schema'` — that
314
+ // would re-introduce a runtime dependency on the schema package and
315
+ // break the OAS-only consumer's zero-dep posture.
316
+ const m = await import('@databehave/schema');
317
+ return translate(node, { doc, refStack: [], depth: 0, pointer: '#', m });
318
+ }
319
+ //# sourceMappingURL=translate.js.map