zodselect 1.0.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/LICENSE +21 -0
- package/README.md +241 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +3 -0
- package/dist/merge-select.d.ts +3 -0
- package/dist/merge-select.js +9 -0
- package/dist/refine-schema.d.ts +36 -0
- package/dist/refine-schema.js +56 -0
- package/dist/refine-schema.spec.d.ts +1 -0
- package/dist/refine-schema.spec.js +223 -0
- package/dist/select.d.ts +12 -0
- package/dist/select.js +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/types.js +1 -0
- package/dist/utilities.d.ts +9 -0
- package/dist/utilities.js +41 -0
- package/dist/utilities.spec.d.ts +1 -0
- package/dist/utilities.spec.js +49 -0
- package/package.json +41 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 SecureDataSystems
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
# ZodSelect
|
|
2
|
+
|
|
3
|
+
**ZodSelect** is a strongly typed selection utility library built on top of [Zod](https://github.com/colinhacks/zod) (v4). It allows developers to create dynamic select statements with precise TypeScript inference — perfect for shaping responses in database queries, or APIs. `ZodSelect` ensures that both your types and runtime logic stay in sync — with no external dependencies.
|
|
4
|
+
|
|
5
|
+
- ✅ Built on Zod v4
|
|
6
|
+
- ✅ No external dependencies (except `zod`)
|
|
7
|
+
- ✅ Fully type-safe field selections
|
|
8
|
+
- ✅ Supports merging multiple Zod Selects
|
|
9
|
+
- ✅ Generate Zod schemas from Zod Selects
|
|
10
|
+
- ✅ Allows schema refinement and overrides
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
pnpm add zod @securedatasystems/zod-select
|
|
18
|
+
# or
|
|
19
|
+
npm install zod @securedatasystems/zod-select
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Example Schema
|
|
25
|
+
|
|
26
|
+
All examples use the following Zod schema:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const UserSchema = z.object({
|
|
30
|
+
email: z.string(),
|
|
31
|
+
firstName: z.string(),
|
|
32
|
+
lastName: z.string(),
|
|
33
|
+
address: z.object({
|
|
34
|
+
street: z.string(),
|
|
35
|
+
city: z.string(),
|
|
36
|
+
state: z.string(),
|
|
37
|
+
zip: z.string()
|
|
38
|
+
})
|
|
39
|
+
});
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## 🧠 Zod Select with Inferred Type
|
|
45
|
+
|
|
46
|
+
A single selection object returns a strongly typed result:
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
function find<T extends ZodSelect<typeof UserSchema>>(select: T): InferType<typeof UserSchema, T> {
|
|
50
|
+
// ...Find Logic
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const user = find({
|
|
54
|
+
firstName: true,
|
|
55
|
+
lastName: true,
|
|
56
|
+
address: { street: true }
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
// user inferred as:
|
|
62
|
+
{
|
|
63
|
+
firstName: string;
|
|
64
|
+
lastName: string;
|
|
65
|
+
address: { street: string };
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## 🔀 Merging Multiple Zod Selects
|
|
72
|
+
|
|
73
|
+
You can combine multiple select statements and still get precise inference:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
function find<T extends MultiSelect<typeof UserSchema>>(select: T): InferMergedType<typeof UserSchema, T> {
|
|
77
|
+
// ...Find Logic
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const user = find([
|
|
81
|
+
{ lastName: true },
|
|
82
|
+
{ firstName: true }
|
|
83
|
+
]);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
// user inferred as:
|
|
88
|
+
{
|
|
89
|
+
firstName: string;
|
|
90
|
+
lastName: string;
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 🧹 Mix Zod Schemas with Zod Selects
|
|
97
|
+
|
|
98
|
+
You can mix plain field selections with Zod object schemas for flexible results:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
function find<T extends MultiSelect<typeof UserSchema>>(select: T): InferMergedType<typeof UserSchema, T> {
|
|
102
|
+
// ...Find Logic
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const user = find([
|
|
106
|
+
{ lastName: true },
|
|
107
|
+
z.object({
|
|
108
|
+
address: z.object({ state: z.string() })
|
|
109
|
+
})
|
|
110
|
+
]);
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
// user inferred as:
|
|
115
|
+
{
|
|
116
|
+
lastName: string;
|
|
117
|
+
address: { state: string };
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## 🛠️ Generate Zod Schemas from Zod Selects
|
|
124
|
+
|
|
125
|
+
Use `refineSchema` to turn a selection into a Zod schema:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
const schema = refineSchema(UserSchema, {
|
|
129
|
+
firstName: true,
|
|
130
|
+
address: true
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// schema.shape:
|
|
136
|
+
{
|
|
137
|
+
firstName: z.ZodString;
|
|
138
|
+
address: z.ZodObject<...>;
|
|
139
|
+
}
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## 🧪 Refine Field Types Inline
|
|
145
|
+
|
|
146
|
+
Selections can refine field types inline using Zod methods:
|
|
147
|
+
|
|
148
|
+
```ts
|
|
149
|
+
const schema = refineSchema(UserSchema, {
|
|
150
|
+
firstName: s => s.optional()
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
```ts
|
|
155
|
+
// schema.shape:
|
|
156
|
+
{
|
|
157
|
+
firstName: z.ZodOptional<z.ZodString>;
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
---
|
|
162
|
+
|
|
163
|
+
## 🧬 Redefine Field Types
|
|
164
|
+
|
|
165
|
+
Selections can also redefine types entirely:
|
|
166
|
+
|
|
167
|
+
```ts
|
|
168
|
+
const schema = refineSchema(UserSchema, {
|
|
169
|
+
firstName: z.number()
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
```ts
|
|
174
|
+
// schema.shape:
|
|
175
|
+
{
|
|
176
|
+
firstName: z.ZodNumber;
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## 🧵 Putting it all together
|
|
183
|
+
|
|
184
|
+
Combining all methods and types together you can build powerful, type-safe selection and inference using the zod schemas you already have.
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
function find<T extends MultiSelect<typeof UserSchema>>(select: T): InferMergedType<typeof UserSchema, T>[] {
|
|
188
|
+
const selectArr = Array.isArray(select) ? select : [select];
|
|
189
|
+
const sel: Record<string, 1> = {};
|
|
190
|
+
|
|
191
|
+
for (let schema of selectArr) {
|
|
192
|
+
if (!(schema instanceof ZodType)) {
|
|
193
|
+
schema = refineSchema(UserSchema, schema);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Crawl each zod schema building the select statement
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Find Logic
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function getUsersFromTexas<T extends MultiSelect<typeof UserSchema>>(
|
|
204
|
+
sel: T
|
|
205
|
+
): InferMergedType<typeof UserSchema, MergeSelect<typeof UserSchema, { address: boolean }, T>>[] {
|
|
206
|
+
const users = find(
|
|
207
|
+
// Use the merge select function to combined a ZodSelect with a MultiSelect
|
|
208
|
+
mergeSelect(UserSchema, { address: true }, sel)
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
return users.filter((user) =>
|
|
212
|
+
// At this point the user object has address and whatever fields are specified in the
|
|
213
|
+
// passed in select but VS code doesn't know what fields are going to be passed in at
|
|
214
|
+
// runtime. It does know that address was selected inside this function so it is
|
|
215
|
+
// strongly typed with a fully selected address object
|
|
216
|
+
user.address.state === 'TX'
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const users = getUsersFromTexas({
|
|
221
|
+
firstName: true,
|
|
222
|
+
lastName: true
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
// At this point we know what fields we selected and the extra fields added by the
|
|
226
|
+
// getUsersFromTexas function
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
```ts
|
|
230
|
+
// user inferred as:
|
|
231
|
+
{
|
|
232
|
+
firstName: string;
|
|
233
|
+
lastName: string;
|
|
234
|
+
address: {
|
|
235
|
+
street: string;
|
|
236
|
+
city: string;
|
|
237
|
+
state: string;
|
|
238
|
+
zip: string;
|
|
239
|
+
};
|
|
240
|
+
}[]
|
|
241
|
+
```
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { ZodType, ZodUnion } from 'zod/v4';
|
|
2
|
+
import { MergeSelect, MultiSelect, ZodSelect } from './select.js';
|
|
3
|
+
export declare function mergeSelect<T extends ZodType<object> | ZodUnion, TSelect1 extends ZodSelect<T>, TSelect2 extends MultiSelect<T>>(schema: T, select1: TSelect1, select2?: TSelect2): MergeSelect<T, TSelect1, TSelect2>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { core, z, ZodAny, ZodArray, ZodBoolean, ZodDate, ZodLiteral, ZodNever, ZodNullable, ZodNumber, ZodObject, ZodOptional, ZodRawShape, ZodString, ZodType, ZodUnion, ZodUnknown } from 'zod/v4';
|
|
2
|
+
import { IsAny, IsJsonType } from './types.js';
|
|
3
|
+
export type RefinedSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T>> = T extends ZodUnion ? RefinedUnionSchema<T, TShape> : T extends ZodType<object> ? RefinedTypeSchema<z.infer<T>, TShape> : never;
|
|
4
|
+
export type RefinedTypeSchema<T extends object, TShape, TDepth extends number = 0> = TDepth extends keyof DepthLimit ? ZodObject<{
|
|
5
|
+
[K in keyof T & keyof TShape]: TShape[K] extends (schema: infer S) => ZodType ? ReturnType<TShape[K]> : TShape[K] extends ZodType ? TShape[K] : TShape[K] extends boolean ? Zodify<T[K]> : TShape[K] extends object ? Exclude<T[K], null | undefined> extends Array<infer U> ? U extends object ? ApplyOptionalNullable<T[K], z.ZodArray<RefinedTypeSchema<U, TShape[K], NextDepth<TDepth>>>> : Zodify<T[K]> : Exclude<T[K], null | undefined> extends object ? ApplyOptionalNullable<T[K], RefinedTypeSchema<Exclude<T[K], null | undefined>, TShape[K], NextDepth<TDepth>>> : ZodNever : ZodNever;
|
|
6
|
+
}, core.$strict> : any;
|
|
7
|
+
export type RefinedUnionSchema<T extends ZodUnion, TShape> = ZodUnion<T['options'] extends Readonly<[infer A, ...infer Rest]> ? Rest extends ZodType[] ? [
|
|
8
|
+
ReapplyOptionalNullable<A, A extends ZodType<object> ? RefinedTypeSchema<z.infer<A>, TShape> : Extract<A, ZodType>>,
|
|
9
|
+
...{
|
|
10
|
+
[K in keyof Rest]: Rest[K] extends ZodType ? z.infer<Rest[K]> extends object ? RefinedTypeSchema<z.infer<Rest[K]>, TShape> : Extract<Rest[K], ZodType> : Extract<Rest[K], ZodType>;
|
|
11
|
+
}
|
|
12
|
+
] : never : never>;
|
|
13
|
+
export type RefineObject<T extends object> = {
|
|
14
|
+
[K in keyof T]?: RefineType<T[K]>;
|
|
15
|
+
};
|
|
16
|
+
export type RefineSchema<T extends ZodType<object> | ZodUnion> = T extends ZodUnion ? RefineZodUnion<T> : T extends ZodType<object> ? RefineObject<z.infer<T>> : never;
|
|
17
|
+
export type RefineZodUnion<T extends ZodUnion> = {
|
|
18
|
+
[K in keyof UnionToIntersection<T['options'][number] extends ZodObject<infer Shape extends ZodRawShape> ? Shape : object>]?: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type TuplifyUnion<T, TDepth extends number = 0, L = LastOf<T>, N = [T] extends [never] ? true : false> = TDepth extends 9 ? [] : true extends N ? [] : Push<TuplifyUnion<Exclude<T, L>, NextDepth<TDepth>>, InternalZodify<L>>;
|
|
21
|
+
export type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
22
|
+
export type Zodify<T, TDepth extends number = 0> = ApplyOptionalNullable<T, IsTuple<TuplifyUnion<Exclude<T, null | undefined>, TDepth>, ZodUnion<TuplifyUnion<Exclude<T, null | undefined>, TDepth>>, InternalZodify<Exclude<T, null | undefined>, TDepth>>>;
|
|
23
|
+
type ApplyOptionalNullable<T, U extends ZodType> = IsAny<T, U, undefined extends T ? null extends T ? ZodOptional<ZodNullable<U>> : ZodOptional<U> : null extends T ? ZodNullable<U> : U>;
|
|
24
|
+
type DepthLimit = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
|
|
25
|
+
type InternalZodify<T, TDepth extends number = 0> = TDepth extends 9 ? ZodType : IsAny<T, ZodAny, T extends string ? string extends T ? ZodString : ZodLiteral<T> : T extends number ? ZodNumber : T extends boolean ? ZodBoolean : T extends Date ? ZodDate : unknown extends T ? ZodUnknown : T extends Array<infer U> ? ZodArray<Zodify<U, NextDepth<TDepth>>> : T extends object ? IsJsonType<T, true, ZodObject<{
|
|
26
|
+
[K in keyof T]-?: Zodify<T[K], NextDepth<TDepth>>;
|
|
27
|
+
}, core.$strict>, ZodType<T>> : never>;
|
|
28
|
+
type IsTuple<T, TTrue = true, TFalse = false> = T extends [infer TFirst, infer TSecond, ...infer TRest] ? TTrue : TFalse;
|
|
29
|
+
type LastOf<T> = UnionToIntersection<T extends any ? () => T : never> extends () => (infer R) ? R : never;
|
|
30
|
+
type NextDepth<T extends number> = T extends 0 ? 1 : T extends 1 ? 2 : T extends 2 ? 3 : T extends 3 ? 4 : T extends 4 ? 5 : T extends 5 ? 6 : T extends 6 ? 7 : T extends 7 ? 8 : T extends 8 ? 9 : 9;
|
|
31
|
+
type Push<T extends any[], V> = [...T, V];
|
|
32
|
+
type ReapplyOptionalNullable<T, U extends z.ZodType> = T extends ZodOptional<ZodNullable<ZodType>> ? ZodOptional<ZodNullable<U>> : T extends ZodNullable<ZodOptional<ZodType>> ? ZodNullable<ZodOptional<U>> : T extends ZodOptional<ZodType> ? ZodOptional<U> : T extends ZodNullable<ZodType> ? ZodNullable<U> : U;
|
|
33
|
+
type Refinement<T extends ZodType> = ((schema: T) => ZodType) | boolean | ZodType;
|
|
34
|
+
type RefineType<T> = IsTuple<TuplifyUnion<Exclude<T, null | undefined>>, Refinement<Zodify<T>>, Exclude<T, null | undefined> extends string ? Refinement<ApplyOptionalNullable<T, ZodString>> : Exclude<T, null | undefined> extends number ? Refinement<ApplyOptionalNullable<T, ZodNumber>> : Exclude<T, null | undefined> extends boolean ? Refinement<ApplyOptionalNullable<T, ZodBoolean>> : Exclude<T, null | undefined> extends Date ? Refinement<ApplyOptionalNullable<T, ZodDate>> : Exclude<T, null | undefined> extends Array<infer U> ? Exclude<U, null | undefined> extends object ? Refinement<Zodify<T>> | RefineObject<Extract<U, object>> : Refinement<Zodify<T>> : Exclude<T, null | undefined> extends object ? IsJsonType<Exclude<T, null | undefined>, false, Refinement<Zodify<T>> | RefineObject<Extract<T, object>>, Refinement<Zodify<T>>> : unknown extends Exclude<T, null | undefined> ? Refinement<Zodify<T>> : never>;
|
|
35
|
+
export declare function refineSchema<T extends ZodType<object> | ZodUnion, TShape extends RefineSchema<T>>(schema: T, shape: TShape): RefinedSchema<T, TShape>;
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { z } from 'zod/v4';
|
|
2
|
+
import { getInnerType, isZodArray, isZodNullable, isZodObject, isZodOptional, isZodType, isZodUnion } from './utilities.js';
|
|
3
|
+
// Main function to refine the schema
|
|
4
|
+
export function refineSchema(schema, shape) {
|
|
5
|
+
if (isZodObject(schema)) {
|
|
6
|
+
const refinedShape = {};
|
|
7
|
+
const schemaShape = schema.shape;
|
|
8
|
+
for (const key in shape) {
|
|
9
|
+
if (key in schemaShape) {
|
|
10
|
+
refinedShape[key] = refineSchemaField(schemaShape[key], shape[key]);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
return z.object(refinedShape);
|
|
14
|
+
}
|
|
15
|
+
else if (isZodUnion(schema)) {
|
|
16
|
+
const refinedOptions = schema.options.map(option => refineSchemaField(option, shape));
|
|
17
|
+
return z.union(refinedOptions);
|
|
18
|
+
}
|
|
19
|
+
throw new Error('Unsupported schema type for refinement');
|
|
20
|
+
}
|
|
21
|
+
// Helper function to refine a single schema field
|
|
22
|
+
function refineSchemaField(fieldSchema, fieldShape) {
|
|
23
|
+
if (fieldShape === true) {
|
|
24
|
+
return fieldSchema; // Keep original type
|
|
25
|
+
}
|
|
26
|
+
else if (typeof fieldShape === 'function') {
|
|
27
|
+
return fieldShape(fieldSchema); // Apply refinement function
|
|
28
|
+
}
|
|
29
|
+
else if (isZodType(fieldShape)) {
|
|
30
|
+
return fieldShape; // Use redefined type
|
|
31
|
+
}
|
|
32
|
+
else if (isZodObject(fieldSchema)) {
|
|
33
|
+
return refineSchema(fieldSchema, fieldShape); // Recursively refine nested objects
|
|
34
|
+
}
|
|
35
|
+
else if (isZodUnion(fieldSchema)) {
|
|
36
|
+
return refineSchema(fieldSchema, fieldShape);
|
|
37
|
+
}
|
|
38
|
+
else if (isZodArray(fieldSchema)) {
|
|
39
|
+
// Recursively refine array items
|
|
40
|
+
return z.array(refineSchemaField(fieldSchema.element, fieldShape));
|
|
41
|
+
}
|
|
42
|
+
else if (isZodOptional(fieldSchema)) {
|
|
43
|
+
return refineSchemaField(fieldSchema._def.innerType, fieldShape).optional();
|
|
44
|
+
}
|
|
45
|
+
else if (isZodNullable(fieldSchema)) {
|
|
46
|
+
return refineSchemaField(fieldSchema._def.innerType, fieldShape).nullable();
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
const innerType = getInnerType(fieldSchema);
|
|
50
|
+
// There are wrapper types we can't forward because the underlying object structures are changing.
|
|
51
|
+
if (innerType) {
|
|
52
|
+
return refineSchemaField(innerType, fieldShape);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return fieldSchema;
|
|
56
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { strict as assert, deepStrictEqual, throws } from 'assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { z, ZodError } from 'zod/v4';
|
|
4
|
+
import { refineSchema } from './refine-schema.js';
|
|
5
|
+
describe('.refineSchema()', () => {
|
|
6
|
+
it('should retain required fields', () => {
|
|
7
|
+
const UserSchema = z.object({
|
|
8
|
+
age: z.number().optional(),
|
|
9
|
+
firstName: z.string(),
|
|
10
|
+
lastName: z.string()
|
|
11
|
+
});
|
|
12
|
+
const RefinedSchema = refineSchema(UserSchema, {
|
|
13
|
+
firstName: true,
|
|
14
|
+
lastName: true
|
|
15
|
+
});
|
|
16
|
+
const parsedData = RefinedSchema.parse({
|
|
17
|
+
firstName: 'John',
|
|
18
|
+
lastName: 'Doe'
|
|
19
|
+
});
|
|
20
|
+
assert.equal(parsedData.firstName, 'John');
|
|
21
|
+
assert.equal(parsedData.lastName, 'Doe');
|
|
22
|
+
});
|
|
23
|
+
it('should handle optional fields', () => {
|
|
24
|
+
const UserSchema = z.object({
|
|
25
|
+
age: z.number().optional(),
|
|
26
|
+
firstName: z.string(),
|
|
27
|
+
lastName: z.string()
|
|
28
|
+
});
|
|
29
|
+
const RefinedSchema = refineSchema(UserSchema, {
|
|
30
|
+
age: true
|
|
31
|
+
});
|
|
32
|
+
const parsedData = RefinedSchema.parse({ age: 25 });
|
|
33
|
+
assert.equal(parsedData.age, 25);
|
|
34
|
+
const parsedWithoutAge = RefinedSchema.parse({});
|
|
35
|
+
assert.equal(parsedWithoutAge.age, undefined);
|
|
36
|
+
});
|
|
37
|
+
it('should handle nullable fields', () => {
|
|
38
|
+
const RoleSchema = z.object({
|
|
39
|
+
name: z.string()
|
|
40
|
+
});
|
|
41
|
+
const UserSchema = z.object({
|
|
42
|
+
firstName: z.string(),
|
|
43
|
+
role: RoleSchema.nullable()
|
|
44
|
+
});
|
|
45
|
+
const RefinedSchema = refineSchema(UserSchema, {
|
|
46
|
+
role: true
|
|
47
|
+
});
|
|
48
|
+
const parsedWithNullRole = RefinedSchema.parse({
|
|
49
|
+
firstName: 'Alice',
|
|
50
|
+
role: null
|
|
51
|
+
});
|
|
52
|
+
assert.equal(parsedWithNullRole.role, null);
|
|
53
|
+
});
|
|
54
|
+
it('should handle both optional and nullable fields', () => {
|
|
55
|
+
const RoleSchema = z.object({
|
|
56
|
+
name: z.string()
|
|
57
|
+
});
|
|
58
|
+
const UserSchema = z.object({
|
|
59
|
+
firstName: z.string(),
|
|
60
|
+
role: RoleSchema.nullable().optional()
|
|
61
|
+
});
|
|
62
|
+
const RefinedSchema = refineSchema(UserSchema, {
|
|
63
|
+
role: {
|
|
64
|
+
name: true
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
const parsedWithRole = RefinedSchema.parse({
|
|
68
|
+
firstName: 'Alice',
|
|
69
|
+
role: { name: 'Admin' }
|
|
70
|
+
});
|
|
71
|
+
assert.deepStrictEqual(parsedWithRole.role, { name: 'Admin' });
|
|
72
|
+
const parsedWithNullRole = RefinedSchema.parse({
|
|
73
|
+
firstName: 'Alice',
|
|
74
|
+
role: null
|
|
75
|
+
});
|
|
76
|
+
assert.strictEqual(parsedWithNullRole.role, null);
|
|
77
|
+
const parsedWithoutRole = RefinedSchema.parse({ firstName: 'Alice' });
|
|
78
|
+
assert.strictEqual(parsedWithoutRole.role, undefined);
|
|
79
|
+
});
|
|
80
|
+
it('should apply custom schema transformations', () => {
|
|
81
|
+
const RoleSchema = z.object({
|
|
82
|
+
name: z.string()
|
|
83
|
+
});
|
|
84
|
+
const UserSchema = z.object({
|
|
85
|
+
firstName: z.string(),
|
|
86
|
+
role: RoleSchema
|
|
87
|
+
});
|
|
88
|
+
const RefinedSchema = refineSchema(UserSchema, {
|
|
89
|
+
role: (schema) => schema.extend({ permissions: z.array(z.string()) })
|
|
90
|
+
});
|
|
91
|
+
const parsedData = RefinedSchema.parse({
|
|
92
|
+
firstName: 'Bob',
|
|
93
|
+
role: { name: 'User', permissions: ['read', 'write'] }
|
|
94
|
+
});
|
|
95
|
+
assert.deepEqual(parsedData.role, {
|
|
96
|
+
name: 'User',
|
|
97
|
+
permissions: ['read', 'write']
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
it('should handle nested arrays', () => {
|
|
101
|
+
const RoleSchema = z.object({
|
|
102
|
+
level: z.number(),
|
|
103
|
+
title: z.string()
|
|
104
|
+
});
|
|
105
|
+
const UserSchema = z.object({
|
|
106
|
+
age: z.number().optional(),
|
|
107
|
+
firstName: z.string(),
|
|
108
|
+
lastName: z.string(),
|
|
109
|
+
nestedArray: z.array(z.object({ name: z.string(), value: z.number() })).optional().nullable(),
|
|
110
|
+
role: RoleSchema.nullable().optional(),
|
|
111
|
+
tags: z.array(z.string()).optional()
|
|
112
|
+
});
|
|
113
|
+
const RefinedSchema = refineSchema(UserSchema, {
|
|
114
|
+
nestedArray: {
|
|
115
|
+
name: true
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
const parsedData = RefinedSchema.parse({
|
|
119
|
+
firstName: 'Alice',
|
|
120
|
+
lastName: 'Smith',
|
|
121
|
+
nestedArray: [{ name: 'Feature1', value: 10 }, { name: 'Feature2', value: 20 }]
|
|
122
|
+
});
|
|
123
|
+
assert.deepStrictEqual(parsedData.nestedArray, [{ name: 'Feature1' }, { name: 'Feature2' }]);
|
|
124
|
+
// Test optional array
|
|
125
|
+
const parsedWithoutNestedArray = RefinedSchema.parse({
|
|
126
|
+
firstName: 'Alice',
|
|
127
|
+
lastName: 'Smith'
|
|
128
|
+
});
|
|
129
|
+
assert.equal(parsedWithoutNestedArray.nestedArray, undefined);
|
|
130
|
+
});
|
|
131
|
+
it('should handle custom fields', () => {
|
|
132
|
+
const UserSchema = z.object({
|
|
133
|
+
address: z.custom().optional(),
|
|
134
|
+
firstName: z.string(),
|
|
135
|
+
lastName: z.string()
|
|
136
|
+
});
|
|
137
|
+
const RefinedSchema = refineSchema(UserSchema, {
|
|
138
|
+
address: true
|
|
139
|
+
});
|
|
140
|
+
const parsedData = RefinedSchema.parse({
|
|
141
|
+
address: {
|
|
142
|
+
city: 'Austin',
|
|
143
|
+
state: 'TX',
|
|
144
|
+
street: '123 Main Street'
|
|
145
|
+
},
|
|
146
|
+
firstName: 'Alice',
|
|
147
|
+
lastName: 'Smith'
|
|
148
|
+
});
|
|
149
|
+
assert.deepStrictEqual(parsedData, {
|
|
150
|
+
address: {
|
|
151
|
+
city: 'Austin',
|
|
152
|
+
state: 'TX',
|
|
153
|
+
street: '123 Main Street'
|
|
154
|
+
}
|
|
155
|
+
});
|
|
156
|
+
});
|
|
157
|
+
it('should support unions of objects', () => {
|
|
158
|
+
const Person = z.object({
|
|
159
|
+
firstName: z.string(),
|
|
160
|
+
lastName: z.string()
|
|
161
|
+
});
|
|
162
|
+
const Address = z.object({
|
|
163
|
+
address: z.string(),
|
|
164
|
+
city: z.string()
|
|
165
|
+
});
|
|
166
|
+
const Schema = refineSchema(z.union([Person, Address]), {
|
|
167
|
+
address: true,
|
|
168
|
+
firstName: true
|
|
169
|
+
});
|
|
170
|
+
let parseResult = Schema.parse({
|
|
171
|
+
address: '123 Main Street',
|
|
172
|
+
firstName: 'Jerry',
|
|
173
|
+
lastName: 'Springer'
|
|
174
|
+
});
|
|
175
|
+
deepStrictEqual(parseResult, { firstName: 'Jerry' });
|
|
176
|
+
parseResult = Schema.parse({
|
|
177
|
+
address: '123 Main Street',
|
|
178
|
+
city: 'Austin'
|
|
179
|
+
});
|
|
180
|
+
deepStrictEqual(parseResult, { address: '123 Main Street' });
|
|
181
|
+
});
|
|
182
|
+
it('should error if none of the types are matched', () => {
|
|
183
|
+
const Person = z.object({
|
|
184
|
+
firstName: z.string(),
|
|
185
|
+
lastName: z.string()
|
|
186
|
+
});
|
|
187
|
+
const Address = z.object({
|
|
188
|
+
address: z.string(),
|
|
189
|
+
city: z.string()
|
|
190
|
+
});
|
|
191
|
+
const Schema = refineSchema(z.union([Person, Address]), {
|
|
192
|
+
address: true,
|
|
193
|
+
firstName: true
|
|
194
|
+
});
|
|
195
|
+
throws(() => {
|
|
196
|
+
Schema.parse({
|
|
197
|
+
lastName: 'Springer'
|
|
198
|
+
});
|
|
199
|
+
}, (err) => {
|
|
200
|
+
if (!(err instanceof ZodError) || err.issues[0].code !== 'invalid_union') {
|
|
201
|
+
return false;
|
|
202
|
+
}
|
|
203
|
+
return true;
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
it('should support unions of objects and simple types', () => {
|
|
207
|
+
const Person = z.object({
|
|
208
|
+
firstName: z.string(),
|
|
209
|
+
lastName: z.string()
|
|
210
|
+
});
|
|
211
|
+
const Schema = refineSchema(z.union([Person, z.string()]), {
|
|
212
|
+
address: true,
|
|
213
|
+
firstName: true
|
|
214
|
+
});
|
|
215
|
+
let parseResult = Schema.parse({
|
|
216
|
+
firstName: 'Jerry',
|
|
217
|
+
lastName: 'Springer'
|
|
218
|
+
});
|
|
219
|
+
deepStrictEqual(parseResult, { firstName: 'Jerry' });
|
|
220
|
+
parseResult = Schema.parse('123 Main Street');
|
|
221
|
+
deepStrictEqual(parseResult, '123 Main Street');
|
|
222
|
+
});
|
|
223
|
+
});
|
package/dist/select.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z, ZodType, ZodUnion } from 'zod/v4';
|
|
2
|
+
import { RefinedTypeSchema, RefinedUnionSchema, RefineObject, RefineZodUnion } from './refine-schema.js';
|
|
3
|
+
import { DeepMergeAll, IsEqual } from './types.js';
|
|
4
|
+
export type InferMergedType<T extends ZodType<object> | ZodUnion, U extends readonly ZodSelect<T>[] | undefined | ZodSelect<T>> = U extends undefined ? z.infer<T> : U extends readonly ZodSelect<T>[] ? DeepMergeAll<InferTuple<T, U>> : U extends ZodSelect<T> ? InferType<T, U> : never;
|
|
5
|
+
export type InferType<T extends ZodType<object> | ZodUnion, U extends ZodSelect<T>> = U extends ZodType<object> ? z.infer<U> : U extends ZodUnion ? z.infer<U> : T extends ZodUnion ? (U extends RefineZodUnion<T> ? z.infer<RefinedUnionSchema<T, U>> : z.infer<T>) : T extends ZodType<object> ? (U extends RefineObject<z.infer<T>> ? z.infer<RefinedTypeSchema<z.infer<T>, U>> : z.infer<T>) : z.infer<T>;
|
|
6
|
+
export type MergeSelect<T extends ZodType<object> | ZodUnion, TSelect1 extends ZodSelect<T>, TSelect2 extends MultiSelect<T>> = TSelect2 extends undefined ? [TSelect1, T] : IsEqual<TSelect2, T, TSelect1, TSelect2 extends [ZodSelect<T>, ...ZodSelect<T>[]] ? [TSelect1, ...TSelect2] : [TSelect1, TSelect2]>;
|
|
7
|
+
export type MultiSelect<T extends ZodType<object> | ZodUnion> = [ZodSelect<T>, ...ZodSelect<T>[]] | undefined | ZodSelect<T>;
|
|
8
|
+
export type ZodSelect<T extends ZodType<object> | ZodUnion> = (T extends ZodUnion ? (RefineZodUnion<T> | ZodUnion) : T extends ZodType<object> ? (RefineObject<z.infer<T>> | ZodType<object>) : never);
|
|
9
|
+
type InferTuple<T extends ZodType<object> | ZodUnion, U extends readonly ZodSelect<T>[]> = {
|
|
10
|
+
[K in keyof U]: InferType<T, U[K]>;
|
|
11
|
+
};
|
|
12
|
+
export {};
|
package/dist/select.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export type DeepMerge<T, U> = {
|
|
2
|
+
[K in keyof T | keyof U]: K extends keyof T ? K extends keyof U ? T[K] extends object ? U[K] extends object ? DeepMerge<T[K], U[K]> : T[K] | U[K] : T[K] | U[K] : T[K] : K extends keyof U ? U[K] : never;
|
|
3
|
+
};
|
|
4
|
+
export type DeepMergeAll<T> = T extends readonly any[] ? T extends [infer First, ...infer Rest] ? Rest extends [] ? First : DeepMerge<First, DeepMergeAll<Rest>> : unknown : T;
|
|
5
|
+
export type IsAny<T, TTrue = true, TFalse = false> = 0 extends (1 & T) ? TTrue : TFalse;
|
|
6
|
+
export type IsEqual<T, U, TTrue = true, TFalse = false> = (<V>() => V extends T ? 1 : 2) extends (<V>() => V extends U ? 1 : 2) ? TTrue : TFalse;
|
|
7
|
+
export type IsJsonType<T, TDeep extends boolean = true, TTrue = true, TFalse = false> = T extends JsonType ? TTrue : T extends Function ? TFalse : T extends Array<infer U> ? TDeep extends false ? TTrue : IsJsonType<U, TDeep, TTrue, TFalse> : T extends object ? TDeep extends false ? TTrue : {
|
|
8
|
+
[K in keyof T]: IsJsonType<T[K], true, false>;
|
|
9
|
+
} extends {
|
|
10
|
+
[K in keyof T]: true;
|
|
11
|
+
} ? TTrue : TFalse : TFalse;
|
|
12
|
+
export type Mutable<T> = {
|
|
13
|
+
-readonly [P in keyof T]: T[P];
|
|
14
|
+
};
|
|
15
|
+
type JsonType = boolean | null | number | string | undefined;
|
|
16
|
+
export {};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { ZodArray, ZodNullable, ZodObject, ZodOptional, ZodType, ZodUnion, ZodUnknown } from 'zod/v4';
|
|
2
|
+
export declare function getInnerType(schema: ZodType): undefined | ZodType;
|
|
3
|
+
export declare function isZodArray(obj: unknown): obj is ZodArray<ZodType>;
|
|
4
|
+
export declare function isZodNullable(obj: unknown): obj is ZodNullable<ZodType>;
|
|
5
|
+
export declare function isZodObject(obj: unknown, isLoose?: boolean): obj is ZodObject;
|
|
6
|
+
export declare function isZodOptional(obj: unknown): obj is ZodOptional<ZodType>;
|
|
7
|
+
export declare function isZodType(obj: unknown): obj is ZodType;
|
|
8
|
+
export declare function isZodUnion(obj: unknown): obj is ZodUnion<ZodType[]>;
|
|
9
|
+
export declare function isZodUnknown(obj: unknown): obj is ZodUnknown;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { ZodArray, ZodCatch, ZodDefault, ZodLazy, ZodNullable, ZodOptional } from 'zod/v4';
|
|
2
|
+
export function getInnerType(schema) {
|
|
3
|
+
if (schema instanceof ZodOptional || schema instanceof ZodNullable || schema instanceof ZodDefault || schema instanceof ZodCatch) {
|
|
4
|
+
return schema.def.innerType;
|
|
5
|
+
}
|
|
6
|
+
else if (schema instanceof ZodLazy) {
|
|
7
|
+
return schema.def.getter();
|
|
8
|
+
}
|
|
9
|
+
else if (schema instanceof ZodArray) {
|
|
10
|
+
return schema.element;
|
|
11
|
+
}
|
|
12
|
+
// Not a recognized wrapper type
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
export function isZodArray(obj) {
|
|
16
|
+
return isZodType(obj) && (obj.def.type === 'array');
|
|
17
|
+
}
|
|
18
|
+
export function isZodNullable(obj) {
|
|
19
|
+
return isZodType(obj) && (obj.def.type === 'nullable');
|
|
20
|
+
}
|
|
21
|
+
export function isZodObject(obj, isLoose) {
|
|
22
|
+
if (!isZodType(obj) || (obj.def.type !== 'object')) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
if (typeof isLoose === 'undefined') {
|
|
26
|
+
return true;
|
|
27
|
+
}
|
|
28
|
+
return isLoose === isZodUnknown(obj.def.catchall);
|
|
29
|
+
}
|
|
30
|
+
export function isZodOptional(obj) {
|
|
31
|
+
return isZodType(obj) && (obj.def.type === 'optional');
|
|
32
|
+
}
|
|
33
|
+
export function isZodType(obj) {
|
|
34
|
+
return typeof obj === 'object' && obj !== null && 'def' in obj && 'safeParse' in obj && typeof obj.safeParse === 'function';
|
|
35
|
+
}
|
|
36
|
+
export function isZodUnion(obj) {
|
|
37
|
+
return isZodType(obj) && (obj.def.type === 'union');
|
|
38
|
+
}
|
|
39
|
+
export function isZodUnknown(obj) {
|
|
40
|
+
return isZodType(obj) && (obj.def.type === 'unknown');
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import assert from 'node:assert';
|
|
2
|
+
import { describe, it } from 'node:test';
|
|
3
|
+
import { z } from 'zod/v4';
|
|
4
|
+
import { getInnerType, isZodObject, isZodOptional, isZodUnion, isZodUnknown } from './utilities.js';
|
|
5
|
+
describe('.utilities()', () => {
|
|
6
|
+
it('getInnerType identifies inner types', () => {
|
|
7
|
+
const optionalString = z.string().optional();
|
|
8
|
+
const nullableString = z.string().nullable();
|
|
9
|
+
const defaultString = z.string().default('default');
|
|
10
|
+
const catchString = z.string().catch('fallback');
|
|
11
|
+
const lazyString = z.lazy(() => z.string());
|
|
12
|
+
const arrayString = z.array(z.string());
|
|
13
|
+
assert.strictEqual(getInnerType(optionalString).def.type, optionalString.def.innerType.def.type);
|
|
14
|
+
assert.strictEqual(getInnerType(nullableString).def.type, nullableString.def.innerType.def.type);
|
|
15
|
+
assert.strictEqual(getInnerType(defaultString).def.type, defaultString.def.innerType.def.type);
|
|
16
|
+
assert.strictEqual(getInnerType(catchString).def.type, catchString.def.innerType.def.type);
|
|
17
|
+
assert.strictEqual(getInnerType(lazyString).def.type, lazyString.def.getter().def.type);
|
|
18
|
+
assert.strictEqual(getInnerType(arrayString).def.type, arrayString.element.def.type);
|
|
19
|
+
assert.strictEqual(getInnerType(z.string()), undefined); // Not a wrapper type
|
|
20
|
+
});
|
|
21
|
+
it('isZodObject detects ZodObject', () => {
|
|
22
|
+
const objectSchema = z.object({ name: z.string() });
|
|
23
|
+
const stringSchema = z.string();
|
|
24
|
+
assert.ok(isZodObject(objectSchema));
|
|
25
|
+
assert.ok(!isZodObject(stringSchema));
|
|
26
|
+
});
|
|
27
|
+
it('isZodObject should detect loose objects when isLoose is passed', () => {
|
|
28
|
+
assert.ok(isZodObject(z.looseObject({}), true));
|
|
29
|
+
assert.ok(!isZodObject(z.looseObject({}), false));
|
|
30
|
+
assert.ok(!isZodObject(z.strictObject({}), true));
|
|
31
|
+
assert.ok(isZodObject(z.strictObject({}), false));
|
|
32
|
+
});
|
|
33
|
+
it('isZodOptional detects ZodOptional', () => {
|
|
34
|
+
const optionalSchema = z.string().optional();
|
|
35
|
+
const stringSchema = z.string();
|
|
36
|
+
assert.ok(isZodOptional(optionalSchema));
|
|
37
|
+
assert.ok(!isZodOptional(stringSchema));
|
|
38
|
+
});
|
|
39
|
+
it('isZodUnion detects ZodUnion', () => {
|
|
40
|
+
const unionSchema = z.union([z.string(), z.number()]);
|
|
41
|
+
const stringSchema = z.string();
|
|
42
|
+
assert.ok(isZodUnion(unionSchema));
|
|
43
|
+
assert.ok(!isZodUnion(stringSchema));
|
|
44
|
+
});
|
|
45
|
+
it('isZodUnknown detects ZodUnknown', () => {
|
|
46
|
+
assert.ok(isZodUnknown(z.unknown()));
|
|
47
|
+
assert.ok(!isZodUnknown(z.string()));
|
|
48
|
+
});
|
|
49
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zodselect",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Type-safe selection utility built on top of Zod. Allows for strongly typed field selection, schema merging, and runtime refinement for frontend or backend use.",
|
|
5
|
+
"author": "SecureDataSystems",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"types": "dist/index.d.ts",
|
|
10
|
+
"files": [
|
|
11
|
+
"./dist",
|
|
12
|
+
"package.json",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./dist/index.d.ts",
|
|
19
|
+
"import": "./dist/index.js",
|
|
20
|
+
"default": "./dist/index.js"
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@securedatasystems/eslint-config": "^1.0.2",
|
|
25
|
+
"@securedatasystems/tsconfig": "^1.0.0",
|
|
26
|
+
"@types/node": "^24.0.0",
|
|
27
|
+
"glob": "^11.0.2",
|
|
28
|
+
"tsx": "^4.20.0",
|
|
29
|
+
"typescript": "^5.8.3"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"zod": "^3.25.58"
|
|
33
|
+
},
|
|
34
|
+
"scripts": {
|
|
35
|
+
"build": "node scripts/build.mjs && tsc --project tsconfig.build.json",
|
|
36
|
+
"deploy": "npm run build && node ./scripts/publish.mjs",
|
|
37
|
+
"lint": "eslint . --c eslint.config.mjs",
|
|
38
|
+
"lint:fix": "eslint . --c eslint.config.mjs --fix",
|
|
39
|
+
"test": "glob -c \"tsx --tsconfig ./tsconfig.build.json --test\" \"./src/**/*.{spec,test}.ts\""
|
|
40
|
+
}
|
|
41
|
+
}
|