@forgehive/schema 0.1.4 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @forgehive/schema
2
2
 
3
- A powerful schema validation library built on top of Zod, providing type-safe validation and type inference.
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
4
 
5
5
  ## Installation
6
6
 
@@ -10,59 +10,68 @@ npm install @forgehive/schema
10
10
 
11
11
  ## Overview
12
12
 
13
- The `@forgehive/schema` package provides a wrapper around Zod with additional features:
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:
14
16
 
15
- - Type-safe schema definitions
16
- - Built-in validation methods
17
- - Schema description capabilities
18
- - Type inference utilities
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
19
21
 
20
- ## Basic Usage
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.
21
25
 
22
- Here's a simple example of creating and using a schema:
26
+ ## Basic Usage
23
27
 
24
28
  ```typescript
25
29
  import { Schema } from '@forgehive/schema';
26
30
 
27
- // Create a schema with validation
28
31
  const userSchema = new Schema({
29
- name: Schema.string(),
32
+ name: Schema.string().describe('The name of the user'),
30
33
  age: Schema.number().min(0).max(120),
31
- email: Schema.string().email(),
32
- tags: Schema.array(Schema.string())
34
+ email: Schema.email(),
35
+ tags: Schema.array(Schema.string()),
33
36
  });
34
37
 
35
- // Validate data
36
38
  const result = userSchema.safeParse({
37
39
  name: 'John Doe',
38
40
  age: 30,
39
41
  email: 'john@example.com',
40
- tags: ['user', 'active']
42
+ tags: ['user', 'active'],
41
43
  });
42
44
 
43
45
  if (result.success) {
44
46
  // TypeScript knows the shape of the data
45
- const user = result.data;
46
- console.log(user.name); // TypeScript knows this is a string
47
+ console.log(result.data.name); // string
47
48
  }
48
49
  ```
49
50
 
50
- ## Schema Types
51
+ ## Building Fields
51
52
 
52
- The package provides several schema types:
53
+ Use the `Schema.*` helpers to declare fields:
53
54
 
54
55
  ```typescript
55
56
  // Basic types
56
57
  Schema.string()
57
58
  Schema.number()
58
59
  Schema.boolean()
59
- Schema.date()
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"
60
66
 
61
67
  // Arrays
62
68
  Schema.array(Schema.string())
63
- Schema.array(Schema.number())
64
- Schema.array(Schema.boolean())
65
- Schema.array(Schema.date())
69
+
70
+ // Nested objects
71
+ Schema.object({
72
+ street: Schema.string(),
73
+ zip: Schema.string().regex(/^[0-9]{5}$/),
74
+ })
66
75
 
67
76
  // Records
68
77
  Schema.stringRecord() // Record<string, string>
@@ -71,91 +80,128 @@ Schema.booleanRecord() // Record<string, boolean>
71
80
  Schema.mixedRecord() // Record<string, string | number | boolean>
72
81
  ```
73
82
 
74
- ## Validation
83
+ Validations and metadata are added with chaining: `.min()`, `.max()`, `.regex()`, `.optional()`,
84
+ `.describe('...')`, etc.
75
85
 
76
- Schemas provide several validation methods:
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
77
101
 
78
102
  ```typescript
79
103
  const schema = new Schema({
80
104
  name: Schema.string(),
81
- age: Schema.number()
105
+ age: Schema.number(),
82
106
  });
83
107
 
84
108
  // Parse and throw on error
85
109
  const data = schema.parse({ name: 'John', age: 30 });
86
110
 
87
- // Safe parse with result object
111
+ // Safe parse with a result object
88
112
  const result = schema.safeParse({ name: 'John', age: 30 });
89
113
  if (result.success) {
90
114
  const data = result.data;
91
115
  } else {
92
- const errors = result.error;
116
+ const issues = result.error.issues; // ZodError
93
117
  }
94
118
 
95
- // Validate without parsing
119
+ // Validate without parsing (boolean)
96
120
  const isValid = schema.validate({ name: 'John', age: 30 });
97
121
  ```
98
122
 
99
- ## Schema Description
123
+ ## Serialization (`describe`) and Rehydration (`from`)
100
124
 
101
- You can get a description of the schema structure:
125
+ `describe()` returns standard JSON Schema. Every validation and `.describe()` on a field is
126
+ included automatically:
102
127
 
103
128
  ```typescript
104
129
  const schema = new Schema({
105
- name: Schema.string(),
130
+ name: Schema.string().describe('The name of the user'),
106
131
  age: Schema.number().min(0),
107
- email: Schema.string().email()
132
+ email: Schema.email(),
133
+ nickname: Schema.string().optional(),
108
134
  });
109
135
 
110
136
  const description = schema.describe();
111
- // Returns:
112
137
  // {
113
- // name: { type: 'string' },
114
- // age: { type: 'number', validations: { min: 0 } },
115
- // email: { type: 'string', validations: { email: true } }
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
116
148
  // }
117
149
  ```
