@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/README.md +50 -11
- package/dist/index.js +664 -486
- package/dist/index.js.map +4 -4
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/emitters/zod.d.ts +3 -3
- package/dist/lib/emitters/zod.d.ts.map +1 -1
- package/dist/lib/generate.d.ts +2 -2
- package/dist/lib/generate.d.ts.map +1 -1
- package/dist/lib/generator.d.ts.map +1 -1
- package/dist/lib/readme/prop.emitter.d.ts +3 -2
- package/dist/lib/readme/prop.emitter.d.ts.map +1 -1
- package/dist/lib/sdk.d.ts +3 -2
- package/dist/lib/sdk.d.ts.map +1 -1
- package/dist/lib/security.d.ts +4 -0
- package/dist/lib/security.d.ts.map +1 -0
- package/dist/lib/typescript-snippet.d.ts.map +1 -1
- package/package.json +4 -4
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
|
|
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
|
-
...
|
|
425
|
-
|
|
426
|
-
|
|
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
|
-
*
|
|
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
|
|
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
|
-
{
|
|
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
|
|
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
|
-
{
|
|
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
|
|
630
|
-
isEmpty
|
|
631
|
-
isRef
|
|
632
|
-
parseRef
|
|
633
|
-
pascalcase
|
|
312
|
+
followRef,
|
|
313
|
+
isEmpty,
|
|
314
|
+
isRef,
|
|
315
|
+
parseRef,
|
|
316
|
+
pascalcase,
|
|
634
317
|
resolveRef
|
|
635
318
|
} from "@sdk-it/core";
|
|
636
|
-
import { isPrimitiveSchema
|
|
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
|
|
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
|
|
380
|
+
return appendOptional("any", required);
|
|
698
381
|
}
|
|
699
382
|
}
|
|
700
383
|
#ref($ref, required) {
|
|
701
|
-
const schemaName =
|
|
702
|
-
const schema =
|
|
703
|
-
if (
|
|
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.${
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
739
|
-
const
|
|
740
|
-
|
|
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
|
|
648
|
+
* Handle a `string` schema with possible format keywords (JSON Schema).
|
|
744
649
|
*/
|
|
745
|
-
string(schema
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
703
|
+
case "binary":
|
|
704
|
+
base = "z.custom<Blob>()";
|
|
759
705
|
break;
|
|
760
706
|
default:
|
|
761
|
-
|
|
707
|
+
break;
|
|
762
708
|
}
|
|
763
|
-
return
|
|
709
|
+
return base;
|
|
764
710
|
}
|
|
765
711
|
/**
|
|
766
|
-
* Handle number/integer
|
|
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(
|
|
769
|
-
|
|
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(
|
|
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.
|
|
788
|
-
return this.
|
|
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.
|
|
791
|
-
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
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
|
-
|
|
804
|
-
return appendOptional2(`${tsType} | null`, required);
|
|
774
|
+
return this.normal(realTypes[0], schema, required, true);
|
|
805
775
|
}
|
|
806
|
-
const
|
|
807
|
-
return
|
|
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(
|
|
813
|
-
return isRequired ?
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
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
|
|
2139
|
-
|
|
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
|
-
|
|
2145
|
-
|
|
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
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
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
|
|
2571
|
-
sections
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
}
|
|
2578
|
-
sections.push(
|
|
2579
|
-
|
|
2580
|
-
|
|
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
|
-
|
|
2652
|
-
|
|
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
|
-
|
|
2666
|
-
|
|
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
|
-
|
|
2954
|
+
securitySchemes: spec.components.securitySchemes,
|
|
2777
2955
|
makeImport
|
|
2778
2956
|
}),
|
|
2779
2957
|
...inputs,
|