Skip to content

Commit 685f974

Browse files
committed
ci
1 parent 044e96e commit 685f974

8 files changed

Lines changed: 131 additions & 57 deletions

File tree

.github/workflows/ci.yml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request: ~
8+
9+
jobs:
10+
ci:
11+
name: Lint and test
12+
runs-on: ubuntu-latest
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v4
16+
- name: Setup Node
17+
uses: actions/setup-node@v4
18+
with:
19+
node-version: "current"
20+
- name: Install dependencies
21+
run: npm install
22+
- name: Check formatting
23+
run: npm run lint
24+
- name: Run tests
25+
run: npm test

.github/workflows/release.yml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Release
2+
3+
on:
4+
push:
5+
tags:
6+
- "v*"
7+
workflow_dispatch: ~
8+
9+
permissions:
10+
id-token: write
11+
contents: read
12+
13+
jobs:
14+
release:
15+
name: Create and publish a release
16+
runs-on: ubuntu-latest
17+
steps:
18+
- name: Checkout
19+
uses: actions/checkout@v4
20+
- name: Setup Node
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: "current"
24+
registry-url: https://registry.npmjs.org
25+
- name: Install dependencies
26+
run: npm install
27+
- name: Run tests
28+
run: npm test
29+
- name: Publish to npm
30+
run: npm publish --provenance --access public

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules

README.md

Lines changed: 39 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,19 @@ Requires Zod v4.
1515
## Quick Start
1616

1717
```js
18-
import { createSchemas } from '@api-platform/zod'
19-
import { safeParse } from 'zod/v4'
18+
import { createSchemas } from "@api-platform/zod";
19+
import { safeParse } from "zod/v4";
2020

2121
// Generate schemas from an API entrypoint
22-
const { schemas, collections } = await createSchemas('https://api.example.com')
22+
const { schemas, collections } = await createSchemas("https://api.example.com");
2323

2424
// schemas.Book -> z.looseObject({ '@id': z.string(), '@type': z.literal('Book'), title: z.string(), ... })
2525
// collections.Book -> Hydra collection schema wrapping Book
2626

2727
// Validate API responses
28-
const result = safeParse(schemas.Book, responseData)
28+
const result = safeParse(schemas.Book, responseData);
2929
if (result.success) {
30-
console.log(result.data.title)
30+
console.log(result.data.title);
3131
}
3232
```
3333

