@arrirpc/codegen-ts 0.54.0 → 0.57.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 CHANGED
@@ -1,316 +1,29 @@
1
- import { defineGeneratorPlugin, pascalCase, unflattenProcedures, isRpcDefinition, isServiceDefinition, isSchemaFormProperties, isSchemaFormDiscriminator, isSchemaFormType, isSchemaFormEnum, isSchemaFormElements, isSchemaFormValues, isSchemaFormRef, camelCase } from '@arrirpc/codegen-utils';
2
- import { getSchemaParsingCode, getSchemaSerializationCode } from '@arrirpc/schema';
1
+ import { stringStartsWithNumber, removeDisallowedChars, pascalCase, camelCase, isServiceDefinition, isRpcDefinition, defineGeneratorPlugin, unflattenProcedures, isSchemaFormType, isSchemaFormEnum, isSchemaFormProperties, isSchemaFormElements, isSchemaFormValues, isSchemaFormDiscriminator, isSchemaFormRef } from '@arrirpc/codegen-utils';
3
2
  import { writeFileSync } from 'fs';
4
3
  import prettier from 'prettier';
5
4
 
6
- const typescriptClientGenerator = defineGeneratorPlugin(
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
- importParts.push("type EventSourceController");
80
- }
81
- if (rpcOptions.hasWsProcedures) {
82
- importParts.push("arriWsRequest");
83
- importParts.push("type WsOptions");
84
- }
85
- if (procedureParts.length === 0 && serviceFieldParts.length === 0) {
86
- const result2 = `/* eslint-disable */
87
- // @ts-nocheck
88
- // this file was autogenerated by arri-codegen-ts
89
-
90
- ${subContentParts.join("\n")}`;
91
- return prettier.format(result2, {
92
- parser: "typescript",
93
- ...options.prettierOptions
94
- });
95
- }
96
- const result = `/* eslint-disable */
97
- // @ts-nocheck
98
- // this file was autogenerated by arri-codegen-ts
99
- import { ${importParts.join(", ")} } from '@arrirpc/client';
100
-
101
- interface ${clientName}Options {
102
- baseUrl?: string;
103
- headers?: Record<string, string | undefined> | (() => Record<string, string | undefined> | Promise<Record<string, string | undefined>>);
104
- }
105
-
106
- export class ${clientName} {
107
- private readonly baseUrl: string;
108
- private readonly headers: Record<string, string | undefined> | (() => Record<string, string | undefined> | Promise<Record<string, string | undefined>>)
109
- private readonly clientVersion = '${rpcOptions.versionNumber}';
110
- ${serviceFieldParts.join("\n ")}
111
-
112
- constructor(options: ${clientName}Options = {}) {
113
- this.baseUrl = options.baseUrl ?? "";
114
- this.headers = options.headers ?? {};
115
- ${serviceInitializationParts.join(";\n ")}
116
- }
117
- ${procedureParts.join("\n ")}
118
- }
119
-
120
- ${subContentParts.join("\n")}
121
- `;
122
- return await prettier.format(result, {
123
- parser: "typescript",
124
- ...options.prettierOptions
125
- });
126
- }
127
- function tsRpcFromDefinition(key, schema, options) {
128
- if (schema.transport === "http") {
129
- return tsHttpRpcFromDefinition(key, schema, options);
130
- }
131
- if (schema.transport === "ws") {
132
- return tsWsRpcFromDefinition(key, schema, options);
133
- }
134
- console.warn(
135
- `[codegen-ts] Unsupported transport "${schema.transport}". Ignoring rpc.`
136
- );
137
- return "";
138
- }
139
- function tsHttpRpcFromDefinition(key, schema, options) {
140
- const paramName = pascalCase(schema.params ?? "");
141
- const responseName = pascalCase(schema.response ?? "");
142
- const paramsInput = schema.params ? `params: ${paramName}` : "";
143
- const hasInput = paramName.length > 0;
144
- const hasOutput = responseName.length > 0;
145
- let serializerPart = `(_) => {}`;
146
- let parserPart = `(_) => {}`;
147
- if (hasInput) {
148
- serializerPart = `$$${paramName}.serialize`;
149
- }
150
- if (hasOutput) {
151
- parserPart = `$$${responseName}.parse`;
152
- }
153
- const paramsOutput = hasInput ? `params` : `params: undefined`;
154
- if (schema.isEventStream) {
155
- options.hasSseProcedures = true;
156
- return `${getJsDocComment({ isDeprecated: schema.isDeprecated, description: schema.description })}${key}(${paramsInput.length ? `${paramsInput}, ` : ""}options: SseOptions<${schema.response ?? "undefined"}>): EventSourceController {
157
- return arriSseRequest<${schema.response ?? "undefined"}, ${schema.params ?? "undefined"}>({
158
- url: \`\${this.baseUrl}${schema.path}\`,
159
- method: "${schema.method}",
160
- headers: this.headers,
161
- ${paramsOutput},
162
- parser: ${parserPart},
163
- serializer: ${serializerPart},
164
- clientVersion: this.clientVersion,
165
- }, options);
166
- }`;
167
- }
168
- return `${getJsDocComment({ isDeprecated: schema.isDeprecated, description: schema.description })}${key}(${paramsInput}) {
169
- return arriRequest<${schema.response ?? "undefined"}, ${schema.params ?? "undefined"}>({
170
- url: \`\${this.baseUrl}${schema.path}\`,
171
- method: "${schema.method}",
172
- headers: this.headers,
173
- ${paramsOutput},
174
- parser: ${parserPart},
175
- serializer: ${serializerPart},
176
- clientVersion: this.clientVersion,
177
- });
178
- }`;
179
- }
180
- function tsWsRpcFromDefinition(key, schema, options) {
181
- options.hasWsProcedures = true;
182
- const paramName = schema.params ? pascalCase(schema.params) : "undefined";
183
- const responseName = schema.response ? pascalCase(schema.response) : "undefined";
184
- const hasInput = paramName.length > 0 && paramName !== "undefined";
185
- const hasOutput = responseName.length > 0 && paramName !== "undefined";
186
- let serializerPart = `(_) => {}`;
187
- let parserPart = `(_) => {}`;
188
- if (hasInput) {
189
- serializerPart = `$$${paramName}.serialize`;
190
- }
191
- if (hasOutput) {
192
- parserPart = `$$${responseName}.parse`;
193
- }
194
- return `${getJsDocComment(schema)}${key}(options: WsOptions<${responseName}> = {}) {
195
- return arriWsRequest<${paramName}, ${responseName}>({
196
- url: \`\${this.baseUrl}${schema.path}\`,
197
- headers: this.headers,
198
- parser: ${parserPart},
199
- serializer: ${serializerPart},
200
- onOpen: options.onOpen,
201
- onClose: options.onClose,
202
- onError: options.onError,
203
- onConnectionError: options.onConnectionError,
204
- onMessage: options.onMessage,
205
- clientVersion: this.clientVersion,
206
- })
207
- }`;
208
- }
209
- function tsServiceFromDefinition(name, schema, options) {
210
- const serviceFieldParts = [];
211
- const serviceConstructorParts = [];
212
- const subServiceContent = [];
213
- const rpcContent = [];
214
- Object.keys(schema).forEach((key) => {
215
- const def = schema[key];
216
- if (isRpcDefinition(def)) {
217
- const rpc = tsRpcFromDefinition(key, def, options);
218
- if (rpc) {
219
- rpcContent.push(rpc);
220
- }
221
- return;
222
- }
223
- if (isServiceDefinition(def)) {
224
- const serviceName = pascalCase(`${name}_${key}`);
225
- const service = tsServiceFromDefinition(serviceName, def, options);
226
- serviceFieldParts.push(`${key}: ${serviceName}Service;`);
227
- serviceConstructorParts.push(
228
- `this.${key} = new ${serviceName}Service(options);`
229
- );
230
- subServiceContent.push(service);
231
- }
232
- });
233
- return `export class ${name}Service {
234
- private readonly baseUrl: string;
235
- private readonly headers: Record<string, string | undefined> | (() => Record<string, string | undefined> | Promise<Record<string, string | undefined>>);
236
- private readonly clientVersion = '${options.versionNumber}';
237
- ${serviceFieldParts.join("\n ")}
238
-
239
- constructor(options: ${pascalCase(
240
- `${options.clientName}_Options`
241
- )} = {}) {
242
- this.baseUrl = options.baseUrl ?? '';
243
- this.headers = options.headers ?? {};
244
- ${serviceConstructorParts.join("\n ")}
245
- }
246
- ${rpcContent.join("\n ")}
247
- }
248
- ${subServiceContent.join("\n")}`;
249
- }
250
- function tsTypeFromJtdSchema(nodePath, def, options, additionalOptions) {
251
- if (additionalOptions.logDef) {
252
- console.log(def);
253
- }
254
- if (isSchemaFormType(def)) {
255
- return tsScalarFromJtdSchema(nodePath, def, options, additionalOptions);
256
- }
257
- if (isSchemaFormProperties(def)) {
258
- return tsObjectFromJtdSchema(nodePath, def, options, additionalOptions);
259
- }
260
- if (isSchemaFormEnum(def)) {
261
- return tsEnumFromJtdSchema(nodePath, def, options, additionalOptions);
262
- }
263
- if (isSchemaFormElements(def)) {
264
- return tsArrayFromJtdSchema(nodePath, def, options, additionalOptions);
265
- }
266
- if (isSchemaFormDiscriminator(def)) {
267
- return tsDiscriminatedUnionFromJtdSchema(
268
- nodePath,
269
- def,
270
- options,
271
- additionalOptions
272
- );
273
- }
274
- if (isSchemaFormValues(def)) {
275
- return tsRecordFromJtdSchema(nodePath, def, options, additionalOptions);
276
- }
277
- if (isSchemaFormRef(def)) {
278
- return tsRefFromJtdSchema(nodePath, def, options, additionalOptions);
279
- }
280
- return tsAnyFromJtdSchema(nodePath, def, options, additionalOptions);
281
- }
282
- function maybeOptionalKey(keyName, isOptional = false) {
283
- if (isOptional) {
284
- return `${keyName}?`;
285
- }
286
- return keyName;
287
- }
288
- function maybeNullType(typeName, isNullable = false) {
289
- if (isNullable) {
290
- return `${typeName} | null`;
291
- }
292
- return typeName;
293
- }
294
- function tsAnyFromJtdSchema(nodePath, def, options, additionalOptions) {
295
- const key = nodePath.split(".").pop() ?? "";
296
- const isNullable = def.nullable ?? false;
297
- const isOptional = additionalOptions.isOptional;
5
+ function tsAnyFromSchema(schema, context) {
6
+ const typeName = "any";
7
+ const defaultValue = schema.nullable ? "null" : "undefined";
298
8
  return {
299
- tsType: "any",
300
- schema: def,
301
- fieldTemplate: `${maybeOptionalKey(key, isOptional)}: ${maybeNullType(
302
- "any",
303
- isNullable
304
- )}`,
305
- fromJsonTemplate(input) {
306
- return input;
307
- },
308
- toJsonTemplate(input) {
309
- return `JSON.stringify(${input})`;
9
+ typeName,
10
+ defaultValue,
11
+ validationTemplate(_) {
12
+ return "true";
13
+ },
14
+ fromJsonTemplate(input, target) {
15
+ return `${target} = ${input}`;
16
+ },
17
+ toJsonTemplate(input, target) {
18
+ return `${target} += JSON.stringify(${input})`;
19
+ },
20
+ toQueryStringTemplate(_input, _target, _key) {
21
+ return `console.warn("[WARNING] Cannot serialize any's to query string. Skipping property at ${context.instancePath}.")`;
310
22
  },
311
23
  content: ""
312
24
  };
313
25
  }
26
+
314
27
  function getJsDocComment(metadata) {
315
28
  const descriptionParts = [];
316
29
  if (metadata?.description?.length) {
@@ -330,507 +43,1169 @@ ${descriptionParts.join("\n")}
330
43
  */
331
44
  `;
332
45
  }
333
- function tsScalarFromJtdSchema(nodePath, def, options, additionalOptions) {
334
- const key = nodePath.split(".").pop() ?? "";
335
- const isNullable = def.nullable ?? false;
336
- const isOptional = additionalOptions.isOptional;
337
- switch (def.type) {
338
- case "boolean":
339
- return {
340
- tsType: "boolean",
341
- schema: def,
342
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
343
- key,
344
- isOptional
345
- )}: ${maybeNullType("boolean", isNullable)}`,
346
- fromJsonTemplate(input) {
347
- if (isOptional) {
348
- return `typeof ${input} === 'boolean' ? ${input} : undefined`;
349
- }
350
- if (isNullable) {
351
- return `typeof ${input} === 'boolean' ? ${input} : null`;
352
- }
353
- return `typeof ${input} === 'boolean' ? ${input} : false`;
354
- },
355
- toJsonTemplate(input) {
356
- if (isNullable) {
357
- return `typeof ${input} === 'boolean' ? ${input} : null`;
358
- }
359
- return `typeof ${input} === 'boolean' ? ${input} : false`;
360
- },
361
- content: ""
362
- };
363
- case "string":
364
- return {
365
- tsType: "string",
366
- schema: def,
367
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
368
- key,
369
- isOptional
370
- )}: ${maybeNullType("string", isNullable)}`,
371
- fromJsonTemplate(input) {
372
- if (isOptional) {
373
- return `typeof ${input} === 'string' ? ${input} : undefined`;
374
- }
375
- if (isNullable) {
376
- return `typeof ${input} === 'string' ? ${input} : null`;
377
- }
378
- return `typeof ${input} === 'string' ? ${input} : ''`;
379
- },
380
- toJsonTemplate(input) {
381
- if (isNullable) {
382
- return `typeof ${input} === 'string' ? \`"${input}"\` : null`;
383
- }
384
- return `typeof ${input} === 'string' ? \`"${input}"\` : ""`;
385
- },
386
- content: ""
387
- };
388
- case "timestamp":
389
- return {
390
- tsType: "Date",
391
- schema: def,
392
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
393
- key,
394
- isOptional
395
- )}: ${maybeNullType("Date", isNullable)}`,
396
- fromJsonTemplate(input) {
397
- if (isOptional) {
398
- return `typeof ${input} === 'string' ? new Date(${input}) : undefined`;
399
- }
400
- if (isNullable) {
401
- return `typeof ${input} === 'string' ? new Date(${input}) : null`;
402
- }
403
- return `typeof ${input} === 'string' ? new Date(${input}) : new Date(0)`;
404
- },
405
- toJsonTemplate(input) {
406
- if (isOptional || isNullable) {
407
- return `typeof ${input} === 'object' && ${input} instanceof Date ? "${input}.toISOString()" : null`;
408
- }
409
- return `"${input}.toISOString()"`;
410
- },
411
- content: ""
412
- };
413
- case "float32":
414
- case "float64":
415
- case "int8":
416
- case "int16":
417
- case "int32":
418
- case "uint8":
419
- case "uint16":
420
- case "uint32":
421
- return {
422
- tsType: "number",
423
- schema: def,
424
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
425
- key,
426
- isOptional
427
- )}: ${maybeNullType("number", isNullable)}`,
428
- fromJsonTemplate(input) {
429
- if (isOptional) {
430
- return `typeof ${input} === 'number' ? ${input} : undefined`;
431
- }
432
- if (isNullable) {
433
- return `typeof ${input} === 'number' ? ${input} : null`;
434
- }
435
- return `typeof ${input} === 'number' ? ${input} : 0`;
436
- },
437
- toJsonTemplate(input) {
438
- return `JSON.stringify(${input})`;
439
- },
440
- content: ""
441
- };
442
- case "int64":
443
- case "uint64":
444
- return {
445
- tsType: "bigint",
446
- schema: def,
447
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
448
- key,
449
- isOptional
450
- )}: ${maybeNullType("bigint", isNullable)}`,
451
- fromJsonTemplate(input) {
452
- if (isOptional) {
453
- return `typeof ${input} === 'string' ? BigInt(${input}) : undefined`;
454
- }
455
- if (isNullable) {
456
- return `typeof ${input} === 'string' ? BigInt(${input}) : null`;
457
- }
458
- return `typeof ${input} === 'string' ? BigInt(${input}) : BigInt("0")`;
459
- },
460
- toJsonTemplate(input) {
461
- if (isOptional || isNullable) {
462
- return `${input}?.toString()`;
463
- }
464
- return `${input}.toString()`;
465
- },
466
- content: ""
467
- };
468
- default:
469
- def.type;
470
- throw new Error(`Invalid type in SchemaFormType`);
46
+ const illegalChars = "!#^&*()-+=?/][{}|\\~`'\"";
47
+ function validVarName(name) {
48
+ if (stringStartsWithNumber(name)) {
49
+ return `_${removeDisallowedChars(name, illegalChars)}`;
471
50
  }
51
+ return removeDisallowedChars(name, illegalChars);
472
52
  }
