@cleverbrush/schema-json 2.0.0 → 3.0.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.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @cleverbrush/schema-json
2
2
  <!-- coverage-badge-start -->
3
- ![Coverage](https://img.shields.io/badge/coverage-95.5%25-brightgreen)
3
+ ![Coverage](https://img.shields.io/badge/coverage-92.8%25-brightgreen)
4
4
  <!-- coverage-badge-end -->
5
5
 
6
6
  Bidirectional JSON Schema (Draft 7 / 2020-12) interop for
@@ -115,6 +115,7 @@ const schema = fromJsonSchema(S); // ObjectSchemaBuilder<{ x: NumberSchemaBuilde
115
115
  | `const` | literal builder (`.equals(...)`) |
116
116
  | `enum` | `union(…)` of const builders |
117
117
  | `anyOf` | `union(…)` of sub-builders |
118
+ | `anyOf` + `discriminator` | auto-emitted for discriminated `union()` branches (see below) |
118
119
  | `allOf` | not supported — falls back to `any()` |
119
120
  | `minLength` / `maxLength` | `.minLength()` / `.maxLength()` |
120
121
  | `pattern` | `.matches(regex)` (invalid patterns silently ignored) |
@@ -153,12 +154,41 @@ function toJsonSchema(
153
154
 
154
155
  Descriptions set via `.describe(text)` are emitted as the `description` field on the corresponding JSON Schema node (including nested object properties).
155
156
 
157
+ Examples set via `.example(value)` are emitted as the `examples` array on the corresponding JSON Schema node.
158
+
159
+ #### Discriminated unions
160
+
161
+ When a `union()` is a **discriminated union** — all branches are objects sharing a required property with unique literal values — `toJsonSchema()` automatically emits the `discriminator` keyword alongside `anyOf`:
162
+
163
+ ```ts
164
+ const schema = union(
165
+ object({ type: string('cat'), name: string() })
166
+ ).or(
167
+ object({ type: string('dog'), breed: string() })
168
+ );
169
+
170
+ toJsonSchema(schema, { $schema: false });
171
+ // {
172
+ // anyOf: [ { ... type: { const: 'cat' } ... }, { ... type: { const: 'dog' } ... } ],
173
+ // discriminator: { propertyName: 'type' }
174
+ // }
175
+ ```
176
+
177
+ When a `nameResolver` is provided and union branches resolve to `$ref` pointers, a `mapping` is also emitted:
178
+
179
+ ```ts
180
+ // discriminator: { propertyName: 'type', mapping: { cat: '#/components/schemas/Cat', dog: '#/components/schemas/Dog' } }
181
+ ```
182
+
183
+ This enables code-generation tools (openapi-generator, orval, etc.) to produce proper tagged union types.
184
+
156
185
  #### `ToJsonSchemaOptions`
157
186
 
158
187
  | Option | Type | Default | Description |
159
188
  | --- | --- | --- | --- |
160
189
  | `draft` | `'2020-12' \| '07'` | `'2020-12'` | JSON Schema draft version for the `$schema` URI |
161
190
  | `$schema` | `boolean` | `true` | Whether to include the `$schema` header in the output |
191
+ | `nameResolver` | `(schema: SchemaBuilder) => string \| null` | `undefined` | Called for every node before conversion. Return a non-null string to emit `{ $ref: '#/components/schemas/<name>' }` instead of an inline schema. Used by `@cleverbrush/server-openapi` to wire named schemas from `.schemaName()` into `$ref` pointers. |
162
192
 
163
193
  ```ts
164
194
  // Embed in OpenAPI (suppress the $schema header)
@@ -168,6 +198,46 @@ toJsonSchema(schema, { $schema: false });
168
198
  toJsonSchema(schema, { draft: '07' });
169
199
  ```
170
200
 
201
+ ### Lazy / Recursive Schemas
202
+
203
+ `toJsonSchema` resolves `lazy()` schemas transparently. When the resolved schema
204
+ has a name returned by `nameResolver`, the output is a `$ref` pointer — which
205
+ is the key mechanism for breaking recursive cycles:
206
+
207
+ ```ts
208
+ import { object, number, array, lazy } from '@cleverbrush/schema';
209
+ import { toJsonSchema } from '@cleverbrush/schema-json';
210
+
211
+ type TreeNode = { value: number; children: TreeNode[] };
212
+
213
+ const treeNode: ReturnType<typeof object> = object({
214
+ value: number(),
215
+ children: array(lazy(() => treeNode))
216
+ }).schemaName('TreeNode');
217
+
218
+ let rootSeen = false;
219
+ toJsonSchema(treeNode, {
220
+ $schema: false,
221
+ nameResolver: s => {
222
+ // Inline the root once (for the definition itself), then emit $ref
223
+ if (s === treeNode && !rootSeen) { rootSeen = true; return null; }
224
+ return (s.introspect() as any).schemaName ?? null;
225
+ }
226
+ });
227
+ // {
228
+ // type: 'object',
229
+ // properties: {
230
+ // value: { type: 'integer' },
231
+ // children: { type: 'array', items: { $ref: '#/components/schemas/TreeNode' } }
232
+ // },
233
+ // ...
234
+ // }
235
+ ```
236
+
237
+ When using `@cleverbrush/server-openapi`, this is handled automatically — call
238
+ `.schemaName()` on the root schema and `generateOpenApiSpec` will emit the
239
+ correct `$ref` pointers and component definition with no extra configuration.
240
+
171
241
  ---
172
242
 
173
243
  ## Type Utilities
@@ -221,3 +291,4 @@ type B = JsonSchemaNodeToBuilder<typeof S>;
221
291
  | `allOf` in `fromJsonSchema` | Falls back to `SchemaBuilder<unknown>` (no deep merge) |
222
292
  | Dual IP format (`ip()` with both v4 + v6) | `format` is omitted in `toJsonSchema` output (no standard keyword covers both) |
223
293
  | JSDoc comments on properties | Not preserved in `toJsonSchema` output |
294
+ | `nameResolver` + `$ref` / `$defs` round-trip | `nameResolver` emits `$ref` pointers based on external registry; `fromJsonSchema` does not resolve `$ref` references — they fall back to `any()` |
package/dist/index.js CHANGED
@@ -1,2 +1,2 @@
1
- import{any as u,array as J,boolean as b,nul as v,number as l,object as B,string as x,tuple as N,union as O}from"@cleverbrush/schema";function p(n){return{valid:!1,errors:[{message:n}]}}function y(){return{valid:!0,errors:[]}}function m(n){if(typeof n!="object"||n===null)return u();let e=n,t;if("enum"in e&&Array.isArray(e.enum))t=j(e.enum);else if("const"in e)t=h(e.const);else if("anyOf"in e&&Array.isArray(e.anyOf))t=I(e.anyOf);else if("allOf"in e&&Array.isArray(e.allOf))t=u();else if(!("type"in e))t=u();else switch(e.type){case"string":t=T(e);break;case"number":t=S(e,!1);break;case"integer":t=S(e,!0);break;case"boolean":t=b();break;case"null":t=v();break;case"array":t=L(e);break;case"object":t=R(e);break;default:t=u()}return e.readOnly===!0&&(t=t.readonly()),typeof e.description=="string"&&e.description&&(t=t.describe(e.description)),t}function h(n){return typeof n=="string"?x(n):typeof n=="number"?l(n):typeof n=="boolean"?b().equals(n):u()}function T(n){let e=x(),t=n.format;if(t==="email"?e=e.email():t==="uuid"?e=e.uuid():t==="uri"||t==="url"?e=e.url():t==="ipv4"?e=e.ip({version:"v4"}):t==="ipv6"?e=e.ip({version:"v6"}):t==="date-time"&&(e=e.addValidator(a=>typeof a=="string"&&!Number.isNaN(Date.parse(a))?y():p("must be a valid date-time string"))),typeof n.minLength=="number"&&(e=e.minLength(n.minLength)),typeof n.maxLength=="number"&&(e=e.maxLength(n.maxLength)),typeof n.pattern=="string")try{e=e.matches(new RegExp(n.pattern))}catch{}return e}function S(n,e){let t=e?l().isInteger():l().isFloat();if(typeof n.minimum=="number"&&(t=t.min(n.minimum)),typeof n.maximum=="number"&&(t=t.max(n.maximum)),typeof n.multipleOf=="number"&&(t=t.multipleOf(n.multipleOf)),typeof n.exclusiveMinimum=="number"){let a=n.exclusiveMinimum;t=t.addValidator(r=>typeof r=="number"&&r>a?y():p(`must be greater than ${a}`))}if(typeof n.exclusiveMaximum=="number"){let a=n.exclusiveMaximum;t=t.addValidator(r=>typeof r=="number"&&r<a?y():p(`must be less than ${a}`))}return t}function L(n){if(Array.isArray(n.prefixItems)){let t=n.prefixItems.map(m),a=N(t);return n.items!==void 0&&n.items!==!1&&typeof n.items=="object"&&(a=a.rest(m(n.items))),a}let e=J();return n.items!==void 0&&(e=e.of(m(n.items))),typeof n.minItems=="number"&&(e=e.minLength(n.minItems)),typeof n.maxItems=="number"&&(e=e.maxLength(n.maxItems)),e}function R(n){let e=n.properties,t=new Set(Array.isArray(n.required)?n.required:[]),a=B();if(e)for(let[r,i]of Object.entries(e)){let s=m(i);a=a.addProp(r,t.has(r)?s.required():s.optional())}return n.additionalProperties!==!1&&(a=a.acceptUnknownProps()),a}function j(n){if(n.length===0)return u();let[e,...t]=n,a=O(h(e));for(let r of t)a=a.or(h(r));return a}function I(n){if(n.length===0)return u();let[e,...t]=n,a=O(m(e));for(let r of t)a=a.or(m(r));return a}function q(n){return m(n)}function w(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function $(n){let e=n.introspect(),t=e.extensions??{},a=e.isReadonly===!0?{readOnly:!0}:{};switch(e.type){case"string":{if(e.equalsTo!==void 0)return{...a,const:e.equalsTo};let r={...a,type:"string"};if(t.email===!0)r.format="email";else if(t.uuid===!0)r.format="uuid";else if(t.url)r.format="uri";else if(t.ip){let i=typeof t.ip=="object"?t.ip.version:void 0;i==="v4"?r.format="ipv4":i==="v6"&&(r.format="ipv6")}if(e.minLength!==void 0&&(r.minLength=e.minLength),e.maxLength!==void 0&&(r.maxLength=e.maxLength),t.nonempty===!0&&r.minLength===void 0&&(r.minLength=1),e.matches instanceof RegExp){if(e.matches.flags!=="")throw new Error(`Cannot convert RegExp /${e.matches.source}/${e.matches.flags} to JSON Schema pattern: RegExp flags are not representable in standard JSON Schema.`);r.pattern=e.matches.source}else e.startsWith!==void 0?r.pattern=`^${w(e.startsWith)}`:e.endsWith!==void 0&&(r.pattern=`${w(e.endsWith)}$`);return r}case"number":{if(e.equalsTo!==void 0)return{...a,const:e.equalsTo};let r={...a,type:e.isInteger?"integer":"number"};return e.min!==void 0&&(r.minimum=e.min),e.max!==void 0&&(r.maximum=e.max),t.multipleOf!==void 0&&(r.multipleOf=t.multipleOf),t.positive===!0&&(r.exclusiveMinimum=0),t.negative===!0&&(r.exclusiveMaximum=0),r}case"boolean":return e.equalsTo!==void 0?{...a,const:e.equalsTo}:{...a,type:"boolean"};case"date":return{...a,type:"string",format:"date-time"};case"array":{let r={...a,type:"array"};return e.elementSchema&&(r.items=c(e.elementSchema)),e.minLength!==void 0&&(r.minItems=e.minLength),e.maxLength!==void 0&&(r.maxItems=e.maxLength),t.nonempty===!0&&r.minItems===void 0&&(r.minItems=1),r}case"tuple":{let r=e.elements??[],i={type:"array",prefixItems:r.map(c),minItems:r.length};return e.restSchema?i.items=c(e.restSchema):(i.items=!1,i.maxItems=r.length),i}case"object":{let r={...a,type:"object"},i=e.properties;if(i){let s={},f=[];for(let[o,g]of Object.entries(i))s[o]=c(g),g.introspect().isRequired!==!1&&f.push(o);r.properties=s,f.length>0&&(r.required=f)}return e.acceptUnknownProps===!1&&(r.additionalProperties=!1),r}case"null":return{...a,type:"null"};case"union":{let r=e.options??[],i=[],s=r.length>0;for(let f of r){let o=f.introspect();if((o.type==="string"||o.type==="number"||o.type==="boolean")&&o.equalsTo!==void 0)i.push(o.equalsTo);else{s=!1;break}}return s?{...a,enum:i}:{...a,anyOf:r.map(c)}}default:return{}}}function c(n){let e=$(n),t=n.introspect();return typeof t.description=="string"&&t.description!==""&&(e.description=t.description),e}function d(n,e){let t=c(n);return e?.$schema===!1?t:{$schema:(e?.draft??"2020-12")==="07"?"http://json-schema.org/draft-07/schema#":"https://json-schema.org/draft/2020-12/schema",...t}}function k(n){switch(n){case"draft-2020-12":return{draft:"2020-12",$schema:!1};case"draft-07":return{draft:"07",$schema:!1};case"openapi-3.0":return{draft:"07",$schema:!1};default:throw new Error(`@cleverbrush/schema-json: unsupported JSON Schema target "${n}".`)}}function V(n){let e={input(r){return d(n,k(r.target))},output(r){return d(n,k(r.target))}},a={...n["~standard"],jsonSchema:e};return Object.defineProperty(n,"~standard",{value:a,configurable:!0,writable:!1,enumerable:!1}),n}export{q as fromJsonSchema,d as toJsonSchema,V as withStandardJsonSchema};
1
+ import{any as f,array as j,boolean as R,nul as q,number as b,object as I,string as v,tuple as V,union as T}from"@cleverbrush/schema";function O(n){return{valid:!1,errors:[{message:n}]}}function x(){return{valid:!0,errors:[]}}function m(n){if(typeof n!="object"||n===null)return f();let t=n,e;if("enum"in t&&Array.isArray(t.enum))e=M(t.enum);else if("const"in t)e=k(t.const);else if("anyOf"in t&&Array.isArray(t.anyOf))e=C(t.anyOf);else if("allOf"in t&&Array.isArray(t.allOf))e=f();else if(!("type"in t))e=f();else switch(t.type){case"string":e=A(t);break;case"number":e=B(t,!1);break;case"integer":e=B(t,!0);break;case"boolean":e=R();break;case"null":e=q();break;case"array":e=P(t);break;case"object":e=E(t);break;default:e=f()}return t.readOnly===!0&&(e=e.readonly()),typeof t.description=="string"&&t.description&&(e=e.describe(t.description)),e}function k(n){return typeof n=="string"?v(n):typeof n=="number"?b(n):typeof n=="boolean"?R().equals(n):f()}function A(n){let t=v(),e=n.format;if(e==="email"?t=t.email():e==="uuid"?t=t.uuid():e==="uri"||e==="url"?t=t.url():e==="ipv4"?t=t.ip({version:"v4"}):e==="ipv6"?t=t.ip({version:"v6"}):e==="date-time"&&(t=t.addValidator(r=>typeof r=="string"&&!Number.isNaN(Date.parse(r))?x():O("must be a valid date-time string"))),typeof n.minLength=="number"&&(t=t.minLength(n.minLength)),typeof n.maxLength=="number"&&(t=t.maxLength(n.maxLength)),typeof n.pattern=="string")try{t=t.matches(new RegExp(n.pattern))}catch{}return t}function B(n,t){let e=t?b().isInteger():b().isFloat();if(typeof n.minimum=="number"&&(e=e.min(n.minimum)),typeof n.maximum=="number"&&(e=e.max(n.maximum)),typeof n.multipleOf=="number"&&(e=e.multipleOf(n.multipleOf)),typeof n.exclusiveMinimum=="number"){let r=n.exclusiveMinimum;e=e.addValidator(i=>typeof i=="number"&&i>r?x():O(`must be greater than ${r}`))}if(typeof n.exclusiveMaximum=="number"){let r=n.exclusiveMaximum;e=e.addValidator(i=>typeof i=="number"&&i<r?x():O(`must be less than ${r}`))}return e}function P(n){if(Array.isArray(n.prefixItems)){let e=n.prefixItems.map(m),r=V(e);return n.items!==void 0&&n.items!==!1&&typeof n.items=="object"&&(r=r.rest(m(n.items))),r}let t=j();return n.items!==void 0&&(t=t.of(m(n.items))),typeof n.minItems=="number"&&(t=t.minLength(n.minItems)),typeof n.maxItems=="number"&&(t=t.maxLength(n.maxItems)),t}function E(n){let t=n.properties,e=new Set(Array.isArray(n.required)?n.required:[]),r=I();if(t)for(let[i,a]of Object.entries(t)){let s=m(a);r=r.addProp(i,e.has(i)?s.required():s.optional())}return n.additionalProperties!==!1&&(r=r.acceptUnknownProps()),r}function M(n){if(n.length===0)return f();let[t,...e]=n,r=T(k(t));for(let i of e)r=r.or(k(i));return r}function C(n){if(n.length===0)return f();let[t,...e]=n,r=T(m(t));for(let i of e)r=r.or(m(i));return r}function W(n){return m(n)}function L(n){return n.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}function D(n){return n.replace(/~/g,"~0").replace(/\//g,"~1")}function F(n,t){let e=n.introspect(),r=e.extensions??{},i=e.isReadonly===!0?{readOnly:!0}:{};switch(e.type){case"string":{if(e.equalsTo!==void 0)return{...i,const:e.equalsTo};let a={...i,type:"string"};if(r.email===!0)a.format="email";else if(r.uuid===!0)a.format="uuid";else if(r.url)a.format="uri";else if(r.ip){let s=typeof r.ip=="object"?r.ip.version:void 0;s==="v4"?a.format="ipv4":s==="v6"&&(a.format="ipv6")}if(e.minLength!==void 0&&(a.minLength=e.minLength),e.maxLength!==void 0&&(a.maxLength=e.maxLength),r.nonempty===!0&&a.minLength===void 0&&(a.minLength=1),e.matches instanceof RegExp){if(e.matches.flags!=="")throw new Error(`Cannot convert RegExp /${e.matches.source}/${e.matches.flags} to JSON Schema pattern: RegExp flags are not representable in standard JSON Schema.`);a.pattern=e.matches.source}else e.startsWith!==void 0?a.pattern=`^${L(e.startsWith)}`:e.endsWith!==void 0&&(a.pattern=`${L(e.endsWith)}$`);return Array.isArray(r.oneOf)&&r.oneOf.length>0&&(a.enum=[...r.oneOf]),a}case"number":{if(e.equalsTo!==void 0)return{...i,const:e.equalsTo};let a={...i,type:e.isInteger?"integer":"number"};return e.min!==void 0&&(a.minimum=e.min),e.max!==void 0&&(a.maximum=e.max),r.multipleOf!==void 0&&(a.multipleOf=r.multipleOf),r.positive===!0&&(a.exclusiveMinimum=0),r.negative===!0&&(a.exclusiveMaximum=0),Array.isArray(r.oneOf)&&r.oneOf.length>0&&(a.enum=[...r.oneOf]),a}case"boolean":return e.equalsTo!==void 0?{...i,const:e.equalsTo}:{...i,type:"boolean"};case"date":return{...i,type:"string",format:"date-time"};case"array":{let a={...i,type:"array"};return e.elementSchema&&(a.items=c(e.elementSchema,t)),e.minLength!==void 0&&(a.minItems=e.minLength),e.maxLength!==void 0&&(a.maxItems=e.maxLength),r.nonempty===!0&&a.minItems===void 0&&(a.minItems=1),a}case"tuple":{let a=e.elements??[],s={type:"array",prefixItems:a.map(u=>c(u,t)),minItems:a.length};return e.restSchema?s.items=c(e.restSchema,t):(s.items=!1,s.maxItems=a.length),s}case"object":{let a={...i,type:"object"},s=e.properties;if(s){let u={},l=[];for(let[y,d]of Object.entries(s))u[y]=c(d,t),d.introspect().isRequired!==!1&&l.push(y);a.properties=u,l.length>0&&(a.required=l)}return e.acceptUnknownProps===!1&&(a.additionalProperties=!1),a}case"null":return{...i,type:"null"};case"union":{let a=e.options??[],s=[],u=a.length>0;for(let p of a){let o=p.introspect();if((o.type==="string"||o.type==="number"||o.type==="boolean")&&o.equalsTo!==void 0)s.push(o.equalsTo);else{u=!1;break}}if(u)return{...i,enum:s};let l=a.map(p=>c(p,t)),y={...i,anyOf:l},d=e.discriminatorPropertyName;if(d){let p={propertyName:d},o={},h=a.length>0;for(let g=0;g<a.length;g++){let w=l[g].$ref;if(typeof w!="string"){h=!1;break}let N=a[g].introspect().properties?.[d];if(!N){h=!1;break}let J=N.introspect();if(J.equalsTo===void 0){h=!1;break}o[String(J.equalsTo)]=w}h&&(p.mapping=o),y.discriminator=p}return y}case"lazy":{let a=e.getter();return c(a,t)}default:return{}}}function c(n,t){if(t){let i=t(n);if(typeof i=="string"&&i.length>0)return{$ref:`#/components/schemas/${D(i)}`}}let e=F(n,t),r=n.introspect();if(typeof r.description=="string"&&r.description!==""&&(e.description=r.description),r.example!==void 0&&(e.examples=[r.example]),r.hasDefault===!0&&r.defaultValue!==void 0&&typeof r.defaultValue!="function"&&(e.default=r.defaultValue),r.isNullable===!0){if(e.anyOf!==void 0){let i=e.anyOf;i.some(s=>s.type==="null")||i.push({type:"null"})}else if(e.enum!==void 0){let i=e.enum;i.includes(null)||(e.enum=[...i,null])}else if(typeof e.type=="string")e.type=[e.type,"null"];else if(e.const!==void 0){let i=e.const;delete e.const,e.anyOf=[{const:i},{type:"null"}]}}return e}function S(n,t){let e=c(n,t?.nameResolver);return t?.$schema===!1?e:{$schema:(t?.draft??"2020-12")==="07"?"http://json-schema.org/draft-07/schema#":"https://json-schema.org/draft/2020-12/schema",...e}}function $(n){switch(n){case"draft-2020-12":return{draft:"2020-12",$schema:!1};case"draft-07":return{draft:"07",$schema:!1};case"openapi-3.0":return{draft:"07",$schema:!1};default:throw new Error(`@cleverbrush/schema-json: unsupported JSON Schema target "${n}".`)}}function U(n){let t={input(i){return S(n,$(i.target))},output(i){return S(n,$(i.target))}},r={...n["~standard"],jsonSchema:t};return Object.defineProperty(n,"~standard",{value:r,configurable:!0,writable:!1,enumerable:!1}),n}export{W as fromJsonSchema,S as toJsonSchema,U as withStandardJsonSchema};
2
2
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/fromJsonSchema.ts","../src/toJsonSchema.ts","../src/standardJsonSchema.ts"],"sourcesContent":["import type { SchemaBuilder } from '@cleverbrush/schema';\nimport {\n any,\n array,\n boolean,\n nul,\n number,\n object,\n string,\n tuple,\n union\n} from '@cleverbrush/schema';\nimport type { JsonSchemaNodeToBuilder } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction fail(msg: string) {\n return { valid: false as const, errors: [{ message: msg }] };\n}\n\nfunction ok() {\n return { valid: true as const, errors: [] };\n}\n\nfunction buildNode(s: unknown): SchemaBuilder<any, any, any> {\n if (typeof s !== 'object' || s === null) return any();\n const node = s as Record<string, unknown>;\n\n let b: SchemaBuilder<any, any, any>;\n\n if ('enum' in node && Array.isArray(node['enum']))\n b = buildEnum(node['enum']);\n else if ('const' in node) b = buildConst(node['const']);\n else if ('anyOf' in node && Array.isArray(node['anyOf']))\n b = buildAnyOf(node['anyOf']);\n // allOf is not supported (no intersection builder); fall back to any()\n else if ('allOf' in node && Array.isArray(node['allOf'])) b = any();\n else if (!('type' in node)) b = any();\n else {\n switch (node['type']) {\n case 'string':\n b = buildString(node);\n break;\n case 'number':\n b = buildNumber(node, false);\n break;\n case 'integer':\n b = buildNumber(node, true);\n break;\n case 'boolean':\n b = boolean();\n break;\n case 'null':\n b = nul();\n break;\n case 'array':\n b = buildArray(node);\n break;\n case 'object':\n b = buildObject(node);\n break;\n default:\n b = any();\n }\n }\n\n if (node['readOnly'] === true) b = (b as any).readonly();\n if (typeof node['description'] === 'string' && node['description'])\n b = (b as any).describe(node['description']);\n return b;\n}\n\nfunction buildConst(val: unknown): SchemaBuilder<any, any, any> {\n if (typeof val === 'string') return string(val as any);\n if (typeof val === 'number') return number(val as any);\n if (typeof val === 'boolean') return boolean().equals(val);\n return any();\n}\n\nfunction buildString(\n node: Record<string, unknown>\n): SchemaBuilder<any, any, any> {\n let b: any = string();\n const format = node['format'] as string | undefined;\n\n if (format === 'email') {\n b = b.email();\n } else if (format === 'uuid') {\n b = b.uuid();\n } else if (format === 'uri' || format === 'url') {\n b = b.url();\n } else if (format === 'ipv4') {\n b = b.ip({ version: 'v4' });\n } else if (format === 'ipv6') {\n b = b.ip({ version: 'v6' });\n } else if (format === 'date-time') {\n b = b.addValidator((v: unknown) =>\n typeof v === 'string' && !Number.isNaN(Date.parse(v))\n ? ok()\n : fail('must be a valid date-time string')\n );\n }\n\n if (typeof node['minLength'] === 'number')\n b = b.minLength(node['minLength']);\n if (typeof node['maxLength'] === 'number')\n b = b.maxLength(node['maxLength']);\n if (typeof node['pattern'] === 'string') {\n try {\n b = b.matches(new RegExp(node['pattern']));\n } catch {\n // invalid regex pattern — silently ignore\n }\n }\n return b;\n}\n\nfunction buildNumber(\n node: Record<string, unknown>,\n integer: boolean\n): SchemaBuilder<any, any, any> {\n let b: any = integer ? number().isInteger() : number().isFloat();\n if (typeof node['minimum'] === 'number') b = b.min(node['minimum']);\n if (typeof node['maximum'] === 'number') b = b.max(node['maximum']);\n if (typeof node['multipleOf'] === 'number') {\n b = b.multipleOf(node['multipleOf']);\n }\n if (typeof node['exclusiveMinimum'] === 'number') {\n const min = node['exclusiveMinimum'];\n b = b.addValidator((v: unknown) =>\n typeof v === 'number' && v > min\n ? ok()\n : fail(`must be greater than ${min}`)\n );\n }\n if (typeof node['exclusiveMaximum'] === 'number') {\n const max = node['exclusiveMaximum'];\n b = b.addValidator((v: unknown) =>\n typeof v === 'number' && v < max\n ? ok()\n : fail(`must be less than ${max}`)\n );\n }\n return b;\n}\n\nfunction buildArray(\n node: Record<string, unknown>\n): SchemaBuilder<any, any, any> {\n // JSON Schema 2020-12 tuples use `prefixItems`\n if (Array.isArray(node['prefixItems'])) {\n const elements = (node['prefixItems'] as unknown[]).map(buildNode);\n let b: any = tuple(elements as any);\n // `items: false` means no extra elements (default for tuple)\n // `items: <schema>` means rest elements validated against that schema\n if (\n node['items'] !== undefined &&\n node['items'] !== false &&\n typeof node['items'] === 'object'\n ) {\n b = b.rest(buildNode(node['items']));\n }\n return b;\n }\n let b: any = array();\n if (node['items'] !== undefined) b = b.of(buildNode(node['items']));\n if (typeof node['minItems'] === 'number') b = b.minLength(node['minItems']);\n if (typeof node['maxItems'] === 'number') b = b.maxLength(node['maxItems']);\n return b;\n}\n\nfunction buildObject(\n node: Record<string, unknown>\n): SchemaBuilder<any, any, any> {\n const props = node['properties'] as Record<string, unknown> | undefined;\n const requiredSet = new Set<string>(\n Array.isArray(node['required']) ? (node['required'] as string[]) : []\n );\n let b: any = object();\n if (props) {\n for (const [key, propNode] of Object.entries(props)) {\n const propBuilder = buildNode(propNode);\n b = b.addProp(\n key,\n requiredSet.has(key)\n ? propBuilder.required()\n : propBuilder.optional()\n );\n }\n }\n if (node['additionalProperties'] !== false) b = b.acceptUnknownProps();\n return b;\n}\n\nfunction buildEnum(values: unknown[]): SchemaBuilder<any, any, any> {\n if (values.length === 0) return any();\n const [first, ...rest] = values;\n let b: any = union(buildConst(first));\n for (const v of rest) b = b.or(buildConst(v));\n return b;\n}\n\nfunction buildAnyOf(options: unknown[]): SchemaBuilder<any, any, any> {\n if (options.length === 0) return any();\n const [first, ...rest] = options;\n let b: any = union(buildNode(first));\n for (const opt of rest) b = b.or(buildNode(opt));\n return b;\n}\n\n/**\n * Converts a JSON Schema object into a `@cleverbrush/schema` builder.\n *\n * @remarks\n * **`as const` is required for precise TypeScript inference.** Without it\n * TypeScript widens string literals to `string` and the inferred builder\n * type collapses to `SchemaBuilder<unknown>`. Always pass the schema literal\n * (or the variable holding it) with `as const`.\n *\n * **Supported JSON Schema keywords**\n *\n * | Keyword | Builder equivalent |\n * | ------------------------------ | ------------------------------- |\n * | `type: 'string'` | `string()` |\n * | `type: 'number'` | `number()` |\n * | `type: 'integer'` | `number()` (integer flag) |\n * | `type: 'boolean'` | `boolean()` |\n * | `type: 'null'` | `SchemaBuilder<null>` |\n * | `type: 'array'` + `items` | `array(itemBuilder)` |\n * | `type: 'object'` + `properties`| `object({ … })` |\n * | `required: […]` | required / optional per-prop |\n * | `additionalProperties: true` | `.acceptUnknownProps()` |\n * | `const` | `string/number/boolean eq` |\n * | `enum` | `union(…)` |\n * | `anyOf` | `union(…)` |\n * | `minLength` / `maxLength` | `.minLength()` / `.maxLength()` |\n * | `pattern` | `.matches(regex)` (invalid patterns silently ignored) |\n * | `minimum` / `maximum` | `.min()` / `.max()` |\n * | `exclusiveMinimum` / `exclusiveMaximum` | custom validator (not round-trippable via `toJsonSchema`) |\n * | `multipleOf` | `.multipleOf()` |\n * | `minItems` / `maxItems` | `.minLength()` / `.maxLength()` |\n * | `format: 'email'` | `.email()` extension |\n * | `format: 'uuid'` | `.uuid()` extension |\n * | `format: 'uri'` / `'url'` | `.url()` extension |\n * | `format: 'ipv4'` | `.ip({ version: 'v4' })` |\n * | `format: 'ipv6'` | `.ip({ version: 'v6' })` |\n * | `format: 'date-time'` | `.matches(iso8601 regex)` |\n *\n * Keywords **not** supported: `allOf` (falls back to `any()`), `$ref`,\n * `$defs`, `if/then/else`, `not`, `contains`, `unevaluatedProperties`,\n * `contentEncoding`.\n *\n * @param schema - A JSON Schema literal. Pass with `as const` for precise\n * TypeScript type inference on the returned builder.\n * @returns A `@cleverbrush/schema` builder whose static type mirrors the\n * structure described by the JSON Schema node.\n *\n * @example\n * ```ts\n * import { fromJsonSchema } from '@cleverbrush/schema-json';\n *\n * const schema = fromJsonSchema({\n * type: 'object',\n * properties: {\n * name: { type: 'string', minLength: 1 },\n * score: { type: 'number', minimum: 0, maximum: 100 },\n * },\n * required: ['name'],\n * } as const);\n *\n * schema.parse({ name: 'Alice', score: 95 });\n * // TypeScript infers: { name: string; score?: number }\n * ```\n *\n * @example\n * ```ts\n * // Union types via `enum`\n * const statusSchema = fromJsonSchema({\n * enum: ['active', 'inactive', 'pending']\n * } as const);\n *\n * statusSchema.parse('active'); // valid — 'active' | 'inactive' | 'pending'\n * ```\n *\n * @example\n * ```ts\n * // Use InferFromJsonSchema to derive the TypeScript type statically\n * import type { InferFromJsonSchema } from '@cleverbrush/schema-json';\n *\n * const S = { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] } as const;\n * type Payload = InferFromJsonSchema<typeof S>; // { id: number }\n * const payloadSchema = fromJsonSchema(S);\n * ```\n */\nexport function fromJsonSchema<const S>(schema: S): JsonSchemaNodeToBuilder<S> {\n return buildNode(schema) as JsonSchemaNodeToBuilder<S>;\n}\n","import type { SchemaBuilder } from '@cleverbrush/schema';\nimport type { ToJsonSchemaOptions } from './types.js';\n\ntype Out = Record<string, unknown>;\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction convertNodeInner(schema: SchemaBuilder<any, any, any>): Out {\n const info = schema.introspect() as any;\n const ext: Record<string, unknown> = info.extensions ?? {};\n const readOnly: Out = info.isReadonly === true ? { readOnly: true } : {};\n\n switch (info.type) {\n case 'string': {\n if (info.equalsTo !== undefined)\n return { ...readOnly, const: info.equalsTo };\n const out: Out = { ...readOnly, type: 'string' };\n if (ext['email'] === true) {\n out['format'] = 'email';\n } else if (ext['uuid'] === true) {\n out['format'] = 'uuid';\n } else if (ext['url']) {\n out['format'] = 'uri';\n } else if (ext['ip']) {\n const ver =\n typeof ext['ip'] === 'object'\n ? (ext['ip'] as any).version\n : undefined;\n if (ver === 'v4') out['format'] = 'ipv4';\n else if (ver === 'v6') out['format'] = 'ipv6';\n // both versions: no single standard JSON Schema format — omit\n }\n if (info.minLength !== undefined) out['minLength'] = info.minLength;\n if (info.maxLength !== undefined) out['maxLength'] = info.maxLength;\n // .nonempty() sets minLength via extension; if not already set\n if (ext['nonempty'] === true && out['minLength'] === undefined)\n out['minLength'] = 1;\n if (info.matches instanceof RegExp) {\n if (info.matches.flags !== '') {\n throw new Error(\n `Cannot convert RegExp /${info.matches.source}/${info.matches.flags} to JSON Schema pattern: RegExp flags are not representable in standard JSON Schema.`\n );\n }\n out['pattern'] = info.matches.source;\n } else if (info.startsWith !== undefined) {\n out['pattern'] = `^${escapeRegex(info.startsWith)}`;\n } else if (info.endsWith !== undefined) {\n out['pattern'] = `${escapeRegex(info.endsWith)}$`;\n }\n return out;\n }\n\n case 'number': {\n if (info.equalsTo !== undefined)\n return { ...readOnly, const: info.equalsTo };\n const out: Out = {\n ...readOnly,\n type: info.isInteger ? 'integer' : 'number'\n };\n if (info.min !== undefined) out['minimum'] = info.min;\n if (info.max !== undefined) out['maximum'] = info.max;\n if (ext['multipleOf'] !== undefined)\n out['multipleOf'] = ext['multipleOf'];\n if (ext['positive'] === true) out['exclusiveMinimum'] = 0;\n if (ext['negative'] === true) out['exclusiveMaximum'] = 0;\n return out;\n }\n\n case 'boolean': {\n if (info.equalsTo !== undefined)\n return { ...readOnly, const: info.equalsTo };\n return { ...readOnly, type: 'boolean' };\n }\n\n case 'date':\n return { ...readOnly, type: 'string', format: 'date-time' };\n\n case 'array': {\n const out: Out = { ...readOnly, type: 'array' };\n if (info.elementSchema)\n out['items'] = convertNode(info.elementSchema);\n if (info.minLength !== undefined) out['minItems'] = info.minLength;\n if (info.maxLength !== undefined) out['maxItems'] = info.maxLength;\n if (ext['nonempty'] === true && out['minItems'] === undefined)\n out['minItems'] = 1;\n return out;\n }\n\n case 'tuple': {\n const elements: SchemaBuilder<any, any, any>[] =\n info.elements ?? [];\n const out: Out = {\n type: 'array',\n prefixItems: elements.map(convertNode),\n minItems: elements.length\n };\n if (info.restSchema) {\n out['items'] = convertNode(info.restSchema);\n } else {\n out['items'] = false;\n out['maxItems'] = elements.length;\n }\n return out;\n }\n\n case 'object': {\n const out: Out = { ...readOnly, type: 'object' };\n const props = info.properties as\n | Record<string, SchemaBuilder<any, any, any>>\n | undefined;\n if (props) {\n const outProps: Record<string, unknown> = {};\n const required: string[] = [];\n for (const [key, propSchema] of Object.entries(props)) {\n outProps[key] = convertNode(propSchema);\n if ((propSchema.introspect() as any).isRequired !== false)\n required.push(key);\n }\n out['properties'] = outProps;\n if (required.length > 0) out['required'] = required;\n }\n if (info.acceptUnknownProps === false)\n out['additionalProperties'] = false;\n return out;\n }\n\n case 'null':\n return { ...readOnly, type: 'null' };\n\n case 'union': {\n const options: SchemaBuilder<any, any, any>[] = info.options ?? [];\n const enumValues: unknown[] = [];\n let allConst = options.length > 0;\n for (const opt of options) {\n const oi = opt.introspect() as any;\n if (\n (oi.type === 'string' ||\n oi.type === 'number' ||\n oi.type === 'boolean') &&\n oi.equalsTo !== undefined\n ) {\n enumValues.push(oi.equalsTo);\n } else {\n allConst = false;\n break;\n }\n }\n if (allConst) return { ...readOnly, enum: enumValues };\n return { ...readOnly, anyOf: options.map(convertNode) };\n }\n\n default:\n return {};\n }\n}\n\nfunction convertNode(schema: SchemaBuilder<any, any, any>): Out {\n const out = convertNodeInner(schema);\n const info = schema.introspect() as any;\n if (typeof info.description === 'string' && info.description !== '')\n out['description'] = info.description;\n return out;\n}\n\n/**\n * Converts a `@cleverbrush/schema` builder to a JSON Schema object.\n *\n * @remarks\n * **What round-trips cleanly**: all declarative constraints — type, format,\n * minLength/maxLength, minimum/maximum, multipleOf, pattern, required/optional\n * per property, additionalProperties, items, enum/const literals, anyOf/union.\n *\n * **What is silently omitted**:\n * - Custom validators added via `addValidator` (no JSON Schema equivalent)\n * - Preprocessors added via `addPreprocessor`\n * - JSDoc comments on schema properties\n * - `exclusiveMinimum`/`exclusiveMaximum` constraints from `fromJsonSchema`\n * (stored as custom validators — not introspectable; only `positive()`/\n * `negative()` extension-based exclusives are emitted)\n * - IP format with both v4 _and_ v6 allowed simultaneously (no single\n * standard JSON Schema format covers both; the `format` keyword is omitted\n * in that case)\n *\n * By default the output includes a `$schema` header for JSON Schema Draft\n * 2020-12. Pass `{ $schema: false }` when embedding the result in an OpenAPI\n * specification, or `{ draft: '07' }` for Draft 07 compatibility.\n *\n * @param schema - Any `@cleverbrush/schema` builder instance.\n * @param opts - Optional output configuration (see {@link ToJsonSchemaOptions}).\n * @returns A plain JSON-serialisable object representing the schema.\n *\n * @example\n * ```ts\n * import { toJsonSchema } from '@cleverbrush/schema-json';\n * import { object, string, number } from '@cleverbrush/schema';\n *\n * const UserSchema = object({\n * name: string().minLength(1),\n * email: string().email(),\n * age: number().optional(),\n * });\n *\n * const spec = toJsonSchema(UserSchema);\n * // {\n * // \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n * // \"type\": \"object\",\n * // \"properties\": {\n * // \"name\": { \"type\": \"string\", \"minLength\": 1 },\n * // \"email\": { \"type\": \"string\", \"format\": \"email\" },\n * // \"age\": { \"type\": \"number\" }\n * // },\n * // \"required\": [\"name\", \"email\"]\n * // }\n * ```\n *\n * @example\n * ```ts\n * // Embed in an OpenAPI spec (strip the $schema header)\n * toJsonSchema(schema, { $schema: false });\n *\n * // Use JSON Schema Draft 07\n * toJsonSchema(schema, { draft: '07' });\n * ```\n */\nexport function toJsonSchema(\n schema: SchemaBuilder<any, any, any>,\n opts?: ToJsonSchemaOptions\n): Record<string, unknown> {\n const body = convertNode(schema);\n if (opts?.$schema === false) return body;\n const draft = opts?.draft ?? '2020-12';\n const uri =\n draft === '07'\n ? 'http://json-schema.org/draft-07/schema#'\n : 'https://json-schema.org/draft/2020-12/schema';\n return { $schema: uri, ...body };\n}\n","import type { SchemaBuilder } from '@cleverbrush/schema';\nimport type { StandardJSONSchemaV1 } from '@standard-schema/spec';\nimport { toJsonSchema } from './toJsonSchema.js';\n\n/**\n * Maps Standard JSON Schema target identifiers to the options accepted by\n * {@link toJsonSchema}.\n */\nfunction targetToOptions(target: StandardJSONSchemaV1.Target): {\n draft: '2020-12' | '07';\n $schema: false;\n} {\n switch (target) {\n case 'draft-2020-12':\n return { draft: '2020-12', $schema: false };\n case 'draft-07':\n return { draft: '07', $schema: false };\n case 'openapi-3.0':\n // OpenAPI 3.0 is a superset of JSON Schema Draft 04; Draft 07 is\n // the closest we support.\n return { draft: '07', $schema: false };\n default:\n throw new Error(\n `@cleverbrush/schema-json: unsupported JSON Schema target \"${target}\".`\n );\n }\n}\n\n/**\n * Wraps a `@cleverbrush/schema` builder with the\n * [Standard JSON Schema v1](https://standardschema.dev/) interface.\n *\n * The returned schema object's `~standard` property is enriched with a\n * `jsonSchema` converter. Because `@cleverbrush/schema` does not\n * distinguish between input and output types, both `input()` and `output()`\n * produce the same JSON Schema document.\n *\n * **Note:** this mutates the schema instance by overriding the `~standard`\n * property. The returned reference is the same schema object.\n *\n * @example\n * ```ts\n * import { object, string, number } from '@cleverbrush/schema';\n * import { withStandardJsonSchema } from '@cleverbrush/schema-json';\n *\n * const schema = object({ name: string(), age: number().optional() });\n * const wrapped = withStandardJsonSchema(schema);\n *\n * // Access standard JSON Schema properties\n * wrapped['~standard'].jsonSchema.input({ target: 'draft-2020-12' });\n * wrapped['~standard'].jsonSchema.output({ target: 'draft-07' });\n * ```\n *\n * @param schema - Any `@cleverbrush/schema` builder instance.\n * @returns The same schema instance, now also conforming to `StandardJSONSchemaV1`.\n */\nexport function withStandardJsonSchema<\n T extends SchemaBuilder<any, any, any, any, any>\n>(schema: T): T & StandardJSONSchemaV1 {\n const converter: StandardJSONSchemaV1.Converter = {\n input(options: StandardJSONSchemaV1.Options): Record<string, unknown> {\n return toJsonSchema(schema, targetToOptions(options.target));\n },\n output(options: StandardJSONSchemaV1.Options): Record<string, unknown> {\n return toJsonSchema(schema, targetToOptions(options.target));\n }\n };\n\n // Extend the existing `~standard` props (which already include the\n // StandardSchemaV1 validate function from the schema library) with the\n // jsonSchema converter.\n const existingStandard = schema['~standard'];\n const enrichedStandard = { ...existingStandard, jsonSchema: converter };\n\n // Override the prototype getter with an instance-level property.\n // A Proxy wrapper was considered but JavaScript private fields (#field)\n // are bound to the original class instance; when a method is invoked\n // through a Proxy the receiver is the Proxy, not the real instance, so\n // private-field access throws a TypeError. defineProperty avoids this\n // because `this` inside methods still refers to the original schema.\n Object.defineProperty(schema, '~standard', {\n value: enrichedStandard,\n configurable: true,\n writable: false,\n enumerable: false\n });\n\n return schema as T & StandardJSONSchemaV1;\n}\n"],"mappings":"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","names":["any","array","boolean","nul","number","object","string","tuple","union","fail","msg","ok","buildNode","s","node","b","buildEnum","buildConst","buildAnyOf","buildString","buildNumber","buildArray","buildObject","val","format","v","integer","min","max","elements","props","requiredSet","key","propNode","propBuilder","values","first","rest","options","opt","fromJsonSchema","schema","escapeRegex","s","convertNodeInner","schema","info","ext","readOnly","out","ver","convertNode","elements","props","outProps","required","key","propSchema","options","enumValues","allConst","opt","oi","toJsonSchema","opts","body","targetToOptions","target","withStandardJsonSchema","schema","converter","options","toJsonSchema","enrichedStandard"]}
1
+ {"version":3,"sources":["../src/fromJsonSchema.ts","../src/toJsonSchema.ts","../src/standardJsonSchema.ts"],"sourcesContent":["import type { SchemaBuilder } from '@cleverbrush/schema';\nimport {\n any,\n array,\n boolean,\n nul,\n number,\n object,\n string,\n tuple,\n union\n} from '@cleverbrush/schema';\nimport type { JsonSchemaNodeToBuilder } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction fail(msg: string) {\n return { valid: false as const, errors: [{ message: msg }] };\n}\n\nfunction ok() {\n return { valid: true as const, errors: [] };\n}\n\nfunction buildNode(s: unknown): SchemaBuilder<any, any, any> {\n if (typeof s !== 'object' || s === null) return any();\n const node = s as Record<string, unknown>;\n\n let b: SchemaBuilder<any, any, any>;\n\n if ('enum' in node && Array.isArray(node['enum']))\n b = buildEnum(node['enum']);\n else if ('const' in node) b = buildConst(node['const']);\n else if ('anyOf' in node && Array.isArray(node['anyOf']))\n b = buildAnyOf(node['anyOf']);\n // allOf is not supported (no intersection builder); fall back to any()\n else if ('allOf' in node && Array.isArray(node['allOf'])) b = any();\n else if (!('type' in node)) b = any();\n else {\n switch (node['type']) {\n case 'string':\n b = buildString(node);\n break;\n case 'number':\n b = buildNumber(node, false);\n break;\n case 'integer':\n b = buildNumber(node, true);\n break;\n case 'boolean':\n b = boolean();\n break;\n case 'null':\n b = nul();\n break;\n case 'array':\n b = buildArray(node);\n break;\n case 'object':\n b = buildObject(node);\n break;\n default:\n b = any();\n }\n }\n\n if (node['readOnly'] === true) b = (b as any).readonly();\n if (typeof node['description'] === 'string' && node['description'])\n b = (b as any).describe(node['description']);\n return b;\n}\n\nfunction buildConst(val: unknown): SchemaBuilder<any, any, any> {\n if (typeof val === 'string') return string(val as any);\n if (typeof val === 'number') return number(val as any);\n if (typeof val === 'boolean') return boolean().equals(val);\n return any();\n}\n\nfunction buildString(\n node: Record<string, unknown>\n): SchemaBuilder<any, any, any> {\n let b: any = string();\n const format = node['format'] as string | undefined;\n\n if (format === 'email') {\n b = b.email();\n } else if (format === 'uuid') {\n b = b.uuid();\n } else if (format === 'uri' || format === 'url') {\n b = b.url();\n } else if (format === 'ipv4') {\n b = b.ip({ version: 'v4' });\n } else if (format === 'ipv6') {\n b = b.ip({ version: 'v6' });\n } else if (format === 'date-time') {\n b = b.addValidator((v: unknown) =>\n typeof v === 'string' && !Number.isNaN(Date.parse(v))\n ? ok()\n : fail('must be a valid date-time string')\n );\n }\n\n if (typeof node['minLength'] === 'number')\n b = b.minLength(node['minLength']);\n if (typeof node['maxLength'] === 'number')\n b = b.maxLength(node['maxLength']);\n if (typeof node['pattern'] === 'string') {\n try {\n b = b.matches(new RegExp(node['pattern']));\n } catch {\n // invalid regex pattern — silently ignore\n }\n }\n return b;\n}\n\nfunction buildNumber(\n node: Record<string, unknown>,\n integer: boolean\n): SchemaBuilder<any, any, any> {\n let b: any = integer ? number().isInteger() : number().isFloat();\n if (typeof node['minimum'] === 'number') b = b.min(node['minimum']);\n if (typeof node['maximum'] === 'number') b = b.max(node['maximum']);\n if (typeof node['multipleOf'] === 'number') {\n b = b.multipleOf(node['multipleOf']);\n }\n if (typeof node['exclusiveMinimum'] === 'number') {\n const min = node['exclusiveMinimum'];\n b = b.addValidator((v: unknown) =>\n typeof v === 'number' && v > min\n ? ok()\n : fail(`must be greater than ${min}`)\n );\n }\n if (typeof node['exclusiveMaximum'] === 'number') {\n const max = node['exclusiveMaximum'];\n b = b.addValidator((v: unknown) =>\n typeof v === 'number' && v < max\n ? ok()\n : fail(`must be less than ${max}`)\n );\n }\n return b;\n}\n\nfunction buildArray(\n node: Record<string, unknown>\n): SchemaBuilder<any, any, any> {\n // JSON Schema 2020-12 tuples use `prefixItems`\n if (Array.isArray(node['prefixItems'])) {\n const elements = (node['prefixItems'] as unknown[]).map(buildNode);\n let b: any = tuple(elements as any);\n // `items: false` means no extra elements (default for tuple)\n // `items: <schema>` means rest elements validated against that schema\n if (\n node['items'] !== undefined &&\n node['items'] !== false &&\n typeof node['items'] === 'object'\n ) {\n b = b.rest(buildNode(node['items']));\n }\n return b;\n }\n let b: any = array();\n if (node['items'] !== undefined) b = b.of(buildNode(node['items']));\n if (typeof node['minItems'] === 'number') b = b.minLength(node['minItems']);\n if (typeof node['maxItems'] === 'number') b = b.maxLength(node['maxItems']);\n return b;\n}\n\nfunction buildObject(\n node: Record<string, unknown>\n): SchemaBuilder<any, any, any> {\n const props = node['properties'] as Record<string, unknown> | undefined;\n const requiredSet = new Set<string>(\n Array.isArray(node['required']) ? (node['required'] as string[]) : []\n );\n let b: any = object();\n if (props) {\n for (const [key, propNode] of Object.entries(props)) {\n const propBuilder = buildNode(propNode);\n b = b.addProp(\n key,\n requiredSet.has(key)\n ? propBuilder.required()\n : propBuilder.optional()\n );\n }\n }\n if (node['additionalProperties'] !== false) b = b.acceptUnknownProps();\n return b;\n}\n\nfunction buildEnum(values: unknown[]): SchemaBuilder<any, any, any> {\n if (values.length === 0) return any();\n const [first, ...rest] = values;\n let b: any = union(buildConst(first));\n for (const v of rest) b = b.or(buildConst(v));\n return b;\n}\n\nfunction buildAnyOf(options: unknown[]): SchemaBuilder<any, any, any> {\n if (options.length === 0) return any();\n const [first, ...rest] = options;\n let b: any = union(buildNode(first));\n for (const opt of rest) b = b.or(buildNode(opt));\n return b;\n}\n\n/**\n * Converts a JSON Schema object into a `@cleverbrush/schema` builder.\n *\n * @remarks\n * **`as const` is required for precise TypeScript inference.** Without it\n * TypeScript widens string literals to `string` and the inferred builder\n * type collapses to `SchemaBuilder<unknown>`. Always pass the schema literal\n * (or the variable holding it) with `as const`.\n *\n * **Supported JSON Schema keywords**\n *\n * | Keyword | Builder equivalent |\n * | ------------------------------ | ------------------------------- |\n * | `type: 'string'` | `string()` |\n * | `type: 'number'` | `number()` |\n * | `type: 'integer'` | `number()` (integer flag) |\n * | `type: 'boolean'` | `boolean()` |\n * | `type: 'null'` | `SchemaBuilder<null>` |\n * | `type: 'array'` + `items` | `array(itemBuilder)` |\n * | `type: 'object'` + `properties`| `object({ … })` |\n * | `required: […]` | required / optional per-prop |\n * | `additionalProperties: true` | `.acceptUnknownProps()` |\n * | `const` | `string/number/boolean eq` |\n * | `enum` | `union(…)` |\n * | `anyOf` | `union(…)` |\n * | `minLength` / `maxLength` | `.minLength()` / `.maxLength()` |\n * | `pattern` | `.matches(regex)` (invalid patterns silently ignored) |\n * | `minimum` / `maximum` | `.min()` / `.max()` |\n * | `exclusiveMinimum` / `exclusiveMaximum` | custom validator (not round-trippable via `toJsonSchema`) |\n * | `multipleOf` | `.multipleOf()` |\n * | `minItems` / `maxItems` | `.minLength()` / `.maxLength()` |\n * | `format: 'email'` | `.email()` extension |\n * | `format: 'uuid'` | `.uuid()` extension |\n * | `format: 'uri'` / `'url'` | `.url()` extension |\n * | `format: 'ipv4'` | `.ip({ version: 'v4' })` |\n * | `format: 'ipv6'` | `.ip({ version: 'v6' })` |\n * | `format: 'date-time'` | `.matches(iso8601 regex)` |\n *\n * Keywords **not** supported: `allOf` (falls back to `any()`), `$ref`,\n * `$defs`, `if/then/else`, `not`, `contains`, `unevaluatedProperties`,\n * `contentEncoding`.\n *\n * @param schema - A JSON Schema literal. Pass with `as const` for precise\n * TypeScript type inference on the returned builder.\n * @returns A `@cleverbrush/schema` builder whose static type mirrors the\n * structure described by the JSON Schema node.\n *\n * @example\n * ```ts\n * import { fromJsonSchema } from '@cleverbrush/schema-json';\n *\n * const schema = fromJsonSchema({\n * type: 'object',\n * properties: {\n * name: { type: 'string', minLength: 1 },\n * score: { type: 'number', minimum: 0, maximum: 100 },\n * },\n * required: ['name'],\n * } as const);\n *\n * schema.parse({ name: 'Alice', score: 95 });\n * // TypeScript infers: { name: string; score?: number }\n * ```\n *\n * @example\n * ```ts\n * // Union types via `enum`\n * const statusSchema = fromJsonSchema({\n * enum: ['active', 'inactive', 'pending']\n * } as const);\n *\n * statusSchema.parse('active'); // valid — 'active' | 'inactive' | 'pending'\n * ```\n *\n * @example\n * ```ts\n * // Use InferFromJsonSchema to derive the TypeScript type statically\n * import type { InferFromJsonSchema } from '@cleverbrush/schema-json';\n *\n * const S = { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] } as const;\n * type Payload = InferFromJsonSchema<typeof S>; // { id: number }\n * const payloadSchema = fromJsonSchema(S);\n * ```\n */\nexport function fromJsonSchema<const S>(schema: S): JsonSchemaNodeToBuilder<S> {\n return buildNode(schema) as JsonSchemaNodeToBuilder<S>;\n}\n","import type { SchemaBuilder } from '@cleverbrush/schema';\nimport type { ToJsonSchemaOptions } from './types.js';\n\ntype Out = Record<string, unknown>;\n\nfunction escapeRegex(s: string): string {\n return s.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction escapeJsonPointerSegment(s: string): string {\n return s.replace(/~/g, '~0').replace(/\\//g, '~1');\n}\n\ntype Resolver =\n | ((schema: SchemaBuilder<any, any, any>) => string | null)\n | undefined;\n\nfunction convertNodeInner(\n schema: SchemaBuilder<any, any, any>,\n resolver: Resolver\n): Out {\n const info = schema.introspect() as any;\n const ext: Record<string, unknown> = info.extensions ?? {};\n const readOnly: Out = info.isReadonly === true ? { readOnly: true } : {};\n\n switch (info.type) {\n case 'string': {\n if (info.equalsTo !== undefined)\n return { ...readOnly, const: info.equalsTo };\n const out: Out = { ...readOnly, type: 'string' };\n if (ext['email'] === true) {\n out['format'] = 'email';\n } else if (ext['uuid'] === true) {\n out['format'] = 'uuid';\n } else if (ext['url']) {\n out['format'] = 'uri';\n } else if (ext['ip']) {\n const ver =\n typeof ext['ip'] === 'object'\n ? (ext['ip'] as any).version\n : undefined;\n if (ver === 'v4') out['format'] = 'ipv4';\n else if (ver === 'v6') out['format'] = 'ipv6';\n // both versions: no single standard JSON Schema format — omit\n }\n if (info.minLength !== undefined) out['minLength'] = info.minLength;\n if (info.maxLength !== undefined) out['maxLength'] = info.maxLength;\n // .nonempty() sets minLength via extension; if not already set\n if (ext['nonempty'] === true && out['minLength'] === undefined)\n out['minLength'] = 1;\n if (info.matches instanceof RegExp) {\n if (info.matches.flags !== '') {\n throw new Error(\n `Cannot convert RegExp /${info.matches.source}/${info.matches.flags} to JSON Schema pattern: RegExp flags are not representable in standard JSON Schema.`\n );\n }\n out['pattern'] = info.matches.source;\n } else if (info.startsWith !== undefined) {\n out['pattern'] = `^${escapeRegex(info.startsWith)}`;\n } else if (info.endsWith !== undefined) {\n out['pattern'] = `${escapeRegex(info.endsWith)}$`;\n }\n if (Array.isArray(ext['oneOf']) && ext['oneOf'].length > 0)\n out['enum'] = [...(ext['oneOf'] as unknown[])];\n return out;\n }\n\n case 'number': {\n if (info.equalsTo !== undefined)\n return { ...readOnly, const: info.equalsTo };\n const out: Out = {\n ...readOnly,\n type: info.isInteger ? 'integer' : 'number'\n };\n if (info.min !== undefined) out['minimum'] = info.min;\n if (info.max !== undefined) out['maximum'] = info.max;\n if (ext['multipleOf'] !== undefined)\n out['multipleOf'] = ext['multipleOf'];\n if (ext['positive'] === true) out['exclusiveMinimum'] = 0;\n if (ext['negative'] === true) out['exclusiveMaximum'] = 0;\n if (Array.isArray(ext['oneOf']) && ext['oneOf'].length > 0)\n out['enum'] = [...(ext['oneOf'] as unknown[])];\n return out;\n }\n\n case 'boolean': {\n if (info.equalsTo !== undefined)\n return { ...readOnly, const: info.equalsTo };\n return { ...readOnly, type: 'boolean' };\n }\n\n case 'date':\n return { ...readOnly, type: 'string', format: 'date-time' };\n\n case 'array': {\n const out: Out = { ...readOnly, type: 'array' };\n if (info.elementSchema)\n out['items'] = convertNode(info.elementSchema, resolver);\n if (info.minLength !== undefined) out['minItems'] = info.minLength;\n if (info.maxLength !== undefined) out['maxItems'] = info.maxLength;\n if (ext['nonempty'] === true && out['minItems'] === undefined)\n out['minItems'] = 1;\n return out;\n }\n\n case 'tuple': {\n const elements: SchemaBuilder<any, any, any>[] =\n info.elements ?? [];\n const out: Out = {\n type: 'array',\n prefixItems: elements.map(e => convertNode(e, resolver)),\n minItems: elements.length\n };\n if (info.restSchema) {\n out['items'] = convertNode(info.restSchema, resolver);\n } else {\n out['items'] = false;\n out['maxItems'] = elements.length;\n }\n return out;\n }\n\n case 'object': {\n const out: Out = { ...readOnly, type: 'object' };\n const props = info.properties as\n | Record<string, SchemaBuilder<any, any, any>>\n | undefined;\n if (props) {\n const outProps: Record<string, unknown> = {};\n const required: string[] = [];\n for (const [key, propSchema] of Object.entries(props)) {\n outProps[key] = convertNode(propSchema, resolver);\n if ((propSchema.introspect() as any).isRequired !== false)\n required.push(key);\n }\n out['properties'] = outProps;\n if (required.length > 0) out['required'] = required;\n }\n if (info.acceptUnknownProps === false)\n out['additionalProperties'] = false;\n return out;\n }\n\n case 'null':\n return { ...readOnly, type: 'null' };\n\n case 'union': {\n const options: SchemaBuilder<any, any, any>[] = info.options ?? [];\n const enumValues: unknown[] = [];\n let allConst = options.length > 0;\n for (const opt of options) {\n const oi = opt.introspect() as any;\n if (\n (oi.type === 'string' ||\n oi.type === 'number' ||\n oi.type === 'boolean') &&\n oi.equalsTo !== undefined\n ) {\n enumValues.push(oi.equalsTo);\n } else {\n allConst = false;\n break;\n }\n }\n if (allConst) return { ...readOnly, enum: enumValues };\n\n const converted = options.map(o => convertNode(o, resolver));\n const out: Out = {\n ...readOnly,\n anyOf: converted\n };\n\n // Emit discriminator keyword for discriminated unions\n const discriminatorProp: string | undefined =\n info.discriminatorPropertyName;\n if (discriminatorProp) {\n const disc: Out = { propertyName: discriminatorProp };\n // Only emit mapping when every option resolved to a $ref\n // and every discriminator value can be populated.\n const mapping: Record<string, string> = {};\n let allRefsMapped = options.length > 0;\n for (let i = 0; i < options.length; i++) {\n const ref = converted[i]['$ref'];\n if (typeof ref !== 'string') {\n allRefsMapped = false;\n break;\n }\n\n const oi = options[i].introspect() as any;\n const props:\n | Record<string, SchemaBuilder<any, any, any>>\n | undefined = oi.properties;\n const discriminatorSchema = props?.[discriminatorProp];\n if (!discriminatorSchema) {\n allRefsMapped = false;\n break;\n }\n\n const propInfo = discriminatorSchema.introspect() as any;\n if (propInfo.equalsTo === undefined) {\n allRefsMapped = false;\n break;\n }\n\n mapping[String(propInfo.equalsTo)] = ref;\n }\n if (allRefsMapped) disc['mapping'] = mapping;\n out['discriminator'] = disc;\n }\n\n return out;\n }\n\n case 'lazy': {\n // Resolve the lazy schema once and delegate conversion.\n // If the resolved schema has a name registered in the nameResolver,\n // convertNode will short-circuit to a $ref — breaking recursive cycles.\n // Recursive schemas without a registered name will cause infinite\n // recursion here; callers must use .schemaName() to break the cycle.\n const resolved: SchemaBuilder<any, any, any> = info.getter();\n return convertNode(resolved, resolver);\n }\n\n default:\n return {};\n }\n}\n\nfunction convertNode(\n schema: SchemaBuilder<any, any, any>,\n resolver: Resolver\n): Out {\n if (resolver) {\n const name = resolver(schema);\n if (typeof name === 'string' && name.length > 0) {\n return {\n $ref: `#/components/schemas/${escapeJsonPointerSegment(name)}`\n };\n }\n }\n const out = convertNodeInner(schema, resolver);\n const info = schema.introspect() as any;\n if (typeof info.description === 'string' && info.description !== '')\n out['description'] = info.description;\n\n // Emit example value if set\n if (info.example !== undefined) {\n out['examples'] = [info.example];\n }\n\n // Emit default for serializable primitives (not factory functions)\n if (\n info.hasDefault === true &&\n info.defaultValue !== undefined &&\n typeof info.defaultValue !== 'function'\n ) {\n out['default'] = info.defaultValue;\n }\n\n // Handle nullable — JSON Schema 2020-12 style: type becomes an array\n if (info.isNullable === true) {\n if (out['anyOf'] !== undefined) {\n // Union type — add { type: 'null' } to anyOf if not already present\n const anyOf = out['anyOf'] as Out[];\n const hasNull = anyOf.some(o => o['type'] === 'null');\n if (!hasNull) anyOf.push({ type: 'null' });\n } else if (out['enum'] !== undefined) {\n // Enum — add null to enum values if not already present\n const enumValues = out['enum'] as unknown[];\n if (!enumValues.includes(null)) out['enum'] = [...enumValues, null];\n } else if (typeof out['type'] === 'string') {\n // Simple type — make it an array: [\"string\", \"null\"]\n out['type'] = [out['type'], 'null'];\n } else if (out['const'] !== undefined) {\n // Const value — convert to oneOf with null\n const constVal = out['const'];\n delete out['const'];\n out['anyOf'] = [{ const: constVal }, { type: 'null' }];\n }\n }\n\n return out;\n}\n\n/**\n * Converts a `@cleverbrush/schema` builder to a JSON Schema object.\n *\n * @remarks\n * **What round-trips cleanly**: all declarative constraints — type, format,\n * minLength/maxLength, minimum/maximum, multipleOf, pattern, required/optional\n * per property, additionalProperties, items, enum/const literals, anyOf/union.\n *\n * **What is silently omitted**:\n * - Custom validators added via `addValidator` (no JSON Schema equivalent)\n * - Preprocessors added via `addPreprocessor`\n * - JSDoc comments on schema properties\n * - `exclusiveMinimum`/`exclusiveMaximum` constraints from `fromJsonSchema`\n * (stored as custom validators — not introspectable; only `positive()`/\n * `negative()` extension-based exclusives are emitted)\n * - IP format with both v4 _and_ v6 allowed simultaneously (no single\n * standard JSON Schema format covers both; the `format` keyword is omitted\n * in that case)\n *\n * By default the output includes a `$schema` header for JSON Schema Draft\n * 2020-12. Pass `{ $schema: false }` when embedding the result in an OpenAPI\n * specification, or `{ draft: '07' }` for Draft 07 compatibility.\n *\n * @param schema - Any `@cleverbrush/schema` builder instance.\n * @param opts - Optional output configuration (see {@link ToJsonSchemaOptions}).\n * @returns A plain JSON-serialisable object representing the schema.\n *\n * @example\n * ```ts\n * import { toJsonSchema } from '@cleverbrush/schema-json';\n * import { object, string, number } from '@cleverbrush/schema';\n *\n * const UserSchema = object({\n * name: string().minLength(1),\n * email: string().email(),\n * age: number().optional(),\n * });\n *\n * const spec = toJsonSchema(UserSchema);\n * // {\n * // \"$schema\": \"https://json-schema.org/draft/2020-12/schema\",\n * // \"type\": \"object\",\n * // \"properties\": {\n * // \"name\": { \"type\": \"string\", \"minLength\": 1 },\n * // \"email\": { \"type\": \"string\", \"format\": \"email\" },\n * // \"age\": { \"type\": \"number\" }\n * // },\n * // \"required\": [\"name\", \"email\"]\n * // }\n * ```\n *\n * @example\n * ```ts\n * // Embed in an OpenAPI spec (strip the $schema header)\n * toJsonSchema(schema, { $schema: false });\n *\n * // Use JSON Schema Draft 07\n * toJsonSchema(schema, { draft: '07' });\n * ```\n */\nexport function toJsonSchema(\n schema: SchemaBuilder<any, any, any, any, any>,\n opts?: ToJsonSchemaOptions\n): Record<string, unknown> {\n const body = convertNode(schema, opts?.nameResolver);\n if (opts?.$schema === false) return body;\n const draft = opts?.draft ?? '2020-12';\n const uri =\n draft === '07'\n ? 'http://json-schema.org/draft-07/schema#'\n : 'https://json-schema.org/draft/2020-12/schema';\n return { $schema: uri, ...body };\n}\n","import type { SchemaBuilder } from '@cleverbrush/schema';\nimport type { StandardJSONSchemaV1 } from '@standard-schema/spec';\nimport { toJsonSchema } from './toJsonSchema.js';\n\n/**\n * Maps Standard JSON Schema target identifiers to the options accepted by\n * {@link toJsonSchema}.\n */\nfunction targetToOptions(target: StandardJSONSchemaV1.Target): {\n draft: '2020-12' | '07';\n $schema: false;\n} {\n switch (target) {\n case 'draft-2020-12':\n return { draft: '2020-12', $schema: false };\n case 'draft-07':\n return { draft: '07', $schema: false };\n case 'openapi-3.0':\n // OpenAPI 3.0 is a superset of JSON Schema Draft 04; Draft 07 is\n // the closest we support.\n return { draft: '07', $schema: false };\n default:\n throw new Error(\n `@cleverbrush/schema-json: unsupported JSON Schema target \"${target}\".`\n );\n }\n}\n\n/**\n * Wraps a `@cleverbrush/schema` builder with the\n * [Standard JSON Schema v1](https://standardschema.dev/) interface.\n *\n * The returned schema object's `~standard` property is enriched with a\n * `jsonSchema` converter. Because `@cleverbrush/schema` does not\n * distinguish between input and output types, both `input()` and `output()`\n * produce the same JSON Schema document.\n *\n * **Note:** this mutates the schema instance by overriding the `~standard`\n * property. The returned reference is the same schema object.\n *\n * @example\n * ```ts\n * import { object, string, number } from '@cleverbrush/schema';\n * import { withStandardJsonSchema } from '@cleverbrush/schema-json';\n *\n * const schema = object({ name: string(), age: number().optional() });\n * const wrapped = withStandardJsonSchema(schema);\n *\n * // Access standard JSON Schema properties\n * wrapped['~standard'].jsonSchema.input({ target: 'draft-2020-12' });\n * wrapped['~standard'].jsonSchema.output({ target: 'draft-07' });\n * ```\n *\n * @param schema - Any `@cleverbrush/schema` builder instance.\n * @returns The same schema instance, now also conforming to `StandardJSONSchemaV1`.\n */\nexport function withStandardJsonSchema<\n T extends SchemaBuilder<any, any, any, any, any>\n>(schema: T): T & StandardJSONSchemaV1 {\n const converter: StandardJSONSchemaV1.Converter = {\n input(options: StandardJSONSchemaV1.Options): Record<string, unknown> {\n return toJsonSchema(schema, targetToOptions(options.target));\n },\n output(options: StandardJSONSchemaV1.Options): Record<string, unknown> {\n return toJsonSchema(schema, targetToOptions(options.target));\n }\n };\n\n // Extend the existing `~standard` props (which already include the\n // StandardSchemaV1 validate function from the schema library) with the\n // jsonSchema converter.\n const existingStandard = schema['~standard'];\n const enrichedStandard = { ...existingStandard, jsonSchema: converter };\n\n // Override the prototype getter with an instance-level property.\n // A Proxy wrapper was considered but JavaScript private fields (#field)\n // are bound to the original class instance; when a method is invoked\n // through a Proxy the receiver is the Proxy, not the real instance, so\n // private-field access throws a TypeError. defineProperty avoids this\n // because `this` inside methods still refers to the original schema.\n Object.defineProperty(schema, '~standard', {\n value: enrichedStandard,\n configurable: true,\n writable: false,\n enumerable: false\n });\n\n return schema as T & StandardJSONSchemaV1;\n}\n"],"mappings":"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","names":["any","array","boolean","nul","number","object","string","tuple","union","fail","msg","ok","buildNode","s","node","b","buildEnum","buildConst","buildAnyOf","buildString","buildNumber","buildArray","buildObject","val","format","v","integer","min","max","elements","props","requiredSet","key","propNode","propBuilder","values","first","rest","options","opt","fromJsonSchema","schema","escapeRegex","s","escapeJsonPointerSegment","convertNodeInner","schema","resolver","info","ext","readOnly","out","ver","convertNode","elements","e","props","outProps","required","key","propSchema","options","enumValues","allConst","opt","oi","converted","o","discriminatorProp","disc","mapping","allRefsMapped","i","ref","discriminatorSchema","propInfo","resolved","name","anyOf","constVal","toJsonSchema","opts","body","targetToOptions","target","withStandardJsonSchema","schema","converter","options","toJsonSchema","enrichedStandard"]}
@@ -60,4 +60,4 @@ import type { ToJsonSchemaOptions } from './types.js';
60
60
  * toJsonSchema(schema, { draft: '07' });
61
61
  * ```
62
62
  */
63
- export declare function toJsonSchema(schema: SchemaBuilder<any, any, any>, opts?: ToJsonSchemaOptions): Record<string, unknown>;
63
+ export declare function toJsonSchema(schema: SchemaBuilder<any, any, any, any, any>, opts?: ToJsonSchemaOptions): Record<string, unknown>;
package/dist/types.d.ts CHANGED
@@ -51,6 +51,10 @@ export type JsonSchemaNode = {
51
51
  [k: string]: unknown;
52
52
  } | {
53
53
  readonly anyOf: readonly JsonSchemaNode[];
54
+ readonly discriminator?: {
55
+ readonly propertyName: string;
56
+ readonly mapping?: Readonly<Record<string, string>>;
57
+ };
54
58
  [k: string]: unknown;
55
59
  } | {
56
60
  readonly allOf: readonly JsonSchemaNode[];
@@ -119,6 +123,25 @@ export type ToJsonSchemaOptions = {
119
123
  * @default true
120
124
  */
121
125
  $schema?: boolean;
126
+ /**
127
+ * Optional hook called for every schema node before conversion.
128
+ *
129
+ * When provided, the function receives each {@link SchemaBuilder} instance
130
+ * encountered during recursive conversion (including nested ones inside
131
+ * objects, arrays, and unions). If the function returns a non-null string,
132
+ * conversion of that node is short-circuited and a
133
+ * `{ $ref: '#/components/schemas/<name>' }` object is returned instead of
134
+ * the full inline JSON Schema.
135
+ *
136
+ * Return `null` to let conversion proceed normally.
137
+ *
138
+ * Primarily used by `@cleverbrush/server-openapi` to emit `$ref` pointers
139
+ * for schemas registered via `.schemaName()`.
140
+ *
141
+ * @param schema - The schema node currently being converted.
142
+ * @returns The component name to reference, or `null` to inline.
143
+ */
144
+ nameResolver?: (schema: import('@cleverbrush/schema').SchemaBuilder<any, any, any>) => string | null;
122
145
  };
123
146
  /**
124
147
  * Required variant: `ExtendedString<T>` (the return type of `string()`).
package/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "files": [
9
9
  "dist"
10
10
  ],
11
- "homepage": "https://docs.cleverbrush.com/modules/_cleverbrush_schema-json.html",
11
+ "homepage": "https://schema.cleverbrush.com/schema-json",
12
12
  "keywords": [
13
13
  "json schema",
14
14
  "schema",
@@ -26,7 +26,7 @@
26
26
  "sideEffects": false,
27
27
  "name": "@cleverbrush/schema-json",
28
28
  "peerDependencies": {
29
- "@cleverbrush/schema": "^2.0.0",
29
+ "@cleverbrush/schema": "^3.0.0",
30
30
  "@standard-schema/spec": "^1.1.0"
31
31
  },
32
32
  "readme": "https://github.com/cleverbrush/framework/tree/master/libs/schema-json#readme",
@@ -36,10 +36,10 @@
36
36
  },
37
37
  "scripts": {
38
38
  "watch": "tsc --build --watch",
39
- "build": "tsup && tsc --project tsconfig.build.json --emitDeclarationOnly",
39
+ "build": "tsup && rm -f tsconfig.build.tsbuildinfo && tsc --project tsconfig.build.json --emitDeclarationOnly",
40
40
  "clean": "rm -rf dist tsconfig.tsbuildinfo"
41
41
  },
42
42
  "type": "module",
43
43
  "types": "./dist/index.d.ts",
44
- "version": "2.0.0"
44
+ "version": "3.0.0"
45
45
  }