zopia 0.3.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.
Files changed (47) hide show
  1. package/CHANGELOG.md +354 -0
  2. package/LICENSE +21 -0
  3. package/README.md +167 -0
  4. package/bin/zopia.js +20 -0
  5. package/docs/01-overview.md +94 -0
  6. package/docs/02-targets.md +55 -0
  7. package/docs/03-roadmap.md +205 -0
  8. package/docs/04-architecture.md +345 -0
  9. package/docs/05-concepts.md +239 -0
  10. package/docs/06-conversions.md +493 -0
  11. package/docs/07-api-docs.md +337 -0
  12. package/docs/08-components.md +223 -0
  13. package/docs/09-configuration.md +167 -0
  14. package/docs/10-usage.md +208 -0
  15. package/docs/11-testing.md +267 -0
  16. package/docs/12-standards.md +242 -0
  17. package/docs/README.md +42 -0
  18. package/docs/publish-workflow.yml.example +48 -0
  19. package/package.json +77 -0
  20. package/src/api-docs-navigation.ts +353 -0
  21. package/src/cli-command.ts +537 -0
  22. package/src/cli.ts +4 -0
  23. package/src/config.ts +190 -0
  24. package/src/conversions/api-docs-facade.ts +42 -0
  25. package/src/conversions/api-docs-generate.ts +567 -0
  26. package/src/conversions/api-docs-layout.ts +39 -0
  27. package/src/conversions/api-docs-plan.ts +130 -0
  28. package/src/conversions/api-docs-presets.ts +246 -0
  29. package/src/conversions/json-schema-to-zod.ts +931 -0
  30. package/src/conversions/manifest-staleness.ts +211 -0
  31. package/src/conversions/manifest-to-openapi.ts +1861 -0
  32. package/src/conversions/manifest-writer.ts +778 -0
  33. package/src/conversions/openapi-contracts.ts +333 -0
  34. package/src/conversions/openapi-external-ref.ts +233 -0
  35. package/src/conversions/openapi-ir.ts +74 -0
  36. package/src/conversions/openapi-ref.ts +38 -0
  37. package/src/conversions/openapi-to-api-docs-public.ts +466 -0
  38. package/src/conversions/openapi-to-api-docs.ts +203 -0
  39. package/src/conversions/openapi.ts +80 -0
  40. package/src/conversions/reverse-security.ts +68 -0
  41. package/src/conversions/yaml.ts +876 -0
  42. package/src/conversions/zod-to-json-schema.ts +536 -0
  43. package/src/diff.ts +353 -0
  44. package/src/errors.ts +114 -0
  45. package/src/index.ts +80 -0
  46. package/src/validation.ts +299 -0
  47. package/src/warnings.ts +164 -0
