Skip to content

Commit 47d2e6e

Browse files
authored
Merge pull request #14 from DouglasNeuroInformatics/dev
update to Zod v4
2 parents 82934ea + c29326f commit 47d2e6e

File tree

14 files changed

+255
-110
lines changed

14 files changed

+255
-110
lines changed

package.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,6 @@
1818
".": {
1919
"types": "./dist/index.d.ts",
2020
"import": "./dist/index.js"
21-
},
22-
"./vendor/*": {
23-
"types": "./dist/vendor/*.d.ts",
24-
"import": "./dist/vendor/*.js"
2521
}
2622
},
2723
"files": [
@@ -39,7 +35,7 @@
3935
},
4036
"peerDependencies": {
4137
"neverthrow": "^8.2.0",
42-
"zod": "^3.22.6"
38+
"zod": "^3.25.x"
4339
},
4440
"dependencies": {
4541
"clean-stack": "^5.2.0",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/__tests__/exception.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Err } from 'neverthrow';
12
import type { Simplify } from 'type-fest';
23
import { describe, expect, expectTypeOf, it, test } from 'vitest';
34

@@ -10,7 +11,6 @@ import {
1011
RuntimeException,
1112
ValueException
1213
} from '../exception.js';
13-
import { Err } from '../vendor/neverthrow.js';
1414

1515
import type { ExceptionConstructor } from '../exception.js';
1616

src/__tests__/http.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1+
import type { Err } from 'neverthrow';
12
import { afterEach, describe, expect, it, vi } from 'vitest';
23

34
import * as datetime from '../datetime.js';
45
import { safeFetch, waitForServer } from '../http.js';
56

6-
import type { Err } from '../vendor/neverthrow.js';
7-
87
const fetch = vi.hoisted(() => vi.fn());
98
const networkError = new Error('Network Error');
109

src/__tests__/result.test.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1+
import { Err, err, Ok, ok } from 'neverthrow';
12
import { describe, expect, it } from 'vitest';
23

3-
import { asyncResultify } from '../result.js';
4-
import { Err, err, Ok, ok } from '../vendor/neverthrow.js';
4+
import { asyncResultify, unwrap } from '../result.js';
55