@@ -38,14 +38,18 @@ if (result.success) {
3838
High-level function that fetches and parses API documentation, then generates Zod schemas.
3939

4040
```js
41-
const { schemas, collections, resources, api, response } = await createSchemas('https://api.example.com')
41+
const { schemas, collections, resources, api, response } = await createSchemas(
42+
"https://api.example.com",
43+
);
4244
```
4345

4446
**Parameters:**
47+
4548
- `entrypoint` — The API entrypoint URL
4649
- `options` — Options passed to `parseHydraDocumentation`
4750

4851
**Returns:**
52+
4953
- `schemas``{ [ResourceName]: ZodSchema }` for each resource
5054
- `collections``{ [ResourceName]: ZodSchema }` Hydra collection schemas
5155
- `resources` — The parsed resource objects from api-doc-parser
@@ -57,59 +61,59 @@ const { schemas, collections, resources, api, response } = await createSchemas('
5761
Lower-level function that works with pre-parsed Resource objects (from api-doc-parser). Useful when you already have the parsed API documentation.
5862

5963
```js
60-
import { schemasFromResources } from '@api-platform/zod'
64+
import { schemasFromResources } from "@api-platform/zod";
6165

62-
const { schemas, collections } = schemasFromResources(api.resources)
66+
const { schemas, collections } = schemasFromResources(api.resources);
6367
```
6468

6569
### `resourceToSchema(resource, schemaMap?)`
6670

6771
Converts a single api-doc-parser Resource into a `z.looseObject` schema with `@id`, `@type`, and all readable fields.
6872

6973
```js
70-
import { resourceToSchema } from '@api-platform/zod'
74+
import { resourceToSchema } from "@api-platform/zod";
7175

72-
const bookSchema = resourceToSchema(bookResource)
76+
const bookSchema = resourceToSchema(bookResource);
7377
```
7478

7579
### `fieldToZod(field, schemaMap?)`
7680

7781
Converts a single api-doc-parser Field into a Zod type.
7882

7983
```js
80-
import { fieldToZod } from '@api-platform/zod'
84+
import { fieldToZod } from "@api-platform/zod";
8185

82-
const zodType = fieldToZod(field)
86+
const zodType = fieldToZod(field);
8387
```
8488

8589
### `collectionSchema(itemSchema)`
8690

8791
Creates a Hydra collection schema wrapping the given item schema.
8892

8993
```js
90-
import { collectionSchema } from '@api-platform/zod'
94+
import { collectionSchema } from "@api-platform/zod";
9195

92-
const booksCollectionSchema = collectionSchema(bookSchema)
96+
const booksCollectionSchema = collectionSchema(bookSchema);
9397
```
9498

9599
## Type Mapping
96100

97-
| Field Type | Zod Type |
98-
|---|---|
99-
| `string`, `password`, `byte`, `binary`, `hexBinary`, `base64Binary`, `duration` | `z.string()` |
100-
| `email` | `z.string().email()` |
101-
| `url` | `z.string().url()` |
102-
| `uuid` | `z.string().uuid()` |
103-
| `integer` | `z.int()` |
104-
| `positiveInteger` | `z.int().min(1)` |
105-
| `negativeInteger` | `z.int().max(-1)` |
106-
| `nonNegativeInteger` | `z.int().min(0)` |
107-
| `nonPositiveInteger` | `z.int().max(0)` |
108-
| `number`, `decimal`, `double`, `float` | `z.number()` |
109-
| `boolean` | `z.boolean()` |
110-
| `date` | `z.string().date()` |
111-
| `dateTime` | `z.string().datetime()` |
112-
| `time` | `z.string().time()` |
101+
| Field Type | Zod Type |
102+
| ------------------------------------------------------------------------------- | ----------------------- |
103+
| `string`, `password`, `byte`, `binary`, `hexBinary`, `base64Binary`, `duration` | `z.string()` |
104+
| `email` | `z.string().email()` |
105+
| `url` | `z.string().url()` |
106+
| `uuid` | `z.string().uuid()` |
107+
| `integer` | `z.int()` |
108+
| `positiveInteger` | `z.int().min(1)` |
109+
| `negativeInteger` | `z.int().max(-1)` |
110+
| `nonNegativeInteger` | `z.int().min(0)` |
111+
| `nonPositiveInteger` | `z.int().max(0)` |
112+
| `number`, `decimal`, `double`, `float` | `z.number()` |
113+
| `boolean` | `z.boolean()` |
114+
| `date` | `z.string().date()` |
115+
| `dateTime` | `z.string().datetime()` |
116+
| `time` | `z.string().time()` |
113117

114118
### Special Cases
115119

@@ -127,11 +131,11 @@ Schemas use `z.looseObject()`, which allows unknown properties to pass through w
127131
```js
128132
// Extra fields in the response are preserved, not stripped
129133
const result = safeParse(schemas.Book, {
130-
'@id': '/api/books/1',
131-
'@type': 'Book',
132-
title: 'The Great Gatsby',
133-
newFieldAddedLater: 'still available',
134-
})
134+
"@id": "/api/books/1",
135+
"@type": "Book",
136+
title: "The Great Gatsby",
137+
newFieldAddedLater: "still available",
138+
});
135139
// result.data.newFieldAddedLater === 'still available'
136140
```
137141

package-lock.json

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

package.json

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,9 @@
88
".": "./src/index.js"
99
},
1010
"scripts": {
11-
"test": "node --experimental-vm-modules node_modules/.bin/jest"
11+
"test": "node --experimental-vm-modules node_modules/.bin/jest",
12+
"lint": "prettier --check .",
13+
"format": "prettier --write ."
1214
},
1315
"peerDependencies": {
1416
"zod": "^4.0.0"
@@ -17,8 +19,9 @@
1719
"@api-platform/api-doc-parser": "^0.16.9"
1820
},
1921
"devDependencies": {
20-
"zod": "^4.0.0",
22+
"@jest/globals": "^30.0.0",
2123
"jest": "^30.0.0",
22-
"@jest/globals": "^30.0.0"
24+
"prettier": "^3.8.1",
25+
"zod": "^4.0.0"
2326
}
2427
}

