@cleverbrush/schema 2.0.0 → 3.0.1
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 +412 -8
- package/dist/builders/AnySchemaBuilder.js +1 -1
- package/dist/builders/ArraySchemaBuilder.d.ts +2 -0
- package/dist/builders/ArraySchemaBuilder.js +1 -1
- package/dist/builders/BooleanSchemaBuilder.d.ts +15 -0
- package/dist/builders/BooleanSchemaBuilder.js +1 -1
- package/dist/builders/DateSchemaBuilder.d.ts +20 -0
- package/dist/builders/DateSchemaBuilder.js +1 -1
- package/dist/builders/ExternSchemaBuilder.d.ts +2 -0
- package/dist/builders/ExternSchemaBuilder.js +1 -1
- package/dist/builders/FunctionSchemaBuilder.d.ts +162 -18
- package/dist/builders/FunctionSchemaBuilder.js +1 -1
- package/dist/builders/GenericSchemaBuilder.d.ts +294 -0
- package/dist/builders/LazySchemaBuilder.d.ts +2 -0
- package/dist/builders/NumberSchemaBuilder.d.ts +16 -0
- package/dist/builders/NumberSchemaBuilder.js +1 -1
- package/dist/builders/ObjectSchemaBuilder.d.ts +262 -42
- package/dist/builders/ObjectSchemaBuilder.js +1 -1
- package/dist/builders/ParseStringSchemaBuilder.d.ts +204 -0
- package/dist/builders/ParseStringSchemaBuilder.js +2 -0
- package/dist/builders/ParseStringSchemaBuilder.js.map +1 -0
- package/dist/builders/PromiseSchemaBuilder.d.ts +213 -0
- package/dist/builders/PromiseSchemaBuilder.js +2 -0
- package/dist/builders/PromiseSchemaBuilder.js.map +1 -0
- package/dist/builders/RecordSchemaBuilder.d.ts +2 -0
- package/dist/builders/RecordSchemaBuilder.js +1 -1
- package/dist/builders/SchemaBuilder.d.ts +89 -0
- package/dist/builders/StringSchemaBuilder.d.ts +2 -0
- package/dist/builders/StringSchemaBuilder.js +1 -1
- package/dist/builders/TupleSchemaBuilder.d.ts +2 -0
- package/dist/builders/TupleSchemaBuilder.js +1 -1
- package/dist/builders/UnionSchemaBuilder.d.ts +8 -0
- package/dist/builders/UnionSchemaBuilder.js +1 -1
- package/dist/chunk-3JMDGYDT.js +2 -0
- package/dist/chunk-3JMDGYDT.js.map +1 -0
- package/dist/{chunk-KESLT5EE.js → chunk-BUEVZ3KA.js} +2 -2
- package/dist/{chunk-KESLT5EE.js.map → chunk-BUEVZ3KA.js.map} +1 -1
- package/dist/{chunk-KFTO7TMB.js → chunk-CFIJQ4GP.js} +2 -2
- package/dist/{chunk-KFTO7TMB.js.map → chunk-CFIJQ4GP.js.map} +1 -1
- package/dist/chunk-DY7J6RNN.js +2 -0
- package/dist/chunk-DY7J6RNN.js.map +1 -0
- package/dist/{chunk-YWBNVHR6.js → chunk-EIVZX4ZO.js} +2 -2
- package/dist/{chunk-YWBNVHR6.js.map → chunk-EIVZX4ZO.js.map} +1 -1
- package/dist/chunk-GXPV6UQK.js +2 -0
- package/dist/chunk-GXPV6UQK.js.map +1 -0
- package/dist/{chunk-ZAN4ZHCJ.js → chunk-HN774HD7.js} +2 -2
- package/dist/chunk-K6Z47OQY.js +2 -0
- package/dist/chunk-K6Z47OQY.js.map +1 -0
- package/dist/chunk-NUW3VXZV.js +2 -0
- package/dist/chunk-NUW3VXZV.js.map +1 -0
- package/dist/{chunk-MKAYFPAR.js → chunk-PHE4LIAN.js} +2 -2
- package/dist/chunk-QARCEYGO.js +2 -0
- package/dist/chunk-QARCEYGO.js.map +1 -0
- package/dist/chunk-WDMJBGBD.js +2 -0
- package/dist/chunk-WDMJBGBD.js.map +1 -0
- package/dist/chunk-WQDYWDOE.js +2 -0
- package/dist/chunk-WQDYWDOE.js.map +1 -0
- package/dist/{chunk-FWUEUW2N.js → chunk-YQZHDMRF.js} +2 -2
- package/dist/chunk-ZC6YBKCP.js +2 -0
- package/dist/chunk-ZC6YBKCP.js.map +1 -0
- package/dist/{chunk-IJ6FO37G.js → chunk-ZFI27R3L.js} +2 -2
- package/dist/core.d.ts +7 -2
- package/dist/core.js +1 -1
- package/dist/extension.d.ts +10 -2
- package/dist/extensions/index.d.ts +5 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +12 -4
- package/dist/chunk-BFCMDDTX.js +0 -2
- package/dist/chunk-BFCMDDTX.js.map +0 -1
- package/dist/chunk-CTP4RHDG.js +0 -2
- package/dist/chunk-CTP4RHDG.js.map +0 -1
- package/dist/chunk-EUQ5AE3H.js +0 -2
- package/dist/chunk-EUQ5AE3H.js.map +0 -1
- package/dist/chunk-NXPH3ZUW.js +0 -2
- package/dist/chunk-NXPH3ZUW.js.map +0 -1
- package/dist/chunk-S5TC6NSU.js +0 -2
- package/dist/chunk-S5TC6NSU.js.map +0 -1
- package/dist/chunk-Z7QZ7IXT.js +0 -2
- package/dist/chunk-Z7QZ7IXT.js.map +0 -1
- package/dist/chunk-ZUPYV5TI.js +0 -2
- package/dist/chunk-ZUPYV5TI.js.map +0 -1
- /package/dist/{chunk-ZAN4ZHCJ.js.map → chunk-HN774HD7.js.map} +0 -0
- /package/dist/{chunk-MKAYFPAR.js.map → chunk-PHE4LIAN.js.map} +0 -0
- /package/dist/{chunk-FWUEUW2N.js.map → chunk-YQZHDMRF.js.map} +0 -0
- /package/dist/{chunk-IJ6FO37G.js.map → chunk-ZFI27R3L.js.map} +0 -0
package/README.md
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
[](https://github.com/cleverbrush/framework/actions/workflows/ci.yml)
|
|
4
4
|
[](https://standardschema.dev/)
|
|
5
5
|
<!-- bundle-badge-start -->
|
|
6
|
-
[](https://github.com/cleverbrush/framework/blob/master/libs/schema)
|
|
7
7
|
<!-- bundle-badge-end -->
|
|
8
8
|
[](../../LICENSE)
|
|
9
9
|
<!-- coverage-badge-start -->
|
|
10
|
-

|
|
11
11
|
<!-- coverage-badge-end -->
|
|
12
12
|
|
|
13
13
|
A schema definition and validation library for TypeScript — faster than Zod in 14/15 benchmarks (up to 204× faster on invalid input), 3× smaller than Zod v4, and compatible with 50+ ecosystem tools via [Standard Schema v1](https://standardschema.dev/).
|
|
@@ -126,10 +126,11 @@ The following builder functions are available:
|
|
|
126
126
|
| --------------- | ------------------------------------------------- | --------------------------------------------------------------- |
|
|
127
127
|
| `any()` | Any value. Similar to TypeScript's `any` type. | `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)`, `.addValidator(fn)` |
|
|
128
128
|
| `string()` | String value with constraints. | `.minLength(n)`, `.maxLength(n)`, `.matches(re)`, `.email()`, `.url()`, `.uuid()`, `.ip()`, `.trim()`, `.toLowerCase()`, `.nonempty()`, `.oneOf(...values)`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
129
|
-
| `number()` | Numeric value with constraints. | `.min(n)`, `.max(n)`, `.integer()`, `.positive()`, `.negative()`, `.finite()`, `.multipleOf(n)`, `.oneOf(...values)`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
130
|
-
| `boolean()` | Boolean value. | `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
131
|
-
| `date()` | JavaScript `Date` instance. | `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
132
|
-
| `func()` | Function value.
|
|
129
|
+
| `number()` | Numeric value with constraints. | `.min(n)`, `.max(n)`, `.integer()`, `.positive()`, `.negative()`, `.finite()`, `.multipleOf(n)`, `.coerce()`, `.oneOf(...values)`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
130
|
+
| `boolean()` | Boolean value. | `.coerce()`, `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
131
|
+
| `date()` | JavaScript `Date` instance. | `.coerce()`, `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
132
|
+
| `func()` | Function value. Supports typed parameter and return-type schemas. | `.addParameter(schema)`, `.hasReturnType(schema)`, `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
133
|
+
| `promise(schema?)` | JavaScript `Promise`. Optionally typed resolved value via `promise(schema)` or `.hasResolvedType(schema)`. | `.hasResolvedType(schema)`, `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
133
134
|
| `nul()` | Exactly `null`. Useful in nullable unions. | `.optional()`, `.default(value)` |
|
|
134
135
|
| `object(props)` | Object with typed properties. Supports nesting. | `.validate(data)`, `.addProps({...})`, `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
135
136
|
| `array()` | Array with optional element schema (via `.of()`). | `.minLength(n)`, `.maxLength(n)`, `.of(schema)`, `.nonempty()`, `.unique()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
@@ -138,6 +139,8 @@ The following builder functions are available:
|
|
|
138
139
|
| `union(schema)` | Union of schemas — e.g. `string \| number`. | `.or(schema)`, `.validate(data)`, `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
139
140
|
| `enumOf(...values)` | String enum — sugar for `string().oneOf(...)`. | `.optional()`, `.nullable()`, `.notNullable()`, `.default(value)` |
|
|
140
141
|
| `lazy(getter)` | Recursive/self-referential schema. The getter is called once and its result is cached. Enables tree structures, linked lists, and other recursive types. | `.resolve()`, `.optional()`, `.addValidator(fn)`, `.default(value)` |
|
|
142
|
+
| `generic(fn)` | Parameterized schema template. Call `.apply(...schemas)` with concrete schemas to obtain a fully typed concrete schema builder. TypeScript infers the result type from the template function's own generic signature. Optionally pass a `defaults` array as the first argument to enable direct validation without calling `.apply()`. | `.apply(...schemas)`, `.optional()`, `.nullable()`, `.default(value)` |
|
|
143
|
+
| `parseString(objectSchema, templateFn)` | Validates a string against a template pattern and parses it into a strongly-typed object. Property schemas handle their own coercion (e.g. `number().coerce()`). | `.optional()`, `.nullable()`, `.default(value)`, `.readonly()`, `.brand()` |
|
|
141
144
|
|
|
142
145
|
## Immutability
|
|
143
146
|
|
|
@@ -198,6 +201,224 @@ const IdOrEmail = union(string().minLength(1)).or(
|
|
|
198
201
|
);
|
|
199
202
|
```
|
|
200
203
|
|
|
204
|
+
## Generic Schemas
|
|
205
|
+
|
|
206
|
+
[▶ Open in Playground](https://docs.cleverbrush.com/playground/generic-basic)
|
|
207
|
+
|
|
208
|
+
Use `generic(fn)` to create a reusable, parameterized schema template. The template function accepts one or more schema builders as arguments and returns a concrete schema. Call `.apply(...schemas)` to instantiate the template — TypeScript infers the resulting type automatically from the function's own generic signature.
|
|
209
|
+
|
|
210
|
+
**Single type parameter** — a paginated list that works for any element type:
|
|
211
|
+
|
|
212
|
+
```typescript
|
|
213
|
+
import {
|
|
214
|
+
generic, object, array, number, string,
|
|
215
|
+
type SchemaBuilder, type InferType
|
|
216
|
+
} from '@cleverbrush/schema';
|
|
217
|
+
|
|
218
|
+
const PaginatedList = generic(
|
|
219
|
+
<T extends SchemaBuilder<any, any, any, any, any>>(itemSchema: T) =>
|
|
220
|
+
object({
|
|
221
|
+
items: array(itemSchema),
|
|
222
|
+
total: number(),
|
|
223
|
+
page: number(),
|
|
224
|
+
})
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
const PaginatedUsers = PaginatedList.apply(
|
|
228
|
+
object({ name: string(), age: number() })
|
|
229
|
+
);
|
|
230
|
+
|
|
231
|
+
type PaginatedUsersType = InferType<typeof PaginatedUsers>;
|
|
232
|
+
// → { items: { name: string; age: number }[]; total: number; page: number }
|
|
233
|
+
|
|
234
|
+
PaginatedUsers.validate({
|
|
235
|
+
items: [{ name: 'Alice', age: 30 }],
|
|
236
|
+
total: 1,
|
|
237
|
+
page: 1,
|
|
238
|
+
}); // { valid: true }
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
**Multiple type parameters** — a Result / Either type:
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import {
|
|
245
|
+
generic, object, boolean, string, number,
|
|
246
|
+
type SchemaBuilder, type InferType
|
|
247
|
+
} from '@cleverbrush/schema';
|
|
248
|
+
|
|
249
|
+
const Result = generic(
|
|
250
|
+
<
|
|
251
|
+
T extends SchemaBuilder<any, any, any, any, any>,
|
|
252
|
+
E extends SchemaBuilder<any, any, any, any, any>
|
|
253
|
+
>(
|
|
254
|
+
valueSchema: T,
|
|
255
|
+
errorSchema: E
|
|
256
|
+
) =>
|
|
257
|
+
object({
|
|
258
|
+
ok: boolean(),
|
|
259
|
+
value: valueSchema.optional(),
|
|
260
|
+
error: errorSchema.optional(),
|
|
261
|
+
})
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
const StringResult = Result.apply(string(), number());
|
|
265
|
+
// InferType → { ok: boolean; value?: string; error?: number }
|
|
266
|
+
|
|
267
|
+
StringResult.validate({ ok: true, value: 'hello' }); // valid
|
|
268
|
+
StringResult.validate({ ok: false, error: 404 }); // valid
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
**Default arguments** — pass a `defaults` array as the first argument so the template can be validated directly without calling `.apply()` first:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
import {
|
|
275
|
+
generic, object, array, number, any,
|
|
276
|
+
type SchemaBuilder
|
|
277
|
+
} from '@cleverbrush/schema';
|
|
278
|
+
|
|
279
|
+
const AnyList = generic(
|
|
280
|
+
[any()], // default for the single type parameter
|
|
281
|
+
<T extends SchemaBuilder<any, any, any, any, any>>(itemSchema: T) =>
|
|
282
|
+
object({ items: array(itemSchema), total: number() })
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
// Validate directly — uses the any() default
|
|
286
|
+
AnyList.validate({ items: [1, 'two', true], total: 3 }); // valid
|
|
287
|
+
|
|
288
|
+
// Or apply a stricter schema first
|
|
289
|
+
AnyList.apply(string()).validate({ items: ['a', 'b'], total: 2 }); // valid
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
> **Tip:** Each call to `.apply()` returns an independent schema builder. You can chain `.optional()`, `.addValidator()`, `.default(value)`, and every other fluent method on the result.
|
|
293
|
+
|
|
294
|
+
## Function Schemas
|
|
295
|
+
|
|
296
|
+
[▶ Open in Playground](https://docs.cleverbrush.com/playground/function-schema)
|
|
297
|
+
|
|
298
|
+
Use `func()` to validate that a value is a function. Two fluent methods let you annotate the expected signature — the inferred TypeScript type reflects both the parameter types and the return type:
|
|
299
|
+
|
|
300
|
+
- **`.addParameter(schema)`** — appends a parameter schema. Each call extends the inferred tuple of parameter types. The schemas are accessible at runtime via `introspect().parameters`.
|
|
301
|
+
- **`.hasReturnType(schema)`** — sets the return type schema. The inferred function type gains a concrete return type instead of `any`. Accessible via `introspect().returnType`.
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
import { func, string, number, boolean, InferType } from '@cleverbrush/schema';
|
|
305
|
+
|
|
306
|
+
// Plain function validator — accepts any () => any
|
|
307
|
+
const anyFn = func();
|
|
308
|
+
anyFn.validate(() => 42); // { valid: true }
|
|
309
|
+
anyFn.validate('not a fn'); // { valid: false }
|
|
310
|
+
|
|
311
|
+
// Typed parameter and return type
|
|
312
|
+
const greet = func()
|
|
313
|
+
.addParameter(string()) // first param: string
|
|
314
|
+
.addParameter(number().optional()) // second param: number | undefined
|
|
315
|
+
.hasReturnType(string()); // return type: string
|
|
316
|
+
|
|
317
|
+
// InferType preserves the declared parameter types and return type.
|
|
318
|
+
// Function schemas also remain compatible with callbacks of any arity.
|
|
319
|
+
type Greet = InferType<typeof greet>;
|
|
320
|
+
// → (param0: string, param1: number | undefined, ...args: any[]) => string
|
|
321
|
+
|
|
322
|
+
// Introspect at runtime
|
|
323
|
+
const info = greet.introspect();
|
|
324
|
+
// info.parameters → [StringSchemaBuilder, NumberSchemaBuilder]
|
|
325
|
+
// info.returnType → StringSchemaBuilder
|
|
326
|
+
|
|
327
|
+
// Optional function schemas infer a union with undefined
|
|
328
|
+
const optionalHandler = func()
|
|
329
|
+
.addParameter(string())
|
|
330
|
+
.addParameter(boolean())
|
|
331
|
+
.hasReturnType(boolean())
|
|
332
|
+
.optional();
|
|
333
|
+
|
|
334
|
+
type OptionalHandler = InferType<typeof optionalHandler>;
|
|
335
|
+
// → ((param0: string, param1: boolean, ...args: any[]) => boolean) | undefined
|
|
336
|
+
```
|
|
337
|
+
|
|
338
|
+
## Constructor Schemas
|
|
339
|
+
|
|
340
|
+
[▶ Open in Playground](https://docs.cleverbrush.com/playground/constructor-schema)
|
|
341
|
+
|
|
342
|
+
Use `.addConstructor(funcSchema)` on an `object()` schema to declare one or more constructor overloads. Each call appends a `FunctionSchemaBuilder` to an accumulated list; the inferred TypeScript type becomes an intersection of all construct signatures and the plain instance type.
|
|
343
|
+
|
|
344
|
+
- **`.addConstructor(funcSchema)`** — appends a constructor overload. Each call extends the inferred tuple of constructor schemas. Chainable.
|
|
345
|
+
- **`.clearConstructors()`** — resets constructor schemas to an empty list, removing all construct signatures from the inferred type.
|
|
346
|
+
- **`introspect().constructorSchemas`** — array of all accumulated constructor `FunctionSchemaBuilder` schemas.
|
|
347
|
+
|
|
348
|
+
Constructor signatures are **type-only**: runtime `validate()` continues to validate plain objects as before.
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
import { object, func, string, number, InferType } from '@cleverbrush/schema';
|
|
352
|
+
|
|
353
|
+
// Single constructor overload
|
|
354
|
+
const PersonSchema = object({ name: string(), age: number() })
|
|
355
|
+
.addConstructor(func().addParameter(string()));
|
|
356
|
+
|
|
357
|
+
type Person = InferType<typeof PersonSchema>;
|
|
358
|
+
// → { new(p0: string): { name: string; age: number } } & { name: string; age: number }
|
|
359
|
+
|
|
360
|
+
// Multiple chained constructors → overloaded construct signatures
|
|
361
|
+
const FlexPersonSchema = object({ name: string(), age: number() })
|
|
362
|
+
.addConstructor(func().addParameter(string()))
|
|
363
|
+
.addConstructor(func().addParameter(string()).addParameter(number()));
|
|
364
|
+
|
|
365
|
+
type FlexPerson = InferType<typeof FlexPersonSchema>;
|
|
366
|
+
// → { new(p0: string): { name: string; age: number } }
|
|
367
|
+
// & { new(p0: string, p1: number): { name: string; age: number } }
|
|
368
|
+
// & { name: string; age: number }
|
|
369
|
+
|
|
370
|
+
// Runtime validation is unchanged — plain objects still validate
|
|
371
|
+
FlexPersonSchema.validate({ name: 'Alice', age: 30 }); // { valid: true }
|
|
372
|
+
|
|
373
|
+
// Introspect constructor schemas at runtime
|
|
374
|
+
const { constructorSchemas } = FlexPersonSchema.introspect();
|
|
375
|
+
// constructorSchemas.length → 2
|
|
376
|
+
|
|
377
|
+
// Remove all constructor signatures
|
|
378
|
+
const PlainSchema = FlexPersonSchema.clearConstructors();
|
|
379
|
+
type Plain = InferType<typeof PlainSchema>;
|
|
380
|
+
// → { name: string; age: number }
|
|
381
|
+
```
|
|
382
|
+
|
|
383
|
+
## Promise Schemas
|
|
384
|
+
|
|
385
|
+
[▶ Open in Playground](https://docs.cleverbrush.com/playground/promise-schema)
|
|
386
|
+
|
|
387
|
+
Use `promise()` to validate that a value is a JavaScript `Promise`. Pass an optional schema to type the resolved value — the inferred TypeScript type becomes `Promise<T>`. You can also call `.hasResolvedType(schema)` on the returned builder to set or replace the resolved-value schema.
|
|
388
|
+
|
|
389
|
+
- **`promise(schema?)`** — factory shorthand. When `schema` is provided the inferred type is `Promise<InferType<typeof schema>>`.
|
|
390
|
+
- **`.hasResolvedType(schema)`** — sets the resolved-value schema; replaces any previously set one. Accessible via `introspect().resolvedType`.
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import { promise, string, number, InferType } from '@cleverbrush/schema';
|
|
394
|
+
|
|
395
|
+
// Untyped — validates that the value is any Promise
|
|
396
|
+
const anyPromise = promise();
|
|
397
|
+
anyPromise.validate(Promise.resolve(42)); // { valid: true }
|
|
398
|
+
anyPromise.validate('not a promise' as any); // { valid: false }
|
|
399
|
+
|
|
400
|
+
// Typed resolved value via factory argument
|
|
401
|
+
const stringPromise = promise(string());
|
|
402
|
+
type StringPromise = InferType<typeof stringPromise>;
|
|
403
|
+
// → Promise<string>
|
|
404
|
+
|
|
405
|
+
// Typed resolved value via fluent method
|
|
406
|
+
const numPromise = promise().hasResolvedType(number());
|
|
407
|
+
type NumPromise = InferType<typeof numPromise>;
|
|
408
|
+
// → Promise<number>
|
|
409
|
+
|
|
410
|
+
// Introspect at runtime
|
|
411
|
+
const info = numPromise.introspect();
|
|
412
|
+
// info.resolvedType → NumberSchemaBuilder
|
|
413
|
+
|
|
414
|
+
// Optional promise — undefined is also accepted
|
|
415
|
+
const optPromise = promise(string()).optional();
|
|
416
|
+
type OptPromise = InferType<typeof optPromise>;
|
|
417
|
+
// → Promise<string> | undefined
|
|
418
|
+
|
|
419
|
+
optPromise.validate(undefined as any); // { valid: true }
|
|
420
|
+
```
|
|
421
|
+
|
|
201
422
|
## Record Schemas
|
|
202
423
|
|
|
203
424
|
[▶ Open in Playground](https://docs.cleverbrush.com/playground/record-basics)
|
|
@@ -262,6 +483,86 @@ if (!result.valid) {
|
|
|
262
483
|
}
|
|
263
484
|
```
|
|
264
485
|
|
|
486
|
+
## Parse String Schemas
|
|
487
|
+
|
|
488
|
+
[▶ Open in Playground](https://docs.cleverbrush.com/playground/parse-string-basic)
|
|
489
|
+
|
|
490
|
+
Use `parseString(objectSchema, templateFn)` to validate a string against a template pattern and parse it into a strongly-typed object. The template expression uses a tagged-template syntax with type-safe property selectors — you get full IntelliSense in the selector lambdas.
|
|
491
|
+
|
|
492
|
+
```typescript
|
|
493
|
+
import { parseString, object, string, number, type InferType } from '@cleverbrush/schema';
|
|
494
|
+
|
|
495
|
+
const RouteSchema = parseString(
|
|
496
|
+
object({ userId: string().uuid(), id: number().coerce() }),
|
|
497
|
+
$t => $t`/orders/${t => t.id}/${t => t.userId}`
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
type Route = InferType<typeof RouteSchema>;
|
|
501
|
+
// { userId: string; id: number }
|
|
502
|
+
|
|
503
|
+
const result = RouteSchema.validate('/orders/42/550e8400-e29b-41d4-a716-446655440000');
|
|
504
|
+
// result.valid === true
|
|
505
|
+
// result.object === { id: 42, userId: '550e8400-...' }
|
|
506
|
+
```
|
|
507
|
+
|
|
508
|
+
**Nested objects** — navigate deep properties via `t => t.parent.child`:
|
|
509
|
+
|
|
510
|
+
```typescript
|
|
511
|
+
const schema = parseString(
|
|
512
|
+
object({
|
|
513
|
+
order: object({ id: number().coerce() }),
|
|
514
|
+
user: object({ name: string() })
|
|
515
|
+
}),
|
|
516
|
+
$t => $t`/orders/${t => t.order.id}/by/${t => t.user.name}`
|
|
517
|
+
);
|
|
518
|
+
// InferType → { order: { id: number }; user: { name: string } }
|
|
519
|
+
|
|
520
|
+
schema.validate('/orders/42/by/Alice');
|
|
521
|
+
// { valid: true, object: { order: { id: 42 }, user: { name: 'Alice' } } }
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
**Coercion** is the property schema's responsibility — the builder passes the raw captured substring directly to each property schema's `validate()`. Use `.coerce()` on `number()`, `boolean()`, or `date()` to convert from strings:
|
|
525
|
+
|
|
526
|
+
```typescript
|
|
527
|
+
const LogEntry = parseString(
|
|
528
|
+
object({
|
|
529
|
+
level: string(),
|
|
530
|
+
ts: date().coerce(),
|
|
531
|
+
message: string()
|
|
532
|
+
}),
|
|
533
|
+
$t => $t`[${t => t.level}] ${t => t.ts} ${t => t.message}`
|
|
534
|
+
);
|
|
535
|
+
```
|
|
536
|
+
|
|
537
|
+
Error messages include the property path for easy debugging:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
const result = RouteSchema.validate('/orders/abc/bad-uuid');
|
|
541
|
+
// result.errors[0].message → "id: expected an integer number"
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
## Coercion
|
|
545
|
+
|
|
546
|
+
The `number()`, `boolean()`, and `date()` builders each have a `.coerce()` method that adds a preprocessor to convert string values to the target type. This is especially useful with parse-string schemas where captured segments are always strings, but also works standalone for URL parameters, form inputs, or any other string source.
|
|
547
|
+
|
|
548
|
+
```typescript
|
|
549
|
+
import { number, boolean, date } from '@cleverbrush/schema';
|
|
550
|
+
|
|
551
|
+
// number().coerce() — uses Number(value)
|
|
552
|
+
number().coerce().validate('42'); // { valid: true, object: 42 }
|
|
553
|
+
number().coerce().validate('hello'); // { valid: false } — NaN fails
|
|
554
|
+
|
|
555
|
+
// boolean().coerce() — "true" → true, "false" → false
|
|
556
|
+
boolean().coerce().validate('true'); // { valid: true, object: true }
|
|
557
|
+
boolean().coerce().validate('yes'); // { valid: false } — unrecognized
|
|
558
|
+
|
|
559
|
+
// date().coerce() — new Date(value) if valid
|
|
560
|
+
date().coerce().validate('2024-01-15'); // { valid: true, object: Date }
|
|
561
|
+
date().coerce().validate('nope'); // { valid: false } — invalid date
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
Non-string values pass through unchanged, so `.coerce()` is safe to chain even when the input might already be the correct type. All three methods return a new immutable schema instance.
|
|
565
|
+
|
|
265
566
|
## Recursive Schemas
|
|
266
567
|
|
|
267
568
|
[▶ Open in Playground](https://docs.cleverbrush.com/playground/recursive-schemas)
|
|
@@ -596,6 +897,43 @@ const SignupSchema = object({
|
|
|
596
897
|
});
|
|
597
898
|
```
|
|
598
899
|
|
|
900
|
+
### Property-Targeted Validator Errors
|
|
901
|
+
|
|
902
|
+
By default, errors returned from object-level validators are attached to the root object. You can target an error to a specific property by providing a `property` selector — the same selector used by `getErrorsFor()` and react-form's `forProperty`:
|
|
903
|
+
|
|
904
|
+
```typescript
|
|
905
|
+
const SignupSchema = object({
|
|
906
|
+
password: string().minLength(8),
|
|
907
|
+
confirmPassword: string().minLength(8)
|
|
908
|
+
}).addValidator((value) => {
|
|
909
|
+
if (value.password !== value.confirmPassword) {
|
|
910
|
+
return {
|
|
911
|
+
valid: false,
|
|
912
|
+
errors: [{
|
|
913
|
+
message: 'Passwords do not match',
|
|
914
|
+
property: (t) => t.confirmPassword
|
|
915
|
+
}]
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
return { valid: true };
|
|
919
|
+
});
|
|
920
|
+
|
|
921
|
+
const result = SignupSchema.validate(
|
|
922
|
+
{ password: 'secret1', confirmPassword: 'secret2' },
|
|
923
|
+
{ doNotStopOnFirstError: true }
|
|
924
|
+
);
|
|
925
|
+
|
|
926
|
+
// Error is routed to confirmPassword:
|
|
927
|
+
result.getErrorsFor((t) => t.confirmPassword).errors;
|
|
928
|
+
// → ['Passwords do not match']
|
|
929
|
+
|
|
930
|
+
// Other properties are unaffected:
|
|
931
|
+
result.getErrorsFor((t) => t.password).errors;
|
|
932
|
+
// → []
|
|
933
|
+
```
|
|
934
|
+
|
|
935
|
+
You can target multiple properties from a single validator by returning multiple errors with different `property` selectors. Errors without a `property` selector are attached to the root object as before.
|
|
936
|
+
|
|
599
937
|
### Per-Property Errors with `getErrorsFor()` (Recommended)
|
|
600
938
|
|
|
601
939
|
`ObjectSchemaBuilder.validate()` returns an extended result with a `getErrorsFor()` method for inspecting errors on individual properties — perfect for showing inline form errors. **This is the recommended way to inspect validation errors on object schemas** and replaces the deprecated `errors` array on `ObjectSchemaValidationResult`:
|
|
@@ -700,6 +1038,28 @@ console.log(info.hasDefault); // true
|
|
|
700
1038
|
console.log(info.defaultValue); // 'hello'
|
|
701
1039
|
```
|
|
702
1040
|
|
|
1041
|
+
## Examples
|
|
1042
|
+
|
|
1043
|
+
Attach example values to any schema with `.example(value)`. The example is typed to the schema's result type, so the compiler catches mismatches. Examples are purely metadata — validation is unaffected.
|
|
1044
|
+
|
|
1045
|
+
```typescript
|
|
1046
|
+
import { string, number, object } from '@cleverbrush/schema';
|
|
1047
|
+
|
|
1048
|
+
const Email = string().example('user@example.com');
|
|
1049
|
+
const Age = number().example(30);
|
|
1050
|
+
|
|
1051
|
+
const User = object({
|
|
1052
|
+
email: Email,
|
|
1053
|
+
age: Age,
|
|
1054
|
+
}).example({ email: 'alice@example.com', age: 25 });
|
|
1055
|
+
```
|
|
1056
|
+
|
|
1057
|
+
Examples are exposed via `.introspect()` and consumed by `@cleverbrush/schema-json` (emitted as the JSON Schema `examples` keyword) and `@cleverbrush/server-openapi` (pre-fills parameter and response schemas in generated specs).
|
|
1058
|
+
|
|
1059
|
+
```typescript
|
|
1060
|
+
Email.introspect().example; // 'user@example.com'
|
|
1061
|
+
```
|
|
1062
|
+
|
|
703
1063
|
## Catch / Fallback
|
|
704
1064
|
|
|
705
1065
|
Every schema builder supports `.catch(value)`. When validation **fails for any reason** — wrong type, constraint violation, missing required value — the fallback is returned as a successful result instead of errors.
|
|
@@ -790,6 +1150,50 @@ console.log(schema.introspect().isReadonly); // true
|
|
|
790
1150
|
|
|
791
1151
|
> **Note:** `.readonly()` is **shallow** — only top-level object properties or the array itself are marked readonly. For deeply nested immutability consider applying `.readonly()` at each level, or use a `DeepReadonly` utility type post-validation.
|
|
792
1152
|
|
|
1153
|
+
## schemaName
|
|
1154
|
+
|
|
1155
|
+
Every schema builder supports `.schemaName(name)`. This is a **metadata-only** modifier — it attaches a component name to the schema for use by OpenAPI tooling. It has no effect on validation or type inference.
|
|
1156
|
+
|
|
1157
|
+
```typescript
|
|
1158
|
+
import { object, string, number } from '@cleverbrush/schema';
|
|
1159
|
+
|
|
1160
|
+
export const UserSchema = object({
|
|
1161
|
+
id: number(),
|
|
1162
|
+
name: string(),
|
|
1163
|
+
}).schemaName('User');
|
|
1164
|
+
|
|
1165
|
+
// Accessible at runtime
|
|
1166
|
+
UserSchema.introspect().schemaName; // 'User'
|
|
1167
|
+
```
|
|
1168
|
+
|
|
1169
|
+
Chains naturally with all other modifiers:
|
|
1170
|
+
|
|
1171
|
+
```typescript
|
|
1172
|
+
const ProductSchema = object({
|
|
1173
|
+
sku: string().nonempty(),
|
|
1174
|
+
price: number().min(0),
|
|
1175
|
+
})
|
|
1176
|
+
.schemaName('Product')
|
|
1177
|
+
.describe('A product in the catalogue');
|
|
1178
|
+
```
|
|
1179
|
+
|
|
1180
|
+
When used with [`@cleverbrush/server-openapi`](../server-openapi), any schema that carries a `schemaName` is automatically extracted into `components/schemas` and all usages in the document are replaced with `$ref` pointers — eliminating repeated inline definitions:
|
|
1181
|
+
|
|
1182
|
+
```typescript
|
|
1183
|
+
import { generateOpenApiSpec } from '@cleverbrush/server-openapi';
|
|
1184
|
+
|
|
1185
|
+
// UserSchema is emitted once under components.schemas.User
|
|
1186
|
+
// Every endpoint that references it gets: { $ref: '#/components/schemas/User' }
|
|
1187
|
+
generateOpenApiSpec({ registrations, info: { title: 'My API', version: '1.0.0' } });
|
|
1188
|
+
```
|
|
1189
|
+
|
|
1190
|
+
> **Name uniqueness:** Registering two *different* schema instances under the same name throws an error. Always export named schemas as constants and reuse the same reference everywhere.
|
|
1191
|
+
|
|
1192
|
+
| Method / Property | Signature | Notes |
|
|
1193
|
+
|---|---|---|
|
|
1194
|
+
| `.schemaName(name)` | `schemaName(name: string): this` | Returns a new builder; original is unchanged |
|
|
1195
|
+
| `.introspect().schemaName` | `string \| undefined` | The name passed to `.schemaName()`, or `undefined` |
|
|
1196
|
+
|
|
793
1197
|
## Describe
|
|
794
1198
|
|
|
795
1199
|
Every schema builder supports `.describe(text)`. This is a **metadata-only** modifier — it stores a human-readable description on the schema at runtime with no effect on validation.
|
|
@@ -1255,9 +1659,9 @@ Define a schema once and use it for runtime validation, object mapping between d
|
|
|
1255
1659
|
|
|
1256
1660
|
## Exports
|
|
1257
1661
|
|
|
1258
|
-
**Builder functions:** `any`, `lazy`, `string`, `number`, `boolean`, `func`, `object`, `date`, `array`, `union`
|
|
1662
|
+
**Builder functions:** `any`, `lazy`, `string`, `number`, `boolean`, `func`, `promise`, `object`, `date`, `array`, `union`, `parseString`
|
|
1259
1663
|
|
|
1260
|
-
**Builder classes** (for extending): `SchemaBuilder`, `AnySchemaBuilder`, `ArraySchemaBuilder`, `BooleanSchemaBuilder`, `DateSchemaBuilder`, `FunctionSchemaBuilder`, `LazySchemaBuilder`, `NumberSchemaBuilder`, `ObjectSchemaBuilder`, `StringSchemaBuilder`, `UnionSchemaBuilder`
|
|
1664
|
+
**Builder classes** (for extending): `SchemaBuilder`, `AnySchemaBuilder`, `ArraySchemaBuilder`, `BooleanSchemaBuilder`, `DateSchemaBuilder`, `FunctionSchemaBuilder`, `ParseStringSchemaBuilder`, `LazySchemaBuilder`, `NumberSchemaBuilder`, `ObjectSchemaBuilder`, `PromiseSchemaBuilder`, `StringSchemaBuilder`, `UnionSchemaBuilder`
|
|
1261
1665
|
|
|
1262
1666
|
**Extension system:** `defineExtension`, `withExtensions`, `stringExtensions`, `numberExtensions`, `arrayExtensions`, `nullableExtension`
|
|
1263
1667
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a,b}from"../chunk-
|
|
1
|
+
import{a,b}from"../chunk-HN774HD7.js";import"../chunk-3JMDGYDT.js";export{a as AnySchemaBuilder,b as any};
|
|
2
2
|
//# sourceMappingURL=AnySchemaBuilder.js.map
|
|
@@ -147,8 +147,10 @@ export declare class ArraySchemaBuilder<TElementSchema extends SchemaBuilder<any
|
|
|
147
147
|
hasDefault: boolean;
|
|
148
148
|
defaultValue: TResult | (() => TResult) | undefined;
|
|
149
149
|
description: string | undefined;
|
|
150
|
+
schemaName: string | undefined;
|
|
150
151
|
hasCatch: boolean;
|
|
151
152
|
catchValue: TResult | (() => TResult) | undefined;
|
|
153
|
+
example: unknown;
|
|
152
154
|
};
|
|
153
155
|
/**
|
|
154
156
|
* Set a schema that every array item has to satisfy. If it is not set,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a,b}from"../chunk-
|
|
1
|
+
import{a,b}from"../chunk-NUW3VXZV.js";import"../chunk-3JMDGYDT.js";export{a as ArraySchemaBuilder,b as array};
|
|
2
2
|
//# sourceMappingURL=ArraySchemaBuilder.js.map
|
|
@@ -60,8 +60,10 @@ export declare class BooleanSchemaBuilder<TResult = boolean, TRequired extends b
|
|
|
60
60
|
hasDefault: boolean;
|
|
61
61
|
defaultValue: TFinalResult | (() => TFinalResult) | undefined;
|
|
62
62
|
description: string | undefined;
|
|
63
|
+
schemaName: string | undefined;
|
|
63
64
|
hasCatch: boolean;
|
|
64
65
|
catchValue: TFinalResult | (() => TFinalResult) | undefined;
|
|
66
|
+
example: unknown;
|
|
65
67
|
};
|
|
66
68
|
/**
|
|
67
69
|
* @inheritdoc
|
|
@@ -130,6 +132,19 @@ export declare class BooleanSchemaBuilder<TResult = boolean, TRequired extends b
|
|
|
130
132
|
* Removes a `value` defined by `equals()` call.
|
|
131
133
|
*/
|
|
132
134
|
clearEquals(): BooleanSchemaBuilder<boolean, TRequired, TNullable, TExplicitType, THasDefault, TExtensions> & TExtensions;
|
|
135
|
+
/**
|
|
136
|
+
* Adds a preprocessor that coerces a string value to a boolean.
|
|
137
|
+
* Accepts `"true"` → `true` and `"false"` → `false`; other values are
|
|
138
|
+
* left unchanged so the boolean schema rejects them.
|
|
139
|
+
*
|
|
140
|
+
* @example ```ts
|
|
141
|
+
* const schema = boolean().coerce();
|
|
142
|
+
* const result = schema.validate('true');
|
|
143
|
+
* // result.valid === true
|
|
144
|
+
* // result.object === true
|
|
145
|
+
* ```
|
|
146
|
+
*/
|
|
147
|
+
coerce(): BooleanSchemaBuilder<TResult, TRequired, TNullable, TExplicitType, THasDefault, TExtensions> & TExtensions;
|
|
133
148
|
/**
|
|
134
149
|
* @hidden
|
|
135
150
|
*/
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a,b}from"../chunk-
|
|
1
|
+
import{a,b}from"../chunk-CFIJQ4GP.js";import"../chunk-3JMDGYDT.js";export{a as BooleanSchemaBuilder,b as boolean};
|
|
2
2
|
//# sourceMappingURL=BooleanSchemaBuilder.js.map
|
|
@@ -122,8 +122,10 @@ export declare class DateSchemaBuilder<TResult = Date, TRequired extends boolean
|
|
|
122
122
|
hasDefault: boolean;
|
|
123
123
|
defaultValue: TResult | (() => TResult) | undefined;
|
|
124
124
|
description: string | undefined;
|
|
125
|
+
schemaName: string | undefined;
|
|
125
126
|
hasCatch: boolean;
|
|
126
127
|
catchValue: TResult | (() => TResult) | undefined;
|
|
128
|
+
example: unknown;
|
|
127
129
|
};
|
|
128
130
|
/**
|
|
129
131
|
* @inheritdoc
|
|
@@ -243,6 +245,24 @@ export declare class DateSchemaBuilder<TResult = Date, TRequired extends boolean
|
|
|
243
245
|
* Clear `max()` call.
|
|
244
246
|
*/
|
|
245
247
|
clearMax(): DateSchemaBuilder<TResult, TRequired, TNullable, THasDefault, TExtensions> & TExtensions;
|
|
248
|
+
/**
|
|
249
|
+
* Adds a preprocessor that coerces a string value to a Date
|
|
250
|
+
* using `new Date(value)`. If the string does not represent a
|
|
251
|
+
* valid date the original value is left unchanged so the date
|
|
252
|
+
* schema rejects it.
|
|
253
|
+
*
|
|
254
|
+
* This is a convenient alias for common string→Date coercion.
|
|
255
|
+
* For more specific parsing, see {@link acceptJsonString} and
|
|
256
|
+
* {@link acceptEpoch}.
|
|
257
|
+
*
|
|
258
|
+
* @example ```ts
|
|
259
|
+
* const schema = date().coerce();
|
|
260
|
+
* const result = schema.validate('2024-01-15');
|
|
261
|
+
* // result.valid === true
|
|
262
|
+
* // result.object instanceof Date === true
|
|
263
|
+
* ```
|
|
264
|
+
*/
|
|
265
|
+
coerce(): DateSchemaBuilder<TResult, TRequired, TNullable, THasDefault, TExtensions> & TExtensions;
|
|
246
266
|
/**
|
|
247
267
|
* Accepts JSON string as a valid Date.
|
|
248
268
|
* String must be in ISO format and will be parsed using `JSON.parse()`.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a,b}from"../chunk-
|
|
1
|
+
import{a,b}from"../chunk-EIVZX4ZO.js";import"../chunk-3JMDGYDT.js";export{a as DateSchemaBuilder,b as date};
|
|
2
2
|
//# sourceMappingURL=DateSchemaBuilder.js.map
|
|
@@ -107,8 +107,10 @@ export declare class ExternSchemaBuilder<TStandardSchema extends StandardSchemaV
|
|
|
107
107
|
hasDefault: boolean;
|
|
108
108
|
defaultValue: TResult | (() => TResult) | undefined;
|
|
109
109
|
description: string | undefined;
|
|
110
|
+
schemaName: string | undefined;
|
|
110
111
|
hasCatch: boolean;
|
|
111
112
|
catchValue: TResult | (() => TResult) | undefined;
|
|
113
|
+
example: unknown;
|
|
112
114
|
};
|
|
113
115
|
/**
|
|
114
116
|
* @hidden
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{a,b}from"../chunk-
|
|
1
|
+
import{a,b}from"../chunk-PHE4LIAN.js";import"../chunk-3JMDGYDT.js";export{a as ExternSchemaBuilder,b as extern};
|
|
2
2
|
//# sourceMappingURL=ExternSchemaBuilder.js.map
|