@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/README.md +50 -11
- package/dist/index.js +671 -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 +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,395 @@ 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, 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
|
-
|
|
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
|
-
|
|
774
|
+
return this.normal(
|
|
775
|
+
realTypes[0],
|
|
776
|
+
{ ...schema, type: realTypes[0] },
|
|
777
|
+
required,
|
|
778
|
+
true
|
|
779
|
+
);
|
|
805
780
|
}
|
|
806
|
-
const
|
|
807
|
-
|
|
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(
|
|
813
|
-
return isRequired ?
|
|
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 {
|
|
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 {
|
|
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
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
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
|
|
2139
|
-
|
|
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
|
-
|
|
2145
|
-
|
|
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
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
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
|
|
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("");
|
|
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
|
-
|
|
2652
|
-
|
|
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
|
-
|
|
2666
|
-
|
|
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
|
-
|
|
2961
|
+
securitySchemes: spec.components.securitySchemes,
|
|
2777
2962
|
makeImport
|
|
2778
2963
|
}),
|
|
2779
2964
|
...inputs,
|