473
- function getTypeName(nodePath, def) {
474
- if (def.metadata?.id) {
475
- return pascalCase(def.metadata.id);
53
+ function getTsTypeName(schema, context) {
54
+ if (schema.metadata?.id) {
55
+ const name2 = pascalCase(schema.metadata.id, { normalize: true });
56
+ return validVarName(name2);
476
57
  }
477
- return pascalCase(nodePath.split(".").join("_"));
58
+ if (context.discriminatorParent && context.discriminatorValue) {
59
+ const name2 = pascalCase(
60
+ `${context.discriminatorParent}_${context.discriminatorValue}`,
61
+ { normalize: true }
62
+ );
63
+ return validVarName(name2);
64
+ }
65
+ const name = pascalCase(context.instancePath.split("/").join("_"), {
66
+ normalize: true
67
+ });
68
+ return validVarName(name);
69
+ }
70
+
71
+ function tsArrayFromSchema(schema, context) {
72
+ const innerType = tsTypeFromSchema(schema.elements, {
73
+ clientName: context.clientName,
74
+ typePrefix: context.typePrefix,
75
+ generatedTypes: context.generatedTypes,
76
+ instancePath: `${context.instancePath}/[element]`,
77
+ schemaPath: `${context.schemaPath}/elements`,
78
+ discriminatorParent: "",
79
+ discriminatorKey: "",
80
+ discriminatorValue: "",
81
+ versionNumber: context.versionNumber,
82
+ usedFeatures: context.usedFeatures
83
+ });
84
+ const typeName = `(${innerType.typeName})[]`;
85
+ const defaultValue = schema.nullable ? "null" : "[]";
86
+ return {
87
+ typeName: schema.nullable ? `${typeName} | null` : typeName,
88
+ defaultValue,
89
+ validationTemplate(input) {
90
+ const mainPart = `Array.isArray(${input})
91
+ && ${input}.every(
92
+ (_element) => ${innerType.validationTemplate("_element")}
93
+ )`;
94
+ if (schema.nullable) {
95
+ return `((${mainPart}) ||
96
+ ${input} === null)`;
97
+ }
98
+ return mainPart;
99
+ },
100
+ fromJsonTemplate(input, target) {
101
+ return `if (Array.isArray(${input})) {
102
+ ${target} = [];
103
+ for (const ${target}El of ${input}) {
104
+ let ${target}ElValue: ${innerType.typeName};
105
+ ${innerType.fromJsonTemplate(`${target}El`, `${target}ElValue`)}
106
+ ${target}.push(${target}ElValue);
107
+ }
108
+ } else {
109
+ ${target} = ${defaultValue};
110
+ }`;
111
+ },
112
+ toJsonTemplate(input, target) {
113
+ const elVar = `_${camelCase(validVarName(input.split(".").join("_")), { normalize: true })}El`;
114
+ const elKeyVar = `${elVar}Key`;
115
+ if (schema.nullable) {
116
+ return `if (${input} !== null) {
117
+ ${target} += '[';
118
+ for (let i = 0; i < ${input}.length; i++) {
119
+ if (i !== 0) ${target} += ',';
120
+ const ${elVar} = ${input}[i];
121
+ ${innerType.toJsonTemplate(elVar, target, elKeyVar)}
122
+ }
123
+ ${target} += ']';
124
+ } else {
125
+ ${target} += 'null';
126
+ }`;
127
+ }
128
+ return `${target} += '[';
129
+ for (let i = 0; i < ${input}.length; i++) {
130
+ if (i !== 0) ${target} += ',';
131
+ const ${elVar} = ${input}[i];
132
+ ${innerType.toJsonTemplate(elVar, target, elKeyVar)}
133
+ }
134
+ ${target} += ']';`;
135
+ },
136
+ toQueryStringTemplate(_input, _target, _key) {
137
+ return `console.warn('[WARNING] Cannot serialize arrays to query string. Skipping property at ${context.instancePath}.')`;
138
+ },
139
+ content: innerType.content
140
+ };
478
141
  }
479
- function tsObjectFromJtdSchema(nodePath, def, options, additionalOptions) {
480
- const typeName = getTypeName(nodePath, def);
481
- const key = nodePath.split(".").pop() ?? "";
482
- const fieldNames = [];
142
+
143
+ function tsObjectFromSchema(schema, context) {
144
+ const typeName = getTsTypeName(schema, context);
145
+ const prefixedTypeName = `${context.typePrefix}${typeName}`;
146
+ const defaultValue = schema.nullable ? "null" : `$$${prefixedTypeName}.new()`;
147
+ const result = {
148
+ typeName: schema.nullable ? `${prefixedTypeName} | null` : prefixedTypeName,
149
+ defaultValue,
150
+ validationTemplate(input) {
151
+ if (schema.nullable) {
152
+ return `($$${prefixedTypeName}.validate(${input}) || ${input} === null)`;
153
+ }
154
+ return `$$${prefixedTypeName}.validate(${input})`;
155
+ },
156
+ fromJsonTemplate(input, target) {
157
+ return `if (isObject(${input})) {
158
+ ${target} = $$${prefixedTypeName}.fromJson(${input});
159
+ } else {
160
+ ${target} = ${defaultValue};
161
+ }`;
162
+ },
163
+ toJsonTemplate(input, target) {
164
+ if (schema.nullable) {
165
+ return `if (${input} !== null) {
166
+ ${target} += $$${prefixedTypeName}.toJsonString(${input});
167
+ } else {
168
+ ${target} += 'null';
169
+ }`;
170
+ }
171
+ return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
172
+ },
173
+ toQueryStringTemplate(_input, _target) {
174
+ return `console.warn("[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.")`;
175
+ },
176
+ content: ""
177
+ };
178
+ if (context.generatedTypes.includes(typeName)) {
179
+ return result;
180
+ }
181
+ const newParts = [];
483
182
  const fieldParts = [];
183
+ const fromJsonParts = [];
184
+ const constructionParts = [];
185
+ const toJsonParts = [];
186
+ const toQueryParts = [];
187
+ const validationParts = ["isObject(input)"];
484
188
  const subContentParts = [];
485
- const parserParts = [];
486
- if (additionalOptions.discriminatorKey && additionalOptions.discriminatorKeyValue) {
487
- parserParts.push(
488
- `${additionalOptions.discriminatorKey}: '${additionalOptions.discriminatorKeyValue}'`
189
+ let hasKey = false;
190
+ if (context.discriminatorParent && context.discriminatorValue && context.discriminatorKey) {
191
+ hasKey = true;
192
+ const key = validVarName(
193
+ camelCase(context.discriminatorKey, { normalize: true })
489
194
  );
490
- fieldParts.push(
491
- `${additionalOptions.discriminatorKey}: "${additionalOptions.discriminatorKeyValue}"`
195
+ fieldParts.push(`${key}: "${context.discriminatorValue}",`);
196
+ fromJsonParts.push(`const _${key} = "${context.discriminatorValue}"`);
197
+ toJsonParts.push(`json += '"${key}":"${context.discriminatorValue}"'`);
198
+ toQueryParts.push(
199
+ `queryParts.push('${context.discriminatorKey}=${context.discriminatorValue}');`
492
200
  );
493
- fieldNames.push(additionalOptions.discriminatorKey);
201
+ validationParts.push(
202
+ `input.${key} === '${context.discriminatorValue}'`
203
+ );
204
+ newParts.push(`${key}: "${context.discriminatorValue}",`);
205
+ constructionParts.push(`${key}: _${key},`);
494
206
  }
495
- if (def.properties) {
496
- for (const propKey of Object.keys(def.properties)) {
497
- const propSchema = def.properties[propKey];
498
- const type = tsTypeFromJtdSchema(
499
- `${nodePath}.${propKey}`,
500
- propSchema,
501
- options,
502
- {
503
- isOptional: false,
504
- existingTypeNames: additionalOptions.existingTypeNames
505
- }
506
- );
507
- parserParts.push(
508
- `${propKey}: ${type.fromJsonTemplate(`input.${propKey}`)}`
509
- );
510
- fieldParts.push(type.fieldTemplate);
511
- fieldNames.push(propKey);
512
- if (type.content) {
513
- subContentParts.push(type.content);
514
- }
207
+ for (const key of Object.keys(schema.properties)) {
208
+ const subSchema = schema.properties[key];
209
+ const prop = tsTypeFromSchema(subSchema, {
210
+ clientName: context.clientName,
211
+ typePrefix: context.typePrefix,
212
+ generatedTypes: context.generatedTypes,
213
+ instancePath: `/${typeName}/${key}`,
214
+ schemaPath: `/${typeName}/properties/${key}`,
215
+ discriminatorParent: "",
216
+ discriminatorKey: "",
217
+ discriminatorValue: "",
218
+ versionNumber: context.versionNumber,
219
+ usedFeatures: context.usedFeatures
220
+ });
221
+ if (prop.content)
222
+ subContentParts.push(prop.content);
223
+ const fieldName = validVarName(camelCase(key));
224
+ fieldParts.push(`${fieldName}: ${prop.typeName},`);
225
+ newParts.push(`${fieldName}: ${prop.defaultValue},`);
226
+ const tempKey = `_${validVarName(key)}`;
227
+ fromJsonParts.push(`let ${tempKey}: ${prop.typeName};`);
228
+ fromJsonParts.push(prop.fromJsonTemplate(`input.${key}`, tempKey));
229
+ if (hasKey) {
230
+ toJsonParts.push(`json += ',"${key}":';`);
231
+ } else {
232
+ toJsonParts.push(`json += '"${key}":';`);
515
233
  }
234
+ toJsonParts.push(
235
+ prop.toJsonTemplate(`input.${fieldName}`, "json", key)
236
+ );
237
+ toQueryParts.push(
238
+ prop.toQueryStringTemplate(`input.${fieldName}`, "queryParts", key)
239
+ );
240
+ const validationPart = prop.validationTemplate(`input.${fieldName}`);
241
+ validationParts.push(validationPart);
242
+ constructionParts.push(`${fieldName}: ${tempKey},`);
243
+ hasKey = true;
516
244
  }
517
- if (def.optionalProperties) {
518
- for (const propKey of Object.keys(def.optionalProperties)) {
519
- const propSchema = def.optionalProperties[propKey];
520
- const type = tsTypeFromJtdSchema(
521
- `${nodePath}.${propKey}`,
522
- propSchema,
523
- options,
524
- {
525
- isOptional: true,
526
- existingTypeNames: additionalOptions.existingTypeNames
527
- }
528
- );
529
- parserParts.push(
530
- `${propKey}: ${type.fromJsonTemplate(`input.${propKey}`)}`
531
- );
532
- fieldParts.push(type.fieldTemplate);
533
- fieldNames.push(propKey);
534
- if (type.content) {
535
- subContentParts.push(type.content);
536
- }
245
+ if (!hasKey) {
246
+ toJsonParts.push(`let _hasKey = false;`);
247
+ }
248
+ for (const key of Object.keys(schema.optionalProperties ?? {})) {
249
+ const subSchema = schema.optionalProperties[key];
250
+ const prop = tsTypeFromSchema(subSchema, {
251
+ clientName: context.clientName,
252
+ typePrefix: context.typePrefix,
253
+ generatedTypes: context.generatedTypes,
254
+ instancePath: `/${typeName}/${key}`,
255
+ schemaPath: `/${typeName}/optionalProperties/${key}`,
256
+ discriminatorParent: "",
257
+ discriminatorKey: "",
258
+ discriminatorValue: "",
259
+ versionNumber: context.versionNumber,
260
+ usedFeatures: context.usedFeatures
261
+ });
262
+ if (prop.content)
263
+ subContentParts.push(prop.content);
264
+ const fieldName = validVarName(camelCase(key));
265
+ fieldParts.push(`${fieldName}?: ${prop.typeName},`);
266
+ const tempKey = `_${validVarName(key)}`;
267
+ fromJsonParts.push(`let ${tempKey}: ${prop.typeName} | undefined;`);
268
+ fromJsonParts.push(`if (typeof input.${key} !== 'undefined') {
269
+ ${prop.fromJsonTemplate(`input.${key}`, tempKey)}
270
+ }`);
271
+ if (hasKey) {
272
+ toJsonParts.push(`if (typeof input.${key} !== 'undefined') {
273
+ json += \`,"${key}":\`;
274
+ ${prop.toJsonTemplate(`input.${key}`, "json", key)}
275
+ }`);
276
+ } else {
277
+ toJsonParts.push(`if (typeof input.${key} !== 'undefined') {
278
+ if (_hasKey) json += ',';
279
+ json += '"${key}":';
280
+ ${prop.toJsonTemplate(`input.${key}`, "json", key)}
281
+ _hasKey = true;
282
+ }`);
537
283
  }
284
+ toQueryParts.push(`if (typeof input.${fieldName} !== 'undefined') {
285
+ ${prop.toQueryStringTemplate(`input.${fieldName}`, "queryParts", key)}
286
+ }`);
287
+ const validationPart = prop.validationTemplate(`input.${fieldName}`);
288
+ validationParts.push(
289
+ `((${validationPart}) || typeof input.${fieldName} === 'undefined')`
290
+ );
291
+ constructionParts.push(`${fieldName}: ${tempKey},`);
538
292
  }
539
- let content = "";
540
- const modifiedDef = {
541
- ...def,
542
- metadata: {
543
- id: def.metadata?.id ?? typeName,
544
- description: def.metadata?.description
545
- },
546
- nullable: false
293
+ result.content = `${getJsDocComment(schema.metadata)}export interface ${prefixedTypeName} {
294
+ ${fieldParts.map((part) => ` ${part}`).join("\n")}
295
+ }
296
+ ${context.discriminatorParent && context.discriminatorValue ? "" : "export "}const $$${prefixedTypeName}: ArriModelValidator<${prefixedTypeName}> = {
297
+ new(): ${prefixedTypeName} {
298
+ return {
299
+ ${newParts.map((part) => ` ${part}`).join("\n")}
300
+ };
301
+ },
302
+ validate(input): input is ${prefixedTypeName} {
303
+ return (
304
+ ${validationParts.map((part) => ` ${part}`).join("&& \n")}
305
+ )
306
+ },
307
+ fromJson(input): ${prefixedTypeName} {
308
+ ${fromJsonParts.map((part) => ` ${part}`).join("\n")}
309
+ return {
310
+ ${constructionParts.map((part) => ` ${part}`).join("\n")}
311
+ }
312
+ },
313
+ fromJsonString(input): ${prefixedTypeName} {
314
+ return $$${prefixedTypeName}.fromJson(JSON.parse(input));
315
+ },
316
+ toJsonString(input): string {
317
+ let json = "{";
318
+ ${toJsonParts.map((part) => ` ${part}`).join("\n")}
319
+ json += "}";
320
+ return json;
321
+ },
322
+ toUrlQueryString(input): string {
323
+ const queryParts: string[] = [];
324
+ ${toQueryParts.map((part) => ` ${part}`).join("\n")}
325
+ return queryParts.join("&");
326
+ }
327
+ }
328
+
329
+ ${subContentParts.join("\n")}`;
330
+ context.generatedTypes.push(typeName);
331
+ return result;
332
+ }
333
+
334
+ function tsTaggedUnionFromSchema(schema, context) {
335
+ const typeName = getTsTypeName(schema, context);
336
+ const prefixedTypeName = `${context.typePrefix}${typeName}`;
337
+ const defaultValue = schema.nullable ? "null" : `$$${prefixedTypeName}.new()`;
338
+ const result = {
339
+ typeName: schema.nullable ? `${prefixedTypeName} | null` : prefixedTypeName,
340
+ defaultValue,
341
+ validationTemplate(input) {
342
+ const mainPart = `$$${prefixedTypeName}.validate(${input})`;
343
+ if (schema.nullable) {
344
+ return `(${mainPart} || ${input} === null)`;
345
+ }
346
+ return mainPart;
347
+ },
348
+ fromJsonTemplate(input, target) {
349
+ return `if (isObject(${input})) {
350
+ ${target} = $$${prefixedTypeName}.fromJson(${input});
351
+ } else {
352
+ ${target} = ${defaultValue};
353
+ }`;
354
+ },
355
+ toJsonTemplate(input, target, _key) {
356
+ if (schema.nullable) {
357
+ return `if (${input} != null) {
358
+ ${target} += $$${prefixedTypeName}.toJsonString(${input});
359
+ } else {
360
+ ${target} += 'null';
361
+ }`;
362
+ }
363
+ return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
364
+ },
365
+ toQueryStringTemplate(_, __, ___) {
366
+ return `console.warn("[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.");`;
367
+ },
368
+ content: ""
547
369
  };
548
- let validatorPart = "";
549
- if (options.typesNeedingParser.includes(typeName)) {
550
- const parsingCode = getSchemaParsingCode("input", modifiedDef);
551
- const serializationCode = getSchemaSerializationCode(
552
- "input",
553
- modifiedDef
554
- );
555
- validatorPart = `export const $$${typeName} = {
556
- parse(input: Record<any, any>): ${typeName} {
557
- ${parsingCode}
558
- },
559
- serialize(input: ${typeName}): string {
560
- ${serializationCode}
561
- }
562
- }`;
370
+ if (context.generatedTypes.includes(typeName))
371
+ return result;
372
+ const subTypes = [];
373
+ const discriminatorKey = schema.discriminator;
374
+ for (const key of Object.keys(schema.mapping)) {
375
+ const subSchema = schema.mapping[key];
376
+ const subType = tsObjectFromSchema(subSchema, {
377
+ clientName: context.clientName,
378
+ typePrefix: context.typePrefix,
379
+ generatedTypes: context.generatedTypes,
380
+ instancePath: context.instancePath,
381
+ schemaPath: `${context.schemaPath}/mapping/${key}`,
382
+ discriminatorParent: typeName,
383
+ discriminatorKey,
384
+ discriminatorValue: key,
385
+ versionNumber: context.versionNumber,
386
+ usedFeatures: context.usedFeatures
387
+ });
388
+ subTypes.push({ value: key, data: subType });
563
389
  }
564
- if (!additionalOptions.existingTypeNames.includes(typeName)) {
565
- content = `${getJsDocComment(def.metadata)}export interface ${typeName} {
566
- ${fieldParts.join(";\n ")};
390
+ result.content = `export type ${prefixedTypeName} = ${subTypes.map((type) => type.data.typeName).join(" |")};
391
+ export const $$${prefixedTypeName}: ArriModelValidator<${prefixedTypeName}> = {
392
+ new(): ${prefixedTypeName} {
393
+ return $$${subTypes[0].data.typeName}.new();
394
+ },
395
+ validate(input): input is ${prefixedTypeName} {
396
+ if (!isObject(input)) {
397
+ return false;
398
+ }
399
+ if (typeof input.${discriminatorKey} !== 'string') {
400
+ return false;
401
+ }
402
+ switch (input.${discriminatorKey}) {
403
+ ${subTypes.map(
404
+ (type) => ` case "${type.value}":
405
+ return $$${type.data.typeName}.validate(input);`
406
+ ).join("\n")}
407
+ default:
408
+ return false;
409
+ }
410
+ },
411
+ fromJson(input): ${prefixedTypeName} {
412
+ switch (input.${discriminatorKey}) {
413
+ ${subTypes.map(
414
+ (type) => ` case "${type.value}":
415
+ return $$${type.data.typeName}.fromJson(input);`
416
+ ).join("\n")}
417
+ default:
418
+ return $$${subTypes[0].data.typeName}.new();
419
+ }
420
+ },
421
+ fromJsonString(input): ${prefixedTypeName} {
422
+ return $$${prefixedTypeName}.fromJson(JSON.parse(input));
423
+ },
424
+ toJsonString(input): string {
425
+ switch (input.${discriminatorKey}) {
426
+ ${subTypes.map(
427
+ (type) => ` case "${type.value}":
428
+ return $$${type.data.typeName}.toJsonString(input);`
429
+ ).join("\n")}
430
+ default:
431
+ throw new Error(\`Unhandled case "\${(input as any).${discriminatorKey}}"\`);
432
+ }
433
+ },
434
+ toUrlQueryString(input): string {
435
+ switch (input.${discriminatorKey}) {
436
+ ${subTypes.map(
437
+ (type) => ` case "${type.value}":
438
+ return $$${type.data.typeName}.toUrlQueryString(input);`
439
+ ).join("\n")}
440
+ default:
441
+ throw new Error('Unhandled case');
442
+ }
567
443
  }
568
- ${validatorPart}
569
- ${subContentParts.join("\n")}`;
570
- additionalOptions.existingTypeNames.push(typeName);
444
+ }
445
+ ${subTypes.map((type) => type.data.content).join("\n")}
446
+ `;
447
+ context.generatedTypes.push(typeName);
448
+ return result;
449
+ }
450
+
451
+ function tsEnumFromSchema(schema, context) {
452
+ if (schema.enum.length === 0)
453
+ throw new Error(
454
+ `Error at ${context.schemaPath}. Enum schemas must have at least one enum value.`
455
+ );
456
+ const enumName = getTsTypeName(schema, context);
457
+ const prefixedEnumName = `${context.typePrefix}${enumName}`;
458
+ const typeName = schema.nullable ? `${prefixedEnumName} | null` : prefixedEnumName;
459
+ const defaultValue = schema.nullable ? "null" : `$$${prefixedEnumName}.new()`;
460
+ const result = {
461
+ typeName,
462
+ defaultValue,
463
+ validationTemplate(input) {
464
+ if (schema.nullable) {
465
+ return `($$${prefixedEnumName}.validate(${input}) || ${input} === null)`;
466
+ }
467
+ return `$$${prefixedEnumName}.validate(${input})`;
468
+ },
469
+ fromJsonTemplate(input, target) {
470
+ return `if (typeof ${input} === 'string') {
471
+ ${target} = $$${prefixedEnumName}.fromSerialValue(${input});
472
+ } else {
473
+ ${target} = ${defaultValue};
474
+ }`;
475
+ },
476
+ toJsonTemplate(input, target) {
477
+ if (schema.nullable) {
478
+ return `if (typeof ${input} === 'string') {
479
+ ${target} += \`"\${${input}}"\`;
480
+ } else {
481
+ ${target} += 'null';
482
+ }`;
483
+ }
484
+ return `${target} += \`"\${${input}}"\``;
485
+ },
486
+ toQueryStringTemplate(input, target, key) {
487
+ return `${target}.push(\`${key}=\${${input}}\`)`;
488
+ },
489
+ content: ""
490
+ };
491
+ if (context.generatedTypes.includes(enumName)) {
492
+ return result;
571
493
  }
494
+ const name = `${context.typePrefix}${enumName}`;
495
+ const valuesName = `$$${name}Values`;
496
+ result.content = `${getJsDocComment(schema.metadata)}export type ${name} = (typeof ${valuesName})[number];
497
+ const ${valuesName} = [${schema.enum.map((val) => `"${val}"`).join(", ")}] as const;
498
+ export const $$${name}: ArriEnumValidator<${name}> = {
499
+ new(): ${name} {
500
+ return ${valuesName}[0];
501
+ },
502
+ validate(input): input is ${enumName} {
503
+ return (
504
+ typeof input === 'string' &&
505
+ ${valuesName}.includes(input as any)
506
+ );
507
+ },
508
+ values: ${valuesName},
509
+ fromSerialValue(input): ${name} {
510
+ if (${valuesName}.includes(input as any)) {
511
+ return input as ${name};
512
+ }
513
+ if (${valuesName}.includes(input.toLowerCase() as any)) {
514
+ return input.toLowerCase() as ${name};
515
+ }
516
+ if (${valuesName}.includes(input.toUpperCase() as any)) {
517
+ return input.toUpperCase() as ${name};
518
+ }
519
+ return "${schema.enum[0]}";
520
+ }
521
+ }
522
+ `;
523
+ context.generatedTypes.push(enumName);
524
+ return result;
525
+ }
526
+
527
+ function tsStringFromSchema(schema, _context) {
528
+ const typeName = schema.nullable ? "string | null" : "string";
529
+ const defaultValue = schema.nullable ? "null" : '""';
572
530
  return {
573
- tsType: typeName,
574
- schema: def,
575
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
576
- key,
577
- additionalOptions.isOptional
578
- )}: ${maybeNullType(typeName, def.nullable ?? false)}`,
579
- fromJsonTemplate(input) {
580
- if (additionalOptions.isOptional) {
581
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : undefined`;
531
+ typeName,
532
+ defaultValue,
533
+ validationTemplate(input) {
534
+ if (schema.nullable) {
535
+ return `(typeof ${input} === 'string' || ${input} === null)`;
582
536
  }
583
- if (def.nullable) {
584
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : null`;
537
+ return `typeof ${input} === 'string'`;
538
+ },
539
+ fromJsonTemplate(input, target) {
540
+ return `if (typeof ${input} === 'string') {
541
+ ${target} = ${input};
542
+ } else {
543
+ ${target} = ${defaultValue};
544
+ }`;
545
+ },
546
+ toJsonTemplate(input, target) {
547
+ if (schema.nullable) {
548
+ return `if (typeof ${input} === 'string') {
549
+ ${target} += serializeString(${input});
550
+ } else {
551
+ ${target} += 'null';
552
+ }`;
585
553
  }
586
- return `$$${typeName}.parse(${input})`;
554
+ return `${target} += serializeString(${input});`;
587
555
  },
588
- toJsonTemplate(input) {
589
- return `JSON.stringify(${input})`;
556
+ toQueryStringTemplate(input, target, key) {
557
+ return `${target}.push(\`${key}=\${${input}}\`);`;
590
558
  },
591
- content
559
+ content: ""
592
560
  };
593
561
  }
594
- function tsEnumFromJtdSchema(nodePath, def, options, additionalOptions) {
595
- const keyName = nodePath.split(".").pop() ?? "";
596
- const typeName = getTypeName(nodePath, def);
597
- let content = `${getJsDocComment(def.metadata)}export type ${typeName} = ${def.enum.map((val) => `"${val}"`).join(" | ")}`;
598
- if (additionalOptions.existingTypeNames.includes(typeName)) {
599
- content = "";
600
- } else {
601
- additionalOptions.existingTypeNames.push(typeName);
602
- }
562
+ function tsBooleanFromSchema(schema, _context) {
563
+ const typeName = schema.nullable ? `boolean | null` : "boolean";
564
+ const defaultValue = schema.nullable ? `null` : "false";
603
565
  return {
604
- tsType: typeName,
605
- schema: def,
606
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
607
- keyName,
608
- additionalOptions.isOptional
609
- )}: ${maybeNullType(typeName, def.nullable ?? false)}`,
610
- fromJsonTemplate(input) {
611
- if (additionalOptions.isOptional) {
612
- return `typeof ${input} === 'string' ? $$${typeName}.parse(${input}) : undefined`;
613
- }
614
- if (def.nullable) {
615
- return `typeof ${input} === 'string' ? $$${typeName}.parse(${input}) : null`;
566
+ typeName,
567
+ defaultValue,
568
+ validationTemplate(input) {
569
+ if (schema.nullable) {
570
+ return `(typeof ${input} === 'boolean' || ${input} === null)`;
616
571
  }
617
- return `$$${typeName}.parse(${input})`;
572
+ return `typeof ${input} === 'boolean'`;
618
573
  },
619
- toJsonTemplate(input) {
620
- return `JSON.stringify(${input})`;
574
+ fromJsonTemplate(input, target) {
575
+ return `if (typeof ${input} === 'boolean') {
576
+ ${target} = ${input};
577
+ } else {
578
+ ${target} = ${defaultValue};
579
+ }`;
621
580
  },
622
- content
581
+ toJsonTemplate(input, target) {
582
+ return `${target} += \`\${${input}}\`;`;
583
+ },
584
+ toQueryStringTemplate(input, target, key) {
585
+ return `${target}.push(\`${key}=\${${input}}\`);`;
586
+ },
587
+ content: ""
623
588
  };
624
589
  }
625
- function tsArrayFromJtdSchema(nodePath, def, options, additionalOptions) {
626
- const key = nodePath.split(".").pop() ?? "";
627
- const subType = tsTypeFromJtdSchema(
628
- `${nodePath}.item`,
629
- def.elements,
630
- options,
631
- {
632
- isOptional: false,
633
- existingTypeNames: additionalOptions.existingTypeNames
634
- }
635
- );
636
- const tsType = `Array<${maybeNullType(
637
- subType.tsType,
638
- def.elements.nullable
639
- )}>`;
590
+ function tsDateFromSchema(schema, _context) {
591
+ const typeName = schema.nullable ? `Date | null` : "Date";
592
+ const defaultValue = schema.nullable ? `null` : "new Date()";
640
593
  return {
641
- tsType,
642
- schema: def,
643
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
644
- key,
645
- additionalOptions.isOptional ?? false
646
- )}: ${maybeNullType(tsType, def.nullable ?? false)}`,
647
- fromJsonTemplate(input) {
648
- if (additionalOptions.isOptional) {
649
- return `Array.isArray(${input}) ? ${input}.map((item) => ${subType.fromJsonTemplate(
650
- "item"
651
- )}) : undefined`;
594
+ typeName,
595
+ defaultValue,
596
+ validationTemplate(input) {
597
+ if (schema.nullable) {
598
+ return `(${input} instanceof Date || ${input} === null)`;
652
599
  }
653
- if (def.nullable) {
654
- return `Array.isArray(${input}) ? ${input}.map((item) => ${subType.fromJsonTemplate(
655
- "item"
656
- )}) : null`;
600
+ return `${input} instanceof Date`;
601
+ },
602
+ fromJsonTemplate(input, target) {
603
+ return `if (typeof ${input} === 'string') {
604
+ ${target} = new Date(${input});
605
+ } else if (${input} instanceof Date) {
606
+ ${target} = ${input};
607
+ } else {
608
+ ${target} = ${defaultValue}
609
+ }`;
610
+ },
611
+ toJsonTemplate(input, target) {
612
+ if (schema.nullable) {
613
+ return `if (${input} instanceof Date) {
614
+ ${target} += \`"\${${input}.toISOString()}"\`
615
+ } else {
616
+ ${target} += 'null';
617
+ }`;
657
618
  }
658
- return `Array.isArray(${input}) ? ${input}.map((item) => ${subType.fromJsonTemplate(
659
- "item"
660
- )}) : []`;
619
+ return `${target} += \`"\${${input}.toISOString()}"\``;
661
620
  },
662
- toJsonTemplate(input) {
663
- return `JSON.stringify(${input})`;
621
+ toQueryStringTemplate(input, target, key) {
622
+ if (schema.nullable) {
623
+ return `${target}.push(\`${key}=\${${input}?.toISOString()}\`)`;
624
+ }
625
+ return `${target}.push(\`${key}=\${${input}.toISOString()}\`)`;
664
626
  },
665
- content: subType.content
627
+ content: ""
666
628
  };
667
629
  }
668
- function tsDiscriminatedUnionFromJtdSchema(nodePath, def, options, additionalOptions) {
669
- const key = nodePath.split(".").pop() ?? "";
670
- const typeName = getTypeName(nodePath, def);
671
- const subTypeNames = [];
672
- const subContentParts = [];
673
- const parserParts = [];
674
- for (const val of Object.keys(def.mapping)) {
675
- const optionSchema = def.mapping[val];
676
- if (!isSchemaFormProperties(optionSchema)) {
677
- continue;
678
- }
679
- const optionType = tsObjectFromJtdSchema(
680
- `${nodePath}.${camelCase(val.toLowerCase())}`,
681
- optionSchema,
682
- options,
683
- {
684
- discriminatorKey: def.discriminator,
685
- discriminatorKeyValue: val,
686
- isOptional: false,
687
- existingTypeNames: additionalOptions.existingTypeNames
630
+ function tsFloatFromSchema(schema, _context) {
631
+ const typeName = schema.nullable ? "number | null" : "number";
632
+ const defaultValue = schema.nullable ? "null" : "0";
633
+ return {
634
+ typeName,
635
+ defaultValue,
636
+ validationTemplate(input) {
637
+ if (schema.nullable) {
638
+ return `(typeof ${input} === 'number' || ${input} === null)`;
688
639
  }
689
- );
690
- parserParts.push(`case '${val}':
691
- return $$${optionType.tsType}.parse(input);`);
692
- subTypeNames.push(optionType.tsType);
693
- subContentParts.push(optionType.content);
694
- }
695
- let validatorPart = "";
696
- if (options.typesNeedingParser.includes(typeName)) {
697
- const modifiedDef = { ...def, nullable: false };
698
- const parsingCode = getSchemaParsingCode(
699
- "input",
700
- modifiedDef
701
- );
702
- const serializationCode = getSchemaSerializationCode(
703
- "input",
704
- modifiedDef
705
- );
706
- validatorPart = `export const $$${typeName} = {
707
- parse(input: Record<any, any>): ${typeName} {
708
- ${parsingCode}
640
+ return `typeof ${input} === 'number'`;
709
641
  },
710
- serialize(input: ${typeName}): string {
711
- ${serializationCode}
712
- }
713
- }`;
714
- }
715
- let content = `${getJsDocComment(def.metadata)}export type ${typeName} = ${subTypeNames.join(" | ")};
716
- ${validatorPart}
717
- ${subContentParts.join("\n")}`;
718
- if (additionalOptions.existingTypeNames.includes(typeName)) {
719
- content = "";
720
- } else {
721
- additionalOptions.existingTypeNames.push(typeName);
642
+ fromJsonTemplate(input, target) {
643
+ return `if (typeof ${input} === 'number') {
644
+ ${target} = ${input};
645
+ } else {
646
+ ${target} = ${defaultValue};
647
+ }`;
648
+ },
649
+ toJsonTemplate(input, target) {
650
+ return `${target} += \`\${${input}}\``;
651
+ },
652
+ toQueryStringTemplate(input, target, key) {
653
+ return `${target}.push(\`${key}=\${${input}}\`)`;
654
+ },
655
+ content: ""
656
+ };
657
+ }
658
+ function tsIntFromSchema(schema, intType, _context) {
659
+ const typeName = schema.nullable ? "number | null" : "number";
660
+ const defaultValue = schema.nullable ? "null" : "0";
661
+ let min;
662
+ let max;
663
+ switch (intType) {
664
+ case "int8":
665
+ min = "INT8_MIN";
666
+ max = "INT8_MAX";
667
+ break;
668
+ case "uint8":
669
+ min = "0";
670
+ max = "UINT8_MAX";
671
+ break;
672
+ case "int16":
673
+ min = "INT16_MIN";
674
+ max = "INT16_MAX";
675
+ break;
676
+ case "uint16":
677
+ min = "0";
678
+ max = "UINT16_MAX";
679
+ break;
680
+ case "int32":
681
+ min = "INT32_MIN";
682
+ max = "INT32_MAX";
683
+ break;
684
+ case "uint32":
685
+ min = "0";
686
+ max = "UINT32_MAX";
687
+ break;
722
688
  }
723
689
  return {
724
- tsType: typeName,
725
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
726
- key,
727
- additionalOptions.isOptional
728
- )}: ${maybeNullType(typeName, def.nullable)}`,
729
- schema: def,
730
- fromJsonTemplate(input) {
731
- if (additionalOptions.isOptional) {
732
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : undefined`;
690
+ typeName,
691
+ defaultValue,
692
+ validationTemplate(input) {
693
+ const mainPart = `typeof ${input} === 'number' && Number.isInteger(${input}) && ${input} >= ${min} && ${input} <= ${max}`;
694
+ if (schema.nullable) {
695
+ return `((${mainPart}) || ${input} === null)`;
733
696
  }
734
- if (def.nullable) {
735
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : null`;
736
- }
737
- return `$$${typeName}.parse(${input})`;
697
+ return mainPart;
698
+ },
699
+ fromJsonTemplate(input, target) {
700
+ return `if (
701
+ typeof ${input} === 'number' &&
702
+ Number.isInteger(${input}) &&
703
+ ${input} >= ${min} &&
704
+ ${input} <= ${max}
705
+ ) {
706
+ ${target} = ${input};
707
+ } else {
708
+ ${target} = ${defaultValue};
709
+ }`;
710
+ },
711
+ toJsonTemplate(input, target) {
712
+ return `${target} += \`\${${input}}\``;
738
713
  },
739
- toJsonTemplate(input) {
740
- return `JSON.stringify(${input})`;
714
+ toQueryStringTemplate(input, target, key) {
715
+ return `${target}.push(\`${key}=\${${input}}\`)`;
741
716
  },
742
- content
717
+ content: ""
743
718
  };
744
719
  }
745
- function tsRecordFromJtdSchema(nodePath, def, options, additionalOptions) {
746
- const key = nodePath.split(".").pop() ?? "";
747
- const typeName = getTypeName(nodePath, def);
748
- const subType = tsTypeFromJtdSchema(
749
- `${nodePath}.value`,
750
- def.values,
751
- options,
752
- {
753
- isOptional: false,
754
- existingTypeNames: additionalOptions.existingTypeNames
755
- }
756
- );
757
- let content = "";
758
- if (!additionalOptions.existingTypeNames.includes(typeName)) {
759
- let validatorPart = "";
760
- if (options.typesNeedingParser.includes(typeName)) {
761
- const modifiedDef = { ...def, nullable: false };
762
- const parsingCode = getSchemaParsingCode(
763
- "input",
764
- modifiedDef
765
- );
766
- const serializationCode = getSchemaSerializationCode(
767
- "input",
768
- modifiedDef
769
- );
770
- validatorPart = `export const $$${typeName} = {
771
- parse(input: Record<any, any>): ${typeName} {
772
- ${parsingCode}
773
- },
774
- serialize(input: ${typeName}): string {
775
- ${serializationCode}
776
- }
777
- }`;
778
- }
779
- content = `${getJsDocComment(def.metadata)}export type ${typeName} = Record<string, ${subType.tsType}>;
780
- ${validatorPart}
781
- ${subType.content}`;
782
- }
720
+ function tsBigIntFromSchema(schema, isUnsigned, _context) {
721
+ const typeName = schema.nullable ? `bigint | null` : `bigint`;
722
+ const defaultValue = schema.nullable ? `null` : "BigInt(0)";
783
723
  return {
784
- tsType: typeName,
785
- schema: def,
786
- fieldTemplate: `${getJsDocComment(def.metadata)}${maybeOptionalKey(
787
- key,
788
- additionalOptions.isOptional
789
- )}: ${maybeNullType(typeName, def.nullable)}`,
790
- fromJsonTemplate(input) {
791
- if (additionalOptions.isOptional) {
792
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : undefined`;
724
+ typeName,
725
+ defaultValue,
726
+ validationTemplate(input) {
727
+ const mainPart = isUnsigned ? `typeof ${input} === 'bigint' && ${input} >= BigInt(0) && ${input} <= UINT64_MAX` : `typeof ${input} === 'bigint' && ${input} >= INT64_MIN && ${input} <= INT64_MAX`;
728
+ if (schema.nullable) {
729
+ return `((${mainPart}) || ${input} === null)`;
793
730
  }
794
- if (def.nullable) {
795
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : null`;
731
+ return mainPart;
732
+ },
733
+ fromJsonTemplate(input, target) {
734
+ if (isUnsigned) {
735
+ return `if (typeof ${input} === 'string' && BigInt(${input}) >= BigInt(0)) {
736
+ ${target} = BigInt(${input});
737
+ } else if (typeof ${input} === 'bigint' && ${input} >= BigInt(0)) {
738
+ ${target} = ${input};
739
+ } else {
740
+ ${target} = ${defaultValue};
741
+ }`;
796
742
  }
797
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : {}`;
743
+ return `if (typeof ${input} === 'string') {
744
+ ${target} = BigInt(${input});
745
+ } else if (typeof ${input} === 'bigint') {
746
+ ${target} = ${input};
747
+ } else {
748
+ ${target} = ${defaultValue};
749
+ }`;
798
750
  },
799
- toJsonTemplate(input) {
800
- return `JSON.stringify(${input})`;
751
+ toJsonTemplate(input, target) {
752
+ if (schema.nullable) {
753
+ return `if (typeof ${input} === 'bigint') {
754
+ ${target} += \`"\${${input}}"\`
755
+ } else {
756
+ ${target} += 'null';
757
+ }`;
758
+ }
759
+ return `${target} += \`"\${${input}}"\``;
801
760
  },
802
- content
761
+ toQueryStringTemplate(input, target, key) {
762
+ return `${target}.push(\`${key}=\${${input}}\`)`;
763
+ },
764
+ content: ""
803
765
  };
804
766
  }
805
- function tsRefFromJtdSchema(nodePath, def, options, additionalOptions) {
806
- const key = nodePath.split(".").pop() ?? "";
807
- const typeName = pascalCase(def.ref, {
808
- normalize: true
767
+
768
+ function tsRecordFromSchema(schema, context) {
769
+ const innerType = tsTypeFromSchema(schema.values, {
770
+ clientName: context.clientName,
771
+ typePrefix: context.typePrefix,
772
+ generatedTypes: context.generatedTypes,
773
+ instancePath: `${context.instancePath}/[value]`,
774
+ schemaPath: `${context.schemaPath}/values`,
775
+ discriminatorParent: "",
776
+ discriminatorKey: "",
777
+ discriminatorValue: "",
778
+ versionNumber: context.versionNumber,
779
+ usedFeatures: context.usedFeatures
809
780
  });
781
+ const typeName = `Record<string, ${innerType.typeName}>`;
782
+ const defaultValue = schema.nullable ? "null" : "{}";
810
783
  return {
811
- tsType: typeName,
812
- schema: def,
813
- fieldTemplate: `${maybeOptionalKey(key, additionalOptions.isOptional)}: ${maybeNullType(typeName, def.nullable)}`,
814
- fromJsonTemplate(input) {
815
- if (additionalOptions.isOptional) {
816
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : undefined`;
784
+ typeName: schema.nullable ? `${typeName} | null` : typeName,
785
+ defaultValue,
786
+ validationTemplate(input) {
787
+ const mainPart = `isObject(${input}) && Object.values(${input}).every(
788
+ (_value) => ${innerType.validationTemplate("_value")},
789
+ )`;
790
+ if (schema.nullable) {
791
+ return `((${mainPart}) || ${input} === null)`;
817
792
  }
818
- if (def.nullable) {
819
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : null`;
793
+ return mainPart;
794
+ },
795
+ fromJsonTemplate(input, target) {
796
+ return `if (isObject(${input})) {
797
+ ${target} = {};
798
+ for (const [_key, _value] of Object.entries(${input})) {
799
+ let ${target}Value: ${innerType.typeName};
800
+ if (typeof _value === 'boolean') {
801
+ ${target}Value = _value;
802
+ } else {
803
+ ${target}Value = false;
804
+ }
805
+ ${target}[_key] = ${target}Value;
806
+ }
807
+ } else {
808
+ ${target} = ${defaultValue};
809
+ }`;
810
+ },
811
+ toJsonTemplate(input, target, key) {
812
+ const countVal = `_${validVarName(key)}PropertyCount`;
813
+ if (schema.nullable) {
814
+ return `if (${input} !== null) {
815
+ ${target} += '{';
816
+ let ${countVal} = 0;
817
+ for (const [_key, _value] of Object.entries(${input})) {
818
+ if (${countVal} !== 0) {
819
+ ${target} += ',';
820
+ }
821
+ ${target} += \`"\${_key}":\`;
822
+ ${innerType.toJsonTemplate("_value", target, "_key")}
823
+ ${countVal}++;
824
+ }
825
+ ${target} += '}';
826
+ } else {
827
+ ${target} += 'null';
828
+ }`;
820
829
  }
821
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.parse(${input}) : {}`;
830
+ return `${target} += '{'
831
+ let ${countVal} = 0;
832
+ for (const [_key, _value] of Object.entries(${input})) {
833
+ if (${countVal} !== 0) {
834
+ ${target} += ',';
835
+ }
836
+ ${target} += \`"\${_key}":\`;
837
+ ${innerType.toJsonTemplate("_value", target, "_key")};
838
+ ${countVal}++;
839
+ }
840
+ ${target} += '}';
841
+ `;
842
+ },
843
+ toQueryStringTemplate(_, __, ___) {
844
+ return `console.warn('[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.')`;
822
845
  },
823
- toJsonTemplate(input) {
824
- if (additionalOptions.isOptional) {
825
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.serialize(${input}) : 'undefined'`;
846
+ content: innerType.content
847
+ };
848
+ }
849
+
850
+ function tsRefFromSchema(schema, context) {
851
+ const typeName = pascalCase(validVarName(schema.ref), { normalize: true });
852
+ const prefixedTypeName = `${context.typePrefix}${typeName}`;
853
+ const defaultValue = schema.nullable ? "null" : `${prefixedTypeName}.new()`;
854
+ return {
855
+ typeName: schema.nullable ? `${prefixedTypeName} | null` : prefixedTypeName,
856
+ defaultValue,
857
+ validationTemplate(input) {
858
+ if (schema.nullable) {
859
+ return `($$${prefixedTypeName}.validate(${input}) || ${input} === null)`;
826
860
  }
827
- if (def.nullable) {
828
- return `typeof ${input} === 'object' && ${input} !== null ? $$${typeName}.serialize(${input}) : 'null'`;
861
+ return `$$${prefixedTypeName}.validate(${input})`;
862
+ },
863
+ fromJsonTemplate(input, target) {
864
+ return `if (isObject(${input})) {
865
+ ${target} = $$${prefixedTypeName}.fromJson(${input});
866
+ } else {
867
+ ${target} = ${defaultValue};
868
+ }`;
869
+ },
870
+ toJsonTemplate(input, target, _key) {
871
+ if (schema.nullable) {
872
+ return `if (${input} !== null) {
873
+ ${target} += $$${prefixedTypeName}.toJsonString(${input});
874
+ } else {
875
+ ${target} += 'null';
876
+ }`;
829
877
  }
830
- return `$$${typeName}.serialize(${input})`;
878
+ return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
879
+ },
880
+ toQueryStringTemplate(_, __, ___) {
881
+ return `console.warn("[WARNING] Nested objects cannot be serialized to query string. Ignoring property at ${context.instancePath}.");`;
831
882
  },
832
883
  content: ""
833
884
  };
834
885
  }
835
886
 
836
- export { createTypescriptClient, getJsDocComment, maybeNullType, maybeOptionalKey, tsAnyFromJtdSchema, tsArrayFromJtdSchema, tsDiscriminatedUnionFromJtdSchema, tsEnumFromJtdSchema, tsHttpRpcFromDefinition, tsObjectFromJtdSchema, tsRecordFromJtdSchema, tsRefFromJtdSchema, tsRpcFromDefinition, tsScalarFromJtdSchema, tsServiceFromDefinition, tsTypeFromJtdSchema, tsWsRpcFromDefinition, typescriptClientGenerator };
887
+ function tsRpcFromDefinition(def, context) {
888
+ switch (def.transport) {
889
+ case "http":
890
+ return httpRpcFromDefinition(def, context);
891
+ case "ws":
892
+ return wsRpcFromDefinition(def, context);
893
+ default:
894
+ console.warn(
895
+ `[ts-codegen] Warning: unsupported transport "${def.transport}". Ignoring ${context.instancePath}.`
896
+ );
897
+ return "";
898
+ }
899
+ }
900
+ function httpRpcFromDefinition(def, context) {
901
+ const key = getRpcKey(context);
902
+ const params = def.params ? `${context.typePrefix}${pascalCase(validVarName(def.params))}` : void 0;
903
+ const response = def.response ? `${context.typePrefix}${pascalCase(validVarName(def.response), { normalize: true })}` : void 0;
904
+ const serializerMethod = def.method === "get" ? "toUrlQueryString" : "toJsonString";
905
+ if (def.isEventStream) {
906
+ context.usedFeatures.sse = true;
907
+ return ` ${key}(${params ? `params: ${params},` : ""} options: SseOptions<${response ?? "undefined"}> = {}): EventSourceController {
908
+ return arriSseRequest<${response ?? "undefined"}, ${params ?? "undefined"}>(
909
+ {
910
+ url: \`\${this._baseUrl}${def.path}\`,
911
+ method: "${def.method.toLowerCase()}",
912
+ headers: this._headers,
913
+ ${params ? "params: params," : ""}
914
+ responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
915
+ responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
916
+ serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"},
917
+ clientVersion: "${context.versionNumber}",
918
+ },
919
+ options,
920
+ )
921
+ }`;
922
+ }
923
+ return ` async ${key}(${params ? `params: ${params}` : ""}): Promise<${response ?? "undefined"}> {
924
+ return arriRequest<${response ?? "undefined"}, ${params ?? "undefined"}>({
925
+ url: \`\${this._baseUrl}${def.path}\`,
926
+ method: "${def.method.toLowerCase()}",
927
+ headers: this._headers,
928
+ ${params ? "params: params," : ""}
929
+ responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
930
+ responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
931
+ serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"},
932
+ clientVersion: "${context.versionNumber}",
933
+ });
934
+ }`;
935
+ }
936
+ function wsRpcFromDefinition(def, context) {
937
+ context.usedFeatures.ws = true;
938
+ const key = getRpcKey(context);
939
+ const params = def.params ? `${context.typePrefix}${pascalCase(validVarName(def.params))}` : void 0;
940
+ const response = def.response ? `${context.typePrefix}${pascalCase(validVarName(def.response), { normalize: true })}` : void 0;
941
+ return ` async ${key}(options: WsOptions<${response ?? "undefined"}> = {}): Promise<WsController<${params ?? "undefined"},${response ?? "undefined"}>> {
942
+ return arriWsRequest<${params ?? "undefined"}, ${response ?? "undefined"}>({
943
+ url: \`\${this._baseUrl}${def.path}\`,
944
+ headers: this._headers,
945
+ responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
946
+ responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
947
+ serializer: ${params ? `$$${params}.toJsonString` : "() => {}"},
948
+ onOpen: options.onOpen,
949
+ onClose: options.onClose,
950
+ onError: options.onError,
951
+ onConnectionError: options.onConnectionError,
952
+ onMessage: options.onMessage,
953
+ clientVersion: "${context.versionNumber}",
954
+ });
955
+ }`;
956
+ }
957
+ function getRpcKey(context) {
958
+ const name = context.instancePath.split(".").pop() ?? "";
959
+ return validVarName(name);
960
+ }
961
+
962
+ function tsServiceFromDefinition(def, context) {
963
+ const key = getServiceKey(context);
964
+ const serviceName = getServiceName(context);
965
+ const rpcParts = [];
966
+ const subServices = [];
967
+ for (const key2 of Object.keys(def)) {
968
+ const subDef = def[key2];
969
+ if (isServiceDefinition(subDef)) {
970
+ const subService = tsServiceFromDefinition(subDef, {
971
+ clientName: context.clientName,
972
+ typePrefix: context.typePrefix,
973
+ generatedTypes: context.generatedTypes,
974
+ instancePath: `${context.instancePath}.${key2}`,
975
+ schemaPath: `${context.instancePath}.${key2}`,
976
+ discriminatorParent: "",
977
+ discriminatorKey: "",
978
+ discriminatorValue: "",
979
+ versionNumber: context.versionNumber,
980
+ usedFeatures: context.usedFeatures
981
+ });
982
+ if (subService.content) {
983
+ subServices.push(subService);
984
+ }
985
+ continue;
986
+ }
987
+ if (isRpcDefinition(subDef)) {
988
+ const rpc = tsRpcFromDefinition(subDef, {
989
+ clientName: context.clientName,
990
+ typePrefix: context.typePrefix,
991
+ generatedTypes: context.generatedTypes,
992
+ instancePath: `${context.instancePath}.${key2}`,
993
+ schemaPath: `${context.schemaPath}.${key2}`,
994
+ discriminatorParent: "",
995
+ discriminatorKey: "",
996
+ discriminatorValue: "",
997
+ versionNumber: context.versionNumber,
998
+ usedFeatures: context.usedFeatures
999
+ });
1000
+ if (rpc) {
1001
+ rpcParts.push(rpc);
1002
+ }
1003
+ continue;
1004
+ }
1005
+ console.warn(
1006
+ `Invalid definition found at procedures.${context.schemaPath}`
1007
+ );
1008
+ }
1009
+ if (subServices.length === 0 && rpcParts.length === 0) {
1010
+ return {
1011
+ key,
1012
+ name: serviceName,
1013
+ content: ""
1014
+ };
1015
+ }
1016
+ return {
1017
+ key,
1018
+ name: serviceName,
1019
+ content: `export class ${serviceName} {
1020
+ private readonly _baseUrl: string;
1021
+ private readonly _headers: HeaderMap | (() => HeaderMap | Promise<HeaderMap>);
1022
+ ${subServices.map((service) => ` ${service.key}: ${service.name};`).join("\n")}
1023
+ constructor(
1024
+ options: {
1025
+ baseUrl?: string;
1026
+ headers?: HeaderMap | (() => HeaderMap | Promise<HeaderMap>);
1027
+ } = {},
1028
+ ) {
1029
+ this._baseUrl = options.baseUrl ?? "";
1030
+ this._headers = options.headers ?? {};
1031
+ ${subServices.map((service) => ` this.${service.key} = new ${service.name}(options);`).join("\n")}
1032
+ }
1033
+ ${rpcParts.map((rpc) => ` ${rpc}`).join("\n")}
1034
+ }
1035
+
1036
+ ${subServices.map((service) => service.content).join("\n")}`
1037
+ };
1038
+ }
1039
+ function getServiceKey(context) {
1040
+ const name = context.instancePath.split(".").pop() ?? "";
1041
+ return validVarName(name);
1042
+ }
1043
+ function getServiceName(context) {
1044
+ if (context.instancePath.length === 0) {
1045
+ return context.clientName;
1046
+ }
1047
+ const serviceName = pascalCase(context.instancePath.split(".").join("_"), {
1048
+ normalize: true
1049
+ });
1050
+ return `${context.clientName}${serviceName}Service`;
1051
+ }
1052
+
1053
+ const typescriptClientGenerator = defineGeneratorPlugin(
1054
+ (options) => ({
1055
+ generator: async (def) => {
1056
+ if (!options.clientName) {
1057
+ throw new Error("Name is requires");
1058
+ }
1059
+ if (!options.outputFile) {
1060
+ throw new Error("No output file specified");
1061
+ }
1062
+ if (Object.keys(def.procedures).length <= 0) {
1063
+ console.warn(
1064
+ `No procedures defined in AppDefinition. Only data models will be outputted.`
1065
+ );
1066
+ }
1067
+ const result = await createTypescriptClient(def, options);
1068
+ writeFileSync(options.outputFile, result);
1069
+ },
1070
+ options
1071
+ })
1072
+ );
1073
+ async function createTypescriptClient(def, options) {
1074
+ const types = [];
1075
+ const context = {
1076
+ clientName: options.clientName,
1077
+ typePrefix: options.typePrefix ?? "",
1078
+ generatedTypes: [],
1079
+ instancePath: "",
1080
+ schemaPath: "",
1081
+ discriminatorParent: "",
1082
+ discriminatorKey: "",
1083
+ discriminatorValue: "",
1084
+ versionNumber: def.info?.version ?? "",
1085
+ usedFeatures: {
1086
+ sse: false,
1087
+ ws: false
1088
+ }
1089
+ };
1090
+ const serviceDefinitions = unflattenProcedures(def.procedures);
1091
+ const mainService = tsServiceFromDefinition(serviceDefinitions, context);
1092
+ for (const key of Object.keys(def.definitions)) {
1093
+ const typeDef = def.definitions[key];
1094
+ const result2 = tsTypeFromSchema(typeDef, {
1095
+ clientName: context.clientName,
1096
+ typePrefix: context.typePrefix,
1097
+ generatedTypes: context.generatedTypes,
1098
+ instancePath: `/${key}`,
1099
+ schemaPath: `/${key}`,
1100
+ discriminatorParent: "",
1101
+ discriminatorKey: "",
1102
+ discriminatorValue: "",
1103
+ versionNumber: context.versionNumber,
1104
+ usedFeatures: context.usedFeatures
1105
+ });
1106
+ if (result2.content) {
1107
+ types.push(result2.content);
1108
+ }
1109
+ }
1110
+ const imports = `// This file was autogenerated by @arrirpc/codegen-ts. Do not modify directly.
1111
+ // For more information visit https://github.com/modiimedia/arri
1112
+
1113
+ /* eslint-disable */
1114
+ // @ts-nocheck
1115
+ import {
1116
+ ArriEnumValidator,
1117
+ ArriModelValidator,
1118
+ arriRequest,
1119
+ ${context.usedFeatures.sse ? "arriSseRequest," : ""}
1120
+ ${context.usedFeatures.ws ? "arriWsRequest," : ""}
1121
+ ${context.usedFeatures.sse ? "type EventSourceController," : ""}
1122
+ INT8_MAX,
1123
+ INT8_MIN,
1124
+ INT16_MAX,
1125
+ INT16_MIN,
1126
+ INT32_MAX,
1127
+ INT32_MIN,
1128
+ INT64_MAX,
1129
+ INT64_MIN,
1130
+ isObject,
1131
+ serializeString,
1132
+ ${context.usedFeatures.sse ? "type SseOptions," : ""}
1133
+ UINT8_MAX,
1134
+ UINT16_MAX,
1135
+ UINT32_MAX,
1136
+ UINT64_MAX,
1137
+ ${context.usedFeatures.ws ? "type WsController," : ""}
1138
+ ${context.usedFeatures.ws ? "type WsOptions," : ""}
1139
+ } from "@arrirpc/client";
1140
+
1141
+ type HeaderMap = Record<string, string | undefined>;`;
1142
+ if (!mainService.content) {
1143
+ const result2 = `${imports}
1144
+
1145
+ ${types.join("\n")}`;
1146
+ return await prettier.format(result2, {
1147
+ ...options.prettierOptions,
1148
+ parser: "typescript"
1149
+ });
1150
+ }
1151
+ const result = `${imports}
1152
+ ${mainService.content}
1153
+
1154
+ ${types.join("\n")}`;
1155
+ return await prettier.format(result, {
1156
+ ...options.prettierOptions,
1157
+ parser: "typescript"
1158
+ });
1159
+ }
1160
+ function tsTypeFromSchema(schema, context) {
1161
+ if (isSchemaFormType(schema)) {
1162
+ switch (schema.type) {
1163
+ case "string":
1164
+ return tsStringFromSchema(schema);
1165
+ case "boolean":
1166
+ return tsBooleanFromSchema(schema);
1167
+ case "timestamp":
1168
+ return tsDateFromSchema(schema);
1169
+ case "float32":
1170
+ case "float64":
1171
+ return tsFloatFromSchema(schema);
1172
+ case "int8":
1173
+ return tsIntFromSchema(schema, "int8");
1174
+ case "uint8":
1175
+ return tsIntFromSchema(schema, "uint8");
1176
+ case "int16":
1177
+ return tsIntFromSchema(schema, "int16");
1178
+ case "uint16":
1179
+ return tsIntFromSchema(schema, "uint16");
1180
+ case "int32":
1181
+ return tsIntFromSchema(schema, "int32");
1182
+ case "uint32":
1183
+ return tsIntFromSchema(schema, "uint32");
1184
+ case "int64":
1185
+ return tsBigIntFromSchema(schema, false);
1186
+ case "uint64":
1187
+ return tsBigIntFromSchema(schema, true);
1188
+ }
1189
+ }
1190
+ if (isSchemaFormEnum(schema)) {
1191
+ return tsEnumFromSchema(schema, context);
1192
+ }
1193
+ if (isSchemaFormProperties(schema)) {
1194
+ return tsObjectFromSchema(schema, context);
1195
+ }
1196
+ if (isSchemaFormElements(schema)) {
1197
+ return tsArrayFromSchema(schema, context);
1198
+ }
1199
+ if (isSchemaFormValues(schema)) {
1200
+ return tsRecordFromSchema(schema, context);
1201
+ }
1202
+ if (isSchemaFormDiscriminator(schema)) {
1203
+ return tsTaggedUnionFromSchema(schema, context);
1204
+ }
1205
+ if (isSchemaFormRef(schema)) {
1206
+ return tsRefFromSchema(schema, context);
1207
+ }
1208
+ return tsAnyFromSchema(schema, context);
1209
+ }
1210
+
1211
+ export { createTypescriptClient, tsTypeFromSchema, typescriptClientGenerator };