zopia 0.3.0 โ†’ 0.5.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.
@@ -1,239 +0,0 @@
1
- # ๐Ÿงฉ Concepts & Glossary
2
-
3
- The vocabulary of this project, pinned to exact versions. If a term is used in
4
- any other zopia document, it means what it means here.
5
-
6
- ## ๐Ÿ“š The big picture
7
-
8
- ```mermaid
9
- flowchart TB
10
- S1["๐Ÿ“„ Swagger 2.0<br/>(2016 spec)"] ---|dialect| JS1["๐Ÿ“ JSON Schema draft-04-ish"]
11
- S2["๐Ÿ“„ OpenAPI 3.0"] ---|dialect| JS2["๐Ÿ“ JSON Schema draft-07 subset<br/>+ nullable"]
12
- S3["๐Ÿ“„ OpenAPI 3.1"] ---|dialect| JS3["๐Ÿ“ JSON Schema 2020-12 (identical)"]
13
- JS3 ---|same format| Z1["โš›๏ธ Zod v4 schemas"]
14
- S2 --> C["๐Ÿ“‚ api docs<br/>(.ts ยท km-api ยท zod v4)"]
15
- S1 --> C
16
- S3 --> C
17
- ```
18
-
19
- > ๐Ÿ’ก **Key fact** โ€” OpenAPI 3.1's *Schema Object* **is** JSON Schema 2020-12
20
- > ("the standards of openapi are the same as json schema"). That identity is
21
- > what lets zopia treat 3.1 schemas and JSON Schema as one format.
22
-
23
- ## ๐Ÿ“– Terms
24
-
25
- ### ๐Ÿ“„ Swagger 2.0
26
-
27
- The 2016 API description format. Recognized by the top-level key
28
- `"swagger": "2.0"`.
29
-
30
- | ๐Ÿงฉ Piece | ๐Ÿ“ In Swagger 2.0 |
31
- | --- | --- |
32
- | ๐Ÿ“ Schemas | `definitions` (referenced as `#/definitions/X`) |
33
- | ๐ŸŒ Hosting | `host` + `basePath` + `schemes` |
34
- | ๐Ÿ“ฆ Request bodies | a special parameter with `"in": "body"` (has `schema`) |
35
- | ๐Ÿงพ Form data | parameters with `"in": "formData"` (media type from `consumes`) |
36
- | ๐Ÿช Cookie parameters | โŒ none (introduced in OpenAPI 3.0) |
37
- | ๐Ÿ” Security | `securityDefinitions` โ†’ mapped to OpenAPI 3 `securitySchemes` |
38
- | ๐Ÿ“ Schema dialect | draft-04-ish subset (no `nullable`, no `const`, โ€ฆ) |
39
- | ๐Ÿšฆ Responses | `responses` map โ€” each response may carry a single `schema` |
40
-
41
- ### ๐Ÿ“„ OpenAPI 3.0
42
-
43
- Recognized by `"openapi": "3.0.x"`.
44
-
45
- | ๐Ÿงฉ Piece | ๐Ÿ“ In OpenAPI 3.0 |
46
- | --- | --- |
47
- | ๐Ÿ“ Schemas | `components.schemas` (referenced as `#/components/schemas/X`) |
48
- | ๐ŸŒ Hosting | `servers: [{ url, variables? }]` |
49
- | ๐Ÿ“ฆ Request bodies | dedicated `requestBody` object with a `content` map (media type โ†’ schema) |
50
- | ๐Ÿช Cookie parameters | โœ… `"in": "cookie"` |
51
- | ๐ŸŒ— Nullable | `nullable: true` keyword on schemas |
52
- | ๐Ÿ“ Schema dialect | draft-07 **subset** + OpenAPI extensions (`nullable`, `discriminator`, `example`, `deprecated`, โ€ฆ) |
53
-
54
- ### ๐Ÿ“„ OpenAPI 3.1
55
-
56
- Recognized by `"openapi": "3.1.x"`. Same shape as 3.0, but:
57
-
58
- - ๐Ÿ“ Schema dialect = **JSON Schema 2020-12** (full equality)
59
- - ๐ŸŒ— Nullability via `"type": ["string", "null"]` โ€” `nullable` is removed
60
- - ๐Ÿ“ธ `examples` (array) replaces `example`; `const` is allowed
61
- - ๐Ÿงฎ `exclusiveMinimum/Maximum` are **numbers** (were booleans in draft-04/07)
62
- - ๐Ÿช adds `webhooks` (not paths): v0.1.0 warns, preserves them in the manifest,
63
- and restores them for 3.1 output but defers webhook endpoint files; valid local
64
- path-item `$ref`s are supported and round-trip
65
- - ๐Ÿ“Œ zopia treats 3.0 and 3.1 with the same normalizer + a small dialect shim
66
-
67
- ### ๐Ÿ“ JSON Schema
68
-
69
- The vocabulary used to describe data shape (`type`, `properties`, `required`,
70
- `enum`, `oneOf`, โ€ฆ). zopia's engine โ‘ก accepts the union of keywords found in
71
- the three dialects above and emits warnings for anything it cannot represent
72
- in Zod (D-12). The canonical dialect of engine โ‘ 's output is **2020-12**.
73
-
74
- ### ๐Ÿ”— $ref (reference)
75
-
76
- A pointer like `"#/components/schemas/User"` that makes one schema *use*
77
- another. Three shapes matter for zopia:
78
-
79
- ```jsonc
80
- // 1๏ธโƒฃ operation โ†’ component (responses, requestBody, parameters)
81
- { "schema": { "$ref": "#/components/schemas/User" } }
82
-
83
- // 2๏ธโƒฃ component โ†’ component (a part of another component)
84
- { "properties": { "address": { "$ref": "#/components/schemas/Address" } } }
85
-
86
- // 3๏ธโƒฃ component โ†’ itself (cycle โ€” always allowed, becomes z.lazy())
87
- { "properties": { "replies": { "type": "array", "items": { "$ref": "#/components/schemas/Comment" } } } }
88
- ```
89
-
90
- > ๐Ÿ“Œ **Rule R-501** โ€” zopia resolves *internal* refs natively (refs stay
91
- > inside the bundled document). Spec file paths additionally bundle
92
- > **same-folder** external refs before processing (D-17); everything else โ€”
93
- > URLs, other folders, external refs in non-file inputs โ€” stays a
94
- > `ZOPIA_REF_EXTERNAL` error.
95
-
96
- ### โš›๏ธ Zod v4
97
-
98
- The schema library (validation at runtime + types at compile time). zopia
99
- depends on Zod **v4** idioms โ€” *not* v3 โ€” throughout:
100
-
101
- | ๐Ÿงฉ v4 feature | ๐Ÿ“ Used by zopia |
102
- | --- | --- |
103
- | `z.toJSONSchema(schema, { target })` | engine โ‘  โ€” built-in, no third-party converter (D-03); targets: `draft-2020-12` (default), `draft-07`, `draft-04`, `openapi-3.0` |
104
- | `z.fromJSONSchema(schema)` | โš ๏ธ experimental in Zod โ€” used **only** as a cross-check in tests, never in the output path (D-04) |
105
- | top-level format schemas | `z.email()`, `z.uuid()`, `z.url()`, `z.hostname()`, `z.ipv4()`, `z.ipv6()` โ€” โš ๏ธ each emits `format` **plus a strict `pattern`** (engine โ‘  strips the redundant pair โ€” R-618); `z.url()` emits `format: "uri"` |
106
- | ISO builders | `z.iso.datetime()`, `z.iso.date()`, `z.iso.time()`, `z.iso.duration()` โ€” โš ๏ธ `z.iso.time()` emits a pattern but **no `format` key** (round-trip needs the manifest overlay โ€” R-635) |
107
- | *(no v4 API for arbitrary formats)* | custom `format` values โ†’ `z.string()` + warning + manifest overlay preserving the format verbatim (R-627) |
108
- | `z.enum([...])`, `z.literal(v)` | `enum` / `const` keywords |
109
- | `z.union([...])`, `z.discriminatedUnion(key, [...])` | `oneOf` / `anyOf` (with the `discriminator` heuristic) |
110
- | `z.intersection(a, b)` | `allOf` |
111
- | `z.tuple([...])` | `prefixItems` / tuple `items` |
112
- | `z.lazy(() => โ€ฆ)` | circular `$ref`s (R-402) |
113
- | `.meta({ title, description, examples, โ€ฆ })` / `z.globalRegistry` | **all** metadata fields are copied verbatim into the JSON Schema output (verified); โš ๏ธ never set the `id` key โ€” it triggers `$def` extraction |
114
- | `z.object({โ€ฆ})` + `.strict()` / `.catchall(s)` / `.optional()` / `.default(v)` | objects, `additionalProperties`, `required`, `default` |
115
- | `.min()` / `.max()` / `.int()` / `.regex()` / `.multipleOf()` | string/number/array constraints |
116
-
117
- ### ๐Ÿงฑ km-api
118
-
119
- The user's endpoint-definition package โ€” **the make function of this project's
120
- generated code**. zopia targets **km-api `^0.4.1` (0.4.x)** and generates the
121
- following conceptual shape (formatting abridged):
122
-
123
- ```ts
124
- import { makeApiConfig } from 'km-api';
125
-
126
- const getUser = makeApiConfig({
127
- method: 'GET', // ๐Ÿงญ IMethod โ€” HTTP method
128
- pathShape: '/admin/users/{id}', // ๐Ÿ›ฃ๏ธ IPath โ€” OpenAPI {param} syntax (D-05)
129
- operationId: 'getUser', // ๐Ÿ†” preserved in config + export name
130
- auth: 'YES', // ๐Ÿ” 'YES' | 'NO'
131
- responseContentType: 'application/json',
132
- summary: 'Get user by ID', // ๐Ÿ“
133
- description: 'Retrieves โ€ฆ', // ๐Ÿ“ (Markdown)
134
- tags: ['#admin', '#users'], // ๐Ÿท๏ธ km-api convention: '#' prefix
135
- request: {
136
- body: z.any(), // ๐Ÿ“ฆ required field โ€” z.any() โ‡” no body
137
- params: z.object({ id: z.uuid() }), // ๐Ÿ›ฃ๏ธ path params
138
- query: z.object({}), // โ“
139
- headers: z.object({}), // ๐ŸŽฉ
140
- cookies: z.object({}), // ๐Ÿช
141
- },
142
- response: { // ๐Ÿšฆ status code โ†’ Zod schema
143
- 200: z.object({ id: z.uuid(), name: z.string() }),
144
- 404: z.object({ message: z.string() }),
145
- },
146
- });
147
- ```
148
-
149
- Helper methods on the result (`makeFullPath`, `makeOpenApiPathShape`,
150
- `convertResponseType`, โ€ฆ) are available to consumers for free โ€” zopia never
151
- needs them at generation time, and engine โ‘ฃ uses `makeOpenApiPathShape()` to
152
- normalize paths back to `{param}` form.
153
-
154
- > ๐Ÿ“Œ **Rule R-502** โ€” generated files import **only** `zod` and `km-api`.
155
- > No zopia runtime is imported by generated code. Component files are
156
- > generated as standalone files, and exact endpoint component references can
157
- > import through the barrel. Nested component references are recursively rendered, including nested objects, arrays, compositions, nullable schemas, enums, constants, and additional-property schemas. Direct and mutual cyclic component graphs use lazy Zod references.
158
-
159
- ### ๐Ÿ“ km-api's type surface (verified against published `0.4.1`)
160
-
161
- `makeApiConfig` is a **type-level factory** โ€” it does no runtime validation, so
162
- the generated tree's contract is that it **typechecks** against the installed
163
- published package (D-14; the golden-tree contract test enforces this, R-126):
164
-
165
- | ๐Ÿงฉ Field | ๐Ÿ“ Published km-api 0.4.1 / zopia contract |
166
- | --- | --- |
167
- | `method` (`IMethod`) | all **8** standard methods โ€” `get/post/put/delete/head/options/patch/trace` (each case-insensitive: `GET`, `Get`, โ€ฆ) |
168
- | `response` keys | standard statuses, **any valid custom `100`โ€“`599` code** (`419`, `499`, `512`, โ€ฆ), OpenAPI `1XX`โ€“`5XX` ranges, and the **`default`** key; all are pinned by generated compilation and validated against the source/output dialect |
169
- | `responseContentType` / `requestContentType` | km-api's declarations enumerate known MIME values, while OpenAPI media-type maps are open; zopia emits every exact string and applies a narrow type-only assertion at this package boundary, preserving the runtime value and reverse conversion |
170
- | `operationId` | any string โ€” a real km-api field in 0.4.1; zopia emits it and reuses it as the export identifier (R-732) |
171
-
172
- Other fixed shapes (source-verified): `ITags` = strings **prefixed with `#`**;
173
- `IPath` = string starting with `/`; `auth`, `disable`, and the semantically
174
- separate `deprecated` field use `'YES' | 'NO'`; `request.body` is required (any
175
- Zod schema), and `params/query/headers/cookies` are required Zod objects. The
176
- remaining km-api gaps โ€” **per-parameter metadata**
177
- (`style`, `explode`, `allowEmptyValue`, `deprecated`, `example`) and **response
178
- `headers`** โ€” have no home in km-api; zopia preserves them in the manifest
179
- (overlay / `responseOverlay`, R-635/R-754).
180
-
181
- > ๐Ÿ”— **Where km-api lives.** zopia consumes the published `km-api@^0.4.1`
182
- > npm package. No Git submodule or unpublished commit is required (D-15).
183
-
184
- ### ๐Ÿ“‚ api docs
185
-
186
- The generated artifact: an `api_docs/` directory containing one `index.ts` per
187
- endpoint (plus optional `components/**` and the manifest). Defined fully in
188
- [API docs format](07-api-docs.md).
189
-
190
- ### ๐Ÿ“ฆ .zopia-manifest.json
191
-
192
- The hidden metadata file written into `api_docs/` (D-06). It is what makes
193
- engine โ‘ฃ lossless: exact paths & methods per file, spec identity
194
- (kind/title/version/sha256), servers, tags, security schemes, the **full
195
- component schemas**, **`$ref` placement** (per-API ref pointers), and
196
- **non-representable facts** (overlay entries: titles, examples, custom
197
- formats, unsupported keywords). See
198
- [API docs format โ†’ The manifest](07-api-docs.md).
199
-
200
- ### ๐Ÿงฌ Stage-specific representations
201
-
202
- Engine โ‘ข narrows a validated document into operation-level IR and normalized
203
- request/response contracts. Engine โ‘ฃ combines imported runtime values with the
204
- versioned manifest instead of sharing one repository-wide model with engine โ‘ข.
205
- See [Architecture โ†’ The internal representations](04-architecture.md#-the-internal-representations).
206
-
207
- ### ๐Ÿ” Round-trip
208
-
209
- For a spec `S`: `openApiToApiDocs(S) โ†’ apiDocsToOpenApi(...) โ‰ˆ S` โ€” equal after
210
- **canonicalization** (R-401 order, whitespace-independent JSON equality, and
211
- documented metadata moves to the manifest). Round-trips are *tested as
212
- properties*, not by eye (see [Testing](11-testing.md#-round-trip-property-tests)).
213
-
214
- ## โš–๏ธ Dialect comparison (the table that answers most "why?" questions)
215
-
216
- | ๐Ÿงฉ Concern | Swagger 2.0 | OpenAPI 3.0 | OpenAPI 3.1 |
217
- | --- | --- | --- | --- |
218
- | ๐Ÿท๏ธ Version key | `swagger: "2.0"` | `openapi: "3.0.x"` | `openapi: "3.1.x"` |
219
- | ๐Ÿ“ Schema home | `definitions` | `components.schemas` | `components.schemas` |
220
- | ๐Ÿ“ Schema dialect | draft-04-ish | draft-07 subset + extensions | **JSON Schema 2020-12** |
221
- | ๐ŸŒ Servers | `host`+`basePath`+`schemes` | `servers[]` | `servers[]` |
222
- | ๐Ÿ“ฆ Body | param `in: body` | `requestBody.content` | `requestBody.content` |
223
- | ๐Ÿงพ FormData | param `in: formData` | `requestBody.content['multipart/โ€ฆ' \| 'x-www-form-urlencoded']` | same as 3.0 |
224
- | ๐Ÿช Cookie params | โŒ | โœ… | โœ… |
225
- | ๐ŸŒ— Nullable | โŒ no standard way (draft-04 allows type arrays, but 2.0 tooling rarely supports them) | `nullable: true` | `type: ["โ€ฆ", "null"]` |
226
- | ๐Ÿ“ธ Examples | `examples` (media-type map, legacy) | `example` (single) | `examples` (array) |
227
- | ๐Ÿ” Security | `securityDefinitions` | `components.securitySchemes` | same as 3.0 |
228
- | ๐Ÿšฆ Response schema | response.schema (single) | per media type | per media type |
229
- | ๐Ÿงฎ exclusiveMinimum | boolean | boolean | **number** |
230
-
231
- > ๐Ÿ“Œ **Rule R-503** โ€” the normalizers convert *everything* into the IR using
232
- > the **OpenAPI-3.1-flavoured** representation (2020-12 schemas, numeric
233
- > exclusive bounds, `type: [t, "null"]` for nullables). Dialect differences
234
- > die at the IR boundary; they never leak into generated code.
235
-
236
- ## ๐Ÿ”— Next
237
-
238
- - ๐Ÿ”„ How the IR is produced and consumed โ†’ [Conversions](06-conversions.md)
239
- - ๐Ÿ—๏ธ Where the IR lives in code โ†’ [Architecture](04-architecture.md)