@arrirpc/codegen-ts 0.83.1 → 0.84.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
@@ -2,28 +2,9 @@ import { pascalCase, stringStartsWithNumber, removeDisallowedChars, camelCase, i
2
2
  import { writeFileSync } from 'fs';
3
3
  import prettier from 'prettier';
4
4
 
5
- function tsAnyFromSchema(schema, context) {
6
- const typeName = "any";
7
- const defaultValue = schema.isNullable ? "null" : "undefined";
8
- return {
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}.')`;
22
- },
23
- content: ""
24
- };
5
+ function defaultCloneTemplate(input, target) {
6
+ return `${target} = ${input};`;
25
7
  }
26
-
27
8
  function getJsDocComment(metadata) {
28
9
  const descriptionParts = [];
29
10
  if (metadata?.description?.length) {
@@ -71,6 +52,29 @@ function getTsTypeName(schema, context) {
71
52
  return validVarName(name);
72
53
  }
73
54
 
55
+ function tsAnyFromSchema(schema, context) {
56
+ const typeName = "any";
57
+ const defaultValue = schema.isNullable ? "null" : "undefined";
58
+ return {
59
+ typeName,
60
+ defaultValue,
61
+ validationTemplate(_) {
62
+ return "true";
63
+ },
64
+ cloneTemplate: defaultCloneTemplate,
65
+ fromJsonTemplate(input, target) {
66
+ return `${target} = ${input}`;
67
+ },
68
+ toJsonTemplate(input, target) {
69
+ return `${target} += JSON.stringify(${input})`;
70
+ },
71
+ setSearchParamTemplate(_input, _target, _key) {
72
+ return `console.warn('[WARNING] Cannot serialize any\\'s to query string. Skipping property at ${context.instancePath}.')`;
73
+ },
74
+ content: ""
75
+ };
76
+ }
77
+
74
78
  function tsArrayFromSchema(schema, context) {
75
79
  const innerType = tsTypeFromSchema(schema.elements, {
76
80
  clientName: context.clientName,
@@ -82,8 +86,9 @@ function tsArrayFromSchema(schema, context) {
82
86
  discriminatorKey: "",
83
87
  discriminatorValue: "",
84
88
  versionNumber: context.versionNumber,
85
- usedFeatures: context.usedFeatures,
86
- rpcGenerators: context.rpcGenerators
89
+ useRpcTypes: context.useRpcTypes,
90
+ rpcGenerators: context.rpcGenerators,
91
+ features: context.features
87
92
  });
88
93
  const typeName = `(${innerType.typeName})[]`;
89
94
  const defaultValue = schema.isNullable ? "null" : "[]";
@@ -101,6 +106,22 @@ function tsArrayFromSchema(schema, context) {
101
106
  }
102
107
  return mainPart;
103
108
  },
109
+ cloneTemplate(input, target) {
110
+ const main = `${target} = [];
111
+ for (const ${target}El of ${input}) {
112
+ let ${target}ElValue: ${innerType.typeName};
113
+ ${innerType.cloneTemplate(`${target}El`, `${target}ElValue`)}
114
+ ${target}.push(${target}ElValue);
115
+ }`;
116
+ if (schema.isNullable) {
117
+ return `if (${input} !== null) {
118
+ ${main}
119
+ } else {
120
+ ${target} = null;
121
+ }`;
122
+ }
123
+ return main;
124
+ },
104
125
  fromJsonTemplate(input, target) {
105
126
  return `if (Array.isArray(${input})) {
106
127
  ${target} = [];
@@ -137,7 +158,7 @@ function tsArrayFromSchema(schema, context) {
137
158
  }
138
159
  ${target} += ']';`;
139
160
  },
140
- toQueryStringTemplate(_input, _target, _key) {
161
+ setSearchParamTemplate(_input, _target, _key) {
141
162
  return `console.warn('[WARNING] Cannot serialize arrays to query string. Skipping property at ${context.instancePath}.')`;
142
163
  },
143
164
  content: innerType.content
@@ -147,34 +168,44 @@ function tsArrayFromSchema(schema, context) {
147
168
  function tsObjectFromSchema(schema, context) {
148
169
  const typeName = getTsTypeName(schema, context);
149
170
  const prefixedTypeName = `${context.typePrefix}${typeName}`;
150
- const defaultValue = schema.isNullable ? "null" : `$$${prefixedTypeName}.new()`;
171
+ const defaultValue = schema.isNullable ? "null" : `${prefixedTypeName}New()`;
151
172
  const result = {
152
173
  typeName: schema.isNullable ? `${prefixedTypeName} | null` : prefixedTypeName,
153
174
  defaultValue,
154
175
  validationTemplate(input) {
155
176
  if (schema.isNullable) {
156
- return `($$${prefixedTypeName}.validate(${input}) || ${input} === null)`;
177
+ return `(${prefixedTypeName}Validate(${input}) || ${input} === null)`;
157
178
  }
158
- return `$$${prefixedTypeName}.validate(${input})`;
179
+ return `${prefixedTypeName}Validate(${input})`;
159
180
  },
160
181
  fromJsonTemplate(input, target) {
161
182
  return `if (isObject(${input})) {
162
- ${target} = $$${prefixedTypeName}.fromJson(${input});
183
+ ${target} = ${prefixedTypeName}FromJson(${input});
163
184
  } else {
164
185
  ${target} = ${defaultValue};
165
186
  }`;
166
187
  },
188
+ cloneTemplate(input, target) {
189
+ if (schema.isNullable) {
190
+ return `if (${input} !== null) {
191
+ ${target} = ${prefixedTypeName}Clone(${input});
192
+ } else {
193
+ ${target} = null;
194
+ }`;
195
+ }
196
+ return `${target} = ${prefixedTypeName}Clone(${input});`;
197
+ },
167
198
  toJsonTemplate(input, target) {
168
199
  if (schema.isNullable) {
169
200
  return `if (${input} !== null) {
170
- ${target} += $$${prefixedTypeName}.toJsonString(${input});
201
+ ${target} += ${prefixedTypeName}ToJsonString(${input});
171
202
  } else {
172
203
  ${target} += 'null';
173
204
  }`;
174
205
  }
175
- return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
206
+ return `${target} += ${prefixedTypeName}ToJsonString(${input});`;
176
207
  },
177
- toQueryStringTemplate(_input, _target) {
208
+ setSearchParamTemplate(_input, _target) {
178
209
  return `console.warn('[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.')`;
179
210
  },
180
211
  content: ""
@@ -187,8 +218,9 @@ function tsObjectFromSchema(schema, context) {
187
218
  const fromJsonParts = [];
188
219
  const constructionParts = [];
189
220
  const toJsonParts = [];
190
- const toQueryParts = [];
221
+ const setSearchParamParts = [];
191
222
  const validationParts = ["isObject(input)"];
223
+ const cloneParts = [];
192
224
  const subContentParts = [];
193
225
  let hasKey = false;
194
226
  if (context.discriminatorParent && context.discriminatorValue && context.discriminatorKey) {
@@ -199,12 +231,13 @@ function tsObjectFromSchema(schema, context) {
199
231
  fieldParts.push(`${key}: "${context.discriminatorValue}",`);
200
232
  fromJsonParts.push(`const _${key} = "${context.discriminatorValue}"`);
201
233
  toJsonParts.push(`json += '"${key}":"${context.discriminatorValue}"'`);
202
- toQueryParts.push(
203
- `queryParts.push('${context.discriminatorKey}=${context.discriminatorValue}');`
234
+ setSearchParamParts.push(
235
+ `params.set('${context.discriminatorKey}', '${context.discriminatorValue}');`
204
236
  );
205
237
  validationParts.push(
206
238
  `input.${key} === '${context.discriminatorValue}'`
207
239
  );
240
+ cloneParts.push(`const _${key} = "${context.discriminatorValue}"`);
208
241
  newParts.push(`${key}: "${context.discriminatorValue}",`);
209
242
  constructionParts.push(`${key}: _${key},`);
210
243
  }
@@ -220,8 +253,9 @@ function tsObjectFromSchema(schema, context) {
220
253
  discriminatorKey: "",
221
254
  discriminatorValue: "",
222
255
  versionNumber: context.versionNumber,
223
- usedFeatures: context.usedFeatures,
224
- rpcGenerators: context.rpcGenerators
256
+ useRpcTypes: context.useRpcTypes,
257
+ rpcGenerators: context.rpcGenerators,
258
+ features: context.features
225
259
  });
226
260
  if (prop.content) subContentParts.push(prop.content);
227
261
  const fieldName = validVarName(camelCase(key, { normalize: true }));
@@ -240,11 +274,13 @@ function tsObjectFromSchema(schema, context) {
240
274
  toJsonParts.push(
241
275
  prop.toJsonTemplate(`input.${fieldName}`, "json", key)
242
276
  );
243
- toQueryParts.push(
244
- prop.toQueryStringTemplate(`input.${fieldName}`, "queryParts", key)
277
+ setSearchParamParts.push(
278
+ prop.setSearchParamTemplate(`input.${fieldName}`, "params", key)
245
279
  );
246
280
  const validationPart = prop.validationTemplate(`input.${fieldName}`);
247
281
  validationParts.push(validationPart);
282
+ cloneParts.push(`let ${tempKey}: ${prop.typeName}`);
283
+ cloneParts.push(prop.cloneTemplate(`input.${fieldName}`, tempKey));
248
284
  constructionParts.push(`${fieldName}: ${tempKey},`);
249
285
  hasKey = true;
250
286
  }
@@ -263,8 +299,9 @@ function tsObjectFromSchema(schema, context) {
263
299
  discriminatorKey: "",
264
300
  discriminatorValue: "",
265
301
  versionNumber: context.versionNumber,
266
- usedFeatures: context.usedFeatures,
267
- rpcGenerators: context.rpcGenerators
302
+ useRpcTypes: context.useRpcTypes,
303
+ rpcGenerators: context.rpcGenerators,
304
+ features: context.features
268
305
  });
269
306
  if (prop.content) subContentParts.push(prop.content);
270
307
  const fieldName = validVarName(camelCase(key, { normalize: true }));
@@ -289,51 +326,89 @@ function tsObjectFromSchema(schema, context) {
289
326
  _hasKey = true;
290
327
  }`);
291
328
  }
292
- toQueryParts.push(`if (typeof input.${fieldName} !== 'undefined') {
293
- ${prop.toQueryStringTemplate(`input.${fieldName}`, "queryParts", key)}
329
+ setSearchParamParts.push(`if (typeof input.${fieldName} !== 'undefined') {
330
+ ${prop.setSearchParamTemplate(`input.${fieldName}`, "params", key)}
294
331
  }`);
295
332
  const validationPart = prop.validationTemplate(`input.${fieldName}`);
296
333
  validationParts.push(
297
334
  `((${validationPart}) || typeof input.${fieldName} === 'undefined')`
298
335
  );
336
+ cloneParts.push(`let ${tempKey}: ${prop.typeName} | undefined;`);
337
+ const clonePart = prop.cloneTemplate(`input.${fieldName}`, tempKey);
338
+ cloneParts.push(`if (typeof input.${fieldName} !== 'undefined') {
339
+ ${clonePart}
340
+ }`);
299
341
  constructionParts.push(`${fieldName}: ${tempKey},`);
300
342
  }
301
343
  result.content = `${getJsDocComment(schema.metadata)}export interface ${prefixedTypeName} {
302
344
  ${fieldParts.map((part) => ` ${part}`).join("\n")}
303
- }
304
- ${context.discriminatorParent && context.discriminatorValue ? "" : "export "}const $$${prefixedTypeName}: ArriModelValidator<${prefixedTypeName}> = {
305
- new(): ${prefixedTypeName} {
306
- return {
307
- ${newParts.map((part) => ` ${part}`).join("\n")}
308
- };
309
- },
310
- validate(input): input is ${prefixedTypeName} {
311
- return (
312
- ${validationParts.map((part) => ` ${part}`).join("&& \n")}
313
- )
314
- },
315
- fromJson(input): ${prefixedTypeName} {
316
- ${fromJsonParts.map((part) => ` ${part}`).join("\n")}
317
- return {
318
- ${constructionParts.map((part) => ` ${part}`).join("\n")}
319
- }
320
- },
321
- fromJsonString(input): ${prefixedTypeName} {
322
- return $$${prefixedTypeName}.fromJson(JSON.parse(input));
323
- },
324
- toJsonString(input): string {
325
- let json = "{";
326
- ${toJsonParts.map((part) => ` ${part}`).join("\n")}
327
- json += "}";
328
- return json;
329
- },
330
- toUrlQueryString(input): string {
331
- const queryParts: string[] = [];
332
- ${toQueryParts.map((part) => ` ${part}`).join("\n")}
333
- return queryParts.join("&");
345
+ }`;
346
+ result.content += `
347
+ export function ${prefixedTypeName}New(): ${prefixedTypeName} {
348
+ return {
349
+ ${newParts.map((part) => ` ${part}`).join("\n")}
350
+ };
351
+ }`;
352
+ if (context.features.validateFn) {
353
+ result.content += `
354
+ export function ${prefixedTypeName}Validate(input: unknown): input is ${prefixedTypeName} {
355
+ return (
356
+ ${validationParts.map((part) => ` ${part}`).join("&& \n")}
357
+ )
358
+ }`;
359
+ }
360
+ if (context.features.cloneFn) {
361
+ result.content += `
362
+ export function ${prefixedTypeName}Clone(input: ${prefixedTypeName}): ${prefixedTypeName} {
363
+ ${cloneParts.map((part) => ` ${part}`).join("\n")}
364
+ return {
365
+ ${constructionParts.map((part) => ` ${part}`).join("\n")}
334
366
  }
367
+ }`;
368
+ }
369
+ result.content += `
370
+ export function ${prefixedTypeName}FromJson(input: Record<string, unknown>): ${prefixedTypeName} {
371
+ ${fromJsonParts.map((part) => ` ${part}`).join("\n")}
372
+ return {
373
+ ${constructionParts.map((part) => ` ${part}`).join("\n")}
374
+ }
375
+ }`;
376
+ result.content += `
377
+ export function ${prefixedTypeName}FromJsonString(input: string): ${prefixedTypeName} {
378
+ return ${prefixedTypeName}FromJson(JSON.parse(input));
379
+ }`;
380
+ result.content += `
381
+ export function ${prefixedTypeName}ToJsonString(input: ${prefixedTypeName}): string {
382
+ let json = "{";
383
+ ${toJsonParts.map((part) => ` ${part}`).join("\n")}
384
+ json += "}";
385
+ return json;
386
+ }`;
387
+ result.content += `
388
+ export function ${prefixedTypeName}ToUrlSearchParams(input: ${prefixedTypeName}): URLSearchParams {
389
+ const params = new URLSearchParams();
390
+ ${setSearchParamParts.map((part) => ` ${part}`).join("\n")}
391
+ return params;
392
+ }`;
393
+ result.content += `
394
+ export function ${prefixedTypeName}ToUrlSearchParamsString(input: ${prefixedTypeName}): string {
395
+ return ${prefixedTypeName}ToUrlSearchParams(input).toString();
335
396
  }
336
-
397
+ `;
398
+ if (context.features.validatorObj) {
399
+ result.content += `
400
+ ${context.discriminatorParent && context.discriminatorValue ? "" : "export "}const $$${prefixedTypeName}: ${context.clientName}Validator<${prefixedTypeName}> = {
401
+ new: ${prefixedTypeName}New,
402
+ ${context.features.validateFn ? `validate: ${prefixedTypeName}Validate,` : ""}
403
+ ${context.features.cloneFn ? `clone: ${prefixedTypeName}Clone,` : ""}
404
+ fromJson: ${prefixedTypeName}FromJson,
405
+ fromJsonString: ${prefixedTypeName}FromJsonString,
406
+ toJsonString: ${prefixedTypeName}ToJsonString,
407
+ toUrlSearchParams: ${prefixedTypeName}ToUrlSearchParams,
408
+ toUrlSearchParamsString: ${prefixedTypeName}ToUrlSearchParamsString,
409
+ }`;
410
+ }
411
+ result.content += `
337
412
  ${subContentParts.join("\n")}`;
338
413
  context.generatedTypes.push(typeName);
339
414
  return result;
@@ -342,20 +417,30 @@ ${subContentParts.join("\n")}`;
342
417
  function tsTaggedUnionFromSchema(schema, context) {
343
418
  const typeName = getTsTypeName(schema, context);
344
419
  const prefixedTypeName = `${context.typePrefix}${typeName}`;
345
- const defaultValue = schema.isNullable ? "null" : `$$${prefixedTypeName}.new()`;
420
+ const defaultValue = schema.isNullable ? "null" : `${prefixedTypeName}New()`;
346
421
  const result = {
347
422
  typeName: schema.isNullable ? `${prefixedTypeName} | null` : prefixedTypeName,
348
423
  defaultValue,
349
424
  validationTemplate(input) {
350
- const mainPart = `$$${prefixedTypeName}.validate(${input})`;
425
+ const mainPart = `${prefixedTypeName}Validate(${input})`;
351
426
  if (schema.isNullable) {
352
427
  return `(${mainPart} || ${input} === null)`;
353
428
  }
354
429
  return mainPart;
355
430
  },
431
+ cloneTemplate(input, target) {
432
+ if (schema.isNullable) {
433
+ return `if (${input} !== null) {
434
+ ${target} = ${prefixedTypeName}Clone(${input});
435
+ } else {
436
+ ${target} = null;
437
+ }`;
438
+ }
439
+ return `${target} = ${prefixedTypeName}Clone(${input});`;
440
+ },
356
441
  fromJsonTemplate(input, target) {
357
442
  return `if (isObject(${input})) {
358
- ${target} = $$${prefixedTypeName}.fromJson(${input});
443
+ ${target} = ${prefixedTypeName}FromJson(${input});
359
444
  } else {
360
445
  ${target} = ${defaultValue};
361
446
  }`;
@@ -363,14 +448,14 @@ function tsTaggedUnionFromSchema(schema, context) {
363
448
  toJsonTemplate(input, target, _key) {
364
449
  if (schema.isNullable) {
365
450
  return `if (${input} != null) {
366
- ${target} += $$${prefixedTypeName}.toJsonString(${input});
451
+ ${target} += ${prefixedTypeName}ToJsonString(${input});
367
452
  } else {
368
453
  ${target} += 'null';
369
454
  }`;
370
455
  }
371
- return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
456
+ return `${target} += ${prefixedTypeName}ToJsonString(${input});`;
372
457
  },
373
- toQueryStringTemplate(_, __, ___) {
458
+ setSearchParamTemplate(_, __, ___) {
374
459
  return `console.warn('[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.');`;
375
460
  },
376
461
  content: ""
@@ -390,66 +475,104 @@ function tsTaggedUnionFromSchema(schema, context) {
390
475
  discriminatorKey,
391
476
  discriminatorValue: key,
392
477
  versionNumber: context.versionNumber,
393
- usedFeatures: context.usedFeatures,
394
- rpcGenerators: context.rpcGenerators
478
+ useRpcTypes: context.useRpcTypes,
479
+ rpcGenerators: context.rpcGenerators,
480
+ features: context.features
395
481
  });
396
482
  subTypes.push({ value: key, data: subType });
397
483
  }
398
- result.content = `${getJsDocComment(schema.metadata)}export type ${prefixedTypeName} = ${subTypes.map((type) => type.data.typeName).join(" |")};
399
- export const $$${prefixedTypeName}: ArriModelValidator<${prefixedTypeName}> = {
400
- new(): ${prefixedTypeName} {
401
- return $$${subTypes[0].data.typeName}.new();
402
- },
403
- validate(input): input is ${prefixedTypeName} {
404
- if (!isObject(input)) {
405
- return false;
406
- }
407
- if (typeof input.${discriminatorKey} !== 'string') {
484
+ result.content = `${getJsDocComment(schema.metadata)}export type ${prefixedTypeName} = ${subTypes.map((type) => type.data.typeName).join(" |")};`;
485
+ result.content += `
486
+ export function ${prefixedTypeName}New(): ${prefixedTypeName} {
487
+ return ${subTypes[0].data.typeName}New();
488
+ }`;
489
+ if (context.features.validateFn) {
490
+ result.content += `
491
+ export function ${prefixedTypeName}Validate(input: unknown): input is ${prefixedTypeName} {
492
+ if (!isObject(input)) {
493
+ return false;
494
+ }
495
+ if (typeof input.${discriminatorKey} !== 'string') {
496
+ return false;
497
+ }
498
+ switch (input.${discriminatorKey}) {
499
+ ${subTypes.map(
500
+ (type) => ` case "${type.value}":
501
+ return ${type.data.typeName}Validate(input);`
502
+ ).join("\n")}
503
+ default:
408
504
  return false;
409
- }
410
- switch (input.${discriminatorKey}) {
505
+ }
506
+ }`;
507
+ }
508
+ if (context.features.cloneFn) {
509
+ result.content += `
510
+ export function ${prefixedTypeName}Clone(input: ${prefixedTypeName}): ${prefixedTypeName} {
511
+ switch (input.${discriminatorKey}) {
411
512
  ${subTypes.map(
412
- (type) => ` case "${type.value}":
413
- return $$${type.data.typeName}.validate(input);`
414
- ).join("\n")}
415
- default:
416
- return false;
417
- }
418
- },
419
- fromJson(input): ${prefixedTypeName} {
420
- switch (input.${discriminatorKey}) {
513
+ (type) => ` case "${type.value}":
514
+ return ${type.data.typeName}Clone(input);`
515
+ ).join("\n")}
516
+ default:
517
+ throw new Error('Unimplemented');
518
+ }
519
+ }`;
520
+ }
521
+ result.content += `
522
+ export function ${prefixedTypeName}FromJson(input: Record<string, unknown>): ${prefixedTypeName} {
523
+ switch (input.${discriminatorKey}) {
421
524
  ${subTypes.map(
422
- (type) => ` case "${type.value}":
423
- return $$${type.data.typeName}.fromJson(input);`
525
+ (type) => ` case "${type.value}":
526
+ return ${type.data.typeName}FromJson(input);`
424
527
  ).join("\n")}
425
- default:
426
- return $$${subTypes[0].data.typeName}.new();
427
- }
428
- },
429
- fromJsonString(input): ${prefixedTypeName} {
430
- return $$${prefixedTypeName}.fromJson(JSON.parse(input));
431
- },
432
- toJsonString(input): string {
433
- switch (input.${discriminatorKey}) {
528
+ default:
529
+ return ${subTypes[0].data.typeName}New();
530
+ }
531
+ }`;
532
+ result.content += `
533
+ export function ${prefixedTypeName}FromJsonString(input: string): ${prefixedTypeName} {
534
+ return ${prefixedTypeName}FromJson(JSON.parse(input));
535
+ }`;
536
+ result.content += `
537
+ export function ${prefixedTypeName}ToJsonString(input: ${prefixedTypeName}): string {
538
+ switch (input.${discriminatorKey}) {
434
539
  ${subTypes.map(
435
- (type) => ` case "${type.value}":
436
- return $$${type.data.typeName}.toJsonString(input);`
540
+ (type) => ` case "${type.value}":
541
+ return ${type.data.typeName}ToJsonString(input);`
437
542
  ).join("\n")}
438
- default:
439
- throw new Error(\`Unhandled case "\${(input as any).${discriminatorKey}}"\`);
440
- }
441
- },
442
- toUrlQueryString(input): string {
443
- switch (input.${discriminatorKey}) {
543
+ default:
544
+ throw new Error(\`Unhandled case "\${(input as any).${discriminatorKey}}"\`);
545
+ }
546
+ }`;
547
+ result.content += `
548
+ export function ${prefixedTypeName}ToUrlSearchParams(input: ${prefixedTypeName}): URLSearchParams {
549
+ switch (input.${discriminatorKey}) {
444
550
  ${subTypes.map(
445
- (type) => ` case "${type.value}":
446
- return $$${type.data.typeName}.toUrlQueryString(input);`
551
+ (type) => ` case "${type.value}":
552
+ return ${type.data.typeName}ToUrlSearchParams(input);`
447
553
  ).join("\n")}
448
- default:
449
- throw new Error('Unhandled case');
450
- }
554
+ default:
555
+ throw new Error('Unhandled case');
451
556
  }
452
- }
557
+ }`;
558
+ result.content += `
559
+ export function ${prefixedTypeName}ToUrlSearchParamsString(input: ${prefixedTypeName}): string {
560
+ return ${prefixedTypeName}ToUrlSearchParams(input).toString();
561
+ }`;
562
+ if (context.features.validatorObj) {
563
+ result.content += `
564
+ export const $$${prefixedTypeName}: ${context.clientName}Validator<${prefixedTypeName}> = {
565
+ new: ${prefixedTypeName}New,
566
+ ${context.features.validateFn ? `validate: ${prefixedTypeName}Validate,` : ""}
567
+ ${context.features.cloneFn ? `clone: ${prefixedTypeName}Clone,` : ""}
568
+ fromJson: ${prefixedTypeName}FromJson,
569
+ fromJsonString: ${prefixedTypeName}FromJsonString,
570
+ toJsonString: ${prefixedTypeName}ToJsonString,
571
+ toUrlSearchParams: ${prefixedTypeName}ToUrlSearchParams,
572
+ toUrlSearchParamsString: ${prefixedTypeName}ToUrlSearchParamsString,
573
+ }`;
574
+ }
575
+ result.content += `
453
576
  ${subTypes.map((type) => type.data.content).join("\n")}
454
577
  `;
455
578
  context.generatedTypes.push(typeName);
@@ -464,19 +587,20 @@ function tsEnumFromSchema(schema, context) {
464
587
  const enumName = getTsTypeName(schema, context);
465
588
  const prefixedEnumName = `${context.typePrefix}${enumName}`;
466
589
  const typeName = schema.isNullable ? `${prefixedEnumName} | null` : prefixedEnumName;
467
- const defaultValue = schema.isNullable ? "null" : `$$${prefixedEnumName}.new()`;
590
+ const defaultValue = schema.isNullable ? "null" : `${prefixedEnumName}New()`;
468
591
  const result = {
469
592
  typeName,
470
593
  defaultValue,
471
594
  validationTemplate(input) {
472
595
  if (schema.isNullable) {
473
- return `($$${prefixedEnumName}.validate(${input}) || ${input} === null)`;
596
+ return `(${prefixedEnumName}Validate(${input}) || ${input} === null)`;
474
597
  }
475
- return `$$${prefixedEnumName}.validate(${input})`;
598
+ return `${prefixedEnumName}Validate(${input})`;
476
599
  },
600
+ cloneTemplate: defaultCloneTemplate,
477
601
  fromJsonTemplate(input, target) {
478
602
  return `if (typeof ${input} === 'string') {
479
- ${target} = $$${prefixedEnumName}.fromSerialValue(${input});
603
+ ${target} = ${prefixedEnumName}FromSerialValue(${input});
480
604
  } else {
481
605
  ${target} = ${defaultValue};
482
606
  }`;
@@ -491,8 +615,11 @@ function tsEnumFromSchema(schema, context) {
491
615
  }
492
616
  return `${target} += \`"\${${input}}"\``;
493
617
  },
494
- toQueryStringTemplate(input, target, key) {
495
- return `${target}.push(\`${key}=\${${input}}\`)`;
618
+ setSearchParamTemplate(input, target, key) {
619
+ if (schema.isNullable) {
620
+ return `${target}.set('${key}', \`\${${input}}\`);`;
621
+ }
622
+ return `${target}.set('${key}', ${input})`;
496
623
  },
497
624
  content: ""
498
625
  };
@@ -500,7 +627,7 @@ function tsEnumFromSchema(schema, context) {
500
627
  return result;
501
628
  }
502
629
  const name = `${context.typePrefix}${enumName}`;
503
- const valuesName = `$$${name}Values`;
630
+ const valuesName = `${name}Values`;
504
631
  result.content = `${getJsDocComment(schema.metadata)}export type ${name} = ${schema.enum.map((val) => `'${val}'`).join(" | ")};
505
632
  export const ${name} = {
506
633
  ${schema.enum.map((val) => {
@@ -510,32 +637,39 @@ ${schema.enum.map((val) => {
510
637
  return str;
511
638
  }).join("\n")}
512
639
  } as const;
513
- const ${valuesName} = [${schema.enum.map((val) => `'${val}'`).join(", ")}] as const;
514
- export const $$${name}: ArriEnumValidator<${name}> = {
515
- new(): ${name} {
516
- return ${valuesName}[0];
517
- },
518
- validate(input): input is ${enumName} {
519
- return (
520
- typeof input === 'string' &&
521
- ${valuesName}.includes(input as any)
522
- );
523
- },
524
- values: ${valuesName},
525
- fromSerialValue(input): ${name} {
526
- if (${valuesName}.includes(input as any)) {
527
- return input as ${name};
528
- }
529
- if (${valuesName}.includes(input.toLowerCase() as any)) {
530
- return input.toLowerCase() as ${name};
531
- }
532
- if (${valuesName}.includes(input.toUpperCase() as any)) {
533
- return input.toUpperCase() as ${name};
534
- }
535
- return "${schema.enum[0]}";
640
+ export const ${valuesName} = [${schema.enum.map((val) => `'${val}'`).join(", ")}] as const;`;
641
+ result.content += `
642
+ export function ${name}New(): ${name} {
643
+ return ${valuesName}[0];
644
+ }`;
645
+ if (context.features.validateFn) {
646
+ result.content += `
647
+ export function ${name}Validate(input: unknown): input is ${name} {
648
+ return typeof input === 'string' && ${valuesName}.includes(input as any);
649
+ }`;
650
+ }
651
+ result.content += `
652
+ export function ${name}FromSerialValue(input: string): ${name} {
653
+ if (${valuesName}.includes(input as any)) {
654
+ return input as ${name};
536
655
  }
537
- }
538
- `;
656
+ if (${valuesName}.includes(input.toLowerCase() as any)) {
657
+ return input.toLowerCase() as ${name};
658
+ }
659
+ if (${valuesName}.includes(input.toUpperCase() as any)) {
660
+ return input.toUpperCase() as ${name};
661
+ }
662
+ return "${schema.enum[0]}";
663
+ }`;
664
+ if (context.features.validatorObj) {
665
+ result.content += `
666
+ export const $$${name}: ${context.clientName}EnumValidator<${name} > = {
667
+ new: ${name}New,
668
+ ${context.features.validateFn ? `validate: ${name}Validate,` : ""}
669
+ values: ${valuesName},
670
+ fromSerialValue: ${name}FromSerialValue,
671
+ }`;
672
+ }
539
673
  context.generatedTypes.push(enumName);
540
674
  return result;
541
675
  }
@@ -552,6 +686,7 @@ function tsStringFromSchema(schema, _context) {
552
686
  }
553
687
  return `typeof ${input} === 'string'`;
554
688
  },
689
+ cloneTemplate: defaultCloneTemplate,
555
690
  fromJsonTemplate(input, target) {
556
691
  if (schema.isNullable) {
557
692
  return `${target} = parseNullableString(${input});`;
@@ -568,8 +703,11 @@ function tsStringFromSchema(schema, _context) {
568
703
  }
569
704
  return `${target} += serializeString(${input});`;
570
705
  },
571
- toQueryStringTemplate(input, target, key) {
572
- return `${target}.push(\`${key}=\${${input}}\`);`;
706
+ setSearchParamTemplate(input, target, key) {
707
+ if (schema.isNullable) {
708
+ return `${target}.set('${key}', \`\${${input}}\`);`;
709
+ }
710
+ return `${target}.set('${key}', ${input});`;
573
711
  },
574
712
  content: ""
575
713
  };
@@ -586,6 +724,7 @@ function tsBooleanFromSchema(schema, _context) {
586
724
  }
587
725
  return `typeof ${input} === 'boolean'`;
588
726
  },
727
+ cloneTemplate: defaultCloneTemplate,
589
728
  fromJsonTemplate(input, target) {
590
729
  if (schema.isNullable) {
591
730
  return `${target} = parseNullableBoolean(${input});`;
@@ -595,8 +734,8 @@ function tsBooleanFromSchema(schema, _context) {
595
734
  toJsonTemplate(input, target) {
596
735
  return `${target} += \`\${${input}}\`;`;
597
736
  },
598
- toQueryStringTemplate(input, target, key) {
599
- return `${target}.push(\`${key}=\${${input}}\`);`;
737
+ setSearchParamTemplate(input, target, key) {
738
+ return `${target}.set('${key}', \`\${${input}}\`);`;
600
739
  },
601
740
  content: ""
602
741
  };
@@ -613,6 +752,16 @@ function tsDateFromSchema(schema, _context) {
613
752
  }
614
753
  return `${input} instanceof Date`;
615
754
  },
755
+ cloneTemplate(input, target) {
756
+ if (schema.isNullable) {
757
+ return `if (${input} !== null) {
758
+ ${target} = new Date(${input}.getTime());
759
+ } else {
760
+ ${target} = null;
761
+ }`;
762
+ }
763
+ return `${target} = new Date(${input}.getTime());`;
764
+ },
616
765
  fromJsonTemplate(input, target) {
617
766
  if (schema.isNullable) {
618
767
  return `${target} = parseNullableTimestamp(${input});`;
@@ -629,11 +778,11 @@ function tsDateFromSchema(schema, _context) {
629
778
  }
630
779
  return `${target} += \`"\${${input}.toISOString()}"\``;
631
780
  },
632
- toQueryStringTemplate(input, target, key) {
781
+ setSearchParamTemplate(input, target, key) {
633
782
  if (schema.isNullable) {
634
- return `${target}.push(\`${key}=\${${input}?.toISOString()}\`)`;
783
+ return `${target}.set('${key}', \`\${${input}?.toISOString()}\`);`;
635
784
  }
636
- return `${target}.push(\`${key}=\${${input}.toISOString()}\`)`;
785
+ return `${target}.set('${key}', ${input}.toISOString());`;
637
786
  },
638
787
  content: ""
639
788
  };
@@ -650,6 +799,7 @@ function tsFloatFromSchema(schema, _context) {
650
799
  }
651
800
  return `typeof ${input} === 'number'`;
652
801
  },
802
+ cloneTemplate: defaultCloneTemplate,
653
803
  fromJsonTemplate(input, target) {
654
804
  if (schema.isNullable) {
655
805
  return `${target} = parseNullableNumberFloat(${input});`;
@@ -659,8 +809,8 @@ function tsFloatFromSchema(schema, _context) {
659
809
  toJsonTemplate(input, target) {
660
810
  return `${target} += \`\${${input}}\``;
661
811
  },
662
- toQueryStringTemplate(input, target, key) {
663
- return `${target}.push(\`${key}=\${${input}}\`)`;
812
+ setSearchParamTemplate(input, target, key) {
813
+ return `${target}.set('${key}', \`\${${input}}\`);`;
664
814
  },
665
815
  content: ""
666
816
  };
@@ -706,6 +856,7 @@ function tsIntFromSchema(schema, intType, _context) {
706
856
  }
707
857
  return mainPart;
708
858
  },
859
+ cloneTemplate: defaultCloneTemplate,
709
860
  fromJsonTemplate(input, target) {
710
861
  if (schema.isNullable) {
711
862
  return `${target} = parseNullableNumberInt(${input}, ${min}, ${max});`;
@@ -715,8 +866,8 @@ function tsIntFromSchema(schema, intType, _context) {
715
866
  toJsonTemplate(input, target) {
716
867
  return `${target} += \`\${${input}}\``;
717
868
  },
718
- toQueryStringTemplate(input, target, key) {
719
- return `${target}.push(\`${key}=\${${input}}\`)`;
869
+ setSearchParamTemplate(input, target, key) {
870
+ return `${target}.set('${key}', \`\${${input}}\`);`;
720
871
  },
721
872
  content: ""
722
873
  };
@@ -734,6 +885,7 @@ function tsBigIntFromSchema(schema, isUnsigned, _context) {
734
885
  }
735
886
  return mainPart;
736
887
  },
888
+ cloneTemplate: defaultCloneTemplate,
737
889
  fromJsonTemplate(input, target) {
738
890
  if (isUnsigned) {
739
891
  if (schema.isNullable) {
@@ -756,8 +908,8 @@ function tsBigIntFromSchema(schema, isUnsigned, _context) {
756
908
  }
757
909
  return `${target} += \`"\${${input}}"\``;
758
910
  },
759
- toQueryStringTemplate(input, target, key) {
760
- return `${target}.push(\`${key}=\${${input}}\`)`;
911
+ setSearchParamTemplate(input, target, key) {
912
+ return `${target}.set('${key}', \`\${${input}}\`);`;
761
913
  },
762
914
  content: ""
763
915
  };
@@ -774,8 +926,9 @@ function tsRecordFromSchema(schema, context) {
774
926
  discriminatorKey: "",
775
927
  discriminatorValue: "",
776
928
  versionNumber: context.versionNumber,
777
- usedFeatures: context.usedFeatures,
778
- rpcGenerators: context.rpcGenerators
929
+ useRpcTypes: context.useRpcTypes,
930
+ rpcGenerators: context.rpcGenerators,
931
+ features: context.features
779
932
  });
780
933
  const typeName = `Record<string, ${innerType.typeName}>`;
781
934
  const defaultValue = schema.isNullable ? "null" : "{}";
@@ -791,6 +944,22 @@ function tsRecordFromSchema(schema, context) {
791
944
  }
792
945
  return mainPart;
793
946
  },
947
+ cloneTemplate(input, target) {
948
+ const main = `${target} = {};
949
+ for (const [_key, _value] of Object.entries(${input})) {
950
+ let ${target}Value: ${innerType.typeName};
951
+ ${innerType.cloneTemplate("_value", `${target}Value`)}
952
+ ${target}[_key] = ${target}Value;
953
+ }`;
954
+ if (schema.isNullable) {
955
+ return `if (${input} !== null) {
956
+ ${main}
957
+ } else {
958
+ ${target} = null;
959
+ }`;
960
+ }
961
+ return main;
962
+ },
794
963
  fromJsonTemplate(input, target) {
795
964
  return `if (isObject(${input})) {
796
965
  ${target} = {};
@@ -835,7 +1004,7 @@ function tsRecordFromSchema(schema, context) {
835
1004
  ${target} += '}';
836
1005
  `;
837
1006
  },
838
- toQueryStringTemplate(_, __, ___) {
1007
+ setSearchParamTemplate(_, __, ___) {
839
1008
  return `console.warn('[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.')`;
840
1009
  },
841
1010
  content: innerType.content
@@ -845,19 +1014,29 @@ function tsRecordFromSchema(schema, context) {
845
1014
  function tsRefFromSchema(schema, context) {
846
1015
  const typeName = pascalCase(validVarName(schema.ref), { normalize: true });
847
1016
  const prefixedTypeName = `${context.typePrefix}${typeName}`;
848
- const defaultValue = schema.isNullable ? "null" : `${prefixedTypeName}.new()`;
1017
+ const defaultValue = schema.isNullable ? "null" : `${prefixedTypeName}New()`;
849
1018
  return {
850
1019
  typeName: schema.isNullable ? `${prefixedTypeName} | null` : prefixedTypeName,
851
1020
  defaultValue,
852
1021
  validationTemplate(input) {
853
1022
  if (schema.isNullable) {
854
- return `($$${prefixedTypeName}.validate(${input}) || ${input} === null)`;
1023
+ return `(${prefixedTypeName}Validate(${input}) || ${input} === null)`;
1024
+ }
1025
+ return `${prefixedTypeName}Validate(${input})`;
1026
+ },
1027
+ cloneTemplate(input, target) {
1028
+ if (schema.isNullable) {
1029
+ return `if (${input} !== null) {
1030
+ ${target} = ${prefixedTypeName}Clone(${input});
1031
+ } else {
1032
+ ${target} = null;
1033
+ }`;
855
1034
  }
856
- return `$$${prefixedTypeName}.validate(${input})`;
1035
+ return `${target} = ${prefixedTypeName}Clone(${input});`;
857
1036
  },
858
1037
  fromJsonTemplate(input, target) {
859
1038
  return `if (isObject(${input})) {
860
- ${target} = $$${prefixedTypeName}.fromJson(${input});
1039
+ ${target} = ${prefixedTypeName}FromJson(${input});
861
1040
  } else {
862
1041
  ${target} = ${defaultValue};
863
1042
  }`;
@@ -865,14 +1044,14 @@ function tsRefFromSchema(schema, context) {
865
1044
  toJsonTemplate(input, target, _key) {
866
1045
  if (schema.isNullable) {
867
1046
  return `if (${input} !== null) {
868
- ${target} += $$${prefixedTypeName}.toJsonString(${input});
1047
+ ${target} += ${prefixedTypeName}ToJsonString(${input});
869
1048
  } else {
870
1049
  ${target} += 'null';
871
1050
  }`;
872
1051
  }
873
- return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
1052
+ return `${target} += ${prefixedTypeName}ToJsonString(${input});`;
874
1053
  },
875
- toQueryStringTemplate(_, __, ___) {
1054
+ setSearchParamTemplate(_, __, ___) {
876
1055
  return `console.warn('[WARNING] Nested objects cannot be serialized to query string. Ignoring property at ${context.instancePath}.');`;
877
1056
  },
878
1057
  content: ""
@@ -901,9 +1080,9 @@ function httpRpcFromDefinition(def, context) {
901
1080
  const key = getRpcKey(context);
902
1081
  const params = def.params ? `${context.typePrefix}${pascalCase(validVarName(def.params))}` : void 0;
903
1082
  const response = def.response ? `${context.typePrefix}${pascalCase(validVarName(def.response), { normalize: true })}` : void 0;
904
- const serializerMethod = def.method === "get" ? "toUrlQueryString" : "toJsonString";
1083
+ const serializerMethod = def.method === "get" ? "ToUrlSearchParamsString" : "ToJsonString";
905
1084
  if (def.isEventStream) {
906
- context.usedFeatures.sse = true;
1085
+ context.useRpcTypes.sse = true;
907
1086
  return `${getJsDocComment({
908
1087
  description: def.description,
909
1088
  isDeprecated: def.isDeprecated
@@ -916,9 +1095,9 @@ function httpRpcFromDefinition(def, context) {
916
1095
  headers: this._headers,
917
1096
  onError: this._onError,
918
1097
  ${params ? "params: params," : ""}
919
- responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
920
- responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
921
- serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"},
1098
+ responseFromJson: ${response ? `${response}FromJson` : "() => {}"},
1099
+ responseFromString: ${response ? `${response}FromJsonString` : "() => {}"},
1100
+ serializer: ${params ? `${params}${serializerMethod}` : "() => {}"},
922
1101
  clientVersion: "${context.versionNumber}",
923
1102
  },
924
1103
  options,
@@ -936,16 +1115,16 @@ function httpRpcFromDefinition(def, context) {
936
1115
  headers: this._headers,
937
1116
  onError: this._onError,
938
1117
  ${params ? "params: params," : ""}
939
- responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
940
- responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
941
- serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"},
1118
+ responseFromJson: ${response ? `${response}FromJson` : "() => {}"},
1119
+ responseFromString: ${response ? `${response}FromJsonString` : "() => {}"},
1120
+ serializer: ${params ? `${params}${serializerMethod}` : "() => {}"},
942
1121
  clientVersion: "${context.versionNumber}",
943
1122
  options: options ?? this._options,
944
1123
  });
945
1124
  }`;
946
1125
  }
947
1126
  function wsRpcFromDefinition(def, context) {
948
- context.usedFeatures.ws = true;
1127
+ context.useRpcTypes.ws = true;
949
1128
  const key = getRpcKey(context);
950
1129
  const params = def.params ? `${context.typePrefix}${pascalCase(validVarName(def.params))}` : void 0;
951
1130
  const response = def.response ? `${context.typePrefix}${pascalCase(validVarName(def.response), { normalize: true })}` : void 0;
@@ -953,19 +1132,7 @@ function wsRpcFromDefinition(def, context) {
953
1132
  description: def.description,
954
1133
  isDeprecated: def.isDeprecated
955
1134
  })} async ${key}(options: WsOptions<${response ?? "undefined"}> = {}): Promise<WsController<${params ?? "undefined"},${response ?? "undefined"}>> {
956
- return arriWsRequest<${params ?? "undefined"}, ${response ?? "undefined"}>({
957
- url: \`\${this._baseUrl}${def.path}\`,
958
- headers: this._headers,
959
- responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
960
- responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
961
- serializer: ${params ? `$$${params}.toJsonString` : "() => {}"},
962
- onOpen: options.onOpen,
963
- onClose: options.onClose,
964
- onError: options.onError,
965
- onConnectionError: options.onConnectionError,
966
- onMessage: options.onMessage,
967
- clientVersion: "${context.versionNumber}",
968
- });
1135
+ throw new Error('not implemented');
969
1136
  }`;
970
1137
  }
971
1138
  function getRpcKey(context) {
@@ -988,8 +1155,9 @@ function tsServiceFromDefinition(def, context) {
988
1155
  instancePath: `${context.instancePath}.${key2}`,
989
1156
  schemaPath: `${context.instancePath}.${key2}`,
990
1157
  versionNumber: context.versionNumber,
991
- usedFeatures: context.usedFeatures,
992
- rpcGenerators: context.rpcGenerators
1158
+ useRpcTypes: context.useRpcTypes,
1159
+ rpcGenerators: context.rpcGenerators,
1160
+ features: context.features
993
1161
  });
994
1162
  if (subService.content) {
995
1163
  subServices.push(subService);
@@ -1007,8 +1175,9 @@ function tsServiceFromDefinition(def, context) {
1007
1175
  discriminatorKey: "",
1008
1176
  discriminatorValue: "",
1009
1177
  versionNumber: context.versionNumber,
1010
- usedFeatures: context.usedFeatures,
1011
- rpcGenerators: context.rpcGenerators
1178
+ useRpcTypes: context.useRpcTypes,
1179
+ rpcGenerators: context.rpcGenerators,
1180
+ features: context.features
1012
1181
  });
1013
1182
  if (rpc) {
1014
1183
  rpcParts.push(rpc);
@@ -1103,11 +1272,16 @@ async function createTypescriptClient(def, options) {
1103
1272
  instancePath: "",
1104
1273
  schemaPath: "",
1105
1274
  versionNumber: def.info?.version ?? "",
1106
- usedFeatures: {
1275
+ useRpcTypes: {
1107
1276
  sse: false,
1108
1277
  ws: false
1109
1278
  },
1110
- rpcGenerators: options.rpcGenerators ?? {}
1279
+ rpcGenerators: options.rpcGenerators ?? {},
1280
+ features: {
1281
+ validateFn: options.features?.validateFn ?? false,
1282
+ cloneFn: options.features?.cloneFn ?? false,
1283
+ validatorObj: options.features?.validatorObj ?? true
1284
+ }
1111
1285
  };
1112
1286
  const serviceDefinitions = unflattenProcedures(
1113
1287
  def.procedures,
@@ -1126,13 +1300,37 @@ async function createTypescriptClient(def, options) {
1126
1300
  discriminatorKey: "",
1127
1301
  discriminatorValue: "",
1128
1302
  versionNumber: context.versionNumber,
1129
- usedFeatures: context.usedFeatures,
1130
- rpcGenerators: context.rpcGenerators
1303
+ useRpcTypes: context.useRpcTypes,
1304
+ rpcGenerators: context.rpcGenerators,
1305
+ features: context.features
1131
1306
  });
1132
1307
  if (result2.content) {
1133
1308
  types.push(result2.content);
1134
1309
  }
1135
1310
  }
1311
+ let validatorStr = "";
1312
+ if (context.features.validatorObj) {
1313
+ const modelFeatures = [];
1314
+ const enumFeatures = [];
1315
+ modelFeatures.push("new");
1316
+ enumFeatures.push("new");
1317
+ if (context.features.validateFn) {
1318
+ modelFeatures.push("validate");
1319
+ enumFeatures.push("validate");
1320
+ }
1321
+ if (context.features.cloneFn) {
1322
+ modelFeatures.push("clone");
1323
+ }
1324
+ modelFeatures.push("fromJson");
1325
+ modelFeatures.push("fromJsonString");
1326
+ modelFeatures.push("toJsonString");
1327
+ modelFeatures.push("toUrlSearchParams");
1328
+ modelFeatures.push("toUrlSearchParamsString");
1329
+ enumFeatures.push("values");
1330
+ enumFeatures.push("fromSerialValue");
1331
+ validatorStr = `export type ${context.clientName}Validator<T> = ArriModelValidator<T, ${modelFeatures.map((feat) => `'${feat}'`).join(" | ")}>;
1332
+ export type ExampleClientEnumValidator<T> = ArriEnumValidator<T, ${enumFeatures.map((feat) => `'${feat}'`).join(" | ")}>;`;
1333
+ }
1136
1334
  const imports = `// This file was autogenerated by @arrirpc/codegen-ts. Do not modify directly.
1137
1335
  // For more information visit https://github.com/modiimedia/arri
1138
1336
 
@@ -1143,9 +1341,8 @@ import {
1143
1341
  ArriModelValidator,
1144
1342
  type ArriRequestOptions,
1145
1343
  arriRequest,
1146
- ${context.usedFeatures.sse ? "arriSseRequest," : ""}
1147
- ${context.usedFeatures.ws ? "arriWsRequest," : ""}
1148
- ${context.usedFeatures.sse ? "type EventSourceController," : ""}
1344
+ ${context.useRpcTypes.sse ? "arriSseRequest," : ""}
1345
+ ${context.useRpcTypes.sse ? "type EventSourceController," : ""}
1149
1346
  INT8_MAX,
1150
1347
  INT8_MIN,
1151
1348
  INT16_MAX,
@@ -1157,7 +1354,6 @@ import {
1157
1354
  isObject,
1158
1355
  parseString,
1159
1356
  parseBoolean,
1160
- parseNumber,
1161
1357
  parseTimestamp,
1162
1358
  parseNumberInt,
1163
1359
  parseNumberBigInt,
@@ -1171,7 +1367,7 @@ import {
1171
1367
  parseNullableNumberBigInt,
1172
1368
  parseNullableNumberUnsignedBigInt,
1173
1369
  serializeString,
1174
- ${context.usedFeatures.sse ? "type SseOptions," : ""}
1370
+ ${context.useRpcTypes.sse ? "type SseOptions," : ""}
1175
1371
  UINT8_MAX,
1176
1372
  UINT16_MAX,
1177
1373
  UINT32_MAX,
@@ -1179,11 +1375,12 @@ import {
1179
1375
  type Fetch,
1180
1376
  type $Fetch,
1181
1377
  createFetch,
1182
- ${context.usedFeatures.ws ? "type WsController," : ""}
1183
- ${context.usedFeatures.ws ? "type WsOptions," : ""}
1378
+ ${context.useRpcTypes.ws ? "type WsController," : ""}
1379
+ ${context.useRpcTypes.ws ? "type WsOptions," : ""}
1184
1380
  } from "@arrirpc/client";
1185
1381
 
1186
- type HeaderMap = Record<string, string | undefined>;`;
1382
+ type HeaderMap = Record<string, string | undefined>;
1383
+ ${validatorStr}`;
1187
1384
  if (!mainService.content) {
1188
1385
  const result2 = `${imports}
1189
1386
 
@@ -1253,4 +1450,4 @@ function tsTypeFromSchema(schema, context) {
1253
1450
  return tsAnyFromSchema(schema, context);
1254
1451
  }
1255
1452
 
1256
- export { createTypescriptClient, defineRpcGenerator, getJsDocComment, getRpcKey, getTsTypeName, httpRpcFromDefinition, tsRpcFromDefinition, tsTypeFromSchema, typescriptClientGenerator, validVarName, wsRpcFromDefinition };
1453
+ export { createTypescriptClient, defaultCloneTemplate, defineRpcGenerator, getJsDocComment, getRpcKey, getTsTypeName, httpRpcFromDefinition, tsRpcFromDefinition, tsTypeFromSchema, typescriptClientGenerator, validVarName, wsRpcFromDefinition };