@contractkit/plugin-typescript 0.16.1
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 +35 -0
- package/.turbo/turbo-build.log +15 -0
- package/.turbo/turbo-test$colon$ci.log +81 -0
- package/.turbo/turbo-test.log +19 -0
- package/CHANGELOG.md +151 -0
- package/README.md +153 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +1882 -0
- package/coverage/coverage-final.json +9 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +131 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/coverage/src/codegen-contract.ts.html +3331 -0
- package/coverage/src/codegen-operation.ts.html +2530 -0
- package/coverage/src/codegen-plain-types.ts.html +901 -0
- package/coverage/src/codegen-sdk.ts.html +2797 -0
- package/coverage/src/index.html +206 -0
- package/coverage/src/index.ts.html +1360 -0
- package/coverage/src/path-utils.ts.html +649 -0
- package/coverage/src/ts-render.ts.html +592 -0
- package/coverage/tests/helpers.ts.html +826 -0
- package/coverage/tests/index.html +116 -0
- package/dist/codegen-contract.d.ts +56 -0
- package/dist/codegen-contract.d.ts.map +1 -0
- package/dist/codegen-operation.d.ts +25 -0
- package/dist/codegen-operation.d.ts.map +1 -0
- package/dist/codegen-plain-types.d.ts +10 -0
- package/dist/codegen-plain-types.d.ts.map +1 -0
- package/dist/codegen-sdk.d.ts +38 -0
- package/dist/codegen-sdk.d.ts.map +1 -0
- package/dist/index.d.ts +77 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3162 -0
- package/dist/index.js.map +1 -0
- package/dist/path-utils.d.ts +15 -0
- package/dist/path-utils.d.ts.map +1 -0
- package/dist/ts-render.d.ts +20 -0
- package/dist/ts-render.d.ts.map +1 -0
- package/eslint.config.js +6 -0
- package/package.json +43 -0
- package/src/codegen-contract.ts +1082 -0
- package/src/codegen-operation.ts +815 -0
- package/src/codegen-plain-types.ts +272 -0
- package/src/codegen-sdk.ts +904 -0
- package/src/index.ts +425 -0
- package/src/path-utils.ts +188 -0
- package/src/ts-render.ts +169 -0
- package/tests/codegen-contract.test.ts +1004 -0
- package/tests/codegen-operation.test.ts +939 -0
- package/tests/codegen-plain-types.test.ts +636 -0
- package/tests/codegen-sdk.test.ts +1500 -0
- package/tests/codegen-server.test.ts +192 -0
- package/tests/helpers.ts +247 -0
- package/tests/pipeline.test.ts +372 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +14 -0
package/src/ts-render.ts
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import type { ContractTypeNode, FieldNode } from '@contractkit/core';
|
|
2
|
+
|
|
3
|
+
export const JSON_VALUE_TYPE_DECL = 'export type JsonValue = string | number | boolean | null | JsonValue[] | { [key: string]: JsonValue };';
|
|
4
|
+
|
|
5
|
+
export function quoteKey(name: string): string {
|
|
6
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name) ? name : `'${name}'`;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** Convert an HTTP header name (e.g. `preference-applied`, `X-Request-ID`, `ETag`) to camelCase for use as a JS property. */
|
|
10
|
+
export function headerNameToProperty(name: string): string {
|
|
11
|
+
const parts = name.split(/[-_]/).filter(Boolean);
|
|
12
|
+
return parts
|
|
13
|
+
.map((p, i) => {
|
|
14
|
+
const lower = p.toLowerCase();
|
|
15
|
+
return i === 0 ? lower : lower.charAt(0).toUpperCase() + lower.slice(1);
|
|
16
|
+
})
|
|
17
|
+
.join('');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// ─── TypeScript type rendering ────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
export function renderTsType(type: ContractTypeNode): string {
|
|
23
|
+
switch (type.kind) {
|
|
24
|
+
case 'scalar':
|
|
25
|
+
return renderTsScalar(type.name);
|
|
26
|
+
case 'array': {
|
|
27
|
+
const inner = renderTsType(type.item);
|
|
28
|
+
const needsParens =
|
|
29
|
+
type.item.kind === 'union' ||
|
|
30
|
+
type.item.kind === 'discriminatedUnion' ||
|
|
31
|
+
type.item.kind === 'intersection' ||
|
|
32
|
+
type.item.kind === 'enum';
|
|
33
|
+
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
34
|
+
}
|
|
35
|
+
case 'tuple':
|
|
36
|
+
return `[${type.items.map(renderTsType).join(', ')}]`;
|
|
37
|
+
case 'record':
|
|
38
|
+
return `Record<${renderTsType(type.key)}, ${renderTsType(type.value)}>`;
|
|
39
|
+
case 'enum':
|
|
40
|
+
return type.values.map(v => `'${v}'`).join(' | ');
|
|
41
|
+
case 'literal':
|
|
42
|
+
return typeof type.value === 'string' ? `'${type.value}'` : String(type.value);
|
|
43
|
+
case 'union':
|
|
44
|
+
return type.members.map(renderTsType).join(' | ');
|
|
45
|
+
case 'discriminatedUnion':
|
|
46
|
+
return type.members.map(renderTsType).join(' | ');
|
|
47
|
+
case 'intersection':
|
|
48
|
+
return type.members.map(renderTsType).join(' & ');
|
|
49
|
+
case 'ref':
|
|
50
|
+
return type.name;
|
|
51
|
+
case 'lazy':
|
|
52
|
+
return renderTsType(type.inner);
|
|
53
|
+
case 'inlineObject':
|
|
54
|
+
return renderTsInlineObject(type.fields);
|
|
55
|
+
default:
|
|
56
|
+
return 'unknown';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function renderTsScalar(name: string): string {
|
|
61
|
+
switch (name) {
|
|
62
|
+
case 'string':
|
|
63
|
+
case 'email':
|
|
64
|
+
case 'url':
|
|
65
|
+
case 'uuid':
|
|
66
|
+
return 'string';
|
|
67
|
+
case 'number':
|
|
68
|
+
case 'int':
|
|
69
|
+
return 'number';
|
|
70
|
+
case 'bigint':
|
|
71
|
+
return 'bigint';
|
|
72
|
+
case 'boolean':
|
|
73
|
+
return 'boolean';
|
|
74
|
+
case 'date':
|
|
75
|
+
case 'datetime':
|
|
76
|
+
case 'duration':
|
|
77
|
+
case 'interval':
|
|
78
|
+
return 'string';
|
|
79
|
+
case 'null':
|
|
80
|
+
return 'null';
|
|
81
|
+
case 'unknown':
|
|
82
|
+
return 'unknown';
|
|
83
|
+
case 'object':
|
|
84
|
+
return 'Record<string, unknown>';
|
|
85
|
+
case 'binary':
|
|
86
|
+
return 'Blob';
|
|
87
|
+
case 'json':
|
|
88
|
+
return 'JsonValue';
|
|
89
|
+
default:
|
|
90
|
+
return 'unknown';
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function renderTsInlineObject(fields: FieldNode[]): string {
|
|
95
|
+
const entries = fields.map(f => {
|
|
96
|
+
const opt = f.optional ? '?' : '';
|
|
97
|
+
return `${quoteKey(f.name)}${opt}: ${renderTsType(f.type)}`;
|
|
98
|
+
});
|
|
99
|
+
return `{ ${entries.join('; ')} }`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Like renderTsType, but substitutes model refs with their Input variant
|
|
104
|
+
* when the model has visibility modifiers. Used for request-side types
|
|
105
|
+
* (body, params, query, headers).
|
|
106
|
+
*/
|
|
107
|
+
export function renderInputTsType(type: ContractTypeNode, modelsWithInput?: Set<string>): string {
|
|
108
|
+
if (!modelsWithInput || modelsWithInput.size === 0) return renderTsType(type);
|
|
109
|
+
switch (type.kind) {
|
|
110
|
+
case 'ref':
|
|
111
|
+
return modelsWithInput.has(type.name) ? `${type.name}Input` : type.name;
|
|
112
|
+
case 'array': {
|
|
113
|
+
const inner = renderInputTsType(type.item, modelsWithInput);
|
|
114
|
+
const needsParens =
|
|
115
|
+
type.item.kind === 'union' ||
|
|
116
|
+
type.item.kind === 'discriminatedUnion' ||
|
|
117
|
+
type.item.kind === 'intersection' ||
|
|
118
|
+
type.item.kind === 'enum';
|
|
119
|
+
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
120
|
+
}
|
|
121
|
+
case 'intersection':
|
|
122
|
+
return type.members.map(m => renderInputTsType(m, modelsWithInput)).join(' & ');
|
|
123
|
+
case 'union':
|
|
124
|
+
return type.members.map(m => renderInputTsType(m, modelsWithInput)).join(' | ');
|
|
125
|
+
case 'discriminatedUnion':
|
|
126
|
+
return type.members.map(m => renderInputTsType(m, modelsWithInput)).join(' | ');
|
|
127
|
+
case 'inlineObject':
|
|
128
|
+
return `{ ${type.fields.map(f => `${quoteKey(f.name)}${f.optional ? '?' : ''}: ${renderInputTsType(f.type, modelsWithInput)}`).join('; ')} }`;
|
|
129
|
+
case 'lazy':
|
|
130
|
+
return renderInputTsType(type.inner, modelsWithInput);
|
|
131
|
+
default:
|
|
132
|
+
return renderTsType(type);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Like renderTsType, but substitutes model refs with their Output variant
|
|
138
|
+
* (post-transform wire shape) when the model has format(output=...) or
|
|
139
|
+
* transitively references one. Used for response-side types in routers
|
|
140
|
+
* and SDK return types.
|
|
141
|
+
*/
|
|
142
|
+
export function renderOutputTsType(type: ContractTypeNode, modelsWithOutput?: Set<string>): string {
|
|
143
|
+
if (!modelsWithOutput || modelsWithOutput.size === 0) return renderTsType(type);
|
|
144
|
+
switch (type.kind) {
|
|
145
|
+
case 'ref':
|
|
146
|
+
return modelsWithOutput.has(type.name) ? `${type.name}Output` : type.name;
|
|
147
|
+
case 'array': {
|
|
148
|
+
const inner = renderOutputTsType(type.item, modelsWithOutput);
|
|
149
|
+
const needsParens =
|
|
150
|
+
type.item.kind === 'union' ||
|
|
151
|
+
type.item.kind === 'discriminatedUnion' ||
|
|
152
|
+
type.item.kind === 'intersection' ||
|
|
153
|
+
type.item.kind === 'enum';
|
|
154
|
+
return needsParens ? `(${inner})[]` : `${inner}[]`;
|
|
155
|
+
}
|
|
156
|
+
case 'intersection':
|
|
157
|
+
return type.members.map(m => renderOutputTsType(m, modelsWithOutput)).join(' & ');
|
|
158
|
+
case 'union':
|
|
159
|
+
return type.members.map(m => renderOutputTsType(m, modelsWithOutput)).join(' | ');
|
|
160
|
+
case 'discriminatedUnion':
|
|
161
|
+
return type.members.map(m => renderOutputTsType(m, modelsWithOutput)).join(' | ');
|
|
162
|
+
case 'inlineObject':
|
|
163
|
+
return `{ ${type.fields.map(f => `${quoteKey(f.name)}${f.optional ? '?' : ''}: ${renderOutputTsType(f.type, modelsWithOutput)}`).join('; ')} }`;
|
|
164
|
+
case 'lazy':
|
|
165
|
+
return renderOutputTsType(type.inner, modelsWithOutput);
|
|
166
|
+
default:
|
|
167
|
+
return renderTsType(type);
|
|
168
|
+
}
|
|
169
|
+
}
|