@huanglangjian/specs 0.4.0 → 0.6.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/dist/index.d.mts +1729 -482
- package/dist/index.mjs +1644 -836
- package/dist/index.mjs.map +1 -1
- package/package.json +15 -11
package/dist/index.mjs
CHANGED
|
@@ -1,20 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
import PrettierJavaPlugin from "prettier-plugin-java";
|
|
4
|
-
import PrettierRustPlugin from "prettier-plugin-rust";
|
|
5
|
-
//#region src/type-system/basic.ts
|
|
1
|
+
import { camelCase, pascalCase, snakeCase } from "text-case";
|
|
2
|
+
//#region src/types.ts
|
|
6
3
|
function int32(options) {
|
|
7
4
|
return {
|
|
8
5
|
kind: "int32",
|
|
9
6
|
...options
|
|
10
7
|
};
|
|
11
8
|
}
|
|
12
|
-
function int64(options) {
|
|
13
|
-
return {
|
|
14
|
-
kind: "int64",
|
|
15
|
-
...options
|
|
16
|
-
};
|
|
17
|
-
}
|
|
18
9
|
function float32(options) {
|
|
19
10
|
return {
|
|
20
11
|
kind: "float32",
|
|
@@ -39,56 +30,54 @@ function string(options) {
|
|
|
39
30
|
...options
|
|
40
31
|
};
|
|
41
32
|
}
|
|
42
|
-
function
|
|
33
|
+
function array(options) {
|
|
43
34
|
return {
|
|
44
|
-
kind: "
|
|
35
|
+
kind: "array",
|
|
45
36
|
...options
|
|
46
37
|
};
|
|
47
38
|
}
|
|
48
|
-
|
|
49
|
-
* 创建一个可选类型模型。
|
|
50
|
-
*
|
|
51
|
-
* @param base 基础模型类型
|
|
52
|
-
* @param options 可选配置选项
|
|
53
|
-
* @returns 可选类型模型
|
|
54
|
-
*/
|
|
55
|
-
function optional(base, options) {
|
|
39
|
+
function set(options) {
|
|
56
40
|
return {
|
|
57
|
-
kind: "
|
|
58
|
-
base,
|
|
41
|
+
kind: "set",
|
|
59
42
|
...options
|
|
60
43
|
};
|
|
61
44
|
}
|
|
62
|
-
function
|
|
45
|
+
function map(options) {
|
|
63
46
|
return {
|
|
64
|
-
kind: "
|
|
65
|
-
base,
|
|
47
|
+
kind: "map",
|
|
66
48
|
...options
|
|
67
49
|
};
|
|
68
50
|
}
|
|
69
|
-
function
|
|
51
|
+
function record(options) {
|
|
70
52
|
return {
|
|
71
|
-
kind: "
|
|
72
|
-
base,
|
|
53
|
+
kind: "record",
|
|
73
54
|
...options
|
|
74
55
|
};
|
|
75
56
|
}
|
|
76
|
-
function
|
|
57
|
+
function union(options) {
|
|
77
58
|
return {
|
|
78
|
-
kind: "
|
|
79
|
-
base,
|
|
59
|
+
kind: "union",
|
|
80
60
|
...options
|
|
81
61
|
};
|
|
82
62
|
}
|
|
83
|
-
function
|
|
63
|
+
function taggedUnion(options) {
|
|
84
64
|
return {
|
|
85
|
-
kind: "
|
|
65
|
+
kind: "taggedUnion",
|
|
86
66
|
...options
|
|
87
67
|
};
|
|
88
68
|
}
|
|
89
|
-
function
|
|
69
|
+
function literal(value) {
|
|
90
70
|
return {
|
|
91
|
-
kind: "
|
|
71
|
+
kind: "literal",
|
|
72
|
+
value
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
function nullLike() {
|
|
76
|
+
return { kind: "null" };
|
|
77
|
+
}
|
|
78
|
+
function enums(options) {
|
|
79
|
+
return {
|
|
80
|
+
kind: "enums",
|
|
92
81
|
...options
|
|
93
82
|
};
|
|
94
83
|
}
|
|
@@ -110,904 +99,1723 @@ function duration(options) {
|
|
|
110
99
|
...options
|
|
111
100
|
};
|
|
112
101
|
}
|
|
113
|
-
|
|
102
|
+
//#endregion
|
|
103
|
+
//#region src/api.ts
|
|
104
|
+
function route(options) {
|
|
114
105
|
return {
|
|
115
|
-
kind: "
|
|
106
|
+
kind: "route",
|
|
116
107
|
...options
|
|
117
108
|
};
|
|
118
109
|
}
|
|
119
|
-
function
|
|
110
|
+
function json(options) {
|
|
120
111
|
return {
|
|
121
|
-
kind: "
|
|
112
|
+
kind: "json-response",
|
|
122
113
|
...options
|
|
123
114
|
};
|
|
124
115
|
}
|
|
125
|
-
function
|
|
116
|
+
function jsonStream(options) {
|
|
117
|
+
return {
|
|
118
|
+
kind: "stream-response",
|
|
119
|
+
...options
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function sseStream(options) {
|
|
123
|
+
return {
|
|
124
|
+
kind: "sse-response",
|
|
125
|
+
...options
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
function binary(options) {
|
|
129
|
+
return {
|
|
130
|
+
kind: "binary",
|
|
131
|
+
...options
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
//#endregion
|
|
135
|
+
//#region src/security.ts
|
|
136
|
+
function apikey(options) {
|
|
137
|
+
const component = {
|
|
138
|
+
kind: "apikey",
|
|
139
|
+
...options
|
|
140
|
+
};
|
|
126
141
|
return {
|
|
127
|
-
|
|
142
|
+
...component,
|
|
143
|
+
apply: () => ({
|
|
144
|
+
component,
|
|
145
|
+
scopes: []
|
|
146
|
+
})
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function openIdConnect(options) {
|
|
150
|
+
const component = {
|
|
151
|
+
kind: "openIdConnect",
|
|
128
152
|
...options
|
|
129
153
|
};
|
|
154
|
+
return {
|
|
155
|
+
...component,
|
|
156
|
+
apply: (...scopes) => ({
|
|
157
|
+
component,
|
|
158
|
+
scopes
|
|
159
|
+
})
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/deployment.ts
|
|
164
|
+
function deployOpenIdConnect(options) {
|
|
165
|
+
return {
|
|
166
|
+
kind: "openIdConnectDeployment",
|
|
167
|
+
component: options.component,
|
|
168
|
+
issuer: options.issuer
|
|
169
|
+
};
|
|
130
170
|
}
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/generate-jsonschema.ts
|
|
131
173
|
function generateJsonSchema(options) {
|
|
132
|
-
const { model,
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
174
|
+
const { model, toJsonSchema = () => ({}) } = options;
|
|
175
|
+
const registry = options.registry ?? createJsonSchemaRegistry();
|
|
176
|
+
const schema = toJsonSchema(model.schema);
|
|
177
|
+
switch (model.kind) {
|
|
178
|
+
case "int32": return {
|
|
179
|
+
jsonSchema: {
|
|
180
|
+
...schema,
|
|
181
|
+
type: "integer",
|
|
182
|
+
format: "int32"
|
|
183
|
+
},
|
|
184
|
+
registry
|
|
185
|
+
};
|
|
186
|
+
case "float32": return {
|
|
187
|
+
jsonSchema: {
|
|
188
|
+
...schema,
|
|
189
|
+
type: "number",
|
|
190
|
+
format: "float"
|
|
191
|
+
},
|
|
192
|
+
registry
|
|
193
|
+
};
|
|
194
|
+
case "float64": return {
|
|
195
|
+
jsonSchema: {
|
|
196
|
+
...schema,
|
|
197
|
+
type: "number",
|
|
198
|
+
format: "float"
|
|
199
|
+
},
|
|
200
|
+
registry
|
|
201
|
+
};
|
|
202
|
+
case "boolean": return {
|
|
203
|
+
jsonSchema: {
|
|
204
|
+
...schema,
|
|
205
|
+
type: "boolean"
|
|
206
|
+
},
|
|
207
|
+
registry
|
|
208
|
+
};
|
|
209
|
+
case "null": return {
|
|
210
|
+
jsonSchema: {
|
|
211
|
+
...schema,
|
|
212
|
+
type: "null"
|
|
213
|
+
},
|
|
214
|
+
registry
|
|
215
|
+
};
|
|
216
|
+
case "enums": return {
|
|
217
|
+
jsonSchema: {
|
|
218
|
+
...schema,
|
|
219
|
+
type: "string",
|
|
220
|
+
enum: Object.values(model.variants)
|
|
221
|
+
},
|
|
222
|
+
registry
|
|
223
|
+
};
|
|
224
|
+
case "datetime": return {
|
|
225
|
+
jsonSchema: {
|
|
226
|
+
...schema,
|
|
227
|
+
type: "string",
|
|
228
|
+
format: "date-time"
|
|
229
|
+
},
|
|
230
|
+
registry
|
|
231
|
+
};
|
|
232
|
+
case "date": return {
|
|
233
|
+
jsonSchema: {
|
|
234
|
+
...schema,
|
|
235
|
+
type: "string",
|
|
236
|
+
format: "date"
|
|
237
|
+
},
|
|
238
|
+
registry
|
|
239
|
+
};
|
|
240
|
+
case "duration": return {
|
|
241
|
+
jsonSchema: {
|
|
242
|
+
...schema,
|
|
243
|
+
type: "string",
|
|
244
|
+
format: "duration"
|
|
245
|
+
},
|
|
246
|
+
registry
|
|
247
|
+
};
|
|
248
|
+
case "literal": return {
|
|
249
|
+
jsonSchema: {
|
|
250
|
+
...schema,
|
|
251
|
+
const: model.value
|
|
252
|
+
},
|
|
253
|
+
registry
|
|
254
|
+
};
|
|
255
|
+
case "string": return {
|
|
256
|
+
jsonSchema: {
|
|
257
|
+
...schema,
|
|
258
|
+
type: "string"
|
|
259
|
+
},
|
|
260
|
+
registry
|
|
261
|
+
};
|
|
262
|
+
case "array": {
|
|
263
|
+
const { jsonSchema, registry: newRegistry } = generateJsonSchema({
|
|
264
|
+
model: model.base,
|
|
265
|
+
registry,
|
|
266
|
+
toJsonSchema
|
|
157
267
|
});
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
268
|
+
return {
|
|
269
|
+
jsonSchema: {
|
|
270
|
+
...schema,
|
|
271
|
+
type: "array",
|
|
272
|
+
items: jsonSchema
|
|
273
|
+
},
|
|
274
|
+
registry: newRegistry
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
case "map": {
|
|
278
|
+
const { jsonSchema, registry: newRegistry } = generateJsonSchema({
|
|
279
|
+
model: model.base,
|
|
280
|
+
registry,
|
|
281
|
+
toJsonSchema
|
|
163
282
|
});
|
|
283
|
+
return {
|
|
284
|
+
jsonSchema: {
|
|
285
|
+
...schema,
|
|
286
|
+
type: "object",
|
|
287
|
+
additionalProperties: jsonSchema
|
|
288
|
+
},
|
|
289
|
+
registry: newRegistry
|
|
290
|
+
};
|
|
164
291
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
});
|
|
171
|
-
case "float32": return convert({
|
|
172
|
-
type: "number",
|
|
173
|
-
format: "float"
|
|
174
|
-
});
|
|
175
|
-
case "float64": return convert({
|
|
176
|
-
type: "number",
|
|
177
|
-
format: "float"
|
|
178
|
-
});
|
|
179
|
-
case "int64": return convert({
|
|
180
|
-
type: "string",
|
|
181
|
-
format: "int64"
|
|
182
|
-
});
|
|
183
|
-
case "boolean": return convert({ type: "boolean" });
|
|
184
|
-
case "string": return convert({ type: "string" });
|
|
185
|
-
case "literal": return convert({ const: model.value });
|
|
186
|
-
case "optional": {
|
|
187
|
-
const baseSchema = getReferenceSchema(model.base);
|
|
188
|
-
if ("$ref" in baseSchema) return convert({ oneOf: [baseSchema, { type: "null" }] });
|
|
189
|
-
const { type = [], ...others } = baseSchema;
|
|
190
|
-
return convert({
|
|
191
|
-
type: Array.isArray(type) ? [...new Set([...type, "null"])] : [type, "null"],
|
|
192
|
-
...others
|
|
292
|
+
case "set": {
|
|
293
|
+
const { jsonSchema, registry: newRegistry } = generateJsonSchema({
|
|
294
|
+
model: model.base,
|
|
295
|
+
registry,
|
|
296
|
+
toJsonSchema
|
|
193
297
|
});
|
|
298
|
+
return {
|
|
299
|
+
jsonSchema: {
|
|
300
|
+
...schema,
|
|
301
|
+
type: "array",
|
|
302
|
+
items: jsonSchema,
|
|
303
|
+
uniqueItems: true
|
|
304
|
+
},
|
|
305
|
+
registry: newRegistry
|
|
306
|
+
};
|
|
194
307
|
}
|
|
195
|
-
case "
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
type: "string",
|
|
225
|
-
format: "date-time"
|
|
226
|
-
});
|
|
227
|
-
case "date": return convert({
|
|
228
|
-
type: "string",
|
|
229
|
-
format: "date"
|
|
230
|
-
});
|
|
231
|
-
case "duration": return convert({
|
|
232
|
-
type: "string",
|
|
233
|
-
format: "duration"
|
|
234
|
-
});
|
|
235
|
-
case "error": return convert({
|
|
236
|
-
type: "object",
|
|
237
|
-
properties: {
|
|
238
|
-
code: { const: model.code },
|
|
239
|
-
context: model.context == null ? void 0 : {
|
|
308
|
+
case "record": {
|
|
309
|
+
const result = Object.entries(model.properties).reduce((acc, [key, propModel]) => {
|
|
310
|
+
const ref = acc.registry.getRef(propModel);
|
|
311
|
+
if (ref) return {
|
|
312
|
+
registry: acc.registry,
|
|
313
|
+
properties: {
|
|
314
|
+
...acc.properties,
|
|
315
|
+
[key]: { $ref: ref }
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
const generated = generateJsonSchema({
|
|
319
|
+
model: propModel,
|
|
320
|
+
registry: acc.registry,
|
|
321
|
+
toJsonSchema
|
|
322
|
+
});
|
|
323
|
+
return {
|
|
324
|
+
registry: generated.registry,
|
|
325
|
+
properties: {
|
|
326
|
+
...acc.properties,
|
|
327
|
+
[key]: generated.jsonSchema
|
|
328
|
+
}
|
|
329
|
+
};
|
|
330
|
+
}, {
|
|
331
|
+
registry,
|
|
332
|
+
properties: {}
|
|
333
|
+
});
|
|
334
|
+
return {
|
|
335
|
+
jsonSchema: {
|
|
336
|
+
...schema,
|
|
240
337
|
type: "object",
|
|
241
|
-
|
|
242
|
-
additionalProperties: false
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
338
|
+
required: model.required,
|
|
339
|
+
additionalProperties: false,
|
|
340
|
+
properties: result.properties
|
|
341
|
+
},
|
|
342
|
+
registry: result.registry
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
case "union": {
|
|
346
|
+
const result = Object.entries(model.variants).reduce((acc, [key, variantModel]) => {
|
|
347
|
+
const ref = acc.registry.getRef(variantModel);
|
|
348
|
+
if (ref) return {
|
|
349
|
+
registry: acc.registry,
|
|
350
|
+
oneOf: [...acc.oneOf, {
|
|
351
|
+
type: "object",
|
|
352
|
+
required: [key],
|
|
353
|
+
properties: { [key]: { $ref: ref } }
|
|
354
|
+
}]
|
|
355
|
+
};
|
|
356
|
+
const generated = generateJsonSchema({
|
|
357
|
+
model: variantModel,
|
|
358
|
+
registry: acc.registry,
|
|
359
|
+
toJsonSchema
|
|
360
|
+
});
|
|
361
|
+
return {
|
|
362
|
+
registry: generated.registry,
|
|
363
|
+
oneOf: [...acc.oneOf, {
|
|
364
|
+
type: "object",
|
|
365
|
+
required: [key],
|
|
366
|
+
properties: { [key]: generated.jsonSchema }
|
|
367
|
+
}]
|
|
368
|
+
};
|
|
369
|
+
}, {
|
|
370
|
+
registry,
|
|
371
|
+
oneOf: []
|
|
372
|
+
});
|
|
373
|
+
return {
|
|
374
|
+
jsonSchema: {
|
|
375
|
+
...schema,
|
|
376
|
+
oneOf: result.oneOf
|
|
377
|
+
},
|
|
378
|
+
registry: result.registry
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
case "taggedUnion": {
|
|
382
|
+
const result = Object.entries(model.variants).reduce((acc, [key, variantModel]) => {
|
|
383
|
+
const ref = acc.registry.getRef(variantModel);
|
|
384
|
+
if (ref) return {
|
|
385
|
+
registry: acc.registry,
|
|
386
|
+
oneOf: [...acc.oneOf, {
|
|
387
|
+
type: "object",
|
|
388
|
+
required: [model.variantKey, model.payloadKey],
|
|
389
|
+
properties: {
|
|
390
|
+
[model.variantKey]: { const: key },
|
|
391
|
+
[model.payloadKey]: { $ref: ref }
|
|
392
|
+
}
|
|
393
|
+
}]
|
|
394
|
+
};
|
|
395
|
+
const generated = generateJsonSchema({
|
|
396
|
+
model: variantModel,
|
|
397
|
+
registry: acc.registry,
|
|
398
|
+
toJsonSchema
|
|
399
|
+
});
|
|
400
|
+
return {
|
|
401
|
+
registry: generated.registry,
|
|
402
|
+
oneOf: [...acc.oneOf, {
|
|
403
|
+
type: "object",
|
|
404
|
+
required: [model.variantKey, model.payloadKey],
|
|
405
|
+
properties: {
|
|
406
|
+
[model.variantKey]: { const: key },
|
|
407
|
+
[model.payloadKey]: generated.jsonSchema
|
|
408
|
+
}
|
|
409
|
+
}]
|
|
410
|
+
};
|
|
411
|
+
}, {
|
|
412
|
+
registry,
|
|
413
|
+
oneOf: []
|
|
414
|
+
});
|
|
415
|
+
return {
|
|
416
|
+
jsonSchema: {
|
|
417
|
+
...schema,
|
|
418
|
+
oneOf: result.oneOf
|
|
419
|
+
},
|
|
420
|
+
registry: result.registry
|
|
421
|
+
};
|
|
422
|
+
}
|
|
251
423
|
}
|
|
252
424
|
}
|
|
253
|
-
function
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
425
|
+
function createJsonSchemaRegistry(models) {
|
|
426
|
+
const map = new Map(models);
|
|
427
|
+
const registry = {
|
|
428
|
+
getRef(model) {
|
|
429
|
+
const entry = map.get(model);
|
|
430
|
+
return entry ? "#/$defs/" + entry.id : void 0;
|
|
431
|
+
},
|
|
432
|
+
add(id, model) {
|
|
433
|
+
const { jsonSchema } = generateJsonSchema({
|
|
434
|
+
model,
|
|
435
|
+
registry
|
|
436
|
+
});
|
|
437
|
+
return createJsonSchemaRegistry(map.set(model, {
|
|
438
|
+
id,
|
|
439
|
+
schema: jsonSchema
|
|
440
|
+
}));
|
|
441
|
+
}
|
|
442
|
+
};
|
|
443
|
+
return registry;
|
|
444
|
+
}
|
|
445
|
+
function createOpenapiSchemaRegistry(models) {
|
|
446
|
+
const map = new Map(models);
|
|
447
|
+
const registry = {
|
|
448
|
+
getRef(model) {
|
|
449
|
+
const entry = map.get(model);
|
|
450
|
+
return entry ? "#/components/schemas/" + entry.id : void 0;
|
|
451
|
+
},
|
|
452
|
+
add(id, model) {
|
|
453
|
+
const { jsonSchema } = generateJsonSchema({
|
|
454
|
+
model,
|
|
455
|
+
registry
|
|
456
|
+
});
|
|
457
|
+
return createOpenapiSchemaRegistry(map.set(model, {
|
|
458
|
+
id,
|
|
459
|
+
schema: jsonSchema
|
|
460
|
+
}));
|
|
461
|
+
}
|
|
462
|
+
};
|
|
463
|
+
return registry;
|
|
257
464
|
}
|
|
258
465
|
//#endregion
|
|
259
|
-
//#region src/
|
|
466
|
+
//#region src/generate-openapi.ts
|
|
467
|
+
function joinPath$1(basePath, routePath) {
|
|
468
|
+
if (!basePath) return routePath;
|
|
469
|
+
return `${basePath.endsWith("/") ? basePath.slice(0, -1) : basePath}${routePath.startsWith("/") ? routePath : `/${routePath}`}`;
|
|
470
|
+
}
|
|
260
471
|
function generateOpenapi(options) {
|
|
261
|
-
const { info,
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
472
|
+
const { info, servers, routers, security } = options;
|
|
473
|
+
const flatRoutes = routers.flatMap((rm) => Object.entries(rm.routes).map(([_key, route]) => ({
|
|
474
|
+
route,
|
|
475
|
+
group: rm.name,
|
|
476
|
+
fullPath: joinPath$1(rm.basePath ?? "", route.path)
|
|
477
|
+
})));
|
|
478
|
+
const namedModels = collectNamedModels$1(flatRoutes.map((fr) => fr.route));
|
|
479
|
+
const registry = [...namedModels.entries()].reduce((reg, [id, model]) => reg.add(id, model), createOpenapiSchemaRegistry());
|
|
480
|
+
const schemas = [...namedModels.entries()].reduce((acc, [id, model]) => ({
|
|
481
|
+
...acc,
|
|
482
|
+
[id]: generateJsonSchema({
|
|
483
|
+
model,
|
|
484
|
+
registry
|
|
485
|
+
}).jsonSchema
|
|
486
|
+
}), {});
|
|
487
|
+
const hasSchemas = Object.keys(schemas).length > 0;
|
|
488
|
+
const paths = generatePaths(flatRoutes, registry);
|
|
489
|
+
let components = hasSchemas ? { schemas } : void 0;
|
|
490
|
+
if (security?.policy) {
|
|
491
|
+
const schemeResult = buildSecuritySchemes(security.policy, security.deployments);
|
|
492
|
+
if (schemeResult.schemes && Object.keys(schemeResult.schemes).length > 0) components = {
|
|
493
|
+
...components,
|
|
494
|
+
securitySchemes: schemeResult.schemes
|
|
495
|
+
};
|
|
496
|
+
const securedPaths = applySecurityToPaths(paths, security.policy);
|
|
497
|
+
if (securedPaths) return {
|
|
498
|
+
openapi: {
|
|
499
|
+
openapi: "3.2.0",
|
|
500
|
+
info,
|
|
501
|
+
servers,
|
|
502
|
+
paths: securedPaths,
|
|
503
|
+
components
|
|
504
|
+
},
|
|
505
|
+
registry
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
return {
|
|
509
|
+
openapi: {
|
|
510
|
+
openapi: "3.2.0",
|
|
511
|
+
info,
|
|
512
|
+
servers,
|
|
513
|
+
paths,
|
|
514
|
+
components
|
|
270
515
|
},
|
|
271
|
-
|
|
272
|
-
servers
|
|
516
|
+
registry
|
|
273
517
|
};
|
|
274
518
|
}
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
if (model.id != null) ret.set(model.id, model);
|
|
283
|
-
switch (model.kind) {
|
|
284
|
-
case "optional":
|
|
285
|
-
case "array":
|
|
286
|
-
case "set":
|
|
287
|
-
case "map":
|
|
288
|
-
for (const [id, nestModel] of collectModelDeep(model.base)) ret.set(id, nestModel);
|
|
289
|
-
return ret;
|
|
290
|
-
case "record":
|
|
291
|
-
for (const property of Object.values(model.properties)) for (const [id, nestedModel] of collectModelDeep(property)) ret.set(id, nestedModel);
|
|
292
|
-
return ret;
|
|
293
|
-
case "union":
|
|
294
|
-
for (const variant of Object.values(model.variants)) for (const [id, nestedModel] of collectModelDeep(variant)) ret.set(id, nestedModel);
|
|
295
|
-
return ret;
|
|
296
|
-
case "error":
|
|
297
|
-
if (model.context) for (const detail of Object.values(model.context)) for (const [id, nestedModel] of collectModelDeep(detail)) ret.set(id, nestedModel);
|
|
298
|
-
return ret;
|
|
299
|
-
default: return ret;
|
|
300
|
-
}
|
|
519
|
+
function collectNamedModels$1(routes) {
|
|
520
|
+
return routes.reduce((models, route) => {
|
|
521
|
+
return [...collectModelsFromRoute(route).entries()].reduce((acc, [id, model]) => {
|
|
522
|
+
if (!acc.has(id)) acc.set(id, model);
|
|
523
|
+
return acc;
|
|
524
|
+
}, models);
|
|
525
|
+
}, /* @__PURE__ */ new Map());
|
|
301
526
|
}
|
|
302
527
|
function collectModelsFromRoute(route) {
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
].filter((o) => !!o).flatMap(Object.values)) for (const [id, subModel] of collectModelDeep(model)) models.set(id, subModel);
|
|
310
|
-
if (route.content) for (const [id, subModel] of collectModelsFromContent(route.content)) models.set(id, subModel);
|
|
311
|
-
if (route.responses) for (const response of Object.values(route.responses)) for (const [id, subModel] of collectModelsFromResponse(response)) models.set(id, subModel);
|
|
312
|
-
return models;
|
|
313
|
-
}
|
|
314
|
-
function collectModelsFromContent(content) {
|
|
315
|
-
const models = /* @__PURE__ */ new Map();
|
|
316
|
-
if (content.kind !== "binary-stream-content") {
|
|
317
|
-
if (content.model) for (const [id, subModel] of collectModelDeep(content.model)) models.set(id, subModel);
|
|
318
|
-
}
|
|
319
|
-
return models;
|
|
528
|
+
return [route.body, ...Object.values(route.responses).flatMap(collectModelsFromResponse)].filter((m) => m != null).reduce((acc, model) => {
|
|
529
|
+
return collectModelDeep(model).reduce((inner, [id, m]) => {
|
|
530
|
+
if (!inner.has(id)) inner.set(id, m);
|
|
531
|
+
return inner;
|
|
532
|
+
}, acc);
|
|
533
|
+
}, /* @__PURE__ */ new Map());
|
|
320
534
|
}
|
|
321
535
|
function collectModelsFromResponse(response) {
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
function generateComponentSchemasFromModels(models) {
|
|
328
|
-
const ret = /* @__PURE__ */ new Map();
|
|
329
|
-
for (const [id, model] of models) {
|
|
330
|
-
const schema = generateJsonSchema({
|
|
331
|
-
model,
|
|
332
|
-
reference: "openapi"
|
|
333
|
-
});
|
|
334
|
-
ret.set(id, schema);
|
|
335
|
-
}
|
|
336
|
-
return ret;
|
|
337
|
-
}
|
|
338
|
-
function generatePathsObject(routes) {
|
|
339
|
-
const pathItems = /* @__PURE__ */ new Map();
|
|
340
|
-
for (const route of routes) {
|
|
341
|
-
const item = pathItems.get(route.path) ?? {};
|
|
342
|
-
pathItems.set(route.path, {
|
|
343
|
-
...item,
|
|
344
|
-
...generatePathItemObject(route)
|
|
345
|
-
});
|
|
536
|
+
switch (response.kind) {
|
|
537
|
+
case "json-response":
|
|
538
|
+
case "stream-response":
|
|
539
|
+
case "sse-response": return response.body ? [response.body] : [];
|
|
540
|
+
case "binary": return [];
|
|
346
541
|
}
|
|
347
|
-
return Object.fromEntries(pathItems);
|
|
348
|
-
}
|
|
349
|
-
function generatePathItemObject(route) {
|
|
350
|
-
return { [route.method]: generateOperationObject(route) };
|
|
351
542
|
}
|
|
352
|
-
function
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
});
|
|
543
|
+
function collectModelDeep(model) {
|
|
544
|
+
const self = "id" in model && model.id != null ? [[model.id, model]] : [];
|
|
545
|
+
const nested = collectNestedModels(model);
|
|
546
|
+
return [...self, ...nested];
|
|
357
547
|
}
|
|
358
|
-
function
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
if (queries) for (const [name, model] of Object.entries(queries)) parameterObjects.push({
|
|
368
|
-
in: "query",
|
|
369
|
-
name,
|
|
370
|
-
required: model.kind !== "optional",
|
|
371
|
-
schema: getSchemaReference(model)
|
|
372
|
-
});
|
|
373
|
-
if (headers) for (const [name, model] of Object.entries(headers)) parameterObjects.push({
|
|
374
|
-
in: "header",
|
|
375
|
-
name,
|
|
376
|
-
required: model.kind !== "optional",
|
|
377
|
-
schema: getSchemaReference(model)
|
|
378
|
-
});
|
|
379
|
-
if (cookies) for (const [name, model] of Object.entries(cookies)) parameterObjects.push({
|
|
380
|
-
in: "cookie",
|
|
381
|
-
name,
|
|
382
|
-
required: model.kind !== "optional",
|
|
383
|
-
schema: getSchemaReference(model)
|
|
384
|
-
});
|
|
385
|
-
const responseObjects = /* @__PURE__ */ new Map();
|
|
386
|
-
if (responses) for (const [name, response] of Object.entries(responses)) responseObjects.set(response.status.toString(), generateResponseObjectFromResponse(name, response));
|
|
387
|
-
return {
|
|
388
|
-
operationId: id,
|
|
389
|
-
summary: summary ?? route.id,
|
|
390
|
-
description,
|
|
391
|
-
parameters: parameterObjects,
|
|
392
|
-
responses: Object.fromEntries(responseObjects),
|
|
393
|
-
requestBody: content == null ? void 0 : generateRequestBodyObject(content)
|
|
394
|
-
};
|
|
395
|
-
}
|
|
396
|
-
function generateRequestBodyObject(content) {
|
|
397
|
-
switch (content.kind) {
|
|
398
|
-
case "plain-text-content":
|
|
399
|
-
case "json-content":
|
|
400
|
-
case "form-content":
|
|
401
|
-
case "server-sent-event-content": return { content: { [content.type]: generateMeidaTypeObject(content) } };
|
|
402
|
-
default: return {
|
|
403
|
-
description: content.description,
|
|
404
|
-
content: { [content.type]: generateMeidaTypeObject(content) }
|
|
405
|
-
};
|
|
548
|
+
function collectNestedModels(model) {
|
|
549
|
+
switch (model.kind) {
|
|
550
|
+
case "array":
|
|
551
|
+
case "set":
|
|
552
|
+
case "map": return collectModelDeep(model.base);
|
|
553
|
+
case "record": return Object.values(model.properties).flatMap(collectModelDeep);
|
|
554
|
+
case "union": return Object.values(model.variants).flatMap(collectModelDeep);
|
|
555
|
+
case "taggedUnion": return Object.values(model.variants).flatMap(collectModelDeep);
|
|
556
|
+
default: return [];
|
|
406
557
|
}
|
|
407
558
|
}
|
|
408
|
-
function
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
itemSchema: getSchemaReference(content.model)
|
|
418
|
-
};
|
|
419
|
-
case "binary-stream-content": return {
|
|
420
|
-
schema: {
|
|
421
|
-
type: "string",
|
|
422
|
-
format: "binary"
|
|
423
|
-
},
|
|
424
|
-
itemSchema: {
|
|
425
|
-
type: "string",
|
|
426
|
-
format: "binary"
|
|
559
|
+
function generatePaths(flatRoutes, registry) {
|
|
560
|
+
return flatRoutes.reduce((paths, { route, group, fullPath }) => {
|
|
561
|
+
const existing = paths[fullPath] ?? {};
|
|
562
|
+
const method = route.method.toLowerCase();
|
|
563
|
+
return {
|
|
564
|
+
...paths,
|
|
565
|
+
[fullPath]: {
|
|
566
|
+
...existing,
|
|
567
|
+
[method]: generateOperation(route, group, registry)
|
|
427
568
|
}
|
|
428
569
|
};
|
|
429
|
-
|
|
430
|
-
schema: {
|
|
431
|
-
type: "string",
|
|
432
|
-
format: "binary"
|
|
433
|
-
},
|
|
434
|
-
itemSchema: content.model ? getSchemaReference(content.model) : { type: "string" }
|
|
435
|
-
};
|
|
436
|
-
case "form-content": return { schema: getSchemaReference(content.model) };
|
|
437
|
-
}
|
|
570
|
+
}, {});
|
|
438
571
|
}
|
|
439
|
-
function
|
|
440
|
-
const
|
|
441
|
-
if (response.headers) for (const [name, model] of Object.entries(response.headers)) headerObjects.set(name, {
|
|
442
|
-
schema: getSchemaReference(model),
|
|
443
|
-
required: model.kind !== "optional"
|
|
444
|
-
});
|
|
572
|
+
function generateOperation(route, group, registry) {
|
|
573
|
+
const tags = route.tags?.includes(group) ? route.tags : [...route.tags ?? [], group];
|
|
445
574
|
return {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
575
|
+
summary: route.summary,
|
|
576
|
+
description: route.description,
|
|
577
|
+
tags,
|
|
578
|
+
parameters: generateParameters(route, registry),
|
|
579
|
+
requestBody: generateRequestBody(route, registry),
|
|
580
|
+
responses: generateResponses(route.responses, registry)
|
|
449
581
|
};
|
|
450
582
|
}
|
|
451
|
-
function route
|
|
583
|
+
function generateParameters(route, registry) {
|
|
584
|
+
const pathParams = Object.entries(route.variables ?? {}).map(([name, model]) => generateParameter(name, model, "path", true, registry));
|
|
585
|
+
const queries = route.queries;
|
|
586
|
+
const headers = route.headers;
|
|
587
|
+
const queryParams = queries ? Object.entries(queries.properties).map(([name, model]) => generateParameter(name, model, "query", queries.required.includes(name), registry)) : [];
|
|
588
|
+
const headerParams = headers ? Object.entries(headers.properties).map(([name, model]) => generateParameter(name, model, "header", headers.required.includes(name), registry)) : [];
|
|
589
|
+
const all = [
|
|
590
|
+
...pathParams,
|
|
591
|
+
...queryParams,
|
|
592
|
+
...headerParams
|
|
593
|
+
];
|
|
594
|
+
return all.length > 0 ? all : void 0;
|
|
595
|
+
}
|
|
596
|
+
function generateParameter(name, model, location, required, registry) {
|
|
452
597
|
return {
|
|
453
|
-
|
|
454
|
-
|
|
598
|
+
name,
|
|
599
|
+
in: location,
|
|
600
|
+
required,
|
|
601
|
+
schema: getSchema(model, registry)
|
|
455
602
|
};
|
|
456
603
|
}
|
|
457
|
-
function
|
|
458
|
-
return
|
|
459
|
-
|
|
460
|
-
...options
|
|
461
|
-
};
|
|
604
|
+
function generateRequestBody(route, registry) {
|
|
605
|
+
if (route.body == null || route.body.kind === "null") return void 0;
|
|
606
|
+
return { content: { [route.contentType ?? "application/json"]: generateMediaType(route.body, registry) } };
|
|
462
607
|
}
|
|
463
|
-
function
|
|
464
|
-
return {
|
|
465
|
-
kind: "json-content",
|
|
466
|
-
...options
|
|
467
|
-
};
|
|
608
|
+
function generateMediaType(model, registry) {
|
|
609
|
+
return { schema: getSchema(model, registry) };
|
|
468
610
|
}
|
|
469
|
-
function
|
|
470
|
-
return {
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
};
|
|
611
|
+
function generateResponses(responses, registry) {
|
|
612
|
+
return Object.entries(responses).reduce((acc, [status, response]) => ({
|
|
613
|
+
...acc,
|
|
614
|
+
[status]: generateResponseObject(response, registry)
|
|
615
|
+
}), {});
|
|
474
616
|
}
|
|
475
|
-
function
|
|
617
|
+
function generateResponseObject(response, registry) {
|
|
476
618
|
return {
|
|
477
|
-
|
|
478
|
-
|
|
619
|
+
description: response.summary ?? "",
|
|
620
|
+
headers: response.kind !== "binary" && response.headers ? generateResponseHeaders(response.headers, registry) : void 0,
|
|
621
|
+
content: generateResponseContent(response, registry)
|
|
479
622
|
};
|
|
480
623
|
}
|
|
481
|
-
function
|
|
624
|
+
function generateResponseHeaders(headers, registry) {
|
|
625
|
+
return Object.entries(headers.properties).reduce((acc, [name, model]) => ({
|
|
626
|
+
...acc,
|
|
627
|
+
[name]: { schema: getSchema(model, registry) }
|
|
628
|
+
}), {});
|
|
629
|
+
}
|
|
630
|
+
function generateResponseContent(response, registry) {
|
|
631
|
+
switch (response.kind) {
|
|
632
|
+
case "json-response": {
|
|
633
|
+
if (response.body == null) return void 0;
|
|
634
|
+
const contentType = response.contentType ?? "application/json";
|
|
635
|
+
const mediaType = { schema: getSchema(response.body, registry) };
|
|
636
|
+
return { [contentType]: mediaType };
|
|
637
|
+
}
|
|
638
|
+
case "stream-response": {
|
|
639
|
+
if (response.body == null) return void 0;
|
|
640
|
+
const contentType = response.contentType ?? "application/x-ndjson";
|
|
641
|
+
const mediaType = {
|
|
642
|
+
schema: {
|
|
643
|
+
type: "string",
|
|
644
|
+
format: "binary"
|
|
645
|
+
},
|
|
646
|
+
itemSchema: getSchema(response.body, registry)
|
|
647
|
+
};
|
|
648
|
+
return { [contentType]: mediaType };
|
|
649
|
+
}
|
|
650
|
+
case "sse-response": {
|
|
651
|
+
const contentType = response.contentType ?? "text/event-stream";
|
|
652
|
+
const mediaType = {
|
|
653
|
+
schema: {
|
|
654
|
+
type: "string",
|
|
655
|
+
format: "binary"
|
|
656
|
+
},
|
|
657
|
+
itemSchema: response.body ? getSchema(response.body, registry) : { type: "string" }
|
|
658
|
+
};
|
|
659
|
+
return { [contentType]: mediaType };
|
|
660
|
+
}
|
|
661
|
+
case "binary": return { [response.contentType ?? "application/octet-stream"]: { schema: {
|
|
662
|
+
type: "string",
|
|
663
|
+
format: "binary"
|
|
664
|
+
} } };
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function getSchema(model, registry) {
|
|
668
|
+
const ref = registry.getRef(model);
|
|
669
|
+
return ref ? { $ref: ref } : generateJsonSchema({
|
|
670
|
+
model,
|
|
671
|
+
registry
|
|
672
|
+
}).jsonSchema;
|
|
673
|
+
}
|
|
674
|
+
function buildSecuritySchemes(policy, deployments) {
|
|
675
|
+
const components = collectSecurityComponents(policy);
|
|
676
|
+
if (components.length === 0) return { schemes: void 0 };
|
|
677
|
+
const schemes = {};
|
|
678
|
+
for (const component of components) switch (component.kind) {
|
|
679
|
+
case "apikey":
|
|
680
|
+
schemes[component.id] = toApiKeyScheme(component);
|
|
681
|
+
break;
|
|
682
|
+
case "openIdConnect": {
|
|
683
|
+
const deployment = findDeployment(component, deployments);
|
|
684
|
+
if (!deployment || deployment.kind !== "openIdConnectDeployment") {
|
|
685
|
+
console.warn(`[generateOpenapi] Security scheme "${component.id}" (openIdConnect) has no matching deployment, skipping`);
|
|
686
|
+
break;
|
|
687
|
+
}
|
|
688
|
+
schemes[component.id] = toOpenIdConnectScheme(component, deployment);
|
|
689
|
+
break;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return { schemes: Object.keys(schemes).length > 0 ? schemes : void 0 };
|
|
693
|
+
}
|
|
694
|
+
function findDeployment(component, deployments) {
|
|
695
|
+
if (!deployments) return void 0;
|
|
696
|
+
for (const dep of Object.values(deployments)) if (dep.component.id === component.id) return dep;
|
|
697
|
+
}
|
|
698
|
+
function toApiKeyScheme(component) {
|
|
482
699
|
return {
|
|
483
|
-
|
|
484
|
-
|
|
700
|
+
type: "apiKey",
|
|
701
|
+
name: component.name,
|
|
702
|
+
in: "header",
|
|
703
|
+
description: component.description
|
|
485
704
|
};
|
|
486
705
|
}
|
|
487
|
-
function
|
|
488
|
-
const { kind = "server-sent-event-content", type = "text/event-stream", model } = options ?? {};
|
|
706
|
+
function toOpenIdConnectScheme(component, deployment) {
|
|
489
707
|
return {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
708
|
+
type: "openIdConnect",
|
|
709
|
+
openIdConnectUrl: deployment.issuer.endsWith("/") ? `${deployment.issuer}.well-known/openid-configuration` : `${deployment.issuer}/.well-known/openid-configuration`,
|
|
710
|
+
description: component.description
|
|
493
711
|
};
|
|
494
712
|
}
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
import org.jspecify.annotations.NullMarked;
|
|
504
|
-
|
|
505
|
-
@NullMarked
|
|
506
|
-
public record ${model.id}(
|
|
507
|
-
${Object.entries(model.properties).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generatePropertyCode(packageName, k, v))}
|
|
508
|
-
){
|
|
509
|
-
${Object.entries(model.properties).filter(([__dirname, v]) => v.kind === "literal").map(([k, v]) => generateStaticPropertyCode(packageName, k, v)).join(";")}
|
|
510
|
-
}
|
|
511
|
-
`;
|
|
512
|
-
case "union": return `
|
|
513
|
-
package ${packageName};
|
|
514
|
-
|
|
515
|
-
import org.jspecify.annotations.NullMarked;
|
|
516
|
-
|
|
517
|
-
@NullMarked
|
|
518
|
-
public sealed interface ${model.id}{
|
|
519
|
-
${Object.entries(model.variants).map(([k, v]) => generateUnionVariantCode(packageName, model.id, k, v)).join("\n")}
|
|
520
|
-
}
|
|
521
|
-
`;
|
|
522
|
-
case "error": return `
|
|
523
|
-
package ${packageName};
|
|
524
|
-
|
|
525
|
-
import org.jspecify.annotations.NullMarked;
|
|
526
|
-
|
|
527
|
-
@NullMarked
|
|
528
|
-
public record ${model.id}(Context context){
|
|
529
|
-
${generateStaticPropertyCode(packageName, "code", literal({ value: model.code }))}
|
|
530
|
-
|
|
531
|
-
public ${model.id}(
|
|
532
|
-
${Object.entries(model.context ?? {}).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generatePropertyCode(packageName, k, v))}
|
|
533
|
-
){
|
|
534
|
-
this(new Context(${getPropertyNames(model.context)}));
|
|
535
|
-
}
|
|
536
|
-
|
|
537
|
-
public record Context(
|
|
538
|
-
${Object.entries(model.context ?? {}).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generatePropertyCode(packageName, k, v))}
|
|
539
|
-
){
|
|
540
|
-
${Object.entries(model.context ?? {}).filter(([_, v]) => v.kind === "literal").map(([k, v]) => generateStaticPropertyCode(packageName, k, v)).join(";")}
|
|
541
|
-
}
|
|
542
|
-
}
|
|
543
|
-
`;
|
|
544
|
-
case "enums": return `
|
|
545
|
-
|
|
546
|
-
package ${packageName};
|
|
547
|
-
|
|
548
|
-
import org.jspecify.annotations.NullMarked;
|
|
549
|
-
|
|
550
|
-
public enum ${model.id} {
|
|
551
|
-
|
|
552
|
-
${Object.entries(model.variants).map(([k, v]) => `${k}("${v}")`)};
|
|
553
|
-
|
|
554
|
-
private final String value;
|
|
555
|
-
|
|
556
|
-
public ${model.id}(String value){
|
|
557
|
-
this.value = value;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
@com.fasterxml.jackson.annotation.JsonValue
|
|
561
|
-
public String getValue() {
|
|
562
|
-
return value;
|
|
563
|
-
}
|
|
564
|
-
}
|
|
565
|
-
`;
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
function getPropertyNames(properties) {
|
|
569
|
-
if (properties == null) return "";
|
|
570
|
-
return Object.keys(properties).join(",");
|
|
571
|
-
}
|
|
572
|
-
function generateUnionVariantCode(packageName, unionId, variantName, variant) {
|
|
573
|
-
switch (variant.kind) {
|
|
574
|
-
case "literal": return `
|
|
575
|
-
record ${variantName}() implements ${unionId} {
|
|
576
|
-
@com.fasterxml.jackson.annotation.JsonValue
|
|
577
|
-
${generateStaticPropertyCode(packageName, "value", variant)}
|
|
578
|
-
}
|
|
579
|
-
`;
|
|
580
|
-
default: return `
|
|
581
|
-
record ${variantName}(
|
|
582
|
-
@com.fasterxml.jackson.annotation.JsonCreator
|
|
583
|
-
@com.fasterxml.jackson.annotation.JsonValue
|
|
584
|
-
${generateModelSignature$1(packageName, variant)} value
|
|
585
|
-
) implements ${unionId} {}
|
|
586
|
-
`;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
function generatePropertyCode(packageName, name, property) {
|
|
590
|
-
return `${generateModelSignature$1(packageName, property)} ${name}`;
|
|
591
|
-
}
|
|
592
|
-
function generateStaticPropertyCode(packageName, name, property) {
|
|
593
|
-
return `public ${generateModelSignature$1(packageName, property)} ${name} = ${generateStaticValue$1(property.value)}`;
|
|
594
|
-
}
|
|
595
|
-
function generateStaticValue$1(value) {
|
|
596
|
-
switch (typeof value) {
|
|
597
|
-
case "string": return `"${value}"`;
|
|
598
|
-
case "boolean": return String(value);
|
|
599
|
-
case "number": return Number.isInteger(value) ? String(value) : value + "d";
|
|
600
|
-
default: throw Error("Unsupported literal value");
|
|
601
|
-
}
|
|
602
|
-
}
|
|
603
|
-
function generateModelSignature$1(packageName, model) {
|
|
604
|
-
switch (model.kind) {
|
|
605
|
-
case "string": return "String";
|
|
606
|
-
case "int32": return "int";
|
|
607
|
-
case "int64": return "long";
|
|
608
|
-
case "float32": return "float";
|
|
609
|
-
case "float64": return "double";
|
|
610
|
-
case "boolean": return "boolean";
|
|
611
|
-
case "date": return "java.time.LocalDate";
|
|
612
|
-
case "datetime": return "java.time.LocalDateTime";
|
|
613
|
-
case "duration": return "java.time.Duration";
|
|
614
|
-
case "array": return `Array<${generateModelSignature$1(packageName, model.base)}>`;
|
|
615
|
-
case "set": return `Set<${generateModelSignature$1(packageName, model.base)}>`;
|
|
616
|
-
case "map": return `Map<String, ${generateModelSignature$1(packageName, model.base)}>`;
|
|
617
|
-
case "optional": switch (model.base.kind) {
|
|
618
|
-
case "int32": return "@Nullable Integer";
|
|
619
|
-
case "int64": return "@Nullable Long";
|
|
620
|
-
case "float32": return "@Nullable Float";
|
|
621
|
-
case "float64": return "@Nullable Double";
|
|
622
|
-
case "boolean": return "@Nullable Boolean";
|
|
623
|
-
case "optional": return generateModelSignature$1(packageName, model.base);
|
|
624
|
-
default: return `@Nullable ${generateModelSignature$1(packageName, model.base)}`;
|
|
713
|
+
function collectSecurityComponents(policy) {
|
|
714
|
+
const seen = /* @__PURE__ */ new Set();
|
|
715
|
+
const result = [];
|
|
716
|
+
for (const pathItem of Object.values(policy.paths)) {
|
|
717
|
+
if (!pathItem.pipeline) continue;
|
|
718
|
+
for (const apply of pathItem.pipeline) if (!seen.has(apply.component.id)) {
|
|
719
|
+
seen.add(apply.component.id);
|
|
720
|
+
result.push(apply.component);
|
|
625
721
|
}
|
|
626
|
-
case "literal": switch (typeof model.value) {
|
|
627
|
-
case "string": return "String";
|
|
628
|
-
case "boolean": return "boolean";
|
|
629
|
-
case "number": return Number.isInteger(model.value) ? "int" : "double";
|
|
630
|
-
default: throw Error("Unsupported literal value");
|
|
631
|
-
}
|
|
632
|
-
default: return packageName + "." + model.id;
|
|
633
722
|
}
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
function applySecurityToPaths(paths, policy) {
|
|
726
|
+
if (!paths) return void 0;
|
|
727
|
+
const operationMethods = [
|
|
728
|
+
"get",
|
|
729
|
+
"put",
|
|
730
|
+
"post",
|
|
731
|
+
"delete",
|
|
732
|
+
"options",
|
|
733
|
+
"head",
|
|
734
|
+
"patch",
|
|
735
|
+
"trace"
|
|
736
|
+
];
|
|
737
|
+
return Object.entries(paths).reduce((acc, [pathKey, pathItem]) => {
|
|
738
|
+
const securedItem = operationMethods.reduce((methodAcc, method) => {
|
|
739
|
+
const op = pathItem[method];
|
|
740
|
+
if (!op) return methodAcc;
|
|
741
|
+
const requirements = resolveSecurityRequirements(policy, pathKey, method);
|
|
742
|
+
if (requirements.length === 0) return methodAcc;
|
|
743
|
+
return {
|
|
744
|
+
...methodAcc,
|
|
745
|
+
[method]: {
|
|
746
|
+
...op,
|
|
747
|
+
security: requirements
|
|
748
|
+
}
|
|
749
|
+
};
|
|
750
|
+
}, pathItem);
|
|
751
|
+
return {
|
|
752
|
+
...acc,
|
|
753
|
+
[pathKey]: securedItem
|
|
754
|
+
};
|
|
755
|
+
}, {});
|
|
756
|
+
}
|
|
757
|
+
function resolveSecurityRequirements(policy, pathKey, method) {
|
|
758
|
+
const requirements = [];
|
|
759
|
+
for (const [pattern, pathItem] of Object.entries(policy.paths)) {
|
|
760
|
+
if (!matchesPath(pattern, pathKey)) continue;
|
|
761
|
+
const methods = pathItem.methods;
|
|
762
|
+
if (methods && methods.length > 0 && !methods.includes(method.toUpperCase())) continue;
|
|
763
|
+
if (!pathItem.pipeline) continue;
|
|
764
|
+
for (const apply of pathItem.pipeline) requirements.push({ [apply.component.id]: apply.scopes });
|
|
765
|
+
}
|
|
766
|
+
return requirements;
|
|
634
767
|
}
|
|
635
|
-
|
|
768
|
+
function matchesPath(pattern, path) {
|
|
636
769
|
try {
|
|
637
|
-
return
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
} catch (error) {
|
|
642
|
-
console.error(error);
|
|
643
|
-
return code;
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
async function generateJavaCodes(options) {
|
|
647
|
-
const { package: packageName, srcDir, routes = [], models = [] } = options;
|
|
648
|
-
const codes = /* @__PURE__ */ new Map();
|
|
649
|
-
const namedModels = collectModelFromRoutes(routes);
|
|
650
|
-
for (const model of models) for (const [id, subModel] of collectModelDeep(model)) namedModels.set(id, subModel);
|
|
651
|
-
for (const [id, model] of namedModels) {
|
|
652
|
-
if (model.kind !== "union" && model.kind !== "record" && model.kind !== "enums" && model.kind !== "error") continue;
|
|
653
|
-
const path = resolve(srcDir, ...(packageName + "." + id).split("."), ".java");
|
|
654
|
-
const code = await formatJava(generateJavaClass({
|
|
655
|
-
package: packageName,
|
|
656
|
-
model
|
|
657
|
-
}));
|
|
658
|
-
codes.set(path, code);
|
|
659
|
-
}
|
|
660
|
-
return codes.entries().map(([path, code]) => ({
|
|
661
|
-
path,
|
|
662
|
-
code
|
|
663
|
-
})).toArray();
|
|
770
|
+
return new RegExp(pattern).test(path);
|
|
771
|
+
} catch {
|
|
772
|
+
return false;
|
|
773
|
+
}
|
|
664
774
|
}
|
|
665
775
|
//#endregion
|
|
666
|
-
//#region src/
|
|
667
|
-
function
|
|
668
|
-
const
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
776
|
+
//#region src/codegen/collect.ts
|
|
777
|
+
function collectNamedModels(models, options) {
|
|
778
|
+
const identifier = options?.identifier ?? pascalCase;
|
|
779
|
+
const namespace = options?.namespace;
|
|
780
|
+
return models.reduce((acc, model) => {
|
|
781
|
+
if (model.kind === "record") return [...acc, toRecordDescriptor(model, identifier, namespace)];
|
|
782
|
+
if (model.kind === "enums") return [...acc, toEnumsDescriptor(model, identifier, namespace)];
|
|
783
|
+
if (model.kind === "union") return [...acc, {
|
|
784
|
+
kind: "union",
|
|
785
|
+
originalId: model.id,
|
|
786
|
+
identifier: identifier(model.id),
|
|
787
|
+
namespace,
|
|
788
|
+
title: model.title,
|
|
789
|
+
description: model.description,
|
|
790
|
+
deprecated: model.deprecated,
|
|
791
|
+
examples: model.examples,
|
|
792
|
+
variants: model.variants
|
|
793
|
+
}];
|
|
794
|
+
if (model.kind === "taggedUnion") return [...acc, {
|
|
795
|
+
kind: "taggedUnion",
|
|
796
|
+
originalId: model.id,
|
|
797
|
+
identifier: identifier(model.id),
|
|
798
|
+
namespace,
|
|
799
|
+
title: model.title,
|
|
800
|
+
description: model.description,
|
|
801
|
+
deprecated: model.deprecated,
|
|
802
|
+
examples: model.examples,
|
|
803
|
+
variants: model.variants,
|
|
804
|
+
variantKey: model.variantKey,
|
|
805
|
+
payloadKey: model.payloadKey
|
|
806
|
+
}];
|
|
807
|
+
return acc;
|
|
808
|
+
}, []);
|
|
809
|
+
}
|
|
810
|
+
function toRecordDescriptor(model, identifier, namespace) {
|
|
811
|
+
return {
|
|
812
|
+
kind: "record",
|
|
813
|
+
originalId: model.id,
|
|
814
|
+
identifier: identifier(model.id),
|
|
815
|
+
namespace,
|
|
816
|
+
title: model.title,
|
|
817
|
+
description: model.description,
|
|
818
|
+
deprecated: "deprecated" in model ? model.deprecated : void 0,
|
|
819
|
+
examples: model.examples,
|
|
820
|
+
fields: Object.entries(model.properties).map(([name, propModel]) => ({
|
|
821
|
+
name,
|
|
822
|
+
model: propModel,
|
|
823
|
+
required: model.required.includes(name),
|
|
824
|
+
title: propModel.title,
|
|
825
|
+
description: propModel.description,
|
|
826
|
+
deprecated: propModel.deprecated
|
|
827
|
+
}))
|
|
828
|
+
};
|
|
829
|
+
}
|
|
830
|
+
function toEnumsDescriptor(model, identifier, namespace) {
|
|
831
|
+
return {
|
|
832
|
+
kind: "enums",
|
|
833
|
+
originalId: model.id,
|
|
834
|
+
identifier: identifier(model.id),
|
|
835
|
+
namespace,
|
|
836
|
+
title: model.title,
|
|
837
|
+
description: model.description,
|
|
838
|
+
values: model.variants
|
|
839
|
+
};
|
|
840
|
+
}
|
|
841
|
+
function joinPath(basePath, routePath) {
|
|
842
|
+
if (!basePath) return routePath;
|
|
843
|
+
return `${basePath.endsWith("/") ? basePath.slice(0, -1) : basePath}${routePath.startsWith("/") ? routePath : `/${routePath}`}`;
|
|
844
|
+
}
|
|
845
|
+
function collectOperations(routers) {
|
|
846
|
+
return routers.flatMap((routerModel) => {
|
|
847
|
+
const basePath = routerModel.basePath ?? "";
|
|
848
|
+
return Object.entries(routerModel.routes).map(([id, route]) => {
|
|
849
|
+
const queries = {};
|
|
850
|
+
if (route.queries) for (const [name, propModel] of Object.entries(route.queries.properties)) queries[name] = {
|
|
851
|
+
model: propModel,
|
|
852
|
+
name,
|
|
853
|
+
required: route.queries.required.includes(name)
|
|
854
|
+
};
|
|
855
|
+
const headers = {};
|
|
856
|
+
if (route.headers) for (const [name, propModel] of Object.entries(route.headers.properties)) headers[name] = {
|
|
857
|
+
model: propModel,
|
|
858
|
+
name,
|
|
859
|
+
required: route.headers.required.includes(name)
|
|
860
|
+
};
|
|
861
|
+
const pathVariables = {};
|
|
862
|
+
if (route.variables) for (const [name, model] of Object.entries(route.variables)) pathVariables[name] = {
|
|
863
|
+
model,
|
|
864
|
+
name
|
|
865
|
+
};
|
|
866
|
+
const responses = {};
|
|
867
|
+
const responseKinds = {};
|
|
868
|
+
for (const [status, resp] of Object.entries(route.responses)) {
|
|
869
|
+
responses[Number(status)] = "body" in resp && resp.body != null ? resp.body : null;
|
|
870
|
+
responseKinds[Number(status)] = resp.kind;
|
|
871
|
+
}
|
|
872
|
+
return {
|
|
873
|
+
id,
|
|
874
|
+
group: routerModel.name,
|
|
875
|
+
method: route.method,
|
|
876
|
+
path: joinPath(basePath, route.path),
|
|
877
|
+
summary: route.summary,
|
|
878
|
+
description: route.description,
|
|
879
|
+
tags: route.tags,
|
|
880
|
+
requestModel: route.body ?? null,
|
|
881
|
+
responses,
|
|
882
|
+
responseKinds,
|
|
883
|
+
pathVariables,
|
|
884
|
+
queries,
|
|
885
|
+
headers
|
|
886
|
+
};
|
|
687
887
|
});
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
888
|
+
});
|
|
889
|
+
}
|
|
890
|
+
function collectSchemaMap(ops) {
|
|
891
|
+
const all = collectAll(ops);
|
|
892
|
+
const map = /* @__PURE__ */ new Map();
|
|
893
|
+
for (const m of all) {
|
|
894
|
+
if ("id" in m && map.has(m.id)) continue;
|
|
895
|
+
if (m.kind === "record") map.set(m.id, {
|
|
896
|
+
kind: "record",
|
|
897
|
+
fields: Object.entries(m.properties).map(([name, p]) => ({
|
|
898
|
+
name,
|
|
899
|
+
model: p,
|
|
900
|
+
required: m.required.includes(name)
|
|
901
|
+
}))
|
|
693
902
|
});
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
structFields.forEach((f) => {
|
|
698
|
-
code += ` ${f},\n`;
|
|
903
|
+
else if (m.kind === "enums") map.set(m.id, {
|
|
904
|
+
kind: "enums",
|
|
905
|
+
variants: m.variants
|
|
699
906
|
});
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
code += `impl ${structName} {\n`;
|
|
704
|
-
code += ` pub const CODE: &'static str = "${model.id}";\n`;
|
|
705
|
-
constFields.forEach((f) => {
|
|
706
|
-
code += ` pub ${f};\n`;
|
|
907
|
+
else if (m.kind === "union") map.set(m.id, {
|
|
908
|
+
kind: "union",
|
|
909
|
+
unionVariants: m.variants
|
|
707
910
|
});
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const enumName = toPascalCase(model.id || "Unknown");
|
|
714
|
-
let code = `use serde::Serialize;\n\n`;
|
|
715
|
-
code += `#[derive(Serialize)]\n#[serde(tag = "type", content = "value")]\npub enum ${enumName} {\n`;
|
|
716
|
-
Object.entries(model.variants).forEach(([k, v]) => {
|
|
717
|
-
const variantName = toPascalCase(k);
|
|
718
|
-
if (v.kind === "literal") code += ` ${variantName},\n`;
|
|
719
|
-
else {
|
|
720
|
-
const variantType = generateModelSignature(v);
|
|
721
|
-
code += ` ${variantName}(${variantType}),\n`;
|
|
722
|
-
}
|
|
723
|
-
});
|
|
724
|
-
code += `}\n`;
|
|
725
|
-
return code;
|
|
726
|
-
}
|
|
727
|
-
function generateErrorCode(model) {
|
|
728
|
-
const structName = toPascalCase(model.id || "Unknown");
|
|
729
|
-
const contextFields = Object.entries(model.context ?? {}).filter(([_, v]) => v.kind !== "literal").map(([k, v]) => generateStructField(k, v));
|
|
730
|
-
const constFields = Object.entries(model.context ?? {}).filter(([_, v]) => v.kind === "literal").map(([k, v]) => generateConstField(k, v));
|
|
731
|
-
let code = `use serde::Serialize;\n\n`;
|
|
732
|
-
if (contextFields.length > 0 || constFields.length > 0) {
|
|
733
|
-
code += `#[derive(Serialize)]\npub struct ${structName} {\n`;
|
|
734
|
-
code += ` pub code: &'static str,\n`;
|
|
735
|
-
if (contextFields.length > 0) code += ` pub context: ${structName}Context,\n`;
|
|
736
|
-
code += `}\n\n`;
|
|
737
|
-
if (contextFields.length > 0) {
|
|
738
|
-
code += `#[derive(Serialize)]\npub struct ${structName}Context {\n`;
|
|
739
|
-
contextFields.forEach((f) => {
|
|
740
|
-
code += ` ${f};\n`;
|
|
741
|
-
});
|
|
742
|
-
code += `}\n\n`;
|
|
743
|
-
}
|
|
744
|
-
code += `impl ${structName} {\n`;
|
|
745
|
-
code += ` pub const CODE: &'static str = "${model.code}";\n`;
|
|
746
|
-
constFields.forEach((f) => {
|
|
747
|
-
code += ` pub ${f};\n`;
|
|
911
|
+
else if (m.kind === "taggedUnion") map.set(m.id, {
|
|
912
|
+
kind: "taggedUnion",
|
|
913
|
+
unionVariants: m.variants,
|
|
914
|
+
variantKey: m.variantKey,
|
|
915
|
+
payloadKey: m.payloadKey
|
|
748
916
|
});
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
const
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
return
|
|
917
|
+
}
|
|
918
|
+
return map;
|
|
919
|
+
}
|
|
920
|
+
function collectAll(ops) {
|
|
921
|
+
const seen = /* @__PURE__ */ new Set();
|
|
922
|
+
const out = [];
|
|
923
|
+
const add = (m) => {
|
|
924
|
+
if (m == null || seen.has(m)) return;
|
|
925
|
+
seen.add(m);
|
|
926
|
+
out.push(m);
|
|
927
|
+
if (m.kind === "array" || m.kind === "set" || m.kind === "map") add(m.base);
|
|
928
|
+
if (m.kind === "record") Object.values(m.properties).forEach((v) => add(v));
|
|
929
|
+
if (m.kind === "union" || m.kind === "taggedUnion") Object.values(m.variants).forEach((v) => add(v));
|
|
930
|
+
};
|
|
931
|
+
for (const op of ops) {
|
|
932
|
+
add(op.requestModel);
|
|
933
|
+
for (const v of Object.values(op.responses)) add(v);
|
|
934
|
+
for (const v of Object.values(op.pathVariables)) add(v.model);
|
|
935
|
+
for (const v of Object.values(op.queries)) add(v.model);
|
|
936
|
+
for (const v of Object.values(op.headers)) add(v.model);
|
|
937
|
+
}
|
|
938
|
+
return out;
|
|
771
939
|
}
|
|
772
|
-
function
|
|
773
|
-
|
|
940
|
+
function resolveNamedRoot(m) {
|
|
941
|
+
switch (m.kind) {
|
|
942
|
+
case "record":
|
|
943
|
+
case "enums":
|
|
944
|
+
case "union":
|
|
945
|
+
case "taggedUnion": return m;
|
|
946
|
+
case "array":
|
|
947
|
+
case "set":
|
|
948
|
+
case "map": return resolveNamedRoot(m.base);
|
|
949
|
+
default: return null;
|
|
950
|
+
}
|
|
774
951
|
}
|
|
775
|
-
|
|
776
|
-
|
|
952
|
+
//#endregion
|
|
953
|
+
//#region src/codegen/json-schema.ts
|
|
954
|
+
function mergeJsonSchemas(schemas) {
|
|
955
|
+
const entries = Object.entries(schemas);
|
|
956
|
+
const registry = entries.reduce((reg, [id, model]) => reg.add(id, model), createJsonSchemaRegistry());
|
|
957
|
+
return {
|
|
958
|
+
$schema: "https://json-schema.org/draft/2020-12/schema",
|
|
959
|
+
$defs: entries.reduce((acc, [id, model]) => {
|
|
960
|
+
const { jsonSchema } = generateJsonSchema({
|
|
961
|
+
model,
|
|
962
|
+
registry
|
|
963
|
+
});
|
|
964
|
+
return typeof jsonSchema === "object" && jsonSchema != null ? {
|
|
965
|
+
...acc,
|
|
966
|
+
[id]: jsonSchema
|
|
967
|
+
} : acc;
|
|
968
|
+
}, {})
|
|
969
|
+
};
|
|
777
970
|
}
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
971
|
+
//#endregion
|
|
972
|
+
//#region src/codegen/hono-server.ts
|
|
973
|
+
function generateHonoServer(options) {
|
|
974
|
+
const { routers, identifier = pascalCase, namespace, configuration } = options;
|
|
975
|
+
const operations = collectOperations(routers);
|
|
976
|
+
const schemaMap = collectSchemaMap(operations);
|
|
977
|
+
const files = {};
|
|
978
|
+
files["models.ts"] = generateModels$1(schemaMap, identifier, namespace);
|
|
979
|
+
for (const operation of operations) files[`${camelCase(operation.id)}.ts`] = generateOpFile(operation, schemaMap, identifier, namespace);
|
|
980
|
+
files["index.ts"] = generateIndex(operations, identifier);
|
|
981
|
+
if (configuration) files["config.ts"] = generateConfig(configuration);
|
|
982
|
+
return files;
|
|
983
|
+
}
|
|
984
|
+
function generateModels$1(schemaMap, identifier, namespace) {
|
|
985
|
+
const lines = [];
|
|
986
|
+
lines.push(`import { z } from "zod"`);
|
|
987
|
+
lines.push("");
|
|
988
|
+
for (const [id, schemaInfo] of schemaMap) {
|
|
989
|
+
const schemaName = camelCase(id) + "Schema";
|
|
990
|
+
const tsName = identifier(id);
|
|
991
|
+
switch (schemaInfo.kind) {
|
|
992
|
+
case "record":
|
|
993
|
+
lines.push(`export const ${schemaName} = z.object({`);
|
|
994
|
+
for (const field of schemaInfo.fields) lines.push(` ${field.name}: ${toZod$1(field.model, schemaMap)}${field.required ? "" : ".optional()"},`);
|
|
995
|
+
lines.push(`})`);
|
|
996
|
+
lines.push("");
|
|
997
|
+
lines.push(`export interface ${tsName} {`);
|
|
998
|
+
for (const field of schemaInfo.fields) lines.push(` ${field.name}${field.required ? "" : "?"}: ${toTs$1(field.model, schemaMap, identifier, namespace)};`);
|
|
999
|
+
lines.push(`}`);
|
|
1000
|
+
lines.push("");
|
|
1001
|
+
break;
|
|
1002
|
+
case "enums":
|
|
1003
|
+
lines.push(`export const ${schemaName} = z.enum(${JSON.stringify(Object.values(schemaInfo.variants))})`);
|
|
1004
|
+
lines.push(`export type ${tsName} = z.infer<typeof ${schemaName}>`);
|
|
1005
|
+
lines.push("");
|
|
1006
|
+
break;
|
|
1007
|
+
case "union": {
|
|
1008
|
+
const unionItems = Object.entries(schemaInfo.unionVariants).map(([, v]) => toZod$1(v, schemaMap)).join(", ");
|
|
1009
|
+
lines.push(`export const ${schemaName} = z.union([${unionItems}])`);
|
|
1010
|
+
lines.push(`export type ${tsName} = z.infer<typeof ${schemaName}>`);
|
|
1011
|
+
lines.push("");
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
case "taggedUnion": {
|
|
1015
|
+
const variantKey = schemaInfo.variantKey;
|
|
1016
|
+
const payloadKey = schemaInfo.payloadKey;
|
|
1017
|
+
const unionItems = Object.entries(schemaInfo.unionVariants).map(([key, v]) => `z.object({ ${JSON.stringify(variantKey)}: z.literal(${JSON.stringify(key)}), ${JSON.stringify(payloadKey)}: ${toZod$1(v, schemaMap)} })`);
|
|
1018
|
+
lines.push(`export const ${schemaName} = z.discriminatedUnion(${JSON.stringify(variantKey)}, [${unionItems.join(", ")}])`);
|
|
1019
|
+
lines.push(`export type ${tsName} = z.infer<typeof ${schemaName}>`);
|
|
1020
|
+
lines.push("");
|
|
1021
|
+
break;
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return lines.join("\n");
|
|
1026
|
+
}
|
|
1027
|
+
function generateOpFile(operation, schemaMap, identifier, namespace) {
|
|
1028
|
+
const lines = [];
|
|
1029
|
+
const OperationName = pascalCase(operation.id);
|
|
1030
|
+
const operationName = camelCase(operation.id);
|
|
1031
|
+
const hasBody = operation.requestModel != null && operation.requestModel.kind !== "null";
|
|
1032
|
+
const hasParams = Object.keys(operation.pathVariables).length > 0;
|
|
1033
|
+
const hasQuery = Object.keys(operation.queries).length > 0;
|
|
1034
|
+
const hasHeaders = Object.keys(operation.headers).length > 0;
|
|
1035
|
+
if (hasParams || hasQuery || hasHeaders) lines.push(`import { z } from "zod"`);
|
|
1036
|
+
const schemaImports = [];
|
|
1037
|
+
if (hasBody && "id" in operation.requestModel) {
|
|
1038
|
+
const root = resolveNamedRoot(operation.requestModel);
|
|
1039
|
+
if (root && schemaMap.has(root.id)) schemaImports.push(camelCase(root.id) + "Schema");
|
|
1040
|
+
}
|
|
1041
|
+
const typeImports = [];
|
|
1042
|
+
if (hasBody && "id" in operation.requestModel) {
|
|
1043
|
+
const root = resolveNamedRoot(operation.requestModel);
|
|
1044
|
+
if (root) {
|
|
1045
|
+
const typeName = identifier(root.id);
|
|
1046
|
+
if (schemaMap.has(root.id) && !typeImports.includes(typeName)) typeImports.push(typeName);
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
for (const responseModel of Object.values(operation.responses)) {
|
|
1050
|
+
if (responseModel == null) continue;
|
|
1051
|
+
const root = resolveNamedRoot(responseModel);
|
|
1052
|
+
if (root) {
|
|
1053
|
+
const typeName = identifier(root.id);
|
|
1054
|
+
if (schemaMap.has(root.id) && !typeImports.includes(typeName)) typeImports.push(typeName);
|
|
1055
|
+
}
|
|
1056
|
+
}
|
|
1057
|
+
const allImports = [...new Set([...schemaImports, ...typeImports])];
|
|
1058
|
+
lines.push(`import type { Context } from "hono"`);
|
|
1059
|
+
if (allImports.length > 0 || schemaImports.length > 0) {
|
|
1060
|
+
const parts = [];
|
|
1061
|
+
if (typeImports.length > 0) parts.push(typeImports.join(", "));
|
|
1062
|
+
if (schemaImports.length > 0) parts.push(schemaImports.join(", "));
|
|
1063
|
+
lines.push(`import { ${parts.join(", ")} } from "./models"`);
|
|
1064
|
+
}
|
|
1065
|
+
lines.push("");
|
|
1066
|
+
if (hasParams) {
|
|
1067
|
+
const fields = Object.entries(operation.pathVariables).map(([key, value]) => ` ${key}: ${toZod$1(value.model, schemaMap)},`);
|
|
1068
|
+
lines.push(`const ${operationName}Params = z.object({`);
|
|
1069
|
+
lines.push(...fields);
|
|
1070
|
+
lines.push(`})`);
|
|
1071
|
+
lines.push("");
|
|
1072
|
+
}
|
|
1073
|
+
if (hasQuery) {
|
|
1074
|
+
const fields = Object.entries(operation.queries).map(([key, query]) => ` ${key}: ${toZod$1(query.model, schemaMap)}${query.required ? "" : ".optional()"},`);
|
|
1075
|
+
lines.push(`const ${operationName}Query = z.object({`);
|
|
1076
|
+
lines.push(...fields);
|
|
1077
|
+
lines.push(`})`);
|
|
1078
|
+
lines.push("");
|
|
1079
|
+
}
|
|
1080
|
+
if (hasHeaders) {
|
|
1081
|
+
const fields = Object.entries(operation.headers).map(([key, header]) => ` "${key}": ${toZod$1(header.model, schemaMap)}${header.required ? "" : ".optional()"},`);
|
|
1082
|
+
lines.push(`const ${operationName}Headers = z.object({`);
|
|
1083
|
+
lines.push(...fields);
|
|
1084
|
+
lines.push(`})`);
|
|
1085
|
+
lines.push("");
|
|
1086
|
+
}
|
|
1087
|
+
const requestFields = [];
|
|
1088
|
+
if (hasParams) {
|
|
1089
|
+
const field = Object.entries(operation.pathVariables).map(([key, value]) => `${key}: ${toTs$1(value.model, schemaMap, identifier, namespace)}`).join("; ");
|
|
1090
|
+
requestFields.push(`params: { ${field} }`);
|
|
1091
|
+
}
|
|
1092
|
+
if (hasQuery) {
|
|
1093
|
+
const field = Object.entries(operation.queries).map(([key, query]) => `${key}${query.required ? "" : "?"}: ${toTs$1(query.model, schemaMap, identifier, namespace)}`).join("; ");
|
|
1094
|
+
requestFields.push(`query: { ${field} }`);
|
|
1095
|
+
}
|
|
1096
|
+
if (hasHeaders) {
|
|
1097
|
+
const field = Object.entries(operation.headers).map(([key, header]) => `"${key}"${header.required ? "" : "?"}: ${toTs$1(header.model, schemaMap, identifier, namespace)}`).join("; ");
|
|
1098
|
+
requestFields.push(`headers: { ${field} }`);
|
|
1099
|
+
}
|
|
1100
|
+
if (hasBody) requestFields.push(`body: ${toTs$1(operation.requestModel, schemaMap, identifier, namespace)}`);
|
|
1101
|
+
lines.push(`export interface ${OperationName}Request {`);
|
|
1102
|
+
for (const field of requestFields) lines.push(` ${field}`);
|
|
1103
|
+
lines.push(`}`);
|
|
1104
|
+
lines.push("");
|
|
1105
|
+
const responseEntries = Object.entries(operation.responses);
|
|
1106
|
+
const responseKind = (status) => operation.responseKinds[Number(status)] ?? "json-response";
|
|
1107
|
+
const responseBodyField = (kind, model) => {
|
|
1108
|
+
if (model == null) {
|
|
1109
|
+
if (kind === "binary") return "body: Blob";
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
if (kind === "stream-response" || kind === "sse-response") return `stream: ReadableStream<${toTs$1(model, schemaMap, identifier, namespace)}>`;
|
|
1113
|
+
if (kind === "binary") return "body: Blob";
|
|
1114
|
+
return `body: ${toTs$1(model, schemaMap, identifier, namespace)}`;
|
|
1115
|
+
};
|
|
1116
|
+
if (responseEntries.length === 1) {
|
|
1117
|
+
const [status, responseModel] = responseEntries[0];
|
|
1118
|
+
const bodyField = responseBodyField(responseKind(status), responseModel);
|
|
1119
|
+
if (bodyField != null) lines.push(`export type ${OperationName}Response = { status: ${status}; ${bodyField} }`);
|
|
1120
|
+
else lines.push(`export type ${OperationName}Response = { status: ${status} }`);
|
|
1121
|
+
} else {
|
|
1122
|
+
lines.push(`export type ${OperationName}Response =`);
|
|
1123
|
+
const parts = responseEntries.map(([status, responseModel]) => {
|
|
1124
|
+
const bodyField = responseBodyField(responseKind(status), responseModel);
|
|
1125
|
+
if (bodyField != null) return ` | { status: ${status}; ${bodyField} }`;
|
|
1126
|
+
return ` | { status: ${status} }`;
|
|
1127
|
+
});
|
|
1128
|
+
lines.push(parts.join("\n"));
|
|
1129
|
+
}
|
|
1130
|
+
lines.push("");
|
|
1131
|
+
if (requestFields.length > 0) lines.push(`export type ${OperationName}Handler = (req: ${OperationName}Request) => Promise<${OperationName}Response>`);
|
|
1132
|
+
else lines.push(`export type ${OperationName}Handler = () => Promise<${OperationName}Response>`);
|
|
1133
|
+
lines.push("");
|
|
1134
|
+
lines.push(`export function ${operationName}(handler: ${OperationName}Handler) {`);
|
|
1135
|
+
lines.push(` return {`);
|
|
1136
|
+
lines.push(` method: "${operation.method.toUpperCase()}",`);
|
|
1137
|
+
lines.push(` path: "${toHonoPath(operation.path)}",`);
|
|
1138
|
+
lines.push(` async handler(context: Context): Promise<Response> {`);
|
|
1139
|
+
const requestArgs = [];
|
|
1140
|
+
if (hasParams) {
|
|
1141
|
+
lines.push(` const params = ${operationName}Params.parse(context.req.param())`);
|
|
1142
|
+
requestArgs.push("params");
|
|
1143
|
+
}
|
|
1144
|
+
if (hasQuery) {
|
|
1145
|
+
lines.push(` const query = ${operationName}Query.parse(context.req.query())`);
|
|
1146
|
+
requestArgs.push("query");
|
|
1147
|
+
}
|
|
1148
|
+
if (hasHeaders) {
|
|
1149
|
+
const headerParts = Object.keys(operation.headers).map((key) => ` "${key}": context.req.header("${key}"),`);
|
|
1150
|
+
lines.push(` const headers = ${operationName}Headers.parse({`);
|
|
1151
|
+
lines.push(...headerParts);
|
|
1152
|
+
lines.push(` })`);
|
|
1153
|
+
requestArgs.push("headers");
|
|
1154
|
+
}
|
|
1155
|
+
if (hasBody) {
|
|
1156
|
+
const schemaName = camelCase(operation.requestModel.id) + "Schema";
|
|
1157
|
+
lines.push(` const body = ${schemaName}.parse(await context.req.json())`);
|
|
1158
|
+
requestArgs.push("body");
|
|
1159
|
+
}
|
|
1160
|
+
if (requestArgs.length > 0) lines.push(` const result = await handler({ ${requestArgs.join(", ")} })`);
|
|
1161
|
+
else lines.push(` const result = await handler()`);
|
|
1162
|
+
lines.push(` switch (result.status) {`);
|
|
1163
|
+
for (const [status, responseModel] of Object.entries(operation.responses)) {
|
|
1164
|
+
const kind = operation.responseKinds[Number(status)] ?? "json-response";
|
|
1165
|
+
if (responseModel != null) if (kind === "json-response") lines.push(` case ${status}: return new Response(JSON.stringify(result.body), { status: ${status}, headers: { "Content-Type": "application/json" } })`);
|
|
1166
|
+
else if (kind === "binary") lines.push(` case ${status}: return new Response(result.body, { status: ${status}, headers: { "Content-Type": "${contentTypeForKind(kind)}" } })`);
|
|
1167
|
+
else lines.push(` case ${status}: return new Response(result.stream, { status: ${status}, headers: { "Content-Type": "${contentTypeForKind(kind)}" } })`);
|
|
1168
|
+
else if (kind === "binary") lines.push(` case ${status}: return new Response(result.body, { status: ${status}, headers: { "Content-Type": "${contentTypeForKind(kind)}" } })`);
|
|
1169
|
+
else lines.push(` case ${status}: return new Response(null, { status: ${status} })`);
|
|
1170
|
+
}
|
|
1171
|
+
lines.push(` default: return new Response(JSON.stringify({ message: \`Unexpected response status \${(result as { status: number }).status}\` }), { status: 500, headers: { "Content-Type": "application/json" } })`);
|
|
1172
|
+
lines.push(` }`);
|
|
1173
|
+
lines.push(` },`);
|
|
1174
|
+
lines.push(` }`);
|
|
1175
|
+
lines.push(`}`);
|
|
1176
|
+
return lines.join("\n");
|
|
1177
|
+
}
|
|
1178
|
+
function generateIndex(operations, _identifier) {
|
|
1179
|
+
const lines = [];
|
|
1180
|
+
lines.push(`import { Hono } from "hono"`);
|
|
1181
|
+
lines.push("");
|
|
1182
|
+
for (const operation of operations) {
|
|
1183
|
+
const operationName = camelCase(operation.id);
|
|
1184
|
+
const OperationName = pascalCase(operation.id);
|
|
1185
|
+
lines.push(`import { ${operationName}, type ${OperationName}Handler, type ${OperationName}Request, type ${OperationName}Response } from "./${operationName}"`);
|
|
1186
|
+
lines.push(`export type { ${OperationName}Handler, ${OperationName}Request, ${OperationName}Response }`);
|
|
1187
|
+
}
|
|
1188
|
+
lines.push("");
|
|
1189
|
+
const groups = groupBy$1(operations, (operation) => operation.group);
|
|
1190
|
+
lines.push(`export function mountRoutes(`);
|
|
1191
|
+
lines.push(` app: Hono,`);
|
|
1192
|
+
lines.push(` handlers: {`);
|
|
1193
|
+
for (const [group, groupOps] of Object.entries(groups)) {
|
|
1194
|
+
lines.push(` ${camelCase(group)}: {`);
|
|
1195
|
+
for (const operation of groupOps) {
|
|
1196
|
+
const operationName = camelCase(operation.id);
|
|
1197
|
+
lines.push(` ${operationName}: ${pascalCase(operation.id)}Handler,`);
|
|
1198
|
+
}
|
|
1199
|
+
lines.push(` },`);
|
|
1200
|
+
}
|
|
1201
|
+
lines.push(` },`);
|
|
1202
|
+
lines.push(`) {`);
|
|
1203
|
+
for (const [group, groupOps] of Object.entries(groups)) {
|
|
1204
|
+
lines.push(` // ── ${group} ──`);
|
|
1205
|
+
for (const operation of groupOps) {
|
|
1206
|
+
const operationName = camelCase(operation.id);
|
|
1207
|
+
lines.push(` const ${operationName}Def = ${operationName}(handlers.${camelCase(group)}.${operationName})`);
|
|
1208
|
+
lines.push(` app.on(${operationName}Def.method, ${operationName}Def.path, ${operationName}Def.handler)`);
|
|
1209
|
+
}
|
|
1210
|
+
lines.push("");
|
|
1211
|
+
}
|
|
1212
|
+
lines.push(`}`);
|
|
1213
|
+
lines.push("");
|
|
1214
|
+
return lines.join("\n");
|
|
1215
|
+
}
|
|
1216
|
+
function generateConfig(config) {
|
|
1217
|
+
const root = collectLevel(config.properties, config.required, "");
|
|
1218
|
+
const out = [];
|
|
1219
|
+
out.push(`import { createEnv } from "@t3-oss/env-core"`);
|
|
1220
|
+
out.push(`import { z } from "zod"`);
|
|
1221
|
+
out.push("");
|
|
1222
|
+
out.push(`const _env = createEnv({`);
|
|
1223
|
+
out.push(` server: {`);
|
|
1224
|
+
for (const v of root.envVars) out.push(` ${v.envName}: ${v.zodExpr},`);
|
|
1225
|
+
out.push(` },`);
|
|
1226
|
+
out.push(` runtimeEnv: process.env,`);
|
|
1227
|
+
out.push(` emptyStringAsUndefined: true,`);
|
|
1228
|
+
out.push(`})`);
|
|
1229
|
+
out.push("");
|
|
1230
|
+
for (const sw of root.switches) emitSwitch(sw, out);
|
|
1231
|
+
out.push(`export function get${pascalCase(config.id)}() {`);
|
|
1232
|
+
out.push(` return {`);
|
|
1233
|
+
for (const f of root.fields) out.push(` ${f.name}: ${emitFieldExpr(f, "_env")},`);
|
|
1234
|
+
out.push(` }`);
|
|
1235
|
+
out.push(`}`);
|
|
1236
|
+
out.push("");
|
|
1237
|
+
return out.join("\n");
|
|
1238
|
+
}
|
|
1239
|
+
function emitSwitch(sw, out) {
|
|
1240
|
+
for (const v of sw.variants) {
|
|
1241
|
+
for (const nestedSw of v.switches) emitSwitch(nestedSw, out);
|
|
1242
|
+
out.push(`function ${v.resolveFnName}() {`);
|
|
1243
|
+
out.push(` const env = createEnv({`);
|
|
1244
|
+
out.push(` server: {`);
|
|
1245
|
+
for (const ev of v.envVars) out.push(` ${ev.envName}: ${ev.zodExpr},`);
|
|
1246
|
+
out.push(` },`);
|
|
1247
|
+
out.push(` runtimeEnv: process.env,`);
|
|
1248
|
+
out.push(` emptyStringAsUndefined: true,`);
|
|
1249
|
+
out.push(` })`);
|
|
1250
|
+
out.push("");
|
|
1251
|
+
out.push(` return {`);
|
|
1252
|
+
for (const f of v.fields) out.push(` ${f.name}: ${emitFieldExpr(f, "env")},`);
|
|
1253
|
+
out.push(` }`);
|
|
1254
|
+
out.push(`}`);
|
|
1255
|
+
out.push("");
|
|
1256
|
+
}
|
|
1257
|
+
out.push(`function ${sw.resolveFnName}() {`);
|
|
1258
|
+
out.push(` switch (_env.${sw.dvEnvName}) {`);
|
|
1259
|
+
for (const v of sw.variants) if (sw.payloadKey != null) out.push(` case "${v.varName}": return { ${sw.variantKey}: "${v.varName}" as const, ${sw.payloadKey}: ${v.resolveFnName}() }`);
|
|
1260
|
+
else out.push(` case "${v.varName}": return { type: "${v.varName}" as const, ...${v.resolveFnName}() }`);
|
|
1261
|
+
out.push(` }`);
|
|
1262
|
+
out.push(`}`);
|
|
1263
|
+
out.push("");
|
|
1264
|
+
}
|
|
1265
|
+
function emitFieldExpr(field, envRef) {
|
|
1266
|
+
switch (field.kind) {
|
|
1267
|
+
case "env": return `${envRef}.${field.envName}`;
|
|
1268
|
+
case "record": return `{ ${field.childFields.map((f) => `${f.name}: ${emitFieldExpr(f, envRef)}`).join(", ")} }`;
|
|
1269
|
+
case "switch": return `${field.switchFnName}()`;
|
|
784
1270
|
}
|
|
785
1271
|
}
|
|
786
|
-
function
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
case "
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
1272
|
+
function collectLevel(properties, required, prefix) {
|
|
1273
|
+
const envVars = [];
|
|
1274
|
+
const fields = [];
|
|
1275
|
+
const switches = [];
|
|
1276
|
+
for (const [propName, model] of Object.entries(properties)) {
|
|
1277
|
+
const envPrefix = prefix ? `${prefix}_${snakeCase(propName).toUpperCase()}` : snakeCase(propName).toUpperCase();
|
|
1278
|
+
switch (model.kind) {
|
|
1279
|
+
case "int32":
|
|
1280
|
+
case "float32":
|
|
1281
|
+
case "float64":
|
|
1282
|
+
case "boolean":
|
|
1283
|
+
case "string":
|
|
1284
|
+
case "datetime":
|
|
1285
|
+
case "date":
|
|
1286
|
+
case "duration":
|
|
1287
|
+
case "literal":
|
|
1288
|
+
case "null":
|
|
1289
|
+
envVars.push({
|
|
1290
|
+
envName: envPrefix,
|
|
1291
|
+
zodExpr: toZodEnv(model)
|
|
1292
|
+
});
|
|
1293
|
+
fields.push({
|
|
1294
|
+
name: propName,
|
|
1295
|
+
kind: "env",
|
|
1296
|
+
envName: envPrefix
|
|
1297
|
+
});
|
|
1298
|
+
break;
|
|
1299
|
+
case "enums":
|
|
1300
|
+
envVars.push({
|
|
1301
|
+
envName: envPrefix,
|
|
1302
|
+
zodExpr: toZodEnv(model)
|
|
1303
|
+
});
|
|
1304
|
+
fields.push({
|
|
1305
|
+
name: propName,
|
|
1306
|
+
kind: "env",
|
|
1307
|
+
envName: envPrefix
|
|
1308
|
+
});
|
|
1309
|
+
break;
|
|
1310
|
+
case "record": {
|
|
1311
|
+
const rec = model;
|
|
1312
|
+
const child = collectLevel(rec.properties, rec.required, envPrefix);
|
|
1313
|
+
envVars.push(...child.envVars);
|
|
1314
|
+
switches.push(...child.switches);
|
|
1315
|
+
fields.push({
|
|
1316
|
+
name: propName,
|
|
1317
|
+
kind: "record",
|
|
1318
|
+
childFields: child.fields
|
|
1319
|
+
});
|
|
1320
|
+
break;
|
|
1321
|
+
}
|
|
1322
|
+
case "taggedUnion": {
|
|
1323
|
+
const dvZod = `z.enum(${JSON.stringify(Object.keys(model.variants))})`;
|
|
1324
|
+
envVars.push({
|
|
1325
|
+
envName: envPrefix,
|
|
1326
|
+
zodExpr: dvZod
|
|
1327
|
+
});
|
|
1328
|
+
const variantKey = model.variantKey;
|
|
1329
|
+
const payloadKey = model.payloadKey;
|
|
1330
|
+
const resolveFnName = `_resolve${pascalCase(propName)}`;
|
|
1331
|
+
const variants = [];
|
|
1332
|
+
for (const [vKey, vModel] of Object.entries(model.variants)) {
|
|
1333
|
+
const vRec = vModel;
|
|
1334
|
+
const child = collectLevel(vRec.properties, vRec.required, envPrefix);
|
|
1335
|
+
variants.push({
|
|
1336
|
+
varName: vKey,
|
|
1337
|
+
resolveFnName: `${resolveFnName}${pascalCase(vKey)}`,
|
|
1338
|
+
envVars: child.envVars,
|
|
1339
|
+
fields: child.fields,
|
|
1340
|
+
switches: child.switches
|
|
1341
|
+
});
|
|
1342
|
+
}
|
|
1343
|
+
switches.push({
|
|
1344
|
+
resolveFnName,
|
|
1345
|
+
dvEnvName: envPrefix,
|
|
1346
|
+
dvZodExpr: dvZod,
|
|
1347
|
+
variantKey,
|
|
1348
|
+
payloadKey,
|
|
1349
|
+
variants
|
|
1350
|
+
});
|
|
1351
|
+
fields.push({
|
|
1352
|
+
name: propName,
|
|
1353
|
+
kind: "switch",
|
|
1354
|
+
switchFnName: resolveFnName
|
|
1355
|
+
});
|
|
1356
|
+
break;
|
|
1357
|
+
}
|
|
1358
|
+
case "union": {
|
|
1359
|
+
const dvEnvName = `${envPrefix}_TYPE`;
|
|
1360
|
+
const dvZod = `z.enum(${JSON.stringify(Object.keys(model.variants))})`;
|
|
1361
|
+
envVars.push({
|
|
1362
|
+
envName: dvEnvName,
|
|
1363
|
+
zodExpr: dvZod
|
|
1364
|
+
});
|
|
1365
|
+
const resolveFnName = `_resolve${pascalCase(propName)}`;
|
|
1366
|
+
const variants = [];
|
|
1367
|
+
for (const [vKey, vModel] of Object.entries(model.variants)) {
|
|
1368
|
+
const child = collectVariant(vModel, envPrefix);
|
|
1369
|
+
variants.push({
|
|
1370
|
+
varName: vKey,
|
|
1371
|
+
resolveFnName: `${resolveFnName}${pascalCase(vKey)}`,
|
|
1372
|
+
envVars: child.envVars,
|
|
1373
|
+
fields: child.fields,
|
|
1374
|
+
switches: child.switches
|
|
1375
|
+
});
|
|
1376
|
+
}
|
|
1377
|
+
switches.push({
|
|
1378
|
+
resolveFnName,
|
|
1379
|
+
dvEnvName,
|
|
1380
|
+
dvZodExpr: dvZod,
|
|
1381
|
+
variantKey: "type",
|
|
1382
|
+
payloadKey: null,
|
|
1383
|
+
variants
|
|
1384
|
+
});
|
|
1385
|
+
fields.push({
|
|
1386
|
+
name: propName,
|
|
1387
|
+
kind: "switch",
|
|
1388
|
+
switchFnName: resolveFnName
|
|
1389
|
+
});
|
|
1390
|
+
break;
|
|
1391
|
+
}
|
|
1392
|
+
case "array":
|
|
1393
|
+
case "set": {
|
|
1394
|
+
const base = model.base;
|
|
1395
|
+
if (!isSimpleType(base)) throw new Error(`unsupported configuration value of kind ${model.kind}<non-simple>, only simple element types are allowed`);
|
|
1396
|
+
envVars.push({
|
|
1397
|
+
envName: envPrefix,
|
|
1398
|
+
zodExpr: toZodEnv(model)
|
|
1399
|
+
});
|
|
1400
|
+
fields.push({
|
|
1401
|
+
name: propName,
|
|
1402
|
+
kind: "env",
|
|
1403
|
+
envName: envPrefix
|
|
1404
|
+
});
|
|
1405
|
+
break;
|
|
1406
|
+
}
|
|
1407
|
+
case "map": throw new Error("unsupported configuration value of kind map");
|
|
1408
|
+
default:
|
|
1409
|
+
envVars.push({
|
|
1410
|
+
envName: envPrefix,
|
|
1411
|
+
zodExpr: "z.string()"
|
|
1412
|
+
});
|
|
1413
|
+
fields.push({
|
|
1414
|
+
name: propName,
|
|
1415
|
+
kind: "env",
|
|
1416
|
+
envName: envPrefix
|
|
1417
|
+
});
|
|
806
1418
|
}
|
|
807
|
-
default: return model.id ? toPascalCase(model.id) : "()";
|
|
808
1419
|
}
|
|
1420
|
+
return {
|
|
1421
|
+
envVars,
|
|
1422
|
+
fields,
|
|
1423
|
+
switches
|
|
1424
|
+
};
|
|
809
1425
|
}
|
|
810
|
-
function
|
|
811
|
-
|
|
1426
|
+
function collectVariant(model, prefix) {
|
|
1427
|
+
if (model.kind === "record") {
|
|
1428
|
+
const rec = model;
|
|
1429
|
+
return collectLevel(rec.properties, rec.required, prefix);
|
|
1430
|
+
}
|
|
1431
|
+
const envName = prefix;
|
|
1432
|
+
return {
|
|
1433
|
+
envVars: [{
|
|
1434
|
+
envName,
|
|
1435
|
+
zodExpr: toZodEnv(model)
|
|
1436
|
+
}],
|
|
1437
|
+
fields: [{
|
|
1438
|
+
name: "value",
|
|
1439
|
+
kind: "env",
|
|
1440
|
+
envName
|
|
1441
|
+
}],
|
|
1442
|
+
switches: []
|
|
1443
|
+
};
|
|
812
1444
|
}
|
|
813
|
-
function
|
|
814
|
-
return
|
|
1445
|
+
function isSimpleType(model) {
|
|
1446
|
+
return [
|
|
1447
|
+
"int32",
|
|
1448
|
+
"float32",
|
|
1449
|
+
"float64",
|
|
1450
|
+
"boolean",
|
|
1451
|
+
"string",
|
|
1452
|
+
"datetime",
|
|
1453
|
+
"date",
|
|
1454
|
+
"duration",
|
|
1455
|
+
"literal",
|
|
1456
|
+
"enums"
|
|
1457
|
+
].includes(model.kind);
|
|
1458
|
+
}
|
|
1459
|
+
function toZodEnv(model) {
|
|
1460
|
+
switch (model.kind) {
|
|
1461
|
+
case "int32": return "z.coerce.number().int()";
|
|
1462
|
+
case "float32":
|
|
1463
|
+
case "float64": return "z.coerce.number()";
|
|
1464
|
+
case "boolean": return "z.coerce.boolean()";
|
|
1465
|
+
case "string": return "z.string()";
|
|
1466
|
+
case "datetime": return "z.string().datetime()";
|
|
1467
|
+
case "date": return "z.string().date()";
|
|
1468
|
+
case "duration": return "z.string()";
|
|
1469
|
+
case "literal": return `z.literal(${JSON.stringify(model.value)})`;
|
|
1470
|
+
case "null": return "z.null()";
|
|
1471
|
+
case "enums": return `z.enum(${JSON.stringify(Object.values(model.variants))})`;
|
|
1472
|
+
case "array":
|
|
1473
|
+
if (!isSimpleType(model.base)) throw new Error("unsupported configuration value of kind array<non-simple>, only simple element types are allowed");
|
|
1474
|
+
return `z.coerce.string().transform(s => s.split(',').filter(Boolean)).pipe(z.array(${toZodEnv(model.base)}))`;
|
|
1475
|
+
case "set":
|
|
1476
|
+
if (!isSimpleType(model.base)) throw new Error("unsupported configuration value of kind set<non-simple>, only simple element types are allowed");
|
|
1477
|
+
return `z.coerce.string().transform(s => new Set(s.split(',').filter(Boolean))).pipe(z.set(${toZodEnv(model.base)}))`;
|
|
1478
|
+
case "map": throw new Error("unsupported configuration value of kind map");
|
|
1479
|
+
default: return "z.string()";
|
|
1480
|
+
}
|
|
815
1481
|
}
|
|
816
|
-
function
|
|
817
|
-
|
|
1482
|
+
function groupBy$1(items, keyFn) {
|
|
1483
|
+
const map = {};
|
|
1484
|
+
for (const item of items) {
|
|
1485
|
+
const key = keyFn(item);
|
|
1486
|
+
if (!map[key]) map[key] = [];
|
|
1487
|
+
map[key].push(item);
|
|
1488
|
+
}
|
|
1489
|
+
return map;
|
|
818
1490
|
}
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
return await format(code, {
|
|
822
|
-
parser: "jinx-rust",
|
|
823
|
-
plugins: [PrettierRustPlugin]
|
|
824
|
-
});
|
|
825
|
-
} catch (error) {
|
|
826
|
-
console.error(error);
|
|
827
|
-
return code;
|
|
828
|
-
}
|
|
829
|
-
}
|
|
830
|
-
async function generateRustCodes(options) {
|
|
831
|
-
const { module_name, srcDir, routes = [], models = [] } = options;
|
|
832
|
-
const codes = /* @__PURE__ */ new Map();
|
|
833
|
-
const namedModels = collectModelFromRoutes(routes);
|
|
834
|
-
for (const model of models) for (const [id, subModel] of collectModelDeep(model)) namedModels.set(id, subModel);
|
|
835
|
-
for (const [id, model] of namedModels) {
|
|
836
|
-
if (model.kind !== "union" && model.kind !== "record" && model.kind !== "enums" && model.kind !== "error") continue;
|
|
837
|
-
const fullname = module_name + "::" + toSnakeCase(id);
|
|
838
|
-
const path = resolve(srcDir, ...fullname.split("::"), ".rs");
|
|
839
|
-
const code = await formatRust(generateRustCode({
|
|
840
|
-
module_name: fullname,
|
|
841
|
-
model
|
|
842
|
-
}));
|
|
843
|
-
codes.set(path, code);
|
|
844
|
-
}
|
|
845
|
-
return codes.entries().map(([path, code]) => ({
|
|
846
|
-
path,
|
|
847
|
-
code
|
|
848
|
-
})).toArray();
|
|
1491
|
+
function toHonoPath(path) {
|
|
1492
|
+
return path.replace(/\{(\w+)\}/g, ":$1");
|
|
849
1493
|
}
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
1494
|
+
function contentTypeForKind(kind) {
|
|
1495
|
+
switch (kind) {
|
|
1496
|
+
case "binary": return "application/octet-stream";
|
|
1497
|
+
case "stream-response": return "application/x-ndjson";
|
|
1498
|
+
case "sse-response": return "text/event-stream";
|
|
1499
|
+
default: return "application/json";
|
|
1500
|
+
}
|
|
854
1501
|
}
|
|
855
|
-
function
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
return
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
return
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
return
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
return
|
|
875
|
-
|
|
876
|
-
return
|
|
1502
|
+
function toZod$1(model, schemaMap) {
|
|
1503
|
+
switch (model.kind) {
|
|
1504
|
+
case "int32": return "z.coerce.number().int()";
|
|
1505
|
+
case "float32":
|
|
1506
|
+
case "float64": return "z.coerce.number()";
|
|
1507
|
+
case "boolean": return "z.coerce.boolean()";
|
|
1508
|
+
case "string": return "z.string()";
|
|
1509
|
+
case "datetime": return "z.string().datetime()";
|
|
1510
|
+
case "date": return "z.string().date()";
|
|
1511
|
+
case "duration": return "z.string()";
|
|
1512
|
+
case "literal": return `z.literal(${JSON.stringify(model.value)})`;
|
|
1513
|
+
case "null": return "z.null()";
|
|
1514
|
+
case "array": return `${toZod$1(model.base, schemaMap)}.array()`;
|
|
1515
|
+
case "set": return `${toZod$1(model.base, schemaMap)}.array()`;
|
|
1516
|
+
case "map": return `z.record(z.string(), ${toZod$1(model.base, schemaMap)})`;
|
|
1517
|
+
case "enums": return `z.enum(${JSON.stringify(Object.values(model.variants))})`;
|
|
1518
|
+
case "record": return schemaMap.get(model.id)?.fields ? camelCase(model.id) + "Schema" : "z.unknown()";
|
|
1519
|
+
case "union":
|
|
1520
|
+
if (schemaMap.get(model.id)?.unionVariants) return camelCase(model.id) + "Schema";
|
|
1521
|
+
return `z.union([${Object.values(model.variants).map((v) => toZod$1(v, schemaMap)).join(", ")}])`;
|
|
1522
|
+
case "taggedUnion": {
|
|
1523
|
+
if (schemaMap.get(model.id)?.unionVariants) return camelCase(model.id) + "Schema";
|
|
1524
|
+
const unionItems = Object.entries(model.variants).map(([key, v]) => `z.object({ ${JSON.stringify(model.variantKey)}: z.literal(${JSON.stringify(key)}), ${JSON.stringify(model.payloadKey)}: ${toZod$1(v, schemaMap)} })`);
|
|
1525
|
+
return `z.discriminatedUnion(${JSON.stringify(model.variantKey)}, [${unionItems.join(", ")}])`;
|
|
877
1526
|
}
|
|
878
|
-
return
|
|
1527
|
+
default: return "z.unknown()";
|
|
879
1528
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
return
|
|
1529
|
+
}
|
|
1530
|
+
function toTs$1(model, schemaMap, identifier, namespace) {
|
|
1531
|
+
switch (model.kind) {
|
|
1532
|
+
case "int32":
|
|
1533
|
+
case "float32":
|
|
1534
|
+
case "float64": return "number";
|
|
1535
|
+
case "boolean": return "boolean";
|
|
1536
|
+
case "string":
|
|
1537
|
+
case "datetime":
|
|
1538
|
+
case "date":
|
|
1539
|
+
case "duration": return "string";
|
|
1540
|
+
case "literal": return JSON.stringify(model.value);
|
|
1541
|
+
case "null": return "null";
|
|
1542
|
+
case "array":
|
|
1543
|
+
case "set": return `${toTs$1(model.base, schemaMap, identifier, namespace)}[]`;
|
|
1544
|
+
case "map": return `Record<string, ${toTs$1(model.base, schemaMap, identifier, namespace)}>`;
|
|
1545
|
+
case "enums":
|
|
1546
|
+
if (schemaMap.get(model.id)?.variants) return identifier(model.id);
|
|
1547
|
+
return Object.values(model.variants).map((v) => JSON.stringify(v)).join(" | ");
|
|
1548
|
+
case "record": return schemaMap.get(model.id)?.fields ? identifier(model.id) : "unknown";
|
|
1549
|
+
case "union":
|
|
1550
|
+
case "taggedUnion":
|
|
1551
|
+
if (schemaMap.get(model.id)?.unionVariants) return identifier(model.id);
|
|
1552
|
+
return Object.values(model.variants).map((v) => toTs$1(v, schemaMap, identifier, namespace)).join(" | ");
|
|
1553
|
+
default: return "unknown";
|
|
887
1554
|
}
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1555
|
+
}
|
|
1556
|
+
//#endregion
|
|
1557
|
+
//#region src/codegen/ts-client.ts
|
|
1558
|
+
function generateTsClient(options) {
|
|
1559
|
+
const { routers, identifier = pascalCase, namespace } = options;
|
|
1560
|
+
const operations = collectOperations(routers);
|
|
1561
|
+
const schemaMap = collectSchemaMap(operations);
|
|
1562
|
+
const files = {};
|
|
1563
|
+
files["models.ts"] = generateModels(schemaMap, identifier, namespace);
|
|
1564
|
+
for (const operation of operations) files[`${camelCase(operation.id)}.ts`] = generateClientFn(operation, schemaMap, identifier, namespace);
|
|
1565
|
+
files["index.ts"] = generateClientIndex(operations, identifier);
|
|
1566
|
+
return files;
|
|
1567
|
+
}
|
|
1568
|
+
function generateModels(schemaMap, identifier, namespace) {
|
|
1569
|
+
const lines = [];
|
|
1570
|
+
lines.push(`import { z } from "zod"`);
|
|
1571
|
+
lines.push("");
|
|
1572
|
+
for (const [id, schemaInfo] of schemaMap) {
|
|
1573
|
+
const schemaName = camelCase(id) + "Schema";
|
|
1574
|
+
const tsName = identifier(id);
|
|
1575
|
+
switch (schemaInfo.kind) {
|
|
1576
|
+
case "record":
|
|
1577
|
+
lines.push(`export const ${schemaName} = z.object({`);
|
|
1578
|
+
for (const f of schemaInfo.fields) lines.push(` ${f.name}: ${toZod(f.model, schemaMap)}${f.required ? "" : ".optional()"},`);
|
|
1579
|
+
lines.push(`})`);
|
|
1580
|
+
lines.push("");
|
|
1581
|
+
lines.push(`export interface ${tsName} {`);
|
|
1582
|
+
for (const f of schemaInfo.fields) lines.push(` ${f.name}${f.required ? "" : "?"}: ${toTs(f.model, schemaMap, identifier, namespace)};`);
|
|
1583
|
+
lines.push(`}`);
|
|
1584
|
+
lines.push("");
|
|
1585
|
+
break;
|
|
1586
|
+
case "enums":
|
|
1587
|
+
lines.push(`export const ${schemaName} = z.enum(${JSON.stringify(Object.values(schemaInfo.variants))})`);
|
|
1588
|
+
lines.push(`export type ${tsName} = z.infer<typeof ${schemaName}>`);
|
|
1589
|
+
lines.push("");
|
|
1590
|
+
break;
|
|
1591
|
+
case "union": {
|
|
1592
|
+
const items = Object.entries(schemaInfo.unionVariants).map(([, v]) => toZod(v, schemaMap)).join(", ");
|
|
1593
|
+
lines.push(`export const ${schemaName} = z.union([${items}])`);
|
|
1594
|
+
lines.push(`export type ${tsName} = z.infer<typeof ${schemaName}>`);
|
|
1595
|
+
lines.push("");
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
case "taggedUnion": {
|
|
1599
|
+
const vk = schemaInfo.variantKey;
|
|
1600
|
+
const pk = schemaInfo.payloadKey;
|
|
1601
|
+
const items = Object.entries(schemaInfo.unionVariants).map(([key, v]) => `z.object({ ${JSON.stringify(vk)}: z.literal(${JSON.stringify(key)}), ${JSON.stringify(pk)}: ${toZod(v, schemaMap)} })`);
|
|
1602
|
+
lines.push(`export const ${schemaName} = z.discriminatedUnion(${JSON.stringify(vk)}, [${items.join(", ")}])`);
|
|
1603
|
+
lines.push(`export type ${tsName} = z.infer<typeof ${schemaName}>`);
|
|
1604
|
+
lines.push("");
|
|
1605
|
+
break;
|
|
916
1606
|
}
|
|
917
|
-
return success(properties);
|
|
918
1607
|
}
|
|
919
|
-
return deserializeFailed(model, value);
|
|
920
|
-
}
|
|
921
|
-
if (model.kind === "enums") {
|
|
922
|
-
if (typeof value === "string" && Object.values(model.variants).includes(value)) return success(value);
|
|
923
|
-
return deserializeFailed(model, value);
|
|
924
1608
|
}
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
1609
|
+
return lines.join("\n");
|
|
1610
|
+
}
|
|
1611
|
+
function generateClientFn(operation, schemaMap, identifier, namespace) {
|
|
1612
|
+
const lines = [];
|
|
1613
|
+
const functionName = camelCase(operation.id);
|
|
1614
|
+
const OperationName = pascalCase(operation.id);
|
|
1615
|
+
const hasBody = operation.requestModel != null && operation.requestModel.kind !== "null";
|
|
1616
|
+
const hasParams = Object.keys(operation.pathVariables).length > 0;
|
|
1617
|
+
const hasQuery = Object.keys(operation.queries).length > 0;
|
|
1618
|
+
const hasHeaders = Object.keys(operation.headers).length > 0;
|
|
1619
|
+
const typeImports = [];
|
|
1620
|
+
const addNamedRef = (model) => {
|
|
1621
|
+
const root = resolveNamedRoot(model);
|
|
1622
|
+
if (root) {
|
|
1623
|
+
const typeName = identifier(root.id);
|
|
1624
|
+
if (schemaMap.has(root.id) && !typeImports.includes(typeName)) typeImports.push(typeName);
|
|
934
1625
|
}
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1626
|
+
};
|
|
1627
|
+
if (hasBody) addNamedRef(operation.requestModel);
|
|
1628
|
+
for (const responseModel of Object.values(operation.responses)) if (responseModel != null) addNamedRef(responseModel);
|
|
1629
|
+
const okStatus = Object.keys(operation.responses).find((s) => Number(s) >= 200 && Number(s) < 300);
|
|
1630
|
+
const okModel = okStatus ? operation.responses[Number(okStatus)] : null;
|
|
1631
|
+
const okKind = okStatus ? operation.responseKinds[Number(okStatus)] ?? "json-response" : "json-response";
|
|
1632
|
+
const isStreamLike = okKind === "stream-response" || okKind === "sse-response" || okKind === "binary";
|
|
1633
|
+
const okSchema = okModel ? resolveZodSchema(okModel, schemaMap) : null;
|
|
1634
|
+
const allModelImports = [...typeImports];
|
|
1635
|
+
if (okSchema && !isStreamLike) {
|
|
1636
|
+
const schemaRefs = collectSchemaRefs(okModel, schemaMap);
|
|
1637
|
+
for (const ref of schemaRefs) if (!allModelImports.includes(ref)) allModelImports.push(ref);
|
|
1638
|
+
}
|
|
1639
|
+
if (allModelImports.length > 0) {
|
|
1640
|
+
lines.push(`import { ${allModelImports.join(", ")} } from "./models"`);
|
|
1641
|
+
lines.push("");
|
|
1642
|
+
}
|
|
1643
|
+
const requestFields = [];
|
|
1644
|
+
for (const [n, v] of Object.entries(operation.pathVariables)) requestFields.push(`${n}: ${toTs(v.model, schemaMap, identifier, namespace)}`);
|
|
1645
|
+
for (const [n, q] of Object.entries(operation.queries)) {
|
|
1646
|
+
const required = q.required ? "" : "?";
|
|
1647
|
+
requestFields.push(`${n}${required}: ${toTs(q.model, schemaMap, identifier, namespace)}`);
|
|
1648
|
+
}
|
|
1649
|
+
if (hasBody) requestFields.push(`body: ${toTs(operation.requestModel, schemaMap, identifier, namespace)}`);
|
|
1650
|
+
if (hasHeaders) {
|
|
1651
|
+
const field = Object.entries(operation.headers).map(([key, header]) => `"${key}"${header.required ? "" : "?"}: ${toTs(header.model, schemaMap, identifier, namespace)}`).join("; ");
|
|
1652
|
+
requestFields.push(`headers?: { ${field} } & Record<string, string>`);
|
|
1653
|
+
} else requestFields.push("headers?: Record<string, string>");
|
|
1654
|
+
requestFields.push("baseUrl?: string");
|
|
1655
|
+
lines.push(`export interface ${OperationName}Request {`);
|
|
1656
|
+
for (const field of requestFields) lines.push(` ${field}`);
|
|
1657
|
+
lines.push(`}`);
|
|
1658
|
+
lines.push("");
|
|
1659
|
+
const responseEntries = Object.entries(operation.responses);
|
|
1660
|
+
const responseKind = (status) => operation.responseKinds[Number(status)] ?? "json-response";
|
|
1661
|
+
const responseBodyField = (kind, responseModel) => {
|
|
1662
|
+
if (responseModel == null) {
|
|
1663
|
+
if (kind === "binary") return "body: Blob";
|
|
1664
|
+
return null;
|
|
944
1665
|
}
|
|
945
|
-
return
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
1666
|
+
if (kind === "stream-response" || kind === "sse-response") return `stream: ReadableStream<${toTs(responseModel, schemaMap, identifier, namespace)}>`;
|
|
1667
|
+
if (kind === "binary") return "body: Blob";
|
|
1668
|
+
return `body: ${toTs(responseModel, schemaMap, identifier, namespace)}`;
|
|
1669
|
+
};
|
|
1670
|
+
if (responseEntries.length === 1) {
|
|
1671
|
+
const [status, responseModel] = responseEntries[0];
|
|
1672
|
+
const bodyField = responseBodyField(responseKind(status), responseModel);
|
|
1673
|
+
if (bodyField != null) lines.push(`export type ${OperationName}Response = { status: ${status}; ${bodyField} }`);
|
|
1674
|
+
else lines.push(`export type ${OperationName}Response = { status: ${status} }`);
|
|
1675
|
+
} else {
|
|
1676
|
+
lines.push(`export type ${OperationName}Response =`);
|
|
1677
|
+
for (const [status, responseModel] of responseEntries) {
|
|
1678
|
+
const bodyField = responseBodyField(responseKind(status), responseModel);
|
|
1679
|
+
if (bodyField != null) lines.push(` | { status: ${status}; ${bodyField} }`);
|
|
1680
|
+
else lines.push(` | { status: ${status} }`);
|
|
957
1681
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
1682
|
+
}
|
|
1683
|
+
lines.push("");
|
|
1684
|
+
const reqParam = hasParams || hasBody ? `req: ${OperationName}Request` : `req?: ${OperationName}Request`;
|
|
1685
|
+
const retType = okModel && !isStreamLike ? toTs(okModel, schemaMap, identifier, namespace) : "Response";
|
|
1686
|
+
const pathExpr = operation.path.replace(/\{(\w+)\}/g, (_, name) => `\${encodeURIComponent(req.${name})}`);
|
|
1687
|
+
lines.push(`export async function ${functionName}(${reqParam}): Promise<${retType}> {`);
|
|
1688
|
+
lines.push(` const baseUrl = req?.baseUrl ?? ""`);
|
|
1689
|
+
if (hasQuery) {
|
|
1690
|
+
lines.push(` const parts: string[] = []`);
|
|
1691
|
+
for (const [n, q] of Object.entries(operation.queries)) if (q.required) lines.push(` parts.push("${n}=" + encodeURIComponent(req.${n}))`);
|
|
1692
|
+
else lines.push(` if (req?.${n} != null) parts.push("${n}=" + encodeURIComponent(req.${n}))`);
|
|
1693
|
+
lines.push(` const qs = parts.length > 0 ? "?" + parts.join("&") : ""`);
|
|
1694
|
+
lines.push(` const url = \`\${baseUrl}${pathExpr}\${qs}\``);
|
|
1695
|
+
} else lines.push(` const url = \`\${baseUrl}${pathExpr}\``);
|
|
1696
|
+
lines.push("");
|
|
1697
|
+
lines.push(` const res = await fetch(url, {`);
|
|
1698
|
+
lines.push(` method: "${operation.method}",`);
|
|
1699
|
+
if (hasBody) {
|
|
1700
|
+
lines.push(` headers: { "Content-Type": "application/json", ...req.headers },`);
|
|
1701
|
+
lines.push(` body: JSON.stringify(req.body),`);
|
|
1702
|
+
} else lines.push(` headers: req?.headers,`);
|
|
1703
|
+
lines.push(` })`);
|
|
1704
|
+
lines.push(` if (!res.ok) throw new Error(\`${operation.method} ${operation.path} failed: \${res.status}\`)`);
|
|
1705
|
+
if (okModel && !isStreamLike) if (okSchema) lines.push(` return ${okSchema}.parse(await res.json())`);
|
|
1706
|
+
else lines.push(` return res.json()`);
|
|
1707
|
+
else lines.push(` return res`);
|
|
1708
|
+
lines.push(`}`);
|
|
1709
|
+
return lines.join("\n");
|
|
1710
|
+
}
|
|
1711
|
+
function generateClientIndex(operations, _identifier) {
|
|
1712
|
+
const lines = [];
|
|
1713
|
+
const groups = groupBy(operations, (operation) => operation.group);
|
|
1714
|
+
for (const [group, groupOps] of Object.entries(groups)) {
|
|
1715
|
+
lines.push(`// ── ${group} ──`);
|
|
1716
|
+
for (const operation of groupOps) {
|
|
1717
|
+
const n = camelCase(operation.id);
|
|
1718
|
+
const OperationName = pascalCase(operation.id);
|
|
1719
|
+
lines.push(`export { ${n}, type ${OperationName}Request, type ${OperationName}Response } from "./${n}"`);
|
|
966
1720
|
}
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
1721
|
+
lines.push("");
|
|
1722
|
+
}
|
|
1723
|
+
return lines.join("\n");
|
|
1724
|
+
}
|
|
1725
|
+
function groupBy(items, keyFn) {
|
|
1726
|
+
const map = {};
|
|
1727
|
+
for (const item of items) {
|
|
1728
|
+
const key = keyFn(item);
|
|
1729
|
+
if (!map[key]) map[key] = [];
|
|
1730
|
+
map[key].push(item);
|
|
971
1731
|
}
|
|
972
|
-
return
|
|
1732
|
+
return map;
|
|
973
1733
|
}
|
|
974
|
-
function
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
1734
|
+
function toTs(model, schemaMap, identifier, namespace) {
|
|
1735
|
+
switch (model.kind) {
|
|
1736
|
+
case "int32":
|
|
1737
|
+
case "float32":
|
|
1738
|
+
case "float64": return "number";
|
|
1739
|
+
case "boolean": return "boolean";
|
|
1740
|
+
case "string":
|
|
1741
|
+
case "datetime":
|
|
1742
|
+
case "date":
|
|
1743
|
+
case "duration": return "string";
|
|
1744
|
+
case "literal": return JSON.stringify(model.value);
|
|
1745
|
+
case "null": return "null";
|
|
1746
|
+
case "array":
|
|
1747
|
+
case "set": return `${toTs(model.base, schemaMap, identifier, namespace)}[]`;
|
|
1748
|
+
case "map": return `Record<string, ${toTs(model.base, schemaMap, identifier, namespace)}>`;
|
|
1749
|
+
case "enums":
|
|
1750
|
+
if (schemaMap.get(model.id)?.variants) return identifier(model.id);
|
|
1751
|
+
return Object.values(model.variants).map((v) => JSON.stringify(v)).join(" | ");
|
|
1752
|
+
case "record": return schemaMap.get(model.id)?.fields ? identifier(model.id) : "unknown";
|
|
1753
|
+
case "union":
|
|
1754
|
+
case "taggedUnion":
|
|
1755
|
+
if (schemaMap.get(model.id)?.unionVariants) return identifier(model.id);
|
|
1756
|
+
return Object.values(model.variants).map((v) => toTs(v, schemaMap, identifier, namespace)).join(" | ");
|
|
1757
|
+
default: return "unknown";
|
|
1758
|
+
}
|
|
983
1759
|
}
|
|
984
|
-
function
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1760
|
+
function toZod(model, schemaMap) {
|
|
1761
|
+
switch (model.kind) {
|
|
1762
|
+
case "int32": return "z.coerce.number().int()";
|
|
1763
|
+
case "float32":
|
|
1764
|
+
case "float64": return "z.coerce.number()";
|
|
1765
|
+
case "boolean": return "z.coerce.boolean()";
|
|
1766
|
+
case "string": return "z.string()";
|
|
1767
|
+
case "datetime": return "z.string().datetime()";
|
|
1768
|
+
case "date": return "z.string().date()";
|
|
1769
|
+
case "duration": return "z.string()";
|
|
1770
|
+
case "literal": return `z.literal(${JSON.stringify(model.value)})`;
|
|
1771
|
+
case "null": return "z.null()";
|
|
1772
|
+
case "array": return `${toZod(model.base, schemaMap)}.array()`;
|
|
1773
|
+
case "set": return `${toZod(model.base, schemaMap)}.array()`;
|
|
1774
|
+
case "map": return `z.record(z.string(), ${toZod(model.base, schemaMap)})`;
|
|
1775
|
+
case "enums": return `z.enum(${JSON.stringify(Object.values(model.variants))})`;
|
|
1776
|
+
case "record": return schemaMap.get(model.id)?.fields ? camelCase(model.id) + "Schema" : "z.unknown()";
|
|
1777
|
+
case "union":
|
|
1778
|
+
if (schemaMap.get(model.id)?.unionVariants) return camelCase(model.id) + "Schema";
|
|
1779
|
+
return `z.union([${Object.values(model.variants).map((v) => toZod(v, schemaMap)).join(", ")}])`;
|
|
1780
|
+
case "taggedUnion": {
|
|
1781
|
+
if (schemaMap.get(model.id)?.unionVariants) return camelCase(model.id) + "Schema";
|
|
1782
|
+
const items = Object.entries(model.variants).map(([key, v]) => `z.object({ ${JSON.stringify(model.variantKey)}: z.literal(${JSON.stringify(key)}), ${JSON.stringify(model.payloadKey)}: ${toZod(v, schemaMap)} })`);
|
|
1783
|
+
return `z.discriminatedUnion(${JSON.stringify(model.variantKey)}, [${items.join(", ")}])`;
|
|
1784
|
+
}
|
|
1785
|
+
default: return "z.unknown()";
|
|
997
1786
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1787
|
+
}
|
|
1788
|
+
function resolveZodSchema(model, schemaMap) {
|
|
1789
|
+
switch (model.kind) {
|
|
1790
|
+
case "record":
|
|
1791
|
+
case "union":
|
|
1792
|
+
case "taggedUnion":
|
|
1793
|
+
case "enums": return schemaMap.get(model.id) ? camelCase(model.id) + "Schema" : null;
|
|
1794
|
+
case "array":
|
|
1795
|
+
case "set": {
|
|
1796
|
+
const inner = resolveZodSchema(model.base, schemaMap);
|
|
1797
|
+
return inner ? `${inner}.array()` : null;
|
|
1798
|
+
}
|
|
1799
|
+
case "map": {
|
|
1800
|
+
const inner = resolveZodSchema(model.base, schemaMap);
|
|
1801
|
+
return inner ? `z.record(z.string(), ${inner})` : null;
|
|
1802
|
+
}
|
|
1803
|
+
default: return null;
|
|
1002
1804
|
}
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1805
|
+
}
|
|
1806
|
+
function collectSchemaRefs(model, schemaMap) {
|
|
1807
|
+
switch (model.kind) {
|
|
1808
|
+
case "record":
|
|
1809
|
+
case "union":
|
|
1810
|
+
case "taggedUnion":
|
|
1811
|
+
case "enums": return schemaMap.get(model.id) ? [camelCase(model.id) + "Schema"] : [];
|
|
1812
|
+
case "array":
|
|
1813
|
+
case "set": return collectSchemaRefs(model.base, schemaMap);
|
|
1814
|
+
case "map": return collectSchemaRefs(model.base, schemaMap);
|
|
1815
|
+
default: return [];
|
|
1006
1816
|
}
|
|
1007
|
-
if (model.kind === "tagged-union") return JSON.stringify(value);
|
|
1008
|
-
return String(value);
|
|
1009
1817
|
}
|
|
1010
1818
|
//#endregion
|
|
1011
|
-
export { array, binary, boolean,
|
|
1819
|
+
export { apikey, array, binary, boolean, collectNamedModels, collectOperations, collectSchemaMap, createJsonSchemaRegistry, createOpenapiSchemaRegistry, date, datetime, deployOpenIdConnect, duration, enums, float32, float64, generateHonoServer, generateJsonSchema, generateOpenapi, generateTsClient, int32, json, jsonStream, literal, map, mergeJsonSchemas, nullLike, openIdConnect, record, resolveNamedRoot, route, set, sseStream, string, taggedUnion, union };
|
|
1012
1820
|
|
|
1013
1821
|
//# sourceMappingURL=index.mjs.map
|