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,55 +0,0 @@
1
- # ๐ŸŽฏ Targets
2
-
3
- These are the **explicit, testable targets** of zopia. Every Phase 1 target maps
4
- to its governing document and automated release-gate coverage โ€” see
5
- [Testing โ†’ Scenario matrix](11-testing.md).
6
-
7
- > โœ… **Complete** = implemented, documented, and covered by the release gate.
8
- > Deferred work is listed explicitly under [Roadmap โ†’ Phase 2](03-roadmap.md#-phase-2--breadth-v02x-).
9
-
10
- ## ๐Ÿ”„ Conversion targets
11
-
12
- | # | ๐ŸŽฏ Target | Spec | Status |
13
- | --- | --- | --- | --- |
14
- | T-1 | **Convert Zod โ†’ JSON Schema** | [Conversions โ†’ Engine โ‘ ](06-conversions.md) | โœ… |
15
- | T-2 | **Convert JSON Schema โ†’ Zod** | [Conversions โ†’ Engine โ‘ก](06-conversions.md) | โœ… |
16
- | T-3 | **Convert OpenAPI โ†’ api docs** โ€” Swagger 2.0 *and* OpenAPI 3.0/3.1 | [Conversions โ†’ Engine โ‘ข](06-conversions.md) | โœ… |
17
- | T-4 | **Convert api docs โ†’ OpenAPI** (the reverse direction) | [Conversions โ†’ Engine โ‘ฃ](06-conversions.md) | โœ… |
18
-
19
- ## ๐Ÿ“‚ API docs targets
20
-
21
- | # | ๐ŸŽฏ Target | Spec | Status |
22
- | --- | --- | --- | --- |
23
- | T-5 | **Layout mode `directory`** โ€” `api_docs/` + path segments as nested directories + a method-named directory at the last level + `index` file | [API docs format โ†’ directory mode](07-api-docs.md) | โœ… |
24
- | T-6 | **Layout mode `flat`** โ€” `api_docs/` + one directory per API + method directory + `index` file | [API docs format โ†’ flat mode](07-api-docs.md#-mode--flat) | โœ… |
25
- | T-7 | **`index.ts` files are TypeScript** and fill **all practical content** of the endpoint (method, path, operationId, summary, description, tags, auth, content types, request, response, examples) using **`makeApiConfig()` from km-api** (the package's make function) | [API docs format โ†’ `index.ts` contract](07-api-docs.md#-the-indexts-contract) | โœ… |
26
- | T-8 | **Option `insertComponents: boolean`** โ€” when `true`, components are written into `api_docs` as their own schema files; **default `false`** | [Components](08-components.md) ยท [Configuration](09-configuration.md) | โœ… |
27
- | T-9 | **Option `useComponentAsReference: boolean`** โ€” endpoints import emitted components recursively; requires `insertComponents: true`; **default `false`** | [Components โ†’ option matrix](08-components.md) | โœ… |
28
-
29
- ## ๐Ÿ” Reverse-conversion targets
30
-
31
- | # | ๐ŸŽฏ Target | Spec | Status |
32
- | --- | --- | --- | --- |
33
- | T-10 | **Reversible output** โ€” the generated tree contains everything needed to regenerate the spec (paths, methods, schemas, metadata), guaranteed by the manifest | [API docs format โ†’ manifest](07-api-docs.md) | โœ… |
34
- | T-11 | **Round-trip stability** โ€” `openapi โ†’ api docs โ†’ openapi` and `zod โ†’ JSON Schema โ†’ zod` converge: re-running the pipeline on its own output is a no-op (idempotent) | [Testing โ†’ round-trip tests](11-testing.md#-round-trip-property-tests) | โœ… |
35
-
36
- ## ๐Ÿ—๏ธ Engineering targets
37
-
38
- | # | ๐ŸŽฏ Target | Spec | Status |
39
- | --- | --- | --- | --- |
40
- | T-12 | **JSDoc everywhere** โ€” every exported function, type, constant, and class is documented (template + rules fixed) | [Standards โ†’ JSDoc standard](12-standards.md) | โœ… |
41
- | T-13 | **Tests in all scenarios** โ€” Vitest suite covering the full scenario matrix (spec versions, ref graphs, modes, option combinations, zod features, edge cases), run with Bun | [Testing](11-testing.md) | โœ… |
42
- | T-14 | **Bun runs the project** โ€” install, typecheck, test, coverage, CLI, and generated-TypeScript imports all work through one pinned-Bun gate | [Standards โ†’ Bun gate](12-standards.md#-bun-gate) | โœ… |
43
- | T-15 | **Standard documentation** โ€” `docs/` directory with targets, roadmap, architecture, conventions; README links into it; beautiful, icon-based markdown | this document set ยท [Standards โ†’ Docs convention](12-standards.md#-docs-convention) | โœ… |
44
- | T-16 | **Quality bar** โ€” pure, safe, clean code with descriptions; deterministic output; typed errors; no silent lossy conversions | [Standards](12-standards.md) | โœ… |
45
- | T-17 | **Changelog after commits** โ€” `CHANGELOG.md` updated by every user-facing commit under `Unreleased` | [Standards โ†’ Changelog convention](12-standards.md#-changelog-convention) | โœ… |
46
-
47
- ## ๐Ÿ—บ๏ธ Out of scope for the first phase
48
-
49
- > ๐Ÿ“Œ The reverse-conversion *capability* (T-4/T-10/T-11) **is** in the first
50
- > phase โ€” it is part of the four targets above. What is deferred:
51
-
52
- - ~~๐Ÿ“ YAML spec input~~ โ†’ shipped in v0.2.x (D-16; `.yaml`/`.yml` files and inline YAML text reach the same normalized model as JSON)
53
- - ๐Ÿ”— External (multi-file) `$ref`s โ†’ [Roadmap Phase 2](03-roadmap.md)
54
- - ๐Ÿงฉ Reusable **parameters / responses** as emitted components (Phase 1 emits `components.schemas` only) โ†’ [Roadmap Phase 2](03-roadmap.md)
55
- - ~~๐Ÿ–ฅ๏ธ `zopia validate` (spec linting)~~ โ†’ shipped in v0.3.x (S-89; [Roadmap Phase 3](03-roadmap.md)); incremental regeneration remains deferred
@@ -1,205 +0,0 @@
1
- # ๐Ÿ—บ๏ธ Roadmap
2
-
3
- zopia ships in phases. **Phase 0 is this documentation set** โ€” the contracts
4
- are fixed before a line of code is written, so implementation never re-decides
5
- anything.
6
-
7
- ## ๐Ÿšฆ Phase 0 โ€” Documentation & standards โœ…
8
-
9
- The standard documentation of the project:
10
-
11
- - ๐Ÿ“š `docs/` โ€” overview, targets, roadmap, architecture, concepts, conversions,
12
- api-docs format, components, configuration, usage, testing, standards
13
- - ๐Ÿ  `README.md` โ€” project home with the documentation map
14
- - ๐Ÿ“œ `CHANGELOG.md` โ€” Keep-a-Changelog format + the "changelog after commits" rule
15
- - ๐Ÿ” `LICENSE` (MIT) ยท ๐Ÿงน `.gitignore`
16
- - ๐Ÿ”‘ Key decisions **D-01 โ€ฆ D-15** recorded in [Standards](12-standards.md#-key-decisions)
17
-
18
- **Completed:** the documentation fixed the Phase 1 contracts before
19
- implementation; it now describes the implemented v0.1.0 behavior and deferred scope.
20
-
21
- ## ๐Ÿš€ Phase 1 โ€” The four engines (v0.1.0) โœ…
22
-
23
- **Released:** 2026-09-28. Everything in [Targets](02-targets.md) is marked โœ…:
24
-
25
- - [x] โš™๏ธ Package scaffold โ€” ESM `package.json` (Bun-first, zero direct runtime
26
- dependencies; `zod` + `km-api ^0.4.1` peers from npm โ€” D-15), strict `tsconfig`,
27
- Vitest/V8, pinned Bun, and checked-in `bun.lock`
28
- - [x] โ‘  **Engine 1** โ€” `zodToJsonSchema()` on top of `z.toJSONSchema()`
29
- ([rules](06-conversions.md))
30
- - [x] โ‘ก **Engine 2** โ€” `jsonSchemaToZod()` recursive emitter
31
- ([rules](06-conversions.md))
32
- - [x] โ‘ข **Engine 3** โ€” `openApiToApiDocs()` โ€” normalize v2/v3 โ†’ operation IR
33
- โ†’ render `directory` / `flat` trees of `index.ts` files
34
- ([rules](06-conversions.md),
35
- [format](07-api-docs.md))
36
- - [x] โ‘ฃ **Engine 4** โ€” `apiDocsToOpenApi()` โ€” manifest-driven reverse
37
- conversion to OpenAPI 3.0/3.1
38
- ([rules](06-conversions.md))
39
- - [x] ๐Ÿงฑ Component options โ€” `insertComponents`, `useComponentAsReference`,
40
- nested references, and direct/mutual cycles ([rules](08-components.md))
41
- - [x] ๐Ÿ“ **YAML input** โ€” owned deterministic YAML 1.2 core-schema parser,
42
- `.yaml`/`.yml` paths and inline YAML text, anchors/aliases/merge keys,
43
- stable `ZOPIA_SPEC_INVALID_YAML` ([rules](06-conversions.md#engine-โ‘ข--openapi--api-docs), D-16)
44
- - [x] ๐Ÿ”— **External `$ref`s** โ€” same-folder references bundled inline for
45
- spec file paths, byte-identical to inline twins ([rules](06-conversions.md#engine-โ‘ข--openapi--api-docs), D-17)
46
- - [x] ๐Ÿ“ฆ Manifest writer/reader โ€” `.zopia-manifest.json` (D-06)
47
- - [x] โš ๏ธ **Warnings pipeline** โ€” stable typed codes, exact JSON Pointer locations,
48
- deterministic collection/callbacks, generated-code markers, and CLI stderr
49
- reporting across engines โ‘ โ€“โ‘ฃ (R-144/R-408/D-12)
50
- - [x] โ™ป๏ธ **Manifest staleness** โ€” canonical source/config comparison, invalid and
51
- incomplete-tree detection, source-located warnings, safe obsolete-artifact
52
- pruning, and symlink-safe regeneration (R-741โ€ฆR-743)
53
- - [x] ๐Ÿ” **Reverse security fallback** โ€” manifest-authoritative requirements,
54
- validated metadata, collision-safe deterministic bearer synthesis,
55
- OpenAPI/Swagger representations, and operation-located warnings (R-656)
56
- - [x] ๐Ÿ›‘ **Typed errors** โ€” one immutable stable-code catalogue and `ZopiaError`
57
- boundary across engines, manifests, generated-module imports, filesystem
58
- operations, warnings, and CLI validation, with locations, hints, and causes
59
- (R-141โ€ฆR-143/R-404)
60
- - [x] ๐Ÿ” **Round-trip contract** โ€” fixture-backed source-dialect identity across
61
- Swagger 2.0 and OpenAPI 3.0/3.1, every layout/component mode, refs/cycles,
62
- lossy-keyword overlays, path-item metadata, and byte-identical regeneration;
63
- supported Zod โ†” JSON Schema pipelines converge after canonicalization
64
- (T-10/T-11/R-409)
65
- - [x] โŒจ๏ธ **CLI contract** โ€” strict `generate` / `reverse` grammar, complete option
66
- mapping and help, isolated stdout/stderr channels, stable exit statuses,
67
- trusted-tree disclosure, and direct/package execution ([usage](10-usage.md#-cli))
68
- - [x] ๐Ÿ“ธ **Golden generated-tree contract** โ€” deliberate reproducible updates,
69
- complete byte-for-byte tree comparisons in all canonical layouts, and
70
- strict compilation against installed published `km-api@0.4.1`, including
71
- `trace`, custom/default statuses, and extension media types (R-112/R-126)
72
- - [x] ๐Ÿ“ˆ **Coverage gates** โ€” V8 measures every source file; overall line/function
73
- coverage is gated at 90%, branches at 85%, conversion engines at 95% lines,
74
- and every source file at 80% lines, with omitted-file detection
75
- - [x] ๐ŸŸฃ **Bun gate** โ€” pinned Bun 1.2.21, frozen `bun.lock` installation,
76
- typecheck, Vitest, coverage, direct/package CLI smoke tests, and Bun-native
77
- generated-TypeScript reverse imports run through one command (T-14/D-01)
78
- - [x] ๐Ÿงช **Vitest suite** โ€” unit, integration, round-trip, golden-tree, JSDoc,
79
- CLI, and package-release contracts run in the pinned Bun gate
80
- ([testing](11-testing.md))
81
- - [x] ๐Ÿ“– **JSDoc audit** โ€” every exported declaration and exposed interface/class
82
- member has a useful summary; public callables document parameters and return
83
- values; optional configuration fields state defaults; examples and links are
84
- checked; and named-only exports are enforced by an AST contract suite (T-12,
85
- R-131โ€ฆR-135/R-1003)
86
- - [x] ๐Ÿ“ฆ **Package/release readiness** โ€” public npm metadata, an allowlisted
87
- source archive, executable CLI, isolated packed-consumer import and CLI
88
- smoke tests, and a mandatory prepublish gate (R-191โ€ฆR-193)
89
- - [x] ๐Ÿ“š **Documentation status sync** โ€” current module/test layouts, generated
90
- examples, Phase 1 completion labels, and deferred boundaries agree with
91
- the implementation and are guarded by a documentation contract test
92
- - [x] ๐Ÿ“œ First versioned entry in `CHANGELOG.md` โ†’ **v0.1.0**
93
-
94
- ### โœ… Definition of done โ€” Phase 1 (met)
95
-
96
- The v0.1.0 release satisfies all of the following:
97
-
98
- 1. ๐Ÿ“„ `bun run test` is green โ€” unit + integration + round-trip suites
99
- 2. ๐Ÿ“ˆ Coverage gates are met (see [Testing โ†’ Coverage gates](11-testing.md#-coverage-gates))
100
- 3. ๐Ÿ” Round-trip: for every fixture spec, `openapi(docs(spec))` equals `spec` after canonicalization
101
- 4. ๐Ÿงฉ All four engine docs sections are implemented exactly as specified (mapping tables are the contract)
102
- 5. ๐Ÿ“– Every public symbol has JSDoc; `tsc --noEmit` (strict) passes
103
- 6. ๐Ÿ“œ `CHANGELOG.md` v0.1.0 entry exists and `README` status banner is updated
104
- 7. โœ… **km-api dependency gate** (D-15) โ€” `km-api@0.4.1` is published and zopia
105
- uses `km-api: ^0.4.1` from npm; no Git submodule or unpublished commit remains.
106
- The dependency remains installed and type-checkable in every release gate.
107
-
108
- ## ๐Ÿงฐ Phase 2 โ€” Breadth (v0.2.x) โœ…
109
-
110
- - ๐Ÿ“ **YAML input** โ€” โœ… accept `swagger.yaml` / `openapi.yaml` (D-13 lifts โ†’ D-16)
111
- - ๐Ÿ”— **External `$ref`s** โ€” โœ… resolve references to other files in the same folder (D-17)
112
- - ๐Ÿงฉ **Reusable parameters & responses** โ€” โœ… emitted as their own component
113
- files `components/parameters/<Name>/index.ts` and
114
- `components/responses/<Name>/index.ts` with `<Name>Parameter` / `<Name>Response`
115
- exports, kind barrels, manifest `kind` entries, and a reverse conversion that
116
- refreshes declarations from the modules while restoring every use-site `$ref`
117
- verbatim (D-18; km-api has no standalone-parameter concept, so modules hold the
118
- derived schema only โ€” name/location/`required` remain operation data)
119
- - ๐Ÿงพ **`zopia.config.ts`** โ€” โœ… project-level config file with working-directory
120
- discovery, explicit `--config` paths, validated `{ generate, reverse }` defaults,
121
- and stable precedence CLI flag > config value > built-in default (D-19);
122
- CLI flags stay available
123
- - ๐Ÿ“ค **OpenAPI 2.0 output** โ€” โœ… engine โ‘ฃ accepts `version: '2.0'` (CLI
124
- `--version 2.0`, config `reverse.version`) and downgrades 3.x-sourced
125
- manifests into Swagger 2.0 documents: `nullable` becomes `x-nullable`,
126
- `requestBody` becomes `body`/`formData` parameters, `components` becomes
127
- top-level `definitions`/`parameters`/`responses`, `servers` decomposes into
128
- `host`/`basePath`/`schemes`, and unrepresentable 3.x features (webhooks,
129
- `jsonSchemaDialect`, cookie params, `links`, multi-flow OAuth2, โ€ฆ) drop with
130
- deterministic `ZOPIA_WARN_DIALECT_DOWNGRADE` warnings (D-20)
131
- - ๐Ÿงช **More JSON Schema keywords** โ€” โœ… `propertyNames` graduates from the D-12
132
- approximation to native conversion: the exact
133
- `{ type: 'object', propertyNames: { type: 'string', <pattern/length constraints> }, additionalProperties: <schema> }`
134
- form now converts to `z.record(key, value)` (and back) with no warning or
135
- frozen overlay (D-22); every other listed keyword keeps its runtime-refinement
136
- + frozen-overlay round-trip (`patternProperties`, `if/then/else`,
137
- `minProperties/maxProperties`, non-native `propertyNames` forms, `contains`) (Phase 1: documented approximation + warning, D-12)
138
- - ๐Ÿช **3.1 webhook endpoint generation** โ€” โœ… `document.webhooks` operations now
139
- generate real endpoint files under `webhooks/<name>/<method>/index.ts`
140
- alongside path operations (D-23). The manifest records `webhooks[]` entries
141
- with the same reference/overlay/security metadata as path endpoints plus
142
- `webhookOrder` and a `webhooksOverlay` (item-level metadata, `x-` names, and
143
- operation-less items stay verbatim), so engine โ‘ฃ reassembles webhooks in exact
144
- source order for 3.1 output and omits them with source-located
145
- `ZOPIA_WARN_WEBHOOKS` warnings for 3.0/2.0 output. Operation-less webhook maps
146
- keep the Phase 1 manifest-only behavior (no endpoint files, forward warning)
147
- - ๐Ÿ‘€ **Watch mode** โ€” โœ… `zopia generate --watch` regenerates whenever the spec
148
- file changes (S-88): an immediate initial run, 50 ms-coalesced re-runs that
149
- serialize against in-flight generation, run errors printed to stderr while
150
- watching continues, and abort/cleanup on exit. Uses `fs.watch` with the
151
- existing stale-tree/prune pipeline, so spec edits refresh owned files in place
152
-
153
- ## ๐ŸŒŒ Phase 3 โ€” Ecosystem (v0.3+) โœ…
154
-
155
- - ๐Ÿงน **`zopia validate`** โ€” โœ… CLI command + `validateZopia()` API lint specs
156
- and generated trees (S-89): specs are checked for dialect validity, broken
157
- local `$ref`s, endpoint-planning failures (name collisions, cross-namespace
158
- duplicate operationIds), and components unreachable from any operation
159
- (transitive; Swagger `definitions` included). Generated trees are checked for
160
- manifest presence/validity, a successful reverse dry-run, and km-api peer
161
- drift. Findings are deterministic sorted diagnostics with stable
162
- `ZOPIA_VALIDATE_*` lint codes; the CLI prints them on stdout and exits `1`
163
- when any error-severity finding exists
164
- - โ™ป๏ธ **Incremental regeneration** โ€” โœ… unchanged generated files keep their
165
- mtimes (byte-identical regeneration writes nothing); the opt-in merge-safe
166
- custom layer (D-24, S-90) exports a `custom` companion namespace per
167
- endpoint/webhook scaffolded once and never overwritten (`--custom`,
168
- `generate.custom`, or the `custom` generate option)
169
- - ๐Ÿ” **Diff tool** โ€” โœ… `zopia diff old.json new.json` + `diffOpenApiSpecs()`
170
- API (S-91): semantic comparison of dialect, info, endpoints (with
171
- parameter/request-body/response details), webhooks, schema components,
172
- document fields, and `x-` extensions; key order is ignored; deterministic
173
- `+`/`-`/`~` human-readable lines with a summary; differences are data on
174
- stdout, so the CLI exits `0` for changed pairs
175
- - ๐Ÿงฉ **Presets** โ€” โœ… split-generation layouts (S-92): `--preset multi-tag`
176
- routes each operation by its primary tag, `--preset multi-server` by the
177
- effective first server (repeatedly), producing one independently
178
- reverse-convertible api-docs sub-tree per bucket; untagged/default-server
179
- operations land in `untagged`/`https-default-server`-style buckets; nothing
180
- to split โ†’ the normal single tree. Programmatic `preset` option plus
181
- `generate.preset` configuration; `planPresetBuckets()` exposes the pure
182
- deterministic planner (collision-safe slugs, `ZOPIA_WARN_PRESET_PRIMARY_TAG`
183
- on multi-tagged operations) and results report `trees[]`
184
- - ๐Ÿง‘โ€๐Ÿ’ป **VS Code extension** โ€” โœ… navigate spec โ†” generated code both ways
185
- (S-93): manifest-driven navigation core (`loadNavigationIndex`,
186
- `specToLocations`/`treeToSpecLocation`, one-pass JSON
187
- `specPointersToLines`/`specPointerAtLine` cursor resolution, YAML
188
- operationId fallback) plus the `zopia navigate` CLI (`--to-code` /
189
- `--to-spec`) and a plain-JS extension package under `editors/vscode/`
190
- resolving the workspace's own zopia install
191
-
192
- ## ๐Ÿงฎ Versioning
193
-
194
- SemVer, strictly:
195
-
196
- | ๐Ÿ”– Bump | When |
197
- | --- | --- |
198
- | `MAJOR` | A generated-file format or manifest format breaks (`zopia:manifest@1` โ†’ `@2`) |
199
- | `MINOR` | New option, new engine feature, new CLI flag โ€” backward compatible |
200
- | `PATCH` | Bug fixes, doc corrections, mapping-table clarifications |
201
-
202
- ## ๐Ÿ”— Next
203
-
204
- - ๐ŸŽฏ The exact list of what Phase 1 builds โ†’ [Targets](02-targets.md)
205
- - ๐Ÿ—๏ธ How the pieces fit โ†’ [Architecture](04-architecture.md)
@@ -1,345 +0,0 @@
1
- # ๐Ÿ—๏ธ Architecture
2
-
3
- This document describes **how zopia v0.1.0 is built**: module layout, conversion
4
- pipeline, stage-specific representations, reference handling, and the
5
- error/safety model. Code and documentation change together; disagreement is a
6
- release-blocking defect under the
7
- [docs convention](12-standards.md#-docs-convention).
8
-
9
- ## ๐Ÿงฉ Module layout
10
-
11
- ```text
12
- . # ๐Ÿ“ฆ repository root
13
- โ”œโ”€โ”€ bin/zopia.js # โŒจ๏ธ npm executable; launches the Bun CLI
14
- โ”œโ”€โ”€ src/
15
- โ”‚ โ”œโ”€โ”€ index.ts # ๐Ÿšช public named-export surface
16
- โ”‚ โ”œโ”€โ”€ cli.ts # โŒจ๏ธ process entry point
17
- โ”‚ โ”œโ”€โ”€ cli-command.ts # strict parser, help, output/exit contract
18
- โ”‚ โ”œโ”€โ”€ config.ts # ๐Ÿงพ zopia.config.ts discovery, trusted import, validation (D-19)
19
- โ”‚ โ”œโ”€โ”€ errors.ts # ๐Ÿ›‘ typed error catalogue
20
- โ”‚ โ”œโ”€โ”€ warnings.ts # โš ๏ธ structured warning pipeline
21
- โ”‚ โ”œโ”€โ”€ validation.ts # ๐Ÿงน zopia validate lint batteries (specs + generated trees, S-89)
22
- โ”‚ โ”œโ”€โ”€ diff.ts # ๐Ÿ” zopia diff semantic spec comparison (S-91)
23
- โ”‚ โ””โ”€โ”€ conversions/
24
- โ”‚ โ”œโ”€โ”€ zod-to-json-schema.ts # โ‘  Zod โ†’ JSON Schema
25
- โ”‚ โ”œโ”€โ”€ json-schema-to-zod.ts # โ‘ก JSON Schema โ†’ Zod
26
- โ”‚ โ”œโ”€โ”€ yaml.ts # owned YAML 1.2 core-schema parser (D-16)
27
- โ”‚ โ”œโ”€โ”€ openapi.ts # dialect/envelope normalization
28
- โ”‚ โ”œโ”€โ”€ openapi-ref.ts # local JSON Pointer resolution
29
- โ”‚ โ”œโ”€โ”€ openapi-external-ref.ts # same-folder external $ref bundling (D-17)
30
- โ”‚ โ”œโ”€โ”€ openapi-to-api-docs.ts # operation collection
31
- โ”‚ โ”œโ”€โ”€ openapi-ir.ts # operation-level generation IR
32
- โ”‚ โ”œโ”€โ”€ openapi-contracts.ts # request/response extraction
33
- โ”‚ โ”œโ”€โ”€ api-docs-layout.ts # directory/flat path mapping
34
- โ”‚ โ”œโ”€โ”€ api-docs-plan.ts # collision-safe file planning
35
- โ”‚ โ”œโ”€โ”€ api-docs-facade.ts # ergonomic access-path helper
36
- โ”‚ โ”œโ”€โ”€ api-docs-generate.ts # โ‘ข rendering + guarded writes
37
- โ”‚ โ”œโ”€โ”€ openapi-to-api-docs-public.ts # public Engine โ‘ข wrapper
38
- โ”‚ โ”œโ”€โ”€ api-docs-presets.ts # split-generation bucket planner (S-92)
39
- โ”‚ โ”œโ”€โ”€ api-docs-navigation.ts # spec โ†” tree navigation index (S-93)
40
- โ”‚ โ”œโ”€โ”€ manifest-writer.ts # canonical manifest contract
41
- โ”‚ โ”œโ”€โ”€ manifest-staleness.ts # drift/ownership cleanup
42
- โ”‚ โ”œโ”€โ”€ manifest-to-openapi.ts # โ‘ฃ trusted import + reconstruction
43
- โ”‚ โ””โ”€โ”€ reverse-security.ts # reverse security fallback
44
- โ”œโ”€โ”€ tests/ # ๐Ÿงช focused, integration, contract, round-trip
45
- โ”œโ”€โ”€ scripts/ # ๐ŸŸฃ coverage, golden, package, release gates
46
- โ””โ”€โ”€ docs/ # ๐Ÿ“– this documentation
47
- ```
48
-
49
- `km-api@^0.4.1` is consumed from npm as a peer and development dependency
50
- (D-15). Generated endpoint files and the golden typecheck use that published
51
- surface directly; there is no vendored copy or package swap remaining.
52
-
53
- YAML input (v0.2.x, D-16) is parsed by the owned, deterministic parser in
54
- `src/conversions/yaml.ts` (YAML 1.2 core-schema scalars, block/flow
55
- collections, quoted and block scalars, anchors/aliases/`<<` merge keys,
56
- single-document streams). It stays pure (P-3) and adds no runtime dependency
57
- (D-11); every rejection is a typed `ZOPIA_SPEC_INVALID_YAML`.
58
-
59
- File-path inputs additionally resolve **same-folder external `$ref`s** before
60
- normalization (v0.2.x, D-17): `src/conversions/openapi-external-ref.ts` bundles
61
- references like `other.yaml#/pointer` (plus `other.json`/`other.yml`/`./โ€ฆ`
62
- spellings and whole-file targets) inline, with each sibling file read once
63
- (P-1), bundled content deep-cloned, and nested cross-file references resolved
64
- against their owning file. Targets outside the spec folder (URLs, absolute
65
- paths, `../`, subdirectories) still fail with `ZOPIA_REF_EXTERNAL`, exactly
66
- like external references in non-file inputs.
67
-
68
- > ๐Ÿ“ Production modules use `kebab-case.ts`; `src/index.ts` is the package-root
69
- > re-export surface. Focused and integration tests live under `tests/`, with
70
- > dedicated `tests/contract/` and `tests/roundtrip/` suites. See
71
- > [Standards โ†’ Naming](12-standards.md#-naming).
72
-
73
- ## ๐Ÿ”„ The pipeline
74
-
75
- Conversion work is split between in-memory transforms and explicit adapters.
76
- Engines โ‘ /โ‘ก are in-memory; the public engine โ‘ข/โ‘ฃ wrappers own documented file
77
- reads, guarded generated-tree writes, trusted module imports, and warning
78
- collection. Process arguments/output remain in the CLI.
79
-
80
- ```mermaid
81
- flowchart TB
82
- subgraph IN ["โ‘ข OpenAPI โ†’ api docs"]
83
- A["object ยท JSON/YAML text ยท .json/.yaml path"] --> B["normalizeOpenApiDocument()"]
84
- B --> C["collectOpenApiOperations()"]
85
- C --> D["buildOpenApiOperationIR() + extractOperationContracts()"]
86
- D --> E["planApiDocsFiles()"]
87
- E --> F["render endpoints/components"]
88
- F --> G["createZopiaManifest()"]
89
- G --> H["guarded writes + stale-owned cleanup"]
90
- H --> I["๐Ÿ“‚ api_docs/**"]
91
- end
92
-
93
- subgraph OUT ["โ‘ฃ api docs โ†’ OpenAPI"]
94
- J["๐Ÿ“‚ api_docs/**"] --> K["validate manifest + owned paths"]
95
- K --> L["trusted import of endpoint/component .ts"]
96
- L --> M["runtime Zod โ†’ schema serialization"]
97
- M --> N["apply refs + manifest overlays"]
98
- N --> O["dialect translation + canonical document"]
99
- end
100
- ```
101
-
102
- The manifest boundary in `src/conversions/manifest-writer.ts` separates pure,
103
- detached snapshot construction and canonical validation/serialization from
104
- atomic filesystem output. Engine โ‘ข records source facts that generated Zod or
105
- km-api values cannot carry; engine โ‘ฃ combines those snapshots with imported
106
- runtime values so developer edits remain authoritative where representable.
107
-
108
- Round-trip stability does not depend on one repository-wide `ApiModel`. The
109
- implemented boundaries use a validated document envelope, an operation-level
110
- IR for generation, normalized operation contracts, and the versioned manifest
111
- for reverse reconstruction. Each shape is narrower than the stage that consumes
112
- it, and fixture properties verify their composition (T-11/R-409).
113
-
114
- ## ๐Ÿงฌ The internal representations
115
-
116
- The implementation uses stage-specific public shapes rather than one oversized
117
- model. The generation path starts with the validated source envelope:
118
-
119
- ```ts
120
- interface NormalizedOpenApiDocument {
121
- document: OpenApiDocument;
122
- version: '2.0' | '3.0' | '3.1';
123
- title?: string;
124
- versionString?: string;
125
- }
126
- ```
127
-
128
- Each collected operation is then narrowed to the data endpoint rendering needs:
129
-
130
- ```ts
131
- interface OpenApiOperationIR {
132
- path: string;
133
- method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'OPTIONS' | 'PATCH' | 'TRACE';
134
- pathShape: string;
135
- operationId: string;
136
- summary?: string;
137
- description?: string;
138
- tags: string[];
139
- deprecated: boolean;
140
- security?: unknown[];
141
- operation: Record<string, any>;
142
- parameters: any[];
143
- document: OpenApiDocument;
144
- }
145
-
146
- interface OperationContracts {
147
- parameters: Array<{
148
- name: string;
149
- in: 'path' | 'query' | 'header' | 'cookie';
150
- required: boolean;
151
- schema?: unknown;
152
- }>;
153
- requestBody?: { contentType: string; schema?: unknown; required: boolean };
154
- responses: Array<{
155
- status: string;
156
- description: string;
157
- contentType?: string;
158
- schema?: unknown;
159
- }>;
160
- }
161
- ```
162
-
163
- Reverse conversion is anchored by `ZopiaManifest`, not by a hidden in-memory
164
- model. It records source dialect/frame metadata, generated file ownership,
165
- operation snapshots, component schemas, source path order/empty Path Items,
166
- explicit schema-container presence, local-reference placements, and restoration
167
- overlays. Current writer shapes are validated before serialization;
168
- the reader retains explicit compatibility allowances for older optional fields.
169
-
170
- ### ๐Ÿ” Canonical order (determinism, P-1)
171
-
172
- | ๐Ÿ“ฆ Where | ๐Ÿ“ Order |
173
- | --- | --- |
174
- | collected operations | source path order; fixed method order `get, post, put, delete, head, options, patch, trace` |
175
- | public generated-file result | lexical order by portable relative `path` |
176
- | runtime schema properties | document order (JSON object key order of the source) |
177
- | generated TypeScript schema maps/literal-object keys | lexical order (reverse restores source `required` order only while membership is unchanged) |
178
- | OpenAPI output document | `openapi, info, servers, security, tags, paths, components, externalDocs` |
179
- | path keys inside `paths` | sorted by path string |
180
-
181
- > ๐Ÿ“Œ **Rule R-401** โ€” anywhere zopia *creates* a list or object, the order above
182
- > applies. Anywhere zopia *mirrors* source data (runtime properties, params),
183
- > the source order applies. Generated TypeScript canonicalizes schema-map and
184
- > literal-object keys so parsing a canonical manifest cannot change the next
185
- > generated tree. No `Date.now()`, no `Math.random()` anywhere in the package
186
- > (P-1). Value-level normalizations (Zod sentinel bounds, const-union โ†’
187
- > `enum`, the `io` input/output split for `required`/`default`) live with the
188
- > engines that apply them โ€” see
189
- > [Conversions โ†’ R-615 / R-618 / R-654](06-conversions.md).
190
-
191
- ## ๐Ÿ”— The reference graph
192
-
193
- `$ref` is a graph, and it can cycle (e.g. `Comment.replies โ†’ Comment`):
194
-
195
- ```mermaid
196
- flowchart LR
197
- Post["๐Ÿ“„ Post"] -->|"$.ref #/components/schemas/Author"| Author["๐Ÿ‘ค Author"]
198
- Author -->|"$.ref #/components/schemas/Address"| Address["๐Ÿ“ Address"]
199
- Comment["๐Ÿ’ฌ Comment"] -->|"$.ref #/components/schemas/Comment"| Comment
200
- ```
201
-
202
- **Implemented flow** (`openapi-ref.ts`, generation, and manifest modules):
203
-
204
- 1. ๐Ÿงท **Bundle external refs** (file inputs only, D-17) โ€” resolve same-folder
205
- external references inline first so every later step sees one document.
206
- 2. ๐Ÿ›‘ **Preflight** โ€” walk source values, reject remaining external refs,
207
- validate local pointer escapes, and report missing targets with exact
208
- locations.
209
- 3. ๐Ÿ”— **Resolve operation refs** โ€” path-item and parameter chains use per-chain
210
- seen sets, so malformed and circular non-schema references fail explicitly.
211
- 4. ๐Ÿงฉ **Collect component dependencies** โ€” rendering finds schema-component
212
- targets while excluding literal/example data that merely contains `$ref` text.
213
- 5. ๐Ÿงต **Render schemas** โ€” engine โ‘ก's local-definition state and component
214
- dependency reachability detect recursive edges; self and mutual cycles become
215
- `z.lazy()` references.
216
- 6. ๐Ÿ“ฆ **Record identity** โ€” the manifest stores original reference placements;
217
- reverse conversion combines those records with imported runtime schema
218
- identity to restore local `$ref`s.
219
-
220
- > ๐Ÿ“Œ **Rule R-402** โ€” circular schema components remain executable through
221
- > `z.lazy()`, while linear refs become direct references/imports. **Scope:**
222
- > graph nodes are schema components only. Reusable non-schema objects (Swagger
223
- > 2.0 global `parameters`/`responses`, OpenAPI 3
224
- > `components.parameters`/`responses`) *also* get their own component modules in
225
- > components mode (v0.2.x โ€” D-18): a module holds only the declaration's derived
226
- > schema, and in-source declarations plus use-site `$ref` placements restore
227
- > verbatim on reverse conversion while the declaration refreshes from the
228
- > current module. Bare `$ref` use sites import from the per-kind barrels; merged
229
- > `$ref`-sibling forms still resolve at use sites for generated runtime configs.
230
-
231
- ## ๐Ÿงฎ Schema reuse within generated files
232
-
233
- Default mode keeps every endpoint self-contained: each request/parameter/response
234
- schema occurrence is rendered in place. Engine โ‘ก may build a local-definition
235
- closure inside an expression when resolving `$defs` or inlined component refs;
236
- it does not hoist structurally identical endpoint contracts into shared top-level
237
- constants.
238
-
239
- > ๐Ÿ“Œ **Rule R-403** โ€” schema reuse is explicit, not inferred from structural
240
- > equality. Default mode independently inlines each contract occurrence. With
241
- > `useComponentAsReference: true`, each referenced component export is imported
242
- > at most once per endpoint and reused wherever that identity occurs.
243
-
244
- ## ๐Ÿ›‘ Error model
245
-
246
- All errors crossing a zopia boundary extend one base class โ€” **no raw `Error`, no thrown strings** (standards โ†’ Errors). `ZOPIA_ERROR_CODES` is the immutable runtime catalogue and the source of the `ZopiaErrorCode` union; `isZopiaError()` narrows unknown failures and `asZopiaError()` preserves an existing typed error or attaches a lower-level failure as `cause`.
247
-
248
- ```ts
249
- export class ZopiaError extends Error {
250
- /** ๐Ÿ†” Stable machine-readable code, e.g. 'ZOPIA_REF_NOT_FOUND'. */
251
- readonly code: ZopiaErrorCode;
252
- /** ๐Ÿ“ JSON-pointer, option name, or file location, when discoverable. */
253
- readonly at?: string;
254
- /** ๐Ÿ’ก Actionable, human-readable suggestion (always populated). */
255
- readonly hint: string;
256
- /** ๐Ÿ”— Original parser, import, or filesystem failure, when translated. */
257
- readonly cause?: unknown;
258
- }
259
- ```
260
-
261
- | ๐Ÿ†” Code | ๐Ÿ“ Where | ๐Ÿ’ฅ When | ๐Ÿ’ก Hint pattern |
262
- | --- | --- | --- | --- |
263
- | `ZOPIA_CONFIG_INVALID` | public options / CLI | an argument, option, or option combination is invalid | "correct the invalid option or argument" |
264
- | `ZOPIA_DOCS_IMPORT_FAILED` | engine โ‘ฃ | generated modules cannot load, export one expected value, or serialize edited runtime schemas | "fix or regenerate the affected generated module" |
265
- | `ZOPIA_DOCS_MANIFEST_MISMATCH` | engine โ‘ฃ preflight | a manifest-owned endpoint/component file is missing, renamed, or not a regular file | "regenerate the tree or restore its generated files" |
266
- | `ZOPIA_DOCS_MISSING_MANIFEST` | engine โ‘ฃ entry | no `.zopia-manifest.json` exists at the selected path | "generate api docs first or pass the manifest path" |
267
- | `ZOPIA_FS_OUTSIDE_OUTDIR` | generation guard | a generated path escapes `outDir` or traverses an unsafe ancestor | "keep generated paths inside the output directory" |
268
- | `ZOPIA_FS_WRITE_FAILED` | writers / CLI | output inspection, directory creation, cleanup, or writing fails | "check the output path, permissions, and available disk space" |
269
- | `ZOPIA_MANIFEST_INVALID` | manifest writer/reader | manifest JSON or metadata violates `zopia:manifest@1` | "regenerate the manifest or fix its invalid metadata" |
270
- | `ZOPIA_REF_EXTERNAL` | external-ref bundling / reference validation | `$ref` escapes the spec folder, or a non-file input points to another file | "keep external targets next to the spec file" |
271
- | `ZOPIA_REF_NOT_FOUND` | reference validation | a local `$ref` is malformed, circular where unsupported, or unresolved | "check that the local JSON Pointer target exists" |
272
- | `ZOPIA_SCHEMA_INVALID` | engines โ‘ /โ‘ก | the Zod or JSON Schema input cannot be converted | "provide a valid Zod or JSON Schema value" |
273
- | `ZOPIA_SPEC_INVALID` | OpenAPI validation | the parsed document violates the supported Swagger/OpenAPI shape | "fix the invalid Swagger/OpenAPI document" |
274
- | `ZOPIA_SPEC_INVALID_JSON` | JSON entry points | source text is unreadable or not valid JSON | "provide readable, valid JSON" |
275
- | `ZOPIA_SPEC_INVALID_YAML` | YAML entry points | source text is unreadable, malformed/unsupported YAML, or holds a non-JSON value | "provide readable, valid YAML" |
276
- | `ZOPIA_SPEC_MISSING_PATHS` | normalizers | the document has no object-valued `paths` | "add a paths object" |
277
- | `ZOPIA_SPEC_PATH_REF` | operation collection | a path-item reference is invalid or circular | "use a valid local path-item reference" |
278
- | `ZOPIA_SPEC_UNSUPPORTED_VERSION` | normalization | neither Swagger 2.0 nor OpenAPI 3.0/3.1 is selected | "use Swagger 2.0, OpenAPI 3.0, or OpenAPI 3.1" |
279
- | `ZOPIA_WARNING_INVALID` | warnings pipeline | a warning iterable/code/location/message is malformed | "provide a valid warning code, location, and message" |
280
-
281
- > ๐Ÿ“Œ **Rule R-404** โ€” every error is thrown as a `ZopiaError` with a stable
282
- > code, a location (`at`) when discoverable, and a `hint`. Tests assert on
283
- > `code`, never on message text.
284
-
285
- ## โš ๏ธ Warning model
286
-
287
- Warnings are non-fatal conversion diagnostics. Every public engine uses the
288
- same `ZopiaWarning` contract and stable `ZopiaWarningCode` union:
289
-
290
- ```ts
291
- interface ZopiaWarning {
292
- code: ZopiaWarningCode;
293
- at?: string; // escaped RFC 6901 JSON Pointer, including leading #
294
- message: string;
295
- }
296
- ```
297
-
298
- | ๐Ÿ†” Stable code | ๐Ÿ“ Meaning |
299
- | --- | --- |
300
- | `ZOPIA_WARN_UNREPRESENTABLE` | a Zod node cannot be represented in the selected schema dialect |
301
- | `ZOPIA_WARN_INVALID_SCHEMA` | a malformed JSON Schema keyword is ignored or approximated |
302
- | `ZOPIA_WARN_CUSTOM_FORMAT`, `ZOPIA_WARN_CONTENT_ENCODING`, `ZOPIA_WARN_INT64` | a string/numeric format or encoding has no exact runtime equivalent |
303
- | `ZOPIA_WARN_LEGACY_EXCLUSIVE_BOUND` | a legacy boolean exclusive bound requires normalization |
304
- | `ZOPIA_WARN_ONE_OF`, `ZOPIA_WARN_NOT`, `ZOPIA_WARN_UNIQUE_ITEMS`, `ZOPIA_WARN_FROZEN_SUBTREE` | an applicator or refinement needs an approximation or frozen manifest restoration |
305
- | `ZOPIA_WARN_REF` | a recoverable schema-reference conversion cannot be exact |
306
- | `ZOPIA_WARN_MULTI_CONTENT`, `ZOPIA_WARN_SERVER_VARIABLES`, `ZOPIA_WARN_WEBHOOKS` | an OpenAPI document fact has no direct generated-code representation |
307
- | `ZOPIA_WARN_STALE_TREE` | regeneration found source/config drift, missing owned files, manifest disablement, or invalid existing metadata |
308
- | `ZOPIA_WARN_DEFAULT_INFO`, `ZOPIA_WARN_DEFAULT_SECURITY` | reverse conversion synthesized documented fallback metadata or security |
309
- | `ZOPIA_WARN_DIALECT_DOWNGRADE` | OpenAPI 3.1-only content is omitted from 3.0 output |
310
-
311
- The shared collector validates codes, collapses line breaks in messages,
312
- deduplicates identical diagnostics, and sorts by location, code, then message.
313
- Nested engine warnings are rebased rather than string-concatenated ad hoc, so
314
- `at` always identifies the affected source or output node. Engine โ‘  and engine
315
- โ‘ฃ invoke `onWarning` once per normalized warning; engines โ‘กโ€“โ‘ฃ also return
316
- normalized warning arrays. Engine โ‘ก mirrors losses with the canonical marker
317
- `// @zopia:warn CODE subject โ€” message (pointer)`. The CLI renders the same
318
- warning as `Warning: CODE pointer: message` on **stderr**, leaving reverse JSON
319
- on stdout parseable.
320
-
321
- ## ๐Ÿ›ก๏ธ Safety & boundaries
322
-
323
- | # | Rule | Where enforced |
324
- | --- | --- | --- |
325
- | R-405 | **Pure core** โ€” schema/operation transforms work on in-memory values; documented file input, generated-tree writes/imports, and process output stay in public adapters and the CLI | conversion modules + `cli-command.ts` |
326
- | R-406 | **outDir guard** โ€” every generated path is canonicalized and verified to stay inside `outDir`; regeneration refuses symlinked path ancestors, and manifest temporary writes use exclusive creation so stale symlinks cannot redirect output. Obsolete cleanup trusts only a fully validated manifest and never recursively deletes an output root | `api-docs-generate.ts` + manifest boundaries |
327
- | R-407 | **Trusted-input contract** โ€” engine โ‘ฃ imports generated `.ts` files (executes them). This is by design (D-08) and only for trees that carry a valid zopia manifest | `manifest-to-openapi.ts` |
328
- | R-408 | **No silent loss** โ€” every lossy/unsupported conversion produces a normalized `ZopiaWarning` (D-12): `{ code, at?, message }` (shape fixed by R-144). Public wrappers return or callback each warning; engine โ‘ก and generated api-doc files mirror schema warnings as canonical `// @zopia:warn โ€ฆ` comments; CLI diagnostics go only to stderr | every engine + CLI |
329
- | R-409 | **Idempotent regeneration** โ€” re-running engine โ‘ข with identical input + options produces byte-identical output; regenerating source-preserving engine โ‘ฃ output does too. Fixture properties cover Swagger 2.0 and OpenAPI 3.0/3.1 across layout/component modes, while stale source/config/incomplete-tree state is warned and repaired and only obsolete manifest-owned files are pruned; engine โ‘ฃ output is canonical (R-401) | round-trip + staleness tests |
330
-
331
- ## ๐Ÿ“ Performance
332
-
333
- - ๐Ÿงฎ Operation and local-reference passes are deterministic traversals with
334
- explicit seen sets for reference chains.
335
- - ๐ŸŒณ Component source is rendered and validated before its files are written;
336
- endpoint files are then rendered and written in planned order.
337
- - ๐Ÿงต Cycle-aware definition/reachability state terminates recursive schemas and
338
- emits lazy edges rather than expanding them forever.
339
- - ๐Ÿ“ฆ Optional component extraction/reference imports avoid repeated component
340
- definitions; default mode deliberately favors self-contained endpoint files.
341
-
342
- ## ๐Ÿ”— Next
343
-
344
- - ๐Ÿงฉ Vocabulary used above โ†’ [Concepts](05-concepts.md)
345
- - ๐Ÿ”„ Engine-by-engine rules โ†’ [Conversions](06-conversions.md)