66
describe('asyncResultify', () => {
77
it('should convert a successful Result to ResultAsync', async () => {
@@ -29,3 +29,13 @@ describe('asyncResultify', () => {
2929
expect(result.value).toBe('delayed result');
3030
});
3131
});
32+
33+
describe('unwrap', () => {
34+
it('should throw if the result is an Error', () => {
35+
const error = new Error('Something went wrong!');
36+
expect(() => unwrap(err(error))).toThrow(error);
37+
});
38+
it('should return if the result is Ok', () => {
39+
expect(unwrap(ok(5))).toBe(5);
40+
});
41+
});

src/__tests__/zod.test.ts

Lines changed: 124 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,134 @@
1-
import * as module from 'node:module';
1+
/* eslint-disable @typescript-eslint/no-require-imports */
2+
import { describe, expect, expectTypeOf, it, test } from 'vitest';
3+
import { z as z3 } from 'zod/v3';
4+
import { z as z4 } from 'zod/v4';
25

3-
import { describe, expect, it } from 'vitest';
4-
import { z } from 'zod';
6+
import { $BooleanLike, $NumberLike, $Uint8ArrayLike, $UrlLike, isZodType, isZodTypeLike, safeParse } from '../zod.js';
57

6-
import { $BooleanLike, $NumberLike, $Uint8ArrayLike, $UrlLike, isZodType, safeParse } from '../zod.js';
8+
import type {
9+
ZodErrorLike,
10+
ZodIssueLike,
11+
ZodSafeParseErrorLike,
12+
ZodSafeParseResultLike,
13+
ZodSafeParseSuccessLike,
14+
ZodTypeLike
15+
} from '../zod.js';
716

8-
const require = module.createRequire(import.meta.url);
17+
test('ZodIssueLike', () => {
18+
expectTypeOf<z3.ZodIssue>().toMatchTypeOf<ZodIssueLike>();
19+
expectTypeOf<z4.core.$ZodIssue>().toMatchTypeOf<ZodIssueLike>();
20+
});
921

10-
/** since we use require here, the prototype chain in the cjs module will be different than the esm build */
11-
const { z: zCJS } = require('zod') as typeof import('zod');
22+
test('ZodErrorLike', () => {
23+
expectTypeOf<z3.ZodError>().toMatchTypeOf<ZodErrorLike>();
24+
expectTypeOf<z4.core.$ZodError>().toMatchTypeOf<ZodErrorLike>();
25+
});
1226

13-
describe('isZodType', () => {
14-
it('should return true for an instance of ZodNumber', () => {
15-
expect(isZodType(z.number())).toBe(true);
27+
test('ZodSafeParseSuccessLike', () => {
28+
expectTypeOf<z3.SafeParseSuccess<number>>().toMatchTypeOf<ZodSafeParseSuccessLike<number>>();
29+
expectTypeOf<z4.ZodSafeParseSuccess<number>>().toMatchTypeOf<ZodSafeParseSuccessLike<number>>();
30+
});
31+
32+
test('ZodSafeParseErrorLike', () => {
33+
expectTypeOf<z3.SafeParseError<number>>().toMatchTypeOf<ZodSafeParseErrorLike>();
34+
expectTypeOf<z4.ZodSafeParseError<number>>().toMatchTypeOf<ZodSafeParseErrorLike>();
35+
});
36+
37+
test('ZodSafeParseResultLike', () => {
38+
expectTypeOf<z3.SafeParseReturnType<number, number>>().toMatchTypeOf<ZodSafeParseResultLike<number>>();
39+
expectTypeOf<z4.ZodSafeParseResult<number>>().toMatchTypeOf<ZodSafeParseResultLike<number>>();
40+
});
41+
42+
test('ZodTypeLike', () => {
43+
expectTypeOf<z3.ZodTypeAny>().toMatchTypeOf<ZodTypeLike<any>>();
44+
expectTypeOf<z3.ZodTypeAny>().toMatchTypeOf<ZodTypeLike<unknown>>();
45+
expectTypeOf<z3.ZodNumber>().toMatchTypeOf<ZodTypeLike<unknown>>();
46+
expectTypeOf<z3.ZodNumber>().toMatchTypeOf<ZodTypeLike<number>>();
47+
expectTypeOf<z3.ZodNumber>().not.toMatchTypeOf<ZodTypeLike<string>>();
48+
expectTypeOf<z4.ZodType>().toMatchTypeOf<ZodTypeLike<any>>();
49+
expectTypeOf<z4.ZodType>().toMatchTypeOf<ZodTypeLike<unknown>>();
50+
expectTypeOf<z4.ZodNumber>().toMatchTypeOf<ZodTypeLike<unknown>>();
51+
expectTypeOf<z4.ZodNumber>().toMatchTypeOf<ZodTypeLike<number>>();
52+
expectTypeOf<z4.ZodNumber>().not.toMatchTypeOf<ZodTypeLike<string>>();
53+
});
54+
55+
describe('isZodTypeLike', () => {
56+
it('should return true for Zod v3 types', () => {
57+
expect(isZodTypeLike(z3.object({}))).toBe(true);
58+
expect(isZodTypeLike(z3.number())).toBe(true);
59+
expect(isZodTypeLike(z3.any())).toBe(true);
1660
});
17-
it('should return true for an instance of ZodObject', () => {
18-
expect(isZodType(z.object({}))).toBe(true);
61+
it('should return true for Zod v4 types', () => {
62+
expect(isZodTypeLike(z4.object({}))).toBe(true);
63+
expect(isZodTypeLike(z4.number())).toBe(true);
64+
expect(isZodTypeLike(z4.any())).toBe(true);
1965
});
20-
it('should return false for null', () => {
21-
expect(isZodType(null)).toBe(false);
66+
it('should return false for an object without a standard schema', () => {
67+
expect(isZodTypeLike({})).toBe(false);
2268
});
23-
it('should return false for any empty object', () => {
24-
expect(isZodType({})).toBe(false);
69+
it('should return false for an object with a standard schema, but the incorrect vendor name', () => {
70+
expect(isZodTypeLike({ '~standard': { vendor: 'DNP' } })).toBe(false);
2571
});
26-
it('should return false for an object with a null prototype', () => {
27-
expect(isZodType(Object.create(null))).toBe(false);
72+
});
73+
74+
describe('isZodType', () => {
75+
describe('v3', () => {
76+
// since we use require here, the prototype chain in the cjs module will be different than the esm build
77+
const { z: z3_2 } = require('zod/v3') as typeof import('zod/v3');
78+
79+
it('should return true for an instance of ZodNumber', () => {
80+
expect(isZodType(z3.number(), { version: 3 })).toBe(true);
81+
});
82+
it('should return true for an instance of ZodObject', () => {
83+
expect(isZodType(z3.object({}), { version: 3 })).toBe(true);
84+
});
85+
it('should return false for null', () => {
86+
expect(isZodType(null, { version: 3 })).toBe(false);
87+
});
88+
it('should return false for any empty object', () => {
89+
expect(isZodType({}, { version: 3 })).toBe(false);
90+
});
91+
it('should return false for an object with a null prototype', () => {
92+
expect(isZodType(Object.create(null), { version: 3 })).toBe(false);
93+
});
94+
it('should return false for a Zod v4 object', () => {
95+
expect(isZodType(z4.object({}), { version: 3 })).toBe(false);
96+
});
97+
it('should return true for a ZodObject created in a different context', () => {
98+
const input = z3_2.object({});
99+
expect(input).not.toBeInstanceOf(z3.ZodType);
100+
expect(isZodType(input, { version: 3 })).toBe(true);
101+
});
28102
});
29-
it('should return true for a ZodObject created in a different context', () => {
30-
const input = zCJS.object({});
31-
expect(input).not.toBeInstanceOf(z.ZodType);
32-
expect(isZodType(input)).toBe(true);
103+
describe('v4', () => {
104+
it('should return true for an instance of ZodNumber', () => {
105+
expect(isZodType(z4.number(), { version: 4 })).toBe(true);
106+
});
107+
it('should return true for an instance of ZodObject', () => {
108+
expect(isZodType(z4.object({}), { version: 4 })).toBe(true);
109+
});
110+
it('should return false for null', () => {
111+
expect(isZodType(null, { version: 4 })).toBe(false);
112+
});
113+
it('should return false for any empty object', () => {
114+
expect(isZodType({}, { version: 4 })).toBe(false);
115+
});
116+
it('should return false for a Zod v3 object', () => {
117+
expect(isZodType(z3.object({}), { version: 4 })).toBe(false);
118+
});
119+
it('should return true for a ZodObject created in a different context', () => {
120+
const base = z4.object({});
121+
const input = {
122+
_zod: {
123+
version: structuredClone(base._zod.version)
124+
},
125+
'~standard': {
126+
vendor: base['~standard'].vendor
127+
}
128+
};
129+
expect(input).not.toBeInstanceOf(z4.ZodType);
130+
expect(isZodType(input, { version: 4 })).toBe(true);
131+
});
33132
});
34133
});
35134

@@ -66,7 +165,7 @@ describe('$NumberLike', () => {
66165
expect($NumberLike.safeParse('').success).toBe(false);
67166
});
68167
it('should allow restricting the number through a pipe', () => {
69-
const $IntLike = $NumberLike.pipe(z.number().int());
168+
const $IntLike = $NumberLike.pipe(z4.number().int());
70169
expect($IntLike.safeParse('1.1').success).toBe(false);
71170
expect($IntLike.safeParse('1').data).toBe(1);
72171
});
@@ -118,7 +217,7 @@ describe('$Uint8ArrayLike', () => {
118217
});
119218

120219
describe('safeParse', () => {
121-
const $Schema = z.object({ foo: z.enum(['1', '2']).transform(Number) });
220+
const $Schema = z4.object({ foo: z4.enum(['1', '2']).transform(Number) });
122221
it('should return an Ok result with the parsed data if successful', () => {
123222
const result = safeParse({ foo: '1' }, $Schema);
124223
expect(result.isOk() && result.value).toStrictEqual({ foo: 1 });
@@ -132,7 +231,7 @@ describe('safeParse', () => {
132231
},
133232
issues: [
134233
expect.objectContaining({
135-
code: z.ZodIssueCode.invalid_enum_value,
234+
code: 'invalid_value',
136235
path: ['foo']
137236
})
138237
]

src/datetime.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { OutOfRangeException } from './exception.js';
2-
import { err, ok } from './vendor/neverthrow.js';
1+
import { err, ok } from 'neverthrow';
2+
import type { Result } from 'neverthrow';
33

4-
import type { Result } from './vendor/neverthrow.js';
4+
import { OutOfRangeException } from './exception.js';
55

66
export type Duration = {
77
days: number;

src/exception.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@
44

55
import cleanStack from 'clean-stack';
66
import extractStack from 'extract-stack';
7+
import { err, errAsync, Result, ResultAsync } from 'neverthrow';
78
import { isErrorLike, serializeError } from 'serialize-error';
89
import stringifyObject from 'stringify-object';
910
import type { IsNever, RequiredKeysOf } from 'type-fest';
10-
import type { z } from 'zod';
11+
import type { z } from 'zod/v4';
1112

1213
import { objectify } from './object.js';
1314
import { indentLines } from './string.js';
14-
import { err, errAsync, Result, ResultAsync } from './vendor/neverthrow.js';
1515

1616
import type { SingleKeyMap, ToAbstractConstructor } from './types.js';
1717

@@ -233,7 +233,7 @@ const { OutOfRangeException } = new ExceptionBuilder()
233233
.build();
234234

235235
export const { ValidationException } = new ExceptionBuilder()
236-
.setOptionsType<{ details: { data: unknown; issues: z.ZodIssue[] } }>()
236+
.setOptionsType<{ details: { data: unknown; issues: z.core.$ZodIssue[] } }>()
237237
.setParams({ message: 'Zod schema validation failed', name: 'ValidationException' })
238238
.build();
239239

src/http.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1+
import { ok, ResultAsync } from 'neverthrow';
2+
13
import { sleep } from './datetime.js';
24
import { ExceptionBuilder, RuntimeException } from './exception.js';
35
import { asyncResultify } from './result.js';
4-
import { ok, ResultAsync } from './vendor/neverthrow.js';
56

67
import type { ExceptionLike } from './exception.js';
78

src/random.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { ValueException } from './exception.js';
2-
import { ok } from './vendor/neverthrow.js';
1+
import { ok } from 'neverthrow';
2+
import type { Result } from 'neverthrow';
33

4-
import type { Result } from './vendor/neverthrow.js';
4+
import { ValueException } from './exception.js';
55

66
/** Returns a random integer between `min` (inclusive) and `max` (not inclusive) */
77
export function randomInt(min: number, max: number): Result<number, typeof ValueException.Instance> {

0 commit comments

Comments
 (0)