@vertz/openapi 0.1.1 → 0.1.3

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 CHANGED
@@ -104,15 +104,31 @@ console.log(`${result.written} files written, ${result.skipped} unchanged`);
104
104
 
105
105
  ## Using the Generated SDK
106
106
 
107
+ The generated SDK uses `@vertz/fetch` under the hood. Install it in the project that consumes the SDK:
108
+
109
+ ```bash
110
+ bun add @vertz/fetch
111
+ ```
112
+
107
113
  ```ts
108
114
  import { createClient } from './generated/client';
115
+ import { isOk } from '@vertz/fetch';
109
116
 
110
117
  const api = createClient({ baseURL: 'https://api.example.com' });
111
118
 
112
119
  // Fully typed — params, body, and response types are inferred
113
- const tasks = await api.tasks.list();
114
- const task = await api.tasks.get(taskId);
115
- const created = await api.tasks.create({ title: 'New task' });
120
+ // Returns FetchResponse<T> (Result type) — use isOk/isErr to handle
121
+ const result = await api.tasks.list();
122
+ if (isOk(result)) {
123
+ console.log(result.data); // typed as Task[]
124
+ }
125
+
126
+ // All FetchClient features available: auth strategies, retries, hooks
127
+ const api = createClient({
128
+ baseURL: 'https://api.example.com',
129
+ authStrategies: [{ type: 'bearer', token: 'my-token' }],
130
+ retry: { retries: 3 },
131
+ });
116
132
  ```
117
133
 
118
134
  ## Custom Operation ID Normalization
package/bin/openapi.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env bun
2
- import { runCLI } from '../src/cli';
2
+ import { runCLI } from '../dist/cli.js';
3
3
 
4
4
  const result = await runCLI(process.argv.slice(2));
5
5
 
package/dist/cli.js CHANGED
@@ -4,7 +4,7 @@ import {
4
4
  loadSpec,
5
5
  parseOpenAPI,
6
6
  resolveConfig
7
- } from "./shared/chunk-er3k8t3a.js";
7
+ } from "./shared/chunk-19t7k664.js";
8
8
 
9
9
  // src/cli.ts
10
10
  function parseArgs(args) {
package/dist/index.d.ts CHANGED
@@ -7,6 +7,7 @@ interface ParsedSpec {
7
7
  };
8
8
  resources: ParsedResource[];
9
9
  schemas: ParsedSchema[];
10
+ securitySchemes: ParsedSecurityScheme[];
10
11
  }