118
150
 
119
- ## Type Inference
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.
120
163
 
121
- The package provides type utilities for inferring types from schemas:
164
+ ## Type Inference
122
165
 
123
166
  ```typescript
124
167
  import { Schema, type InferSchema } from '@forgehive/schema';
125
168
 
126
169
  const schema = new Schema({
127
170
  name: Schema.string(),
128
- age: Schema.number()
171
+ age: Schema.number(),
129
172
  });
130
173
 
131
- // Infer the type from the schema
132
174
  type User = InferSchema<typeof schema>;
133
- // Type is: { name: string; age: number }
175
+ // { name: string; age: number }
134
176
  ```
135
177
 
136
178
  ## API Reference
137
179
 
138
- ### `Schema` Class
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
139
190
 
140
- - `constructor(fields: Record<string, SchemaType>)`: Creates a new schema
141
- - `parse(data: unknown)`: Parses and validates data, throws on error
142
- - `safeParse(data: unknown)`: Safely parses and validates data
143
- - `validate(data: unknown)`: Validates data without parsing
144
- - `describe()`: Returns a description of the schema structure
145
- - `asZod()`: Returns the underlying Zod schema
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
146
198
 
147
- ### Static Methods
199
+ ### Exports
148
200
 
149
- - `string()`: Creates a string schema
150
- - `number()`: Creates a number schema
151
- - `boolean()`: Creates a boolean schema
152
- - `date()`: Creates a date schema
153
- - `array(type)`: Creates an array schema
154
- - `stringRecord()`: Creates a record schema with string values
155
- - `numberRecord()`: Creates a record schema with number values
156
- - `booleanRecord()`: Creates a record schema with boolean values
157
- - `mixedRecord()`: Creates a record schema with mixed values
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`
158
204
 
159
205
  ## License
160
206
 
161
- MIT
207
+ MIT
package/dist/index.d.ts CHANGED
@@ -1,110 +1,113 @@
1
1
  import { z } from 'zod';
2
- export type SchemaType = 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>>>;
3
- type AllowedBaseTypes = 'string' | 'boolean' | 'number' | 'date' | 'stringRecord' | 'numberRecord' | 'booleanRecord' | 'mixedRecord';
4
- type ArrayTypes = z.ZodString | z.ZodBoolean | z.ZodNumber | z.ZodDate;
5
- type NumberValidations = {
6
- min?: number;
7
- max?: number;
8
- };
9
- type StringValidations = {
10
- email?: boolean;
11
- minLength?: number;
12
- maxLength?: number;
13
- regex?: string;
14
- };
15
- export type ShadowString = z.ZodString;
16
- export type ShadowBoolean = z.ZodBoolean;
17
- export type ShadowNumber = z.ZodNumber;
18
- export type ShadowDate = z.ZodDate;
19
- export type ShadowArray<T extends ArrayTypes> = z.ZodArray<T>;
20
- export type ShadowStringRecord = z.ZodRecord<z.ZodString, z.ZodString>;
21
- export type ShadowNumberRecord = z.ZodRecord<z.ZodString, z.ZodNumber>;
22
- export type ShadowBooleanRecord = z.ZodRecord<z.ZodString, z.ZodBoolean>;
23
- export type ShadowMixedRecord = z.ZodRecord<z.ZodString, z.ZodUnion<[z.ZodString, z.ZodNumber, z.ZodBoolean]>>;
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(): ShadowString;
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(): ShadowBoolean;
40
+ static boolean(): z.ZodBoolean;
60
41
  /**
61
- * Creates a number schema
62
- * @returns A number schema
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
60
+ */
61
+ static url(): z.ZodURL;
62
+ /**
63
+ * Creates an array schema
64
+ * @param type The type of items in the array
65
+ * @returns An array schema
63
66
  */
64
- static number(): ShadowNumber;
67
+ static array<E extends z.ZodType>(type: E): z.ZodArray<E>;
65
68
  /**
66
- * Creates a date schema
67
- * @returns A date schema
69
+ * Creates a nested object schema
70
+ * @param fields The fields of the object
71
+ * @returns An object schema
68
72
  */
69
- static date(): ShadowDate;
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(): ShadowStringRecord;
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(): ShadowNumberRecord;
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(): ShadowBooleanRecord;
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(): ShadowMixedRecord;
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<Record<string, z.ZodTypeAny>>>(_schema: S): z.infer<S['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 object
104
- * @param description Object describing the schema structure with type information
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<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>>>>>;
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,10 +126,10 @@ 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.SafeParseReturnType<z.infer<z.ZodObject<T>>, z.infer<z.ZodObject<T>>>;
129
+ safeParse(data: unknown): z.ZodSafeParseResult<z.infer<z.ZodObject<T>>>;
127
130
  /**
128
- * Describes the schema structure and allowed types
129
- * @returns An object describing the schema structure with type information
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;
132
135
  /**