@forgehive/schema 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +207 -0
- package/dist/index.d.ts +79 -71
- package/dist/index.js +75 -182
- package/dist/test/custom-validations.test.js +45 -131
- package/dist/test/dates.test.js +41 -21
- package/dist/test/describe.test.d.ts +1 -0
- package/dist/test/describe.test.js +172 -0
- package/dist/test/from.test.d.ts +1 -0
- package/dist/test/from.test.js +162 -0
- package/dist/test/index.test.js +130 -30
- package/dist/test/optionals.test.js +22 -18
- package/dist/test/parse.test.d.ts +1 -0
- package/dist/test/parse.test.js +134 -0
- package/dist/test/record.test.js +47 -62
- package/package.json +2 -2
- package/src/index.ts +112 -250
- package/src/test/custom-validations.test.ts +45 -132
- package/src/test/dates.test.ts +45 -22
- package/src/test/describe.test.ts +190 -0
- package/src/test/from.test.ts +191 -0
- package/src/test/index.test.ts +148 -31
- package/src/test/optionals.test.ts +23 -18
- package/src/test/parse.test.ts +160 -0
- package/src/test/record.test.ts +48 -72
- package/tsconfig.json +2 -3
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# @forgehive/schema
|
|
2
|
+
|
|
3
|
+
A thin, type-safe wrapper around [Zod 4](https://zod.dev) that adds JSON Schema serialization and rehydration on top of native Zod validation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @forgehive/schema
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Overview
|
|
12
|
+
|
|
13
|
+
`@forgehive/schema` wraps a Zod object schema in a `Schema` class. Fields are created with the
|
|
14
|
+
`Schema.*` helpers, which keeps your call sites independent of the underlying validation library —
|
|
15
|
+
so Zod can be swapped or upgraded without touching consumer code. In return you get:
|
|
16
|
+
|
|
17
|
+
- **Type-safe validation** — `parse`, `safeParse`, `validate`
|
|
18
|
+
- **Type inference** — `InferSchema<typeof schema>`
|
|
19
|
+
- **JSON Schema serialization** — `describe()` emits standard [JSON Schema](https://zod.dev/json-schema) (draft 2020-12)
|
|
20
|
+
- **Rehydration** — `Schema.from(jsonSchema)` rebuilds a `Schema` from JSON Schema
|
|
21
|
+
|
|
22
|
+
Helpers return real Zod types, so chaining (`.min()`, `.optional()`, `.describe()`, ...) works as
|
|
23
|
+
usual. The full `z` namespace is also re-exported as an escape hatch for anything the helpers
|
|
24
|
+
don't cover. See [`docs/specs/schema.md`](../../docs/specs/schema.md) for the design rationale.
|
|
25
|
+
|
|
26
|
+
## Basic Usage
|
|
27
|
+
|
|
28
|
+
```typescript
|
|
29
|
+
import { Schema } from '@forgehive/schema';
|
|
30
|
+
|
|
31
|
+
const userSchema = new Schema({
|
|
32
|
+
name: Schema.string().describe('The name of the user'),
|
|
33
|
+
age: Schema.number().min(0).max(120),
|
|
34
|
+
email: Schema.email(),
|
|
35
|
+
tags: Schema.array(Schema.string()),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const result = userSchema.safeParse({
|
|
39
|
+
name: 'John Doe',
|
|
40
|
+
age: 30,
|
|
41
|
+
email: 'john@example.com',
|
|
42
|
+
tags: ['user', 'active'],
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (result.success) {
|
|
46
|
+
// TypeScript knows the shape of the data
|
|
47
|
+
console.log(result.data.name); // string
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Building Fields
|
|
52
|
+
|
|
53
|
+
Use the `Schema.*` helpers to declare fields:
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Basic types
|
|
57
|
+
Schema.string()
|
|
58
|
+
Schema.number()
|
|
59
|
+
Schema.boolean()
|
|
60
|
+
Schema.date() // ISO 8601 date-time string (see note below)
|
|
61
|
+
|
|
62
|
+
// String formats (serialize to JSON Schema `format`)
|
|
63
|
+
Schema.email() // format: "email"
|
|
64
|
+
Schema.uuid() // format: "uuid"
|
|
65
|
+
Schema.url() // format: "uri"
|
|
66
|
+
|
|
67
|
+
// Arrays
|
|
68
|
+
Schema.array(Schema.string())
|
|
69
|
+
|
|
70
|
+
// Nested objects
|
|
71
|
+
Schema.object({
|
|
72
|
+
street: Schema.string(),
|
|
73
|
+
zip: Schema.string().regex(/^[0-9]{5}$/),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
// Records
|
|
77
|
+
Schema.stringRecord() // Record<string, string>
|
|
78
|
+
Schema.numberRecord() // Record<string, number>
|
|
79
|
+
Schema.booleanRecord() // Record<string, boolean>
|
|
80
|
+
Schema.mixedRecord() // Record<string, string | number | boolean>
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Validations and metadata are added with chaining: `.min()`, `.max()`, `.regex()`, `.optional()`,
|
|
84
|
+
`.describe('...')`, etc.
|
|
85
|
+
|
|
86
|
+
For anything the helpers don't cover (enums, unions, refinements), reach for the re-exported `z`:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { Schema, z } from '@forgehive/schema';
|
|
90
|
+
|
|
91
|
+
const schema = new Schema({
|
|
92
|
+
role: z.enum(['admin', 'user', 'guest']),
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
> **Note on dates:** `Schema.date()` validates an ISO 8601 date-time **string**
|
|
97
|
+
> (e.g. `"2024-03-20T12:00:00Z"`), not a `Date` instance. JSON Schema has no native date type,
|
|
98
|
+
> so dates are represented as `{ type: "string", format: "date-time" }`.
|
|
99
|
+
|
|
100
|
+
## Validation
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
const schema = new Schema({
|
|
104
|
+
name: Schema.string(),
|
|
105
|
+
age: Schema.number(),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Parse and throw on error
|
|
109
|
+
const data = schema.parse({ name: 'John', age: 30 });
|
|
110
|
+
|
|
111
|
+
// Safe parse with a result object
|
|
112
|
+
const result = schema.safeParse({ name: 'John', age: 30 });
|
|
113
|
+
if (result.success) {
|
|
114
|
+
const data = result.data;
|
|
115
|
+
} else {
|
|
116
|
+
const issues = result.error.issues; // ZodError
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Validate without parsing (boolean)
|
|
120
|
+
const isValid = schema.validate({ name: 'John', age: 30 });
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Serialization (`describe`) and Rehydration (`from`)
|
|
124
|
+
|
|
125
|
+
`describe()` returns standard JSON Schema. Every validation and `.describe()` on a field is
|
|
126
|
+
included automatically:
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const schema = new Schema({
|
|
130
|
+
name: Schema.string().describe('The name of the user'),
|
|
131
|
+
age: Schema.number().min(0),
|
|
132
|
+
email: Schema.email(),
|
|
133
|
+
nickname: Schema.string().optional(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
const description = schema.describe();
|
|
137
|
+
// {
|
|
138
|
+
// "$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
139
|
+
// "type": "object",
|
|
140
|
+
// "properties": {
|
|
141
|
+
// "name": { "type": "string", "description": "The name of the user" },
|
|
142
|
+
// "age": { "type": "number", "minimum": 0 },
|
|
143
|
+
// "email": { "type": "string", "format": "email", "pattern": "..." },
|
|
144
|
+
// "nickname": { "type": "string" }
|
|
145
|
+
// },
|
|
146
|
+
// "required": ["name", "age", "email"], // optional fields are absent here
|
|
147
|
+
// "additionalProperties": false
|
|
148
|
+
// }
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
Optionality is expressed by the `required` array (JSON Schema semantics), not a per-field flag.
|
|
152
|
+
|
|
153
|
+
`Schema.from()` rebuilds a `Schema` from JSON Schema. A `describe()` → `from()` round-trip
|
|
154
|
+
preserves validations, descriptions, and which fields are optional:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
const clone = Schema.from(schema.describe());
|
|
158
|
+
clone.validate({ name: 'Jane', age: 5, email: 'jane@example.com' }); // true
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
> `Schema.from()` is backed by `z.fromJSONSchema`, which Zod considers semi-experimental.
|
|
162
|
+
> Round-trips of schemas produced by `describe()` are covered by this package's tests.
|
|
163
|
+
|
|
164
|
+
## Type Inference
|
|
165
|
+
|
|
166
|
+
```typescript
|
|
167
|
+
import { Schema, type InferSchema } from '@forgehive/schema';
|
|
168
|
+
|
|
169
|
+
const schema = new Schema({
|
|
170
|
+
name: Schema.string(),
|
|
171
|
+
age: Schema.number(),
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
type User = InferSchema<typeof schema>;
|
|
175
|
+
// { name: string; age: number }
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
## API Reference
|
|
179
|
+
|
|
180
|
+
### `Schema` class
|
|
181
|
+
|
|
182
|
+
- `constructor(fields)` — creates a schema from a map of field name → field type
|
|
183
|
+
- `parse(data)` — parses and validates, throws `ZodError` on failure
|
|
184
|
+
- `safeParse(data)` — returns a Zod `{ success, data | error }` result
|
|
185
|
+
- `validate(data)` — validates without parsing, returns a boolean
|
|
186
|
+
- `describe()` — returns the schema as JSON Schema (draft 2020-12)
|
|
187
|
+
- `asZod()` — returns the underlying `z.ZodObject`
|
|
188
|
+
|
|
189
|
+
### Static methods
|
|
190
|
+
|
|
191
|
+
- `string()`, `number()`, `boolean()`, `date()` — basic field types
|
|
192
|
+
- `email()`, `uuid()`, `url()` — string-format field types
|
|
193
|
+
- `array(type)` — array of the given field type
|
|
194
|
+
- `object(fields)` — nested object schema
|
|
195
|
+
- `stringRecord()`, `numberRecord()`, `booleanRecord()`, `mixedRecord()` — record types
|
|
196
|
+
- `from(jsonSchema)` — rebuilds a `Schema` from JSON Schema
|
|
197
|
+
- `infer(schema)` — type-level inference helper
|
|
198
|
+
|
|
199
|
+
### Exports
|
|
200
|
+
|
|
201
|
+
- `Schema` (also the default export)
|
|
202
|
+
- `z` — the full Zod 4 namespace, re-exported as an escape hatch
|
|
203
|
+
- Types: `SchemaType`, `SchemaDescription` (JSON Schema), `InferSchema`
|
|
204
|
+
|
|
205
|
+
## License
|
|
206
|
+
|
|
207
|
+
MIT
|
package/dist/index.d.ts
CHANGED
|
@@ -1,110 +1,113 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
export
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
export type
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
export
|
|
24
|
-
export type InferShadowString = z.infer<ShadowString>;
|
|
25
|
-
export type InferShadowBoolean = z.infer<ShadowBoolean>;
|
|
26
|
-
export type InferShadowNumber = z.infer<ShadowNumber>;
|
|
27
|
-
export type InferShadowDate = z.infer<ShadowDate>;
|
|
28
|
-
export type InferShadowArray<T extends ArrayTypes> = z.infer<ShadowArray<T>>;
|
|
29
|
-
export type InferShadowStringRecord = z.infer<ShadowStringRecord>;
|
|
30
|
-
export type InferShadowNumberRecord = z.infer<ShadowNumberRecord>;
|
|
31
|
-
export type InferShadowBooleanRecord = z.infer<ShadowBooleanRecord>;
|
|
32
|
-
export type InferShadowMixedRecord = z.infer<ShadowMixedRecord>;
|
|
33
|
-
export type InferSchema<S extends Schema<Record<string, SchemaType>>> = z.infer<S['schema']>;
|
|
34
|
-
type BaseSchemaDescription = {
|
|
35
|
-
type: AllowedBaseTypes;
|
|
36
|
-
optional?: boolean;
|
|
37
|
-
validations?: NumberValidations | StringValidations;
|
|
38
|
-
};
|
|
39
|
-
type ArraySchemaDescription = {
|
|
40
|
-
type: 'array';
|
|
41
|
-
items: {
|
|
42
|
-
type: AllowedBaseTypes;
|
|
43
|
-
};
|
|
44
|
-
optional?: boolean;
|
|
45
|
-
};
|
|
46
|
-
export type SchemaDescription = Record<string, BaseSchemaDescription | ArraySchemaDescription>;
|
|
47
|
-
export declare class Schema<T extends Record<string, z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>> | z.ZodOptional<z.ZodType<string | boolean | number | Date | string[] | boolean[] | number[] | Date[] | Record<string, string | number | boolean>>>>> {
|
|
2
|
+
export { z };
|
|
3
|
+
/**
|
|
4
|
+
* A single field within a schema. Any zod type is allowed, which enables
|
|
5
|
+
* nested objects, arrays, records, unions and the full range of zod validations.
|
|
6
|
+
*/
|
|
7
|
+
export type SchemaType = z.ZodType;
|
|
8
|
+
/**
|
|
9
|
+
* The serialized form of a Schema. `describe()` produces standard JSON Schema
|
|
10
|
+
* (draft 2020-12) and `from()` consumes it.
|
|
11
|
+
*/
|
|
12
|
+
export type SchemaDescription = z.core.JSONSchema.BaseSchema;
|
|
13
|
+
/**
|
|
14
|
+
* Infers the TypeScript type produced by a Schema instance.
|
|
15
|
+
*/
|
|
16
|
+
export type InferSchema<S extends Schema<z.ZodRawShape>> = z.infer<S['schema']>;
|
|
17
|
+
/**
|
|
18
|
+
* A thin wrapper around a zod object schema. Fields are created with the static
|
|
19
|
+
* `Schema.*` helpers so call sites stay independent of the underlying validation
|
|
20
|
+
* library; the wrapper owns validation plus JSON Schema serialization
|
|
21
|
+
* (`describe`) and rehydration (`from`).
|
|
22
|
+
*/
|
|
23
|
+
export declare class Schema<T extends z.ZodRawShape = z.ZodRawShape> {
|
|
48
24
|
readonly schema: z.ZodObject<T>;
|
|
49
25
|
constructor(fields: T);
|
|
50
26
|
/**
|
|
51
27
|
* Creates a string schema
|
|
52
28
|
* @returns A string schema
|
|
53
29
|
*/
|
|
54
|
-
static string():
|
|
30
|
+
static string(): z.ZodString;
|
|
31
|
+
/**
|
|
32
|
+
* Creates a number schema
|
|
33
|
+
* @returns A number schema
|
|
34
|
+
*/
|
|
35
|
+
static number(): z.ZodNumber;
|
|
55
36
|
/**
|
|
56
37
|
* Creates a boolean schema
|
|
57
38
|
* @returns A boolean schema
|
|
58
39
|
*/
|
|
59
|
-
static boolean():
|
|
40
|
+
static boolean(): z.ZodBoolean;
|
|
60
41
|
/**
|
|
61
|
-
* Creates
|
|
62
|
-
*
|
|
42
|
+
* Creates an ISO 8601 date-time schema. The runtime value is a string
|
|
43
|
+
* (e.g. "2020-01-01T00:00:00Z"), which is natively representable in JSON Schema.
|
|
44
|
+
* @returns An ISO date-time schema
|
|
45
|
+
*/
|
|
46
|
+
static date(): z.ZodISODateTime;
|
|
47
|
+
/**
|
|
48
|
+
* Creates an email string schema (serializes to JSON Schema `format: "email"`).
|
|
49
|
+
* @returns An email schema
|
|
50
|
+
*/
|
|
51
|
+
static email(): z.ZodEmail;
|
|
52
|
+
/**
|
|
53
|
+
* Creates a UUID string schema (serializes to JSON Schema `format: "uuid"`).
|
|
54
|
+
* @returns A UUID schema
|
|
55
|
+
*/
|
|
56
|
+
static uuid(): z.ZodUUID;
|
|
57
|
+
/**
|
|
58
|
+
* Creates a URL string schema (serializes to JSON Schema `format: "uri"`).
|
|
59
|
+
* @returns A URL schema
|
|
63
60
|
*/
|
|
64
|
-
static
|
|
61
|
+
static url(): z.ZodURL;
|
|
65
62
|
/**
|
|
66
|
-
* Creates
|
|
67
|
-
* @
|
|
63
|
+
* Creates an array schema
|
|
64
|
+
* @param type The type of items in the array
|
|
65
|
+
* @returns An array schema
|
|
68
66
|
*/
|
|
69
|
-
static
|
|
67
|
+
static array<E extends z.ZodType>(type: E): z.ZodArray<E>;
|
|
68
|
+
/**
|
|
69
|
+
* Creates a nested object schema
|
|
70
|
+
* @param fields The fields of the object
|
|
71
|
+
* @returns An object schema
|
|
72
|
+
*/
|
|
73
|
+
static object<S extends z.ZodRawShape>(fields: S): z.ZodObject<S>;
|
|
70
74
|
/**
|
|
71
75
|
* Creates a record schema with string keys and string values
|
|
72
76
|
* @returns A record schema with string values
|
|
73
77
|
*/
|
|
74
|
-
static stringRecord():
|
|
78
|
+
static stringRecord(): z.ZodRecord<z.ZodString, z.ZodString>;
|
|
75
79
|
/**
|
|
76
80
|
* Creates a record schema with string keys and number values
|
|
77
81
|
* @returns A record schema with number values
|
|
78
82
|
*/
|
|
79
|
-
static numberRecord():
|
|
83
|
+
static numberRecord(): z.ZodRecord<z.ZodString, z.ZodNumber>;
|
|
80
84
|
/**
|
|
81
85
|
* Creates a record schema with string keys and boolean values
|
|
82
86
|
* @returns A record schema with boolean values
|
|
83
87
|
*/
|
|
84
|
-
static booleanRecord():
|
|
88
|
+
static booleanRecord(): z.ZodRecord<z.ZodString, z.ZodBoolean>;
|
|
85
89
|
/**
|
|
86
90
|
* Creates a record schema with string keys and mixed values (string, number, or boolean)
|
|
87
91
|
* @returns A record schema with mixed values
|
|
88
92
|
*/
|
|
89
|
-
static mixedRecord():
|
|
90
|
-
/**
|
|
91
|
-
* Creates an array schema
|
|
92
|
-
* @param type The type of items in the array
|
|
93
|
-
* @returns An array schema
|
|
94
|
-
*/
|
|
95
|
-
static array<T extends ArrayTypes>(type: T): ShadowArray<T>;
|
|
93
|
+
static mixedRecord(): z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
|
|
96
94
|
/**
|
|
97
95
|
* Infers the TypeScript type from a Schema instance
|
|
98
96
|
* @template S The Schema type
|
|
99
97
|
* @returns The inferred TypeScript type
|
|
100
98
|
*/
|
|
101
|
-
static infer<S extends Schema<
|
|
99
|
+
static infer<S extends Schema<z.ZodRawShape>>(_schema: S): z.infer<S['schema']>;
|
|
102
100
|
/**
|
|
103
|
-
* Creates a Schema instance from a description
|
|
104
|
-
*
|
|
101
|
+
* Creates a Schema instance from a JSON Schema description.
|
|
102
|
+
*
|
|
103
|
+
* Note: this relies on zod's `fromJSONSchema`, which zod considers
|
|
104
|
+
* semi-experimental. Round-trips of schemas produced by `describe()` are
|
|
105
|
+
* covered by the package tests.
|
|
106
|
+
*
|
|
107
|
+
* @param description A JSON Schema object describing an object schema
|
|
105
108
|
* @returns A new Schema instance
|
|
106
109
|
*/
|
|
107
|
-
static from(description: SchemaDescription): Schema<
|
|
110
|
+
static from(description: SchemaDescription): Schema<z.ZodRawShape>;
|
|
108
111
|
/**
|
|
109
112
|
* Validates the provided data against the schema
|
|
110
113
|
* @param data The data to validate
|
|
@@ -123,11 +126,16 @@ export declare class Schema<T extends Record<string, z.ZodType<string | boolean
|
|
|
123
126
|
* @param data The data to parse and validate
|
|
124
127
|
* @returns An object containing either the successfully parsed data or error information
|
|
125
128
|
*/
|
|
126
|
-
safeParse(data: unknown): z.
|
|
129
|
+
safeParse(data: unknown): z.ZodSafeParseResult<z.infer<z.ZodObject<T>>>;
|
|
127
130
|
/**
|
|
128
|
-
*
|
|
129
|
-
* @returns
|
|
131
|
+
* Serializes the schema to JSON Schema (draft 2020-12).
|
|
132
|
+
* @returns A JSON Schema object describing the schema structure
|
|
130
133
|
*/
|
|
131
134
|
describe(): SchemaDescription;
|
|
135
|
+
/**
|
|
136
|
+
* Returns the underlying Zod schema object
|
|
137
|
+
* @returns The Zod schema object
|
|
138
|
+
*/
|
|
139
|
+
asZod(): z.ZodObject<T>;
|
|
132
140
|
}
|
|
133
141
|
export default Schema;
|