@workos/oagen 0.1.0 → 0.2.0

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.
Files changed (47) hide show
  1. package/dist/cli/index.d.mts +2 -0
  2. package/dist/cli/index.mjs +6018 -0
  3. package/dist/cli/index.mjs.map +1 -0
  4. package/dist/compat/{index.d.ts → index.d.mts} +43 -114
  5. package/dist/compat/index.d.mts.map +1 -0
  6. package/dist/compat/index.mjs +4562 -0
  7. package/dist/compat/index.mjs.map +1 -0
  8. package/dist/diff-DyQF4sel.mjs +2250 -0
  9. package/dist/diff-DyQF4sel.mjs.map +1 -0
  10. package/dist/differ-BwelBJN0.mjs +1100 -0
  11. package/dist/differ-BwelBJN0.mjs.map +1 -0
  12. package/dist/index.d.mts +295 -0
  13. package/dist/index.d.mts.map +1 -0
  14. package/dist/index.mjs +1553 -0
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/scripts/smoke/baseline.d.mts +1 -0
  17. package/dist/scripts/smoke/baseline.mjs +1916 -0
  18. package/dist/scripts/smoke/baseline.mjs.map +1 -0
  19. package/dist/scripts/smoke/sdk-test.d.mts +1 -0
  20. package/dist/scripts/smoke/sdk-test.mjs +111 -0
  21. package/dist/scripts/smoke/sdk-test.mjs.map +1 -0
  22. package/dist/smoke.d.mts +331 -0
  23. package/dist/smoke.d.mts.map +1 -0
  24. package/dist/smoke.mjs +2084 -0
  25. package/dist/smoke.mjs.map +1 -0
  26. package/dist/tree-sitter-BPNfbEzY.mjs +359 -0
  27. package/dist/tree-sitter-BPNfbEzY.mjs.map +1 -0
  28. package/dist/types-O25qZxMl.d.mts +397 -0
  29. package/dist/types-O25qZxMl.d.mts.map +1 -0
  30. package/dist/verify/index.d.mts +82 -0
  31. package/dist/verify/index.d.mts.map +1 -0
  32. package/dist/verify/index.mjs +327 -0
  33. package/dist/verify/index.mjs.map +1 -0
  34. package/package.json +19 -16
  35. package/dist/chunk-CIX5NOBF.js +0 -263
  36. package/dist/chunk-IJ5E5AE7.js +0 -1040
  37. package/dist/chunk-VUSRNIG2.js +0 -1661
  38. package/dist/cli/index.js +0 -5314
  39. package/dist/compat/index.js +0 -4491
  40. package/dist/index.d.ts +0 -277
  41. package/dist/index.js +0 -1565
  42. package/dist/scripts/smoke/baseline.js +0 -1813
  43. package/dist/scripts/smoke/sdk-test.js +0 -93
  44. package/dist/smoke.js +0 -2025
  45. package/dist/types-B5t9O9Wm.d.ts +0 -367
  46. package/dist/verify/index.d.ts +0 -76
  47. package/dist/verify/index.js +0 -336
