@contractkit/openapi-to-ck 0.10.2 → 0.12.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.
- package/.turbo/turbo-build$colon$ci.log +7 -7
- package/.turbo/turbo-test$colon$ci.log +24 -20
- package/CHANGELOG.md +172 -0
- package/README.md +30 -8
- package/dist/ast-to-ck.d.ts +32 -16
- package/dist/ast-to-ck.d.ts.map +1 -1
- package/dist/{chunk-JPI3AQ7V.js → chunk-U7V7LFBV.js} +219 -391
- package/dist/chunk-U7V7LFBV.js.map +1 -0
- package/dist/convert.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/normalize.d.ts +6 -2
- package/dist/normalize.d.ts.map +1 -1
- package/dist/paths-to-ast.d.ts +2 -0
- package/dist/paths-to-ast.d.ts.map +1 -1
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +18 -3
- package/dist/plugin.js.map +1 -1
- package/dist/schema-to-ast.d.ts +13 -1
- package/dist/schema-to-ast.d.ts.map +1 -1
- package/dist/tag-splitter.d.ts.map +1 -1
- package/dist/types.d.ts +36 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -5
- package/src/ast-to-ck.ts +29 -453
- package/src/convert.ts +57 -3
- package/src/normalize.ts +87 -11
- package/src/paths-to-ast.ts +92 -11
- package/src/plugin.ts +17 -2
- package/src/schema-to-ast.ts +71 -7
- package/src/tag-splitter.ts +21 -16
- package/src/types.ts +36 -0
- package/tests/__snapshots__/kitchen-sink.ck +102 -0
- package/tests/ast-to-ck.test.ts +34 -17
- package/tests/component-refs.test.ts +114 -0
- package/tests/coverage.test.ts +246 -0
- package/tests/error-responses.test.ts +94 -0
- package/tests/fixtures/kitchen-sink-3.1.json +100 -0
- package/tests/helpers.ts +40 -0
- package/tests/kitchen-sink.test.ts +116 -0
- package/tests/schema-to-ast.test.ts +11 -2
- package/dist/chunk-JPI3AQ7V.js.map +0 -1
|
@@ -6,13 +6,13 @@ $ tsup src/index.ts src/plugin.ts --format esm --sourcemap --dts && tsc --emitDe
|
|
|
6
6
|
[34mCLI[39m Target: esnext
|
|
7
7
|
[34mESM[39m Build start
|
|
8
8
|
[32mESM[39m [1mdist/index.js [22m[32m498.00 B[39m
|
|
9
|
-
[32mESM[39m [1mdist/plugin.js [22m[
|
|
10
|
-
[32mESM[39m [1mdist/chunk-
|
|
9
|
+
[32mESM[39m [1mdist/plugin.js [22m[32m3.28 KB[39m
|
|
10
|
+
[32mESM[39m [1mdist/chunk-U7V7LFBV.js [22m[32m45.52 KB[39m
|
|
11
11
|
[32mESM[39m [1mdist/index.js.map [22m[32m71.00 B[39m
|
|
12
|
-
[32mESM[39m [1mdist/
|
|
13
|
-
[32mESM[39m [1mdist/
|
|
14
|
-
[32mESM[39m ⚡️ Build success in
|
|
12
|
+
[32mESM[39m [1mdist/plugin.js.map [22m[32m6.11 KB[39m
|
|
13
|
+
[32mESM[39m [1mdist/chunk-U7V7LFBV.js.map [22m[32m113.55 KB[39m
|
|
14
|
+
[32mESM[39m ⚡️ Build success in 214ms
|
|
15
15
|
[34mDTS[39m Build start
|
|
16
|
-
[32mDTS[39m ⚡️ Build success in
|
|
17
|
-
[32mDTS[39m [1mdist/index.d.ts [22m[
|
|
16
|
+
[32mDTS[39m ⚡️ Build success in 4902ms
|
|
17
|
+
[32mDTS[39m [1mdist/index.d.ts [22m[32m12.87 KB[39m
|
|
18
18
|
[32mDTS[39m [1mdist/plugin.d.ts [22m[32m128.00 B[39m
|
|
@@ -3,32 +3,36 @@ $ vitest run --coverage
|
|
|
3
3
|
[1m[30m[46m RUN [49m[39m[22m [36mv4.1.5 [39m[90m/home/runner/work/ContractKit/ContractKit/packages/openapi-to-ck[39m
|
|
4
4
|
[2mCoverage enabled with [22m[33mv8[39m
|
|
5
5
|
|
|
6
|
-
[32m✓[39m tests/schema-to-ast.test.ts [2m([22m[
|
|
7
|
-
[32m✓[39m tests/
|
|
8
|
-
[32m✓[39m tests/
|
|
9
|
-
[32m✓[39m tests/
|
|
10
|
-
[32m✓[39m tests/
|
|
11
|
-
[32m✓[39m tests/
|
|
6
|
+
[32m✓[39m tests/schema-to-ast.test.ts [2m([22m[2m37 tests[22m[2m)[22m[32m 89[2mms[22m[39m
|
|
7
|
+
[32m✓[39m tests/ast-to-ck.test.ts [2m([22m[2m45 tests[22m[2m)[22m[32m 135[2mms[22m[39m
|
|
8
|
+
[32m✓[39m tests/convert.test.ts [2m([22m[2m17 tests[22m[2m)[22m[33m 1127[2mms[22m[39m
|
|
9
|
+
[32m✓[39m tests/coverage.test.ts [2m([22m[2m17 tests[22m[2m)[22m[32m 258[2mms[22m[39m
|
|
10
|
+
[32m✓[39m tests/normalize.test.ts [2m([22m[2m6 tests[22m[2m)[22m[32m 24[2mms[22m[39m
|
|
11
|
+
[32m✓[39m tests/kitchen-sink.test.ts [2m([22m[2m7 tests[22m[2m)[22m[33m 802[2mms[22m[39m
|
|
12
|
+
[32m✓[39m tests/component-refs.test.ts [2m([22m[2m4 tests[22m[2m)[22m[32m 155[2mms[22m[39m
|
|
13
|
+
[32m✓[39m tests/circular-refs.test.ts [2m([22m[2m8 tests[22m[2m)[22m[32m 7[2mms[22m[39m
|
|
14
|
+
[32m✓[39m tests/error-responses.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 156[2mms[22m[39m
|
|
15
|
+
[32m✓[39m tests/tag-splitter.test.ts [2m([22m[2m5 tests[22m[2m)[22m[32m 11[2mms[22m[39m
|
|
12
16
|
|
|
13
|
-
[2m Test Files [22m [1m[
|
|
14
|
-
[2m Tests [22m [1m[
|
|
15
|
-
[2m Start at [22m
|
|
16
|
-
[2m Duration [22m
|
|
17
|
+
[2m Test Files [22m [1m[32m10 passed[39m[22m[90m (10)[39m
|
|
18
|
+
[2m Tests [22m [1m[32m151 passed[39m[22m[90m (151)[39m
|
|
19
|
+
[2m Start at [22m 17:30:03
|
|
20
|
+
[2m Duration [22m 7.34s[2m (transform 1.30s, setup 0ms, import 11.71s, tests 2.76s, environment 1ms)[22m
|
|
17
21
|
|
|
18
22
|
[34m % [39m[2mCoverage report from [22m[33mv8[39m
|
|
19
23
|
-------------------|---------|----------|---------|---------|-------------------------------------------------------------
|
|
20
24
|
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
|
|
21
25
|
-------------------|---------|----------|---------|---------|-------------------------------------------------------------
|
|
22
|
-
All files |
|
|
23
|
-
src | 81.
|
|
24
|
-
ast-to-ck.ts |
|
|
26
|
+
All files | 81.76 | 72.21 | 94.78 | 84.86 |
|
|
27
|
+
src | 81.73 | 72.63 | 95.45 | 84.46 |
|
|
28
|
+
ast-to-ck.ts | 100 | 100 | 100 | 100 |
|
|
25
29
|
circular-refs.ts | 100 | 90 | 100 | 100 | 20-23
|
|
26
|
-
convert.ts |
|
|
27
|
-
normalize.ts |
|
|
28
|
-
paths-to-ast.ts |
|
|
29
|
-
schema-to-ast.ts |
|
|
30
|
-
tag-splitter.ts |
|
|
30
|
+
convert.ts | 79.01 | 78.78 | 88.88 | 81.08 | 123-124,137-152,166-171
|
|
31
|
+
normalize.ts | 67.52 | 61.2 | 85.71 | 67.59 | 67-68,80,155,197-239,313-331,346-347,356-385,422,426,434
|
|
32
|
+
paths-to-ast.ts | 90.68 | 77.23 | 100 | 96.45 | 130,158,168,246,260
|
|
33
|
+
schema-to-ast.ts | 83.41 | 75.8 | 100 | 87.64 | 151-152,174,182,196,219-220,228-229,246-250,286-289,395-402
|
|
34
|
+
tag-splitter.ts | 85.71 | 78.94 | 90.9 | 89.02 | 71,153-154,167-168,179-183
|
|
31
35
|
warnings.ts | 100 | 100 | 100 | 100 |
|
|
32
|
-
tests |
|
|
33
|
-
helpers.ts |
|
|
36
|
+
tests | 82.14 | 64.7 | 92.59 | 91.11 |
|
|
37
|
+
helpers.ts | 82.14 | 64.7 | 92.59 | 91.11 | 142-143,179,184
|
|
34
38
|
-------------------|---------|----------|---------|---------|-------------------------------------------------------------
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,177 @@
|
|
|
1
1
|
# @contractkit/openapi-to-ck
|
|
2
2
|
|
|
3
|
+
## 0.12.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e102a2c: Add a `decimal` scalar, for money and anything else that has to be exact
|
|
8
|
+
|
|
9
|
+
`number` compiles to `z.coerce.number()` — an IEEE-754 double. Anything monetary has to be exact,
|
|
10
|
+
so contracts either lied about their types or routed the value through `string` by hand. `decimal`
|
|
11
|
+
gives the language a type for it:
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
contract Payslip: {
|
|
15
|
+
gross: decimal(min=0, scale=2)
|
|
16
|
+
rate: decimal
|
|
17
|
+
}
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
A decimal travels as a **quoted JSON string** (`{"gross": "1250.00"}`) and becomes a decimal.js
|
|
21
|
+
`Decimal` — the same class Prisma hands you for a `Decimal` column, so a value moves between the
|
|
22
|
+
two with no conversion. Python gets `decimal.Decimal`; OpenAPI gets `type: string, format: decimal`.
|
|
23
|
+
|
|
24
|
+
A raw JSON number is **rejected**, not coerced. By the time one reaches the schema it has already
|
|
25
|
+
been through a double, which is precisely the loss the scalar exists to prevent, so accepting it
|
|
26
|
+
would defeat the point silently.
|
|
27
|
+
|
|
28
|
+
**`scale=` is a validation constraint — at most N decimal places — not a formatting directive.**
|
|
29
|
+
It cannot be one: the router assigns `ctx.body` and Koa serializes it with a `JSON.stringify` we
|
|
30
|
+
have no replacer for, so the wire form is whatever decimal.js normalizes to and `"1250.00"` reads
|
|
31
|
+
back as `"1250"`. The two are the same number; format at the display edge if you need the trailing
|
|
32
|
+
zeros. This is also what `scale` means in OpenAPI `pattern`, pydantic `condecimal(decimal_places=)`
|
|
33
|
+
and Prisma `@db.Decimal(_, n)`, so every downstream mapping stays honest.
|
|
34
|
+
|
|
35
|
+
Generated code sets `Decimal.set({ toExpNeg: -9e15, toExpPos: 9e15 })` so values never serialize in
|
|
36
|
+
exponential notation — without it `0.00000001` ships as `"1e-8"` and any peer validating
|
|
37
|
+
`^-?\d+(\.\d+)?$` rejects it. Note this is global decimal.js configuration and affects every
|
|
38
|
+
`Decimal` in the consuming process.
|
|
39
|
+
|
|
40
|
+
SDK clients rehydrate decimals through generated `reviveX` functions. The `bigint` approach does
|
|
41
|
+
not transfer — it works only because bigint invented a tagged `"123n"` wire encoding, and tagging a
|
|
42
|
+
decimal would corrupt the format for every non-ContractKit consumer. Re-parsing responses through
|
|
43
|
+
the Zod schema is not available either: `XOutput` is a type alias with no runtime value, and models
|
|
44
|
+
default to `z.strictObject`, so any field the server added would throw in every deployed client.
|
|
45
|
+
The revivers mutate in place, which preserves unknown server-added keys.
|
|
46
|
+
|
|
47
|
+
Two placements are rejected at parse time, both errors rather than warnings since no existing
|
|
48
|
+
contract can be relying on them. A decimal inside an **undiscriminated union** cannot be rehydrated
|
|
49
|
+
— the SDK has no way to tell which arm arrived, and a convert-if-string fallback would silently
|
|
50
|
+
rewrite a genuine `string` field in a sibling arm. A decimal in a **response header** has no
|
|
51
|
+
parsing step at all, so the annotation would simply be false at runtime.
|
|
52
|
+
|
|
53
|
+
`min`/`max` are kept as exact decimal strings rather than coerced through `Number()`, and OpenAPI
|
|
54
|
+
carries them in `x-contractkit-min`/`-max` extensions, since JSON Schema's numeric `minimum` and
|
|
55
|
+
`maximum` are ignored on a string type. A contract round-trips through OpenAPI and back with its
|
|
56
|
+
bounds intact.
|
|
57
|
+
|
|
58
|
+
SDKs that scaffold a `package.json` gain `decimal.js` as a dependency when a covered model uses the
|
|
59
|
+
scalar. Existing scaffolds are write-once and are not updated, so add it by hand there.
|
|
60
|
+
|
|
61
|
+
### Patch Changes
|
|
62
|
+
|
|
63
|
+
- Updated dependencies [e102a2c]
|
|
64
|
+
- @contractkit/core@0.28.0
|
|
65
|
+
|
|
66
|
+
## 0.11.0
|
|
67
|
+
|
|
68
|
+
### Minor Changes
|
|
69
|
+
|
|
70
|
+
- 7b3b270: Import 4xx/5xx responses that declare a body as `(documented)`
|
|
71
|
+
|
|
72
|
+
**This changes the output of re-running the importer.** OpenAPI cannot say whether a handler
|
|
73
|
+
_returns_ a status or merely documents it, but `.ck` distinguishes the two and every generator
|
|
74
|
+
depends on the answer. Every declared status used to be imported as service-produced: a spec
|
|
75
|
+
declaring `404: {application/json: Error}` became `404: { … }`, which made the generated Koa
|
|
76
|
+
handler responsible for returning the 404 and made the TypeScript and Python SDKs hand it back
|
|
77
|
+
as a value rather than throwing.
|
|
78
|
+
|
|
79
|
+
A bodied 4xx or 5xx now imports as `404(documented): { … }` — the body is the error contract, the
|
|
80
|
+
SDK throws it as an `SdkError`, and the service is not responsible for producing it. 2xx and 3xx
|
|
81
|
+
are unchanged, as is a bare bodyless error status (marking those would be redundant, and core
|
|
82
|
+
warns about it).
|
|
83
|
+
|
|
84
|
+
Pass `errorResponses: 'emitted'`, or `--error-responses emitted` on the command line, to restore
|
|
85
|
+
the previous behaviour.
|
|
86
|
+
|
|
87
|
+
Also in this release:
|
|
88
|
+
- `$ref`s to `#/components/parameters`, `requestBodies`, `responses` and `headers` are now
|
|
89
|
+
resolved. A `$ref`'d parameter previously reached the printer with no name and emitted
|
|
90
|
+
`undefined: string` — which parses, so nothing reported it. Anything still unresolvable is
|
|
91
|
+
warned about and skipped instead of emitted.
|
|
92
|
+
- Every generated file is re-parsed before it is returned, and a file that does not parse is
|
|
93
|
+
reported as a warning rather than written out silently.
|
|
94
|
+
- The command line gained `--no-comments`, which was documented but never implemented.
|
|
95
|
+
|
|
96
|
+
Coverage, in the same release:
|
|
97
|
+
- `name:` is imported from an operation's `summary`, which used to be dropped entirely.
|
|
98
|
+
- Request bodies keep any RFC 6838 `type/subtype` content type. The importer previously allowed
|
|
99
|
+
only JSON, form-urlencoded and multipart, silently discarding everything else, even though the
|
|
100
|
+
grammar has accepted any mime since vendor MIME support landed.
|
|
101
|
+
- `format: duration` maps to the `duration` scalar, and the `idn-email`, `uri-reference`, `iri`
|
|
102
|
+
and `iri-reference` formats map alongside their existing counterparts.
|
|
103
|
+
- `additionalProperties: true` imports as `mode(loose)`.
|
|
104
|
+
- A spec-level `security` requirement now applies to operations that do not override it; it was
|
|
105
|
+
collected and never read, so a globally unsecured spec imported as secured.
|
|
106
|
+
- Constructs with no `.ck` equivalent are warned about rather than dropped in silence: `head`,
|
|
107
|
+
`options` and `trace` operations, non-numeric response keys (`default`, `4XX`), cookie
|
|
108
|
+
parameters, unparameterised mime types, and the `exclusiveMinimum`, `exclusiveMaximum`,
|
|
109
|
+
`multipleOf` and `uniqueItems` constraints. A `4XX` response key previously became status `4`,
|
|
110
|
+
because `parseInt` stops at the first non-digit.
|
|
111
|
+
|
|
112
|
+
### Patch Changes
|
|
113
|
+
|
|
114
|
+
- fd62377: Fix tag splitting, schema-name sanitization, and stale docs
|
|
115
|
+
- A model reached only from a `params`, `query` or `headers` block was filed under `shared.ck`
|
|
116
|
+
instead of its own tag's file. `collectParamSourceRefs` was written against the shape
|
|
117
|
+
`ParamSource` had before it became a tagged union, so only the `ref` case still worked — and
|
|
118
|
+
only by coincidence, since it happens to look like a model reference.
|
|
119
|
+
- A schema whose name starts with a digit (`3DModel`) produced an identifier the parser rejects.
|
|
120
|
+
It is now prefixed with `_`.
|
|
121
|
+
- `@scalar/openapi-parser` has been removed from the dependencies. It was never imported; the
|
|
122
|
+
normalization is hand-written, despite a comment claiming otherwise.
|
|
123
|
+
- The README documented an `openapi-to-ck --input …` command that does not exist. The command is
|
|
124
|
+
`import-openapi <spec-path>`, and the docs now cover `--no-comments`, `--error-responses`, and
|
|
125
|
+
what the converter warns about rather than dropping silently.
|
|
126
|
+
|
|
127
|
+
- 841af6e: Only wrap a circular reference in `lazy()` where it breaks a real cycle
|
|
128
|
+
|
|
129
|
+
`lazy()` exists so that a reference between two contracts that depend on each other can be
|
|
130
|
+
deferred: `topoSortModels` emits dependencies before dependents and can only fall back to source
|
|
131
|
+
order for a cycle. A reference from an operation — a response body, request body, parameter, or
|
|
132
|
+
response header — names a model the generated module has already imported and fully evaluated,
|
|
133
|
+
so there is no cycle to break.
|
|
134
|
+
|
|
135
|
+
Every reference to a self-referential schema used to be wrapped, so importing a spec with a tree-
|
|
136
|
+
shaped model produced `application/json: lazy(Widget)` on every body mentioning it. References
|
|
137
|
+
inside a contract, including one extracted from an inline body schema, still wrap as before.
|
|
138
|
+
|
|
139
|
+
Also fixed: a model extracted from an inline request or response body schema was referenced by
|
|
140
|
+
the generated operation and never emitted, because the extracted-model list was read before path
|
|
141
|
+
conversion filled it. Any spec with an inline (non-`$ref`) body schema produced a contract
|
|
142
|
+
pointing at something that did not exist. The post-conversion self-check now runs reference
|
|
143
|
+
validation as well as parsing, which is what caught it — a reference to an undefined contract is
|
|
144
|
+
perfectly good syntax, so parsing alone could not.
|
|
145
|
+
|
|
146
|
+
- aea5e21: Move the `.ck` printer into core, so the language has exactly one
|
|
147
|
+
|
|
148
|
+
`.ck` had two printers: the prettier plugin's `printCk` and a hand-rolled one inside
|
|
149
|
+
`openapi-to-ck`. Only the prettier copy was covered by the round-trip tests that the grammar
|
|
150
|
+
checklist points at, so the other silently fell behind the grammar — it ignored `hasBlock` and
|
|
151
|
+
the `(documented)` response modifier, could not emit `mcp:`, `plugins:`, `name:`, `override`,
|
|
152
|
+
`format(output=)` or options-level header globals, and emitted source that does not parse for a
|
|
153
|
+
regex containing `/` or an enum value carrying both quote styles.
|
|
154
|
+
|
|
155
|
+
`printCk` now lives in `@contractkit/core` next to `parseCk` and is exported from it. The
|
|
156
|
+
prettier plugin re-exports it unchanged, and `openapi-to-ck`'s `astToCk` is a thin adapter over
|
|
157
|
+
it, so all of the above now print correctly.
|
|
158
|
+
|
|
159
|
+
Three printing fixes come with the move, all of which affect `pnpm format` on existing files:
|
|
160
|
+
- A regex containing `/` prints as `regex="…"` instead of an unterminated regex literal.
|
|
161
|
+
- A string containing `"` prints single-quoted; one carrying both quote styles is degraded
|
|
162
|
+
rather than emitted unparseable (use the new `isUnquotable` to warn before printing).
|
|
163
|
+
- A description containing newlines is flattened when it prints as a trailing `# …` comment,
|
|
164
|
+
instead of leaking the remainder as raw source.
|
|
165
|
+
|
|
166
|
+
Files containing any of these currently cannot round-trip at all, so this is a fix rather than a
|
|
167
|
+
break. `openapi-to-ck` output changes shape in two ways: model descriptions now print as a
|
|
168
|
+
doc-comment block above the `contract` rather than as a trailing comment, and scalar constraints
|
|
169
|
+
use the canonical `len=` / positional `format` spellings.
|
|
170
|
+
|
|
171
|
+
- Updated dependencies [aea5e21]
|
|
172
|
+
- Updated dependencies [5dc2693]
|
|
173
|
+
- @contractkit/core@0.27.0
|
|
174
|
+
|
|
3
175
|
## 0.10.2
|
|
4
176
|
|
|
5
177
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -10,18 +10,19 @@ pnpm add -D @contractkit/openapi-to-ck
|
|
|
10
10
|
|
|
11
11
|
## Use as a CLI subcommand
|
|
12
12
|
|
|
13
|
-
When the package is installed, the `@contractkit/cli` binary picks up an `openapi
|
|
13
|
+
When the package is installed, the `@contractkit/cli` binary picks up an `import-openapi` subcommand. The spec path is a positional argument:
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
contractkit openapi
|
|
16
|
+
contractkit import-openapi openapi.yaml --output contracts/
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
| Flag | Description |
|
|
20
20
|
| --- | --- |
|
|
21
|
-
|
|
|
22
|
-
|
|
|
21
|
+
| `<spec-path>` | Positional. Path to an OpenAPI YAML or JSON file. |
|
|
22
|
+
| `-o, --output <dir>` | Directory to write `.ck` files into. Defaults to the current directory. |
|
|
23
23
|
| `--split <single \| by-tag>` | Output mode. `by-tag` (default) writes one file per OpenAPI tag; `single` writes one combined file. |
|
|
24
24
|
| `--no-comments` | Don't emit OpenAPI descriptions as `#` comments. |
|
|
25
|
+
| `--error-responses <documented \| emitted>` | How to import a 4xx/5xx that declares a body. See [Error responses](#error-responses). |
|
|
25
26
|
|
|
26
27
|
## Programmatic use
|
|
27
28
|
|
|
@@ -32,6 +33,7 @@ const { files, warnings } = await convertOpenApiToCk({
|
|
|
32
33
|
input: 'openapi.yaml', // file path, JSON/YAML string, or pre-parsed object
|
|
33
34
|
split: 'by-tag', // 'single' | 'by-tag' (default: 'by-tag')
|
|
34
35
|
includeComments: true, // emit OpenAPI descriptions as # comments (default: true)
|
|
36
|
+
errorResponses: 'documented', // 'documented' | 'emitted' (default: 'documented')
|
|
35
37
|
onWarning: w => console.warn(w),
|
|
36
38
|
});
|
|
37
39
|
|
|
@@ -41,7 +43,17 @@ for (const [filename, source] of files) {
|
|
|
41
43
|
}
|
|
42
44
|
```
|
|
43
45
|
|
|
44
|
-
`Warning` entries carry a JSON-pointer-style `path` into the OpenAPI spec, a human-readable `message`, and a `severity` of `'info' | 'warn'`.
|
|
46
|
+
`Warning` entries carry a JSON-pointer-style `path` into the OpenAPI spec, a human-readable `message`, and a `severity` of `'info' | 'warn'`. Anything the spec declares that `.ck` cannot express is reported this way rather than dropped in silence — `head`/`options`/`trace` operations, non-numeric response keys such as `default` or `4XX`, cookie parameters, content types that are not a plain `type/subtype`, and the `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` and `uniqueItems` constraints. Every generated file is also re-parsed before it is returned, so output that would not compile arrives as a warning instead of as a broken file on disk.
|
|
47
|
+
|
|
48
|
+
## Error responses
|
|
49
|
+
|
|
50
|
+
OpenAPI cannot say whether a service *produces* a status or merely documents it. `.ck` draws that distinction, and it decides what the generated router writes and whether the SDKs return a status or throw it.
|
|
51
|
+
|
|
52
|
+
By default a 4xx or 5xx that declares a body is imported as `404(documented): { ... }`: the body is the error contract, the SDKs throw it as an `SdkError`, and the service is not responsible for returning it. 2xx and 3xx are imported as service-produced, as is a bare bodyless error status — marking those would be redundant.
|
|
53
|
+
|
|
54
|
+
Pass `errorResponses: 'emitted'` to import every declared status as service-produced instead.
|
|
55
|
+
|
|
56
|
+
A spec generated by `@contractkit/plugin-openapi` carries `x-contractkit-emit` on responses that were marked `(documented)`, and that is honoured ahead of the status-code default — so a `.ck` → OpenAPI → `.ck` round trip preserves the distinction exactly, including on a 2xx.
|
|
45
57
|
|
|
46
58
|
## What's converted
|
|
47
59
|
|
|
@@ -53,7 +65,13 @@ for (const [filename, source] of files) {
|
|
|
53
65
|
| `requestBody.content[mime].schema` | `request: { mime: Type }` |
|
|
54
66
|
| `responses[code].content[mime].schema` | `response: { code: { mime: Type } }` |
|
|
55
67
|
| `responses[code].headers` | per-status `headers: { name: type }` |
|
|
56
|
-
| `
|
|
68
|
+
| `responses[code]` for a bodied 4xx/5xx | `code(documented): { ... }` (see [Error responses](#error-responses)) |
|
|
69
|
+
| `summary` | `name:` |
|
|
70
|
+
| `operationId` | `sdk:` |
|
|
71
|
+
| `deprecated` | `get(deprecated)` |
|
|
72
|
+
| `security: []`, at spec or operation level | `security: none` |
|
|
73
|
+
| `additionalProperties: true` | `mode(loose)` |
|
|
74
|
+
| `allOf` | `&` inheritance on `contract` declarations |
|
|
57
75
|
| `oneOf` + `discriminator` | `discriminated(by=field, A \| B)` |
|
|
58
76
|
| `oneOf` / `anyOf` (no discriminator) | `\|` union |
|
|
59
77
|
| `enum`, `pattern`, `minimum/maximum`, `minLength/maxLength` | type constraint args |
|
|
@@ -71,8 +89,12 @@ import {
|
|
|
71
89
|
detectCircularRefs, // find $ref cycles
|
|
72
90
|
extractRefName, // "#/components/schemas/Foo" → "Foo"
|
|
73
91
|
sanitizeName, // turn an arbitrary string into a valid CK identifier
|
|
74
|
-
normalize, // upgrade 2.0/3.0 → 3.1 +
|
|
75
|
-
astToCk, // ContractKit AST → `.ck` source string
|
|
92
|
+
normalize, // upgrade 2.0/3.0 → 3.1 + inline non-schema component $refs
|
|
93
|
+
astToCk, // ContractKit AST → `.ck` source string (delegates to core's printCk)
|
|
76
94
|
serializeType, // ContractTypeNode → `.ck` type expression
|
|
77
95
|
} from '@contractkit/openapi-to-ck';
|
|
78
96
|
```
|
|
97
|
+
|
|
98
|
+
`astToCk` and `serializeType` are thin adapters over `printCk` and `printType` from
|
|
99
|
+
`@contractkit/core`. `.ck` has one printer, shared with the prettier plugin, so a grammar change
|
|
100
|
+
lands in one place; call the core functions directly if you have no reason to go through here.
|
package/dist/ast-to-ck.d.ts
CHANGED
|
@@ -1,22 +1,38 @@
|
|
|
1
|
-
import type { CkRootNode
|
|
2
|
-
|
|
1
|
+
import type { CkRootNode } from '@contractkit/core';
|
|
2
|
+
import { printType } from '@contractkit/core';
|
|
3
|
+
/**
|
|
4
|
+
* `.ck` serialization for the OpenAPI importer.
|
|
5
|
+
*
|
|
6
|
+
* This module used to carry its own printer. `.ck` had two of them — this one and the prettier
|
|
7
|
+
* plugin's — and only the prettier copy was covered by the round-trip tests that the
|
|
8
|
+
* `ck-grammar-change` checklist points at, so this one silently fell behind the grammar: it
|
|
9
|
+
* ignored `hasBlock` and the `(documented)` response modifier, could not emit `mcp:`,
|
|
10
|
+
* `plugins:`, `name:`, `override`, `format(output=)` or options-level header globals, and
|
|
11
|
+
* emitted unparseable source for a regex containing `/` or an enum value containing both quote
|
|
12
|
+
* styles.
|
|
13
|
+
*
|
|
14
|
+
* The printer now lives in `@contractkit/core` next to `parseCk`, and this module is a thin
|
|
15
|
+
* adapter over it. A grammar change has one printer to update.
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Options controlling how a {@link CkRootNode} is rendered to `.ck` source.
|
|
19
|
+
*
|
|
20
|
+
* @deprecated `includeComments` is a no-op and is kept only so existing callers still compile.
|
|
21
|
+
* Comments are controlled upstream: `ConvertOptions.includeComments` gates every `description`
|
|
22
|
+
* assignment in `schema-to-ast.ts` and `paths-to-ast.ts`, so when it is off the descriptions are
|
|
23
|
+
* absent from the AST and there is nothing left for the printer to suppress.
|
|
24
|
+
*/
|
|
3
25
|
export interface SerializeOptions {
|
|
4
|
-
/**
|
|
26
|
+
/** No-op. See the deprecation note on {@link SerializeOptions}. */
|
|
5
27
|
includeComments?: boolean;
|
|
6
28
|
}
|
|
7
29
|
/**
|
|
8
|
-
* Serialize a `.ck` AST back to formatted `.ck` source text.
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* to re-parse cleanly via `parseCk` (see round-trip tests).
|
|
13
|
-
*/
|
|
14
|
-
export declare function astToCk(root: CkRootNode, options?: SerializeOptions): string;
|
|
15
|
-
/**
|
|
16
|
-
* Serialize a single {@link ContractTypeNode} to its inline `.ck` type
|
|
17
|
-
* expression (e.g. `array(User, min=1)`, `string | int`, `enum(asc, desc)`).
|
|
18
|
-
* Recurses through composite types; enum values are quoted as needed via
|
|
19
|
-
* {@link quoteEnumValue}.
|
|
30
|
+
* Serialize a `.ck` AST back to formatted `.ck` source text.
|
|
31
|
+
*
|
|
32
|
+
* Delegates to `printCk`, which prints from a `CkRootNode` alone — no Ohm CST and no original
|
|
33
|
+
* source — so programmatically built nodes print correctly.
|
|
20
34
|
*/
|
|
21
|
-
export declare function
|
|
35
|
+
export declare function astToCk(root: CkRootNode, _options?: SerializeOptions): string;
|
|
36
|
+
/** Render a `ContractTypeNode` to its `.ck` source string. Re-exported from core. */
|
|
37
|
+
export declare const serializeType: typeof printType;
|
|
22
38
|
//# sourceMappingURL=ast-to-ck.d.ts.map
|
package/dist/ast-to-ck.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ast-to-ck.d.ts","sourceRoot":"","sources":["../src/ast-to-ck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"ast-to-ck.d.ts","sourceRoot":"","sources":["../src/ast-to-ck.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAW,SAAS,EAAE,MAAM,mBAAmB,CAAC;AAEvD;;;;;;;;;;;;;GAaG;AAEH;;;;;;;GAOG;AACH,MAAM,WAAW,gBAAgB;IAC7B,mEAAmE;IACnE,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED;;;;;GAKG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,GAAE,gBAAqB,GAAG,MAAM,CAEjF;AAED,qFAAqF;AACrF,eAAO,MAAM,aAAa,kBAAY,CAAC"}
|