@@ -0,0 +1,167 @@
1
+ # ⚙️ Configuration
2
+
3
+ The complete option reference. Every option is **explicit, typed, and
4
+ validated** — invalid combinations fail fast with `ZOPIA_CONFIG_INVALID`
5
+ (they are never silently coerced).
6
+
7
+ > 📌 **Rule R-901** — options may come from three sources with one fixed
8
+ > precedence: **explicit CLI flags win over config-file values, and
9
+ > config-file values win over built-in defaults.** A project config file
10
+ > (`zopia.config.ts`, D-19) is discovered next to the working directory; the
11
+ > option names below are exactly its shape.
12
+
13
+ ## 🧾 `zopia.config.ts` — project defaults (v0.2.x, D-19)
14
+
15
+ ```ts
16
+ import { defineConfig } from 'zopia';
17
+
18
+ export default defineConfig({
19
+ generate: {
20
+ mode: 'directory',
21
+ insertComponents: true,
22
+ useComponentAsReference: true,
23
+ custom: true,
24
+ outDir: 'api_docs',
25
+ },
26
+ reverse: { version: '3.1', out: 'openapi.json' },
27
+ });
28
+ ```
29
+
30
+ | # | Rule |
31
+ | --- | --- |
32
+ | R-940 | The CLI discovers `zopia.config.ts`, then `zopia.config.mts`, next to the **working directory** (`process.cwd()`); `--config <path>` selects an explicit file instead (bare paths resolve from the working directory) and a missing explicit file fails with `ZOPIA_CONFIG_INVALID`. Library users call `loadZopiaConfig({ cwd?, file? })` for the same discovery. |
33
+ | R-941 | The config module accepts a `default` export or a named `config` export (default wins). Its shape is exactly `{ generate?: …, reverse?: … }` with the option keys below; unknown keys, non-`Options` nesting, and mistyped values fail with `ZOPIA_CONFIG_INVALID` located at the offending key (`generate.mode`, `reverse.version`, …). |
34
+ | R-942 | Precedence is stable: **CLI flag > config value > built-in default.** `--no-manifest` overrides `generate.manifest: true`; omitted generate flags adopt `generate.*` booleans; `generate.outDir`/`reverse.out`/`reverse.version` apply only when the positional/flag is absent. Without `generate.outDir` the `<output-dir>` positional stays required for `zopia generate`. |
35
+ | R-943 | The config file is **executed JavaScript** under the same trust model as reverse conversion — only trusted projects should carry one; evaluation failures surface as `ZOPIA_CONFIG_INVALID` with the module's error chained. |
36
+
37
+ ## 📄 `ZopiaGenerateOptions` — engine ③ (`openApiToApiDocs`)
38
+
39
+ ```ts
40
+ interface ZopiaGenerateOptions {
41
+ /** 📂 Where the tree is written. @default 'api_docs' */
42
+ outDir?: string;
43
+
44
+ /** 📂 Layout mode. @default 'directory' */
45
+ mode?: 'directory' | 'flat';
46
+
47
+ /** 🧱 Write components/** with one file per component. @default false (T-8) */
48
+ insertComponents?: boolean;
49
+
50
+ /** 🔗 Endpoints import components instead of inlining.
51
+ * Requires insertComponents: true. @default false (T-9) */
52
+ useComponentAsReference?: boolean;
53
+
54
+ /** 📦 Write .zopia-manifest.json (needed by engine ④ — keep it on). @default true */
55
+ manifest?: boolean;
56
+
57
+ /** 🧩 Write merge-safe custom.ts companions per endpoint. @default false (D-24) */
58
+ custom?: boolean;
59
+
60
+ /** 🧰 Split generation into per-bucket sub-trees: `multi-tag` (per primary tag)
61
+ * or `multi-server` (per effective first server). @default undefined (S-92) */
62
+ preset?: 'multi-tag' | 'multi-server';
63
+ }
64
+
65
+ interface ZopiaGenerateResult {
66
+ files: GeneratedFile[];
67
+ warnings: ZopiaWarning[];
68
+ manifestPath?: string; // absent when manifest: false or a preset split ran
69
+ trees?: ZopiaPresetTree[]; // {name, directory, manifestPath?} per routed bucket (S-92)
70
+ }
71
+ ```
72
+
73
+ | ⚙️ Option | 📏 Type | 🆔 Default | 📝 Notes |
74
+ | --- | --- | --- | --- |
75
+ | `outDir` | `string` | `'api_docs'` | relative or absolute; created if missing; **never deleted recursively without this exact dir** (safety R-406) |
76
+ | `mode` | `'directory' \| 'flat'` | `'directory'` | the two layouts of [API docs format](07-api-docs.md) |
77
+ | `insertComponents` | `boolean` | `false` | T-8 — emits `components/**` (schemas plus reusable `components/parameters/**` / `components/responses/**` modules, v0.2.x — D-18) |
78
+ | `useComponentAsReference` | `boolean` | `false` | T-9 — imports exact structural component schema references in endpoints and recursively renders nested references through the complete Engine ② schema surface; literal `$ref`-looking data is untouched, aliases/cycles use lazy schemas, and valid `$ref` siblings keep their constraints; **requires** `insertComponents: true` |
79
+ | `manifest` | `boolean` | `true` | disabling it makes engine ④ impossible for that tree — a deliberate escape hatch only; regeneration removes a previous manifest and warns that the tree configuration changed |
80
+ | `custom` | `boolean` | `false` | D-24 — every endpoint/webhook module exports `export * as custom from './custom';` and a sibling `custom.ts` is scaffolded once and never overwritten; CLI flag `--custom`, config key `generate.custom` |
81
+ | `preset` | `'multi-tag' \| 'multi-server'` | `undefined` | S-92 — splits generation into one api-docs sub-tree per routed bucket: `multi-tag` routes each operation by its primary tag (`tags[0]`, warning `ZOPIA_WARN_PRESET_PRIMARY_TAG` when several), `multi-server` by the effective first server (operation → path item → document). Untagged/default-server operations land in `untagged` / `https-default-server`-style buckets; the result gains `trees[]` and every bucket is an independently reverse-convertible tree with its own manifest. Collision-safe lowercase slugs; **nothing to split → the normal single tree** and no `trees[]` |
82
+
83
+ ### ✅ Validation rules
84
+
85
+ | # | Invalid input | 🛑 Result |
86
+ | --- | --- | --- |
87
+ | R-911 | `useComponentAsReference: true` + `insertComponents: false` (or omitted) | `ZOPIA_CONFIG_INVALID` — hint: *"enable `insertComponents` first"* |
88
+ | R-912 | `mode` outside `'directory' \| 'flat'` | `ZOPIA_CONFIG_INVALID` |
89
+ | R-913 | `outDir` empty string | `ZOPIA_CONFIG_INVALID` |
90
+ | R-914 | unknown option keys or non-boolean boolean flags | `ZOPIA_CONFIG_INVALID` |
91
+ | R-915 | `preset` outside `'multi-tag' \| 'multi-server'` (options or `generate.preset`) | `ZOPIA_CONFIG_INVALID` |
92
+
93
+ ## 📄 `ZopiaReverseOptions` — engine ④ (`apiDocsToOpenApi`)
94
+
95
+ ```ts
96
+ interface ZopiaReverseOptions {
97
+ /** 🏷️ Spec version to emit. @default '3.1' (D-09, D-20) */
98
+ version?: '2.0' | '3.0' | '3.1';
99
+ /** ⚠️ Receive every normalized reverse-conversion warning. */
100
+ onWarning?: (warning: ZopiaWarning) => void;
101
+ }
102
+ ```
103
+
104
+ | # | Invalid input | 🛑 Result |
105
+ | --- | --- | --- |
106
+ | R-921 | `version` outside `'3.0' \| '3.1'` | `ZOPIA_CONFIG_INVALID` |
107
+
108
+ ## 📄 `ZodToJsonSchemaOptions` — engine ①
109
+
110
+ See [Conversions → Engine ①](06-conversions.md)
111
+ (`target`, `$schema`, `io`).
112
+
113
+ ## 📄 `JsonSchemaToZodOptions` — engine ②
114
+
115
+ | ⚙️ Option | 📏 Type | 🆔 Default | 📝 Notes |
116
+ | --- | --- | --- | --- |
117
+ | `rootName` | `string` | `'schema'` | name of the root const (component files always keep their spec name — `<ComponentName>Schema`) |
118
+
119
+ ## 🧮 Defaults at a glance
120
+
121
+ ```ts
122
+ // 🆔 The implicit default configuration of engine ③:
123
+ {
124
+ outDir: 'api_docs',
125
+ mode: 'directory',
126
+ insertComponents: false, // 🎯 T-8 default
127
+ useComponentAsReference: false, // 🎯 T-9 default
128
+ manifest: true,
129
+ }
130
+ ```
131
+
132
+ > 💡 The defaults produce the **simplest possible tree**: self-contained
133
+ > `index.ts` files, no component files, plus the manifest that keeps the
134
+ > reverse conversion alive. Developers opt *into* complexity only when they
135
+ > want it.
136
+
137
+ ## ⌨️ CLI ↔ options mapping
138
+
139
+ ```text
140
+ zopia generate <spec.json> <output-dir> [--mode <directory|flat>]
141
+ [--insert-components] [--use-component-as-reference] [--custom] [--no-manifest] [--watch]
142
+ zopia reverse <docs-dir> [--out <file.json>] [--version <2.0|3.0|3.1>]
143
+ zopia validate <spec|docs-dir> (no options yet; `--config path` accepted for parity)
144
+ zopia diff <old-spec> <new-spec> (no options yet; `--config path` accepted for parity)
145
+ ```
146
+
147
+ | 🚩 Flag | ⚙️ Option |
148
+ | --- | --- |
149
+ | positional `<output-dir>` (generate) | `outDir` |
150
+ | `--mode <directory\|flat>` | `mode` |
151
+ | `--insert-components` | `insertComponents: true` |
152
+ | `--use-component-as-reference` | `useComponentAsReference: true` |
153
+ | `--custom` | `custom: true` |
154
+ | `--preset <multi-tag\|multi-server>` | `preset` |
155
+ | `--no-manifest` | `manifest: false` |
156
+ | `--watch` | no config equivalent — CLI-only; watches the spec file's parent directory (survives atomic editor saves) and regenerates on change |
157
+ | `--out <file.json>` (reverse) | output file path |
158
+ | `--version <3.0\|3.1>` | `version` |
159
+
160
+ > 📌 **Rule R-931** — positive boolean flags are additive: absence = `false`.
161
+ > `--no-manifest` is the one explicit inverse because manifests default on.
162
+ > Long flags only; no camelCase/kebab ambiguity.
163
+
164
+ ## 🔗 Next
165
+
166
+ - 🚀 How options are used end-to-end → [Usage](10-usage.md)
167
+ - 📂 What each option changes in the tree → [API docs format](07-api-docs.md) · [Components](08-components.md)
@@ -0,0 +1,208 @@
1
+ # 🚀 Usage
2
+
3
+ How to use zopia — installation, the programmatic API (the primary interface),
4
+ and the CLI. The API below is the implemented **Phase 1 contract**, pinned by
5
+ tests.
6
+
7
+ ## 📦 Installation
8
+
9
+ ```bash
10
+ bun add zopia # 📦 the toolkit (no bundled runtime dependencies)
11
+ bun add zod km-api # ⚛️🧱 peer dependencies for conversion/generated code
12
+ ```
13
+
14
+ | 📦 Package | 🏷️ Kind | 📝 Why |
15
+ | --- | --- | --- |
16
+ | `zopia` | dependency | the engines |
17
+ | `zod` `^4` | peer | engines ①/② convert runtime schemas; generated schemas validate at runtime |
18
+ | `km-api` `^0.4.1` (0.4.x) | peer | generated files call `makeApiConfig()` and engine ④ imports their results |
19
+
20
+ ## ⚡ Quick start — all four engines
21
+
22
+ ```ts
23
+ import {
24
+ zodToJsonSchema,
25
+ jsonSchemaToZod,
26
+ openApiToApiDocs,
27
+ apiDocsToOpenApi,
28
+ } from 'zopia';
29
+ import { z } from 'zod';
30
+
31
+ // ── ① Zod → JSON Schema ────────────────────────────────────
32
+ const schema = z.object({ name: z.string().min(1), email: z.email() });
33
+ const jsonSchema = zodToJsonSchema(schema, { target: 'openapi-3.1' });
34
+ // → { type: 'object', properties: { … }, required: ['name', 'email'], … }
35
+
36
+ // ── ② JSON Schema → Zod ────────────────────────────────────
37
+ const { code, schema: back, warnings, overlays } = jsonSchemaToZod(jsonSchema);
38
+ // → code: executable Zod v4 TypeScript (lossy nodes include @zopia:warn markers)
39
+ // → schema: <runtime Zod schema equivalent to `code`>
40
+ // → warnings: structured diagnostics; overlays: exact reverse-conversion restorations
41
+
42
+ // ── ③ OpenAPI → api docs ───────────────────────────────────
43
+ // input: object · JSON/YAML text · .json/.yaml/.yml path (v0.2.x, D-16)
44
+ const result = await openApiToApiDocs('swagger.yaml', {
45
+ mode: 'directory', // 📂 or 'flat'
46
+ insertComponents: false, // 🧱 default
47
+ useComponentAsReference: false, // 🔗 default
48
+ });
49
+ // → one api_docs/<path>/<method>/index.ts per operation + .zopia-manifest.json
50
+ console.log(result.files, result.warnings);
51
+
52
+ // ── ④ api docs → OpenAPI ───────────────────────────────────
53
+ const { openapi, warnings: w2 } = await apiDocsToOpenApi('api_docs', {
54
+ version: '3.1',
55
+ });
56
+ // → a complete OpenAPI document, ready for JSON.stringify
57
+ ```
58
+
59
+ ## 📄 End-to-end — what a developer actually gets
60
+
61
+ ```bash
62
+ $ bunx zopia generate openapi.json api_docs --mode directory --insert-components --use-component-as-reference
63
+ ```
64
+
65
+ With the canonical Admin API fixture saved as `openapi.json`, that produces:
66
+
67
+ ```text
68
+ api_docs/
69
+ ├── .zopia-manifest.json
70
+ ├── components/
71
+ │ ├── index.ts
72
+ │ ├── CreateUser/index.ts
73
+ │ └── User/index.ts
74
+ ├── health/get/index.ts
75
+ └── users/{userId}/
76
+ ├── get/index.ts
77
+ └── patch/index.ts
78
+ ```
79
+
80
+ …and `api_docs/users/{userId}/get/index.ts` is real, runnable, type-safe code —
81
+ see its exact checked-in output in
82
+ [API docs format → The `index.ts` contract](07-api-docs.md#-the-indexts-contract).
83
+ Drop the tree into your project, import what you need:
84
+
85
+ ```ts
86
+ import getUser from './api_docs/users/{userId}/get/index';
87
+
88
+ const url = getUser.makeFullPath({ userId: '550e8400-e29b-41d4-a716-446655440000' });
89
+ const user = getUser.makeBody(undefined); // type-safe: no body on GET
90
+ ```
91
+
92
+ ## ⚠️ Handling warnings
93
+
94
+ Warnings are structured, deterministic, and non-fatal. `code` is a stable
95
+ `ZopiaWarningCode`; `at` is an escaped JSON Pointer when a location is known.
96
+ Use codes for automation and treat `message` as human-readable context.
97
+
98
+ ```ts
99
+ import { apiDocsToOpenApi, zodToJsonSchema, type ZopiaWarning } from 'zopia';
100
+
101
+ const observed: ZopiaWarning[] = [];
102
+ zodToJsonSchema(schema, { onWarning: (warning) => observed.push(warning) });
103
+
104
+ const result = await apiDocsToOpenApi('api_docs', {
105
+ version: '3.1',
106
+ onWarning: (warning) => observed.push(warning),
107
+ });
108
+
109
+ for (const warning of result.warnings) {
110
+ console.error(warning.code, warning.at, warning.message);
111
+ }
112
+ ```
113
+
114
+ Exact duplicates are removed and results/callbacks are sorted by location,
115
+ code, then message. Engine ② also writes canonical `// @zopia:warn …` comments
116
+ into emitted code. A manifest preserves restorable schema facts; a warning
117
+ still remains visible because the generated Zod expression itself is an
118
+ approximation.
119
+
120
+ ## ⌨️ CLI
121
+
122
+ > The CLI delegates generation to the same validated Engine ③ public API; its
123
+ > flags map to [Configuration](09-configuration.md#-cli--options-mapping).
124
+
125
+ ```text
126
+ zopia generate <spec.json|spec.yaml> [output-dir] [--mode directory|flat] [--preset multi-tag|multi-server]
127
+ [--insert-components] [--use-component-as-reference] [--custom] [--no-manifest]
128
+ [--watch] [--config path]
129
+ zopia reverse <docs-dir|manifest.json> [--out openapi.json]
130
+ [--version 2.0|3.0|3.1] [--config path]
131
+ zopia validate <spec.json|spec.yaml|docs-dir> [--config path]
132
+ zopia diff <old.json|old.yaml> <new.json|new.yaml> [--config path]
133
+ zopia navigate <docs-dir> (--to-code <spec-pointer> | --to-spec <tree-file>) [--config path]
134
+ ```
135
+
136
+ | 🚩 Command | 📝 What it does | 💡 Example |
137
+ | --- | --- | --- |
138
+ | `zopia generate` | generates the endpoint tree and manifest; `--watch` keeps it running and regenerates whenever the spec file changes (survives atomic editor saves; Ctrl+C stops) | `zopia generate swagger.json api_docs`, `zopia generate swagger.yaml api_docs --watch` |
139
+ | `zopia reverse` | imports the manifest's endpoint and emitted component modules, then writes the reconstructed OpenAPI document to stdout or `--out` | `zopia reverse api_docs/.zopia-manifest.json --out openapi.json` |
140
+ | `zopia validate` | lints a spec (broken refs, name collisions, cross-namespace duplicate operationIds, unreachable components) or checks a generated tree (manifest validity, reverse dry-run, km-api peer drift); prints sorted diagnostics and a summary line to stdout | `zopia validate openapi.yaml`, `zopia validate api_docs` |
141
+ | `zopia diff` | compares two specs semantically (dialect, info, endpoints with parameter/request-body/response details, webhooks, schema components and named registries (security schemes, reusable parameters/responses, request bodies…), path-item and webhook-item metadata, document fields, `x-` extensions) — key order is ignored and JSON/YAML inputs mix freely; prints `+`/`-`/`~` lines plus a summary to stdout; differences are data, so a changed pair still exits `0` | `zopia diff v1.json v2.yaml` |
142
+ | `zopia navigate` | manifest-driven jump table between a generated tree and its source spec (S-93): `--to-code '#/paths/~1pets/get'` prints the generated file(s) implementing the pointer (endpoints, webhooks, components, plus custom companions when enabled); `--to-spec pets/get/index.ts` prints the owning pointer — both directions print one stable line per location and exit non-zero with `ZOPIA_CONFIG_INVALID` for unmatched pointers/files or `ZOPIA_DOCS_MISSING_MANIFEST` for generation-less roots | `zopia navigate api_docs --to-spec pets/get/index.ts` |
143
+
144
+ ### 📏 CLI contract
145
+
146
+ | # | Contract |
147
+ | --- | --- |
148
+ | R-931 | Boolean flags are additive and default to `false` when absent; `--custom` enables the merge-safe companion layer ([07 → R-744](07-api-docs.md#-regeneration--manual-edits-phase-1-policy)); `--no-manifest` is the explicit inverse of the default-on manifest option; `--watch` (S-88) is generate-only and requires a spec **file path** — it watches the spec's parent directory so atomic editor saves (`write-temp` + rename) still trigger a regeneration, coalesces change bursts, prints per-run errors to stderr while continuing, and never writes a partial tree beyond the failing run's first output. With a [project config file](09-configuration.md#-zopiaconfigts--project-defaults-v02x-d-19), config values fill every option the flags leave unset, and every explicit flag still wins (D-19); the `<output-dir>` positional is required unless the config supplies `generate.outDir`. |
149
+ | R-932 | Parsing is strict and completes before either engine runs: options may surround positional arguments, but unknown, command-incompatible, repeated, or valueless options and missing/extra positionals fail with `ZOPIA_CONFIG_INVALID` at the offending argument. |
150
+ | R-933 | Data uses stdout (or the selected `--out` file); warnings and errors use stderr. Exit status is `0` success, `1` typed user/configuration failure, and `2` unexpected internal failure. |
151
+ | R-934 | `-h`/`--help` lists the complete grammar and warns that reverse conversion executes generated TypeScript from trusted trees. `--config <path>` selects an explicit config file on every command; loading, discovery, and validation rules live in [09-configuration](09-configuration.md#-zopiaconfigts--project-defaults-v02x-d-19). `zopia validate`, `zopia diff`, and `zopia navigate` have no configurable knobs yet — a named config file only needs to load. |
152
+
153
+ Both commands print warnings only to stderr as
154
+ `Warning: ZOPIA_WARN_* <pointer>: <message>`. In particular, `zopia reverse`
155
+ keeps stdout as valid OpenAPI JSON even when warnings are present; `--out`
156
+ writes only JSON to the selected file. `zopia validate` instead prints every
157
+ diagnostic to **stdout** (`Error|Warning: <CODE> <pointer>: <message>`) followed
158
+ by one summary line, keeping stderr for the typed failure tail; exit status is
159
+ `1` exactly when an error-severity diagnostic exists (S-89). The same coverage
160
+ is available programmatically as `validateZopia(input)` returning
161
+ `{ ok, kind, target, diagnostics }`.
162
+
163
+ > ⚠️ `zopia reverse` executes the TypeScript modules referenced by `apis[].file`
164
+ > and non-null `components[].file` entries. Reverse only trusted generated trees,
165
+ > and only commit config files you trust — `zopia.config.ts` is executed
166
+ > JavaScript under the same model. File paths are restricted to the manifest
167
+ > directory (including after symlink resolution), while edited runtime km-api
168
+ > metadata and endpoint/component Zod schemas take precedence over their
169
+ > manifest snapshots.
170
+
171
+ Exit codes: `0` success · `1` user error (bad input/options — message on
172
+ stderr, hint included) · `2` internal error (should never happen — report it).
173
+
174
+ ## 🧩 Working with the generated tree
175
+
176
+ | # | Practice | Rule |
177
+ | --- | --- | --- |
178
+ | R-101 | 📂 **Import, don't re-type** — your app imports the generated `index.ts` files; their Zod schemas *are* the validation | — |
179
+ | R-102 | 🔄 **Spec or generation options changed?** re-run `zopia generate` — output is idempotent (P-1); manifest staleness warns on source/config/incomplete-tree drift and safely prunes only obsolete manifest-owned files (`ZOPIA_WARN_STALE_TREE`) | [07 → Regeneration](07-api-docs.md#-regeneration--manual-edits-phase-1-policy) |
180
+ | R-103 | ✍️ **Hand edits** — Phase 1 overwrites them on regeneration (see the file banner); the merge-safe custom layer arrives in Phase 3 | [07 → Regeneration](07-api-docs.md#-regeneration--manual-edits-phase-1-policy) |
181
+ | R-104 | 🧪 **km-api helpers** — `makeFullPath`, `makeParams`, `convertResponseType`, … are available on every generated config for free | [Concepts → km-api](05-concepts.md#-km-api) |
182
+ | R-105 | 🚫 **No zopia import in app code** — generated files depend only on `zod` + `km-api` (R-502) | — |
183
+
184
+ ## 🧯 Error handling
185
+
186
+ ```ts
187
+ import { openApiToApiDocs, isZopiaError } from 'zopia';
188
+
189
+ try {
190
+ // Exact component references import through components/index.ts
191
+ await openApiToApiDocs('swagger.json', { insertComponents: true, useComponentAsReference: true });
192
+ } catch (e) {
193
+ if (isZopiaError(e)) {
194
+ console.error(e.code); // 🆔 'ZOPIA_CONFIG_INVALID'
195
+ console.error(e.hint); // 💡 'enable `insertComponents` first'
196
+ console.error(e.at); // 📍 JSON Pointer, option, or file, when discoverable
197
+ console.error(e.cause); // 🔗 original parser/import/filesystem failure, when wrapped
198
+ }
199
+ }
200
+ ```
201
+
202
+ The full error-code table lives in
203
+ [Architecture → Error model](04-architecture.md#-error-model).
204
+
205
+ ## 🔗 Next
206
+
207
+ - ⚙️ Every option → [Configuration](09-configuration.md)
208
+ - 🧪 How all of this is tested → [Testing](11-testing.md)