src/collection.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export function collectionSchema(itemSchema) {
2222
last: z.optional(z.string()),
2323
previous: z.optional(z.string()),
2424
next: z.optional(z.string()),
25-
})
25+
}),
2626
),
2727
search: z.optional(
2828
z.looseObject({
@@ -36,10 +36,10 @@ export function collectionSchema(itemSchema) {
3636
variable: z.string(),
3737
property: z.optional(z.nullable(z.string())),
3838
required: z.optional(z.boolean()),
39-
})
40-
)
39+
}),
40+
),
4141
),
42-
})
42+
}),
4343
),
4444
});
4545
}

test/index.test.js

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ describe("fieldToZod", () => {
5454
it("maps uuid to z.string().uuid()", () => {
5555
const schema = fieldToZod(makeField({ type: "uuid" }));
5656
expect(
57-
z.safeParse(schema, "550e8400-e29b-41d4-a716-446655440000").success
57+
z.safeParse(schema, "550e8400-e29b-41d4-a716-446655440000").success,
5858
).toBe(true);
5959
expect(z.safeParse(schema, "not-a-uuid").success).toBe(false);
6060
});
@@ -129,7 +129,7 @@ describe("fieldToZod", () => {
129129

130130
it("handles reference fields as z.string()", () => {
131131
const schema = fieldToZod(
132-
makeField({ reference: { name: "Author", title: "Author" } })
132+
makeField({ reference: { name: "Author", title: "Author" } }),
133133
);
134134
expect(z.safeParse(schema, "/api/authors/1").success).toBe(true);
135135
expect(z.safeParse(schema, 123).success).toBe(false);
@@ -144,17 +144,17 @@ describe("fieldToZod", () => {
144144

145145
const schema = fieldToZod(
146146
makeField({ embedded: { name: "Author", title: "Author" } }),
147-
schemaMap
147+
schemaMap,
148148
);
149149

150150
expect(
151-
z.safeParse(schema, { "@id": "/api/authors/1", name: "Jane" }).success
151+
z.safeParse(schema, { "@id": "/api/authors/1", name: "Jane" }).success,
152152
).toBe(true);
153153
});
154154

155155
it("handles enum fields", () => {
156156
const schema = fieldToZod(
157-
makeField({ enum: ["draft", "published", "archived"] })
157+
makeField({ enum: ["draft", "published", "archived"] }),
158158
);
159159
expect(z.safeParse(schema, "draft").success).toBe(true);
160160
expect(z.safeParse(schema, "published").success).toBe(true);
@@ -169,16 +169,14 @@ describe("fieldToZod", () => {
169169
});
170170

171171
it("handles optional fields (required === false)", () => {
172-
const schema = fieldToZod(
173-
makeField({ type: "string", required: false })
174-
);
172+
const schema = fieldToZod(makeField({ type: "string", required: false }));
175173
expect(z.safeParse(schema, "hello").success).toBe(true);
176174
expect(z.safeParse(schema, undefined).success).toBe(true);
177175
});
178176

179177
it("handles array fields (maxCardinality !== 1)", () => {
180178
const schema = fieldToZod(
181-
makeField({ type: "string", maxCardinality: null })
179+
makeField({ type: "string", maxCardinality: null }),
182180
);
183181
expect(z.safeParse(schema, ["a", "b"]).success).toBe(true);
184182
expect(z.safeParse(schema, "a").success).toBe(false);
@@ -425,9 +423,7 @@ describe("schemasFromResources", () => {
425423

426424
it("builds collection schemas that validate", () => {
427425
const resources = [
428-
makeResource("Book", [
429-
makeField({ name: "title", type: "string" }),
430-
]),
426+
makeResource("Book", [makeField({ name: "title", type: "string" })]),
431427
];
432428

433429
const { collections } = schemasFromResources(resources);
@@ -436,9 +432,7 @@ describe("schemasFromResources", () => {
436432
"@id": "/api/books",
437433
"@type": "Collection",
438434
totalItems: 1,
439-
member: [
440-
{ "@id": "/api/books/1", "@type": "Book", title: "Test" },
441-
],
435+
member: [{ "@id": "/api/books/1", "@type": "Book", title: "Test" }],
442436
});
443437

444438
expect(result.success).toBe(true);

0 commit comments

Comments
 (0)