@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 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 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
63
60
  */
64
- static number(): ShadowNumber;
61
+ static url(): z.ZodURL;
65
62
  /**
66
- * Creates a date schema
67
- * @returns A date schema
63
+ * Creates an array schema
64
+ * @param type The type of items in the array
65
+ * @returns An array schema
68
66
  */
69
- static date(): ShadowDate;
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(): 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,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.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;
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;