@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.cjs CHANGED
@@ -8,28 +8,9 @@ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'defau
8
8
 
9
9
  const prettier__default = /*#__PURE__*/_interopDefaultCompat(prettier);
10
10
 
11
- function tsAnyFromSchema(schema, context) {
12
- const typeName = "any";
13
- const defaultValue = schema.isNullable ? "null" : "undefined";
14
- return {
15
- typeName,
16
- defaultValue,
17
- validationTemplate(_) {
18
- return "true";
19
- },
20
- fromJsonTemplate(input, target) {
21
- return `${target} = ${input}`;
22
- },
23
- toJsonTemplate(input, target) {
24
- return `${target} += JSON.stringify(${input})`;
25
- },
26
- toQueryStringTemplate(_input, _target, _key) {
27
- return `console.warn('[WARNING] Cannot serialize any\\'s to query string. Skipping property at ${context.instancePath}.')`;
28
- },
29
- content: ""
30
- };
11
+ function defaultCloneTemplate(input, target) {
12
+ return `${target} = ${input};`;
31
13
  }
32
-
33
14
  function getJsDocComment(metadata) {
34
15
  const descriptionParts = [];
35
16
  if (metadata?.description?.length) {
@@ -77,6 +58,29 @@ function getTsTypeName(schema, context) {
77
58
  return validVarName(name);
78
59
  }
79
60
 
61
+ function tsAnyFromSchema(schema, context) {
62
+ const typeName = "any";
63
+ const defaultValue = schema.isNullable ? "null" : "undefined";
64
+ return {
65
+ typeName,
66
+ defaultValue,
67
+ validationTemplate(_) {
68
+ return "true";
69
+ },
70
+ cloneTemplate: defaultCloneTemplate,
71
+ fromJsonTemplate(input, target) {
72
+ return `${target} = ${input}`;
73
+ },
74
+ toJsonTemplate(input, target) {
75
+ return `${target} += JSON.stringify(${input})`;
76
+ },
77
+ setSearchParamTemplate(_input, _target, _key) {
78
+ return `console.warn('[WARNING] Cannot serialize any\\'s to query string. Skipping property at ${context.instancePath}.')`;
79
+ },
80
+ content: ""
81
+ };
82
+ }
83
+
80
84
  function tsArrayFromSchema(schema, context) {
81
85
  const innerType = tsTypeFromSchema(schema.elements, {
82
86
  clientName: context.clientName,
@@ -88,8 +92,9 @@ function tsArrayFromSchema(schema, context) {
88
92
  discriminatorKey: "",
89
93
  discriminatorValue: "",
90
94
  versionNumber: context.versionNumber,
91
- usedFeatures: context.usedFeatures,
92
- rpcGenerators: context.rpcGenerators
95
+ useRpcTypes: context.useRpcTypes,
96
+ rpcGenerators: context.rpcGenerators,
97
+ features: context.features
93
98
  });
94
99
  const typeName = `(${innerType.typeName})[]`;
95
100
  const defaultValue = schema.isNullable ? "null" : "[]";
@@ -107,6 +112,22 @@ function tsArrayFromSchema(schema, context) {
107
112
  }
108
113
  return mainPart;
109
114
  },
115
+ cloneTemplate(input, target) {
116
+ const main = `${target} = [];
117
+ for (const ${target}El of ${input}) {
118
+ let ${target}ElValue: ${innerType.typeName};
119
+ ${innerType.cloneTemplate(`${target}El`, `${target}ElValue`)}
120
+ ${target}.push(${target}ElValue);
121
+ }`;
122
+ if (schema.isNullable) {
123
+ return `if (${input} !== null) {
124
+ ${main}
125
+ } else {
126
+ ${target} = null;
127
+ }`;
128
+ }
129
+ return main;
130
+ },
110
131
  fromJsonTemplate(input, target) {
111
132
  return `if (Array.isArray(${input})) {
112
133
  ${target} = [];
@@ -143,7 +164,7 @@ function tsArrayFromSchema(schema, context) {
143
164
  }
144
165
  ${target} += ']';`;
145
166
  },
146
- toQueryStringTemplate(_input, _target, _key) {
167
+ setSearchParamTemplate(_input, _target, _key) {
147
168
  return `console.warn('[WARNING] Cannot serialize arrays to query string. Skipping property at ${context.instancePath}.')`;
148
169
  },
149
170
  content: innerType.content
@@ -153,34 +174,44 @@ function tsArrayFromSchema(schema, context) {
153
174
  function tsObjectFromSchema(schema, context) {
154
175
  const typeName = getTsTypeName(schema, context);
155
176
  const prefixedTypeName = `${context.typePrefix}${typeName}`;
156
- const defaultValue = schema.isNullable ? "null" : `$$${prefixedTypeName}.new()`;
177
+ const defaultValue = schema.isNullable ? "null" : `${prefixedTypeName}New()`;
157
178
  const result = {
158
179
  typeName: schema.isNullable ? `${prefixedTypeName} | null` : prefixedTypeName,
159
180
  defaultValue,
160
181
  validationTemplate(input) {
161
182
  if (schema.isNullable) {
162
- return `($$${prefixedTypeName}.validate(${input}) || ${input} === null)`;
183
+ return `(${prefixedTypeName}Validate(${input}) || ${input} === null)`;
163
184
  }
164
- return `$$${prefixedTypeName}.validate(${input})`;
185
+ return `${prefixedTypeName}Validate(${input})`;
165
186
  },
166
187
  fromJsonTemplate(input, target) {
167
188
  return `if (isObject(${input})) {
168
- ${target} = $$${prefixedTypeName}.fromJson(${input});
189
+ ${target} = ${prefixedTypeName}FromJson(${input});
169
190
  } else {
170
191
  ${target} = ${defaultValue};
171
192
  }`;
172
193
  },
194
+ cloneTemplate(input, target) {
195
+ if (schema.isNullable) {
196
+ return `if (${input} !== null) {
197
+ ${target} = ${prefixedTypeName}Clone(${input});
198
+ } else {
199
+ ${target} = null;
200
+ }`;
201
+ }
202
+ return `${target} = ${prefixedTypeName}Clone(${input});`;
203
+ },
173
204
  toJsonTemplate(input, target) {
174
205
  if (schema.isNullable) {
175
206
  return `if (${input} !== null) {
176
- ${target} += $$${prefixedTypeName}.toJsonString(${input});
207
+ ${target} += ${prefixedTypeName}ToJsonString(${input});
177
208
  } else {
178
209
  ${target} += 'null';
179
210
  }`;
180
211
  }
181
- return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
212
+ return `${target} += ${prefixedTypeName}ToJsonString(${input});`;
182
213
  },
183
- toQueryStringTemplate(_input, _target) {
214
+ setSearchParamTemplate(_input, _target) {
184
215
  return `console.warn('[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.')`;
185
216
  },
186
217
  content: ""
@@ -193,8 +224,9 @@ function tsObjectFromSchema(schema, context) {
193
224
  const fromJsonParts = [];
194
225
  const constructionParts = [];
195
226
  const toJsonParts = [];
196
- const toQueryParts = [];
227
+ const setSearchParamParts = [];
197
228
  const validationParts = ["isObject(input)"];
229
+ const cloneParts = [];
198
230
  const subContentParts = [];
199
231
  let hasKey = false;
200
232
  if (context.discriminatorParent && context.discriminatorValue && context.discriminatorKey) {
@@ -205,12 +237,13 @@ function tsObjectFromSchema(schema, context) {
205
237
  fieldParts.push(`${key}: "${context.discriminatorValue}",`);
206
238
  fromJsonParts.push(`const _${key} = "${context.discriminatorValue}"`);
207
239
  toJsonParts.push(`json += '"${key}":"${context.discriminatorValue}"'`);
208
- toQueryParts.push(
209
- `queryParts.push('${context.discriminatorKey}=${context.discriminatorValue}');`
240
+ setSearchParamParts.push(
241
+ `params.set('${context.discriminatorKey}', '${context.discriminatorValue}');`
210
242
  );
211
243
  validationParts.push(
212
244
  `input.${key} === '${context.discriminatorValue}'`
213
245
  );
246
+ cloneParts.push(`const _${key} = "${context.discriminatorValue}"`);
214
247
  newParts.push(`${key}: "${context.discriminatorValue}",`);
215
248
  constructionParts.push(`${key}: _${key},`);
216
249
  }
@@ -226,8 +259,9 @@ function tsObjectFromSchema(schema, context) {
226
259
  discriminatorKey: "",
227
260
  discriminatorValue: "",
228
261
  versionNumber: context.versionNumber,
229
- usedFeatures: context.usedFeatures,
230
- rpcGenerators: context.rpcGenerators
262
+ useRpcTypes: context.useRpcTypes,
263
+ rpcGenerators: context.rpcGenerators,
264
+ features: context.features
231
265
  });
232
266
  if (prop.content) subContentParts.push(prop.content);
233
267
  const fieldName = validVarName(codegenUtils.camelCase(key, { normalize: true }));
@@ -246,11 +280,13 @@ function tsObjectFromSchema(schema, context) {
246
280
  toJsonParts.push(
247
281
  prop.toJsonTemplate(`input.${fieldName}`, "json", key)
248
282
  );
249
- toQueryParts.push(
250
- prop.toQueryStringTemplate(`input.${fieldName}`, "queryParts", key)
283
+ setSearchParamParts.push(
284
+ prop.setSearchParamTemplate(`input.${fieldName}`, "params", key)
251
285
  );
252
286
  const validationPart = prop.validationTemplate(`input.${fieldName}`);
253
287
  validationParts.push(validationPart);
288
+ cloneParts.push(`let ${tempKey}: ${prop.typeName}`);
289
+ cloneParts.push(prop.cloneTemplate(`input.${fieldName}`, tempKey));
254
290
  constructionParts.push(`${fieldName}: ${tempKey},`);
255
291
  hasKey = true;
256
292
  }
@@ -269,8 +305,9 @@ function tsObjectFromSchema(schema, context) {
269
305
  discriminatorKey: "",
270
306
  discriminatorValue: "",
271
307
  versionNumber: context.versionNumber,
272
- usedFeatures: context.usedFeatures,
273
- rpcGenerators: context.rpcGenerators
308
+ useRpcTypes: context.useRpcTypes,
309
+ rpcGenerators: context.rpcGenerators,
310
+ features: context.features
274
311
  });
275
312
  if (prop.content) subContentParts.push(prop.content);
276
313
  const fieldName = validVarName(codegenUtils.camelCase(key, { normalize: true }));
@@ -295,51 +332,89 @@ function tsObjectFromSchema(schema, context) {
295
332
  _hasKey = true;
296
333
  }`);
297
334
  }
298
- toQueryParts.push(`if (typeof input.${fieldName} !== 'undefined') {
299
- ${prop.toQueryStringTemplate(`input.${fieldName}`, "queryParts", key)}
335
+ setSearchParamParts.push(`if (typeof input.${fieldName} !== 'undefined') {
336
+ ${prop.setSearchParamTemplate(`input.${fieldName}`, "params", key)}
300
337
  }`);
301
338
  const validationPart = prop.validationTemplate(`input.${fieldName}`);
302
339
  validationParts.push(
303
340
  `((${validationPart}) || typeof input.${fieldName} === 'undefined')`
304
341
  );
342
+ cloneParts.push(`let ${tempKey}: ${prop.typeName} | undefined;`);
343
+ const clonePart = prop.cloneTemplate(`input.${fieldName}`, tempKey);
344
+ cloneParts.push(`if (typeof input.${fieldName} !== 'undefined') {
345
+ ${clonePart}
346
+ }`);
305
347
  constructionParts.push(`${fieldName}: ${tempKey},`);
306
348
  }
307
349
  result.content = `${getJsDocComment(schema.metadata)}export interface ${prefixedTypeName} {
308
350
  ${fieldParts.map((part) => ` ${part}`).join("\n")}
309
- }
310
- ${context.discriminatorParent && context.discriminatorValue ? "" : "export "}const $$${prefixedTypeName}: ArriModelValidator<${prefixedTypeName}> = {
311
- new(): ${prefixedTypeName} {
312
- return {
313
- ${newParts.map((part) => ` ${part}`).join("\n")}
314
- };
315
- },
316
- validate(input): input is ${prefixedTypeName} {
317
- return (
318
- ${validationParts.map((part) => ` ${part}`).join("&& \n")}
319
- )
320
- },
321
- fromJson(input): ${prefixedTypeName} {
322
- ${fromJsonParts.map((part) => ` ${part}`).join("\n")}
323
- return {
324
- ${constructionParts.map((part) => ` ${part}`).join("\n")}
325
- }
326
- },
327
- fromJsonString(input): ${prefixedTypeName} {
328
- return $$${prefixedTypeName}.fromJson(JSON.parse(input));
329
- },
330
- toJsonString(input): string {
331
- let json = "{";
332
- ${toJsonParts.map((part) => ` ${part}`).join("\n")}
333
- json += "}";
334
- return json;
335
- },
336
- toUrlQueryString(input): string {
337
- const queryParts: string[] = [];
338
- ${toQueryParts.map((part) => ` ${part}`).join("\n")}
339
- return queryParts.join("&");
351
+ }`;
352
+ result.content += `
353
+ export function ${prefixedTypeName}New(): ${prefixedTypeName} {
354
+ return {
355
+ ${newParts.map((part) => ` ${part}`).join("\n")}
356
+ };
357
+ }`;
358
+ if (context.features.validateFn) {
359
+ result.content += `
360
+ export function ${prefixedTypeName}Validate(input: unknown): input is ${prefixedTypeName} {
361
+ return (
362
+ ${validationParts.map((part) => ` ${part}`).join("&& \n")}
363
+ )
364
+ }`;
365
+ }
366
+ if (context.features.cloneFn) {
367
+ result.content += `
368
+ export function ${prefixedTypeName}Clone(input: ${prefixedTypeName}): ${prefixedTypeName} {
369
+ ${cloneParts.map((part) => ` ${part}`).join("\n")}
370
+ return {
371
+ ${constructionParts.map((part) => ` ${part}`).join("\n")}
340
372
  }
373
+ }`;
374
+ }
375
+ result.content += `
376
+ export function ${prefixedTypeName}FromJson(input: Record<string, unknown>): ${prefixedTypeName} {
377
+ ${fromJsonParts.map((part) => ` ${part}`).join("\n")}
378
+ return {
379
+ ${constructionParts.map((part) => ` ${part}`).join("\n")}
380
+ }
381
+ }`;
382
+ result.content += `
383
+ export function ${prefixedTypeName}FromJsonString(input: string): ${prefixedTypeName} {
384
+ return ${prefixedTypeName}FromJson(JSON.parse(input));
385
+ }`;
386
+ result.content += `
387
+ export function ${prefixedTypeName}ToJsonString(input: ${prefixedTypeName}): string {
388
+ let json = "{";
389
+ ${toJsonParts.map((part) => ` ${part}`).join("\n")}
390
+ json += "}";
391
+ return json;
392
+ }`;
393
+ result.content += `
394
+ export function ${prefixedTypeName}ToUrlSearchParams(input: ${prefixedTypeName}): URLSearchParams {
395
+ const params = new URLSearchParams();
396
+ ${setSearchParamParts.map((part) => ` ${part}`).join("\n")}
397
+ return params;
398
+ }`;
399
+ result.content += `
400
+ export function ${prefixedTypeName}ToUrlSearchParamsString(input: ${prefixedTypeName}): string {
401
+ return ${prefixedTypeName}ToUrlSearchParams(input).toString();
341
402
  }
342
-
403
+ `;
404
+ if (context.features.validatorObj) {
405
+ result.content += `
406
+ ${context.discriminatorParent && context.discriminatorValue ? "" : "export "}const $$${prefixedTypeName}: ${context.clientName}Validator<${prefixedTypeName}> = {
407
+ new: ${prefixedTypeName}New,
408
+ ${context.features.validateFn ? `validate: ${prefixedTypeName}Validate,` : ""}
409
+ ${context.features.cloneFn ? `clone: ${prefixedTypeName}Clone,` : ""}
410
+ fromJson: ${prefixedTypeName}FromJson,
411
+ fromJsonString: ${prefixedTypeName}FromJsonString,
412
+ toJsonString: ${prefixedTypeName}ToJsonString,
413
+ toUrlSearchParams: ${prefixedTypeName}ToUrlSearchParams,
414
+ toUrlSearchParamsString: ${prefixedTypeName}ToUrlSearchParamsString,
415
+ }`;
416
+ }
417
+ result.content += `
343
418
  ${subContentParts.join("\n")}`;
344
419
  context.generatedTypes.push(typeName);
345
420
  return result;
@@ -348,20 +423,30 @@ ${subContentParts.join("\n")}`;
348
423
  function tsTaggedUnionFromSchema(schema, context) {
349
424
  const typeName = getTsTypeName(schema, context);
350
425
  const prefixedTypeName = `${context.typePrefix}${typeName}`;
351
- const defaultValue = schema.isNullable ? "null" : `$$${prefixedTypeName}.new()`;
426
+ const defaultValue = schema.isNullable ? "null" : `${prefixedTypeName}New()`;
352
427
  const result = {
353
428
  typeName: schema.isNullable ? `${prefixedTypeName} | null` : prefixedTypeName,
354
429
  defaultValue,
355
430
  validationTemplate(input) {
356
- const mainPart = `$$${prefixedTypeName}.validate(${input})`;
431
+ const mainPart = `${prefixedTypeName}Validate(${input})`;
357
432
  if (schema.isNullable) {
358
433
  return `(${mainPart} || ${input} === null)`;
359
434
  }
360
435
  return mainPart;
361
436
  },
437
+ cloneTemplate(input, target) {
438
+ if (schema.isNullable) {
439
+ return `if (${input} !== null) {
440
+ ${target} = ${prefixedTypeName}Clone(${input});
441
+ } else {
442
+ ${target} = null;
443
+ }`;
444
+ }
445
+ return `${target} = ${prefixedTypeName}Clone(${input});`;
446
+ },
362
447
  fromJsonTemplate(input, target) {
363
448
  return `if (isObject(${input})) {
364
- ${target} = $$${prefixedTypeName}.fromJson(${input});
449
+ ${target} = ${prefixedTypeName}FromJson(${input});
365
450
  } else {
366
451
  ${target} = ${defaultValue};
367
452
  }`;
@@ -369,14 +454,14 @@ function tsTaggedUnionFromSchema(schema, context) {
369
454
  toJsonTemplate(input, target, _key) {
370
455
  if (schema.isNullable) {
371
456
  return `if (${input} != null) {
372
- ${target} += $$${prefixedTypeName}.toJsonString(${input});
457
+ ${target} += ${prefixedTypeName}ToJsonString(${input});
373
458
  } else {
374
459
  ${target} += 'null';
375
460
  }`;
376
461
  }
377
- return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
462
+ return `${target} += ${prefixedTypeName}ToJsonString(${input});`;
378
463
  },
379
- toQueryStringTemplate(_, __, ___) {
464
+ setSearchParamTemplate(_, __, ___) {
380
465
  return `console.warn('[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.');`;
381
466
  },
382
467
  content: ""
@@ -396,66 +481,104 @@ function tsTaggedUnionFromSchema(schema, context) {
396
481
  discriminatorKey,
397
482
  discriminatorValue: key,
398
483
  versionNumber: context.versionNumber,
399
- usedFeatures: context.usedFeatures,
400
- rpcGenerators: context.rpcGenerators
484
+ useRpcTypes: context.useRpcTypes,
485
+ rpcGenerators: context.rpcGenerators,
486
+ features: context.features
401
487
  });
402
488
  subTypes.push({ value: key, data: subType });
403
489
  }
404
- result.content = `${getJsDocComment(schema.metadata)}export type ${prefixedTypeName} = ${subTypes.map((type) => type.data.typeName).join(" |")};
405
- export const $$${prefixedTypeName}: ArriModelValidator<${prefixedTypeName}> = {
406
- new(): ${prefixedTypeName} {
407
- return $$${subTypes[0].data.typeName}.new();
408
- },
409
- validate(input): input is ${prefixedTypeName} {
410
- if (!isObject(input)) {
411
- return false;
412
- }
413
- if (typeof input.${discriminatorKey} !== 'string') {
490
+ result.content = `${getJsDocComment(schema.metadata)}export type ${prefixedTypeName} = ${subTypes.map((type) => type.data.typeName).join(" |")};`;
491
+ result.content += `
492
+ export function ${prefixedTypeName}New(): ${prefixedTypeName} {
493
+ return ${subTypes[0].data.typeName}New();
494
+ }`;
495
+ if (context.features.validateFn) {
496
+ result.content += `
497
+ export function ${prefixedTypeName}Validate(input: unknown): input is ${prefixedTypeName} {
498
+ if (!isObject(input)) {
499
+ return false;
500
+ }
501
+ if (typeof input.${discriminatorKey} !== 'string') {
502
+ return false;
503
+ }
504
+ switch (input.${discriminatorKey}) {
505
+ ${subTypes.map(
506
+ (type) => ` case "${type.value}":
507
+ return ${type.data.typeName}Validate(input);`
508
+ ).join("\n")}
509
+ default:
414
510
  return false;
415
- }
416
- switch (input.${discriminatorKey}) {
511
+ }
512
+ }`;
513
+ }
514
+ if (context.features.cloneFn) {
515
+ result.content += `
516
+ export function ${prefixedTypeName}Clone(input: ${prefixedTypeName}): ${prefixedTypeName} {
517
+ switch (input.${discriminatorKey}) {
417
518
  ${subTypes.map(
418
- (type) => ` case "${type.value}":
419
- return $$${type.data.typeName}.validate(input);`
420
- ).join("\n")}
421
- default:
422
- return false;
423
- }
424
- },
425
- fromJson(input): ${prefixedTypeName} {
426
- switch (input.${discriminatorKey}) {
519
+ (type) => ` case "${type.value}":
520
+ return ${type.data.typeName}Clone(input);`
521
+ ).join("\n")}
522
+ default:
523
+ throw new Error('Unimplemented');
524
+ }
525
+ }`;
526
+ }
527
+ result.content += `
528
+ export function ${prefixedTypeName}FromJson(input: Record<string, unknown>): ${prefixedTypeName} {
529
+ switch (input.${discriminatorKey}) {
427
530
  ${subTypes.map(
428
- (type) => ` case "${type.value}":
429
- return $$${type.data.typeName}.fromJson(input);`
531
+ (type) => ` case "${type.value}":
532
+ return ${type.data.typeName}FromJson(input);`
430
533
  ).join("\n")}
431
- default:
432
- return $$${subTypes[0].data.typeName}.new();
433
- }
434
- },
435
- fromJsonString(input): ${prefixedTypeName} {
436
- return $$${prefixedTypeName}.fromJson(JSON.parse(input));
437
- },
438
- toJsonString(input): string {
439
- switch (input.${discriminatorKey}) {
534
+ default:
535
+ return ${subTypes[0].data.typeName}New();
536
+ }
537
+ }`;
538
+ result.content += `
539
+ export function ${prefixedTypeName}FromJsonString(input: string): ${prefixedTypeName} {
540
+ return ${prefixedTypeName}FromJson(JSON.parse(input));
541
+ }`;
542
+ result.content += `
543
+ export function ${prefixedTypeName}ToJsonString(input: ${prefixedTypeName}): string {
544
+ switch (input.${discriminatorKey}) {
440
545
  ${subTypes.map(
441
- (type) => ` case "${type.value}":
442
- return $$${type.data.typeName}.toJsonString(input);`
546
+ (type) => ` case "${type.value}":
547
+ return ${type.data.typeName}ToJsonString(input);`
443
548
  ).join("\n")}
444
- default:
445
- throw new Error(\`Unhandled case "\${(input as any).${discriminatorKey}}"\`);
446
- }
447
- },
448
- toUrlQueryString(input): string {
449
- switch (input.${discriminatorKey}) {
549
+ default:
550
+ throw new Error(\`Unhandled case "\${(input as any).${discriminatorKey}}"\`);
551
+ }
552
+ }`;
553
+ result.content += `
554
+ export function ${prefixedTypeName}ToUrlSearchParams(input: ${prefixedTypeName}): URLSearchParams {
555
+ switch (input.${discriminatorKey}) {
450
556
  ${subTypes.map(
451
- (type) => ` case "${type.value}":
452
- return $$${type.data.typeName}.toUrlQueryString(input);`
557
+ (type) => ` case "${type.value}":
558
+ return ${type.data.typeName}ToUrlSearchParams(input);`
453
559
  ).join("\n")}
454
- default:
455
- throw new Error('Unhandled case');
456
- }
560
+ default:
561
+ throw new Error('Unhandled case');
457
562
  }
458
- }
563
+ }`;
564
+ result.content += `
565
+ export function ${prefixedTypeName}ToUrlSearchParamsString(input: ${prefixedTypeName}): string {
566
+ return ${prefixedTypeName}ToUrlSearchParams(input).toString();
567
+ }`;
568
+ if (context.features.validatorObj) {
569
+ result.content += `
570
+ export const $$${prefixedTypeName}: ${context.clientName}Validator<${prefixedTypeName}> = {
571
+ new: ${prefixedTypeName}New,
572
+ ${context.features.validateFn ? `validate: ${prefixedTypeName}Validate,` : ""}
573
+ ${context.features.cloneFn ? `clone: ${prefixedTypeName}Clone,` : ""}
574
+ fromJson: ${prefixedTypeName}FromJson,
575
+ fromJsonString: ${prefixedTypeName}FromJsonString,
576
+ toJsonString: ${prefixedTypeName}ToJsonString,
577
+ toUrlSearchParams: ${prefixedTypeName}ToUrlSearchParams,
578
+ toUrlSearchParamsString: ${prefixedTypeName}ToUrlSearchParamsString,
579
+ }`;
580
+ }
581
+ result.content += `
459
582
  ${subTypes.map((type) => type.data.content).join("\n")}
460
583
  `;
461
584
  context.generatedTypes.push(typeName);
@@ -470,19 +593,20 @@ function tsEnumFromSchema(schema, context) {
470
593
  const enumName = getTsTypeName(schema, context);
471
594
  const prefixedEnumName = `${context.typePrefix}${enumName}`;
472
595
  const typeName = schema.isNullable ? `${prefixedEnumName} | null` : prefixedEnumName;
473
- const defaultValue = schema.isNullable ? "null" : `$$${prefixedEnumName}.new()`;
596
+ const defaultValue = schema.isNullable ? "null" : `${prefixedEnumName}New()`;
474
597
  const result = {
475
598
  typeName,
476
599
  defaultValue,
477
600
  validationTemplate(input) {
478
601
  if (schema.isNullable) {
479
- return `($$${prefixedEnumName}.validate(${input}) || ${input} === null)`;
602
+ return `(${prefixedEnumName}Validate(${input}) || ${input} === null)`;
480
603
  }
481
- return `$$${prefixedEnumName}.validate(${input})`;
604
+ return `${prefixedEnumName}Validate(${input})`;
482
605
  },
606
+ cloneTemplate: defaultCloneTemplate,
483
607
  fromJsonTemplate(input, target) {
484
608
  return `if (typeof ${input} === 'string') {
485
- ${target} = $$${prefixedEnumName}.fromSerialValue(${input});
609
+ ${target} = ${prefixedEnumName}FromSerialValue(${input});
486
610
  } else {
487
611
  ${target} = ${defaultValue};
488
612
  }`;
@@ -497,8 +621,11 @@ function tsEnumFromSchema(schema, context) {
497
621
  }
498
622
  return `${target} += \`"\${${input}}"\``;
499
623
  },
500
- toQueryStringTemplate(input, target, key) {
501
- return `${target}.push(\`${key}=\${${input}}\`)`;
624
+ setSearchParamTemplate(input, target, key) {
625
+ if (schema.isNullable) {
626
+ return `${target}.set('${key}', \`\${${input}}\`);`;
627
+ }
628
+ return `${target}.set('${key}', ${input})`;
502
629
  },
503
630
  content: ""
504
631
  };
@@ -506,7 +633,7 @@ function tsEnumFromSchema(schema, context) {
506
633
  return result;
507
634
  }
508
635
  const name = `${context.typePrefix}${enumName}`;
509
- const valuesName = `$$${name}Values`;
636
+ const valuesName = `${name}Values`;
510
637
  result.content = `${getJsDocComment(schema.metadata)}export type ${name} = ${schema.enum.map((val) => `'${val}'`).join(" | ")};
511
638
  export const ${name} = {
512
639
  ${schema.enum.map((val) => {
@@ -516,32 +643,39 @@ ${schema.enum.map((val) => {
516
643
  return str;
517
644
  }).join("\n")}
518
645
  } as const;
519
- const ${valuesName} = [${schema.enum.map((val) => `'${val}'`).join(", ")}] as const;
520
- export const $$${name}: ArriEnumValidator<${name}> = {
521
- new(): ${name} {
522
- return ${valuesName}[0];
523
- },
524
- validate(input): input is ${enumName} {
525
- return (
526
- typeof input === 'string' &&
527
- ${valuesName}.includes(input as any)
528
- );
529
- },
530
- values: ${valuesName},
531
- fromSerialValue(input): ${name} {
532
- if (${valuesName}.includes(input as any)) {
533
- return input as ${name};
534
- }
535
- if (${valuesName}.includes(input.toLowerCase() as any)) {
536
- return input.toLowerCase() as ${name};
537
- }
538
- if (${valuesName}.includes(input.toUpperCase() as any)) {
539
- return input.toUpperCase() as ${name};
540
- }
541
- return "${schema.enum[0]}";
646
+ export const ${valuesName} = [${schema.enum.map((val) => `'${val}'`).join(", ")}] as const;`;
647
+ result.content += `
648
+ export function ${name}New(): ${name} {
649
+ return ${valuesName}[0];
650
+ }`;
651
+ if (context.features.validateFn) {
652
+ result.content += `
653
+ export function ${name}Validate(input: unknown): input is ${name} {
654
+ return typeof input === 'string' && ${valuesName}.includes(input as any);
655
+ }`;
656
+ }
657
+ result.content += `
658
+ export function ${name}FromSerialValue(input: string): ${name} {
659
+ if (${valuesName}.includes(input as any)) {
660
+ return input as ${name};
542
661
  }
543
- }
544
- `;
662
+ if (${valuesName}.includes(input.toLowerCase() as any)) {
663
+ return input.toLowerCase() as ${name};
664
+ }
665
+ if (${valuesName}.includes(input.toUpperCase() as any)) {
666
+ return input.toUpperCase() as ${name};
667
+ }
668
+ return "${schema.enum[0]}";
669
+ }`;
670
+ if (context.features.validatorObj) {
671
+ result.content += `
672
+ export const $$${name}: ${context.clientName}EnumValidator<${name} > = {
673
+ new: ${name}New,
674
+ ${context.features.validateFn ? `validate: ${name}Validate,` : ""}
675
+ values: ${valuesName},
676
+ fromSerialValue: ${name}FromSerialValue,
677
+ }`;
678
+ }
545
679
  context.generatedTypes.push(enumName);
546
680
  return result;
547
681
  }
@@ -558,6 +692,7 @@ function tsStringFromSchema(schema, _context) {
558
692
  }
559
693
  return `typeof ${input} === 'string'`;
560
694
  },
695
+ cloneTemplate: defaultCloneTemplate,
561
696
  fromJsonTemplate(input, target) {
562
697
  if (schema.isNullable) {
563
698
  return `${target} = parseNullableString(${input});`;
@@ -574,8 +709,11 @@ function tsStringFromSchema(schema, _context) {
574
709
  }
575
710
  return `${target} += serializeString(${input});`;
576
711
  },
577
- toQueryStringTemplate(input, target, key) {
578
- return `${target}.push(\`${key}=\${${input}}\`);`;
712
+ setSearchParamTemplate(input, target, key) {
713
+ if (schema.isNullable) {
714
+ return `${target}.set('${key}', \`\${${input}}\`);`;
715
+ }
716
+ return `${target}.set('${key}', ${input});`;
579
717
  },
580
718
  content: ""
581
719
  };
@@ -592,6 +730,7 @@ function tsBooleanFromSchema(schema, _context) {
592
730
  }
593
731
  return `typeof ${input} === 'boolean'`;
594
732
  },
733
+ cloneTemplate: defaultCloneTemplate,
595
734
  fromJsonTemplate(input, target) {
596
735
  if (schema.isNullable) {
597
736
  return `${target} = parseNullableBoolean(${input});`;
@@ -601,8 +740,8 @@ function tsBooleanFromSchema(schema, _context) {
601
740
  toJsonTemplate(input, target) {
602
741
  return `${target} += \`\${${input}}\`;`;
603
742
  },
604
- toQueryStringTemplate(input, target, key) {
605
- return `${target}.push(\`${key}=\${${input}}\`);`;
743
+ setSearchParamTemplate(input, target, key) {
744
+ return `${target}.set('${key}', \`\${${input}}\`);`;
606
745
  },
607
746
  content: ""
608
747
  };
@@ -619,6 +758,16 @@ function tsDateFromSchema(schema, _context) {
619
758
  }
620
759
  return `${input} instanceof Date`;
621
760
  },
761
+ cloneTemplate(input, target) {
762
+ if (schema.isNullable) {
763
+ return `if (${input} !== null) {
764
+ ${target} = new Date(${input}.getTime());
765
+ } else {
766
+ ${target} = null;
767
+ }`;
768
+ }
769
+ return `${target} = new Date(${input}.getTime());`;
770
+ },
622
771
  fromJsonTemplate(input, target) {
623
772
  if (schema.isNullable) {
624
773
  return `${target} = parseNullableTimestamp(${input});`;
@@ -635,11 +784,11 @@ function tsDateFromSchema(schema, _context) {
635
784
  }
636
785
  return `${target} += \`"\${${input}.toISOString()}"\``;
637
786
  },
638
- toQueryStringTemplate(input, target, key) {
787
+ setSearchParamTemplate(input, target, key) {
639
788
  if (schema.isNullable) {
640
- return `${target}.push(\`${key}=\${${input}?.toISOString()}\`)`;
789
+ return `${target}.set('${key}', \`\${${input}?.toISOString()}\`);`;
641
790
  }
642
- return `${target}.push(\`${key}=\${${input}.toISOString()}\`)`;
791
+ return `${target}.set('${key}', ${input}.toISOString());`;
643
792
  },
644
793
  content: ""
645
794
  };
@@ -656,6 +805,7 @@ function tsFloatFromSchema(schema, _context) {
656
805
  }
657
806
  return `typeof ${input} === 'number'`;
658
807
  },
808
+ cloneTemplate: defaultCloneTemplate,
659
809
  fromJsonTemplate(input, target) {
660
810
  if (schema.isNullable) {
661
811
  return `${target} = parseNullableNumberFloat(${input});`;
@@ -665,8 +815,8 @@ function tsFloatFromSchema(schema, _context) {
665
815
  toJsonTemplate(input, target) {
666
816
  return `${target} += \`\${${input}}\``;
667
817
  },
668
- toQueryStringTemplate(input, target, key) {
669
- return `${target}.push(\`${key}=\${${input}}\`)`;
818
+ setSearchParamTemplate(input, target, key) {
819
+ return `${target}.set('${key}', \`\${${input}}\`);`;
670
820
  },
671
821
  content: ""
672
822
  };
@@ -712,6 +862,7 @@ function tsIntFromSchema(schema, intType, _context) {
712
862
  }
713
863
  return mainPart;
714
864
  },
865
+ cloneTemplate: defaultCloneTemplate,
715
866
  fromJsonTemplate(input, target) {
716
867
  if (schema.isNullable) {
717
868
  return `${target} = parseNullableNumberInt(${input}, ${min}, ${max});`;
@@ -721,8 +872,8 @@ function tsIntFromSchema(schema, intType, _context) {
721
872
  toJsonTemplate(input, target) {
722
873
  return `${target} += \`\${${input}}\``;
723
874
  },
724
- toQueryStringTemplate(input, target, key) {
725
- return `${target}.push(\`${key}=\${${input}}\`)`;
875
+ setSearchParamTemplate(input, target, key) {
876
+ return `${target}.set('${key}', \`\${${input}}\`);`;
726
877
  },
727
878
  content: ""
728
879
  };
@@ -740,6 +891,7 @@ function tsBigIntFromSchema(schema, isUnsigned, _context) {
740
891
  }
741
892
  return mainPart;
742
893
  },
894
+ cloneTemplate: defaultCloneTemplate,
743
895
  fromJsonTemplate(input, target) {
744
896
  if (isUnsigned) {
745
897
  if (schema.isNullable) {
@@ -762,8 +914,8 @@ function tsBigIntFromSchema(schema, isUnsigned, _context) {
762
914
  }
763
915
  return `${target} += \`"\${${input}}"\``;
764
916
  },
765
- toQueryStringTemplate(input, target, key) {
766
- return `${target}.push(\`${key}=\${${input}}\`)`;
917
+ setSearchParamTemplate(input, target, key) {
918
+ return `${target}.set('${key}', \`\${${input}}\`);`;
767
919
  },
768
920
  content: ""
769
921
  };
@@ -780,8 +932,9 @@ function tsRecordFromSchema(schema, context) {
780
932
  discriminatorKey: "",
781
933
  discriminatorValue: "",
782
934
  versionNumber: context.versionNumber,
783
- usedFeatures: context.usedFeatures,
784
- rpcGenerators: context.rpcGenerators
935
+ useRpcTypes: context.useRpcTypes,
936
+ rpcGenerators: context.rpcGenerators,
937
+ features: context.features
785
938
  });
786
939
  const typeName = `Record<string, ${innerType.typeName}>`;
787
940
  const defaultValue = schema.isNullable ? "null" : "{}";
@@ -797,6 +950,22 @@ function tsRecordFromSchema(schema, context) {
797
950
  }
798
951
  return mainPart;
799
952
  },
953
+ cloneTemplate(input, target) {
954
+ const main = `${target} = {};
955
+ for (const [_key, _value] of Object.entries(${input})) {
956
+ let ${target}Value: ${innerType.typeName};
957
+ ${innerType.cloneTemplate("_value", `${target}Value`)}
958
+ ${target}[_key] = ${target}Value;
959
+ }`;
960
+ if (schema.isNullable) {
961
+ return `if (${input} !== null) {
962
+ ${main}
963
+ } else {
964
+ ${target} = null;
965
+ }`;
966
+ }
967
+ return main;
968
+ },
800
969
  fromJsonTemplate(input, target) {
801
970
  return `if (isObject(${input})) {
802
971
  ${target} = {};
@@ -841,7 +1010,7 @@ function tsRecordFromSchema(schema, context) {
841
1010
  ${target} += '}';
842
1011
  `;
843
1012
  },
844
- toQueryStringTemplate(_, __, ___) {
1013
+ setSearchParamTemplate(_, __, ___) {
845
1014
  return `console.warn('[WARNING] Cannot serialize nested objects to query string. Skipping property at ${context.instancePath}.')`;
846
1015
  },
847
1016
  content: innerType.content
@@ -851,19 +1020,29 @@ function tsRecordFromSchema(schema, context) {
851
1020
  function tsRefFromSchema(schema, context) {
852
1021
  const typeName = codegenUtils.pascalCase(validVarName(schema.ref), { normalize: true });
853
1022
  const prefixedTypeName = `${context.typePrefix}${typeName}`;
854
- const defaultValue = schema.isNullable ? "null" : `${prefixedTypeName}.new()`;
1023
+ const defaultValue = schema.isNullable ? "null" : `${prefixedTypeName}New()`;
855
1024
  return {
856
1025
  typeName: schema.isNullable ? `${prefixedTypeName} | null` : prefixedTypeName,
857
1026
  defaultValue,
858
1027
  validationTemplate(input) {
859
1028
  if (schema.isNullable) {
860
- return `($$${prefixedTypeName}.validate(${input}) || ${input} === null)`;
1029
+ return `(${prefixedTypeName}Validate(${input}) || ${input} === null)`;
1030
+ }
1031
+ return `${prefixedTypeName}Validate(${input})`;
1032
+ },
1033
+ cloneTemplate(input, target) {
1034
+ if (schema.isNullable) {
1035
+ return `if (${input} !== null) {
1036
+ ${target} = ${prefixedTypeName}Clone(${input});
1037
+ } else {
1038
+ ${target} = null;
1039
+ }`;
861
1040
  }
862
- return `$$${prefixedTypeName}.validate(${input})`;
1041
+ return `${target} = ${prefixedTypeName}Clone(${input});`;
863
1042
  },
864
1043
  fromJsonTemplate(input, target) {
865
1044
  return `if (isObject(${input})) {
866
- ${target} = $$${prefixedTypeName}.fromJson(${input});
1045
+ ${target} = ${prefixedTypeName}FromJson(${input});
867
1046
  } else {
868
1047
  ${target} = ${defaultValue};
869
1048
  }`;
@@ -871,14 +1050,14 @@ function tsRefFromSchema(schema, context) {
871
1050
  toJsonTemplate(input, target, _key) {
872
1051
  if (schema.isNullable) {
873
1052
  return `if (${input} !== null) {
874
- ${target} += $$${prefixedTypeName}.toJsonString(${input});
1053
+ ${target} += ${prefixedTypeName}ToJsonString(${input});
875
1054
  } else {
876
1055
  ${target} += 'null';
877
1056
  }`;
878
1057
  }
879
- return `${target} += $$${prefixedTypeName}.toJsonString(${input});`;
1058
+ return `${target} += ${prefixedTypeName}ToJsonString(${input});`;
880
1059
  },
881
- toQueryStringTemplate(_, __, ___) {
1060
+ setSearchParamTemplate(_, __, ___) {
882
1061
  return `console.warn('[WARNING] Nested objects cannot be serialized to query string. Ignoring property at ${context.instancePath}.');`;
883
1062
  },
884
1063
  content: ""
@@ -907,9 +1086,9 @@ function httpRpcFromDefinition(def, context) {
907
1086
  const key = getRpcKey(context);
908
1087
  const params = def.params ? `${context.typePrefix}${codegenUtils.pascalCase(validVarName(def.params))}` : void 0;
909
1088
  const response = def.response ? `${context.typePrefix}${codegenUtils.pascalCase(validVarName(def.response), { normalize: true })}` : void 0;
910
- const serializerMethod = def.method === "get" ? "toUrlQueryString" : "toJsonString";
1089
+ const serializerMethod = def.method === "get" ? "ToUrlSearchParamsString" : "ToJsonString";
911
1090
  if (def.isEventStream) {
912
- context.usedFeatures.sse = true;
1091
+ context.useRpcTypes.sse = true;
913
1092
  return `${getJsDocComment({
914
1093
  description: def.description,
915
1094
  isDeprecated: def.isDeprecated
@@ -922,9 +1101,9 @@ function httpRpcFromDefinition(def, context) {
922
1101
  headers: this._headers,
923
1102
  onError: this._onError,
924
1103
  ${params ? "params: params," : ""}
925
- responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
926
- responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
927
- serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"},
1104
+ responseFromJson: ${response ? `${response}FromJson` : "() => {}"},
1105
+ responseFromString: ${response ? `${response}FromJsonString` : "() => {}"},
1106
+ serializer: ${params ? `${params}${serializerMethod}` : "() => {}"},
928
1107
  clientVersion: "${context.versionNumber}",
929
1108
  },
930
1109
  options,
@@ -942,16 +1121,16 @@ function httpRpcFromDefinition(def, context) {
942
1121
  headers: this._headers,
943
1122
  onError: this._onError,
944
1123
  ${params ? "params: params," : ""}
945
- responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
946
- responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
947
- serializer: ${params ? `$$${params}.${serializerMethod}` : "() => {}"},
1124
+ responseFromJson: ${response ? `${response}FromJson` : "() => {}"},
1125
+ responseFromString: ${response ? `${response}FromJsonString` : "() => {}"},
1126
+ serializer: ${params ? `${params}${serializerMethod}` : "() => {}"},
948
1127
  clientVersion: "${context.versionNumber}",
949
1128
  options: options ?? this._options,
950
1129
  });
951
1130
  }`;
952
1131
  }
953
1132
  function wsRpcFromDefinition(def, context) {
954
- context.usedFeatures.ws = true;
1133
+ context.useRpcTypes.ws = true;
955
1134
  const key = getRpcKey(context);
956
1135
  const params = def.params ? `${context.typePrefix}${codegenUtils.pascalCase(validVarName(def.params))}` : void 0;
957
1136
  const response = def.response ? `${context.typePrefix}${codegenUtils.pascalCase(validVarName(def.response), { normalize: true })}` : void 0;
@@ -959,19 +1138,7 @@ function wsRpcFromDefinition(def, context) {
959
1138
  description: def.description,
960
1139
  isDeprecated: def.isDeprecated
961
1140
  })} async ${key}(options: WsOptions<${response ?? "undefined"}> = {}): Promise<WsController<${params ?? "undefined"},${response ?? "undefined"}>> {
962
- return arriWsRequest<${params ?? "undefined"}, ${response ?? "undefined"}>({
963
- url: \`\${this._baseUrl}${def.path}\`,
964
- headers: this._headers,
965
- responseFromJson: ${response ? `$$${response}.fromJson` : "() => {}"},
966
- responseFromString: ${response ? `$$${response}.fromJsonString` : "() => {}"},
967
- serializer: ${params ? `$$${params}.toJsonString` : "() => {}"},
968
- onOpen: options.onOpen,
969
- onClose: options.onClose,
970
- onError: options.onError,
971
- onConnectionError: options.onConnectionError,
972
- onMessage: options.onMessage,
973
- clientVersion: "${context.versionNumber}",
974
- });
1141
+ throw new Error('not implemented');
975
1142
  }`;
976
1143
  }
977
1144
  function getRpcKey(context) {
@@ -994,8 +1161,9 @@ function tsServiceFromDefinition(def, context) {
994
1161
  instancePath: `${context.instancePath}.${key2}`,
995
1162
  schemaPath: `${context.instancePath}.${key2}`,
996
1163
  versionNumber: context.versionNumber,
997
- usedFeatures: context.usedFeatures,
998
- rpcGenerators: context.rpcGenerators
1164
+ useRpcTypes: context.useRpcTypes,
1165
+ rpcGenerators: context.rpcGenerators,
1166
+ features: context.features
999
1167
  });
1000
1168
  if (subService.content) {
1001
1169
  subServices.push(subService);
@@ -1013,8 +1181,9 @@ function tsServiceFromDefinition(def, context) {
1013
1181
  discriminatorKey: "",
1014
1182
  discriminatorValue: "",
1015
1183
  versionNumber: context.versionNumber,
1016
- usedFeatures: context.usedFeatures,
1017
- rpcGenerators: context.rpcGenerators
1184
+ useRpcTypes: context.useRpcTypes,
1185
+ rpcGenerators: context.rpcGenerators,
1186
+ features: context.features
1018
1187
  });
1019
1188
  if (rpc) {
1020
1189
  rpcParts.push(rpc);
@@ -1109,11 +1278,16 @@ async function createTypescriptClient(def, options) {
1109
1278
  instancePath: "",
1110
1279
  schemaPath: "",
1111
1280
  versionNumber: def.info?.version ?? "",
1112
- usedFeatures: {
1281
+ useRpcTypes: {
1113
1282
  sse: false,
1114
1283
  ws: false
1115
1284
  },
1116
- rpcGenerators: options.rpcGenerators ?? {}
1285
+ rpcGenerators: options.rpcGenerators ?? {},
1286
+ features: {
1287
+ validateFn: options.features?.validateFn ?? false,
1288
+ cloneFn: options.features?.cloneFn ?? false,
1289
+ validatorObj: options.features?.validatorObj ?? true
1290
+ }
1117
1291
  };
1118
1292
  const serviceDefinitions = codegenUtils.unflattenProcedures(
1119
1293
  def.procedures,
@@ -1132,13 +1306,37 @@ async function createTypescriptClient(def, options) {
1132
1306
  discriminatorKey: "",
1133
1307
  discriminatorValue: "",
1134
1308
  versionNumber: context.versionNumber,
1135
- usedFeatures: context.usedFeatures,
1136
- rpcGenerators: context.rpcGenerators
1309
+ useRpcTypes: context.useRpcTypes,
1310
+ rpcGenerators: context.rpcGenerators,
1311
+ features: context.features
1137
1312
  });
1138
1313
  if (result2.content) {
1139
1314
  types.push(result2.content);
1140
1315
  }
1141
1316
  }
1317
+ let validatorStr = "";
1318
+ if (context.features.validatorObj) {
1319
+ const modelFeatures = [];
1320
+ const enumFeatures = [];
1321
+ modelFeatures.push("new");
1322
+ enumFeatures.push("new");
1323
+ if (context.features.validateFn) {
1324
+ modelFeatures.push("validate");
1325
+ enumFeatures.push("validate");
1326
+ }
1327
+ if (context.features.cloneFn) {
1328
+ modelFeatures.push("clone");
1329
+ }
1330
+ modelFeatures.push("fromJson");
1331
+ modelFeatures.push("fromJsonString");
1332
+ modelFeatures.push("toJsonString");
1333
+ modelFeatures.push("toUrlSearchParams");
1334
+ modelFeatures.push("toUrlSearchParamsString");
1335
+ enumFeatures.push("values");
1336
+ enumFeatures.push("fromSerialValue");
1337
+ validatorStr = `export type ${context.clientName}Validator<T> = ArriModelValidator<T, ${modelFeatures.map((feat) => `'${feat}'`).join(" | ")}>;
1338
+ export type ExampleClientEnumValidator<T> = ArriEnumValidator<T, ${enumFeatures.map((feat) => `'${feat}'`).join(" | ")}>;`;
1339
+ }
1142
1340
  const imports = `// This file was autogenerated by @arrirpc/codegen-ts. Do not modify directly.
1143
1341
  // For more information visit https://github.com/modiimedia/arri
1144
1342
 
@@ -1149,9 +1347,8 @@ import {
1149
1347
  ArriModelValidator,
1150
1348
  type ArriRequestOptions,
1151
1349
  arriRequest,
1152
- ${context.usedFeatures.sse ? "arriSseRequest," : ""}
1153
- ${context.usedFeatures.ws ? "arriWsRequest," : ""}
1154
- ${context.usedFeatures.sse ? "type EventSourceController," : ""}
1350
+ ${context.useRpcTypes.sse ? "arriSseRequest," : ""}
1351
+ ${context.useRpcTypes.sse ? "type EventSourceController," : ""}
1155
1352
  INT8_MAX,
1156
1353
  INT8_MIN,
1157
1354
  INT16_MAX,
@@ -1163,7 +1360,6 @@ import {
1163
1360
  isObject,
1164
1361
  parseString,
1165
1362
  parseBoolean,
1166
- parseNumber,
1167
1363
  parseTimestamp,
1168
1364
  parseNumberInt,
1169
1365
  parseNumberBigInt,
@@ -1177,7 +1373,7 @@ import {
1177
1373
  parseNullableNumberBigInt,
1178
1374
  parseNullableNumberUnsignedBigInt,
1179
1375
  serializeString,
1180
- ${context.usedFeatures.sse ? "type SseOptions," : ""}
1376
+ ${context.useRpcTypes.sse ? "type SseOptions," : ""}
1181
1377
  UINT8_MAX,
1182
1378
  UINT16_MAX,
1183
1379
  UINT32_MAX,
@@ -1185,11 +1381,12 @@ import {
1185
1381
  type Fetch,
1186
1382
  type $Fetch,
1187
1383
  createFetch,
1188
- ${context.usedFeatures.ws ? "type WsController," : ""}
1189
- ${context.usedFeatures.ws ? "type WsOptions," : ""}
1384
+ ${context.useRpcTypes.ws ? "type WsController," : ""}
1385
+ ${context.useRpcTypes.ws ? "type WsOptions," : ""}
1190
1386
  } from "@arrirpc/client";
1191
1387
 
1192
- type HeaderMap = Record<string, string | undefined>;`;
1388
+ type HeaderMap = Record<string, string | undefined>;
1389
+ ${validatorStr}`;
1193
1390
  if (!mainService.content) {
1194
1391
  const result2 = `${imports}
1195
1392
 
@@ -1260,6 +1457,7 @@ function tsTypeFromSchema(schema, context) {
1260
1457
  }
1261
1458
 
1262
1459
  exports.createTypescriptClient = createTypescriptClient;
1460
+ exports.defaultCloneTemplate = defaultCloneTemplate;
1263
1461
  exports.defineRpcGenerator = defineRpcGenerator;
1264
1462
  exports.getJsDocComment = getJsDocComment;
1265
1463
  exports.getRpcKey = getRpcKey;