@arrirpc/codegen-ts 0.45.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/LICENSE +9 -0
- package/README.md +11 -0
- package/dist/index.cjs +858 -0
- package/dist/index.d.cts +51 -0
- package/dist/index.d.mts +51 -0
- package/dist/index.d.ts +51 -0
- package/dist/index.mjs +835 -0
- package/package.json +33 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,835 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs';
|
|
2
|
+
import { defineClientGeneratorPlugin, pascalCase, unflattenProcedures, isRpcDefinition, isServiceDefinition, isSchemaFormProperties, isSchemaFormDiscriminator, isSchemaFormType, isSchemaFormEnum, isSchemaFormElements, isSchemaFormValues, isSchemaFormRef, camelCase } from '@arrirpc/codegen-utils';
|
|
3
|
+
import { getSchemaParsingCode, getSchemaSerializationCode } from '@arrirpc/schema';
|
|
4
|
+
import prettier from 'prettier';
|
|
5
|
+
|
|
6
|
+
const typescriptClientGenerator = defineClientGeneratorPlugin(
|
|
7
|
+
(options) => ({
|
|
8
|
+
generator: async (def) => {
|
|
9
|
+
if (!options.clientName) {
|
|
10
|
+
throw new Error("Name is requires");
|
|
11
|
+
}
|
|
12
|
+
if (!options.outputFile) {
|
|
13
|
+
throw new Error("No output file specified");
|
|
14
|
+
}
|
|
15
|
+
if (Object.keys(def.procedures).length <= 0) {
|
|
16
|
+
console.warn(
|
|
17
|
+
`No procedures defined in AppDefinition. Only data models will be outputted.`
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
const result = await createTypescriptClient(def, options);
|
|
21
|
+
writeFileSync(options.outputFile, result);
|
|
22
|
+
},
|
|
23
|
+
options
|
|
24
|
+
})
|
|
25
|
+
);
|
|
26
|
+
async function createTypescriptClient(def, options) {
|
|
27
|
+
const clientName = pascalCase(options.clientName);
|
|
28
|
+
const services = unflattenProcedures(def.procedures);
|
|
29
|
+
const serviceFieldParts = [];
|
|
30
|
+
const serviceInitializationParts = [];
|
|
31
|
+
const procedureParts = [];
|
|
32
|
+
const subContentParts = [];
|
|
33
|
+
const existingTypeNames = [];
|
|
34
|
+
const rpcOptions = {
|
|
35
|
+
...options,
|
|
36
|
+
versionNumber: def.info?.version ?? "",
|
|
37
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
|
38
|
+
typesNeedingParser: Object.keys(def.definitions),
|
|
39
|
+
hasSseProcedures: false,
|
|
40
|
+
hasWsProcedures: false
|
|
41
|
+
};
|
|
42
|
+
Object.keys(services).forEach((key) => {
|
|
43
|
+
const item = services[key];
|
|
44
|
+
if (isRpcDefinition(item)) {
|
|
45
|
+
const rpc = tsRpcFromDefinition(key, item, rpcOptions);
|
|
46
|
+
if (rpc) {
|
|
47
|
+
procedureParts.push(rpc);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (isServiceDefinition(item)) {
|
|
52
|
+
const serviceName = pascalCase(`${clientName}_${key}`);
|
|
53
|
+
const service = tsServiceFromDefinition(
|
|
54
|
+
serviceName,
|
|
55
|
+
item,
|
|
56
|
+
rpcOptions
|
|
57
|
+
);
|
|
58
|
+
serviceFieldParts.push(`${key}: ${serviceName}Service;`);
|
|
59
|
+
serviceInitializationParts.push(
|
|
60
|
+
`this.${key} = new ${serviceName}Service(options);`
|
|
61
|
+
);
|
|
62
|
+
subContentParts.push(service);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
for (const key of rpcOptions.typesNeedingParser) {
|
|
66
|
+
const schema = def.definitions[key];
|
|
67
|
+
if (isSchemaFormProperties(schema) || isSchemaFormDiscriminator(schema)) {
|
|
68
|
+
const type = tsTypeFromJtdSchema(key, schema, rpcOptions, {
|
|
69
|
+
isOptional: false,
|
|
70
|
+
existingTypeNames
|
|
71
|
+
});
|
|
72
|
+
subContentParts.push(type.content);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const importParts = ["arriRequest"];
|
|
76
|
+
if (rpcOptions.hasSseProcedures) {
|
|
77
|
+
importParts.push("arriSseRequest");
|
|
78
|
+
importParts.push("type SseOptions");
|
|
79
|
+
}
|
|
80
|
+
if (rpcOptions.hasWsProcedures) {
|
|
81
|
+
importParts.push("arriWsRequest");
|
|
82
|
+
importParts.push("type WsOptions");
|
|
83
|
+
}
|
|
84
|
+
if (procedureParts.length === 0 && serviceFieldParts.length === 0) {
|
|
85
|
+
const result2 = `/* eslint-disable */
|
|
86
|
+
// @ts-nocheck
|
|
87
|
+
// this file was autogenerated by arri-codegen-ts
|
|
88
|
+
|
|
89
|
+
${subContentParts.join("\n")}`;
|
|
90
|
+
return prettier.format(result2, {
|
|
91
|
+
parser: "typescript",
|
|
92
|
+
...options.prettierOptions
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
const result = `/* eslint-disable */
|
|
96
|
+
// @ts-nocheck
|
|
97
|
+
// this file was autogenerated by arri-codegen-ts
|
|
98
|
+
import { ${importParts.join(", ")} } from '@arrirpc/client';
|
|
99
|
+
|
|
100
|
+
interface ${clientName}Options {
|
|
101
|
+
baseUrl?: string;
|
|
102
|
+
headers?: Record<string, string> | (() => Record<string, string>);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export class ${clientName} {
|
|
106
|
+
private readonly baseUrl: string;
|
|
107
|
+
private readonly headers: Record<string, string> | (() => Record<string, string>)
|
|
108
|
+
private readonly clientVersion = '${rpcOptions.versionNumber}';
|
|
109
|
+
${serviceFieldParts.join("\n ")}
|
|
110
|
+
|
|
111
|
+
constructor(options: ${clientName}Options = {}) {
|
|
112
|
+
this.baseUrl = options.baseUrl ?? "";
|
|
113
|
+
this.headers = options.headers ?? {};
|
|
114
|
+
${serviceInitializationParts.join(";\n ")}
|
|
115
|
+
}
|
|
116
|
+
${procedureParts.join("\n ")}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
${subContentParts.join("\n")}
|
|
120
|
+
`;
|
|
121
|
+
return await prettier.format(result, {
|
|
122
|
+
parser: "typescript",
|
|
123
|
+
...options.prettierOptions
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
function tsRpcFromDefinition(key, schema, options) {
|
|
127
|
+
if (schema.transport === "http") {
|
|
128
|
+
return tsHttpRpcFromDefinition(key, schema, options);
|
|
129
|
+
}
|
|
130
|
+
if (schema.transport === "ws") {
|
|
131
|
+
return tsWsRpcFromDefinition(key, schema, options);
|
|
132
|
+
}
|
|
133
|
+
console.warn(
|
|
134
|
+
`[codegen-ts] Unsupported transport "${schema.transport}". Ignoring rpc.`
|
|
135
|
+
);
|
|
136
|
+
return "";
|
|
137
|
+
}
|
|
138
|
+
function tsHttpRpcFromDefinition(key, schema, options) {
|
|
139
|
+
const paramName = pascalCase(schema.params ?? "");
|
|
140
|
+
const responseName = pascalCase(schema.response ?? "");
|
|
141
|
+
const paramsInput = schema.params ? `params: ${paramName}` : "";
|
|
142
|
+
const hasInput = paramName.length > 0;
|
|
143
|
+
const hasOutput = responseName.length > 0;
|
|
144
|
+
let serializerPart = `(_) => {}`;
|
|
145
|
+
let parserPart = `(_) => {}`;
|
|
146
|
+
if (hasInput) {
|
|
147
|
+
serializerPart = `$$${paramName}.serialize`;
|
|
148
|
+
}
|
|
149
|
+
if (hasOutput) {
|
|
150
|
+
parserPart = `$$${responseName}.parse`;
|
|
151
|
+
}
|
|
152
|
+
const paramsOutput = hasInput ? `params` : `params: undefined`;
|
|
153
|
+
if (schema.isEventStream) {
|
|
154
|
+
options.hasSseProcedures = true;
|
|
155
|
+
return `${getJsDocComment({ isDeprecated: schema.isDeprecated, description: schema.description })}${key}(${paramsInput.length ? `${paramsInput}, ` : ""}options: SseOptions<${schema.response ?? "undefined"}>) {
|
|
156
|
+
return arriSseRequest<${schema.response ?? "undefined"}, ${schema.params ?? "undefined"}>({
|
|
157
|
+
url: \`\${this.baseUrl}${schema.path}\`,
|
|
158
|
+
method: "${schema.method}",
|
|
159
|
+
headers: this.headers,
|
|
160
|
+
${paramsOutput},
|
|
161
|
+
parser: ${parserPart},
|
|
162
|
+
serializer: ${serializerPart},
|
|
163
|
+
clientVersion: this.clientVersion,
|
|
164
|
+
}, options);
|
|
165
|
+
}`;
|
|
166
|
+
}
|
|
167
|
+
return `${getJsDocComment({ isDeprecated: schema.isDeprecated, description: schema.description })}${key}(${paramsInput}) {
|
|
168
|
+
return arriRequest<${schema.response ?? "undefined"}, ${schema.params ?? "undefined"}>({
|
|
169
|
+
url: \`\${this.baseUrl}${schema.path}\`,
|
|
170
|
+
method: "${schema.method}",
|
|
171
|
+
headers: this.headers,
|
|
172
|
+
${paramsOutput},
|
|
173
|
+
parser: ${parserPart},
|
|
174
|
+
serializer: ${serializerPart},
|
|
175
|
+
clientVersion: this.clientVersion,
|
|
176
|
+
});
|
|
177
|
+
}`;
|
|
178
|
+
}
|
|
179
|
+
function tsWsRpcFromDefinition(key, schema, options) {
|
|
180
|
+
options.hasWsProcedures = true;
|
|
181
|
+
const paramName = schema.params ? pascalCase(schema.params) : "undefined";
|
|
182
|
+
const responseName = schema.response ? pascalCase(schema.response) : "undefined";
|
|
183
|
+
const hasInput = paramName.length > 0 && paramName !== "undefined";
|
|
184
|
+
const hasOutput = responseName.length > 0 && paramName !== "undefined";
|
|
185
|
+
let serializerPart = `(_) => {}`;
|
|
186
|
+
let parserPart = `(_) => {}`;
|
|
187
|
+
if (hasInput) {
|
|
188
|
+
serializerPart = `$$${paramName}.serialize`;
|
|
189
|
+
}
|
|
190
|
+
if (hasOutput) {
|
|
191
|
+
parserPart = `$$${responseName}.parse`;
|
|
192
|
+
}
|
|
193
|
+
return `${getJsDocComment(schema)}${key}(options: WsOptions<${responseName}> = {}) {
|
|
194
|
+
return arriWsRequest<${paramName}, ${responseName}>({
|
|
195
|
+
url: \`\${this.baseUrl}${schema.path}\`,
|
|
196
|
+
headers: this.headers,
|
|
197
|
+
parser: ${parserPart},
|
|
198
|
+
serializer: ${serializerPart},
|
|
199
|
+
onOpen: options.onOpen,
|
|
200
|
+
onClose: options.onClose,
|
|
201
|
+
onError: options.onError,
|
|
202
|
+
onConnectionError: options.onConnectionError,
|
|
203
|
+
onMessage: options.onMessage,
|
|
204
|
+
clientVersion: this.clientVersion,
|
|
205
|
+
})
|
|
206
|
+
}`;
|
|
207
|
+
}
|
|
208
|
+
function tsServiceFromDefinition(name, schema, options) {
|
|
209
|
+
const serviceFieldParts = [];
|
|
210
|
+
const serviceConstructorParts = [];
|
|
211
|
+
const subServiceContent = [];
|
|
212
|
+
const rpcContent = [];
|
|
213
|
+
Object.keys(schema).forEach((key) => {
|
|
214
|
+
const def = schema[key];
|
|
215
|
+
if (isRpcDefinition(def)) {
|
|
216
|
+
const rpc = tsRpcFromDefinition(key, def, options);
|
|
217
|
+
if (rpc) {
|
|
218
|
+
rpcContent.push(rpc);
|
|
219
|
+
}
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
if (isServiceDefinition(def)) {
|
|
223
|
+
const serviceName = pascalCase(`${name}_${key}`);
|
|
224
|
+
const service = tsServiceFromDefinition(serviceName, def, options);
|
|
225
|
+
serviceFieldParts.push(`${key}: ${serviceName}Service;`);
|
|
226
|
+
serviceConstructorParts.push(
|
|
227
|
+
`this.${key} = new ${serviceName}Service(options);`
|
|
228
|
+
);
|
|
229
|
+
subServiceContent.push(service);
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
return `export class ${name}Service {
|
|
233
|
+
private readonly baseUrl: string;
|
|
234
|
+
private readonly headers: Record<string, string> | (() => Record<string, string>);
|
|
235
|
+
private readonly clientVersion = '${options.versionNumber}';
|
|
236
|
+
${serviceFieldParts.join("\n ")}
|
|
237
|
+
|
|
238
|
+
constructor(options: ${pascalCase(
|
|
239
|
+
`${options.clientName}_Options`
|
|
240
|
+
)} = {}) {
|
|
241
|
+
this.baseUrl = options.baseUrl ?? '';
|
|
242
|
+
this.headers = options.headers ?? {};
|
|
243
|
+
${serviceConstructorParts.join("\n ")}
|
|
244
|
+
}
|
|
245
|
+
${rpcContent.join("\n ")}
|
|
246
|
+
}
|
|
247
|
+
${subServiceContent.join("\n")}`;
|
|
248
|
+
}
|
|
249
|
+
function tsTypeFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
250
|
+
if (additionalOptions.logDef) {
|
|
251
|
+
console.log(def);
|
|
252
|
+
}
|
|
253
|
+
if (isSchemaFormType(def)) {
|
|
254
|
+
return tsScalarFromJtdSchema(nodePath, def, options, additionalOptions);
|
|
255
|
+
}
|
|
256
|
+
if (isSchemaFormProperties(def)) {
|
|
257
|
+
return tsObjectFromJtdSchema(nodePath, def, options, additionalOptions);
|
|
258
|
+
}
|
|
259
|
+
if (isSchemaFormEnum(def)) {
|
|
260
|
+
return tsEnumFromJtdSchema(nodePath, def, options, additionalOptions);
|
|
261
|
+
}
|
|
262
|
+
if (isSchemaFormElements(def)) {
|
|
263
|
+
return tsArrayFromJtdSchema(nodePath, def, options, additionalOptions);
|
|
264
|
+
}
|
|
265
|
+
if (isSchemaFormDiscriminator(def)) {
|
|
266
|
+
return tsDiscriminatedUnionFromJtdSchema(
|
|
267
|
+
nodePath,
|
|
268
|
+
def,
|
|
269
|
+
options,
|
|
270
|
+
additionalOptions
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
if (isSchemaFormValues(def)) {
|
|
274
|
+
return tsRecordFromJtdSchema(nodePath, def, options, additionalOptions);
|
|
275
|
+
}
|
|
276
|
+
if (isSchemaFormRef(def)) {
|
|
277
|
+
return tsRefFromJtdSchema(nodePath, def, options, additionalOptions);
|
|
278
|
+
}
|
|
279
|
+
return tsAnyFromJtdSchema(nodePath, def, options, additionalOptions);
|
|
280
|
+
}
|
|
281
|
+
function maybeOptionalKey(keyName, isOptional = false) {
|
|
282
|
+
if (isOptional) {
|
|
283
|
+
return `${keyName}?`;
|
|
284
|
+
}
|
|
285
|
+
return keyName;
|
|
286
|
+
}
|
|
287
|
+
function maybeNullType(typeName, isNullable = false) {
|
|
288
|
+
if (isNullable) {
|
|
289
|
+
return `${typeName} | null`;
|
|
290
|
+
}
|
|
291
|
+
return typeName;
|
|
292
|
+
}
|
|
293
|
+
function tsAnyFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
294
|
+
const key = nodePath.split(".").pop() ?? "";
|
|
295
|
+
const isNullable = def.nullable ?? false;
|
|
296
|
+
const isOptional = additionalOptions.isOptional;
|
|
297
|
+
return {
|
|
298
|
+
tsType: "any",
|
|
299
|
+
schema: def,
|
|
300
|
+
fieldTemplate: `${maybeOptionalKey(key, isOptional)}: ${maybeNullType(
|
|
301
|
+
"any",
|
|
302
|
+
isNullable
|
|
303
|
+
)}`,
|
|
304
|
+
fromJsonTemplate(input) {
|
|
305
|
+
return input;
|
|
306
|
+
},
|
|
307
|
+
toJsonTemplate(input) {
|
|
308
|
+
return `JSON.stringify(${input})`;
|
|
309
|
+
},
|
|
310
|
+
content: ""
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
function getJsDocComment(metadata) {
|
|
314
|
+
const descriptionParts = [];
|
|
315
|
+
if (metadata?.description?.length) {
|
|
316
|
+
const parts = metadata.description.split("\n");
|
|
317
|
+
for (const part of parts) {
|
|
318
|
+
descriptionParts.push(`* ${part}`);
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
if (metadata?.isDeprecated) {
|
|
322
|
+
descriptionParts.push("* @deprecated");
|
|
323
|
+
}
|
|
324
|
+
if (descriptionParts.length === 0) {
|
|
325
|
+
return "";
|
|
326
|
+
}
|
|
327
|
+
return `/**
|
|
328
|
+
${descriptionParts.join("\n")}
|
|
329
|
+
*/
|
|
330
|
+
`;
|
|
331
|
+
}
|
|
332
|
+
function tsScalarFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
333
|
+
const key = nodePath.split(".").pop() ?? "";
|
|
334
|
+
const isNullable = def.nullable ?? false;
|
|
335
|
+
const isOptional = additionalOptions.isOptional;
|
|
336
|
+
switch (def.type) {
|
|
337
|
+
case "boolean":
|
|
338
|
+
return {
|
|
339
|
+
tsType: "boolean",
|
|
340
|
+
schema: def,
|
|
341
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
342
|
+
key,
|
|
343
|
+
isOptional
|
|
344
|
+
)}: ${maybeNullType("boolean", isNullable)}`,
|
|
345
|
+
fromJsonTemplate(input) {
|
|
346
|
+
if (isOptional) {
|
|
347
|
+
return `typeof ${input} === 'boolean' ? ${input} : undefined`;
|
|
348
|
+
}
|
|
349
|
+
if (isNullable) {
|
|
350
|
+
return `typeof ${input} === 'boolean' ? ${input} : null`;
|
|
351
|
+
}
|
|
352
|
+
return `typeof ${input} === 'boolean' ? ${input} : false`;
|
|
353
|
+
},
|
|
354
|
+
toJsonTemplate(input) {
|
|
355
|
+
if (isNullable) {
|
|
356
|
+
return `typeof ${input} === 'boolean' ? ${input} : null`;
|
|
357
|
+
}
|
|
358
|
+
return `typeof ${input} === 'boolean' ? ${input} : false`;
|
|
359
|
+
},
|
|
360
|
+
content: ""
|
|
361
|
+
};
|
|
362
|
+
case "string":
|
|
363
|
+
return {
|
|
364
|
+
tsType: "string",
|
|
365
|
+
schema: def,
|
|
366
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
367
|
+
key,
|
|
368
|
+
isOptional
|
|
369
|
+
)}: ${maybeNullType("string", isNullable)}`,
|
|
370
|
+
fromJsonTemplate(input) {
|
|
371
|
+
if (isOptional) {
|
|
372
|
+
return `typeof ${input} === 'string' ? ${input} : undefined`;
|
|
373
|
+
}
|
|
374
|
+
if (isNullable) {
|
|
375
|
+
return `typeof ${input} === 'string' ? ${input} : null`;
|
|
376
|
+
}
|
|
377
|
+
return `typeof ${input} === 'string' ? ${input} : ''`;
|
|
378
|
+
},
|
|
379
|
+
toJsonTemplate(input) {
|
|
380
|
+
if (isNullable) {
|
|
381
|
+
return `typeof ${input} === 'string' ? \`"${input}"\` : null`;
|
|
382
|
+
}
|
|
383
|
+
return `typeof ${input} === 'string' ? \`"${input}"\` : ""`;
|
|
384
|
+
},
|
|
385
|
+
content: ""
|
|
386
|
+
};
|
|
387
|
+
case "timestamp":
|
|
388
|
+
return {
|
|
389
|
+
tsType: "Date",
|
|
390
|
+
schema: def,
|
|
391
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
392
|
+
key,
|
|
393
|
+
isOptional
|
|
394
|
+
)}: ${maybeNullType("Date", isNullable)}`,
|
|
395
|
+
fromJsonTemplate(input) {
|
|
396
|
+
if (isOptional) {
|
|
397
|
+
return `typeof ${input} === 'string' ? new Date(${input}) : undefined`;
|
|
398
|
+
}
|
|
399
|
+
if (isNullable) {
|
|
400
|
+
return `typeof ${input} === 'string' ? new Date(${input}) : null`;
|
|
401
|
+
}
|
|
402
|
+
return `typeof ${input} === 'string' ? new Date(${input}) : new Date(0)`;
|
|
403
|
+
},
|
|
404
|
+
toJsonTemplate(input) {
|
|
405
|
+
if (isOptional || isNullable) {
|
|
406
|
+
return `typeof ${input} === 'object' && ${input} instanceof Date ? "${input}.toISOString()" : null`;
|
|
407
|
+
}
|
|
408
|
+
return `"${input}.toISOString()"`;
|
|
409
|
+
},
|
|
410
|
+
content: ""
|
|
411
|
+
};
|
|
412
|
+
case "float32":
|
|
413
|
+
case "float64":
|
|
414
|
+
case "int8":
|
|
415
|
+
case "int16":
|
|
416
|
+
case "int32":
|
|
417
|
+
case "uint8":
|
|
418
|
+
case "uint16":
|
|
419
|
+
case "uint32":
|
|
420
|
+
return {
|
|
421
|
+
tsType: "number",
|
|
422
|
+
schema: def,
|
|
423
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
424
|
+
key,
|
|
425
|
+
isOptional
|
|
426
|
+
)}: ${maybeNullType("number", isNullable)}`,
|
|
427
|
+
fromJsonTemplate(input) {
|
|
428
|
+
if (isOptional) {
|
|
429
|
+
return `typeof ${input} === 'number' ? ${input} : undefined`;
|
|
430
|
+
}
|
|
431
|
+
if (isNullable) {
|
|
432
|
+
return `typeof ${input} === 'number' ? ${input} : null`;
|
|
433
|
+
}
|
|
434
|
+
return `typeof ${input} === 'number' ? ${input} : 0`;
|
|
435
|
+
},
|
|
436
|
+
toJsonTemplate(input) {
|
|
437
|
+
return `JSON.stringify(${input})`;
|
|
438
|
+
},
|
|
439
|
+
content: ""
|
|
440
|
+
};
|
|
441
|
+
case "int64":
|
|
442
|
+
case "uint64":
|
|
443
|
+
return {
|
|
444
|
+
tsType: "bigint",
|
|
445
|
+
schema: def,
|
|
446
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
447
|
+
key,
|
|
448
|
+
isOptional
|
|
449
|
+
)}: ${maybeNullType("bigint", isNullable)}`,
|
|
450
|
+
fromJsonTemplate(input) {
|
|
451
|
+
if (isOptional) {
|
|
452
|
+
return `typeof ${input} === 'string' ? BigInt(${input}) : undefined`;
|
|
453
|
+
}
|
|
454
|
+
if (isNullable) {
|
|
455
|
+
return `typeof ${input} === 'string' ? BigInt(${input}) : null`;
|
|
456
|
+
}
|
|
457
|
+
return `typeof ${input} === 'string' ? BigInt(${input}) : BigInt("0")`;
|
|
458
|
+
},
|
|
459
|
+
toJsonTemplate(input) {
|
|
460
|
+
if (isOptional || isNullable) {
|
|
461
|
+
return `${input}?.toString()`;
|
|
462
|
+
}
|
|
463
|
+
return `${input}.toString()`;
|
|
464
|
+
},
|
|
465
|
+
content: ""
|
|
466
|
+
};
|
|
467
|
+
default:
|
|
468
|
+
def.type;
|
|
469
|
+
throw new Error(`Invalid type in SchemaFormType`);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
function getTypeName(nodePath, def) {
|
|
473
|
+
if (def.metadata?.id) {
|
|
474
|
+
return pascalCase(def.metadata.id);
|
|
475
|
+
}
|
|
476
|
+
return pascalCase(nodePath.split(".").join("_"));
|
|
477
|
+
}
|
|
478
|
+
function tsObjectFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
479
|
+
const typeName = getTypeName(nodePath, def);
|
|
480
|
+
const key = nodePath.split(".").pop() ?? "";
|
|
481
|
+
const fieldNames = [];
|
|
482
|
+
const fieldParts = [];
|
|
483
|
+
const subContentParts = [];
|
|
484
|
+
const parserParts = [];
|
|
485
|
+
if (additionalOptions.discriminatorKey && additionalOptions.discriminatorKeyValue) {
|
|
486
|
+
parserParts.push(
|
|
487
|
+
`${additionalOptions.discriminatorKey}: '${additionalOptions.discriminatorKeyValue}'`
|
|
488
|
+
);
|
|
489
|
+
fieldParts.push(
|
|
490
|
+
`${additionalOptions.discriminatorKey}: "${additionalOptions.discriminatorKeyValue}"`
|
|
491
|
+
);
|
|
492
|
+
fieldNames.push(additionalOptions.discriminatorKey);
|
|
493
|
+
}
|
|
494
|
+
if (def.properties) {
|
|
495
|
+
for (const propKey of Object.keys(def.properties)) {
|
|
496
|
+
const propSchema = def.properties[propKey];
|
|
497
|
+
const type = tsTypeFromJtdSchema(
|
|
498
|
+
`${nodePath}.${propKey}`,
|
|
499
|
+
propSchema,
|
|
500
|
+
options,
|
|
501
|
+
{
|
|
502
|
+
isOptional: false,
|
|
503
|
+
existingTypeNames: additionalOptions.existingTypeNames
|
|
504
|
+
}
|
|
505
|
+
);
|
|
506
|
+
parserParts.push(
|
|
507
|
+
`${propKey}: ${type.fromJsonTemplate(`input.${propKey}`)}`
|
|
508
|
+
);
|
|
509
|
+
fieldParts.push(type.fieldTemplate);
|
|
510
|
+
fieldNames.push(propKey);
|
|
511
|
+
if (type.content) {
|
|
512
|
+
subContentParts.push(type.content);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
if (def.optionalProperties) {
|
|
517
|
+
for (const propKey of Object.keys(def.optionalProperties)) {
|
|
518
|
+
const propSchema = def.optionalProperties[propKey];
|
|
519
|
+
const type = tsTypeFromJtdSchema(
|
|
520
|
+
`${nodePath}.${propKey}`,
|
|
521
|
+
propSchema,
|
|
522
|
+
options,
|
|
523
|
+
{
|
|
524
|
+
isOptional: true,
|
|
525
|
+
existingTypeNames: additionalOptions.existingTypeNames
|
|
526
|
+
}
|
|
527
|
+
);
|
|
528
|
+
parserParts.push(
|
|
529
|
+
`${propKey}: ${type.fromJsonTemplate(`input.${propKey}`)}`
|
|
530
|
+
);
|
|
531
|
+
fieldParts.push(type.fieldTemplate);
|
|
532
|
+
fieldNames.push(propKey);
|
|
533
|
+
if (type.content) {
|
|
534
|
+
subContentParts.push(type.content);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
let content = "";
|
|
539
|
+
const modifiedDef = {
|
|
540
|
+
...def,
|
|
541
|
+
metadata: {
|
|
542
|
+
id: def.metadata?.id ?? typeName,
|
|
543
|
+
description: def.metadata?.description
|
|
544
|
+
},
|
|
545
|
+
nullable: false
|
|
546
|
+
};
|
|
547
|
+
let validatorPart = "";
|
|
548
|
+
if (options.typesNeedingParser.includes(typeName)) {
|
|
549
|
+
const parsingCode = getSchemaParsingCode("input", modifiedDef);
|
|
550
|
+
const serializationCode = getSchemaSerializationCode(
|
|
551
|
+
"input",
|
|
552
|
+
modifiedDef
|
|
553
|
+
);
|
|
554
|
+
validatorPart = `export const $$${typeName} = {
|
|
555
|
+
parse(input: Record<any, any>): ${typeName} {
|
|
556
|
+
${parsingCode}
|
|
557
|
+
},
|
|
558
|
+
serialize(input: ${typeName}): string {
|
|
559
|
+
${serializationCode}
|
|
560
|
+
}
|
|
561
|
+
}`;
|
|
562
|
+
}
|
|
563
|
+
if (!additionalOptions.existingTypeNames.includes(typeName)) {
|
|
564
|
+
content = `${getJsDocComment(def.metadata)}export interface ${typeName} {
|
|
565
|
+
${fieldParts.join(";\n ")};
|
|
566
|
+
}
|
|
567
|
+
${validatorPart}
|
|
568
|
+
${subContentParts.join("\n")}`;
|
|
569
|
+
additionalOptions.existingTypeNames.push(typeName);
|
|
570
|
+
}
|
|
571
|
+
return {
|
|
572
|
+
tsType: typeName,
|
|
573
|
+
schema: def,
|
|
574
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
575
|
+
key,
|
|
576
|
+
additionalOptions.isOptional
|
|
577
|
+
)}: ${maybeNullType(typeName, def.nullable ?? false)}`,
|
|
578
|
+
fromJsonTemplate(input) {
|
|
579
|
+
if (additionalOptions.isOptional) {
|
|
580
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : undefined`;
|
|
581
|
+
}
|
|
582
|
+
if (def.nullable) {
|
|
583
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : null`;
|
|
584
|
+
}
|
|
585
|
+
return `$$${typeName}.parse(${input})`;
|
|
586
|
+
},
|
|
587
|
+
toJsonTemplate(input) {
|
|
588
|
+
return `JSON.stringify(${input})`;
|
|
589
|
+
},
|
|
590
|
+
content
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
function tsEnumFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
594
|
+
const keyName = nodePath.split(".").pop() ?? "";
|
|
595
|
+
const typeName = getTypeName(nodePath, def);
|
|
596
|
+
let content = `${getJsDocComment(def.metadata)}export type ${typeName} = ${def.enum.map((val) => `"${val}"`).join(" | ")}`;
|
|
597
|
+
if (additionalOptions.existingTypeNames.includes(typeName)) {
|
|
598
|
+
content = "";
|
|
599
|
+
} else {
|
|
600
|
+
additionalOptions.existingTypeNames.push(typeName);
|
|
601
|
+
}
|
|
602
|
+
return {
|
|
603
|
+
tsType: typeName,
|
|
604
|
+
schema: def,
|
|
605
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
606
|
+
keyName,
|
|
607
|
+
additionalOptions.isOptional
|
|
608
|
+
)}: ${maybeNullType(typeName, def.nullable ?? false)}`,
|
|
609
|
+
fromJsonTemplate(input) {
|
|
610
|
+
if (additionalOptions.isOptional) {
|
|
611
|
+
return `typeof ${input} === 'string' ? $$${typeName}.parse(${input}) : undefined`;
|
|
612
|
+
}
|
|
613
|
+
if (def.nullable) {
|
|
614
|
+
return `typeof ${input} === 'string' ? $$${typeName}.parse(${input}) : null`;
|
|
615
|
+
}
|
|
616
|
+
return `$$${typeName}.parse(${input})`;
|
|
617
|
+
},
|
|
618
|
+
toJsonTemplate(input) {
|
|
619
|
+
return `JSON.stringify(${input})`;
|
|
620
|
+
},
|
|
621
|
+
content
|
|
622
|
+
};
|
|
623
|
+
}
|
|
624
|
+
function tsArrayFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
625
|
+
const key = nodePath.split(".").pop() ?? "";
|
|
626
|
+
const subType = tsTypeFromJtdSchema(
|
|
627
|
+
`${nodePath}.item`,
|
|
628
|
+
def.elements,
|
|
629
|
+
options,
|
|
630
|
+
{
|
|
631
|
+
isOptional: false,
|
|
632
|
+
existingTypeNames: additionalOptions.existingTypeNames
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
const tsType = `Array<${maybeNullType(
|
|
636
|
+
subType.tsType,
|
|
637
|
+
def.elements.nullable
|
|
638
|
+
)}>`;
|
|
639
|
+
return {
|
|
640
|
+
tsType,
|
|
641
|
+
schema: def,
|
|
642
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
643
|
+
key,
|
|
644
|
+
additionalOptions.isOptional ?? false
|
|
645
|
+
)}: ${maybeNullType(tsType, def.nullable ?? false)}`,
|
|
646
|
+
fromJsonTemplate(input) {
|
|
647
|
+
if (additionalOptions.isOptional) {
|
|
648
|
+
return `Array.isArray(${input}) ? ${input}.map((item) => ${subType.fromJsonTemplate(
|
|
649
|
+
"item"
|
|
650
|
+
)}) : undefined`;
|
|
651
|
+
}
|
|
652
|
+
if (def.nullable) {
|
|
653
|
+
return `Array.isArray(${input}) ? ${input}.map((item) => ${subType.fromJsonTemplate(
|
|
654
|
+
"item"
|
|
655
|
+
)}) : null`;
|
|
656
|
+
}
|
|
657
|
+
return `Array.isArray(${input}) ? ${input}.map((item) => ${subType.fromJsonTemplate(
|
|
658
|
+
"item"
|
|
659
|
+
)}) : []`;
|
|
660
|
+
},
|
|
661
|
+
toJsonTemplate(input) {
|
|
662
|
+
return `JSON.stringify(${input})`;
|
|
663
|
+
},
|
|
664
|
+
content: subType.content
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
function tsDiscriminatedUnionFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
668
|
+
const key = nodePath.split(".").pop() ?? "";
|
|
669
|
+
const typeName = getTypeName(nodePath, def);
|
|
670
|
+
const subTypeNames = [];
|
|
671
|
+
const subContentParts = [];
|
|
672
|
+
const parserParts = [];
|
|
673
|
+
for (const val of Object.keys(def.mapping)) {
|
|
674
|
+
const optionSchema = def.mapping[val];
|
|
675
|
+
if (!isSchemaFormProperties(optionSchema)) {
|
|
676
|
+
continue;
|
|
677
|
+
}
|
|
678
|
+
const optionType = tsObjectFromJtdSchema(
|
|
679
|
+
`${nodePath}.${camelCase(val.toLowerCase())}`,
|
|
680
|
+
optionSchema,
|
|
681
|
+
options,
|
|
682
|
+
{
|
|
683
|
+
discriminatorKey: def.discriminator,
|
|
684
|
+
discriminatorKeyValue: val,
|
|
685
|
+
isOptional: false,
|
|
686
|
+
existingTypeNames: additionalOptions.existingTypeNames
|
|
687
|
+
}
|
|
688
|
+
);
|
|
689
|
+
parserParts.push(`case '${val}':
|
|
690
|
+
return $$${optionType.tsType}.parse(input);`);
|
|
691
|
+
subTypeNames.push(optionType.tsType);
|
|
692
|
+
subContentParts.push(optionType.content);
|
|
693
|
+
}
|
|
694
|
+
let validatorPart = "";
|
|
695
|
+
if (options.typesNeedingParser.includes(typeName)) {
|
|
696
|
+
const modifiedDef = { ...def, nullable: false };
|
|
697
|
+
const parsingCode = getSchemaParsingCode(
|
|
698
|
+
"input",
|
|
699
|
+
modifiedDef
|
|
700
|
+
);
|
|
701
|
+
const serializationCode = getSchemaSerializationCode(
|
|
702
|
+
"input",
|
|
703
|
+
modifiedDef
|
|
704
|
+
);
|
|
705
|
+
validatorPart = `export const $$${typeName} = {
|
|
706
|
+
parse(input: Record<any, any>): ${typeName} {
|
|
707
|
+
${parsingCode}
|
|
708
|
+
},
|
|
709
|
+
serialize(input: ${typeName}): string {
|
|
710
|
+
${serializationCode}
|
|
711
|
+
}
|
|
712
|
+
}`;
|
|
713
|
+
}
|
|
714
|
+
let content = `${getJsDocComment(def.metadata)}export type ${typeName} = ${subTypeNames.join(" | ")};
|
|
715
|
+
${validatorPart}
|
|
716
|
+
${subContentParts.join("\n")}`;
|
|
717
|
+
if (additionalOptions.existingTypeNames.includes(typeName)) {
|
|
718
|
+
content = "";
|
|
719
|
+
} else {
|
|
720
|
+
additionalOptions.existingTypeNames.push(typeName);
|
|
721
|
+
}
|
|
722
|
+
return {
|
|
723
|
+
tsType: typeName,
|
|
724
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
725
|
+
key,
|
|
726
|
+
additionalOptions.isOptional
|
|
727
|
+
)}: ${maybeNullType(typeName, def.nullable)}`,
|
|
728
|
+
schema: def,
|
|
729
|
+
fromJsonTemplate(input) {
|
|
730
|
+
if (additionalOptions.isOptional) {
|
|
731
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : undefined`;
|
|
732
|
+
}
|
|
733
|
+
if (def.nullable) {
|
|
734
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : null`;
|
|
735
|
+
}
|
|
736
|
+
return `$$${typeName}.parse(${input})`;
|
|
737
|
+
},
|
|
738
|
+
toJsonTemplate(input) {
|
|
739
|
+
return `JSON.stringify(${input})`;
|
|
740
|
+
},
|
|
741
|
+
content
|
|
742
|
+
};
|
|
743
|
+
}
|
|
744
|
+
function tsRecordFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
745
|
+
const key = nodePath.split(".").pop() ?? "";
|
|
746
|
+
const typeName = getTypeName(nodePath, def);
|
|
747
|
+
const subType = tsTypeFromJtdSchema(
|
|
748
|
+
`${nodePath}.value`,
|
|
749
|
+
def.values,
|
|
750
|
+
options,
|
|
751
|
+
{
|
|
752
|
+
isOptional: false,
|
|
753
|
+
existingTypeNames: additionalOptions.existingTypeNames
|
|
754
|
+
}
|
|
755
|
+
);
|
|
756
|
+
let content = "";
|
|
757
|
+
if (!additionalOptions.existingTypeNames.includes(typeName)) {
|
|
758
|
+
let validatorPart = "";
|
|
759
|
+
if (options.typesNeedingParser.includes(typeName)) {
|
|
760
|
+
const modifiedDef = { ...def, nullable: false };
|
|
761
|
+
const parsingCode = getSchemaParsingCode(
|
|
762
|
+
"input",
|
|
763
|
+
modifiedDef
|
|
764
|
+
);
|
|
765
|
+
const serializationCode = getSchemaSerializationCode(
|
|
766
|
+
"input",
|
|
767
|
+
modifiedDef
|
|
768
|
+
);
|
|
769
|
+
validatorPart = `export const $$${typeName} = {
|
|
770
|
+
parse(input: Record<any, any>): ${typeName} {
|
|
771
|
+
${parsingCode}
|
|
772
|
+
},
|
|
773
|
+
serialize(input: ${typeName}): string {
|
|
774
|
+
${serializationCode}
|
|
775
|
+
}
|
|
776
|
+
}`;
|
|
777
|
+
}
|
|
778
|
+
content = `${getJsDocComment(def.metadata)}export type ${typeName} = Record<string, ${subType.tsType}>;
|
|
779
|
+
${validatorPart}
|
|
780
|
+
${subType.content}`;
|
|
781
|
+
}
|
|
782
|
+
return {
|
|
783
|
+
tsType: typeName,
|
|
784
|
+
schema: def,
|
|
785
|
+
fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
|
|
786
|
+
key,
|
|
787
|
+
additionalOptions.isOptional
|
|
788
|
+
)}: ${maybeNullType(typeName, def.nullable)}`,
|
|
789
|
+
fromJsonTemplate(input) {
|
|
790
|
+
if (additionalOptions.isOptional) {
|
|
791
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : undefined`;
|
|
792
|
+
}
|
|
793
|
+
if (def.nullable) {
|
|
794
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : null`;
|
|
795
|
+
}
|
|
796
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : {}`;
|
|
797
|
+
},
|
|
798
|
+
toJsonTemplate(input) {
|
|
799
|
+
return `JSON.stringify(${input})`;
|
|
800
|
+
},
|
|
801
|
+
content
|
|
802
|
+
};
|
|
803
|
+
}
|
|
804
|
+
function tsRefFromJtdSchema(nodePath, def, options, additionalOptions) {
|
|
805
|
+
const key = nodePath.split(".").pop() ?? "";
|
|
806
|
+
const typeName = pascalCase(def.ref, {
|
|
807
|
+
normalize: true
|
|
808
|
+
});
|
|
809
|
+
return {
|
|
810
|
+
tsType: typeName,
|
|
811
|
+
schema: def,
|
|
812
|
+
fieldTemplate: `${maybeOptionalKey(key, additionalOptions.isOptional)}: ${maybeNullType(typeName, def.nullable)}`,
|
|
813
|
+
fromJsonTemplate(input) {
|
|
814
|
+
if (additionalOptions.isOptional) {
|
|
815
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : undefined`;
|
|
816
|
+
}
|
|
817
|
+
if (def.nullable) {
|
|
818
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : null`;
|
|
819
|
+
}
|
|
820
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : {}`;
|
|
821
|
+
},
|
|
822
|
+
toJsonTemplate(input) {
|
|
823
|
+
if (additionalOptions.isOptional) {
|
|
824
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.serialize(${input}) : 'undefined'`;
|
|
825
|
+
}
|
|
826
|
+
if (def.nullable) {
|
|
827
|
+
return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.serialize(${input}) : 'null'`;
|
|
828
|
+
}
|
|
829
|
+
return `$$${typeName}.serialize(${input})`;
|
|
830
|
+
},
|
|
831
|
+
content: ""
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
export { createTypescriptClient, getJsDocComment, maybeNullType, maybeOptionalKey, tsAnyFromJtdSchema, tsArrayFromJtdSchema, tsDiscriminatedUnionFromJtdSchema, tsEnumFromJtdSchema, tsHttpRpcFromDefinition, tsObjectFromJtdSchema, tsRecordFromJtdSchema, tsRefFromJtdSchema, tsRpcFromDefinition, tsScalarFromJtdSchema, tsServiceFromDefinition, tsTypeFromJtdSchema, tsWsRpcFromDefinition, typescriptClientGenerator };
|