@cleverbrush/schema-json 0.0.0-beta-20260410073748

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 ADDED
@@ -0,0 +1,223 @@
1
+ # @cleverbrush/schema-json
2
+ <!-- coverage-badge-start -->
3
+ ![Coverage](https://img.shields.io/badge/coverage-95.5%25-brightgreen)
4
+ <!-- coverage-badge-end -->
5
+
6
+ Bidirectional JSON Schema (Draft 7 / 2020-12) interop for
7
+ [`@cleverbrush/schema`](../schema).
8
+
9
+ Import a JSON Schema and get a fully-typed `@cleverbrush/schema` builder that
10
+ preserves every constraint. Or convert a builder back to a JSON Schema object
11
+ for use in OpenAPI specs, form generators, or any other JSON Schema consumer.
12
+
13
+ ## When to use this library
14
+
15
+ - **Consuming external APIs** — you have a JSON Schema from an OpenAPI spec or
16
+ a third-party service and want to validate incoming data with full TypeScript
17
+ type inference.
18
+ - **OpenAPI / JSON Schema round-trip** — generate JSON Schemas from your
19
+ `@cleverbrush/schema` validators to embed in API specs.
20
+ - **Migrating from raw JSON Schema** — convert an existing schema catalogue to
21
+ `@cleverbrush/schema` builders incrementally.
22
+ - **Code generation** — introspect a JSON Schema at the type level via
23
+ `JsonSchemaNodeToBuilder<S>` without running any code.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ npm install @cleverbrush/schema-json
29
+ ```
30
+
31
+ > **Peer dependency:** `@cleverbrush/schema@^2.0.0` must already be installed.
32
+
33
+ ## Quick Start
34
+
35
+ ```ts
36
+ import { fromJsonSchema, toJsonSchema } from '@cleverbrush/schema-json';
37
+ import { object, string, number } from '@cleverbrush/schema';
38
+
39
+ // ── JSON Schema → builder ──────────────────────────────────────────────────
40
+ const PersonSchema = fromJsonSchema({
41
+ type: 'object',
42
+ properties: {
43
+ name: { type: 'string', minLength: 1 },
44
+ email: { type: 'string', format: 'email' },
45
+ age: { type: 'integer', minimum: 0 },
46
+ },
47
+ required: ['name', 'email'],
48
+ } as const); // ← `as const` is required for precise type inference
49
+
50
+ const result = PersonSchema.parse({ name: 'Alice', email: 'alice@example.com', age: 30 });
51
+ // result.object is typed as { name: string; email: string; age?: number }
52
+
53
+ // ── Builder → JSON Schema ──────────────────────────────────────────────────
54
+ const ApiSchema = object({
55
+ id: string().uuid(),
56
+ title: string().minLength(1).maxLength(255),
57
+ score: number().min(0).max(100).optional(),
58
+ });
59
+
60
+ const spec = toJsonSchema(ApiSchema);
61
+ // {
62
+ // "$schema": "https://json-schema.org/draft/2020-12/schema",
63
+ // "type": "object",
64
+ // "properties": {
65
+ // "id": { "type": "string", "format": "uuid" },
66
+ // "title": { "type": "string", "minLength": 1, "maxLength": 255 },
67
+ // "score": { "type": "number" }
68
+ // },
69
+ // "required": ["id", "title"]
70
+ // }
71
+ ```
72
+
73
+ ## API Reference
74
+
75
+ ### `fromJsonSchema(schema)`
76
+
77
+ Converts a JSON Schema literal to a `@cleverbrush/schema` builder.
78
+
79
+ ```ts
80
+ function fromJsonSchema<const S>(schema: S): JsonSchemaNodeToBuilder<S>
81
+ ```
82
+
83
+ | Parameter | Type | Description |
84
+ | --- | --- | --- |
85
+ | `schema` | JSON Schema literal | Pass with `as const` for precise TypeScript inference |
86
+
87
+ **Returns** a `@cleverbrush/schema` builder (e.g. `StringSchemaBuilder`,
88
+ `ObjectSchemaBuilder`, `UnionSchemaBuilder`, etc.) whose static type mirrors the
89
+ JSON Schema structure.
90
+
91
+ #### `as const` is required
92
+
93
+ Without `as const`, TypeScript widens string literals to `string` and object
94
+ shapes to `Record<string, unknown>`, so inference collapses to
95
+ `SchemaBuilder<unknown>`. Always annotate:
96
+
97
+ ```ts
98
+ const S = { type: 'object', properties: { x: { type: 'number' } } } as const;
99
+ const schema = fromJsonSchema(S); // ObjectSchemaBuilder<{ x: NumberSchemaBuilder<...> }>
100
+ ```
101
+
102
+ #### Supported JSON Schema keywords
103
+
104
+ | Keyword | Builder equivalent |
105
+ | ------------------------------------------ | ----------------------------------- |
106
+ | `type: 'string'` | `string()` |
107
+ | `type: 'number'` | `number()` |
108
+ | `type: 'integer'` | `number()` with integer flag |
109
+ | `type: 'boolean'` | `boolean()` |
110
+ | `type: 'null'` | `SchemaBuilder<null>` |
111
+ | `type: 'array'` + `items` | `array(itemBuilder)` |
112
+ | `type: 'object'` + `properties` | `object({ … })` |
113
+ | `required: […]` | required / optional per property |
114
+ | `additionalProperties: true` | `.acceptUnknownProps()` |
115
+ | `const` | literal builder (`.equals(...)`) |
116
+ | `enum` | `union(…)` of const builders |
117
+ | `anyOf` | `union(…)` of sub-builders |
118
+ | `allOf` | not supported — falls back to `any()` |
119
+ | `minLength` / `maxLength` | `.minLength()` / `.maxLength()` |
120
+ | `pattern` | `.matches(regex)` (invalid patterns silently ignored) |
121
+ | `minimum` / `maximum` | `.min()` / `.max()` |
122
+ | `exclusiveMinimum` / `exclusiveMaximum` | custom validator (not round-trippable via `toJsonSchema`) |
123
+ | `multipleOf` | `.multipleOf()` |
124
+ | `minItems` / `maxItems` | `.minLength()` / `.maxLength()` on array |
125
+ | `format: 'email'` | `.email()` extension |
126
+ | `format: 'uuid'` | `.uuid()` extension |
127
+ | `format: 'uri'` or `'url'` | `.url()` extension |
128
+ | `format: 'ipv4'` | `.ip({ version: 'v4' })` |
129
+ | `format: 'ipv6'` | `.ip({ version: 'v6' })` |
130
+ | `format: 'date-time'` | `.matches(iso8601 regex)` |
131
+ | `readOnly: true` | `.readonly()` |
132
+ | `description` | `.describe(text)` |
133
+
134
+ ---
135
+
136
+ ### `toJsonSchema(schema, opts?)`
137
+
138
+ Converts a `@cleverbrush/schema` builder to a JSON Schema object.
139
+
140
+ ```ts
141
+ function toJsonSchema(
142
+ schema: SchemaBuilder<any, any, any>,
143
+ opts?: ToJsonSchemaOptions,
144
+ ): Record<string, unknown>
145
+ ```
146
+
147
+ | Parameter | Type | Description |
148
+ | --- | --- | --- |
149
+ | `schema` | `SchemaBuilder` | any builder from `@cleverbrush/schema` |
150
+ | `opts` | `ToJsonSchemaOptions` | optional output configuration |
151
+
152
+ **Returns** a plain JavaScript object that is safe to `JSON.stringify`.
153
+
154
+ Descriptions set via `.describe(text)` are emitted as the `description` field on the corresponding JSON Schema node (including nested object properties).
155
+
156
+ #### `ToJsonSchemaOptions`
157
+
158
+ | Option | Type | Default | Description |
159
+ | --- | --- | --- | --- |
160
+ | `draft` | `'2020-12' \| '07'` | `'2020-12'` | JSON Schema draft version for the `$schema` URI |
161
+ | `$schema` | `boolean` | `true` | Whether to include the `$schema` header in the output |
162
+
163
+ ```ts
164
+ // Embed in OpenAPI (suppress the $schema header)
165
+ toJsonSchema(schema, { $schema: false });
166
+
167
+ // Use Draft 07
168
+ toJsonSchema(schema, { draft: '07' });
169
+ ```
170
+
171
+ ---
172
+
173
+ ## Type Utilities
174
+
175
+ ### `InferFromJsonSchema<S>`
176
+
177
+ Recursively derives the TypeScript value type from a statically-known JSON
178
+ Schema. Useful when you want the type without calling `fromJsonSchema` at
179
+ runtime.
180
+
181
+ ```ts
182
+ import type { InferFromJsonSchema } from '@cleverbrush/schema-json';
183
+
184
+ const S = {
185
+ type: 'object',
186
+ properties: {
187
+ id: { type: 'integer' },
188
+ label: { type: 'string' },
189
+ },
190
+ required: ['id'],
191
+ } as const;
192
+
193
+ type Item = InferFromJsonSchema<typeof S>;
194
+ // { id: number; label?: string }
195
+ ```
196
+
197
+ ### `JsonSchemaNodeToBuilder<S, TRequired?>`
198
+
199
+ Maps a statically-known JSON Schema literal to the `@cleverbrush/schema`
200
+ builder type — purely at the type level, no runtime code executed.
201
+
202
+ ```ts
203
+ import type { JsonSchemaNodeToBuilder } from '@cleverbrush/schema-json';
204
+
205
+ const S = { type: 'string', format: 'email' } as const;
206
+ type B = JsonSchemaNodeToBuilder<typeof S>;
207
+ // StringSchemaBuilder<string, true>
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Limitations
213
+
214
+ | Limitation | Notes |
215
+ | ----------------------------------------- | ------------------------------------------------------ |
216
+ | Custom validators (`addValidator`) | Not representable in JSON Schema — omitted in `toJsonSchema` output, not recoverable by `fromJsonSchema` |
217
+ | Preprocessors (`addPreprocessor`) | Same as above |
218
+ | `$ref` / `$defs` | Not supported in `fromJsonSchema` |
219
+ | `if` / `then` / `else` | Not supported |
220
+ | `not` | Not supported |
221
+ | `allOf` in `fromJsonSchema` | Falls back to `SchemaBuilder<unknown>` (no deep merge) |
222
+ | Dual IP format (`ip()` with both v4 + v6) | `format` is omitted in `toJsonSchema` output (no standard keyword covers both) |
223
+ | JSDoc comments on properties | Not preserved in `toJsonSchema` output |
@@ -0,0 +1,86 @@
1
+ import type { JsonSchemaNodeToBuilder } from './types.js';
2
+ /**
3
+ * Converts a JSON Schema object into a `@cleverbrush/schema` builder.
4
+ *
5
+ * @remarks
6
+ * **`as const` is required for precise TypeScript inference.** Without it
7
+ * TypeScript widens string literals to `string` and the inferred builder
8
+ * type collapses to `SchemaBuilder<unknown>`. Always pass the schema literal
9
+ * (or the variable holding it) with `as const`.
10
+ *
11
+ * **Supported JSON Schema keywords**
12
+ *
13
+ * | Keyword | Builder equivalent |
14
+ * | ------------------------------ | ------------------------------- |
15
+ * | `type: 'string'` | `string()` |
16
+ * | `type: 'number'` | `number()` |
17
+ * | `type: 'integer'` | `number()` (integer flag) |
18
+ * | `type: 'boolean'` | `boolean()` |
19
+ * | `type: 'null'` | `SchemaBuilder<null>` |
20
+ * | `type: 'array'` + `items` | `array(itemBuilder)` |
21
+ * | `type: 'object'` + `properties`| `object({ … })` |
22
+ * | `required: […]` | required / optional per-prop |
23
+ * | `additionalProperties: true` | `.acceptUnknownProps()` |
24
+ * | `const` | `string/number/boolean eq` |
25
+ * | `enum` | `union(…)` |
26
+ * | `anyOf` | `union(…)` |
27
+ * | `minLength` / `maxLength` | `.minLength()` / `.maxLength()` |
28
+ * | `pattern` | `.matches(regex)` (invalid patterns silently ignored) |
29
+ * | `minimum` / `maximum` | `.min()` / `.max()` |
30
+ * | `exclusiveMinimum` / `exclusiveMaximum` | custom validator (not round-trippable via `toJsonSchema`) |
31
+ * | `multipleOf` | `.multipleOf()` |
32
+ * | `minItems` / `maxItems` | `.minLength()` / `.maxLength()` |
33
+ * | `format: 'email'` | `.email()` extension |
34
+ * | `format: 'uuid'` | `.uuid()` extension |
35
+ * | `format: 'uri'` / `'url'` | `.url()` extension |
36
+ * | `format: 'ipv4'` | `.ip({ version: 'v4' })` |
37
+ * | `format: 'ipv6'` | `.ip({ version: 'v6' })` |
38
+ * | `format: 'date-time'` | `.matches(iso8601 regex)` |
39
+ *
40
+ * Keywords **not** supported: `allOf` (falls back to `any()`), `$ref`,
41
+ * `$defs`, `if/then/else`, `not`, `contains`, `unevaluatedProperties`,
42
+ * `contentEncoding`.
43
+ *
44
+ * @param schema - A JSON Schema literal. Pass with `as const` for precise
45
+ * TypeScript type inference on the returned builder.
46
+ * @returns A `@cleverbrush/schema` builder whose static type mirrors the
47
+ * structure described by the JSON Schema node.
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * import { fromJsonSchema } from '@cleverbrush/schema-json';
52
+ *
53
+ * const schema = fromJsonSchema({
54
+ * type: 'object',
55
+ * properties: {
56
+ * name: { type: 'string', minLength: 1 },
57
+ * score: { type: 'number', minimum: 0, maximum: 100 },
58
+ * },
59
+ * required: ['name'],
60
+ * } as const);
61
+ *
62
+ * schema.parse({ name: 'Alice', score: 95 });
63
+ * // TypeScript infers: { name: string; score?: number }
64
+ * ```
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * // Union types via `enum`
69
+ * const statusSchema = fromJsonSchema({
70
+ * enum: ['active', 'inactive', 'pending']
71
+ * } as const);
72
+ *
73
+ * statusSchema.parse('active'); // valid — 'active' | 'inactive' | 'pending'
74
+ * ```
75
+ *
76
+ * @example
77
+ * ```ts
78
+ * // Use InferFromJsonSchema to derive the TypeScript type statically
79
+ * import type { InferFromJsonSchema } from '@cleverbrush/schema-json';
80
+ *
81
+ * const S = { type: 'object', properties: { id: { type: 'integer' } }, required: ['id'] } as const;
82
+ * type Payload = InferFromJsonSchema<typeof S>; // { id: number }
83
+ * const payloadSchema = fromJsonSchema(S);
84
+ * ```
85
+ */
86
+ export declare function fromJsonSchema<const S>(schema: S): JsonSchemaNodeToBuilder<S>;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * `@cleverbrush/schema-json` — Bidirectional JSON Schema interop for `@cleverbrush/schema`.
3
+ *
4
+ * @example
5
+ * ```ts
6
+ * import { toJsonSchema, fromJsonSchema } from '@cleverbrush/schema-json';
7
+ * import { object, string, number } from '@cleverbrush/schema';
8
+ *
9
+ * // Schema → JSON Schema
10
+ * const spec = toJsonSchema(object({ name: string(), age: number().optional() }));
11
+ *
12
+ * // JSON Schema → Schema (use `as const` for type inference)
13
+ * const schema = fromJsonSchema({ type: 'object', properties: { name: { type: 'string' } }, required: ['name'] } as const);
14
+ * schema.parse({ name: 'Alice' }); // inferred as { name: string }
15
+ * ```
16
+ *
17
+ * @module
18
+ */
19
+ export type { StandardJSONSchemaV1, StandardTypedV1 } from '@standard-schema/spec';
20
+ export { fromJsonSchema } from './fromJsonSchema.js';
21
+ export { withStandardJsonSchema } from './standardJsonSchema.js';
22
+ export { toJsonSchema } from './toJsonSchema.js';
23
+ export type { InferFromJsonSchema, JsonSchemaNode, JsonSchemaNodeToBuilder, ToJsonSchemaOptions } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +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};
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,31 @@
1
+ import type { SchemaBuilder } from '@cleverbrush/schema';
2
+ import type { StandardJSONSchemaV1 } from '@standard-schema/spec';
3
+ /**
4
+ * Wraps a `@cleverbrush/schema` builder with the
5
+ * [Standard JSON Schema v1](https://standardschema.dev/) interface.
6
+ *
7
+ * The returned schema object's `~standard` property is enriched with a
8
+ * `jsonSchema` converter. Because `@cleverbrush/schema` does not
9
+ * distinguish between input and output types, both `input()` and `output()`
10
+ * produce the same JSON Schema document.
11
+ *
12
+ * **Note:** this mutates the schema instance by overriding the `~standard`
13
+ * property. The returned reference is the same schema object.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * import { object, string, number } from '@cleverbrush/schema';
18
+ * import { withStandardJsonSchema } from '@cleverbrush/schema-json';
19
+ *
20
+ * const schema = object({ name: string(), age: number().optional() });
21
+ * const wrapped = withStandardJsonSchema(schema);
22
+ *
23
+ * // Access standard JSON Schema properties
24
+ * wrapped['~standard'].jsonSchema.input({ target: 'draft-2020-12' });
25
+ * wrapped['~standard'].jsonSchema.output({ target: 'draft-07' });
26
+ * ```
27
+ *
28
+ * @param schema - Any `@cleverbrush/schema` builder instance.
29
+ * @returns The same schema instance, now also conforming to `StandardJSONSchemaV1`.
30
+ */
31
+ export declare function withStandardJsonSchema<T extends SchemaBuilder<any, any, any, any, any>>(schema: T): T & StandardJSONSchemaV1;
@@ -0,0 +1,63 @@
1
+ import type { SchemaBuilder } from '@cleverbrush/schema';
2
+ import type { ToJsonSchemaOptions } from './types.js';
3
+ /**
4
+ * Converts a `@cleverbrush/schema` builder to a JSON Schema object.
5
+ *
6
+ * @remarks
7
+ * **What round-trips cleanly**: all declarative constraints — type, format,
8
+ * minLength/maxLength, minimum/maximum, multipleOf, pattern, required/optional
9
+ * per property, additionalProperties, items, enum/const literals, anyOf/union.
10
+ *
11
+ * **What is silently omitted**:
12
+ * - Custom validators added via `addValidator` (no JSON Schema equivalent)
13
+ * - Preprocessors added via `addPreprocessor`
14
+ * - JSDoc comments on schema properties
15
+ * - `exclusiveMinimum`/`exclusiveMaximum` constraints from `fromJsonSchema`
16
+ * (stored as custom validators — not introspectable; only `positive()`/
17
+ * `negative()` extension-based exclusives are emitted)
18
+ * - IP format with both v4 _and_ v6 allowed simultaneously (no single
19
+ * standard JSON Schema format covers both; the `format` keyword is omitted
20
+ * in that case)
21
+ *
22
+ * By default the output includes a `$schema` header for JSON Schema Draft
23
+ * 2020-12. Pass `{ $schema: false }` when embedding the result in an OpenAPI
24
+ * specification, or `{ draft: '07' }` for Draft 07 compatibility.
25
+ *
26
+ * @param schema - Any `@cleverbrush/schema` builder instance.
27
+ * @param opts - Optional output configuration (see {@link ToJsonSchemaOptions}).
28
+ * @returns A plain JSON-serialisable object representing the schema.
29
+ *
30
+ * @example
31
+ * ```ts
32
+ * import { toJsonSchema } from '@cleverbrush/schema-json';
33
+ * import { object, string, number } from '@cleverbrush/schema';
34
+ *
35
+ * const UserSchema = object({
36
+ * name: string().minLength(1),
37
+ * email: string().email(),
38
+ * age: number().optional(),
39
+ * });
40
+ *
41
+ * const spec = toJsonSchema(UserSchema);
42
+ * // {
43
+ * // "$schema": "https://json-schema.org/draft/2020-12/schema",
44
+ * // "type": "object",
45
+ * // "properties": {
46
+ * // "name": { "type": "string", "minLength": 1 },
47
+ * // "email": { "type": "string", "format": "email" },
48
+ * // "age": { "type": "number" }
49
+ * // },
50
+ * // "required": ["name", "email"]
51
+ * // }
52
+ * ```
53
+ *
54
+ * @example
55
+ * ```ts
56
+ * // Embed in an OpenAPI spec (strip the $schema header)
57
+ * toJsonSchema(schema, { $schema: false });
58
+ *
59
+ * // Use JSON Schema Draft 07
60
+ * toJsonSchema(schema, { draft: '07' });
61
+ * ```
62
+ */
63
+ export declare function toJsonSchema(schema: SchemaBuilder<any, any, any>, opts?: ToJsonSchemaOptions): Record<string, unknown>;
@@ -0,0 +1,257 @@
1
+ /**
2
+ * Types for `@cleverbrush/schema-json`.
3
+ *
4
+ * @module
5
+ */
6
+ import type { BooleanSchemaBuilder, ExtendedArray, ExtendedNumber, ExtendedString, ObjectSchemaBuilder, SchemaBuilder, UnionSchemaBuilder } from '@cleverbrush/schema';
7
+ /**
8
+ * Subset of JSON Schema (Draft 7 / 2020-12) accepted as input.
9
+ *
10
+ * For accurate TypeScript type inference via {@link InferFromJsonSchema},
11
+ * pass schema literals with `as const`.
12
+ */
13
+ export type JsonSchemaNode = {
14
+ readonly type: 'string';
15
+ readonly format?: string;
16
+ readonly minLength?: number;
17
+ readonly maxLength?: number;
18
+ readonly pattern?: string;
19
+ [k: string]: unknown;
20
+ } | {
21
+ readonly type: 'number' | 'integer';
22
+ readonly minimum?: number;
23
+ readonly maximum?: number;
24
+ readonly exclusiveMinimum?: number;
25
+ readonly exclusiveMaximum?: number;
26
+ readonly multipleOf?: number;
27
+ [k: string]: unknown;
28
+ } | {
29
+ readonly type: 'boolean';
30
+ [k: string]: unknown;
31
+ } | {
32
+ readonly type: 'null';
33
+ [k: string]: unknown;
34
+ } | {
35
+ readonly type: 'array';
36
+ readonly items?: JsonSchemaNode;
37
+ readonly minItems?: number;
38
+ readonly maxItems?: number;
39
+ [k: string]: unknown;
40
+ } | {
41
+ readonly type: 'object';
42
+ readonly properties?: Readonly<Record<string, JsonSchemaNode>>;
43
+ readonly required?: readonly string[];
44
+ readonly additionalProperties?: boolean;
45
+ [k: string]: unknown;
46
+ } | {
47
+ readonly const: unknown;
48
+ [k: string]: unknown;
49
+ } | {
50
+ readonly enum: readonly unknown[];
51
+ [k: string]: unknown;
52
+ } | {
53
+ readonly anyOf: readonly JsonSchemaNode[];
54
+ [k: string]: unknown;
55
+ } | {
56
+ readonly allOf: readonly JsonSchemaNode[];
57
+ [k: string]: unknown;
58
+ } | Record<string, never>;
59
+ /**
60
+ * Recursively infers a TypeScript type from a statically-known JSON Schema node.
61
+ *
62
+ * Requires `as const` on the input object for precise inference —
63
+ * without it TypeScript widens string literals to `string` and inference
64
+ * collapses to `unknown`.
65
+ *
66
+ * @example
67
+ * ```ts
68
+ * type User = InferFromJsonSchema<typeof UserJsonSchema>;
69
+ * // { name: string; age?: number }
70
+ * ```
71
+ */
72
+ export type InferFromJsonSchema<S> = S extends {
73
+ readonly const: infer V;
74
+ } ? V : S extends {
75
+ readonly enum: readonly (infer V)[];
76
+ } ? V : S extends {
77
+ readonly type: 'string';
78
+ } ? string : S extends {
79
+ readonly type: 'number' | 'integer';
80
+ } ? number : S extends {
81
+ readonly type: 'boolean';
82
+ } ? boolean : S extends {
83
+ readonly type: 'null';
84
+ } ? null : S extends {
85
+ readonly type: 'array';
86
+ readonly items: infer I;
87
+ } ? InferFromJsonSchema<I>[] : S extends {
88
+ readonly type: 'array';
89
+ } ? unknown[] : S extends {
90
+ readonly type: 'object';
91
+ readonly properties: infer P;
92
+ readonly required: readonly (infer R extends string)[];
93
+ } ? {
94
+ [K in keyof P & string as K extends R ? K : never]: InferFromJsonSchema<P[K]>;
95
+ } & {
96
+ [K in keyof P & string as K extends R ? never : K]?: InferFromJsonSchema<P[K]>;
97
+ } : S extends {
98
+ readonly type: 'object';
99
+ readonly properties: infer P;
100
+ } ? {
101
+ [K in keyof P]?: InferFromJsonSchema<P[K]>;
102
+ } : S extends {
103
+ readonly type: 'object';
104
+ } ? Record<string, unknown> : S extends {
105
+ readonly anyOf: readonly (infer U)[];
106
+ } ? InferFromJsonSchema<U> : S extends {
107
+ readonly allOf: readonly JsonSchemaNode[];
108
+ } ? unknown : unknown;
109
+ /** Options accepted by {@link toJsonSchema}. */
110
+ export type ToJsonSchemaOptions = {
111
+ /**
112
+ * JSON Schema draft version to reference in the `$schema` header.
113
+ * @default '2020-12'
114
+ */
115
+ draft?: '2020-12' | '07';
116
+ /**
117
+ * Whether to include the `$schema` header in the output.
118
+ * Set to `false` when embedding in an OpenAPI spec.
119
+ * @default true
120
+ */
121
+ $schema?: boolean;
122
+ };
123
+ /**
124
+ * Required variant: `ExtendedString<T>` (the return type of `string()`).
125
+ * Optional variant: `ReturnType<ExtendedString<T>['optional']>` (the return
126
+ * type of `string().optional()`).
127
+ * @internal
128
+ */
129
+ type ExtendedStringBuilder<T extends string = string, TRequired extends boolean = true> = TRequired extends true ? ExtendedString<T> : ReturnType<ExtendedString<T>['optional']>;
130
+ /**
131
+ * Required variant: `ExtendedNumber<T>` (the return type of `number()`).
132
+ * Optional variant: `ReturnType<ExtendedNumber<T>['optional']>` (the return
133
+ * type of `number().optional()`).
134
+ * @internal
135
+ */
136
+ type ExtendedNumberBuilder<T extends number = number, TRequired extends boolean = true> = TRequired extends true ? ExtendedNumber<T> : ReturnType<ExtendedNumber<T>['optional']>;
137
+ /**
138
+ * Required variant: `ExtendedArray<TElement>` (the return type of `array()`).
139
+ * Optional variant: `ReturnType<ExtendedArray<TElement>['optional']>` (the
140
+ * return type of `array().optional()`).
141
+ * @internal
142
+ */
143
+ type ExtendedArrayBuilder<TElement extends SchemaBuilder<any, any, any> = SchemaBuilder<any, any, any>, TRequired extends boolean = true> = TRequired extends true ? ExtendedArray<TElement> : ReturnType<ExtendedArray<TElement>['optional']>;
144
+ /**
145
+ * Maps a JSON Schema `const` value to the narrowest possible builder type.
146
+ * @internal
147
+ */
148
+ type ConstToBuilder<V, TRequired extends boolean> = V extends string ? ExtendedStringBuilder<V, TRequired> : V extends number ? ExtendedNumberBuilder<V, TRequired> : V extends boolean ? BooleanSchemaBuilder<V, TRequired> : SchemaBuilder<V, TRequired>;
149
+ /**
150
+ * Recursively maps a readonly tuple of `const`/`enum` values to a tuple of
151
+ * builders, preserving the exact literal types of each element.
152
+ * @internal
153
+ */
154
+ type EnumTupleToBuilders<T extends readonly unknown[]> = T extends readonly [] ? [] : T extends readonly [infer First, ...infer Rest extends readonly unknown[]] ? [ConstToBuilder<First, true>, ...EnumTupleToBuilders<Rest>] : [SchemaBuilder<any, any, any>];
155
+ /**
156
+ * Recursively maps a readonly tuple of JSON Schema nodes (e.g. an `anyOf`
157
+ * array) to a tuple of builders for use with `UnionSchemaBuilder`.
158
+ * @internal
159
+ */
160
+ type SchemaNodesTupleToBuilders<T extends readonly unknown[]> = T extends readonly [] ? [] : T extends readonly [
161
+ infer First,
162
+ ...infer Rest extends readonly unknown[]
163
+ ] ? [
164
+ JsonSchemaNodeToBuilder<First, true>,
165
+ ...SchemaNodesTupleToBuilders<Rest>
166
+ ] : [SchemaBuilder<any, any, any>];
167
+ /**
168
+ * Maps an object schema's `properties` record to the property-descriptor
169
+ * record that `ObjectSchemaBuilder` expects as its first type parameter.
170
+ *
171
+ * Properties present in the `required` array get `TRequired = true`;
172
+ * all others get `TRequired = false`.
173
+ * @internal
174
+ */
175
+ type ObjectPropertiesToBuilders<P extends Record<string, unknown>, R extends string> = {
176
+ [K in keyof P as K extends R ? K : never]: JsonSchemaNodeToBuilder<P[K], true>;
177
+ } & {
178
+ [K in keyof P as K extends R ? never : K]: JsonSchemaNodeToBuilder<P[K], false>;
179
+ };
180
+ /**
181
+ * Recursively maps a statically-known JSON Schema node (passed with
182
+ * `as const`) to the exact `@cleverbrush/schema` builder type, including:
183
+ *
184
+ * - `StringSchemaBuilder`, `NumberSchemaBuilder`, `BooleanSchemaBuilder`
185
+ * for primitive types and `const` literals
186
+ * - `ArraySchemaBuilder<ElementBuilder>` with the element builder inferred
187
+ * from `items`
188
+ * - `ObjectSchemaBuilder<PropertyDescriptors>` where each property is a
189
+ * typed builder with `TRequired = true/false` driven by the `required`
190
+ * array
191
+ * - `UnionSchemaBuilder<[...Builders]>` for `enum` and `anyOf`
192
+ *
193
+ * @typeParam S - The JSON Schema node type (inferred from `as const`).
194
+ * @typeParam TRequired - Whether this node represents a required (`true`) or
195
+ * optional (`false`) value in its parent context. Defaults to `true`.
196
+ *
197
+ * @example
198
+ * ```ts
199
+ * import { fromJsonSchema } from '@cleverbrush/schema-json';
200
+ * import type { JsonSchemaNodeToBuilder } from '@cleverbrush/schema-json';
201
+ *
202
+ * const S = {
203
+ * type: 'object',
204
+ * properties: {
205
+ * name: { type: 'string' },
206
+ * score: { type: 'number' },
207
+ * },
208
+ * required: ['name'],
209
+ * } as const;
210
+ *
211
+ * type Builder = JsonSchemaNodeToBuilder<typeof S>;
212
+ * // ObjectSchemaBuilder<
213
+ * // { name: ExtendedStringBuilder<string, true> } &
214
+ * // { score: ExtendedNumberBuilder<number, false> }
215
+ * // >
216
+ *
217
+ * const schema = fromJsonSchema(S);
218
+ * // TypeScript knows schema is ObjectSchemaBuilder<...>
219
+ * // with full intellisense on .parse(), .addProp(), etc.
220
+ * ```
221
+ */
222
+ export type JsonSchemaNodeToBuilder<S, TRequired extends boolean = true> = S extends {
223
+ readonly const: infer V;
224
+ } ? ConstToBuilder<V, TRequired> : S extends {
225
+ readonly enum: infer Vals extends readonly unknown[];
226
+ } ? UnionSchemaBuilder<EnumTupleToBuilders<Vals>, TRequired> : S extends {
227
+ readonly anyOf: infer Opts extends readonly unknown[];
228
+ } ? UnionSchemaBuilder<SchemaNodesTupleToBuilders<Opts>, TRequired> : S extends {
229
+ readonly allOf: infer _Opts extends readonly unknown[];
230
+ } ? SchemaBuilder<unknown, TRequired> : S extends {
231
+ readonly type: 'string';
232
+ } ? ExtendedStringBuilder<string, TRequired> : S extends {
233
+ readonly type: 'number';
234
+ } ? ExtendedNumberBuilder<number, TRequired> : S extends {
235
+ readonly type: 'integer';
236
+ } ? ExtendedNumberBuilder<number, TRequired> : S extends {
237
+ readonly type: 'boolean';
238
+ } ? BooleanSchemaBuilder<boolean, TRequired> : S extends {
239
+ readonly type: 'null';
240
+ } ? SchemaBuilder<null, TRequired> : S extends {
241
+ readonly type: 'array';
242
+ readonly items: infer I;
243
+ } ? ExtendedArrayBuilder<JsonSchemaNodeToBuilder<I, true>, TRequired> : S extends {
244
+ readonly type: 'array';
245
+ } ? ExtendedArrayBuilder<SchemaBuilder<unknown, true>, TRequired> : S extends {
246
+ readonly type: 'object';
247
+ readonly properties: infer P extends Record<string, unknown>;
248
+ readonly required: readonly (infer R extends string)[];
249
+ } ? ObjectSchemaBuilder<ObjectPropertiesToBuilders<P, R>, TRequired> : S extends {
250
+ readonly type: 'object';
251
+ readonly properties: infer P extends Record<string, unknown>;
252
+ } ? ObjectSchemaBuilder<{
253
+ [K in keyof P]: JsonSchemaNodeToBuilder<P[K], false>;
254
+ }, TRequired> : S extends {
255
+ readonly type: 'object';
256
+ } ? ObjectSchemaBuilder<{}, TRequired> : SchemaBuilder<unknown, TRequired>;
257
+ export {};
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "author": "Andrew Zolotukhin <andrew_zol@cleverbrush.com>",
3
+ "bugs": {
4
+ "url": "https://github.com/cleverbrush/framework/issues",
5
+ "email": "andrew_zol@cleverbrush.com"
6
+ },
7
+ "description": "Bidirectional JSON Schema (Draft 7 / 2020-12) interop for @cleverbrush/schema",
8
+ "files": [
9
+ "dist"
10
+ ],
11
+ "homepage": "https://docs.cleverbrush.com/modules/_cleverbrush_schema-json.html",
12
+ "keywords": [
13
+ "json schema",
14
+ "schema",
15
+ "validation",
16
+ "cleverbrush"
17
+ ],
18
+ "license": "BSD 3-Clause",
19
+ "main": "./dist/index.js",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ }
25
+ },
26
+ "sideEffects": false,
27
+ "name": "@cleverbrush/schema-json",
28
+ "peerDependencies": {
29
+ "@cleverbrush/schema": "0.0.0-beta-20260410073748",
30
+ "@standard-schema/spec": "^1.1.0"
31
+ },
32
+ "readme": "https://github.com/cleverbrush/framework/tree/master/libs/schema-json#readme",
33
+ "repository": {
34
+ "type": "git",
35
+ "url": "github:cleverbrush/framework"
36
+ },
37
+ "scripts": {
38
+ "watch": "tsc --build --watch",
39
+ "build": "tsup && tsc --project tsconfig.build.json --emitDeclarationOnly",
40
+ "clean": "rm -rf dist tsconfig.tsbuildinfo"
41
+ },
42
+ "type": "module",
43
+ "types": "./dist/index.d.ts",
44
+ "version": "0.0.0-beta-20260410073748"
45
+ }