@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/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 };