package/dist/smoke.js DELETED
@@ -1,2025 +0,0 @@
1
- // scripts/smoke/shared.ts
2
- import "dotenv/config";
3
- import { readFileSync } from "fs";
4
-
5
- // src/utils/naming.ts
6
- function splitWords(s) {
7
- if (!s) return [];
8
- return s.replace(/[^a-zA-Z0-9_\-\s.]/g, "_").replace(/([a-z])([A-Z])/g, "$1\0$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1\0$2").replace(/([a-zA-Z])(\d)/g, "$1\0$2").replace(/(\d)([a-zA-Z])/g, "$1\0$2").split(/[\0_\-\s.]+/).filter((w) => w.length > 0);
9
- }
10
- var ACRONYM_SET = /* @__PURE__ */ new Set(["SSO", "FGA", "SAML", "SCIM", "JWT", "HMAC"]);
11
- function toPascalCase(s, acronyms) {
12
- const merged = acronyms ? /* @__PURE__ */ new Set([...ACRONYM_SET, ...acronyms]) : ACRONYM_SET;
13
- return splitWords(s).map((w) => {
14
- const upper = w.toUpperCase();
15
- if (merged.has(upper)) return upper;
16
- if (upper === "OAUTH") return "OAuth";
17
- return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
18
- }).join("");
19
- }
20
- function toCamelCase(s, acronyms) {
21
- const merged = acronyms ? /* @__PURE__ */ new Set([...ACRONYM_SET, ...acronyms]) : ACRONYM_SET;
22
- const words = splitWords(s);
23
- if (words.length === 0) return "";
24
- return words.map((w, i) => {
25
- if (i === 0) return w.toLowerCase();
26
- const upper = w.toUpperCase();
27
- if (merged.has(upper)) return upper;
28
- if (upper === "OAUTH") return "OAuth";
29
- return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();
30
- }).join("");
31
- }
32
- function toSnakeCase(s) {
33
- return splitWords(s).map((w) => w.toLowerCase()).join("_");
34
- }
35
- function toUpperSnakeCase(s) {
36
- return splitWords(s).map((w) => w.toUpperCase()).join("_");
37
- }
38
- var BACKEND_SUFFIXES = ["Dto", "DTO", "Controller"];
39
- function stripBackendSuffixes(name) {
40
- for (const suffix of BACKEND_SUFFIXES) {
41
- if (name.endsWith(suffix) && name.length > suffix.length) {
42
- return name.slice(0, -suffix.length);
43
- }
44
- }
45
- return name;
46
- }
47
- function stripBackendPrefixes(name) {
48
- return name.replace(/Userland/g, "").replace(/Controller/g, "");
49
- }
50
- function stripListItemMarkers(name) {
51
- return name.replace(/ListItem/g, "").replace(/ByExternalId/g, "").replace(/ByResourceId/g, "").replace(/ForResource/g, "");
52
- }
53
- var SINGULAR_SAFE_LIST = /* @__PURE__ */ new Set([
54
- "Status",
55
- "Address",
56
- "Access",
57
- "Process",
58
- "Progress",
59
- "Success",
60
- "Radius",
61
- "Canvas",
62
- "Alias",
63
- "Basis",
64
- "Bus"
65
- ]);
66
- function singularize(word) {
67
- if (SINGULAR_SAFE_LIST.has(word)) return word;
68
- if (word.endsWith("ies") && word.length > 4) {
69
- return word.slice(0, -3) + "y";
70
- }
71
- if (word.endsWith("ses") && word.length > 4) {
72
- return word.slice(0, -2);
73
- }
74
- if (word.endsWith("s") && !word.endsWith("ss") && word.length > 4) {
75
- return word.slice(0, -1);
76
- }
77
- return word;
78
- }
79
- function cleanSchemaName(name) {
80
- let result = stripBackendPrefixes(stripBackendSuffixes(name));
81
- result = stripListItemMarkers(result);
82
- const match = result.match(/^([A-Z][a-z]*)/);
83
- if (match) {
84
- const leadWord = match[1];
85
- const singular = singularize(leadWord);
86
- if (singular !== leadWord) {
87
- result = singular + result.slice(leadWord.length);
88
- }
89
- }
90
- return result;
91
- }
92
-
93
- // src/errors.ts
94
- var OagenError = class extends Error {
95
- hint;
96
- constructor(message, hint) {
97
- const fullMessage = hint ? `${message}
98
- Hint: ${hint}` : message;
99
- super(fullMessage);
100
- this.name = "OagenError";
101
- this.hint = hint;
102
- }
103
- };
104
- var SpecParseError = class extends OagenError {
105
- constructor(message, hint) {
106
- super(message, hint);
107
- this.name = "SpecParseError";
108
- }
109
- };
110
-
111
- // src/parser/refs.ts
112
- import { readFile } from "fs/promises";
113
- import { resolve } from "path";
114
- import { pathToFileURL } from "url";
115
- import { makeDocumentFromString, bundleDocument, createConfig, BaseResolver } from "@redocly/openapi-core";
116
- async function loadAndBundleSpec(specPath) {
117
- const absolutePath = resolve(specPath);
118
- const content = await readFile(absolutePath, "utf-8");
119
- const document = makeDocumentFromString(content, pathToFileURL(absolutePath).href);
120
- const config = await createConfig({});
121
- const resolver = new BaseResolver();
122
- const result = await bundleDocument({
123
- document,
124
- config: config.styleguide,
125
- externalRefResolver: resolver,
126
- dereference: false
127
- });
128
- if (result.problems.some((p) => p.severity === "error")) {
129
- const errors = result.problems.filter((p) => p.severity === "error").map((p) => p.message).join("\n");
130
- throw new SpecParseError(
131
- `Failed to parse spec: ${errors}`,
132
- `Check the OpenAPI spec at "${absolutePath}" for syntax errors. Run a linter such as \`npx @redocly/cli lint ${specPath}\` to identify issues.`
133
- );
134
- }
135
- return {
136
- parsed: result.bundle.parsed,
137
- specPath: absolutePath
138
- };
139
- }
140
-
141
- // src/ir/types.ts
142
- function assertNever(x) {
143
- const kind = x.kind;
144
- const err = new Error(
145
- `Unexpected TypeRef kind: ${kind}
146
- Hint: If you added a new TypeRef variant, handle it in every switch/case that calls assertNever.`
147
- );
148
- err.name = "InternalError";
149
- throw err;
150
- }
151
- function walkTypeRef(ref, visitor) {
152
- switch (ref.kind) {
153
- case "model":
154
- visitor.model?.(ref);
155
- break;
156
- case "enum":
157
- visitor.enum?.(ref);
158
- break;
159
- case "array":
160
- walkTypeRef(ref.items, visitor);
161
- break;
162
- case "nullable":
163
- walkTypeRef(ref.inner, visitor);
164
- break;
165
- case "union":
166
- for (const v of ref.variants) walkTypeRef(v, visitor);
167
- break;
168
- case "map":
169
- if (ref.keyType) walkTypeRef(ref.keyType, visitor);
170
- walkTypeRef(ref.valueType, visitor);
171
- break;
172
- case "literal":
173
- visitor.literal?.(ref);
174
- break;
175
- case "primitive":
176
- visitor.primitive?.(ref);
177
- break;
178
- default:
179
- assertNever(ref);
180
- }
181
- }
182
-
183
- // src/parser/inline-models.ts
184
- function qualifyInlineModelName(baseName, parentName) {
185
- if (!parentName) return baseName;
186
- const cleanParent = stripListItemMarkers(parentName);
187
- if (baseName.startsWith(cleanParent)) return baseName;
188
- const trailingMatch = baseName.match(/^(.*?)([A-Z][a-z]*)$/);
189
- if (trailingMatch) {
190
- const [, prefix, trailingWord] = trailingMatch;
191
- const singular = singularize(trailingWord);
192
- return `${cleanParent}${prefix}${singular}`;
193
- }
194
- return `${cleanParent}${baseName}`;
195
- }
196
- function extractInlineModelsFromSchemas(schemas) {
197
- if (!schemas) return [];
198
- const inlineModels = [];
199
- for (const [schemaName, schema] of Object.entries(schemas)) {
200
- const parentName = cleanSchemaName(toPascalCase(schemaName));
201
- extractInlineModelsFromProperties(schema, inlineModels, parentName);
202
- }
203
- return inlineModels;
204
- }
205
- function extractInlineModelsFromProperties(schema, results, parentName) {
206
- const properties = schema.properties ?? {};
207
- const allOfSchemas = schema.allOf ?? [];
208
- for (const sub of allOfSchemas) {
209
- if (sub.properties) {
210
- extractInlineModelsFromProperties(sub, results, parentName);
211
- }
212
- }
213
- for (const [fieldName, fieldSchema] of Object.entries(properties)) {
214
- if (!fieldSchema) continue;
215
- if (fieldSchema.properties && (fieldSchema.type === "object" || !fieldSchema.type)) {
216
- const baseName = toPascalCase(fieldName);
217
- const modelName = qualifyInlineModelName(baseName, parentName);
218
- results.push(buildInlineModel(modelName, fieldSchema));
219
- extractInlineModelsFromProperties(fieldSchema, results, modelName);
220
- }
221
- if (fieldSchema.type === "array" && fieldSchema.items) {
222
- const items = fieldSchema.items;
223
- if (items.properties && (items.type === "object" || !items.type)) {
224
- const baseName = toPascalCase(fieldName);
225
- const modelName = qualifyInlineModelName(baseName, parentName);
226
- results.push(buildInlineModel(modelName, items));
227
- extractInlineModelsFromProperties(items, results, modelName);
228
- }
229
- }
230
- if (fieldSchema.oneOf) {
231
- const objectVariant = fieldSchema.oneOf.find((v) => v.properties && (v.type === "object" || !v.type));
232
- if (objectVariant) {
233
- const baseName = toPascalCase(fieldName);
234
- const modelName = qualifyInlineModelName(baseName, parentName);
235
- const existingNames = new Set(results.map((r) => r.name));
236
- if (!existingNames.has(modelName)) {
237
- results.push(buildInlineModel(modelName, objectVariant));
238
- extractInlineModelsFromProperties(objectVariant, results, modelName);
239
- }
240
- }
241
- }
242
- }
243
- }
244
- function buildInlineModel(name, schema) {
245
- const requiredSet = new Set(schema.required ?? []);
246
- const fields = [];
247
- for (const [fieldName, fieldSchema] of Object.entries(schema.properties ?? {})) {
248
- if (!fieldSchema) continue;
249
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, name, requiredSet));
250
- }
251
- return { name, description: schema.description, fields };
252
- }
253
-
254
- // src/parser/schemas.ts
255
- function extractSchemas(schemas) {
256
- const enums = [];
257
- if (!schemas) return { models: [], enums };
258
- const modelsByCleanName = /* @__PURE__ */ new Map();
259
- for (const [name, schema] of Object.entries(schemas)) {
260
- const pascalName = cleanSchemaName(toPascalCase(name));
261
- if (schema.enum) {
262
- enums.push(extractEnum(pascalName, schema));
263
- } else {
264
- const model = extractModel(pascalName, schema, schemas);
265
- const existing = modelsByCleanName.get(pascalName);
266
- if (existing) {
267
- if (model.fields.length > existing.fields.length) {
268
- modelsByCleanName.set(pascalName, model);
269
- }
270
- } else {
271
- modelsByCleanName.set(pascalName, model);
272
- }
273
- }
274
- }
275
- const models = [...modelsByCleanName.values()];
276
- const modelNames = new Set(models.map((m) => m.name));
277
- const inlineQueue = [...models];
278
- while (inlineQueue.length > 0) {
279
- const model = inlineQueue.pop();
280
- for (const field of model.fields) {
281
- collectNestedInlineModels(field.type, field.name, model.name, schemas ?? {}, models, modelNames, inlineQueue);
282
- }
283
- }
284
- for (const model of models) {
285
- collectInlineEnums(model.fields, enums);
286
- }
287
- return { models, enums };
288
- }
289
- function collectInlineEnumFromRef(ref, enums, seen) {
290
- walkTypeRef(ref, {
291
- enum: (r) => {
292
- if (r.values && !seen.has(r.name)) {
293
- seen.add(r.name);
294
- enums.push({
295
- name: r.name,
296
- values: r.values.map((v) => ({
297
- name: toUpperSnakeCase(String(v)),
298
- value: v,
299
- description: void 0
300
- }))
301
- });
302
- }
303
- }
304
- });
305
- }
306
- function collectInlineEnums(fields, enums) {
307
- const seen = new Set(enums.map((e) => e.name));
308
- for (const field of fields) {
309
- collectInlineEnumFromRef(field.type, enums, seen);
310
- }
311
- }
312
- function collectNestedInlineModels(ref, fieldName, parentModelName, schemas, models, modelNames, queue) {
313
- walkTypeRef(ref, {
314
- model: (modelRef) => {
315
- if (modelNames.has(modelRef.name)) return;
316
- const isComponent = Object.keys(schemas).some((k) => cleanSchemaName(toPascalCase(k)) === modelRef.name);
317
- if (isComponent) return;
318
- const parentSchema = findSchemaByName(parentModelName, schemas);
319
- if (!parentSchema) return;
320
- const fieldSchema = findNestedFieldSchema(fieldName, parentSchema, schemas);
321
- if (!fieldSchema) return;
322
- const extracted = extractNestedSchema(modelRef.name, fieldSchema);
323
- for (const m of extracted) {
324
- if (!modelNames.has(m.name)) {
325
- models.push(m);
326
- modelNames.add(m.name);
327
- queue.push(m);
328
- }
329
- }
330
- }
331
- });
332
- }
333
- function findSchemaByName(pascalName, schemas) {
334
- for (const [k, v] of Object.entries(schemas)) {
335
- if (cleanSchemaName(toPascalCase(k)) === pascalName) return v;
336
- }
337
- return null;
338
- }
339
- function findNestedFieldSchema(fieldName, parentSchema, schemas) {
340
- if (parentSchema.properties?.[fieldName]) {
341
- return parentSchema.properties[fieldName];
342
- }
343
- if (parentSchema.allOf) {
344
- for (const sub of parentSchema.allOf) {
345
- let resolved = sub;
346
- if (sub.$ref) {
347
- const segments = sub.$ref.split("/");
348
- const refName = segments[segments.length - 1];
349
- if (refName && schemas[refName]) resolved = schemas[refName];
350
- }
351
- if (resolved.properties?.[fieldName]) {
352
- return resolved.properties[fieldName];
353
- }
354
- }
355
- }
356
- return null;
357
- }
358
- function extractNestedSchema(name, schema) {
359
- if (schema.oneOf) {
360
- const models = [];
361
- for (const variant of schema.oneOf) {
362
- if (variant.$ref) continue;
363
- if (variant.properties && (variant.type === "object" || !variant.type)) {
364
- const requiredSet = new Set(variant.required ?? []);
365
- const fields = [];
366
- for (const [fn, fs] of Object.entries(variant.properties)) {
367
- if (!fs) continue;
368
- fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
369
- }
370
- const variantName = models.length === 0 ? name : `${name}${models.length + 1}`;
371
- models.push({ name: variantName, description: variant.description, fields });
372
- }
373
- }
374
- return models;
375
- }
376
- if (schema.properties && (schema.type === "object" || !schema.type)) {
377
- const requiredSet = new Set(schema.required ?? []);
378
- const fields = [];
379
- for (const [fn, fs] of Object.entries(schema.properties)) {
380
- if (!fs) continue;
381
- fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
382
- }
383
- return [{ name, description: schema.description, fields }];
384
- }
385
- if (schema.type === "array" && schema.items?.properties) {
386
- const requiredSet = new Set(schema.items.required ?? []);
387
- const fields = [];
388
- for (const [fn, fs] of Object.entries(schema.items.properties)) {
389
- if (!fs) continue;
390
- fields.push(buildFieldFromSchema(fn, fs, name, requiredSet));
391
- }
392
- return [{ name, description: schema.items.description, fields }];
393
- }
394
- return [];
395
- }
396
- function buildFieldFromSchema(fieldName, fieldSchema, contextName, requiredSet) {
397
- return {
398
- name: fieldName,
399
- type: schemaToTypeRef(fieldSchema, fieldName, contextName),
400
- required: requiredSet.has(fieldName),
401
- description: fieldSchema.description,
402
- readOnly: fieldSchema.readOnly || void 0,
403
- writeOnly: fieldSchema.writeOnly || void 0,
404
- deprecated: fieldSchema.deprecated || void 0,
405
- default: fieldSchema.default
406
- };
407
- }
408
- function extractEnum(name, schema) {
409
- const values = (schema.enum ?? []).map((v) => ({
410
- name: toUpperSnakeCase(String(v)),
411
- value: typeof v === "number" ? v : String(v),
412
- description: void 0
413
- }));
414
- return { name, values };
415
- }
416
- function extractModel(name, schema, schemas) {
417
- if (schema.allOf) {
418
- return extractAllOfModel(name, schema, schemas);
419
- }
420
- const requiredSet = new Set(schema.required ?? []);
421
- const fields = [];
422
- for (const [fieldName, fieldSchema] of Object.entries(schema.properties ?? {})) {
423
- if (!fieldSchema) continue;
424
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, name, requiredSet));
425
- }
426
- if (schema.additionalProperties && typeof schema.additionalProperties === "object" && schema.properties) {
427
- const apKeys = Object.keys(schema.additionalProperties);
428
- if (apKeys.length > 0) {
429
- const valueType = schemaToTypeRef(schema.additionalProperties, "additionalProperties", name);
430
- fields.push({
431
- name: "additionalProperties",
432
- type: { kind: "map", valueType },
433
- required: false,
434
- description: "Additional properties not captured by named fields"
435
- });
436
- }
437
- }
438
- return { name, description: schema.description, fields };
439
- }
440
- function extractAllOfModel(name, schema, schemas) {
441
- const fields = [];
442
- const requiredSet = /* @__PURE__ */ new Set();
443
- for (const subSchema of schema.allOf ?? []) {
444
- let resolved = subSchema;
445
- if (subSchema.$ref && schemas) {
446
- const segments = subSchema.$ref.split("/");
447
- const refName = segments[segments.length - 1];
448
- if (refName && schemas[refName]) {
449
- resolved = schemas[refName];
450
- }
451
- }
452
- if (resolved.allOf) {
453
- const nested = extractAllOfModel(name, resolved, schemas);
454
- for (const f of nested.fields) {
455
- fields.push({ ...f, required: false });
456
- if (f.required) requiredSet.add(f.name);
457
- }
458
- } else {
459
- if (resolved.required) {
460
- for (const r of resolved.required) requiredSet.add(r);
461
- }
462
- if (resolved.properties) {
463
- const emptyRequired = /* @__PURE__ */ new Set();
464
- for (const [fieldName, fieldSchema] of Object.entries(resolved.properties)) {
465
- if (!fieldSchema) continue;
466
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, name, emptyRequired));
467
- }
468
- }
469
- }
470
- }
471
- if (schema.required) {
472
- for (const r of schema.required) requiredSet.add(r);
473
- }
474
- for (const f of fields) {
475
- f.required = requiredSet.has(f.name);
476
- }
477
- return { name, description: schema.description, fields };
478
- }
479
- function schemaToTypeRef(schema, contextName, parentModelName) {
480
- if (schema.$ref) {
481
- const segments = schema.$ref.split("/");
482
- const rawName = segments[segments.length - 1];
483
- return { kind: "model", name: cleanSchemaName(toPascalCase(rawName)) };
484
- }
485
- if (Array.isArray(schema.type)) {
486
- const nonNullTypes = schema.type.filter((t) => t !== "null");
487
- if (nonNullTypes.length === 0) {
488
- return { kind: "nullable", inner: { kind: "primitive", type: "unknown" } };
489
- }
490
- if (schema.type.includes("null") && nonNullTypes.length === 1) {
491
- return {
492
- kind: "nullable",
493
- inner: schemaToTypeRef({ ...schema, type: nonNullTypes[0], nullable: false }, contextName)
494
- };
495
- }
496
- if (nonNullTypes.length > 1) {
497
- const variants = nonNullTypes.map((t) => schemaToTypeRef({ ...schema, type: t }, contextName));
498
- const ref = { kind: "union", variants };
499
- if (schema.type.includes("null")) {
500
- return { kind: "nullable", inner: ref };
501
- }
502
- return ref;
503
- }
504
- }
505
- if (schema.nullable && schema.type) {
506
- return {
507
- kind: "nullable",
508
- inner: schemaToTypeRef({ ...schema, nullable: false }, contextName)
509
- };
510
- }
511
- if (schema.allOf) {
512
- const refItem = schema.allOf.find((s) => s.$ref);
513
- if (refItem) {
514
- const hasAugmentation = schema.allOf.some((s) => !s.$ref && (s.properties || s.type === "object"));
515
- if (hasAugmentation) {
516
- const baseName = toPascalCase(contextName ?? "UnknownModel");
517
- return { kind: "model", name: qualifyInlineModelName(baseName, parentModelName) };
518
- }
519
- return schemaToTypeRef(refItem, contextName, parentModelName);
520
- }
521
- if (schema.allOf.length === 1) {
522
- return schemaToTypeRef(schema.allOf[0], contextName, parentModelName);
523
- }
524
- const hasProperties = schema.allOf.some((s) => s.properties);
525
- if (hasProperties) {
526
- const baseName = toPascalCase(contextName ?? "UnknownModel");
527
- return {
528
- kind: "model",
529
- name: qualifyInlineModelName(baseName, parentModelName)
530
- };
531
- }
532
- }
533
- if (schema.oneOf || schema.anyOf) {
534
- const compositionKind = schema.oneOf ? "oneOf" : "anyOf";
535
- const rawVariants = schema.oneOf ?? schema.anyOf ?? [];
536
- const nullVariant = rawVariants.find(
537
- (v) => v.type === "null" || Array.isArray(v.type) && v.type.length === 1 && v.type[0] === "null"
538
- );
539
- const nonNullVariants = rawVariants.filter((v) => v !== nullVariant);
540
- if (nullVariant && nonNullVariants.length === 1) {
541
- return {
542
- kind: "nullable",
543
- inner: schemaToTypeRef(nonNullVariants[0], contextName, parentModelName)
544
- };
545
- }
546
- const variants = rawVariants.filter((v) => v.type !== "null").map((v) => schemaToTypeRef(v, contextName, parentModelName));
547
- const hasNull = !!nullVariant;
548
- const union = {
549
- kind: "union",
550
- variants,
551
- compositionKind,
552
- ...schema.discriminator ? {
553
- discriminator: {
554
- property: schema.discriminator.propertyName,
555
- mapping: Object.fromEntries(
556
- Object.entries(schema.discriminator.mapping ?? {}).map(([k, v]) => [
557
- k,
558
- v.replace(/^#\/components\/schemas\//, "")
559
- ])
560
- )
561
- }
562
- } : {}
563
- };
564
- return hasNull ? { kind: "nullable", inner: union } : union;
565
- }
566
- if (schema.const !== void 0) {
567
- if (typeof schema.const === "string" || typeof schema.const === "number" || typeof schema.const === "boolean") {
568
- return { kind: "literal", value: schema.const };
569
- }
570
- if (schema.const === null) {
571
- return { kind: "nullable", inner: { kind: "primitive", type: "unknown" } };
572
- }
573
- if (typeof schema.const === "object") {
574
- if (Array.isArray(schema.const)) {
575
- return { kind: "array", items: { kind: "primitive", type: "unknown" } };
576
- }
577
- return { kind: "map", valueType: { kind: "primitive", type: "unknown" } };
578
- }
579
- }
580
- if (schema.enum && schema.enum.length === 1) {
581
- const v = schema.enum[0];
582
- return { kind: "literal", value: typeof v === "number" ? v : String(v) };
583
- }
584
- if (schema.enum) {
585
- const baseName = toPascalCase(contextName ?? "UnknownEnum");
586
- const cleanParent = parentModelName ? stripListItemMarkers(parentModelName) : void 0;
587
- const qualifiedName = cleanParent && !baseName.startsWith(cleanParent) ? `${cleanParent}${baseName}` : baseName;
588
- return {
589
- kind: "enum",
590
- name: qualifiedName,
591
- values: schema.enum.map((v) => typeof v === "number" ? v : String(v))
592
- };
593
- }
594
- if (schema.type === "array" && schema.items) {
595
- return {
596
- kind: "array",
597
- items: schemaToTypeRef(schema.items, contextName, parentModelName)
598
- };
599
- }
600
- if (!schema.type && schema.items) {
601
- return {
602
- kind: "array",
603
- items: schemaToTypeRef(schema.items, contextName, parentModelName)
604
- };
605
- }
606
- if (schema.type === "object" && schema.properties) {
607
- const baseName = toPascalCase(contextName ?? "UnknownModel");
608
- return {
609
- kind: "model",
610
- name: qualifyInlineModelName(baseName, parentModelName)
611
- };
612
- }
613
- if (schema.type === "object" && !schema.properties) {
614
- let valueType = { kind: "primitive", type: "unknown" };
615
- if (schema.additionalProperties && typeof schema.additionalProperties === "object") {
616
- const apKeys = Object.keys(schema.additionalProperties);
617
- if (apKeys.length > 0) {
618
- valueType = schemaToTypeRef(schema.additionalProperties, contextName);
619
- }
620
- } else if (schema.patternProperties) {
621
- const patterns = Object.values(schema.patternProperties);
622
- if (patterns.length > 0) {
623
- valueType = schemaToTypeRef(patterns[0], contextName);
624
- }
625
- }
626
- return {
627
- kind: "map",
628
- valueType
629
- };
630
- }
631
- const primitiveMap = {
632
- string: "string",
633
- integer: "integer",
634
- number: "number",
635
- boolean: "boolean"
636
- };
637
- const typeStr = Array.isArray(schema.type) ? schema.type[0] : schema.type;
638
- if (typeStr && primitiveMap[typeStr]) {
639
- return {
640
- kind: "primitive",
641
- type: primitiveMap[typeStr],
642
- ...schema.format ? { format: schema.format } : {}
643
- };
644
- }
645
- if (!schema.type && schema.properties) {
646
- const baseName = toPascalCase(contextName ?? "UnknownModel");
647
- return {
648
- kind: "model",
649
- name: qualifyInlineModelName(baseName, parentModelName)
650
- };
651
- }
652
- if (!schema.type && !schema.$ref && !schema.oneOf && !schema.anyOf && !schema.allOf && !schema.enum && !schema.properties && !schema.items) {
653
- return { kind: "primitive", type: "unknown" };
654
- }
655
- if (contextName) {
656
- console.warn(`[oagen] Warning: Unknown schema shape treated as unknown (context: ${contextName})`);
657
- }
658
- return { kind: "primitive", type: "unknown" };
659
- }
660
-
661
- // src/parser/pagination.ts
662
- var CURSOR_PARAMS = ["cursor", "after", "before", "starting_after", "ending_before", "page_token", "next_token"];
663
- var OFFSET_PARAMS = ["offset", "page", "page_number", "skip"];
664
- var LIMIT_PARAMS = ["limit", "page_size", "per_page", "size", "count"];
665
- function detectPagination(response, queryParams, dataPath) {
666
- const cursorParam = queryParams.find((p) => CURSOR_PARAMS.includes(p.name));
667
- if (cursorParam) {
668
- const itemType = response.kind === "array" ? response.items : response;
669
- return {
670
- strategy: "cursor",
671
- param: cursorParam.name,
672
- dataPath,
673
- itemType
674
- };
675
- }
676
- const offsetParam = queryParams.find((p) => OFFSET_PARAMS.includes(p.name));
677
- const limitParam = queryParams.find((p) => LIMIT_PARAMS.includes(p.name));
678
- if (offsetParam && limitParam) {
679
- const itemType = response.kind === "array" ? response.items : response;
680
- return {
681
- strategy: "offset",
682
- param: offsetParam.name,
683
- limitParam: limitParam.name,
684
- dataPath,
685
- itemType
686
- };
687
- }
688
- return null;
689
- }
690
-
691
- // src/parser/responses.ts
692
- function classifyAndExtractResponse(schema, contextName) {
693
- if (schema.$ref) {
694
- return { response: schemaToTypeRef(schema, contextName), inlineModels: [], isPaginated: false };
695
- }
696
- const envelope = detectListEnvelope(schema);
697
- if (envelope.isEnvelope) {
698
- return extractListResponse(schema, contextName, envelope.dataPath);
699
- }
700
- if (isSingleResourceWrapper(schema)) {
701
- return extractWrappedResource(schema, contextName);
702
- }
703
- return extractDirectResource(schema, contextName);
704
- }
705
- var KNOWN_DATA_PATHS = /* @__PURE__ */ new Set(["data", "items", "results", "records", "entries", "values", "nodes", "edges"]);
706
- var PAGINATION_METADATA_PATTERNS = [
707
- "metadata",
708
- "pagination",
709
- "cursor",
710
- "has_more",
711
- "page_info",
712
- "total",
713
- "next_page",
714
- "previous_page",
715
- "offset"
716
- ];
717
- function detectListEnvelope(schema) {
718
- const propSources = [];
719
- if (schema.allOf) {
720
- for (const sub of schema.allOf) {
721
- if (sub.properties) propSources.push(sub.properties);
722
- }
723
- }
724
- if (schema.properties) {
725
- propSources.push(schema.properties);
726
- }
727
- if (propSources.length === 0) return { isEnvelope: false, dataPath: null };
728
- const mergedProps = {};
729
- for (const source of propSources) {
730
- Object.assign(mergedProps, source);
731
- }
732
- const arrayProps = [];
733
- const nonArrayKeys = [];
734
- for (const [key, propSchema] of Object.entries(mergedProps)) {
735
- if (!propSchema) continue;
736
- if (propSchema.type === "array") {
737
- arrayProps.push(key);
738
- } else {
739
- nonArrayKeys.push(key);
740
- }
741
- }
742
- if (arrayProps.length === 1 && nonArrayKeys.length >= 1) {
743
- const arrayPropName = arrayProps[0];
744
- if (KNOWN_DATA_PATHS.has(arrayPropName)) {
745
- return { isEnvelope: true, dataPath: arrayPropName };
746
- }
747
- const hasPaginationCompanion = nonArrayKeys.some(
748
- (key) => PAGINATION_METADATA_PATTERNS.some((pattern) => key.toLowerCase().includes(pattern))
749
- );
750
- if (hasPaginationCompanion) {
751
- return { isEnvelope: true, dataPath: arrayPropName };
752
- }
753
- }
754
- return { isEnvelope: false, dataPath: null };
755
- }
756
- function extractListResponse(schema, contextName, dataPath) {
757
- let itemTypeRef = { kind: "primitive", type: "string" };
758
- const inlineModels = [];
759
- const propSources = [];
760
- if (schema.allOf) {
761
- for (const sub of schema.allOf) {
762
- if (sub.properties) propSources.push(sub.properties);
763
- }
764
- }
765
- if (schema.properties) {
766
- propSources.push(schema.properties);
767
- }
768
- for (const props of propSources) {
769
- const dataProp = props[dataPath];
770
- if (!dataProp) continue;
771
- if (dataProp.type === "array" && dataProp.items) {
772
- const items = dataProp.items;
773
- if (items.$ref) {
774
- itemTypeRef = schemaToTypeRef(items, contextName);
775
- } else if (items.type === "object" && items.properties) {
776
- const itemName = contextName.replace(/Response$/, "") + "Item";
777
- itemTypeRef = { kind: "model", name: itemName };
778
- inlineModels.push(...extractInlineModel(itemName, items));
779
- }
780
- }
781
- }
782
- return {
783
- response: { kind: "array", items: itemTypeRef },
784
- inlineModels,
785
- isPaginated: true,
786
- dataPath,
787
- itemType: itemTypeRef
788
- };
789
- }
790
- function hasDiscriminantConstField(schema) {
791
- if (!schema.properties) return null;
792
- const preferred = ["object", "type"];
793
- for (const name of preferred) {
794
- const field = schema.properties[name];
795
- if (!field) continue;
796
- if (field.const !== void 0 && typeof field.const === "string") {
797
- return { property: name, value: field.const };
798
- }
799
- if (field.enum && field.enum.length === 1 && typeof field.enum[0] === "string") {
800
- return { property: name, value: field.enum[0] };
801
- }
802
- }
803
- for (const [name, field] of Object.entries(schema.properties)) {
804
- if (!field) continue;
805
- if (field.const !== void 0 && typeof field.const === "string") {
806
- return { property: name, value: field.const };
807
- }
808
- if (field.enum && field.enum.length === 1 && typeof field.enum[0] === "string") {
809
- return { property: name, value: field.enum[0] };
810
- }
811
- }
812
- return null;
813
- }
814
- function isSingleResourceWrapper(schema) {
815
- if (schema.type !== "object") return false;
816
- if (!schema.properties) return false;
817
- let wrapperKey;
818
- if (schema.required && schema.required.length === 1) {
819
- wrapperKey = schema.required[0];
820
- } else {
821
- const propKeys = Object.keys(schema.properties);
822
- if (propKeys.length === 1) {
823
- wrapperKey = propKeys[0];
824
- }
825
- }
826
- if (!wrapperKey) return false;
827
- const propSchema = schema.properties[wrapperKey];
828
- if (!propSchema) return false;
829
- if (propSchema.type === "object" && hasDiscriminantConstField(propSchema) !== null) return true;
830
- if (propSchema.oneOf) {
831
- return propSchema.oneOf.some((v) => v.type === "object" && hasDiscriminantConstField(v) !== null);
832
- }
833
- return false;
834
- }
835
- function extractWrappedResource(schema, contextName) {
836
- const props = schema.properties;
837
- const wrapperKey = schema.required && schema.required.length === 1 ? schema.required[0] : Object.keys(props)[0];
838
- const propSchema = props[wrapperKey];
839
- const inlineModels = [];
840
- const resourceName = contextName.replace(/Response$/, "");
841
- if (propSchema.oneOf) {
842
- const objectVariant = propSchema.oneOf.find((v) => v.type === "object" && hasDiscriminantConstField(v) !== null);
843
- if (objectVariant) {
844
- const modelName2 = deriveModelName(objectVariant, resourceName);
845
- inlineModels.push(...extractInlineModel(modelName2, objectVariant));
846
- const hasNullVariant = propSchema.oneOf.some(
847
- (v) => v.type === "null" || Array.isArray(v.type) && v.type.includes("null")
848
- );
849
- const modelRef = { kind: "model", name: modelName2 };
850
- return {
851
- response: hasNullVariant ? { kind: "nullable", inner: modelRef } : modelRef,
852
- inlineModels,
853
- isPaginated: false
854
- };
855
- }
856
- }
857
- const modelName = deriveModelName(propSchema, resourceName);
858
- inlineModels.push(...extractInlineModel(modelName, propSchema));
859
- return {
860
- response: { kind: "model", name: modelName },
861
- inlineModels,
862
- isPaginated: false
863
- };
864
- }
865
- function extractDirectResource(schema, contextName) {
866
- const inlineModels = [];
867
- if (schema.properties && (schema.type === "object" || !schema.type)) {
868
- const modelName = deriveModelName(schema, contextName);
869
- inlineModels.push(...extractInlineModel(modelName, schema));
870
- return {
871
- response: { kind: "model", name: modelName },
872
- inlineModels,
873
- isPaginated: false
874
- };
875
- }
876
- if (schema.allOf) {
877
- const mergedProperties = {};
878
- const mergedRequired = [];
879
- for (const sub of schema.allOf) {
880
- if (sub.properties) {
881
- Object.assign(mergedProperties, sub.properties);
882
- }
883
- if (sub.required) {
884
- mergedRequired.push(...sub.required);
885
- }
886
- }
887
- if (Object.keys(mergedProperties).length > 0) {
888
- const merged = { type: "object", properties: mergedProperties, required: mergedRequired };
889
- const modelName = deriveModelName(merged, contextName);
890
- inlineModels.push(...extractInlineModel(modelName, merged));
891
- return {
892
- response: { kind: "model", name: modelName },
893
- inlineModels,
894
- isPaginated: false
895
- };
896
- }
897
- }
898
- if (schema.oneOf) {
899
- for (const variant of schema.oneOf) {
900
- if (variant.properties && (variant.type === "object" || !variant.type)) {
901
- const modelName = deriveModelName(variant, contextName);
902
- const existingNames = new Set(inlineModels.map((m) => m.name));
903
- if (!existingNames.has(modelName)) {
904
- inlineModels.push(...extractInlineModel(modelName, variant));
905
- }
906
- }
907
- }
908
- }
909
- if (schema.type === "array" && schema.items) {
910
- const items = schema.items;
911
- if (items.$ref) {
912
- return {
913
- response: { kind: "array", items: schemaToTypeRef(items, contextName) },
914
- inlineModels,
915
- isPaginated: false
916
- };
917
- }
918
- if (items.properties && (items.type === "object" || !items.type)) {
919
- const itemName = contextName.replace(/Response$/, "") + "Item";
920
- inlineModels.push(...extractInlineModel(itemName, items));
921
- return {
922
- response: { kind: "array", items: { kind: "model", name: itemName } },
923
- inlineModels,
924
- isPaginated: false
925
- };
926
- }
927
- }
928
- return {
929
- response: schemaToTypeRef(schema, contextName),
930
- inlineModels,
931
- isPaginated: false
932
- };
933
- }
934
- function deriveModelName(schema, fallback) {
935
- if (!schema.properties) return toPascalCase(fallback);
936
- const preferred = ["object", "type"];
937
- for (const name of preferred) {
938
- const field = schema.properties[name];
939
- if (field && field.const && typeof field.const === "string") {
940
- return toPascalCase(field.const);
941
- }
942
- }
943
- for (const [, field] of Object.entries(schema.properties)) {
944
- if (field && field.const && typeof field.const === "string") {
945
- return toPascalCase(field.const);
946
- }
947
- }
948
- return toPascalCase(fallback);
949
- }
950
- function qualifyNestedName(parentName, fieldName) {
951
- const pascalField = toPascalCase(fieldName);
952
- if (pascalField.startsWith(parentName)) return pascalField;
953
- const cleanParent = stripListItemMarkers(parentName);
954
- const qualified = `${cleanParent}${pascalField}`;
955
- const match = qualified.match(/^(.+?)([A-Z][a-z]+s?)$/);
956
- if (match && match[2]) {
957
- const trailing = singularize(match[2]);
958
- return match[1] + trailing;
959
- }
960
- return qualified;
961
- }
962
- function extractInlineModel(name, schema) {
963
- const requiredSet = new Set(schema.required ?? []);
964
- const fields = [];
965
- const properties = schema.properties ?? {};
966
- const nestedModels = [];
967
- for (const [fieldName, fieldSchema] of Object.entries(properties)) {
968
- if (!fieldSchema) continue;
969
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, name, requiredSet));
970
- if (fieldSchema.type === "object" && fieldSchema.properties) {
971
- const nestedName = qualifyNestedName(name, fieldName);
972
- nestedModels.push(...extractInlineModel(nestedName, fieldSchema));
973
- }
974
- if (fieldSchema.type === "array" && fieldSchema.items) {
975
- const items = fieldSchema.items;
976
- if (items.type === "object" && items.properties) {
977
- const nestedName = qualifyNestedName(name, fieldName);
978
- nestedModels.push(...extractInlineModel(nestedName, items));
979
- }
980
- }
981
- if (fieldSchema.allOf) {
982
- const mergedProperties = {};
983
- const mergedRequired = [];
984
- for (const sub of fieldSchema.allOf) {
985
- if (sub.properties) Object.assign(mergedProperties, sub.properties);
986
- if (sub.required) mergedRequired.push(...sub.required);
987
- }
988
- if (Object.keys(mergedProperties).length > 0) {
989
- const nestedName = qualifyNestedName(name, fieldName);
990
- const merged = { type: "object", properties: mergedProperties, required: mergedRequired };
991
- const existingNames = new Set(nestedModels.map((m) => m.name));
992
- if (!existingNames.has(nestedName)) {
993
- nestedModels.push(...extractInlineModel(nestedName, merged));
994
- }
995
- }
996
- }
997
- if (fieldSchema.oneOf) {
998
- const objectVariant = fieldSchema.oneOf.find((v) => v.properties && (v.type === "object" || !v.type));
999
- if (objectVariant) {
1000
- const nestedName = qualifyNestedName(name, fieldName);
1001
- const existingNames = new Set(nestedModels.map((m) => m.name));
1002
- if (!existingNames.has(nestedName)) {
1003
- nestedModels.push(...extractInlineModel(nestedName, objectVariant));
1004
- }
1005
- }
1006
- }
1007
- }
1008
- return [{ name, description: schema.description, fields }, ...nestedModels];
1009
- }
1010
-
1011
- // src/parser/operations.ts
1012
- var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
1013
- function extractOperations(paths, operationIdTransform) {
1014
- if (!paths) return { services: [], inlineModels: [] };
1015
- const serviceMap = /* @__PURE__ */ new Map();
1016
- const inlineModels = [];
1017
- for (const [path, pathItem] of Object.entries(paths)) {
1018
- const pathLevelParams = pathItem.parameters ?? [];
1019
- for (const method of HTTP_METHODS) {
1020
- const op = pathItem[method];
1021
- if (!op) continue;
1022
- const serviceName = inferServiceName(path, op.tags?.[0]);
1023
- const { operation, inlineModels: opModels } = buildOperation(
1024
- method,
1025
- path,
1026
- op,
1027
- pathLevelParams,
1028
- operationIdTransform
1029
- );
1030
- inlineModels.push(...opModels);
1031
- const ops = serviceMap.get(serviceName) ?? [];
1032
- ops.push(operation);
1033
- serviceMap.set(serviceName, ops);
1034
- }
1035
- }
1036
- for (const [, operations] of serviceMap) {
1037
- disambiguateOperationNames(operations);
1038
- }
1039
- const services = Array.from(serviceMap.entries()).map(([name, operations]) => ({
1040
- name,
1041
- operations
1042
- }));
1043
- return { services, inlineModels };
1044
- }
1045
- function inferServiceName(path, tag) {
1046
- if (tag) {
1047
- return toPascalCase(tag);
1048
- }
1049
- const segments = path.split("/").filter(Boolean);
1050
- const first = segments[0] ?? "Default";
1051
- if (first.startsWith("{")) return "Default";
1052
- return toPascalCase(first);
1053
- }
1054
- function inferOperationName(method, path, operationId, operationIdTransform) {
1055
- if (operationId) {
1056
- if (operationIdTransform) {
1057
- return operationIdTransform(operationId);
1058
- }
1059
- return toCamelCase(operationId);
1060
- }
1061
- const verb = inferVerb(method, path);
1062
- const resource = inferResourceFromPath(path);
1063
- return resource ? toCamelCase(`${verb}_${resource}`) : verb;
1064
- }
1065
- function inferVerb(method, path) {
1066
- const segments = path.split("/").filter(Boolean);
1067
- const hasTrailingParam = segments.length > 0 && segments[segments.length - 1].startsWith("{");
1068
- switch (method) {
1069
- case "get":
1070
- return hasTrailingParam ? "get" : "list";
1071
- case "post":
1072
- return "create";
1073
- case "put":
1074
- case "patch":
1075
- return "update";
1076
- case "delete":
1077
- return "delete";
1078
- case "head":
1079
- return "check";
1080
- case "options":
1081
- return "options";
1082
- case "trace":
1083
- return "trace";
1084
- default:
1085
- return method;
1086
- }
1087
- }
1088
- function inferResourceFromPath(path) {
1089
- const segments = path.split("/").filter(Boolean);
1090
- if (segments.length <= 1) return null;
1091
- let resourceSegment = null;
1092
- for (let i = segments.length - 1; i >= 0; i--) {
1093
- if (!segments[i].startsWith("{")) {
1094
- resourceSegment = segments[i];
1095
- break;
1096
- }
1097
- }
1098
- if (!resourceSegment) return null;
1099
- if (resourceSegment === segments[0]) return null;
1100
- return toPascalCase(resourceSegment);
1101
- }
1102
- function disambiguateOperationNames(operations) {
1103
- const byName = /* @__PURE__ */ new Map();
1104
- for (const op of operations) {
1105
- const group = byName.get(op.name) ?? [];
1106
- group.push(op);
1107
- byName.set(op.name, group);
1108
- }
1109
- for (const [name, group] of byName) {
1110
- if (group.length <= 1) continue;
1111
- const uniquePaths = new Set(group.map((op) => op.path));
1112
- if (uniquePaths.size <= 1) continue;
1113
- for (const op of group) {
1114
- const resource = inferResourceFromPath(op.path);
1115
- if (resource) {
1116
- const newName = toCamelCase(`${name}_${resource}`);
1117
- op.name = newName;
1118
- }
1119
- }
1120
- const renamed = /* @__PURE__ */ new Map();
1121
- for (const op of group) {
1122
- const rGroup = renamed.get(op.name) ?? [];
1123
- rGroup.push(op);
1124
- renamed.set(op.name, rGroup);
1125
- }
1126
- for (const [, rGroup] of renamed) {
1127
- if (rGroup.length <= 1) continue;
1128
- for (const op of rGroup) {
1129
- const deeper = inferDeeperContext(op.path);
1130
- if (deeper) {
1131
- op.name = toCamelCase(`${op.name}_${deeper}`);
1132
- }
1133
- }
1134
- }
1135
- }
1136
- }
1137
- function inferDeeperContext(path) {
1138
- const segments = path.split("/").filter(Boolean);
1139
- const nonParams = segments.filter((s) => !s.startsWith("{"));
1140
- if (nonParams.length >= 3) {
1141
- return toPascalCase(nonParams.slice(1, -1).join("_"));
1142
- }
1143
- return null;
1144
- }
1145
- function buildOperation(method, path, op, pathLevelParams, operationIdTransform) {
1146
- const allParams = [...pathLevelParams, ...op.parameters ?? []];
1147
- const hasIdempotencyHeader = allParams.some((p) => p.in === "header" && p.name.toLowerCase() === "idempotency-key");
1148
- const pathParams = extractParams(allParams, "path");
1149
- const queryParams = extractParams(allParams, "query");
1150
- const headerParams = extractParams(allParams, "header").filter((p) => p.name.toLowerCase() !== "idempotency-key");
1151
- const cookieParams = extractParams(allParams, "cookie");
1152
- const reqBodyModels = [];
1153
- const { body: requestBody, encoding: requestBodyEncoding } = extractRequestBody(op.requestBody, op, reqBodyModels);
1154
- const {
1155
- response,
1156
- successResponses,
1157
- errors,
1158
- inlineModels,
1159
- isPaginated,
1160
- dataPath: responseDataPath,
1161
- itemType: responseItemType
1162
- } = extractResponses(op.responses, op, path, method);
1163
- inlineModels.push(...reqBodyModels);
1164
- const paginationFromParams = detectPagination(response, queryParams, responseDataPath);
1165
- let pagination;
1166
- if (isPaginated) {
1167
- const itemType = responseItemType ?? (response.kind === "array" ? response.items : response);
1168
- pagination = {
1169
- strategy: paginationFromParams?.strategy ?? "cursor",
1170
- param: paginationFromParams?.param ?? "after",
1171
- limitParam: paginationFromParams?.limitParam,
1172
- dataPath: responseDataPath ?? paginationFromParams?.dataPath,
1173
- itemType
1174
- };
1175
- } else if (paginationFromParams) {
1176
- pagination = paginationFromParams;
1177
- }
1178
- return {
1179
- operation: {
1180
- name: inferOperationName(method, path, op.operationId, operationIdTransform),
1181
- description: buildDescription(op.summary, op.description),
1182
- httpMethod: method,
1183
- path,
1184
- pathParams,
1185
- queryParams,
1186
- headerParams,
1187
- cookieParams: cookieParams.length > 0 ? cookieParams : void 0,
1188
- requestBody,
1189
- requestBodyEncoding,
1190
- response,
1191
- successResponses,
1192
- errors,
1193
- pagination,
1194
- injectIdempotencyKey: hasIdempotencyHeader,
1195
- deprecated: op.deprecated || void 0,
1196
- async: op["x-oagen-async"]
1197
- },
1198
- inlineModels
1199
- };
1200
- }
1201
- function buildDescription(summary, description) {
1202
- if (summary && description && summary !== description) {
1203
- return `${summary}
1204
-
1205
- ${description}`;
1206
- }
1207
- return description ?? summary;
1208
- }
1209
- function extractParams(params, location) {
1210
- return params.filter((p) => p.in === location).map((p) => ({
1211
- name: p.name,
1212
- type: p.schema ? schemaToTypeRef(p.schema, p.name) : { kind: "primitive", type: "string" },
1213
- required: p.required ?? false,
1214
- description: p.description,
1215
- deprecated: p.deprecated || p.schema?.deprecated || void 0,
1216
- default: p.schema?.default,
1217
- example: p.example ?? p.schema?.example
1218
- }));
1219
- }
1220
- function extractRequestBody(body, op, inlineModels) {
1221
- if (!body?.content) return {};
1222
- let encoding = "json";
1223
- let schema;
1224
- if (body.content["application/json"]?.schema) {
1225
- encoding = "json";
1226
- schema = body.content["application/json"].schema;
1227
- } else if (body.content["multipart/form-data"]?.schema) {
1228
- encoding = "form-data";
1229
- schema = body.content["multipart/form-data"].schema;
1230
- } else if (body.content["application/x-www-form-urlencoded"]?.schema) {
1231
- encoding = "form-urlencoded";
1232
- schema = body.content["application/x-www-form-urlencoded"].schema;
1233
- } else if (body.content["application/octet-stream"]) {
1234
- encoding = "binary";
1235
- schema = body.content["application/octet-stream"].schema;
1236
- } else if (body.content["text/plain"]) {
1237
- encoding = "text";
1238
- schema = body.content["text/plain"].schema;
1239
- }
1240
- if (!schema) {
1241
- if (encoding === "binary") {
1242
- return { body: { kind: "primitive", type: "string", format: "binary" }, encoding };
1243
- }
1244
- if (encoding === "text") {
1245
- return { body: { kind: "primitive", type: "string" }, encoding };
1246
- }
1247
- return {};
1248
- }
1249
- const rawName = op?.operationId ? toPascalCase(op.operationId) + "Request" : "RequestBody";
1250
- const contextName = cleanSchemaName(rawName);
1251
- if (schema.properties && (schema.type === "object" || !schema.type)) {
1252
- const requiredSet = new Set(schema.required ?? []);
1253
- const fields = [];
1254
- for (const [fieldName, fieldSchema] of Object.entries(schema.properties)) {
1255
- if (!fieldSchema) continue;
1256
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, contextName, requiredSet));
1257
- }
1258
- inlineModels.push({ name: contextName, description: void 0, fields });
1259
- return { body: { kind: "model", name: contextName }, encoding };
1260
- }
1261
- if (schema.oneOf) {
1262
- const variants = [];
1263
- for (const variant of schema.oneOf) {
1264
- if (variant.$ref) {
1265
- const ref = schemaToTypeRef(variant, contextName);
1266
- variants.push(ref);
1267
- } else if (variant.type === "null") {
1268
- } else if (variant.properties && (variant.type === "object" || !variant.type)) {
1269
- const variantName = deriveOneOfVariantName(variant, contextName, inlineModels);
1270
- const requiredSet = new Set(variant.required ?? []);
1271
- const fields = [];
1272
- for (const [fieldName, fieldSchema] of Object.entries(variant.properties)) {
1273
- if (!fieldSchema) continue;
1274
- fields.push(buildFieldFromSchema(fieldName, fieldSchema, variantName, requiredSet));
1275
- }
1276
- inlineModels.push({ name: variantName, description: variant.description, fields });
1277
- variants.push({ kind: "model", name: variantName });
1278
- } else {
1279
- variants.push(schemaToTypeRef(variant, contextName));
1280
- }
1281
- }
1282
- const hasNull = schema.oneOf.some(
1283
- (v) => v.type === "null" || Array.isArray(v.type) && v.type.includes("null")
1284
- );
1285
- const union = {
1286
- kind: "union",
1287
- variants,
1288
- ...schema.discriminator ? {
1289
- discriminator: { property: schema.discriminator.propertyName, mapping: schema.discriminator.mapping ?? {} }
1290
- } : {}
1291
- };
1292
- const body2 = hasNull ? { kind: "nullable", inner: union } : union;
1293
- return { body: body2, encoding };
1294
- }
1295
- return { body: schemaToTypeRef(schema, contextName), encoding };
1296
- }
1297
- function deriveOneOfVariantName(variant, baseName, existingModels) {
1298
- if (variant.properties) {
1299
- for (const [, propSchema] of Object.entries(variant.properties)) {
1300
- if (propSchema?.const && typeof propSchema.const === "string") {
1301
- return toPascalCase(propSchema.const) + baseName.replace(/Request$/, "") + "Request";
1302
- }
1303
- }
1304
- }
1305
- const existingNames = new Set(existingModels.map((m) => m.name));
1306
- let name = baseName;
1307
- let suffix = 2;
1308
- while (existingNames.has(name)) {
1309
- name = `${baseName}${suffix}`;
1310
- suffix++;
1311
- }
1312
- return name;
1313
- }
1314
- function deriveResponseName(op, path, method) {
1315
- if (op?.operationId) {
1316
- return cleanSchemaName(toPascalCase(op.operationId) + "Response");
1317
- }
1318
- const segments = path.split("/").filter(Boolean);
1319
- const resource = segments[0] ?? "Unknown";
1320
- return toPascalCase(resource) + toPascalCase(method) + "Response";
1321
- }
1322
- function extractResponses(responses, op, path, method) {
1323
- const errors = [];
1324
- let response = { kind: "primitive", type: "string" };
1325
- let inlineModels = [];
1326
- let isPaginated = false;
1327
- let dataPath;
1328
- let itemType;
1329
- if (!responses) return { response, errors, inlineModels, isPaginated };
1330
- const allSuccessResponses = [];
1331
- for (const [statusCode, resp] of Object.entries(responses)) {
1332
- const code = parseInt(statusCode, 10);
1333
- if (code >= 200 && code < 300) {
1334
- let extractedType = { kind: "primitive", type: "unknown" };
1335
- const jsonContent = resp.content?.["application/json"];
1336
- if (jsonContent?.schema) {
1337
- const contextName = deriveResponseName(op, path ?? "/", method ?? "get");
1338
- const result = classifyAndExtractResponse(jsonContent.schema, contextName);
1339
- extractedType = result.response;
1340
- inlineModels = result.inlineModels;
1341
- isPaginated = result.isPaginated;
1342
- dataPath = result.dataPath;
1343
- itemType = result.itemType;
1344
- } else if (resp.content) {
1345
- if (resp.content["application/octet-stream"] || resp.content["application/pdf"]) {
1346
- extractedType = { kind: "primitive", type: "string", format: "binary" };
1347
- } else if (resp.content["text/plain"] || resp.content["text/html"] || resp.content["text/xml"] || resp.content["application/xml"]) {
1348
- extractedType = { kind: "primitive", type: "string" };
1349
- } else {
1350
- const firstKey = Object.keys(resp.content)[0];
1351
- if (firstKey) {
1352
- const firstContent = resp.content[firstKey];
1353
- if (firstContent?.schema) {
1354
- extractedType = schemaToTypeRef(
1355
- firstContent.schema,
1356
- deriveResponseName(op, path ?? "/", method ?? "get")
1357
- );
1358
- }
1359
- }
1360
- }
1361
- }
1362
- allSuccessResponses.push({ statusCode: code, type: extractedType });
1363
- } else if (code >= 400) {
1364
- const jsonContent = resp.content?.["application/json"];
1365
- const type = jsonContent?.schema ? schemaToTypeRef(jsonContent.schema, `Error${code}`) : void 0;
1366
- errors.push({ statusCode: code, type });
1367
- }
1368
- }
1369
- if (allSuccessResponses.length > 0) {
1370
- const sorted = [...allSuccessResponses].sort((a, b) => a.statusCode - b.statusCode);
1371
- const primary = sorted.find((r) => r.type.kind !== "primitive" || r.type.type !== "unknown") ?? sorted[0];
1372
- response = primary.type;
1373
- }
1374
- const successResponses = allSuccessResponses.length > 1 ? allSuccessResponses : void 0;
1375
- return { response, successResponses, errors, inlineModels, isPaginated, dataPath, itemType };
1376
- }
1377
-
1378
- // src/parser/normalize-inline-models.ts
1379
- function keepLargestByName(models) {
1380
- const byName = /* @__PURE__ */ new Map();
1381
- for (const model of models) {
1382
- const existing = byName.get(model.name);
1383
- if (!existing || model.fields.length > existing.fields.length) {
1384
- byName.set(model.name, model);
1385
- }
1386
- }
1387
- return [...byName.values()];
1388
- }
1389
- function rewriteModelRef(ref, oldName, newName) {
1390
- walkTypeRef(ref, {
1391
- model: (r) => {
1392
- if (r.name === oldName) r.name = newName;
1393
- }
1394
- });
1395
- }
1396
- function rewriteModelRefs(models, services, oldName, newName) {
1397
- for (const model of models) {
1398
- for (const field of model.fields) {
1399
- rewriteModelRef(field.type, oldName, newName);
1400
- }
1401
- }
1402
- for (const service of services) {
1403
- for (const op of service.operations) {
1404
- rewriteModelRef(op.response, oldName, newName);
1405
- if (op.requestBody) {
1406
- rewriteModelRef(op.requestBody, oldName, newName);
1407
- }
1408
- }
1409
- }
1410
- }
1411
- function mergeInlineResponseModels(schemaModels, inlineModels) {
1412
- const mergedSchemaModels = [...schemaModels];
1413
- const schemaModelNames = new Set(mergedSchemaModels.map((m) => m.name));
1414
- const schemaModelsByName = new Map(mergedSchemaModels.map((m) => [m.name, m]));
1415
- const deduplicatedInlineModels = inlineModels.filter((model) => {
1416
- if (!schemaModelNames.has(model.name)) return true;
1417
- const existing = schemaModelsByName.get(model.name);
1418
- const existingFieldNames = new Set(existing.fields.map((f) => f.name));
1419
- const inlineFieldNames = new Set(model.fields.map((f) => f.name));
1420
- const hasDifference = model.fields.some((f) => !existingFieldNames.has(f.name)) || existing.fields.some((f) => !inlineFieldNames.has(f.name));
1421
- if (hasDifference && model.fields.length > existing.fields.length) {
1422
- const idx = mergedSchemaModels.indexOf(existing);
1423
- if (idx !== -1) mergedSchemaModels[idx] = model;
1424
- schemaModelsByName.set(model.name, model);
1425
- console.warn(
1426
- `[oagen] Warning: Inline model "${model.name}" has more fields than component schema (${model.fields.length} vs ${existing.fields.length}) \u2014 using inline response model`
1427
- );
1428
- } else if (hasDifference) {
1429
- console.warn(
1430
- `[oagen] Warning: Inline model "${model.name}" has different fields than component schema \u2014 using component schema`
1431
- );
1432
- }
1433
- return false;
1434
- });
1435
- return [...mergedSchemaModels, ...keepLargestByName(deduplicatedInlineModels)];
1436
- }
1437
- function mergeFieldInlineModels(existingModels, fieldInlineModels) {
1438
- const modelNames = new Set(existingModels.map((m) => m.name));
1439
- const modelsByName = new Map(existingModels.map((m) => [m.name, m]));
1440
- const deduplicatedFieldModels = fieldInlineModels.filter((model) => {
1441
- if (!modelNames.has(model.name)) return true;
1442
- const existing = modelsByName.get(model.name);
1443
- const existingFieldNames = new Set(existing.fields.map((f) => f.name));
1444
- const inlineFieldNames = new Set(model.fields.map((f) => f.name));
1445
- const hasDifference = model.fields.some((f) => !existingFieldNames.has(f.name)) || existing.fields.some((f) => !inlineFieldNames.has(f.name));
1446
- if (hasDifference) {
1447
- console.warn(
1448
- `[oagen] Warning: Inline model "${model.name}" has different fields than component schema \u2014 using component schema`
1449
- );
1450
- }
1451
- return false;
1452
- });
1453
- return [...existingModels, ...keepLargestByName(deduplicatedFieldModels)];
1454
- }
1455
- function hasModelConstDiscriminant(model) {
1456
- return model.fields.some(
1457
- (f) => (f.name === "object" || f.name === "type") && f.type.kind === "literal" && typeof f.type.value === "string"
1458
- );
1459
- }
1460
- function isModelReferencedByOthers(name, models, excludeNames) {
1461
- for (const model of models) {
1462
- if (excludeNames.has(model.name)) continue;
1463
- for (const field of model.fields) {
1464
- let found = false;
1465
- walkTypeRef(field.type, {
1466
- model: (r) => {
1467
- if (r.name === name) found = true;
1468
- }
1469
- });
1470
- if (found) return true;
1471
- }
1472
- }
1473
- return false;
1474
- }
1475
- function collapseJsonSuffixModels(models, services) {
1476
- const normalizedModels = [...models];
1477
- const byName = new Map(normalizedModels.map((m) => [m.name, m]));
1478
- const mergeCandidates = [];
1479
- for (const model of normalizedModels) {
1480
- if (!model.name.endsWith("Json")) continue;
1481
- const baseName = model.name.slice(0, -4);
1482
- const baseModel = byName.get(baseName);
1483
- if (!baseModel || model.fields.length <= baseModel.fields.length) continue;
1484
- const isSuperset = baseModel.fields.every((f) => model.fields.some((mf) => mf.name === f.name));
1485
- if (!isSuperset) continue;
1486
- if (hasModelConstDiscriminant(model) && hasModelConstDiscriminant(baseModel)) continue;
1487
- if (isModelReferencedByOthers(model.name, normalizedModels, /* @__PURE__ */ new Set([model.name, baseName]))) continue;
1488
- mergeCandidates.push({ jsonModel: model, baseModel });
1489
- }
1490
- const toRemove = /* @__PURE__ */ new Set();
1491
- for (const { jsonModel, baseModel } of mergeCandidates) {
1492
- baseModel.fields = jsonModel.fields;
1493
- toRemove.add(jsonModel.name);
1494
- rewriteModelRefs(normalizedModels, services, jsonModel.name, baseModel.name);
1495
- console.warn(
1496
- `[oagen] Merged "${jsonModel.name}" into "${baseModel.name}" (${jsonModel.fields.length} fields, superset)`
1497
- );
1498
- }
1499
- return normalizedModels.filter((m) => !toRemove.has(m.name));
1500
- }
1501
-
1502
- // src/parser/collect-inline-enums.ts
1503
- function collectInlineEnumsFromModels(models, enums) {
1504
- const enumNames = new Set(enums.map((e) => e.name));
1505
- for (const model of models) {
1506
- for (const field of model.fields) {
1507
- collectInlineEnumFromRef(field.type, enums, enumNames);
1508
- }
1509
- }
1510
- }
1511
-
1512
- // src/parser/normalize-model-refs.ts
1513
- function validateModelRefs(spec) {
1514
- const knownNames = /* @__PURE__ */ new Set();
1515
- for (const m of spec.models) knownNames.add(m.name);
1516
- for (const e of spec.enums) knownNames.add(e.name);
1517
- function walkRef(ref, context) {
1518
- walkTypeRef(ref, {
1519
- model: (r) => {
1520
- if (!knownNames.has(r.name)) {
1521
- console.warn(`[oagen] Warning: Unresolved model reference "${r.name}" (context: ${context})`);
1522
- }
1523
- }
1524
- });
1525
- }
1526
- for (const model of spec.models) {
1527
- for (const field of model.fields) {
1528
- walkRef(field.type, `${model.name}.${field.name}`);
1529
- }
1530
- }
1531
- for (const service of spec.services) {
1532
- for (const op of service.operations) {
1533
- for (const p of [...op.pathParams, ...op.queryParams, ...op.headerParams]) {
1534
- walkRef(p.type, `${service.name}.${op.name}.${p.name}`);
1535
- }
1536
- if (op.requestBody) walkRef(op.requestBody, `${service.name}.${op.name}.requestBody`);
1537
- walkRef(op.response, `${service.name}.${op.name}.response`);
1538
- }
1539
- }
1540
- }
1541
-
1542
- // src/parser/parse.ts
1543
- async function parseSpec(specPath, options) {
1544
- const { parsed } = await loadAndBundleSpec(specPath);
1545
- const spec = parsed;
1546
- const version = spec.openapi ?? "";
1547
- if (!version.startsWith("3.")) {
1548
- throw new SpecParseError(
1549
- `Unsupported OpenAPI version: ${version}. oagen requires OpenAPI 3.x`,
1550
- `Update the spec to OpenAPI 3.0 or 3.1. If you are using Swagger 2.x, convert it first with \`npx swagger2openapi ${specPath}\`.`
1551
- );
1552
- }
1553
- const { models, enums } = extractSchemas(
1554
- spec.components?.schemas
1555
- );
1556
- const { services, inlineModels } = extractOperations(
1557
- spec.paths,
1558
- options?.operationIdTransform
1559
- );
1560
- const responseNormalizedModels = mergeInlineResponseModels(models, inlineModels);
1561
- const fieldInlineModels = extractInlineModelsFromSchemas(
1562
- spec.components?.schemas
1563
- );
1564
- const fieldMergedModels = mergeFieldInlineModels(responseNormalizedModels, fieldInlineModels);
1565
- const finalModels = collapseJsonSuffixModels(fieldMergedModels, services);
1566
- collectInlineEnumsFromModels(finalModels, enums);
1567
- const auth = extractAuthSchemes(spec.components?.securitySchemes);
1568
- const serverEntries = (spec.servers ?? []).map((s) => ({ url: s.url ?? "", description: s.description })).filter((s) => s.url);
1569
- const result = {
1570
- name: spec.info?.title ?? "Unknown API",
1571
- version: spec.info?.version ?? "0.0.0",
1572
- description: spec.info?.description,
1573
- baseUrl: serverEntries[0]?.url ?? "",
1574
- servers: serverEntries.length > 0 ? serverEntries : void 0,
1575
- services,
1576
- models: finalModels,
1577
- enums,
1578
- auth
1579
- };
1580
- validateModelRefs(result);
1581
- return result;
1582
- }
1583
- function extractAuthSchemes(securitySchemes) {
1584
- if (!securitySchemes) return void 0;
1585
- const schemes = [];
1586
- for (const [, scheme] of Object.entries(securitySchemes)) {
1587
- if (scheme.type === "http" && scheme.scheme === "bearer") {
1588
- schemes.push({ kind: "bearer" });
1589
- } else if (scheme.type === "apiKey" && scheme.in && scheme.name) {
1590
- schemes.push({ kind: "apiKey", in: scheme.in, name: scheme.name });
1591
- } else if (scheme.type === "oauth2" && scheme.flows) {
1592
- schemes.push({ kind: "oauth2", flows: scheme.flows });
1593
- }
1594
- }
1595
- return schemes.length > 0 ? schemes : void 0;
1596
- }
1597
-
1598
- // scripts/smoke/shared.ts
1599
- var SKIP_OPERATIONS = /* @__PURE__ */ new Set();
1600
- var SKIP_SERVICES = /* @__PURE__ */ new Set();
1601
- var SERVICE_PRIORITY = {};
1602
- var SERVICE_PROPERTY_MAP = {};
1603
- function loadSmokeConfig(configPath) {
1604
- if (!configPath) return;
1605
- const raw = readFileSync(configPath, "utf-8");
1606
- const config = JSON.parse(raw);
1607
- if (config.skipOperations) SKIP_OPERATIONS = new Set(config.skipOperations);
1608
- if (config.skipServices) SKIP_SERVICES = new Set(config.skipServices);
1609
- if (config.servicePriority) SERVICE_PRIORITY = config.servicePriority;
1610
- if (config.servicePropertyMap) SERVICE_PROPERTY_MAP = config.servicePropertyMap;
1611
- }
1612
- function getSmokeConfig() {
1613
- return {
1614
- skipOperations: SKIP_OPERATIONS,
1615
- skipServices: SKIP_SERVICES,
1616
- servicePriority: SERVICE_PRIORITY,
1617
- servicePropertyMap: SERVICE_PROPERTY_MAP
1618
- };
1619
- }
1620
- function operationSortKey(op) {
1621
- const hasParams = op.pathParams.length > 0;
1622
- const method = op.httpMethod;
1623
- if (method === "post" && !hasParams) return 0;
1624
- if (method === "get" && !hasParams && op.pagination) return 1;
1625
- if (method === "get" && !hasParams) return 2;
1626
- if (method === "post" && hasParams) return 3;
1627
- if (method === "get" && hasParams && op.pagination) return 4;
1628
- if (method === "get" && hasParams) return 5;
1629
- if (method === "put" || method === "patch") return 6;
1630
- if (method === "delete") return 7;
1631
- return 99;
1632
- }
1633
- function planOperations(spec) {
1634
- const groups = [];
1635
- for (const service of spec.services) {
1636
- if (SKIP_SERVICES.has(service.name)) continue;
1637
- const ops = [];
1638
- for (const op of service.operations) {
1639
- if (SKIP_OPERATIONS.has(op.name)) continue;
1640
- ops.push({ service: service.name, operation: op });
1641
- }
1642
- if (ops.length === 0) continue;
1643
- ops.sort((a, b) => {
1644
- return operationSortKey(a.operation) - operationSortKey(b.operation);
1645
- });
1646
- groups.push({ service: service.name, operations: ops });
1647
- }
1648
- groups.sort((a, b) => {
1649
- const aPri = SERVICE_PRIORITY[a.service] ?? 50;
1650
- const bPri = SERVICE_PRIORITY[b.service] ?? 50;
1651
- return aPri - bPri;
1652
- });
1653
- return groups;
1654
- }
1655
- function* planWaves(groups, ids, resolveMethod) {
1656
- const remaining = [];
1657
- for (const group of groups) {
1658
- for (const planned of group.operations) {
1659
- remaining.push(planned);
1660
- }
1661
- }
1662
- const emitted = /* @__PURE__ */ new Set();
1663
- const skipped = [];
1664
- for (let i = 0; i < remaining.length; i++) {
1665
- const { operation: op, service: irService } = remaining[i];
1666
- if (resolveMethod && !resolveMethod(op, irService)) {
1667
- emitted.add(i);
1668
- skipped.push(remaining[i]);
1669
- }
1670
- }
1671
- let wave = { calls: [] };
1672
- for (let i = 0; i < remaining.length; i++) {
1673
- if (emitted.has(i)) continue;
1674
- const { operation: op, service: irService } = remaining[i];
1675
- if (op.pathParams.length === 0) {
1676
- wave.calls.push({ op, irService, pathParams: {} });
1677
- emitted.add(i);
1678
- }
1679
- }
1680
- if (wave.calls.length > 0) {
1681
- yield wave;
1682
- }
1683
- let madeProgress = true;
1684
- while (madeProgress) {
1685
- madeProgress = false;
1686
- wave = { calls: [] };
1687
- for (let i = 0; i < remaining.length; i++) {
1688
- if (emitted.has(i)) continue;
1689
- const { operation: op, service: irService } = remaining[i];
1690
- const resolved = ids.resolvePathParams(op, irService);
1691
- if (resolved) {
1692
- wave.calls.push({ op, irService, pathParams: resolved });
1693
- emitted.add(i);
1694
- madeProgress = true;
1695
- }
1696
- }
1697
- if (wave.calls.length > 0) {
1698
- yield wave;
1699
- }
1700
- }
1701
- const unresolved = [];
1702
- for (let i = 0; i < remaining.length; i++) {
1703
- if (!emitted.has(i)) {
1704
- unresolved.push(remaining[i]);
1705
- }
1706
- }
1707
- return unresolved;
1708
- }
1709
- function generateFixtureValue(typeRef, fieldName, spec) {
1710
- switch (typeRef.kind) {
1711
- case "primitive":
1712
- return generatePrimitiveFixture(typeRef.type, typeRef.format, fieldName);
1713
- case "array":
1714
- return [generateFixtureValue(typeRef.items, fieldName, spec)];
1715
- case "model": {
1716
- const model = spec.models.find((m) => m.name === typeRef.name);
1717
- if (model) {
1718
- const obj = {};
1719
- for (const field of model.fields) {
1720
- if (field.required) {
1721
- obj[toSnakeCase(field.name)] = generateFixtureValue(field.type, field.name, spec);
1722
- }
1723
- }
1724
- return obj;
1725
- }
1726
- return {};
1727
- }
1728
- case "enum": {
1729
- const e = spec.enums.find((en) => en.name === typeRef.name);
1730
- return e?.values[0]?.value ?? "unknown";
1731
- }
1732
- case "nullable":
1733
- return generateFixtureValue(typeRef.inner, fieldName, spec);
1734
- case "union":
1735
- if (typeRef.variants.length > 0) {
1736
- return generateFixtureValue(typeRef.variants[0], fieldName, spec);
1737
- }
1738
- return null;
1739
- }
1740
- }
1741
- function generatePrimitiveFixture(type, format, fieldName) {
1742
- if (type === "string") {
1743
- if (format === "uuid") return "550e8400-e29b-41d4-a716-446655440000";
1744
- if (format === "date") return "2024-01-01";
1745
- if (format === "date-time") return "2024-01-01T00:00:00Z";
1746
- if (format === "email") return "test@example.com";
1747
- if (format === "uri" || format === "url") return "https://example.com";
1748
- return `test_${toSnakeCase(fieldName)}`;
1749
- }
1750
- if (type === "integer") return 1;
1751
- if (type === "number") return 1;
1752
- if (type === "boolean") return true;
1753
- return null;
1754
- }
1755
- function generatePayload(op, spec) {
1756
- if (!op.requestBody) return null;
1757
- const bodyType = op.requestBody;
1758
- if (bodyType.kind === "model") {
1759
- const model = spec.models.find((m) => m.name === bodyType.name);
1760
- if (!model) return {};
1761
- const payload = {};
1762
- for (const field of model.fields) {
1763
- if (!field.required) continue;
1764
- const key = toSnakeCase(field.name);
1765
- let value = generateFixtureValue(field.type, field.name, spec);
1766
- if ((key === "name" || key === "slug") && typeof value === "string") {
1767
- value = `smoke-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1768
- }
1769
- payload[key] = value;
1770
- }
1771
- return payload;
1772
- }
1773
- return {};
1774
- }
1775
- function generateSDKFixtureValue(typeRef, fieldName, spec) {
1776
- const value = generateFixtureValue(typeRef, fieldName, spec);
1777
- if (typeRef.kind === "primitive" && typeRef.format === "date-time" && typeof value === "string") {
1778
- return new Date(value);
1779
- }
1780
- return value;
1781
- }
1782
- function generateCamelPayload(op, spec) {
1783
- if (!op.requestBody) return null;
1784
- const bodyType = op.requestBody;
1785
- if (bodyType.kind === "model") {
1786
- const model = spec.models.find((m) => m.name === bodyType.name);
1787
- if (!model) return {};
1788
- const payload = {};
1789
- for (const field of model.fields) {
1790
- if (!field.required) continue;
1791
- const key = toCamelCase(field.name);
1792
- let value = generateSDKFixtureValue(field.type, field.name, spec);
1793
- if ((field.name === "name" || field.name === "slug") && typeof value === "string") {
1794
- value = `smoke-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1795
- }
1796
- payload[key] = value;
1797
- }
1798
- return payload;
1799
- }
1800
- return {};
1801
- }
1802
- function generateQueryParams(op, spec) {
1803
- const params = {};
1804
- if (op.pagination) {
1805
- params["limit"] = "1";
1806
- }
1807
- for (const p of op.queryParams) {
1808
- if (!p.required) continue;
1809
- const value = generateFixtureValue(p.type, p.name, spec);
1810
- params[toSnakeCase(p.name)] = String(value);
1811
- }
1812
- const sorted = {};
1813
- for (const key of Object.keys(params).sort()) {
1814
- sorted[key] = params[key];
1815
- }
1816
- return sorted;
1817
- }
1818
- function generateCamelQueryParams(op, spec) {
1819
- const params = {};
1820
- if (op.pagination) {
1821
- params["limit"] = 1;
1822
- }
1823
- for (const p of op.queryParams) {
1824
- if (!p.required) continue;
1825
- const value = generateFixtureValue(p.type, p.name, spec);
1826
- params[toCamelCase(p.name)] = value;
1827
- }
1828
- return params;
1829
- }
1830
- var IdRegistry = class {
1831
- ids = /* @__PURE__ */ new Map();
1832
- set(service, field, value) {
1833
- this.ids.set(`${service}.${field}`, value);
1834
- }
1835
- get(service, field) {
1836
- return this.ids.get(`${service}.${field}`);
1837
- }
1838
- /** Resolve path parameters for an operation using stored IDs */
1839
- resolvePathParams(op, service) {
1840
- const resolved = {};
1841
- for (const p of op.pathParams) {
1842
- const value = this.get(service, p.name) || this.get(service, "id") || this.findAcrossServices(p.name) || this.resolveFromParamName(p.name) || this.resolveFromExample(p);
1843
- if (!value) return null;
1844
- resolved[p.name] = value;
1845
- }
1846
- return resolved;
1847
- }
1848
- /** Try to find a param value across all services */
1849
- findAcrossServices(field) {
1850
- for (const [key, value] of this.ids.entries()) {
1851
- if (key.endsWith(`.${field}`)) return value;
1852
- }
1853
- const snakeField = toSnakeCase(field);
1854
- if (snakeField !== field) {
1855
- for (const [key, value] of this.ids.entries()) {
1856
- if (key.endsWith(`.${snakeField}`)) return value;
1857
- }
1858
- }
1859
- return void 0;
1860
- }
1861
- /**
1862
- * Resolve a path param like "organizationId" by mapping it to the
1863
- * service that stores that entity's ID (e.g. Organizations.id).
1864
- * Also handles snake_case suffixed with _id by stripping the suffix
1865
- * and looking up the pluralized service name.
1866
- */
1867
- resolveFromParamName(paramName) {
1868
- if (paramName.endsWith("_id")) {
1869
- const base = paramName.slice(0, -3);
1870
- const pascal = base.split("_").map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join("");
1871
- const plural = pascal.endsWith("s") ? pascal : pascal + "s";
1872
- const val = this.get(plural, "id") || this.get(plural, "slug");
1873
- if (val) return val;
1874
- const suffixVal = this.findByServiceSuffix(plural, "id") || this.findByServiceSuffix(plural, "slug");
1875
- if (suffixVal) return suffixVal;
1876
- }
1877
- if (paramName.endsWith("Id") && paramName.length > 2) {
1878
- const base = paramName.slice(0, -2);
1879
- const pascal = base.charAt(0).toUpperCase() + base.slice(1);
1880
- const plural = pascal.endsWith("s") ? pascal : pascal + "s";
1881
- const val = this.get(plural, "id") || this.get(plural, "slug");
1882
- if (val) return val;
1883
- const suffixVal = this.findByServiceSuffix(plural, "id") || this.findByServiceSuffix(plural, "slug");
1884
- if (suffixVal) return suffixVal;
1885
- }
1886
- return void 0;
1887
- }
1888
- /**
1889
- * Find a value by looking for any service name that ends with the given suffix.
1890
- * e.g. suffix "Users" matches "UserManagementUsers.id"
1891
- */
1892
- findByServiceSuffix(suffix, field) {
1893
- for (const [key, value] of this.ids.entries()) {
1894
- if (key.endsWith(`.${field}`)) {
1895
- const servicePart = key.slice(0, -(field.length + 1));
1896
- if (servicePart.endsWith(suffix)) {
1897
- return value;
1898
- }
1899
- }
1900
- }
1901
- return void 0;
1902
- }
1903
- /**
1904
- * Last-resort fallback: use the spec's example or default value for a parameter.
1905
- * Many path params (e.g. actionName, slug, type) have example values in the spec.
1906
- */
1907
- resolveFromExample(param) {
1908
- const value = param.example ?? param.default;
1909
- if (value !== void 0 && value !== null) {
1910
- return String(value);
1911
- }
1912
- return void 0;
1913
- }
1914
- /**
1915
- * Extract IDs from a response body and store them.
1916
- * @param isTopLevel - true for operations without path params (their `id` belongs to this service).
1917
- * false for sub-resource operations (their `id` is a child entity, don't overwrite).
1918
- */
1919
- extractAndStore(service, responseBody, isTopLevel = true) {
1920
- if (!responseBody || typeof responseBody !== "object") return;
1921
- const body = responseBody;
1922
- if (isTopLevel) {
1923
- if (typeof body.id === "string") {
1924
- this.set(service, "id", body.id);
1925
- }
1926
- if (Array.isArray(body.data) && body.data.length > 0) {
1927
- const first = body.data[0];
1928
- if (first && typeof first === "object") {
1929
- if (typeof first.id === "string") {
1930
- this.set(service, "id", first.id);
1931
- }
1932
- this.extractNestedIds(service, first);
1933
- }
1934
- }
1935
- if (typeof body.slug === "string") this.set(service, "slug", body.slug);
1936
- if (typeof body.key === "string") this.set(service, "key", body.key);
1937
- }
1938
- this.extractNestedIds(service, body);
1939
- if (Array.isArray(body.data) && body.data.length > 0) {
1940
- const first = body.data[0];
1941
- if (first && typeof first === "object") {
1942
- this.extractNestedIds(service, first);
1943
- }
1944
- }
1945
- }
1946
- /** Extract fields ending in _id or Id and store them for cross-service resolution */
1947
- extractNestedIds(service, obj) {
1948
- for (const [key, value] of Object.entries(obj)) {
1949
- if (typeof value !== "string") continue;
1950
- if (key === "id") continue;
1951
- if (key.endsWith("_id") || key.endsWith("Id") && key.length > 2) {
1952
- this.set(service, key, value);
1953
- }
1954
- }
1955
- }
1956
- };
1957
- function getExpectedStatusCodes(op) {
1958
- const codes = [];
1959
- const defaultSuccess = {
1960
- post: 201,
1961
- get: 200,
1962
- put: 200,
1963
- patch: 200,
1964
- delete: 204
1965
- };
1966
- codes.push(defaultSuccess[op.httpMethod] ?? 200);
1967
- for (const err of op.errors) {
1968
- if (!codes.includes(err.statusCode)) {
1969
- codes.push(err.statusCode);
1970
- }
1971
- }
1972
- return codes.sort((a, b) => a - b);
1973
- }
1974
- function isUnexpectedStatus(status, op) {
1975
- if (status === 0) return false;
1976
- const expected = getExpectedStatusCodes(op);
1977
- return !expected.includes(status);
1978
- }
1979
- function resolvePath(op, pathParams) {
1980
- let path = op.path;
1981
- for (const [name, value] of Object.entries(pathParams)) {
1982
- path = path.replace(`{${name}}`, value);
1983
- }
1984
- return path;
1985
- }
1986
- function delay(ms) {
1987
- return new Promise((resolve2) => setTimeout(resolve2, ms));
1988
- }
1989
- function parseCliArgs() {
1990
- const args = process.argv.slice(2);
1991
- const specIdx = args.indexOf("--spec");
1992
- const spec = specIdx !== -1 && args[specIdx + 1] ? args[specIdx + 1] : process.env.OPENAPI_SPEC_PATH;
1993
- if (!spec) {
1994
- console.error("OpenAPI spec path is required. Set OPENAPI_SPEC_PATH env var or pass --spec <path>.");
1995
- process.exit(1);
1996
- }
1997
- const sdkPathIdx = args.indexOf("--sdk-path");
1998
- const sdkPath = sdkPathIdx !== -1 && args[sdkPathIdx + 1] ? args[sdkPathIdx + 1] : void 0;
1999
- const configIdx = args.indexOf("--smoke-config");
2000
- const smokeConfig = configIdx !== -1 && args[configIdx + 1] ? args[configIdx + 1] : process.env.SMOKE_CONFIG;
2001
- return { spec, sdkPath, smokeConfig };
2002
- }
2003
- export {
2004
- IdRegistry,
2005
- SERVICE_PROPERTY_MAP,
2006
- SKIP_OPERATIONS,
2007
- SKIP_SERVICES,
2008
- delay,
2009
- generateCamelPayload,
2010
- generateCamelQueryParams,
2011
- generateFixtureValue,
2012
- generatePayload,
2013
- generateQueryParams,
2014
- getExpectedStatusCodes,
2015
- getSmokeConfig,
2016
- isUnexpectedStatus,
2017
- loadSmokeConfig,
2018
- parseCliArgs,
2019
- parseSpec,
2020
- planOperations,
2021
- planWaves,
2022
- resolvePath,
2023
- toCamelCase,
2024
- toSnakeCase
2025
- };