@sdk-it/typescript 0.46.2 → 0.46.4

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.js CHANGED
@@ -14,7 +14,6 @@ import {
14
14
  cleanFiles,
15
15
  readWriteMetadata,
16
16
  sanitizeTag as sanitizeTag4,
17
- security,
18
17
  toIR
19
18
  } from "@sdk-it/spec";
20
19
 
@@ -121,319 +120,14 @@ var utils_default = "function coerceContext(context?: any) {\n if (!context) {\
121
120
 
122
121
  // packages/typescript/src/lib/client.ts
123
122
  import { toLitObject } from "@sdk-it/core";
124
-
125
- // packages/typescript/src/lib/emitters/zod.ts
126
- import { followRef, isEmpty, isRef, parseRef, pascalcase } from "@sdk-it/core";
127
- import { isPrimitiveSchema, sanitizeTag } from "@sdk-it/spec";
128
- var ZodEmitter = class {
129
- #generatedRefs = /* @__PURE__ */ new Set();
130
- #spec;
131
- #onRef;
132
- constructor(spec, onRef) {
133
- this.#spec = spec;
134
- this.#onRef = onRef;
135
- }
136
- #object(schema) {
137
- const properties = schema.properties || {};
138
- const propEntries = Object.entries(properties).map(([key, propSchema]) => {
139
- const isRequired = (schema.required ?? []).includes(key);
140
- return `'${key}': ${this.handle(propSchema, isRequired)}`;
141
- });
142
- let additionalProps = "";
143
- if (schema.additionalProperties) {
144
- if (typeof schema.additionalProperties === "object") {
145
- const addPropZod = this.handle(schema.additionalProperties, true);
146
- additionalProps = `.catchall(${addPropZod})`;
147
- } else if (schema.additionalProperties === true) {
148
- additionalProps = `.catchall(z.unknown())`;
149
- }
150
- }
151
- return `z.object({${propEntries.join(", ")}})${additionalProps}`;
152
- }
153
- #array(schema, required = false) {
154
- const { items } = schema;
155
- if (!items) {
156
- return `z.array(z.unknown())${appendOptional(required)}`;
157
- }
158
- if (Array.isArray(items)) {
159
- const tupleItems = items.map((sub) => this.handle(sub, true));
160
- const base = `z.tuple([${tupleItems.join(", ")}])`;
161
- return `${base}${appendOptional(required)}`;
162
- }
163
- const itemsSchema = this.handle(items, true);
164
- return `z.array(${itemsSchema})${this.#suffixes(JSON.stringify(schema.default), required, false)}`;
165
- }
166
- #suffixes = (defaultValue, required, nullable) => {
167
- return `${nullable ? ".nullable()" : ""}${appendOptional(required)}${appendDefault(defaultValue)}`;
168
- };
169
- /**
170
- * Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
171
- * We'll also handle .optional() if needed.
172
- */
173
- normal(type, schema, required = false, nullable = false) {
174
- switch (type) {
175
- case "string": {
176
- const defaultVal = (schema["x-zod-type"] === "date" || schema["x-zod-type"] === "coerce-date") && schema.default ? `new Date(${JSON.stringify(schema.default)})` : JSON.stringify(schema.default);
177
- return `${this.string(schema)}${this.#suffixes(defaultVal, required, nullable)}`;
178
- }
179
- case "number":
180
- case "integer": {
181
- const { base, defaultValue } = this.#number(schema);
182
- return `${base}${this.#suffixes(defaultValue, required, nullable)}`;
183
- }
184
- case "boolean":
185
- return `${schema["x-zod-type"] === "coerce-boolean" ? "z.union([z.boolean(), z.stringbool()])" : "z.boolean()"}${this.#suffixes(schema.default, required, nullable)}`;
186
- case "object":
187
- return `${this.#object(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
188
- // required always
189
- case "array":
190
- return this.#array(schema, required);
191
- case "null":
192
- return `z.null()${appendOptional(required)}`;
193
- default:
194
- return `z.unknown()${appendOptional(required)}`;
195
- }
196
- }
197
- #ref($ref, required) {
198
- const schemaName = pascalcase(sanitizeTag(parseRef($ref).model));
199
- const schema = followRef(this.#spec, $ref);
200
- if (isPrimitiveSchema(schema)) {
201
- const result = this.handle(schema, required);
202
- this.#onRef?.(schemaName, result);
203
- return result;
204
- }
205
- if (this.#generatedRefs.has(schemaName)) {
206
- return schemaName;
207
- }
208
- this.#generatedRefs.add(schemaName);
209
- this.#onRef?.(schemaName, this.handle(schema, required));
210
- return schemaName;
211
- }
212
- #toIntersection(schemas) {
213
- const [left, ...right] = schemas;
214
- if (!right.length) {
215
- return left;
216
- }
217
- return `z.intersection(${left}, ${this.#toIntersection(right)})`;
218
- }
219
- allOf(schemas, required) {
220
- const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
221
- if (allOfSchemas.length === 0) {
222
- return `z.unknown()`;
223
- }
224
- if (allOfSchemas.length === 1) {
225
- return `${allOfSchemas[0]}${appendOptional(required)}`;
226
- }
227
- return `${this.#toIntersection(allOfSchemas)}${appendOptional(required)}`;
228
- }
229
- anyOf(schemas, required) {
230
- const anyOfSchemas = schemas.map((sub) => this.handle(sub, true));
231
- if (anyOfSchemas.length === 1) {
232
- return `${anyOfSchemas[0]}${appendOptional(required)}`;
233
- }
234
- return `z.union([${anyOfSchemas.join(", ")}])${appendOptional(required)}`;
235
- }
236
- oneOf(schemas, required) {
237
- const oneOfSchemas = schemas.map((sub) => this.handle(sub, true));
238
- if (oneOfSchemas.length === 1) {
239
- return `${oneOfSchemas[0]}${appendOptional(required)}`;
240
- }
241
- return `z.xor([${oneOfSchemas.join(", ")}])${appendOptional(required)}`;
242
- }
243
- enum(type, values) {
244
- if (values.length === 1) {
245
- return `z.literal(${values.join(", ")})`;
246
- }
247
- if (values.every((value) => String(value).startsWith('"'))) {
248
- return `z.enum([${values.join(", ")}])`;
249
- }
250
- return `z.literal([${values.join(", ")}])`;
251
- }
252
- /**
253
- * Handle a `string` schema with possible format keywords (JSON Schema).
254
- */
255
- string(schema) {
256
- let base = schema["x-zod-type"] === "coerce-string" ? "z.coerce.string()" : "z.string()";
257
- if (schema.contentEncoding === "binary") {
258
- base = "z.custom<Blob>()";
259
- return base;
260
- }
261
- const coerced = schema["x-zod-type"] === "coerce-string";
262
- const withFormat = (format) => coerced ? `${base}.pipe(${format})` : format;
263
- switch (schema.format) {
264
- case "date-time":
265
- case "datetime":
266
- if (schema["x-zod-type"] === "coerce-date") {
267
- base = "z.coerce.date()";
268
- } else if (schema["x-zod-type"] === "date") {
269
- base = "z.date()";
270
- } else {
271
- base = withFormat("z.iso.datetime({ offset: true })");
272
- }
273
- break;
274
- case "date":
275
- base = withFormat("z.iso.date()");
276
- break;
277
- case "time":
278
- base = withFormat(
279
- "z.string().regex(/^([01]\\d|2[0-3]):[0-5]\\d(:[0-5]\\d(\\.\\d+)?)?(Z|[+-]([01]\\d|2[0-3]):[0-5]\\d)?$/)"
280
- );
281
- break;
282
- case "email":
283
- base = withFormat("z.email()");
284
- break;
285
- case "uuid":
286
- base = withFormat("z.guid()");
287
- break;
288
- case "url":
289
- case "uri":
290
- base = withFormat("z.url()");
291
- break;
292
- case "ipv4":
293
- base = withFormat("z.ipv4()");
294
- break;
295
- case "ipv6":
296
- base = withFormat("z.ipv6()");
297
- break;
298
- case "cidrv4":
299
- base = withFormat("z.cidrv4()");
300
- break;
301
- case "cidrv6":
302
- base = withFormat("z.cidrv6()");
303
- break;
304
- case "phone":
305
- base += " /* or add .regex(...) for phone formats */";
306
- break;
307
- case "byte":
308
- case "binary":
309
- base = "z.custom<Blob>()";
310
- break;
311
- default:
312
- break;
313
- }
314
- return base;
315
- }
316
- /**
317
- * Handle number/integer constraints from OpenAPI/JSON Schema.
318
- * In 3.1, exclusiveMinimum/Maximum hold the actual numeric threshold,
319
- * rather than a boolean toggling `minimum`/`maximum`.
320
- */
321
- #number(schema) {
322
- let base = schema["x-zod-type"] === "coerce-number" ? "z.coerce.number()" : "z.number()";
323
- if (schema.type === "integer") {
324
- base += ".int()";
325
- }
326
- if (typeof schema.exclusiveMinimum === "number") {
327
- base += `.gt(${schema.exclusiveMinimum})`;
328
- }
329
- if (typeof schema.exclusiveMaximum === "number") {
330
- base += `.lt(${schema.exclusiveMaximum})`;
331
- }
332
- if (typeof schema.minimum === "number") {
333
- base += `.min(${schema.minimum})`;
334
- }
335
- if (typeof schema.maximum === "number") {
336
- base += `.max(${schema.maximum})`;
337
- }
338
- if (typeof schema.multipleOf === "number") {
339
- base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
340
- }
341
- return { base, defaultValue: schema.default };
342
- }
343
- handle(schema, required) {
344
- if (isRef(schema)) {
345
- return `${this.#ref(schema.$ref, true)}${appendOptional(required)}`;
346
- }
347
- if (schema.not && isEmpty(schema.not)) {
348
- return `z.never()${appendOptional(required)}`;
349
- }
350
- if (schema.allOf && Array.isArray(schema.allOf)) {
351
- return this.allOf(schema.allOf ?? [], required);
352
- }
353
- if (schema.anyOf && Array.isArray(schema.anyOf)) {
354
- return this.anyOf(schema.anyOf ?? [], required);
355
- }
356
- if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length) {
357
- return this.oneOf(schema.oneOf ?? [], required);
358
- }
359
- if (schema.const !== void 0) {
360
- return `z.literal(${JSON.stringify(schema.const)})${this.#suffixes(JSON.stringify(schema.default), required, false)}`;
361
- }
362
- if (schema.enum && Array.isArray(schema.enum)) {
363
- const enumVals = schema.enum.map((val) => JSON.stringify(val));
364
- const defaultValue = enumVals.includes(JSON.stringify(schema.default)) ? JSON.stringify(schema.default) : void 0;
365
- return `${this.enum(schema.type, enumVals)}${this.#suffixes(defaultValue, required, false)}`;
366
- }
367
- const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
368
- if (!types.length) {
369
- return `z.unknown()${appendOptional(required)}`;
370
- }
371
- if ("nullable" in schema && schema.nullable) {
372
- types.push("null");
373
- } else if (schema.default === null) {
374
- types.push("null");
375
- }
376
- if (types.length > 1) {
377
- const realTypes = types.filter((t) => t !== "null");
378
- if (realTypes.length === 1 && types.includes("null")) {
379
- return this.normal(realTypes[0], schema, required, true);
380
- }
381
- const subSchemas = types.map((t) => this.normal(t, schema, false));
382
- return `z.union([${subSchemas.join(", ")}])${appendOptional(required)}`;
383
- }
384
- return this.normal(types[0], schema, required, false);
385
- }
386
- };
387
- function appendOptional(isRequired) {
388
- return isRequired ? "" : ".optional()";
389
- }
390
- function appendDefault(defaultValue) {
391
- return defaultValue !== void 0 || typeof defaultValue !== "undefined" ? `.default(${defaultValue})` : "";
392
- }
393
- function toZod(schema, required) {
394
- const emitter = new ZodEmitter({});
395
- const schemaStr = emitter.handle(schema, required ?? false);
396
- if (schema["x-prefix"]) {
397
- const prefix = schema["x-prefix"];
398
- if (required === false) {
399
- return schemaStr + `.transform((val) => (val ? \`${prefix}\${val}\` : undefined))`;
400
- } else {
401
- return schemaStr + `.transform((val) => \`${prefix}\${val}\`)`;
402
- }
403
- }
404
- return schemaStr;
405
- }
406
-
407
- // packages/typescript/src/lib/client.ts
408
123
  var client_default = (spec) => {
409
124
  const callableString = `z.custom<() => string | Promise<string>>((value) => typeof value === 'function')`;
410
- const baseUrlSchema = `z.union([z.string(),${callableString},])${spec.servers.length ? ".default(servers[0])" : ""}`;
411
- const defaultHeaders = `{${spec.options.filter((value) => value.in === "header").map(
412
- (value) => `'${value.name}': options['${value["x-optionName"] ?? value.name}']`
413
- ).join(",\n")}}`;
414
- const defaultInputs = `{${spec.options.filter((value) => value.in === "input").map(
415
- (value) => `'${value.name}': options['${value["x-optionName"] ?? value.name}']`
416
- ).join(",\n")}}`;
417
- const globalOptions = Object.fromEntries(
418
- spec.options.map((value) => [
419
- `'${value["x-optionName"] ?? value.name}'`,
420
- { schema: toZod(value.schema, value.required) }
421
- ])
422
- );
125
+ const baseUrlSchema = `z.union([z.string(),${callableString},])${spec.servers.length === 1 ? ".default(servers[0])" : ""}`;
126
+ const securitySchemeNames = Object.keys(spec.securitySchemes);
423
127
  const specOptions = {
424
- ...globalOptions,
425
- ...globalOptions["'token'"] ? {
426
- "'token'": {
427
- schema: `z.union([z.string(),${callableString},]).optional()
428
- .transform(async (token, ctx) => {
429
- if (!token) return undefined;
430
- const value = typeof token === 'function' ? await token() : token;
431
- if (typeof value !== 'string') {
432
- ctx.addIssue({ code: 'custom', message: 'token must resolve to a string' });
433
- return z.NEVER;
434
- }
435
- return \`Bearer \${value}\`;
436
- }).describe('Bearer token for authentication. Can be a string or a function that returns a string.')`
128
+ ...securitySchemeNames.length ? {
129
+ credentials: {
130
+ schema: `credentialsSchema.optional().describe('Credentials keyed by OpenAPI security scheme name.')`
437
131
  }
438
132
  } : {},
439
133
  fetch: {
@@ -467,7 +161,14 @@ import {
467
161
  createHeadersInterceptor,
468
162
  } from './http/${spec.makeImport("interceptors")}';
469
163
 
470
- import { type ParseError, parseInput } from './http/${spec.makeImport("parser")}';
164
+ import { type ParseError, parseInput } from './http/${spec.makeImport("parser")}';${securitySchemeNames.length ? `
165
+ import { credentialsSchema, createSecurityInterceptor } from './http/${spec.makeImport("security")}';
166
+ export type {
167
+ SecurityContext,
168
+ SecurityCredential,
169
+ SecurityCredentialProvider,
170
+ SecurityCredentialValue,
171
+ } from './http/${spec.makeImport("security")}';` : ""}
471
172
 
472
173
  ${spec.servers.length ? `export const servers = ${JSON.stringify(spec.servers, null, 2)} as const` : ""}
473
174
  const optionsSchema = z.object(${toLitObject(specOptions, (x) => x.schema)});
@@ -508,15 +209,7 @@ export class ${spec.name} {
508
209
 
509
210
  async defaultHeaders() {
510
211
  const options = await optionsSchema.parseAsync(this.options);
511
- return {
512
- ...${defaultHeaders},
513
- ...options.headers,
514
- };
515
- }
516
-
517
- async defaultInputs() {
518
- const options = await optionsSchema.parseAsync(this.options);
519
- return ${defaultInputs}
212
+ return { ...options.headers };
520
213
  }
521
214
 
522
215
  setOptions(options: Partial<${spec.name}Options>) {
@@ -530,7 +223,7 @@ export class ${spec.name} {
530
223
 
531
224
  /**
532
225
  * Sends a validated request using the client's configuration and returns the parsed response.
533
- * Merges the client's default inputs and headers before sending.
226
+ * Applies the client's default headers before sending.
534
227
  * Throws \`APIError\` on non-ok responses.
535
228
  *
536
229
  * @example
@@ -546,20 +239,15 @@ export async function request<const E extends keyof typeof schemas>(
546
239
  ): Promise<Awaited<ReturnType<(typeof schemas)[E]['dispatch']>>> {
547
240
  const route = schemas[endpoint];
548
241
  const options = await optionsSchema.parseAsync(client.options);
549
- const withDefaultInputs = Object.assign(
550
- {},
551
- ${defaultInputs},
552
- input,
553
- );
554
- const parsedInput = options.skipValidation ? withDefaultInputs : parseInput(route.schema, withDefaultInputs);
242
+ const parsedInput = options.skipValidation ? input : parseInput(route.schema, input);
555
243
  const result = await route.dispatch(parsedInput as never, {
556
244
  fetch: options.fetch,
557
245
  interceptors: [
558
246
  createHeadersInterceptor(
559
- { ...${defaultHeaders}, ...options.headers },
247
+ { ...options.headers },
560
248
  requestOptions?.headers ?? {},
561
249
  ),
562
- createBaseUrlInterceptor(options.baseUrl),
250
+ ${securitySchemeNames.length ? " createSecurityInterceptor(route.security, options.credentials),\n" : ""} createBaseUrlInterceptor(options.baseUrl),
563
251
  ],
564
252
  signal: requestOptions?.signal,
565
253
  });
@@ -588,18 +276,13 @@ export async function prepare<const E extends keyof typeof schemas>(
588
276
  }> {
589
277
  const route = schemas[endpoint];
590
278
  const options = await optionsSchema.parseAsync(client.options);
591
- const withDefaultInputs = Object.assign(
592
- {},
593
- ${defaultInputs},
594
- input,
595
- );
596
- const parsedInput = options.skipValidation ? withDefaultInputs : parseInput(route.schema, withDefaultInputs);
279
+ const parsedInput = options.skipValidation ? input : parseInput(route.schema, input);
597
280
  const interceptors = [
598
281
  createHeadersInterceptor(
599
- { ...${defaultHeaders}, ...options.headers },
282
+ { ...options.headers },
600
283
  requestOptions?.headers ?? {},
601
284
  ),
602
- createBaseUrlInterceptor(options.baseUrl),
285
+ ${securitySchemeNames.length ? " createSecurityInterceptor(route.security, options.credentials),\n" : ""} createBaseUrlInterceptor(options.baseUrl),
603
286
  ];
604
287
 
605
288
  let config = route.toRequest(parsedInput as never);
@@ -626,14 +309,14 @@ export async function prepare<const E extends keyof typeof schemas>(
626
309
 
627
310
  // packages/typescript/src/lib/emitters/interface.ts
628
311
  import {
629
- followRef as followRef2,
630
- isEmpty as isEmpty2,
631
- isRef as isRef2,
632
- parseRef as parseRef2,
633
- pascalcase as pascalcase2,
312
+ followRef,
313
+ isEmpty,
314
+ isRef,
315
+ parseRef,
316
+ pascalcase,
634
317
  resolveRef
635
318
  } from "@sdk-it/core";
636
- import { isPrimitiveSchema as isPrimitiveSchema2, sanitizeTag as sanitizeTag2 } from "@sdk-it/spec";
319
+ import { isPrimitiveSchema, sanitizeTag } from "@sdk-it/spec";
637
320
  var TypeScriptEmitter = class {
638
321
  #spec;
639
322
  constructor(spec) {
@@ -685,7 +368,7 @@ var TypeScriptEmitter = class {
685
368
  case "integer":
686
369
  return this.number(schema, required);
687
370
  case "boolean":
688
- return appendOptional2("boolean", required);
371
+ return appendOptional("boolean", required);
689
372
  case "object":
690
373
  return this.object(schema, required);
691
374
  case "array":
@@ -694,16 +377,16 @@ var TypeScriptEmitter = class {
694
377
  return "null";
695
378
  default:
696
379
  console.warn(`Unknown type: ${type}`);
697
- return appendOptional2("any", required);
380
+ return appendOptional("any", required);
698
381
  }
699
382
  }
700
383
  #ref($ref, required) {
701
- const schemaName = pascalcase2(sanitizeTag2(parseRef2($ref).model));
702
- const schema = followRef2(this.#spec, $ref);
703
- if (isPrimitiveSchema2(schema)) {
384
+ const schemaName = pascalcase(sanitizeTag(parseRef($ref).model));
385
+ const schema = followRef(this.#spec, $ref);
386
+ if (isPrimitiveSchema(schema)) {
704
387
  return this.handle(schema, required);
705
388
  }
706
- return `models.${appendOptional2(schemaName, required)}`;
389
+ return `models.${appendOptional(schemaName, required)}`;
707
390
  }
708
391
  allOf(schemas) {
709
392
  const allOfTypes = schemas.map((sub) => this.handle(sub, true));
@@ -727,104 +410,388 @@ var TypeScriptEmitter = class {
727
410
  seen.add(part);
728
411
  oneOfTypes.push(part);
729
412
  }
730
- return appendOptional2(
731
- oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
732
- required
733
- );
413
+ return appendOptional(
414
+ oneOfTypes.length > 1 ? `${oneOfTypes.join(" | ")}` : oneOfTypes[0],
415
+ required
416
+ );
417
+ }
418
+ anyOf(schemas, required) {
419
+ return this.oneOf(schemas, required);
420
+ }
421
+ enum(values, required) {
422
+ const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
423
+ return appendOptional(enumValues, required);
424
+ }
425
+ /**
426
+ * Handle string type with formats
427
+ */
428
+ string(schema, required) {
429
+ let type;
430
+ if (schema.contentEncoding === "binary") {
431
+ return appendOptional("Blob", required);
432
+ }
433
+ switch (schema.format) {
434
+ case "date-time":
435
+ case "datetime":
436
+ case "date":
437
+ type = "string";
438
+ break;
439
+ case "binary":
440
+ case "byte":
441
+ type = "Blob";
442
+ break;
443
+ default:
444
+ type = "string";
445
+ }
446
+ return appendOptional(type, required);
447
+ }
448
+ /**
449
+ * Handle number/integer types with formats
450
+ */
451
+ number(_schema, required) {
452
+ return appendOptional("number", required);
453
+ }
454
+ handle(schema, required) {
455
+ if (isRef(schema)) {
456
+ return this.#ref(schema.$ref, required);
457
+ }
458
+ if (schema.not && isEmpty(schema.not)) {
459
+ return appendOptional("never", required);
460
+ }
461
+ if (schema.allOf && Array.isArray(schema.allOf)) {
462
+ return this.allOf(schema.allOf);
463
+ }
464
+ if (schema.anyOf && Array.isArray(schema.anyOf)) {
465
+ return this.anyOf(schema.anyOf, required);
466
+ }
467
+ if (schema.oneOf && Array.isArray(schema.oneOf)) {
468
+ return this.oneOf(schema.oneOf, required);
469
+ }
470
+ if (schema.enum && Array.isArray(schema.enum)) {
471
+ return this.enum(schema.enum, required);
472
+ }
473
+ if (schema.const) {
474
+ return this.enum([schema.const], true);
475
+ }
476
+ const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
477
+ if (!types.length) {
478
+ if ("properties" in schema) {
479
+ return this.object(schema, required);
480
+ }
481
+ return appendOptional("any", required);
482
+ }
483
+ if (types.length > 1) {
484
+ const realTypes = types.filter((t) => t !== "null");
485
+ if (realTypes.length === 1 && types.includes("null")) {
486
+ const tsType = this.normal(realTypes[0], schema, false);
487
+ return appendOptional(`${tsType} | null`, required);
488
+ }
489
+ const typeResults = types.map((t) => this.normal(t, schema, false));
490
+ return appendOptional(typeResults.join(" | "), required);
491
+ }
492
+ return this.normal(types[0], schema, required);
493
+ }
494
+ };
495
+ function appendOptional(type, isRequired) {
496
+ return isRequired ? type : `${type} | undefined`;
497
+ }
498
+
499
+ // packages/typescript/src/lib/generator.ts
500
+ import { merge, template } from "lodash-es";
501
+ import { join } from "node:path";
502
+ import { camelcase as camelcase4, spinalcase as spinalcase2 } from "stringcase";
503
+ import {
504
+ followRef as followRef3,
505
+ isEmpty as isEmpty4,
506
+ isRef as isRef3,
507
+ resolveRef as resolveRef2,
508
+ sortArray
509
+ } from "@sdk-it/core";
510
+ import {
511
+ forEachOperation as forEachOperation3
512
+ } from "@sdk-it/spec";
513
+
514
+ // packages/typescript/src/lib/emitters/zod.ts
515
+ import {
516
+ followRef as followRef2,
517
+ isEmpty as isEmpty2,
518
+ isRef as isRef2,
519
+ parseRef as parseRef2,
520
+ pascalcase as pascalcase2
521
+ } from "@sdk-it/core";
522
+ import { isPrimitiveSchema as isPrimitiveSchema2, sanitizeTag as sanitizeTag2 } from "@sdk-it/spec";
523
+ var ZodEmitter = class {
524
+ #generatedRefs = /* @__PURE__ */ new Set();
525
+ #spec;
526
+ #onRef;
527
+ constructor(spec, onRef) {
528
+ this.#spec = spec;
529
+ this.#onRef = onRef;
530
+ }
531
+ #object(schema) {
532
+ const properties = schema.properties || {};
533
+ const propEntries = Object.entries(properties).map(([key, propSchema]) => {
534
+ const isRequired = (schema.required ?? []).includes(key);
535
+ return `'${key}': ${this.handle(propSchema, isRequired)}`;
536
+ });
537
+ let additionalProps = "";
538
+ if (schema.additionalProperties) {
539
+ if (typeof schema.additionalProperties === "object") {
540
+ const addPropZod = this.handle(schema.additionalProperties, true);
541
+ additionalProps = `.catchall(${addPropZod})`;
542
+ } else if (schema.additionalProperties === true) {
543
+ additionalProps = `.catchall(z.unknown())`;
544
+ }
545
+ }
546
+ return `z.object({${propEntries.join(", ")}})${additionalProps}`;
547
+ }
548
+ #array(schema, required = false) {
549
+ const { items } = schema;
550
+ if (!items) {
551
+ return `z.array(z.unknown())${appendOptional2(required)}`;
552
+ }
553
+ if (Array.isArray(items)) {
554
+ const tupleItems = items.map((sub) => this.handle(sub, true));
555
+ const base = `z.tuple([${tupleItems.join(", ")}])`;
556
+ return `${base}${appendOptional2(required)}`;
557
+ }
558
+ const itemsSchema = this.handle(items, true);
559
+ return `z.array(${itemsSchema})${this.#suffixes(JSON.stringify(schema.default), required, false)}`;
560
+ }
561
+ #suffixes = (defaultValue, required, nullable) => {
562
+ return `${nullable ? ".nullable()" : ""}${appendOptional2(required)}${appendDefault(defaultValue)}`;
563
+ };
564
+ /**
565
+ * Convert a basic type (string | number | boolean | object | array, etc.) to Zod.
566
+ * We'll also handle .optional() if needed.
567
+ */
568
+ normal(type, schema, required = false, nullable = false) {
569
+ switch (type) {
570
+ case "string": {
571
+ const defaultVal = (schema["x-zod-type"] === "date" || schema["x-zod-type"] === "coerce-date") && schema.default ? `new Date(${JSON.stringify(schema.default)})` : JSON.stringify(schema.default);
572
+ return `${this.string(schema)}${this.#suffixes(defaultVal, required, nullable)}`;
573
+ }
574
+ case "number":
575
+ case "integer": {
576
+ const { base, defaultValue } = this.#number(schema);
577
+ return `${base}${this.#suffixes(defaultValue, required, nullable)}`;
578
+ }
579
+ case "boolean":
580
+ return `${schema["x-zod-type"] === "coerce-boolean" ? "z.union([z.boolean(), z.stringbool()])" : "z.boolean()"}${this.#suffixes(schema.default, required, nullable)}`;
581
+ case "object":
582
+ return `${this.#object(schema)}${this.#suffixes(JSON.stringify(schema.default), required, nullable)}`;
583
+ // required always
584
+ case "array":
585
+ return this.#array(schema, required);
586
+ case "null":
587
+ return `z.null()${appendOptional2(required)}`;
588
+ default:
589
+ return `z.unknown()${appendOptional2(required)}`;
590
+ }
591
+ }
592
+ #ref($ref, required) {
593
+ const schemaName = pascalcase2(sanitizeTag2(parseRef2($ref).model));
594
+ const schema = followRef2(this.#spec, $ref);
595
+ if (isPrimitiveSchema2(schema)) {
596
+ const result = this.handle(schema, required);
597
+ this.#onRef?.(schemaName, result);
598
+ return result;
599
+ }
600
+ if (this.#generatedRefs.has(schemaName)) {
601
+ return schemaName;
602
+ }
603
+ this.#generatedRefs.add(schemaName);
604
+ this.#onRef?.(schemaName, this.handle(schema, required));
605
+ return schemaName;
606
+ }
607
+ #toIntersection(schemas) {
608
+ const [left, ...right] = schemas;
609
+ if (!right.length) {
610
+ return left;
611
+ }
612
+ return `z.intersection(${left}, ${this.#toIntersection(right)})`;
613
+ }
614
+ allOf(schemas, required) {
615
+ const allOfSchemas = schemas.map((sub) => this.handle(sub, true));
616
+ if (allOfSchemas.length === 0) {
617
+ return `z.unknown()`;
618
+ }
619
+ if (allOfSchemas.length === 1) {
620
+ return `${allOfSchemas[0]}${appendOptional2(required)}`;
621
+ }
622
+ return `${this.#toIntersection(allOfSchemas)}${appendOptional2(required)}`;
734
623
  }
735
624
  anyOf(schemas, required) {
736
- return this.oneOf(schemas, required);
625
+ const anyOfSchemas = schemas.map((sub) => this.handle(sub, true));
626
+ if (anyOfSchemas.length === 1) {
627
+ return `${anyOfSchemas[0]}${appendOptional2(required)}`;
628
+ }
629
+ return `z.union([${anyOfSchemas.join(", ")}])${appendOptional2(required)}`;
737
630
  }
738
- enum(values, required) {
739
- const enumValues = values.map((val) => typeof val === "string" ? `'${val}'` : `${val}`).join(" | ");
740
- return appendOptional2(enumValues, required);
631
+ oneOf(schemas, required) {
632
+ const oneOfSchemas = schemas.map((sub) => this.handle(sub, true));
633
+ if (oneOfSchemas.length === 1) {
634
+ return `${oneOfSchemas[0]}${appendOptional2(required)}`;
635
+ }
636
+ return `z.xor([${oneOfSchemas.join(", ")}])${appendOptional2(required)}`;
637
+ }
638
+ enum(type, values) {
639
+ if (values.length === 1) {
640
+ return `z.literal(${values.join(", ")})`;
641
+ }
642
+ if (values.every((value) => String(value).startsWith('"'))) {
643
+ return `z.enum([${values.join(", ")}])`;
644
+ }
645
+ return `z.literal([${values.join(", ")}])`;
741
646
  }
742
647
  /**
743
- * Handle string type with formats
648
+ * Handle a `string` schema with possible format keywords (JSON Schema).
744
649
  */
745
- string(schema, required) {
746
- let type;
650
+ string(schema) {
651
+ let base = schema["x-zod-type"] === "coerce-string" ? "z.coerce.string()" : "z.string()";
747
652
  if (schema.contentEncoding === "binary") {
748
- return appendOptional2("Blob", required);
653
+ base = "z.custom<Blob>()";
654
+ return base;
749
655
  }
656
+ const coerced = schema["x-zod-type"] === "coerce-string";
657
+ const withFormat = (format) => coerced ? `${base}.pipe(${format})` : format;
750
658
  switch (schema.format) {
751
659
  case "date-time":
752
660
  case "datetime":
661
+ if (schema["x-zod-type"] === "coerce-date") {
662
+ base = "z.coerce.date()";
663
+ } else if (schema["x-zod-type"] === "date") {
664
+ base = "z.date()";
665
+ } else {
666
+ base = withFormat("z.iso.datetime({ offset: true })");
667
+ }
668
+ break;
753
669
  case "date":
754
- type = "string";
670
+ base = withFormat("z.iso.date()");
671
+ break;
672
+ case "time":
673
+ base = withFormat(
674
+ "z.string().regex(/^([01]\\d|2[0-3]):[0-5]\\d(:[0-5]\\d(\\.\\d+)?)?(Z|[+-]([01]\\d|2[0-3]):[0-5]\\d)?$/)"
675
+ );
676
+ break;
677
+ case "email":
678
+ base = withFormat("z.email()");
679
+ break;
680
+ case "uuid":
681
+ base = withFormat("z.guid()");
682
+ break;
683
+ case "url":
684
+ case "uri":
685
+ base = withFormat("z.url()");
686
+ break;
687
+ case "ipv4":
688
+ base = withFormat("z.ipv4()");
689
+ break;
690
+ case "ipv6":
691
+ base = withFormat("z.ipv6()");
692
+ break;
693
+ case "cidrv4":
694
+ base = withFormat("z.cidrv4()");
695
+ break;
696
+ case "cidrv6":
697
+ base = withFormat("z.cidrv6()");
698
+ break;
699
+ case "phone":
700
+ base += " /* or add .regex(...) for phone formats */";
755
701
  break;
756
- case "binary":
757
702
  case "byte":
758
- type = "Blob";
703
+ case "binary":
704
+ base = "z.custom<Blob>()";
759
705
  break;
760
706
  default:
761
- type = "string";
707
+ break;
762
708
  }
763
- return appendOptional2(type, required);
709
+ return base;
764
710
  }
765
711
  /**
766
- * Handle number/integer types with formats
712
+ * Handle number/integer constraints from OpenAPI/JSON Schema.
713
+ * In 3.1, exclusiveMinimum/Maximum hold the actual numeric threshold,
714
+ * rather than a boolean toggling `minimum`/`maximum`.
767
715
  */
768
- number(_schema, required) {
769
- return appendOptional2("number", required);
716
+ #number(schema) {
717
+ let base = schema["x-zod-type"] === "coerce-number" ? "z.coerce.number()" : "z.number()";
718
+ if (schema.type === "integer") {
719
+ base += ".int()";
720
+ }
721
+ if (typeof schema.exclusiveMinimum === "number") {
722
+ base += `.gt(${schema.exclusiveMinimum})`;
723
+ }
724
+ if (typeof schema.exclusiveMaximum === "number") {
725
+ base += `.lt(${schema.exclusiveMaximum})`;
726
+ }
727
+ if (typeof schema.minimum === "number") {
728
+ base += `.min(${schema.minimum})`;
729
+ }
730
+ if (typeof schema.maximum === "number") {
731
+ base += `.max(${schema.maximum})`;
732
+ }
733
+ if (typeof schema.multipleOf === "number") {
734
+ base += `.refine((val) => Number.isInteger(val / ${schema.multipleOf}), "Must be a multiple of ${schema.multipleOf}")`;
735
+ }
736
+ return { base, defaultValue: schema.default };
770
737
  }
771
738
  handle(schema, required) {
772
739
  if (isRef2(schema)) {
773
- return this.#ref(schema.$ref, required);
740
+ return `${this.#ref(schema.$ref, true)}${appendOptional2(required)}`;
774
741
  }
775
742
  if (schema.not && isEmpty2(schema.not)) {
776
- return appendOptional2("never", required);
743
+ return `z.never()${appendOptional2(required)}`;
777
744
  }
778
745
  if (schema.allOf && Array.isArray(schema.allOf)) {
779
- return this.allOf(schema.allOf);
746
+ return this.allOf(schema.allOf ?? [], required);
780
747
  }
781
748
  if (schema.anyOf && Array.isArray(schema.anyOf)) {
782
- return this.anyOf(schema.anyOf, required);
749
+ return this.anyOf(schema.anyOf ?? [], required);
783
750
  }
784
- if (schema.oneOf && Array.isArray(schema.oneOf)) {
785
- return this.oneOf(schema.oneOf, required);
751
+ if (schema.oneOf && Array.isArray(schema.oneOf) && schema.oneOf.length) {
752
+ return this.oneOf(schema.oneOf ?? [], required);
786
753
  }
787
- if (schema.enum && Array.isArray(schema.enum)) {
788
- return this.enum(schema.enum, required);
754
+ if (schema.const !== void 0) {
755
+ return `z.literal(${JSON.stringify(schema.const)})${this.#suffixes(JSON.stringify(schema.default), required, false)}`;
789
756
  }
790
- if (schema.const) {
791
- return this.enum([schema.const], true);
757
+ if (schema.enum && Array.isArray(schema.enum)) {
758
+ const enumVals = schema.enum.map((val) => JSON.stringify(val));
759
+ const defaultValue = enumVals.includes(JSON.stringify(schema.default)) ? JSON.stringify(schema.default) : void 0;
760
+ return `${this.enum(schema.type, enumVals)}${this.#suffixes(defaultValue, required, false)}`;
792
761
  }
793
762
  const types = Array.isArray(schema.type) ? schema.type : schema.type ? [schema.type] : [];
794
763
  if (!types.length) {
795
- if ("properties" in schema) {
796
- return this.object(schema, required);
797
- }
798
- return appendOptional2("any", required);
764
+ return `z.unknown()${appendOptional2(required)}`;
765
+ }
766
+ if ("nullable" in schema && schema.nullable) {
767
+ types.push("null");
768
+ } else if (schema.default === null) {
769
+ types.push("null");
799
770
  }
800
771
  if (types.length > 1) {
801
772
  const realTypes = types.filter((t) => t !== "null");
802
773
  if (realTypes.length === 1 && types.includes("null")) {
803
- const tsType = this.normal(realTypes[0], schema, false);
804
- return appendOptional2(`${tsType} | null`, required);
774
+ return this.normal(realTypes[0], schema, required, true);
805
775
  }
806
- const typeResults = types.map((t) => this.normal(t, schema, false));
807
- return appendOptional2(typeResults.join(" | "), required);
776
+ const subSchemas = types.map((t) => this.normal(t, schema, false));
777
+ return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
808
778
  }
809
- return this.normal(types[0], schema, required);
779
+ return this.normal(types[0], schema, required, false);
810
780
  }
811
781
  };
812
- function appendOptional2(type, isRequired) {
813
- return isRequired ? type : `${type} | undefined`;
782
+ function appendOptional2(isRequired) {
783
+ return isRequired ? "" : ".optional()";
784
+ }
785
+ function appendDefault(defaultValue) {
786
+ return defaultValue !== void 0 || typeof defaultValue !== "undefined" ? `.default(${defaultValue})` : "";
814
787
  }
815
-
816
- // packages/typescript/src/lib/generator.ts
817
- import { merge, template } from "lodash-es";
818
- import { join } from "node:path";
819
- import { camelcase as camelcase4, spinalcase as spinalcase2 } from "stringcase";
820
- import { followRef as followRef3, isEmpty as isEmpty4, isRef as isRef3, resolveRef as resolveRef2, sortArray } from "@sdk-it/core";
821
- import {
822
- forEachOperation as forEachOperation3
823
- } from "@sdk-it/spec";
824
788
 
825
789
  // packages/typescript/src/lib/sdk.ts
826
790
  import { camelcase as camelcase3 } from "stringcase";
827
- import { isEmpty as isEmpty3, pascalcase as pascalcase3 } from "@sdk-it/core";
791
+ import {
792
+ isEmpty as isEmpty3,
793
+ pascalcase as pascalcase3
794
+ } from "@sdk-it/core";
828
795
  import {
829
796
  isBinaryContentType,
830
797
  isSseContentType,
@@ -949,6 +916,7 @@ function toEndpoint(groupName, spec, specOperation, operation) {
949
916
  const endpoint = `${typePrefix}${operation.method.toUpperCase()} ${operation.path}`;
950
917
  schemas.push(
951
918
  `"${endpoint}": {
919
+ security: ${JSON.stringify(specOperation.security ?? [])} as readonly Record<string, readonly string[]>[],
952
920
  schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
953
921
  output:[${outputs.join(",")}],
954
922
  toRequest(input: z.input<typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}>) {
@@ -1754,6 +1722,198 @@ ${l}`));
1754
1722
  return markdown.join("\n\n");
1755
1723
  }
1756
1724
 
1725
+ // packages/typescript/src/lib/security.ts
1726
+ var security_default = (spec) => {
1727
+ const securitySchemes = JSON.stringify(spec.securitySchemes, null, 2);
1728
+ const credentialProperties = Object.entries(spec.securitySchemes).map(([name, scheme]) => {
1729
+ const schema = scheme.type === "mutualTLS" ? "mutualTlsCredentialSchema" : scheme.type === "http" && scheme.scheme?.toLowerCase() === "basic" ? "basicCredentialSchema" : "stringCredentialSchema";
1730
+ return `${JSON.stringify(name)}: ${schema}.optional()`;
1731
+ }).join(",\n");
1732
+ return `import z from 'zod';
1733
+ import type { Interceptor } from './${spec.makeImport("interceptors")}';
1734
+ import type { RequestConfig } from './${spec.makeImport("request")}';
1735
+
1736
+ export type SecurityContext = {
1737
+ scheme: string;
1738
+ scopes: readonly string[];
1739
+ roles: readonly string[];
1740
+ };
1741
+ export type SecurityCredentialValue = string | true | {
1742
+ username: string;
1743
+ password: string;
1744
+ };
1745
+ export type SecurityCredentialProvider<T extends SecurityCredentialValue> = (
1746
+ context: SecurityContext
1747
+ ) => T | Promise<T>;
1748
+ export type SecurityCredential<
1749
+ T extends SecurityCredentialValue = SecurityCredentialValue,
1750
+ > = T | SecurityCredentialProvider<T>;
1751
+
1752
+ const providerSchema = <T extends SecurityCredentialValue>() =>
1753
+ z.custom<SecurityCredentialProvider<T>>((value) => typeof value === 'function');
1754
+ const stringCredentialSchema = z.union([
1755
+ z.string(),
1756
+ providerSchema<string>(),
1757
+ ]);
1758
+ const basicCredentialValueSchema = z.object({
1759
+ username: z.string(),
1760
+ password: z.string(),
1761
+ });
1762
+ const basicCredentialSchema = z.union([
1763
+ basicCredentialValueSchema,
1764
+ providerSchema<{ username: string; password: string }>(),
1765
+ ]);
1766
+ const mutualTlsCredentialSchema = z.union([
1767
+ z.literal(true),
1768
+ providerSchema<true>(),
1769
+ ]);
1770
+ export const credentialsSchema = z.object({${credentialProperties}});
1771
+
1772
+ type RuntimeSecurityScheme =
1773
+ | { type: 'apiKey'; in: 'header' | 'query' | 'cookie'; name: string }
1774
+ | { type: 'http'; scheme: string }
1775
+ | { type: 'oauth2' }
1776
+ | { type: 'openIdConnect' }
1777
+ | { type: 'mutualTLS' };
1778
+ const securitySchemes = ${securitySchemes} as const;
1779
+
1780
+ export function createSecurityInterceptor(
1781
+ requirements: readonly Record<string, readonly string[]>[],
1782
+ credentials: Record<string, SecurityCredential | undefined> | undefined,
1783
+ ): Interceptor {
1784
+ return {
1785
+ async before(config) {
1786
+ if (requirements.length === 0) return config;
1787
+ const secured = requirements.filter(
1788
+ (requirement) => Object.keys(requirement).length > 0,
1789
+ );
1790
+ const selected = secured.find((requirement) =>
1791
+ Object.keys(requirement).every(
1792
+ (name) => credentials?.[name] !== undefined,
1793
+ ),
1794
+ );
1795
+ if (!selected) {
1796
+ if (requirements.some(
1797
+ (requirement) => Object.keys(requirement).length === 0,
1798
+ )) return config;
1799
+ throw new Error(
1800
+ \`Missing credentials for security requirements: \${secured
1801
+ .map((requirement) => Object.keys(requirement).join(' + '))
1802
+ .join(' or ')}\`,
1803
+ );
1804
+ }
1805
+ for (const [name, values] of Object.entries(selected)) {
1806
+ const scheme = securitySchemes[
1807
+ name as keyof typeof securitySchemes
1808
+ ] as RuntimeSecurityScheme | undefined;
1809
+ if (!scheme) {
1810
+ throw new Error(\`Unsupported external security scheme: \${name}\`);
1811
+ }
1812
+ const configured = credentials?.[name];
1813
+ const isOAuth = scheme.type === 'oauth2' || scheme.type === 'openIdConnect';
1814
+ const credential = typeof configured === 'function'
1815
+ ? await configured({
1816
+ scheme: name,
1817
+ scopes: isOAuth ? values : [],
1818
+ roles: isOAuth ? [] : values,
1819
+ })
1820
+ : configured;
1821
+ applyCredential(config, scheme, credential);
1822
+ }
1823
+ return config;
1824
+ },
1825
+ };
1826
+ }
1827
+
1828
+ function applyCredential(
1829
+ config: RequestConfig,
1830
+ scheme: RuntimeSecurityScheme,
1831
+ credential: SecurityCredentialValue | undefined,
1832
+ ) {
1833
+ if (credential === undefined) {
1834
+ throw new Error('Security credential provider returned no credential');
1835
+ }
1836
+ if (scheme.type === 'apiKey') {
1837
+ if (typeof credential !== 'string') {
1838
+ throw new TypeError('API key credentials must be strings');
1839
+ }
1840
+ if (scheme.in === 'header') {
1841
+ if (!config.init.headers.has(scheme.name)) {
1842
+ config.init.headers.set(scheme.name, credential);
1843
+ }
1844
+ } else if (scheme.in === 'query') {
1845
+ if (!config.url.searchParams.has(scheme.name)) {
1846
+ config.url.searchParams.set(scheme.name, credential);
1847
+ }
1848
+ } else if (scheme.in === 'cookie') {
1849
+ if (typeof document !== 'undefined') {
1850
+ throw new Error(
1851
+ \`Cannot send the \${scheme.name} cookie credential: browsers forbid setting the Cookie header. Let the browser send the cookie and pass a fetch that sets credentials: 'include'.\`,
1852
+ );
1853
+ }
1854
+ const cookie = \`\${scheme.name}=\${encodeURIComponent(credential)}\`;
1855
+ const existing = config.init.headers.get('Cookie');
1856
+ if (!existing?.split(';').some((part) => part.trim().startsWith(\`\${scheme.name}=\`))) {
1857
+ config.init.headers.set('Cookie', existing ? \`\${existing}; \${cookie}\` : cookie);
1858
+ }
1859
+ } else {
1860
+ throw new TypeError(\`Unsupported apiKey location: \${String(scheme.in)}\`);
1861
+ }
1862
+ return;
1863
+ }
1864
+ if (scheme.type === 'mutualTLS') {
1865
+ if (credential !== true) {
1866
+ throw new TypeError('mutualTLS credentials must be true when the custom fetch owns the client certificate');
1867
+ }
1868
+ return;
1869
+ }
1870
+ if (scheme.type === 'http' && scheme.scheme.toLowerCase() === 'basic') {
1871
+ if (
1872
+ typeof credential !== 'object' ||
1873
+ !('username' in credential) ||
1874
+ !('password' in credential)
1875
+ ) {
1876
+ throw new TypeError('Basic credentials require username and password');
1877
+ }
1878
+ const bytes = new TextEncoder().encode(
1879
+ \`\${credential.username}:\${credential.password}\`,
1880
+ );
1881
+ let binary = '';
1882
+ for (const byte of bytes) binary += String.fromCharCode(byte);
1883
+ if (!config.init.headers.has('Authorization')) {
1884
+ config.init.headers.set('Authorization', \`Basic \${btoa(binary)}\`);
1885
+ }
1886
+ return;
1887
+ }
1888
+ if (typeof credential !== 'string') {
1889
+ throw new TypeError(\`\${scheme.type} credentials must be strings\`);
1890
+ }
1891
+ const prefix = scheme.type === 'http' ? authorizationScheme(scheme.scheme) : 'Bearer';
1892
+ if (!config.init.headers.has('Authorization')) {
1893
+ config.init.headers.set('Authorization', \`\${prefix} \${credential}\`);
1894
+ }
1895
+ }
1896
+
1897
+ // OpenAPI carries the lowercase registry name, while servers routinely match the
1898
+ // Authorization prefix case-sensitively (\`header.startsWith('Bearer ')\`).
1899
+ const authorizationSchemes: Record<string, string> = {
1900
+ basic: 'Basic',
1901
+ bearer: 'Bearer',
1902
+ digest: 'Digest',
1903
+ hoba: 'HOBA',
1904
+ mutual: 'Mutual',
1905
+ negotiate: 'Negotiate',
1906
+ oauth: 'OAuth',
1907
+ 'scram-sha-1': 'SCRAM-SHA-1',
1908
+ 'scram-sha-256': 'SCRAM-SHA-256',
1909
+ };
1910
+
1911
+ function authorizationScheme(scheme: string) {
1912
+ return authorizationSchemes[scheme.toLowerCase()] ?? scheme;
1913
+ }
1914
+ `;
1915
+ };
1916
+
1757
1917
  // packages/typescript/src/lib/server-urls.ts
1758
1918
  function expandServerUrls(servers) {
1759
1919
  return servers.flatMap((server) => {
@@ -1782,12 +1942,15 @@ function expandServerUrls(servers) {
1782
1942
 
1783
1943
  // packages/typescript/src/lib/typescript-snippet.ts
1784
1944
  import { camelcase as camelcase5, spinalcase as spinalcase3 } from "stringcase";
1785
- import { isEmpty as isEmpty6, pascalcase as pascalcase4, resolveRef as resolveRef4 } from "@sdk-it/core";
1945
+ import {
1946
+ isEmpty as isEmpty6,
1947
+ pascalcase as pascalcase4,
1948
+ resolveRef as resolveRef4
1949
+ } from "@sdk-it/core";
1786
1950
  import "@sdk-it/readme";
1787
1951
  import {
1788
1952
  forEachOperation as forEachOperation5,
1789
- patchParameters,
1790
- securityToOptions
1953
+ patchParameters
1791
1954
  } from "@sdk-it/spec";
1792
1955
 
1793
1956
  // packages/typescript/src/lib/emitters/snippet.ts
@@ -2014,12 +2177,7 @@ var TypeScriptSnippet = class {
2014
2177
  payload = examplePayload;
2015
2178
  } else {
2016
2179
  const requestBody = { type: "object", properties: {} };
2017
- patchParameters(
2018
- this.#spec,
2019
- requestBody,
2020
- operation.parameters,
2021
- operation.security ?? []
2022
- );
2180
+ patchParameters(this.#spec, requestBody, operation.parameters);
2023
2181
  const examplePayload = this.#snippetEmitter.handle(requestBody);
2024
2182
  Object.assign(
2025
2183
  examplePayload,
@@ -2128,21 +2286,48 @@ const result = await ${camelcase5(this.#clientName)}.request('${entry.method.toU
2128
2286
  return content.join("\n");
2129
2287
  }
2130
2288
  #authentication() {
2131
- return securityToOptions(
2132
- this.#spec,
2133
- this.#spec.security ?? [],
2134
- this.#spec.components?.securitySchemes ?? {}
2135
- );
2289
+ const names = /* @__PURE__ */ new Set();
2290
+ for (const requirement of this.#spec.security ?? []) {
2291
+ for (const name of Object.keys(requirement)) names.add(name);
2292
+ }
2293
+ forEachOperation5(this.#spec, (_entry, operation) => {
2294
+ for (const requirement of operation.security ?? []) {
2295
+ for (const name of Object.keys(requirement)) names.add(name);
2296
+ }
2297
+ });
2298
+ return [...names].flatMap((name) => {
2299
+ const scheme = this.#spec.components.securitySchemes[name];
2300
+ return scheme ? [
2301
+ {
2302
+ name,
2303
+ scheme: resolveRef4(
2304
+ this.#spec,
2305
+ scheme
2306
+ )
2307
+ }
2308
+ ] : [];
2309
+ });
2310
+ }
2311
+ #credentialExample(scheme) {
2312
+ if (scheme.type === "mutualTLS") return true;
2313
+ if (scheme.type === "http" && scheme.scheme?.toLowerCase() === "basic") {
2314
+ return { username: "user", password: "password" };
2315
+ }
2316
+ if (scheme.type === "apiKey") return "test_api_key_1234567890abcdef";
2317
+ return "test_access_token_1234567890abcdef";
2136
2318
  }
2137
2319
  client() {
2138
- const options = {
2139
- baseUrl: expandServerUrls(this.#spec.servers ?? [])[0] ?? "http://localhost:3000"
2140
- };
2320
+ const servers = expandServerUrls(this.#spec.servers ?? []);
2321
+ const options = {};
2322
+ if (servers.length !== 1) {
2323
+ options.baseUrl = servers[0] ?? "http://localhost:3000";
2324
+ }
2141
2325
  const authOptions = this.#authentication();
2142
2326
  if (!isEmpty6(authOptions)) {
2143
2327
  const [firstAuth] = authOptions;
2144
- const optionName = firstAuth["x-optionName"] ?? firstAuth.name;
2145
- options[optionName] = firstAuth.example;
2328
+ options.credentials = {
2329
+ [firstAuth.name]: this.#credentialExample(firstAuth.scheme)
2330
+ };
2146
2331
  }
2147
2332
  const client = this.#constructClient(options);
2148
2333
  return `${client.import}
@@ -2192,10 +2377,10 @@ ${client.use}`;
2192
2377
  "| `baseUrl` | `string | (() => string | Promise<string>)` | No | API base URL (default: `" + baseUrl + "`) |"
2193
2378
  );
2194
2379
  }
2195
- for (const authOption of authOptions) {
2196
- const optionName = authOption["x-optionName"] ?? authOption.name;
2197
- const description = authOption.in === "header" && authOption.name === "authorization" ? "Bearer token for authentication" : `API key for authentication (${authOption.in}: ${authOption.name})`;
2198
- sections.push(`| \`${optionName}\` | \`string\` | No | ${description} |`);
2380
+ if (hasApiKey) {
2381
+ sections.push(
2382
+ "| `credentials` | `Record<string, SecurityCredential>` | No | Credentials keyed by the exact OpenAPI security scheme name |"
2383
+ );
2199
2384
  }
2200
2385
  return { sections, hasServers, baseUrl, hasApiKey };
2201
2386
  }
@@ -2567,66 +2752,27 @@ ${client.use}`;
2567
2752
  if (isEmpty6(authOptions)) {
2568
2753
  return "";
2569
2754
  }
2570
- const sections = [];
2571
- sections.push("## Authentication");
2572
- sections.push("");
2573
- if (authOptions.length === 1) {
2574
- sections.push(
2575
- "The SDK requires authentication to access the API. Configure your client with the required credentials:"
2576
- );
2577
- } else {
2578
- sections.push("The SDK supports the following authentication methods:");
2579
- }
2580
- sections.push("");
2581
- for (const authOption of authOptions) {
2582
- const optionName = authOption["x-optionName"] ?? authOption.name;
2583
- const isBearer = authOption.in === "header" && authOption.name === "authorization";
2584
- const isApiKey = authOption.in === "header" && authOption.name !== "authorization";
2585
- const isQueryParam = authOption.in === "query";
2586
- const headingLevel = authOptions.length === 1 ? "###" : "###";
2587
- if (isBearer) {
2588
- const authenticationHeading = authOptions.length === 1 ? "Bearer Token" : "Bearer Token Authentication";
2589
- sections.push(`${headingLevel} ${authenticationHeading}`);
2590
- sections.push("");
2755
+ const only = authOptions.length === 1;
2756
+ const sections = [
2757
+ "## Authentication",
2758
+ "",
2759
+ only ? "The SDK requires authentication to access the API. Configure your client with the required credentials:" : "The SDK supports the following authentication methods:",
2760
+ ""
2761
+ ];
2762
+ for (const { name, scheme } of authOptions) {
2763
+ sections.push(`### ${authenticationHeading(name, scheme, only)}`);
2764
+ sections.push("");
2765
+ if (isBearer(scheme)) {
2591
2766
  sections.push(
2592
2767
  'Pass your bearer token directly - the "Bearer" prefix is automatically added:'
2593
2768
  );
2594
2769
  sections.push("");
2595
- const bearerAuthClient = this.#constructClient({
2596
- [optionName]: "test_51234567890abcdef1234567890abcdef"
2597
- });
2598
- sections.push(createCodeBlock("typescript", [bearerAuthClient.use]));
2599
- sections.push("");
2600
- } else if (isApiKey) {
2601
- const apiKeyHeading = authOptions.length === 1 ? "API Key (Header)" : "API Key Authentication (Header)";
2602
- sections.push(`${headingLevel} ${apiKeyHeading}`);
2603
- sections.push("");
2604
- const apiKeyAuthClient = this.#constructClient({
2605
- [optionName]: "test_api_key_1234567890abcdef1234567890abcdef"
2606
- });
2607
- sections.push(createCodeBlock("typescript", [apiKeyAuthClient.use]));
2608
- sections.push("");
2609
- } else if (isQueryParam) {
2610
- const queryParamHeading = authOptions.length === 1 ? "API Key (Query Parameter)" : "API Key Authentication (Query Parameter)";
2611
- sections.push(`${headingLevel} ${queryParamHeading}`);
2612
- sections.push("");
2613
- const queryParamAuthClient = this.#constructClient({
2614
- [optionName]: "test_qp_key_1234567890abcdef1234567890abcdef"
2615
- });
2616
- sections.push(
2617
- createCodeBlock("typescript", [queryParamAuthClient.use])
2618
- );
2619
- sections.push("");
2620
- } else {
2621
- const genericAuthHeading = authOptions.length === 1 ? authOption.name : `${authOption.name} Authentication`;
2622
- sections.push(`${headingLevel} ${genericAuthHeading}`);
2623
- sections.push("");
2624
- const genericAuthClient = this.#constructClient({
2625
- [optionName]: "test_auth_token_1234567890abcdef1234567890abcdef"
2626
- });
2627
- sections.push(createCodeBlock("typescript", [genericAuthClient.use]));
2628
- sections.push("");
2629
2770
  }
2771
+ const client = this.#constructClient({
2772
+ credentials: { [name]: this.#credentialExample(scheme) }
2773
+ });
2774
+ sections.push(createCodeBlock("typescript", [client.use]));
2775
+ sections.push("");
2630
2776
  }
2631
2777
  return sections.join("\n");
2632
2778
  }
@@ -2648,8 +2794,9 @@ ${client.use}`;
2648
2794
  };
2649
2795
  if (!isEmpty6(authOptions)) {
2650
2796
  const [primaryAuth] = authOptions;
2651
- const authOptionName = primaryAuth["x-optionName"] ?? primaryAuth.name;
2652
- initialClientOptions[authOptionName] = "YOUR_PRODUCTION_TOKEN";
2797
+ initialClientOptions.credentials = {
2798
+ [primaryAuth.name]: this.#credentialExample(primaryAuth.scheme)
2799
+ };
2653
2800
  }
2654
2801
  const initialClientSetup = this.#constructClient(initialClientOptions);
2655
2802
  const configurationUpdateCode = [
@@ -2662,8 +2809,11 @@ ${client.use}`;
2662
2809
  ];
2663
2810
  if (!isEmpty6(authOptions)) {
2664
2811
  const [primaryAuth] = authOptions;
2665
- const authOptionName = primaryAuth["x-optionName"] ?? primaryAuth.name;
2666
- configurationUpdateCode.push(` ${authOptionName}: 'YOUR_STAGING_TOKEN'`);
2812
+ configurationUpdateCode.push(
2813
+ ` credentials: ${JSON.stringify({
2814
+ [primaryAuth.name]: this.#credentialExample(primaryAuth.scheme)
2815
+ })}`
2816
+ );
2667
2817
  }
2668
2818
  configurationUpdateCode.push("});");
2669
2819
  sections.push(createCodeBlock("typescript", configurationUpdateCode));
@@ -2674,6 +2824,21 @@ ${client.use}`;
2674
2824
  return sections.join("\n");
2675
2825
  }
2676
2826
  };
2827
+ function isBearer(scheme) {
2828
+ return scheme.type === "http" && scheme.scheme?.toLowerCase() === "bearer";
2829
+ }
2830
+ function authenticationHeading(name, scheme, only) {
2831
+ if (isBearer(scheme)) {
2832
+ return only ? "Bearer Token" : "Bearer Token Authentication";
2833
+ }
2834
+ if (scheme.type === "apiKey" && scheme.in === "header") {
2835
+ return only ? "API Key (Header)" : "API Key Authentication (Header)";
2836
+ }
2837
+ if (scheme.type === "apiKey" && scheme.in === "query") {
2838
+ return only ? "API Key (Query Parameter)" : "API Key Authentication (Query Parameter)";
2839
+ }
2840
+ return only ? name : `${name} Authentication`;
2841
+ }
2677
2842
  function createCodeBlock(language, content) {
2678
2843
  return ["```" + language, ...content, "```"].join("\n");
2679
2844
  }
@@ -2709,14 +2874,23 @@ function availablePaginationTypes(spec) {
2709
2874
 
2710
2875
  // packages/typescript/src/lib/generate.ts
2711
2876
  async function generate(openapi, settings) {
2877
+ const unresolvedSecuritySchemes = /* @__PURE__ */ new Set();
2712
2878
  const spec = await toIR(
2713
2879
  {
2714
2880
  spec: openapi,
2715
2881
  responses: { flattenErrorResponses: true },
2716
- pagination: settings.pagination
2882
+ pagination: settings.pagination,
2883
+ onDiagnostic: ({ code, message }) => {
2884
+ if (code === "unresolved-security-scheme") {
2885
+ unresolvedSecuritySchemes.add(message);
2886
+ }
2887
+ }
2717
2888
  },
2718
2889
  false
2719
2890
  );
2891
+ if (unresolvedSecuritySchemes.size) {
2892
+ throw new TypeError([...unresolvedSecuritySchemes].join("\n"));
2893
+ }
2720
2894
  const style = Object.assign({}, { name: "github" }, settings.style ?? {});
2721
2895
  const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
2722
2896
  settings.useTsExtension ??= true;
@@ -2767,13 +2941,17 @@ import { APIError, APIResponse, type SuccessfulResponse, type RebindSuccessPaylo
2767
2941
  ${template2(dispatcher_default, {})()}`,
2768
2942
  "interceptors.ts": `
2769
2943
  import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
2770
- ${interceptors_default}`
2944
+ ${interceptors_default}`,
2945
+ "security.ts": Object.keys(spec.components.securitySchemes).length ? security_default({
2946
+ makeImport,
2947
+ securitySchemes: spec.components.securitySchemes
2948
+ }) : null
2771
2949
  });
2772
2950
  await settings.writer(output, {
2773
2951
  "client.ts": client_default({
2774
2952
  name: clientName,
2775
2953
  servers: expandServerUrls(spec.servers ?? []),
2776
- options: security(spec),
2954
+ securitySchemes: spec.components.securitySchemes,
2777
2955
  makeImport
2778
2956
  }),
2779
2957
  ...inputs,