Skip to content

Commit 05fb980

Browse files
committed
Merge branch 'release/v1.1.0'
2 parents 546922a + 2c4c04d commit 05fb980

6 files changed

Lines changed: 633 additions & 2 deletions

File tree

.github/copilot-instructions.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Zeed AI Coding Instructions
22

3+
Ignore files in the folders: `_archive`, `dist` and `docs`.
4+
35
## Project Structure & Architecture
46

57
- **Universal TypeScript library**: Code is written in strict TypeScript and targets browser, Node.js, Deno, and Bun. See `src/` for main modules.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "zeed",
33
"type": "module",
4-
"version": "1.0.21",
4+
"version": "1.1.0",
55
"packageManager": "pnpm@10.18.0",
66
"description": "🌱 Simple foundation library",
77
"author": {
@@ -68,7 +68,7 @@
6868
"lint:fix": "eslint . --fix",
6969
"prepublishOnly": "nr build && nr circles && nr test:publish",
7070
"start": "nr watch",
71-
"test": "vitest",
71+
"test": "nr check && vitest --run",
7272
"test:release": "nr lint:fix && nr check && vitest --run",
7373
"post:release": "nr upload:docs",
7474
"test:browser": "PREVIEW=1 vitest",

src/common/schema/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ export * from './parse-args'
55
export * from './parse-env'
66
export * from './parse-object'
77
export * from './schema'
8+
export * from './schema-standard'
89
export * from './serialize'
910
export * from './type-test'
1011
export * from './utils'
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
import type { StandardSchemaV1 } from './schema-standard'
2+
import { describe, expect, it } from 'vitest'
3+
import { any, array, boolean, int, literal, number, object, record, string, stringLiterals, tuple, union } from './schema'
4+
5+
describe('standard-schema compatibility', () => {
6+
it('should have ~standard property', () => {
7+
const schema = string()
8+
expect(schema).toHaveProperty('~standard')
9+
expect(schema['~standard']).toBeDefined()
10+
})
11+
12+
it('should have correct vendor and version', () => {
13+
const schema = string()
14+
const standard = schema['~standard']
15+
expect(standard.version).toBe(1)
16+
expect(standard.vendor).toBe('zeed')
17+
})
18+
19+
it('should have validate function', () => {
20+
const schema = string()
21+
const standard = schema['~standard']
22+
expect(standard.validate).toBeInstanceOf(Function)
23+
})
24+
25+
it('should validate string successfully', () => {
26+
const schema = string()
27+
const result = schema['~standard'].validate('hello')
28+
expect(result).toEqual({ value: 'hello' })
29+
expect(result.issues).toBeUndefined()
30+
})
31+
32+
it('should fail string validation', () => {
33+
const schema = string()
34+
const result = schema['~standard'].validate(42)
35+
expect(result.issues).toBeDefined()
36+
expect(result.issues).toHaveLength(1)
37+
expect(result.issues![0].message).toContain('Expected string')
38+
})
39+
40+
it('should validate number successfully', () => {
41+
const schema = number()
42+
const result = schema['~standard'].validate(42.5)
43+
expect(result).toEqual({ value: 42.5 })
44+
})
45+
46+
it('should validate integer successfully', () => {
47+
const schema = int()
48+
const result = schema['~standard'].validate(42)
49+
expect(result).toEqual({ value: 42 })
50+
})
51+
52+
it('should fail integer validation for float', () => {
53+
const schema = int()
54+
const result = schema['~standard'].validate(42.5)
55+
expect(result.issues).toBeDefined()
56+
expect(result.issues).toHaveLength(1)
57+
})
58+
59+
it('should validate boolean successfully', () => {
60+
const schema = boolean()
61+
const result = schema['~standard'].validate(true)
62+
expect(result).toEqual({ value: true })
63+
})
64+
65+
it('should validate optional values', () => {
66+
const schema = string().optional()
67+
68+
const result1 = schema['~standard'].validate(undefined)
69+
expect(result1).toEqual({ value: undefined })
70+
71+
const result2 = schema['~standard'].validate(null)
72+
expect(result2).toEqual({ value: undefined })
73+
74+
const result3 = schema['~standard'].validate('hello')
75+
expect(result3).toEqual({ value: 'hello' })
76+
})
77+
78+
it('should use default values', () => {
79+
const schema = string().default('default-value')
80+
const result = schema['~standard'].validate(undefined)
81+
expect(result).toEqual({ value: 'default-value' })
82+
})
83+
84+
it('should use default function', () => {
85+
const schema = string().default(() => 'computed-default')
86+
const result = schema['~standard'].validate(null)
87+
expect(result).toEqual({ value: 'computed-default' })
88+
})
89+
90+
it('should validate literal values', () => {
91+
const schema = literal('hello')
92+
93+
const result1 = schema['~standard'].validate('hello')
94+
expect(result1).toEqual({ value: 'hello' })
95+
96+
const result2 = schema['~standard'].validate('world')
97+
expect(result2.issues).toBeDefined()
98+
expect(result2.issues![0].message).toContain('Expected literal')
99+
})
100+
101+
it('should validate string literals (enums)', () => {
102+
const schema = stringLiterals(['active', 'inactive', 'pending'])
103+
104+
const result1 = schema['~standard'].validate('active')
105+
expect(result1).toEqual({ value: 'active' })
106+
107+
const result2 = schema['~standard'].validate('invalid')
108+
expect(result2.issues).toBeDefined()
109+
expect(result2.issues![0].message).toContain('Expected one of')
110+
})
111+
112+
it('should validate union types', () => {
113+
const schema = union([string(), number()])
114+
115+
const result1 = schema['~standard'].validate('hello')
116+
expect(result1).toEqual({ value: 'hello' })
117+
118+
const result2 = schema['~standard'].validate(42)
119+
expect(result2).toEqual({ value: 42 })
120+
121+
const result3 = schema['~standard'].validate(true)
122+
expect(result3.issues).toBeDefined()
123+
expect(result3.issues![0].message).toContain('does not match any union')
124+
})
125+
126+
it('should validate array types', () => {
127+
const schema = array(string())
128+
129+
const result1 = schema['~standard'].validate(['a', 'b', 'c'])
130+
expect(result1).toEqual({ value: ['a', 'b', 'c'] })
131+
132+
const result2 = schema['~standard'].validate(['a', 42, 'c'])
133+
expect(result2.issues).toBeDefined()
134+
expect(result2.issues![0].path).toEqual([1])
135+
})
136+
137+
it('should validate tuple types', () => {
138+
const schema = tuple([string(), number(), boolean()])
139+
140+
const result1 = schema['~standard'].validate(['hello', 42, true])
141+
expect(result1).toEqual({ value: ['hello', 42, true] })
142+
143+
const result2 = schema['~standard'].validate(['hello', 42])
144+
expect(result2.issues).toBeDefined()
145+
expect(result2.issues![0].message).toContain('Expected tuple of length 3')
146+
147+
const result3 = schema['~standard'].validate(['hello', 'wrong', true])
148+
expect(result3.issues).toBeDefined()
149+
expect(result3.issues![0].path).toEqual([1])
150+
})
151+
152+
it('should validate object types', () => {
153+
const schema = object({
154+
name: string(),
155+
age: number(),
156+
active: boolean(),
157+
})
158+
159+
const result1 = schema['~standard'].validate({
160+
name: 'Alice',
161+
age: 30,
162+
active: true,
163+
})
164+
expect(result1.value).toEqual({
165+
name: 'Alice',
166+
age: 30,
167+
active: true,
168+
})
169+
expect(result1.issues).toBeUndefined()
170+
171+
const result2 = schema['~standard'].validate({
172+
name: 'Bob',
173+
age: 'invalid',
174+
active: true,
175+
})
176+
expect(result2.issues).toBeDefined()
177+
expect(result2.issues![0].path).toEqual(['age'])
178+
})
179+
180+
it('should validate nested objects', () => {
181+
const schema = object({
182+
user: object({
183+
name: string(),
184+
profile: object({
185+
bio: string(),
186+
}),
187+
}),
188+
})
189+
190+
const result1 = schema['~standard'].validate({
191+
user: {
192+
name: 'Alice',
193+
profile: {
194+
bio: 'Developer',
195+
},
196+
},
197+
})
198+
expect(result1.issues).toBeUndefined()
199+
200+
const result2 = schema['~standard'].validate({
201+
user: {
202+
name: 'Bob',
203+
profile: {
204+
bio: 123,
205+
},
206+
},
207+
})
208+
expect(result2.issues).toBeDefined()
209+
expect(result2.issues![0].path).toEqual(['user', 'profile', 'bio'])
210+
})
211+
212+
it('should validate optional object properties', () => {
213+
const schema = object({
214+
name: string(),
215+
age: number().optional(),
216+
})
217+
218+
const result1 = schema['~standard'].validate({
219+
name: 'Alice',
220+
})
221+
expect(result1.issues).toBeUndefined()
222+
223+
const result2 = schema['~standard'].validate({
224+
name: 'Bob',
225+
age: 30,
226+
})
227+
expect(result2.issues).toBeUndefined()
228+
})
229+
230+
it('should validate record types', () => {
231+
const schema = record(number())
232+
233+
const result1 = schema['~standard'].validate({
234+
a: 1,
235+
b: 2,
236+
c: 3,
237+
})
238+
expect(result1.issues).toBeUndefined()
239+
240+
const result2 = schema['~standard'].validate({
241+
a: 1,
242+
b: 'invalid',
243+
c: 3,
244+
})
245+
expect(result2.issues).toBeDefined()
246+
expect(result2.issues![0].path).toEqual(['b'])
247+
})
248+
249+
it('should support type inference helpers', () => {
250+
const schema = object({
251+
name: string(),
252+
age: number(),
253+
})
254+
255+
const standard = schema['~standard']
256+
expect(standard.types).toBeDefined()
257+
258+
// Type assertions to verify the types property exists
259+
type Input = StandardSchemaV1.InferInput<typeof schema>
260+
type Output = StandardSchemaV1.InferOutput<typeof schema>
261+
262+
// These are compile-time checks that the types are correctly inferred
263+
const _inputTest: Input = { name: 'test', age: 42 }
264+
const _outputTest: Output = { name: 'test', age: 42 }
265+
})
266+
267+
it('should be usable with generic standard-schema validators', () => {
268+
// This mimics how a third-party library would use standard schemas
269+
function standardValidate<T extends StandardSchemaV1>(
270+
schema: T,
271+
value: unknown,
272+
): StandardSchemaV1.InferOutput<T> | undefined {
273+
const result = schema['~standard'].validate(value) as any
274+
if (result.issues) {
275+
return undefined
276+
}
277+
return result.value
278+
}
279+
280+
const stringSchema = string()
281+
const result1 = standardValidate(stringSchema, 'hello')
282+
expect(result1).toBe('hello')
283+
284+
const result2 = standardValidate(stringSchema, 42)
285+
expect(result2).toBeUndefined()
286+
287+
const objectSchema = object({ name: string(), age: number() })
288+
const result3 = standardValidate(objectSchema, { name: 'Alice', age: 30 })
289+
expect(result3).toEqual({ name: 'Alice', age: 30 })
290+
})
291+
292+
it('should handle any type', () => {
293+
const schema = any()
294+
295+
const result1 = schema['~standard'].validate('hello')
296+
expect(result1).toEqual({ value: 'hello' })
297+
298+
const result2 = schema['~standard'].validate(42)
299+
expect(result2).toEqual({ value: 42 })
300+
301+
const result3 = schema['~standard'].validate({ key: 'value' })
302+
expect(result3).toEqual({ value: { key: 'value' } })
303+
304+
// any() rejects null/undefined unless made optional
305+
const result4 = schema['~standard'].validate(null)
306+
expect(result4.issues).toBeDefined()
307+
})
308+
309+
it('should validate with path information in issues', () => {
310+
const schema = object({
311+
items: array(object({
312+
name: string(),
313+
tags: array(string()),
314+
})),
315+
})
316+
317+
const result = schema['~standard'].validate({
318+
items: [
319+
{ name: 'Item1', tags: ['a', 'b'] },
320+
{ name: 'Item2', tags: ['c', 42] },
321+
],
322+
})
323+
324+
expect(result.issues).toBeDefined()
325+
expect(result.issues![0].path).toEqual(['items', 1, 'tags', 1])
326+
})
327+
})

0 commit comments

Comments
 (0)