11
12
  interface ParsedResource {
12
13
  name: string;
@@ -24,6 +25,11 @@ interface ParsedOperation {
24
25
  response?: ParsedSchema;
25
26
  responseStatus: number;
26
27
  tags: string[];
28
+ security?: OperationSecurity;
29
+ }
30
+ interface OperationSecurity {
31
+ required: boolean;
32
+ schemes: string[];
27
33
  }
28
34
  interface ParsedParameter {
29
35
  name: string;
@@ -34,6 +40,37 @@ interface ParsedSchema {
34
40
  name?: string;
35
41
  jsonSchema: Record<string, unknown>;
36
42
  }
43
+ type ParsedSecurityScheme = {
44
+ type: "bearer";
45
+ name: string;
46
+ description?: string;
47
+ } | {
48
+ type: "basic";
49
+ name: string;
50
+ description?: string;
51
+ } | {
52
+ type: "apiKey";
53
+ name: string;
54
+ in: "header" | "query" | "cookie";
55
+ paramName: string;
56
+ description?: string;
57
+ } | {
58
+ type: "oauth2";
59
+ name: string;
60
+ flows: ParsedOAuthFlows;
61
+ description?: string;
62
+ };
63
+ interface ParsedOAuthFlows {
64
+ authorizationCode?: {
65
+ authorizationUrl: string;
66
+ tokenUrl: string;
67
+ scopes: Record<string, string>;
68
+ };
69
+ clientCredentials?: {
70
+ tokenUrl: string;
71
+ scopes: Record<string, string>;
72
+ };
73
+ }
37
74
  interface OperationContext {
38
75
  /** Raw operationId from the spec */
39
76
  operationId: string;
@@ -117,9 +154,11 @@ declare function sanitizeIdentifier(name: string): string;
117
154
  declare function generateAll(spec: ParsedSpec, options?: GenerateOptions): GeneratedFile[];
118
155
  /**
119
156
  * Generate the main client.ts file.
157
+ * Uses @vertz/fetch FetchClient instead of hand-rolling HTTP methods.
120
158
  */
121
159
  declare function generateClient(resources: ParsedResource[], config: {
122
160
  baseURL?: string;
161
+ securitySchemes?: ParsedSecurityScheme[];
123
162
  }): GeneratedFile;
124
163
  /**
125
164
  * Convert a JSON Schema object to a TypeScript type expression string.
@@ -154,6 +193,7 @@ declare function generateTypes(resources: ParsedResource[], schemas: ParsedSchem
154
193
  declare function parseOpenAPI(spec: Record<string, unknown>): {
155
194
  operations: ParsedOperation[];
156
195
  schemas: ParsedSchema[];
196
+ securitySchemes: ParsedSecurityScheme[];
157
197
  version: "3.0" | "3.1";
158
198
  };
159
199
  interface ResolveOptions {
package/dist/index.js CHANGED
@@ -21,7 +21,7 @@ import {
21
21
  sanitizeIdentifier,
22
22
  sanitizeTypeName,
23
23
  writeIncremental
24
- } from "./shared/chunk-er3k8t3a.js";
24
+ } from "./shared/chunk-19t7k664.js";
25
25
  export {
26
26
  writeIncremental,
27
27
  sanitizeTypeName,
@@ -41,86 +41,102 @@ function groupOperations(operations, strategy, options) {
41
41
  }
42
42
 
43
43
  // src/generators/client-generator.ts
44
+ function camelCase(name) {
45
+ const leadingUpper = name.match(/^[A-Z]+/);
46
+ if (leadingUpper) {
47
+ const prefix = leadingUpper[0];
48
+ if (prefix.length >= name.length)
49
+ return name.toLowerCase();
50
+ if (prefix.length > 1) {
51
+ return prefix.slice(0, -1).toLowerCase() + name.slice(prefix.length - 1);
52
+ }
53
+ }
54
+ return name.charAt(0).toLowerCase() + name.slice(1);
55
+ }
56
+ function generateAuthField(scheme) {
57
+ switch (scheme.type) {
58
+ case "bearer":
59
+ return ` ${camelCase(scheme.name)}?: string | (() => string | Promise<string>);`;
60
+ case "basic":
61
+ return ` ${camelCase(scheme.name)}?: { username: string; password: string };`;
62
+ case "apiKey":
63
+ return ` ${camelCase(scheme.name)}?: string | (() => string | Promise<string>);`;
64
+ case "oauth2":
65
+ return ` ${camelCase(scheme.name)}?: string | (() => string | Promise<string>);`;
66
+ }
67
+ }
68
+ function generateAuthStrategy(scheme) {
69
+ const fieldName = camelCase(scheme.name);
70
+ const lines = [];
71
+ switch (scheme.type) {
72
+ case "bearer":
73
+ lines.push(` ...(options.auth?.${fieldName} ? [{`);
74
+ lines.push(` type: 'bearer' as const,`);
75
+ lines.push(` token: options.auth?.${fieldName},`);
76
+ lines.push(" }] : []),");
77
+ break;
78
+ case "basic":
79
+ lines.push(` ...(options.auth?.${fieldName} ? [{`);
80
+ lines.push(` type: 'basic' as const,`);
81
+ lines.push(` ...options.auth?.${fieldName},`);
82
+ lines.push(" }] : []),");
83
+ break;
84
+ case "apiKey":
85
+ lines.push(` ...(options.auth?.${fieldName} ? [{`);
86
+ lines.push(` type: 'apiKey' as const,`);
87
+ lines.push(` key: options.auth?.${fieldName},`);
88
+ lines.push(` location: '${scheme.in}' as const,`);
89
+ lines.push(` name: '${scheme.paramName}',`);
90
+ lines.push(" }] : []),");
91
+ break;
92
+ case "oauth2":
93
+ lines.push(` ...(options.auth?.${fieldName} ? [{`);
94
+ lines.push(` type: 'bearer' as const,`);
95
+ lines.push(` token: options.auth?.${fieldName},`);
96
+ lines.push(" }] : []),");
97
+ break;
98
+ }
99
+ return lines;
100
+ }
44
101
  function generateClient(resources, config) {
45
102
  const defaultBaseURL = config.baseURL ? `'${config.baseURL.replace(/'/g, "\\'")}'` : "''";
103
+ const schemes = config.securitySchemes ?? [];
104
+ const hasAuth = schemes.length > 0;
46
105
  const lines = [];
47
106
  lines.push("// Generated by @vertz/openapi — do not edit");
48
107
  lines.push("");
108
+ lines.push("import { FetchClient } from '@vertz/fetch';");
109
+ lines.push("import type { FetchClientConfig } from '@vertz/fetch';");
49
110
  for (const r of resources) {
50
111
  lines.push(`import { create${r.name}Resource } from './resources/${r.identifier}';`);
51
112
  }
52
113
  lines.push("");
53
- lines.push("export interface HttpClient {");
54
- lines.push(" get<T>(path: string, options?: { query?: Record<string, unknown> }): Promise<T>;");
55
- lines.push(" post<T>(path: string, body?: unknown, options?: { query?: Record<string, unknown> }): Promise<T>;");
56
- lines.push(" put<T>(path: string, body?: unknown, options?: { query?: Record<string, unknown> }): Promise<T>;");
57
- lines.push(" patch<T>(path: string, body?: unknown, options?: { query?: Record<string, unknown> }): Promise<T>;");
58
- lines.push(" delete<T>(path: string, options?: { query?: Record<string, unknown> }): Promise<T>;");
59
- lines.push("}");
60
- lines.push("");
61
- lines.push("export interface ClientOptions {");
62
- lines.push(" baseURL?: string;");
63
- lines.push(" headers?: Record<string, string>;");
64
- lines.push(" fetch?: typeof globalThis.fetch;");
65
- lines.push("}");
114
+ if (hasAuth) {
115
+ lines.push("export interface ClientAuth {");
116
+ for (const scheme of schemes) {
117
+ lines.push(generateAuthField(scheme));
118
+ }
119
+ lines.push("}");
120
+ lines.push("");
121
+ }
122
+ if (hasAuth) {
123
+ lines.push("export type ClientOptions = FetchClientConfig & { auth?: ClientAuth };");
124
+ } else {
125
+ lines.push("export type ClientOptions = FetchClientConfig;");
126
+ }
66
127
  lines.push("");
67
128
  lines.push("export function createClient(options: ClientOptions = {}) {");
68
- lines.push(` const baseURL = (options.baseURL ?? ${defaultBaseURL}).replace(/\\/$/, '');`);
69
- lines.push(" const customFetch = options.fetch ?? globalThis.fetch.bind(globalThis);");
70
- lines.push(" const defaultHeaders = options.headers ?? {};");
71
- lines.push("");
72
- lines.push(" const client: HttpClient = {");
73
- lines.push(" async get<T>(path: string, opts?: { query?: Record<string, unknown> }): Promise<T> {");
74
- lines.push(" let url = `${baseURL}${path}`;");
75
- lines.push(" if (opts?.query) {");
76
- lines.push(" const params = new URLSearchParams();");
77
- lines.push(" for (const [k, v] of Object.entries(opts.query)) { if (v != null) params.set(k, String(v)); }");
78
- lines.push(" const qs = params.toString();");
79
- lines.push(" if (qs) url += `?${qs}`;");
80
- lines.push(" }");
81
- lines.push(" const res = await customFetch(url, { method: 'GET', headers: defaultHeaders });");
82
- lines.push(" if (!res.ok) throw await ApiError.from(res);");
83
- lines.push(" if (res.status === 204) return undefined as T;");
84
- lines.push(" return res.json();");
85
- lines.push(" },");
86
- lines.push("");
87
- for (const method of ["post", "put", "patch"]) {
88
- lines.push(` async ${method}<T>(path: string, body?: unknown, opts?: { query?: Record<string, unknown> }): Promise<T> {`);
89
- lines.push(` let url = \`\${baseURL}\${path}\`;`);
90
- lines.push(" if (opts?.query) {");
91
- lines.push(" const params = new URLSearchParams();");
92
- lines.push(" for (const [k, v] of Object.entries(opts.query)) { if (v != null) params.set(k, String(v)); }");
93
- lines.push(" const qs = params.toString();");
94
- lines.push(" if (qs) url += `?${qs}`;");
95
- lines.push(" }");
96
- lines.push(` const res = await customFetch(url, {`);
97
- lines.push(` method: '${method.toUpperCase()}',`);
98
- lines.push(" headers: { ...defaultHeaders, 'Content-Type': 'application/json' },");
99
- lines.push(" body: body !== undefined ? JSON.stringify(body) : undefined,");
100
- lines.push(" });");
101
- lines.push(" if (!res.ok) throw await ApiError.from(res);");
102
- lines.push(" if (res.status === 204) return undefined as T;");
103
- lines.push(" return res.json();");
104
- lines.push(" },");
105
- lines.push("");
129
+ lines.push(` const client = new FetchClient({`);
130
+ lines.push(` baseURL: ${defaultBaseURL},`);
131
+ if (hasAuth) {
132
+ lines.push(" authStrategies: [");
133
+ for (const scheme of schemes) {
134
+ lines.push(...generateAuthStrategy(scheme));
135
+ }
136
+ lines.push(" ],");
106
137
  }
107
- lines.push(" async delete<T>(path: string, opts?: { query?: Record<string, unknown> }): Promise<T> {");
108
- lines.push(" let url = `${baseURL}${path}`;");
109
- lines.push(" if (opts?.query) {");
110
- lines.push(" const params = new URLSearchParams();");
111
- lines.push(" for (const [k, v] of Object.entries(opts.query)) { if (v != null) params.set(k, String(v)); }");
112
- lines.push(" const qs = params.toString();");
113
- lines.push(" if (qs) url += `?${qs}`;");
114
- lines.push(" }");
115
- lines.push(" const res = await customFetch(url, {");
116
- lines.push(" method: 'DELETE',");
117
- lines.push(" headers: defaultHeaders,");
118
- lines.push(" });");
119
- lines.push(" if (!res.ok) throw await ApiError.from(res);");
120
- lines.push(" if (res.status === 204) return undefined as T;");
121
- lines.push(" return res.json();");
122
- lines.push(" },");
123
- lines.push(" };");
138
+ lines.push(" ...options,");
139
+ lines.push(" });");
124
140
  lines.push("");
125
141
  lines.push(" return {");
126
142
  for (const r of resources) {
@@ -129,21 +145,6 @@ function generateClient(resources, config) {
129
145
  lines.push(" };");
130
146
  lines.push("}");
131
147
  lines.push("");
132
- lines.push("export class ApiError extends Error {");
133
- lines.push(" override name = 'ApiError';");
134
- lines.push(" public data: unknown;");
135
- lines.push("");
136
- lines.push(" constructor(public status: number, body: string) {");
137
- lines.push(" super(`API error ${status}: ${body}`);");
138
- lines.push(" try { this.data = JSON.parse(body); } catch { this.data = body; }");
139
- lines.push(" }");
140
- lines.push("");
141
- lines.push(" static async from(res: Response): Promise<ApiError> {");
142
- lines.push(" const body = await res.text();");
143
- lines.push(" return new ApiError(res.status, body);");
144
- lines.push(" }");
145
- lines.push("}");
146
- lines.push("");
147
148
  lines.push("export type Client = ReturnType<typeof createClient>;");
148
149
  lines.push("");
149
150
  return { path: "client.ts", content: lines.join(`
@@ -249,6 +250,14 @@ var VALID_IDENTIFIER = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
249
250
  function isValidIdentifier(name) {
250
251
  return VALID_IDENTIFIER.test(name);
251
252
  }
253
+ function toPascalCase(name) {
254
+ const cleaned = name.replace(/[^A-Za-z0-9]+/g, " ").trim();
255
+ if (!cleaned)
256
+ return "_";
257
+ const segments = cleaned.split(/\s+/).filter(Boolean);
258
+ const result = segments.map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
259
+ return /^[0-9]/.test(result) ? `_${result}` : result;
260
+ }
252
261
 
253
262
  // src/generators/resource-generator.ts
254
263
  function generateResources(resources) {
@@ -268,13 +277,13 @@ function generateResources(resources) {
268
277
  function generateResourceFile(resource) {
269
278
  const lines = [];
270
279
  const typeImports = collectTypeImports(resource);
271
- lines.push("import type { HttpClient } from '../client';");
280
+ lines.push("import type { FetchClient, FetchResponse } from '@vertz/fetch';");
272
281
  if (typeImports.size > 0) {
273
282
  const sorted = [...typeImports].sort();
274
283
  lines.push(`import type { ${sorted.join(", ")} } from '../types/${resource.identifier}';`);
275
284
  }
276
285
  lines.push("");
277
- lines.push(`export function create${resource.name}Resource(client: HttpClient) {`);
286
+ lines.push(`export function create${resource.name}Resource(client: FetchClient) {`);
278
287
  lines.push(" return {");
279
288
  validateUniqueMethodNames(resource);
280
289
  for (const op of resource.operations) {
@@ -330,22 +339,22 @@ function buildParams(op) {
330
339
  }
331
340
  function buildReturnType(op) {
332
341
  if (op.responseStatus === 204)
333
- return "Promise<void>";
342
+ return "Promise<FetchResponse<void>>";
334
343
  if (op.response?.name) {
335
344
  const safeName = sanitizeTypeName(op.response.name);
336
345
  if (op.response.jsonSchema.type === "array") {
337
- return `Promise<${safeName}[]>`;
346
+ return `Promise<FetchResponse<${safeName}[]>>`;
338
347
  }
339
- return `Promise<${safeName}>`;
348
+ return `Promise<FetchResponse<${safeName}>>`;
340
349
  }
341
350
  if (op.response?.jsonSchema.type === "array") {
342
- return "Promise<unknown[]>";
351
+ return "Promise<FetchResponse<unknown[]>>";
343
352
  }
344
353
  if (op.response) {
345
- const name = capitalize(op.operationId) + "Response";
346
- return `Promise<${name}>`;
354
+ const name = toPascalCase(op.operationId) + "Response";
355
+ return `Promise<FetchResponse<${name}>>`;
347
356
  }
348
- return "Promise<void>";
357
+ return "Promise<FetchResponse<void>>";
349
358
  }
350
359
  function buildCall(op) {
351
360
  const method = op.method.toLowerCase();
@@ -376,7 +385,7 @@ function collectTypeImports(resource) {
376
385
  if (op.response.name) {
377
386
  types.add(sanitizeTypeName(op.response.name));
378
387
  } else {
379
- types.add(capitalize(op.operationId) + "Response");
388
+ types.add(toPascalCase(op.operationId) + "Response");
380
389
  }
381
390
  }
382
391
  if (op.requestBody) {
@@ -391,13 +400,10 @@ function collectTypeImports(resource) {
391
400
  function deriveInputName(op) {
392
401
  if (op.requestBody?.name)
393
402
  return sanitizeTypeName(op.requestBody.name);
394
- return capitalize(op.operationId) + "Input";
403
+ return toPascalCase(op.operationId) + "Input";
395
404
  }
396
405
  function deriveQueryName(op) {
397
- return capitalize(op.operationId) + "Query";
398
- }
399
- function capitalize(s) {
400
- return s.charAt(0).toUpperCase() + s.slice(1);
406
+ return toPascalCase(op.operationId) + "Query";
401
407
  }
402
408
 
403
409
  // src/generators/json-schema-to-zod.ts
@@ -615,18 +621,19 @@ ${entries.join(`,
615
621
  function deriveResponseSchemaName(op) {
616
622
  if (op.response?.name)
617
623
  return toSchemaVarName(op.response.name);
618
- return toSchemaVarName(op.operationId + "Response");
624
+ return toSchemaVarName(toPascalCase(op.operationId) + "Response");
619
625
  }
620
626
  function deriveInputSchemaName(op) {
621
627
  if (op.requestBody?.name)
622
628
  return toSchemaVarName(op.requestBody.name);
623
- return toSchemaVarName(op.operationId + "Input");
629
+ return toSchemaVarName(toPascalCase(op.operationId) + "Input");
624
630
  }
625
631
  function deriveQuerySchemaName(op) {
626
- return toSchemaVarName(op.operationId + "Query");
632
+ return toSchemaVarName(toPascalCase(op.operationId) + "Query");
627
633
  }
628
634
  function toSchemaVarName(name) {
629
- const camel = name.charAt(0).toLowerCase() + name.slice(1);
635
+ const sanitized = sanitizeTypeName(name);
636
+ const camel = sanitized.charAt(0).toLowerCase() + sanitized.slice(1);
630
637
  return camel + "Schema";
631
638
  }
632
639
 
@@ -687,15 +694,15 @@ function generateResourceTypes(resource, namedSchemas) {
687
694
  function deriveResponseName(op) {
688
695
  if (op.response?.name)
689
696
  return sanitizeTypeName(op.response.name);
690
- return capitalize2(op.operationId) + "Response";
697
+ return toPascalCase(op.operationId) + "Response";
691
698
  }
692
699
  function deriveInputName2(op) {
693
700
  if (op.requestBody?.name)
694
701
  return sanitizeTypeName(op.requestBody.name);
695
- return capitalize2(op.operationId) + "Input";
702
+ return toPascalCase(op.operationId) + "Input";
696
703
  }
697
704
  function deriveQueryName2(op) {
698
- return capitalize2(op.operationId) + "Query";
705
+ return toPascalCase(op.operationId) + "Query";
699
706
  }
700
707
  function generateQueryInterface(name, op, namedSchemas) {
701
708
  const lines = op.queryParams.map((param) => {
@@ -710,9 +717,6 @@ ${lines.join(`
710
717
  }
711
718
  `;
712
719
  }
713
- function capitalize2(s) {
714
- return s.charAt(0).toUpperCase() + s.slice(1);
715
- }
716
720
 
717
721
  // src/generators/index.ts
718
722
  function generateAll(spec, options) {
@@ -721,7 +725,10 @@ function generateAll(spec, options) {
721
725
  const files = [];
722
726
  files.push(...generateTypes(resources, schemas));
723
727
  files.push(...generateResources(resources));
724
- files.push(generateClient(resources, { baseURL: opts.baseURL }));
728
+ files.push(generateClient(resources, {
729
+ baseURL: opts.baseURL,
730
+ securitySchemes: spec.securitySchemes
731
+ }));
725
732
  if (opts.schemas) {
726
733
  files.push(...generateSchemas(resources, schemas));
727
734
  }
@@ -734,10 +741,19 @@ function generateReadme(spec, options) {
734
741
  lines.push("");
735
742
  lines.push(`> Auto-generated from OpenAPI ${spec.version} spec (v${spec.info.version})`);
736
743
  lines.push("");
744
+ lines.push("## Prerequisites");
745
+ lines.push("");
746
+ lines.push("This SDK requires `@vertz/fetch` as a peer dependency:");
747
+ lines.push("");
748
+ lines.push("```bash");
749
+ lines.push("bun add @vertz/fetch");
750
+ lines.push("```");
751
+ lines.push("");
737
752
  lines.push("## Usage");
738
753
  lines.push("");
739
754
  lines.push("```typescript");
740
755
  lines.push("import { createClient } from './client';");
756
+ lines.push("import { isOk } from '@vertz/fetch';");
741
757
  lines.push("");
742
758
  lines.push(`const api = createClient(${options.baseURL ? `{ baseURL: '${options.baseURL}' }` : ""});`);
743
759
  lines.push("```");
@@ -1212,6 +1228,64 @@ function collectComponentSchemas(spec, version) {
1212
1228
  name
1213
1229
  }));
1214
1230
  }
1231
+ function extractSecuritySchemes(spec) {
1232
+ const components = isRecord2(spec.components) ? spec.components : undefined;
1233
+ const schemes = components && isRecord2(components.securitySchemes) ? components.securitySchemes : undefined;
1234
+ if (!schemes)
1235
+ return [];
1236
+ const result = [];
1237
+ for (const [name, scheme] of Object.entries(schemes)) {
1238
+ if (!isRecord2(scheme))
1239
+ continue;
1240
+ const description = typeof scheme.description === "string" ? scheme.description : undefined;
1241
+ if (scheme.type === "http") {
1242
+ if (scheme.scheme === "bearer") {
1243
+ result.push({ type: "bearer", name, description });
1244
+ } else if (scheme.scheme === "basic") {
1245
+ result.push({ type: "basic", name, description });
1246
+ }
1247
+ } else if (scheme.type === "apiKey") {
1248
+ const location = scheme.in;
1249
+ const paramName = typeof scheme.name === "string" ? scheme.name : name;
1250
+ result.push({ type: "apiKey", name, in: location, paramName, description });
1251
+ } else if (scheme.type === "oauth2" && isRecord2(scheme.flows)) {
1252
+ const flows = {};
1253
+ const rawFlows = scheme.flows;
1254
+ if (isRecord2(rawFlows.authorizationCode)) {
1255
+ const ac = rawFlows.authorizationCode;
1256
+ flows.authorizationCode = {
1257
+ authorizationUrl: String(ac.authorizationUrl ?? ""),
1258
+ tokenUrl: String(ac.tokenUrl ?? ""),
1259
+ scopes: isRecord2(ac.scopes) ? ac.scopes : {}
1260
+ };
1261
+ }
1262
+ if (isRecord2(rawFlows.clientCredentials)) {
1263
+ const cc = rawFlows.clientCredentials;
1264
+ flows.clientCredentials = {
1265
+ tokenUrl: String(cc.tokenUrl ?? ""),
1266
+ scopes: isRecord2(cc.scopes) ? cc.scopes : {}
1267
+ };
1268
+ }
1269
+ result.push({ type: "oauth2", name, flows, description });
1270
+ }
1271
+ }
1272
+ return result;
1273
+ }
1274
+ function extractOperationSecurity(operation, globalSecurity) {
1275
+ const security = Array.isArray(operation.security) ? operation.security : globalSecurity;
1276
+ if (security.length === 0 && !Array.isArray(operation.security))
1277
+ return;
1278
+ const schemes = [];
1279
+ for (const requirement of security) {
1280
+ if (isRecord2(requirement)) {
1281
+ schemes.push(...Object.keys(requirement));
1282
+ }
1283
+ }
1284
+ return {
1285
+ required: schemes.length > 0,
1286
+ schemes
1287
+ };
1288
+ }
1215
1289
  function parseOpenAPI(spec) {
1216
1290
  const version = getVersion(spec);
1217
1291
  if (!isRecord2(spec.info)) {
@@ -1220,6 +1294,7 @@ function parseOpenAPI(spec) {
1220
1294
  if (!isRecord2(spec.paths)) {
1221
1295
  throw new OpenAPIParserError2("OpenAPI spec is missing required field: paths");
1222
1296
  }
1297
+ const globalSecurity = Array.isArray(spec.security) ? spec.security : [];
1223
1298
  const operations = [];
1224
1299
  for (const [path, pathItem] of Object.entries(spec.paths)) {
1225
1300
  if (!isRecord2(pathItem)) {
@@ -1233,7 +1308,8 @@ function parseOpenAPI(spec) {
1233
1308
  const operationId = typeof operation.operationId === "string" ? operation.operationId : `${method}_${path}`;
1234
1309
  const { pathParams, queryParams } = extractParameters(path, pathItem, operation, spec, version);
1235
1310
  const successResponse = pickSuccessResponse(operation, spec);
1236
- operations.push({
1311
+ const security = extractOperationSecurity(operation, globalSecurity);
1312
+ const parsed = {
1237
1313
  operationId,
1238
1314
  methodName: normalizeOperationId(operationId, method.toUpperCase(), path),
1239
1315
  method: method.toUpperCase(),
@@ -1244,10 +1320,18 @@ function parseOpenAPI(spec) {
1244
1320
  response: successResponse.schema ? resolveSchemaForOutput(successResponse.schema, spec, version) : undefined,
1245
1321
  responseStatus: successResponse.status,
1246
1322
  tags: Array.isArray(operation.tags) ? operation.tags.filter((tag) => typeof tag === "string") : []
1247
- });
1323
+ };
1324
+ if (security)
1325
+ parsed.security = security;
1326
+ operations.push(parsed);
1248
1327
  }
1249
1328
  }
1250
- return { operations, schemas: collectComponentSchemas(spec, version), version };
1329
+ return {
1330
+ operations,
1331
+ schemas: collectComponentSchemas(spec, version),
1332
+ securitySchemes: extractSecuritySchemes(spec),
1333
+ version
1334
+ };
1251
1335
  }
1252
1336
 
1253
1337
  // src/writer/incremental.ts
@@ -1358,7 +1442,8 @@ async function generateFromOpenAPI(config) {
1358
1442
  version: typeof info?.version === "string" ? info.version : "0.0.0"
1359
1443
  },
1360
1444
  resources,
1361
- schemas: parsed.schemas
1445
+ schemas: parsed.schemas,
1446
+ securitySchemes: parsed.securitySchemes
1362
1447
  };
1363
1448
  const files = generateAll(spec, {
1364
1449
  schemas: config.schemas,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vertz/openapi",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "OpenAPI 3.x parser and TypeScript SDK generator for Vertz",
5
5
  "license": "MIT",
6
6
  "repository": {