peta-docs 0.3.0 → 0.3.2

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.
Files changed (2) hide show
  1. package/README.md +128 -234
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,242 +1,174 @@
1
- # peta-hono
1
+ # peta-docs
2
2
 
3
- OpenAPI + [Scalar](https://scalar.com) docs for [Hono](https://hono.dev), powered by [ArkType](https://arktype.io).
3
+ [![npm version](https://img.shields.io/npm/v/peta-docs?style=flat-square)](https://www.npmjs.com/package/peta-docs)
4
+ [![TypeScript](https://img.shields.io/badge/TypeScript-6.0-blue?style=flat-square&logo=typescript)](https://www.typescriptlang.org)
5
+ [![License](https://img.shields.io/badge/License-MIT-yellow?style=flat-square)](LICENSE)
4
6
 
5
- Define routes with ArkType schemas — get an OpenAPI 3.1 spec, auto-generated request validation, and an interactive API reference UI.
7
+ OpenAPI 3.1 + [Scalar](https://scalar.com) docs for [Hono](https://hono.dev), powered by [ArkType](https://arktype.io).
6
8
 
7
- ## Features
8
-
9
- - **ArkType-first** — schemas are ArkType types. Validation + docs from one definition.
10
- - **Auto-validation** — schemas in the `route()` chain generate runtime validators. Invalid requests return `{ error: 'Validation failed', issues }` with status 400 before the handler runs.
11
- - **Auto-load routes** — `loadRoutes()` discovers and mounts route modules from the filesystem.
12
- - **Shorthand responses** — `200: Pet` instead of `200: { description, content: { 'application/json': { schema: Pet } } }`.
13
- - **Status code autocomplete** — `StatusCode` type provides autocomplete for `200`, `201`, `400`, `404`, `500`, etc.
14
- - **OpenAPI 3.1** — full JSON Schema 2020-12 compatibility.
15
- - **Scalar UI** — one-liner to serve the Scalar API reference.
16
- - **`c.req.valid('json')`** — typed body access with no extra imports or casts.
17
-
18
- ## Install
19
-
20
- ```bash
21
- bun add peta-hono hono arktype
22
- ```
23
-
24
- ## Usage
9
+ Define routes with ArkType schemas — get an OpenAPI 3.1 spec, auto-generated request validation, and an interactive API reference UI, all from a single source of truth.
25
10
 
26
11
  ```ts
27
- import { Hono } from "hono";
28
- import { type } from "arktype";
29
- import { getOpenAPISpec, route, serveScalarUI } from "peta-hono";
30
-
31
- const app = new Hono();
12
+ import { Hono } from "hono"
13
+ import { type } from "arktype"
14
+ import { getOpenAPISpec, route, serveScalarUI } from "peta-docs"
32
15
 
33
- const Pet = type({ id: "number", name: "string>0", species: "'cat'|'dog'" });
16
+ const app = new Hono()
17
+ const Pet = type({ id: "number", name: "string", species: "'cat'|'dog'" })
34
18
 
35
- // route() generates docs + runtime validation from the same schema
36
19
  app.get("/pets/:id", route()
37
20
  .summary("Get a pet by ID")
38
21
  .params(type({ id: "string" }))
39
22
  .response(200, Pet)
40
- .response(404, "Not found")
41
23
  .handle((c) => c.json({ id: 1, name: "Fido", species: "dog" })),
42
- );
24
+ )
43
25
 
44
- app.post("/pets", route()
45
- .summary("Create a pet")
46
- .requestBody(type({ name: "string>0", species: "'cat'|'dog'" }))
47
- .response(201, Pet)
48
- .response(400, "Invalid input")
49
- .handle((c) => {
50
- const body = c.req.valid("json");
51
- return c.json(body, 201);
52
- }),
53
- );
26
+ app.get("/openapi.json", (c) => c.json(getOpenAPISpec(app, { title: "Pet Store", version: "1.0.0" })))
27
+ app.get("/docs", ...serveScalarUI({ specUrl: "/openapi.json" }))
28
+ ```
54
29
 
55
- const info = { title: "Pet Store API", version: "1.0.0" };
56
- app.get("/openapi.json", (c) => c.json(getOpenAPISpec(app, info)));
57
- app.get("/docs", serveScalarUI({ specUrl: "/openapi.json", title: "Pet Store API" }));
30
+ ---
58
31
 
59
- export default app;
60
- ```
32
+ ## Features
61
33
 
62
- ## API
34
+ - **ArkType-first** — schemas are ArkType types. Validation + docs from one definition.
35
+ - **Auto-validation** — schemas in the `route()` chain generate runtime validators. Invalid requests return `{ error: "Validation failed", issues }` with status 400.
36
+ - **File-system routing** — `loadRoutes()` discovers and mounts route modules from the filesystem.
37
+ - **Shorthand responses** — `200: Pet` instead of verbose OpenAPI response objects.
38
+ - **OpenAPI 3.1** — full JSON Schema 2020-12 compatibility.
39
+ - **Scalar UI** — one-liner to serve the Scalar API reference.
40
+ - **`c.req.valid("json")`** — typed body access with no extra imports or casts.
41
+ - **Extensible** — custom `RouteScanner` for non-Hono frameworks.
63
42
 
64
- ### `route()` — chain API
43
+ ---
65
44
 
66
- Returns a `RouteBuilder` with chain methods for declaring route metadata and schemas. Terminal `.handle()` returns a Hono handler with validation composed in. Pass directly to `app.get()` / `app.post()`.
45
+ ## Install
67
46
 
68
- ```ts
69
- route()
70
- .summary(string) // optional
71
- .description(string) // optional
72
- .operationId(string) // optional
73
- .tags(...string[]) // optional
74
- .deprecated(boolean?) // optional
75
- .paginated(options?) // optional: adds page/limit/offset to query + spec
76
- .filter(name, schema, opts?) // optional: adds a filterable query param (supports operators)
77
- .sort(fields) // optional: configures ?sort with ±field enum
78
- .include(relations) // optional: configures ?include with related resource enum
79
- .fieldsets(resources) // optional: configures ?fields[type] sparse fieldsets
80
- .auth(scheme?) // optional: marks route as requiring auth (default: 'bearerAuth')
81
- .query(ArkType type) // validates + documents query params
82
- .params(ArkType type) // validates + documents path params
83
- .headers(ArkType type) // validates + documents headers
84
- .requestBody(ArkType type) // validates + documents request body
85
- .response(status, value) // call: value is ArkType type, string, or full config
86
- .onValidationError(handler) // per-route override for validation failure response
87
- .handle(handler) // terminal → returns Hono handler
47
+ ```bash
48
+ bun add peta-docs hono arktype
88
49
  ```
89
50
 
90
- Schema methods (`.requestBody`, `.query`, `.params`, `.headers`) enforce `ArkTypeSchema` at the type level — only ArkType types accepted.
51
+ ---
91
52
 
92
- `.response()` accepts:
93
- | Value | Behavior |
94
- |---|---|
95
- | `ArkType type` | Auto "OK" description + `application/json` content |
96
- | `string` | Description only, no content schema |
97
- | `{ description?, content? }` | Full OpenAPI response object |
53
+ ## Route chain API
54
+
55
+ `route()` returns a `RouteBuilder` with chain methods. Terminal `.handle()` returns a Hono handler with validation composed in.
98
56
 
99
- The handler callback receives a `TypedContext` with typed `.valid()` overloads matching your declared schemas:
57
+ ```ts
58
+ route()
59
+ .summary(string) // optional
60
+ .description(string) // optional
61
+ .operationId(string) // optional
62
+ .tags(...string[]) // optional
63
+ .deprecated(boolean?) // optional
64
+ .paginated(options?) // adds page/limit/offset query params
65
+ .filter(name, schema, opts?) // adds a filterable query param
66
+ .sort(fields) // configures ?sort with ±field enum
67
+ .include(relations) // configures ?include with related resource enum
68
+ .fieldsets(resources) // configures ?fields[type] sparse fieldsets
69
+ .auth(scheme?) // marks route as requiring auth
70
+ .query(ArkType type) // validates + documents query params
71
+ .params(ArkType type) // validates + documents path params
72
+ .headers(ArkType type) // validates + documents headers
73
+ .requestBody(ArkType type) // validates + documents request body
74
+ .response(status, value) // call: value is ArkType type, string, or config
75
+ .onValidationError(handler) // per-route validation failure override
76
+ .handle(handler) // terminal → returns Hono handler
77
+ ```
78
+
79
+ The handler callback receives a `TypedContext` with typed `.valid()` overloads:
100
80
 
101
81
  ```ts
102
82
  .handle((c) => {
103
- const body = c.req.valid("json"); // typed as body schema's infer
104
- const query = c.req.valid("query"); // typed as query schema's infer
105
- // ...
83
+ const body = c.req.valid("json") // typed as body schema's infer
84
+ const query = c.req.valid("query") // typed as query schema's infer
106
85
  })
107
86
  ```
108
87
 
109
- On validation failure, the route returns `{ error: 'Validation failed', issues: [...] }` with status 400.
88
+ ### Response shorthand
110
89
 
111
- #### `.paginated(options?)`
90
+ | Value | Behavior |
91
+ |-------|----------|
92
+ | ArkType type | Auto "OK" description + `application/json` content |
93
+ | string | Description only, no content schema |
94
+ | `{ description?, content? }` | Full OpenAPI response object |
112
95
 
113
- Adds `page`, `limit`, `offset` query parameters to validation and OpenAPI docs. `page` and `limit` are coerced from strings and clamped to valid ranges. `offset` is computed as `(page - 1) * limit`.
96
+ ### Pagination
114
97
 
115
98
  ```ts
116
99
  route()
117
100
  .paginated({ maxLimit: 100, defaultLimit: 20 })
118
101
  .handle((c) => {
119
- const { page, limit, offset } = c.req.valid("query"); // all numbers
120
- const items = db.query(`SELECT * FROM items LIMIT $1 OFFSET $2`, [limit, offset]);
121
- return c.json({ data: items, page, total });
102
+ const { page, limit, offset } = c.req.valid("query")
103
+ // page, limit, offset are typed numbers
122
104
  })
123
105
  ```
124
106
 
125
- Options:
126
-
127
- | Option | Default | Description |
128
- |---|---|---|
129
- | `maxLimit` | `100` | Maximum allowed value for `limit` |
130
- | `defaultLimit` | `20` | Default `limit` when not specified |
131
-
132
- Can be combined with `.query()` — pagination fields are merged into the same validated object.
133
-
134
- #### `.auth(scheme?)`
135
-
136
- Marks the route as requiring authentication in the OpenAPI spec. Adds `security: [{ [scheme]: [] }]` to the operation. The actual auth middleware is applied separately (e.g., via `app.use("/*", requireAuth)` or inline).
137
-
138
- ```ts
139
- route()
140
- .auth() // security: [{ bearerAuth: [] }]
141
- .auth("apiKey") // security: [{ apiKey: [] }]
142
- .auth("bearerAuth")
143
- .auth("oauth2") // multiple: [{ bearerAuth: [] }, { oauth2: [] }]
144
- .handle((c) => c.json({ ok: true }));
145
- ```
146
-
147
- Define the security scheme in `components` via `getOpenAPISpec` options:
107
+ ### Filters
148
108
 
149
109
  ```ts
150
- getOpenAPISpec(app, info, undefined, {
151
- components: {
152
- securitySchemes: {
153
- bearerAuth: { type: "http", scheme: "bearer" },
154
- apiKey: { type: "apiKey", in: "header", name: "X-API-Key" },
155
- },
156
- },
157
- });
158
- ```
159
-
160
- #### `.filter(name, schema, options?)`
161
-
162
- Adds a filterable query parameter to the OpenAPI spec and runtime validation. Supports operator-based filters with `__` suffix convention.
163
-
164
- ```ts
165
- // Simple exact match — adds ?status= query param
110
+ // Simple exact match
166
111
  .filter("status", type("'active'|'inactive'"))
167
112
 
168
- // With operators — adds ?price__gte= and ?price__lte= query params
113
+ // With operators — adds ?price__gte= and ?price__lte=
169
114
  .filter("price", type("number"), { operators: ["gte", "lte"] })
170
115
  ```
171
116
 
172
- Available operators:
173
-
174
- | Operator | Param example | Behavior |
175
- |---|---|---|
176
- | `eq` (default) | `?status=active` | Exact match |
177
- | `ne` | `?name__ne=foo` | Not equal |
178
- | `gte` | `?price__gte=10` | Greater than or equal |
179
- | `gt` | `?price__gt=10` | Greater than |
180
- | `lte` | `?price__lte=50` | Less than or equal |
181
- | `lt` | `?price__lt=50` | Less than |
182
- | `contains` | `?name__contains=foo` | Contains substring |
183
- | `startsWith` | `?name__startsWith=foo` | Starts with |
184
- | `endsWith` | `?name__endsWith=foo` | Ends with |
185
- | `in` | `?status__in=active,pending` | Comma-separated set |
186
-
187
- Filter values are validated against the provided schema and merged into `c.req.valid("query")` alongside `.query()` and `.paginated()` values.
188
-
189
- #### `.sort(fields)`
190
-
191
- Declares sortable fields. Adds a `?sort` query param with an enum of `±field` values.
117
+ ### Sort
192
118
 
193
119
  ```ts
194
120
  .sort(["name", "price", "createdAt"])
195
121
  // → ?sort enum: name, -name, price, -price, createdAt, -createdAt
196
- // → ?sort=-price,name (comma-separated, prefix - for descending)
197
122
  ```
198
123
 
199
- Invalid sort fields are rejected with a 400 response. Validated value is available at `c.req.valid("query").sort` as a `string[]`.
124
+ ### Include
200
125
 
201
- #### `.include(relations)`
126
+ ```ts
127
+ .include(["author", "comments", "tags"])
128
+ // → ?include=author,comments (comma-separated)
129
+ ```
202
130
 
203
- Declares sideloadable related resources. Adds a `?include` query param with an enum of the allowed relation names.
131
+ ### Fieldsets
204
132
 
205
133
  ```ts
206
- .include(["author", "comments", "tags"])
207
- // → ?include enum: author, comments, tags
208
- // → ?include=author,comments (comma-separated)
134
+ .fieldsets(["articles", "people"])
135
+ // → ?fields[articles]=title,body&fields[people]=name
209
136
  ```
210
137
 
211
- Invalid relation names are rejected with a 400 response. Validated value is available at `c.req.valid("query").include` as a `string[]`.
138
+ ### Auth
212
139
 
213
- #### `.fieldsets(resources)`
140
+ ```ts
141
+ .auth() // security: [{ bearerAuth: [] }]
142
+ .auth("apiKey") // security: [{ apiKey: [] }]
143
+ ```
214
144
 
215
- Declares sparse fieldset resources per the JSON:API spec. Adds `?fields[type]` query params for each resource.
145
+ Define security schemes via `getOpenAPISpec` options:
216
146
 
217
147
  ```ts
218
- .fieldsets(["articles", "people"])
219
- // → ?fields[articles]=title,body&fields[people]=name
220
- // → c.req.valid("query")["fields[articles]"] → "title,body"
148
+ getOpenAPISpec(app, info, undefined, {
149
+ components: {
150
+ securitySchemes: {
151
+ bearerAuth: { type: "http", scheme: "bearer" },
152
+ },
153
+ },
154
+ })
221
155
  ```
222
156
 
223
- Each resource generates a `?fields[type]` string parameter in the OpenAPI spec. Values are passed through as strings.
157
+ ---
158
+
159
+ ## Spec generation
224
160
 
225
161
  ### `getOpenAPISpec(app, info, scanner?, options?)`
226
162
 
227
163
  Scans `app.routes` for handlers with OpenAPI metadata and builds the OpenAPI 3.1 document.
228
164
 
229
165
  ```ts
230
- getOpenAPISpec(
231
- app: Hono,
232
- info: InfoObject,
233
- scanner?: RouteScanner,
234
- options?: { basePath?: string; components?: Record<string, unknown> },
235
- ): OpenAPIObject
166
+ getOpenAPISpec(app, { title: "My API", version: "1.0.0" }, undefined, {
167
+ basePath: "/api",
168
+ components: { securitySchemes: { ... } },
169
+ })
236
170
  ```
237
171
 
238
- `scanner` is optional — defaults to `honoScanner`. The `basePath` option (default `"/api"`) strips a URL prefix before deriving auto-tags, so routes under `/api/pets` get tag `"pets"` instead of `"api"`. Pass `basePath: ""` to disable prefix stripping. The `components` option is forwarded directly into the spec — use it to define `securitySchemes`, `schemas`, etc.
239
-
240
172
  ### `serveScalarUI(options)`
241
173
 
242
174
  Returns a handler that serves the Scalar API reference page.
@@ -244,27 +176,21 @@ Returns a handler that serves the Scalar API reference page.
244
176
  ```ts
245
177
  serveScalarUI({
246
178
  specUrl: string
247
- title?: string // default: 'API Reference'
248
- theme?: string // default: 'purple'
179
+ title?: string // default: "API Reference"
180
+ theme?: string // default: "purple"
249
181
  showSidebar?: boolean // default: true
250
- cdnUrl?: string // default: 'https://cdn.jsdelivr.net/npm/@scalar/api-reference'
182
+ cdnUrl?: string // default: Scalar CDN
251
183
  })
252
184
  ```
253
185
 
254
- Uses the `<scalar-api-reference>` web component. The CDN URL is configurable for pinning versions or self-hosting.
255
-
256
- ### `loadRoutes(app, dir, options?)`
186
+ ---
257
187
 
258
- Discovers and mounts route modules from a directory tree. Each subdirectory with an `index.ts` exporting a Hono instance (default export) is mounted as a sub-router at `${basePath}/${name}` where `basePath` defaults to `"/api"`.
259
-
260
- Directories named `[param]` are converted to `:param` path segments for dynamic routing. Directories without `index.ts` (gaps) accumulate their path until a child directory with `index.ts` is found.
188
+ ## File-system routing
261
189
 
262
190
  ```ts
263
- import { loadRoutes } from "peta-hono";
191
+ import { loadRoutes } from "peta-docs/hono"
264
192
 
265
- await loadRoutes(app, "./routes"); // mounts at /api/pets, /api/species
266
- await loadRoutes(app, "./routes", { basePath: "/v2" }); // mounts at /v2/pets, /v2/species
267
- await loadRoutes(app, "./routes", { basePath: "" }); // mounts at /pets, /species
193
+ await loadRoutes(app, "./routes")
268
194
  ```
269
195
 
270
196
  Convention:
@@ -272,79 +198,47 @@ Convention:
272
198
  ```
273
199
  routes/
274
200
  pets/
275
- index.ts → /api/pets
201
+ index.ts → /api/pets
276
202
  [id]/
277
- index.ts → /api/pets/:id
203
+ index.ts → /api/pets/:id
278
204
  comments/
279
- index.ts → /api/pets/:id/comments
205
+ index.ts → /api/pets/:id/comments
280
206
  species/
281
- index.ts → /api/species
282
- admin/ ← no index.ts (gap)
283
- [id]/
284
- settings/
285
- index.ts → /api/admin/:id/settings ← mounted on app, not sub-router
207
+ index.ts → /api/species
286
208
  ```
287
209
 
288
- Each level can have its own `index.ts` — nesting is recursive and mirrors the URL structure. `[param]` directories become `:param` path segments. Gap directories (no `index.ts`) accumulate their path; the next `index.ts` found deeper mounts on the original parent at the full accumulated path.
289
-
290
- Also accepts factory functions. Errors in individual route files are logged — a bad route doesn't crash the app.
210
+ - `[param]` directories become `:param` path segments
211
+ - Directories without `index.ts` (gaps) accumulate their path until a child with `index.ts` is found
212
+ - Errors in individual route files are logged — a bad route doesn't crash the app
291
213
 
292
- Since both `loadRoutes` and `getOpenAPISpec` default to `"/api"`, auto-tags are derived correctly without extra config in most cases. For a custom `basePath`, pass the same value to both.
214
+ ---
293
215
 
294
- ### `setOnValidationError(handler)`
295
-
296
- > **Deprecated.** Use `.onValidationError()` on the route chain instead. Per-route handlers take precedence over the global handler.
297
-
298
- Customize the response returned when request validation fails. Returns a restore function.
299
-
300
- ```ts
301
- const restore = setOnValidationError((issues, c) => {
302
- return c.json({ error: "Invalid", details: issues }, 422);
303
- });
304
-
305
- // later: restore() // resets to default handler
306
- ```
307
-
308
- Default returns `{ error: 'Validation failed', issues }` with status 400.
309
-
310
- Per-route override via the chain method:
216
+ ## Custom validation error handler
311
217
 
312
218
  ```ts
219
+ // Per-route override
313
220
  route()
314
221
  .onValidationError((issues, c) => c.json({ error: "Invalid" }, 422))
315
- .requestBody(type({ name: "string>0" }))
316
- .handle((c) => c.json({ ok: true }, 201));
222
+ .requestBody(type({ name: "string" }))
223
+ .handle((c) => c.json({ ok: true }, 201))
317
224
  ```
318
225
 
319
- The per-route handler takes precedence over the global `setOnValidationError()`.
320
-
321
- ### `StatusCode`
226
+ > [!TIP]
227
+ > Per-route handlers take precedence over the global `setOnValidationError()`.
322
228
 
323
- Union of common HTTP status codes for use in response definitions. Provides autocomplete while accepting any string.
324
-
325
- ```ts
326
- import type { StatusCode } from "peta-hono";
327
-
328
- type Code = StatusCode; // '200' | '201' | '400' | '404' | '500' | (string & {})
329
- ```
330
-
331
- Built into `RouteConfig.responses`.
229
+ ---
332
230
 
333
231
  ## How it works
334
232
 
335
- 1. **`route()`** returns a `RouteBuilder`. Chain methods accumulate route metadata (summary, schemas, responses). Terminal `.handle()` attaches the config to the handler via a `Symbol` property. If schemas are present, it generates Hono validator middleware that runs before the handler and converts schemas via `toJsonSchema()` for OpenAPI docs.
336
- 2. **`getOpenAPISpec()`** iterates `app.routes[]` (via `RouteScanner`), extracts the Symbol metadata, converts ArkType schemas to JSON Schema, and builds an OpenAPI 3.1 document.
337
- 3. **`serveScalarUI()`** returns a handler that serves an HTML page loading the Scalar web component from CDN, pointed at the OpenAPI spec URL.
233
+ 1. **`route()`** returns a `RouteBuilder`. Chain methods accumulate route metadata. Terminal `.handle()` attaches the config to the handler via a `Symbol` property and generates Hono validator middleware.
234
+ 2. **`getOpenAPISpec()`** iterates `app.routes[]` (via `RouteScanner`), extracts the metadata, converts ArkType schemas to JSON Schema, and builds an OpenAPI 3.1 document.
235
+ 3. **`serveScalarUI()`** returns a handler that serves an HTML page loading the Scalar web component from CDN.
338
236
 
339
237
  No Hono subclass, no monkey-patching — works with vanilla `new Hono()`.
340
238
 
341
- ## Build
342
-
343
- ```bash
344
- bun run build # tsdown → dist/index.mjs + dist/index.d.mts
345
- bun run test # 88 tests
346
- ```
239
+ ---
347
240
 
348
- ## License
241
+ ## Related packages
349
242
 
350
- MIT
243
+ - [peta-orm](../orm) — ORM with models, relations, hooks, soft deletes
244
+ - [peta-auth](../auth) — Encrypted cookie sessions, JWT, OAuth
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "peta-docs",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "OpenAPI + Scalar docs for Hono (and more), powered by Standard Schema",