@sdk-it/typescript 0.46.3 → 0.46.5

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,395 @@ 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, true);
487
+ return appendOptional(`${tsType} | null`, required);
488
+ }
489
+ const typeResults = types.map((t) => this.normal(t, schema, true));
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(
775
+ realTypes[0],
776
+ { ...schema, type: realTypes[0] },
777
+ required,
778
+ true
779
+ );
805
780
  }
806
- const typeResults = types.map((t) => this.normal(t, schema, false));
807
- return appendOptional2(typeResults.join(" | "), required);
781
+ const subSchemas = types.map(
782
+ (t) => this.normal(t, { ...schema, type: t }, true)
783
+ );
784
+ return `z.union([${subSchemas.join(", ")}])${appendOptional2(required)}`;
808
785
  }
809
- return this.normal(types[0], schema, required);
786
+ return this.normal(types[0], schema, required, false);
810
787
  }
811
788
  };
812
- function appendOptional2(type, isRequired) {
813
- return isRequired ? type : `${type} | undefined`;
789
+ function appendOptional2(isRequired) {
790
+ return isRequired ? "" : ".optional()";
791
+ }
792
+ function appendDefault(defaultValue) {
793
+ return defaultValue !== void 0 || typeof defaultValue !== "undefined" ? `.default(${defaultValue})` : "";
814
794
  }
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
795
 
825
796
  // packages/typescript/src/lib/sdk.ts
826
797
  import { camelcase as camelcase3 } from "stringcase";
827
- import { isEmpty as isEmpty3, pascalcase as pascalcase3 } from "@sdk-it/core";
798
+ import {
799
+ isEmpty as isEmpty3,
800
+ pascalcase as pascalcase3
801
+ } from "@sdk-it/core";
828
802
  import {
829
803
  isBinaryContentType,
830
804
  isSseContentType,
@@ -949,6 +923,7 @@ function toEndpoint(groupName, spec, specOperation, operation) {
949
923
  const endpoint = `${typePrefix}${operation.method.toUpperCase()} ${operation.path}`;
950
924
  schemas.push(
951
925
  `"${endpoint}": {
926
+ security: ${JSON.stringify(specOperation.security ?? [])} as readonly Record<string, readonly string[]>[],
952
927
  schema: ${schemaRef}${addTypeParser ? `.${type}` : ""},
953
928
  output:[${outputs.join(",")}],
954
929
  toRequest(input: z.input<typeof ${schemaRef}${addTypeParser ? `.${type}` : ""}>) {
@@ -1754,6 +1729,198 @@ ${l}`));
1754
1729
  return markdown.join("\n\n");
1755
1730
  }
1756
1731
 
1732
+ // packages/typescript/src/lib/security.ts
1733
+ var security_default = (spec) => {
1734
+ const securitySchemes = JSON.stringify(spec.securitySchemes, null, 2);
1735
+ const credentialProperties = Object.entries(spec.securitySchemes).map(([name, scheme]) => {
1736
+ const schema = scheme.type === "mutualTLS" ? "mutualTlsCredentialSchema" : scheme.type === "http" && scheme.scheme?.toLowerCase() === "basic" ? "basicCredentialSchema" : "stringCredentialSchema";
1737
+ return `${JSON.stringify(name)}: ${schema}.optional()`;
1738
+ }).join(",\n");
1739
+ return `import z from 'zod';
1740
+ import type { Interceptor } from './${spec.makeImport("interceptors")}';
1741
+ import type { RequestConfig } from './${spec.makeImport("request")}';
1742
+
1743
+ export type SecurityContext = {
1744
+ scheme: string;
1745
+ scopes: readonly string[];
1746
+ roles: readonly string[];
1747
+ };
1748
+ export type SecurityCredentialValue = string | true | {
1749
+ username: string;
1750
+ password: string;
1751
+ };
1752
+ export type SecurityCredentialProvider<T extends SecurityCredentialValue> = (
1753
+ context: SecurityContext
1754
+ ) => T | Promise<T>;
1755
+ export type SecurityCredential<
1756
+ T extends SecurityCredentialValue = SecurityCredentialValue,
1757
+ > = T | SecurityCredentialProvider<T>;
1758
+
1759
+ const providerSchema = <T extends SecurityCredentialValue>() =>
1760
+ z.custom<SecurityCredentialProvider<T>>((value) => typeof value === 'function');
1761
+ const stringCredentialSchema = z.union([
1762
+ z.string(),
1763
+ providerSchema<string>(),
1764
+ ]);
1765
+ const basicCredentialValueSchema = z.object({
1766
+ username: z.string(),
1767
+ password: z.string(),
1768
+ });
1769
+ const basicCredentialSchema = z.union([
1770
+ basicCredentialValueSchema,
1771
+ providerSchema<{ username: string; password: string }>(),
1772
+ ]);
1773
+ const mutualTlsCredentialSchema = z.union([
1774
+ z.literal(true),
1775
+ providerSchema<true>(),
1776
+ ]);
1777
+ export const credentialsSchema = z.object({${credentialProperties}});
1778
+
1779
+ type RuntimeSecurityScheme =
1780
+ | { type: 'apiKey'; in: 'header' | 'query' | 'cookie'; name: string }
1781
+ | { type: 'http'; scheme: string }
1782
+ | { type: 'oauth2' }
1783
+ | { type: 'openIdConnect' }
1784
+ | { type: 'mutualTLS' };
1785
+ const securitySchemes = ${securitySchemes} as const;
1786
+
1787
+ export function createSecurityInterceptor(
1788
+ requirements: readonly Record<string, readonly string[]>[],
1789
+ credentials: Record<string, SecurityCredential | undefined> | undefined,
1790
+ ): Interceptor {
1791
+ return {
1792
+ async before(config) {
1793
+ if (requirements.length === 0) return config;
1794
+ const secured = requirements.filter(
1795
+ (requirement) => Object.keys(requirement).length > 0,
1796
+ );
1797
+ const selected = secured.find((requirement) =>
1798
+ Object.keys(requirement).every(
1799
+ (name) => credentials?.[name] !== undefined,
1800
+ ),
1801
+ );
1802
+ if (!selected) {
1803
+ if (requirements.some(
1804
+ (requirement) => Object.keys(requirement).length === 0,
1805
+ )) return config;
1806
+ throw new Error(
1807
+ \`Missing credentials for security requirements: \${secured
1808
+ .map((requirement) => Object.keys(requirement).join(' + '))
1809
+ .join(' or ')}\`,
1810
+ );
1811
+ }
1812
+ for (const [name, values] of Object.entries(selected)) {
1813
+ const scheme = securitySchemes[
1814
+ name as keyof typeof securitySchemes
1815
+ ] as RuntimeSecurityScheme | undefined;
1816
+ if (!scheme) {
1817
+ throw new Error(\`Unsupported external security scheme: \${name}\`);
1818
+ }
1819
+ const configured = credentials?.[name];
1820
+ const isOAuth = scheme.type === 'oauth2' || scheme.type === 'openIdConnect';
1821
+ const credential = typeof configured === 'function'
1822
+ ? await configured({
1823
+ scheme: name,
1824
+ scopes: isOAuth ? values : [],
1825
+ roles: isOAuth ? [] : values,
1826
+ })
1827
+ : configured;
1828
+ applyCredential(config, scheme, credential);
1829
+ }
1830
+ return config;
1831
+ },
1832
+ };
1833
+ }
1834
+
1835
+ function applyCredential(
1836
+ config: RequestConfig,
1837
+ scheme: RuntimeSecurityScheme,
1838
+ credential: SecurityCredentialValue | undefined,
1839
+ ) {
1840
+ if (credential === undefined) {
1841
+ throw new Error('Security credential provider returned no credential');
1842
+ }
1843
+ if (scheme.type === 'apiKey') {
1844
+ if (typeof credential !== 'string') {
1845
+ throw new TypeError('API key credentials must be strings');
1846
+ }
1847
+ if (scheme.in === 'header') {
1848
+ if (!config.init.headers.has(scheme.name)) {
1849
+ config.init.headers.set(scheme.name, credential);
1850
+ }
1851
+ } else if (scheme.in === 'query') {
1852
+ if (!config.url.searchParams.has(scheme.name)) {
1853
+ config.url.searchParams.set(scheme.name, credential);
1854
+ }
1855
+ } else if (scheme.in === 'cookie') {
1856
+ if ('document' in globalThis) {
1857
+ throw new Error(
1858
+ \`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'.\`,
1859
+ );
1860
+ }
1861
+ const cookie = \`\${scheme.name}=\${encodeURIComponent(credential)}\`;
1862
+ const existing = config.init.headers.get('Cookie');
1863
+ if (!existing?.split(';').some((part) => part.trim().startsWith(\`\${scheme.name}=\`))) {
1864
+ config.init.headers.set('Cookie', existing ? \`\${existing}; \${cookie}\` : cookie);
1865
+ }
1866
+ } else {
1867
+ throw new TypeError(\`Unsupported apiKey location: \${String(scheme.in)}\`);
1868
+ }
1869
+ return;
1870
+ }
1871
+ if (scheme.type === 'mutualTLS') {
1872
+ if (credential !== true) {
1873
+ throw new TypeError('mutualTLS credentials must be true when the custom fetch owns the client certificate');
1874
+ }
1875
+ return;
1876
+ }
1877
+ if (scheme.type === 'http' && scheme.scheme.toLowerCase() === 'basic') {
1878
+ if (
1879
+ typeof credential !== 'object' ||
1880
+ !('username' in credential) ||
1881
+ !('password' in credential)
1882
+ ) {
1883
+ throw new TypeError('Basic credentials require username and password');
1884
+ }
1885
+ const bytes = new TextEncoder().encode(
1886
+ \`\${credential.username}:\${credential.password}\`,
1887
+ );
1888
+ let binary = '';
1889
+ for (const byte of bytes) binary += String.fromCharCode(byte);
1890
+ if (!config.init.headers.has('Authorization')) {
1891
+ config.init.headers.set('Authorization', \`Basic \${btoa(binary)}\`);
1892
+ }
1893
+ return;
1894
+ }
1895
+ if (typeof credential !== 'string') {
1896
+ throw new TypeError(\`\${scheme.type} credentials must be strings\`);
1897
+ }
1898
+ const prefix = scheme.type === 'http' ? authorizationScheme(scheme.scheme) : 'Bearer';
1899
+ if (!config.init.headers.has('Authorization')) {
1900
+ config.init.headers.set('Authorization', \`\${prefix} \${credential}\`);
1901
+ }
1902
+ }
1903
+
1904
+ // OpenAPI carries the lowercase registry name, while servers routinely match the
1905
+ // Authorization prefix case-sensitively (\`header.startsWith('Bearer ')\`).
1906
+ const authorizationSchemes: Record<string, string> = {
1907
+ basic: 'Basic',
1908
+ bearer: 'Bearer',
1909
+ digest: 'Digest',
1910
+ hoba: 'HOBA',
1911
+ mutual: 'Mutual',
1912
+ negotiate: 'Negotiate',
1913
+ oauth: 'OAuth',
1914
+ 'scram-sha-1': 'SCRAM-SHA-1',
1915
+ 'scram-sha-256': 'SCRAM-SHA-256',
1916
+ };
1917
+
1918
+ function authorizationScheme(scheme: string) {
1919
+ return authorizationSchemes[scheme.toLowerCase()] ?? scheme;
1920
+ }
1921
+ `;
1922
+ };
1923
+
1757
1924
  // packages/typescript/src/lib/server-urls.ts
1758
1925
  function expandServerUrls(servers) {
1759
1926
  return servers.flatMap((server) => {
@@ -1782,12 +1949,15 @@ function expandServerUrls(servers) {
1782
1949
 
1783
1950
  // packages/typescript/src/lib/typescript-snippet.ts
1784
1951
  import { camelcase as camelcase5, spinalcase as spinalcase3 } from "stringcase";
1785
- import { isEmpty as isEmpty6, pascalcase as pascalcase4, resolveRef as resolveRef4 } from "@sdk-it/core";
1952
+ import {
1953
+ isEmpty as isEmpty6,
1954
+ pascalcase as pascalcase4,
1955
+ resolveRef as resolveRef4
1956
+ } from "@sdk-it/core";
1786
1957
  import "@sdk-it/readme";
1787
1958
  import {
1788
1959
  forEachOperation as forEachOperation5,
1789
- patchParameters,
1790
- securityToOptions
1960
+ patchParameters
1791
1961
  } from "@sdk-it/spec";
1792
1962
 
1793
1963
  // packages/typescript/src/lib/emitters/snippet.ts
@@ -2014,12 +2184,7 @@ var TypeScriptSnippet = class {
2014
2184
  payload = examplePayload;
2015
2185
  } else {
2016
2186
  const requestBody = { type: "object", properties: {} };
2017
- patchParameters(
2018
- this.#spec,
2019
- requestBody,
2020
- operation.parameters,
2021
- operation.security ?? []
2022
- );
2187
+ patchParameters(this.#spec, requestBody, operation.parameters);
2023
2188
  const examplePayload = this.#snippetEmitter.handle(requestBody);
2024
2189
  Object.assign(
2025
2190
  examplePayload,
@@ -2128,21 +2293,48 @@ const result = await ${camelcase5(this.#clientName)}.request('${entry.method.toU
2128
2293
  return content.join("\n");
2129
2294
  }
2130
2295
  #authentication() {
2131
- return securityToOptions(
2132
- this.#spec,
2133
- this.#spec.security ?? [],
2134
- this.#spec.components?.securitySchemes ?? {}
2135
- );
2296
+ const names = /* @__PURE__ */ new Set();
2297
+ for (const requirement of this.#spec.security ?? []) {
2298
+ for (const name of Object.keys(requirement)) names.add(name);
2299
+ }
2300
+ forEachOperation5(this.#spec, (_entry, operation) => {
2301
+ for (const requirement of operation.security ?? []) {
2302
+ for (const name of Object.keys(requirement)) names.add(name);
2303
+ }
2304
+ });
2305
+ return [...names].flatMap((name) => {
2306
+ const scheme = this.#spec.components.securitySchemes[name];
2307
+ return scheme ? [
2308
+ {
2309
+ name,
2310
+ scheme: resolveRef4(
2311
+ this.#spec,
2312
+ scheme
2313
+ )
2314
+ }
2315
+ ] : [];
2316
+ });
2317
+ }
2318
+ #credentialExample(scheme) {
2319
+ if (scheme.type === "mutualTLS") return true;
2320
+ if (scheme.type === "http" && scheme.scheme?.toLowerCase() === "basic") {
2321
+ return { username: "user", password: "password" };
2322
+ }
2323
+ if (scheme.type === "apiKey") return "test_api_key_1234567890abcdef";
2324
+ return "test_access_token_1234567890abcdef";
2136
2325
  }
2137
2326
  client() {
2138
- const options = {
2139
- baseUrl: expandServerUrls(this.#spec.servers ?? [])[0] ?? "http://localhost:3000"
2140
- };
2327
+ const servers = expandServerUrls(this.#spec.servers ?? []);
2328
+ const options = {};
2329
+ if (servers.length !== 1) {
2330
+ options.baseUrl = servers[0] ?? "http://localhost:3000";
2331
+ }
2141
2332
  const authOptions = this.#authentication();
2142
2333
  if (!isEmpty6(authOptions)) {
2143
2334
  const [firstAuth] = authOptions;
2144
- const optionName = firstAuth["x-optionName"] ?? firstAuth.name;
2145
- options[optionName] = firstAuth.example;
2335
+ options.credentials = {
2336
+ [firstAuth.name]: this.#credentialExample(firstAuth.scheme)
2337
+ };
2146
2338
  }
2147
2339
  const client = this.#constructClient(options);
2148
2340
  return `${client.import}
@@ -2192,10 +2384,10 @@ ${client.use}`;
2192
2384
  "| `baseUrl` | `string | (() => string | Promise<string>)` | No | API base URL (default: `" + baseUrl + "`) |"
2193
2385
  );
2194
2386
  }
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} |`);
2387
+ if (hasApiKey) {
2388
+ sections.push(
2389
+ "| `credentials` | `Record<string, SecurityCredential>` | No | Credentials keyed by the exact OpenAPI security scheme name |"
2390
+ );
2199
2391
  }
2200
2392
  return { sections, hasServers, baseUrl, hasApiKey };
2201
2393
  }
@@ -2567,66 +2759,27 @@ ${client.use}`;
2567
2759
  if (isEmpty6(authOptions)) {
2568
2760
  return "";
2569
2761
  }
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("");
2762
+ const only = authOptions.length === 1;
2763
+ const sections = [
2764
+ "## Authentication",
2765
+ "",
2766
+ only ? "The SDK requires authentication to access the API. Configure your client with the required credentials:" : "The SDK supports the following authentication methods:",
2767
+ ""
2768
+ ];
2769
+ for (const { name, scheme } of authOptions) {
2770
+ sections.push(`### ${authenticationHeading(name, scheme, only)}`);
2771
+ sections.push("");
2772
+ if (isBearer(scheme)) {
2591
2773
  sections.push(
2592
2774
  'Pass your bearer token directly - the "Bearer" prefix is automatically added:'
2593
2775
  );
2594
2776
  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
2777
  }
2778
+ const client = this.#constructClient({
2779
+ credentials: { [name]: this.#credentialExample(scheme) }
2780
+ });
2781
+ sections.push(createCodeBlock("typescript", [client.use]));
2782
+ sections.push("");
2630
2783
  }
2631
2784
  return sections.join("\n");
2632
2785
  }
@@ -2648,8 +2801,9 @@ ${client.use}`;
2648
2801
  };
2649
2802
  if (!isEmpty6(authOptions)) {
2650
2803
  const [primaryAuth] = authOptions;
2651
- const authOptionName = primaryAuth["x-optionName"] ?? primaryAuth.name;
2652
- initialClientOptions[authOptionName] = "YOUR_PRODUCTION_TOKEN";
2804
+ initialClientOptions.credentials = {
2805
+ [primaryAuth.name]: this.#credentialExample(primaryAuth.scheme)
2806
+ };
2653
2807
  }
2654
2808
  const initialClientSetup = this.#constructClient(initialClientOptions);
2655
2809
  const configurationUpdateCode = [
@@ -2662,8 +2816,11 @@ ${client.use}`;
2662
2816
  ];
2663
2817
  if (!isEmpty6(authOptions)) {
2664
2818
  const [primaryAuth] = authOptions;
2665
- const authOptionName = primaryAuth["x-optionName"] ?? primaryAuth.name;
2666
- configurationUpdateCode.push(` ${authOptionName}: 'YOUR_STAGING_TOKEN'`);
2819
+ configurationUpdateCode.push(
2820
+ ` credentials: ${JSON.stringify({
2821
+ [primaryAuth.name]: this.#credentialExample(primaryAuth.scheme)
2822
+ })}`
2823
+ );
2667
2824
  }
2668
2825
  configurationUpdateCode.push("});");
2669
2826
  sections.push(createCodeBlock("typescript", configurationUpdateCode));
@@ -2674,6 +2831,21 @@ ${client.use}`;
2674
2831
  return sections.join("\n");
2675
2832
  }
2676
2833
  };
2834
+ function isBearer(scheme) {
2835
+ return scheme.type === "http" && scheme.scheme?.toLowerCase() === "bearer";
2836
+ }
2837
+ function authenticationHeading(name, scheme, only) {
2838
+ if (isBearer(scheme)) {
2839
+ return only ? "Bearer Token" : "Bearer Token Authentication";
2840
+ }
2841
+ if (scheme.type === "apiKey" && scheme.in === "header") {
2842
+ return only ? "API Key (Header)" : "API Key Authentication (Header)";
2843
+ }
2844
+ if (scheme.type === "apiKey" && scheme.in === "query") {
2845
+ return only ? "API Key (Query Parameter)" : "API Key Authentication (Query Parameter)";
2846
+ }
2847
+ return only ? name : `${name} Authentication`;
2848
+ }
2677
2849
  function createCodeBlock(language, content) {
2678
2850
  return ["```" + language, ...content, "```"].join("\n");
2679
2851
  }
@@ -2709,14 +2881,23 @@ function availablePaginationTypes(spec) {
2709
2881
 
2710
2882
  // packages/typescript/src/lib/generate.ts
2711
2883
  async function generate(openapi, settings) {
2884
+ const unresolvedSecuritySchemes = /* @__PURE__ */ new Set();
2712
2885
  const spec = await toIR(
2713
2886
  {
2714
2887
  spec: openapi,
2715
2888
  responses: { flattenErrorResponses: true },
2716
- pagination: settings.pagination
2889
+ pagination: settings.pagination,
2890
+ onDiagnostic: ({ code, message }) => {
2891
+ if (code === "unresolved-security-scheme") {
2892
+ unresolvedSecuritySchemes.add(message);
2893
+ }
2894
+ }
2717
2895
  },
2718
2896
  false
2719
2897
  );
2898
+ if (unresolvedSecuritySchemes.size) {
2899
+ throw new TypeError([...unresolvedSecuritySchemes].join("\n"));
2900
+ }
2720
2901
  const style = Object.assign({}, { name: "github" }, settings.style ?? {});
2721
2902
  const output = settings.mode === "full" ? join2(settings.output, "src") : settings.output;
2722
2903
  settings.useTsExtension ??= true;
@@ -2767,13 +2948,17 @@ import { APIError, APIResponse, type SuccessfulResponse, type RebindSuccessPaylo
2767
2948
  ${template2(dispatcher_default, {})()}`,
2768
2949
  "interceptors.ts": `
2769
2950
  import type { RequestConfig, HeadersInit } from './${makeImport("request")}';
2770
- ${interceptors_default}`
2951
+ ${interceptors_default}`,
2952
+ "security.ts": Object.keys(spec.components.securitySchemes).length ? security_default({
2953
+ makeImport,
2954
+ securitySchemes: spec.components.securitySchemes
2955
+ }) : null
2771
2956
  });
2772
2957
  await settings.writer(output, {
2773
2958
  "client.ts": client_default({
2774
2959
  name: clientName,
2775
2960
  servers: expandServerUrls(spec.servers ?? []),
2776
- options: security(spec),
2961
+ securitySchemes: spec.components.securitySchemes,
2777
2962
  makeImport
2778
2963
  }),
2779
2964
  ...inputs,