@workos/oagen 0.3.0 → 0.4.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.
@@ -310,11 +310,7 @@ function toKebabCase(s) {
310
310
  function toUpperSnakeCase(s) {
311
311
  return splitWords(s).map((w) => w.toUpperCase()).join("_");
312
312
  }
313
- const BACKEND_SUFFIXES = [
314
- "Dto",
315
- "DTO",
316
- "Controller"
317
- ];
313
+ const BACKEND_SUFFIXES = ["Controller"];
318
314
  function stripBackendSuffixes(name) {
319
315
  for (const suffix of BACKEND_SUFFIXES) if (name.endsWith(suffix) && name.length > suffix.length) return name.slice(0, -suffix.length);
320
316
  return name;
@@ -341,7 +337,9 @@ const SINGULAR_SAFE_LIST = new Set([
341
337
  "Canvas",
342
338
  "Alias",
343
339
  "Basis",
344
- "Bus"
340
+ "Bus",
341
+ "Species",
342
+ "Series"
345
343
  ]);
346
344
  /**
347
345
  * Conservative singularize for the leading PascalCase word.
@@ -391,4 +389,4 @@ function safeParse(parser, source) {
391
389
  //#endregion
392
390
  export { walkTypeRef as A, assertNever as C, collectModelRefs as D, collectFieldDependencies as E, collectRequestBodyModels as O, SpecParseError as S, collectEnumRefs as T, ConfigVersionMismatchError as _, singularize as a, OagenError as b, stripListItemMarkers as c, toPascalCase as d, toSnakeCase as f, ConfigLoadError as g, ConfigError as h, normalizeJsExtension as i, mapTypeRef as k, toCamelCase as l, CommandError as m, ACRONYM_SET as n, splitWords as o, toUpperSnakeCase as p, cleanSchemaName as r, stripBackendPrefixes as s, safeParse as t, toKebabCase as u, ExtractorError as v, assignModelsToServices as w, RegistryError as x, InternalError as y };
393
391
 
394
- //# sourceMappingURL=tree-sitter-DFRXfWg9.mjs.map
392
+ //# sourceMappingURL=tree-sitter-C53w_0WE.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tree-sitter-C53w_0WE.mjs","names":[],"sources":["../src/ir/types.ts","../src/errors.ts","../src/utils/naming.ts","../src/utils/tree-sitter.ts"],"sourcesContent":["import type { SdkBehavior } from './sdk-behavior.js';\n\n/** Authentication scheme extracted from OpenAPI securitySchemes */\nexport type AuthScheme =\n | { kind: 'bearer' }\n | { kind: 'apiKey'; in: 'header' | 'query' | 'cookie'; name: string }\n | { kind: 'oauth2'; flows: Record<string, unknown> };\n\n/** Root IR node representing the full API surface */\n/** A server entry from the OpenAPI servers array */\nexport interface ServerEntry {\n url: string;\n description?: string;\n}\n\n/** Root IR node representing the full API surface */\nexport interface ApiSpec {\n name: string;\n version: string;\n description?: string;\n baseUrl: string;\n servers?: ServerEntry[];\n services: Service[];\n models: Model[];\n enums: Enum[];\n auth?: AuthScheme[];\n /** Language-agnostic runtime policies (retry, errors, telemetry, etc.). */\n sdk: SdkBehavior;\n}\n\n/** A service groups related operations (maps to an SDK resource class) */\nexport interface Service {\n name: string;\n description?: string;\n operations: Operation[];\n}\n\n/** A single API operation (maps to an SDK method) */\n/** Per-operation security requirement: scheme name → scope list. */\nexport interface SecurityRequirement {\n schemeName: string;\n scopes: string[];\n}\n\nexport interface Operation {\n name: string;\n description?: string;\n httpMethod: HttpMethod;\n path: string;\n pathParams: Parameter[];\n queryParams: Parameter[];\n headerParams: Parameter[];\n cookieParams?: Parameter[];\n requestBody?: TypeRef;\n requestBodyEncoding?: 'json' | 'form-data' | 'form-urlencoded' | 'binary' | 'text';\n response: TypeRef;\n successResponses?: SuccessResponse[];\n errors: ErrorResponse[];\n pagination?: PaginationMeta;\n injectIdempotencyKey: boolean;\n deprecated?: boolean;\n async?: boolean;\n /** Per-operation security overrides. When present, overrides the global spec-level security. */\n security?: SecurityRequirement[];\n}\n\n/** Structured pagination metadata for auto-paging iterator generation */\nexport interface PaginationMeta {\n strategy: 'cursor' | 'offset' | 'link-header';\n param: string;\n limitParam?: string;\n dataPath?: string;\n itemType: TypeRef;\n}\n\nexport type HttpMethod = 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head' | 'options' | 'trace';\n\nexport interface Parameter {\n name: string;\n type: TypeRef;\n required: boolean;\n description?: string;\n deprecated?: boolean;\n default?: unknown;\n example?: unknown;\n}\n\n/** Type reference — the core type system of the IR */\nexport type TypeRef = PrimitiveType | ArrayType | ModelRef | EnumRef | UnionType | NullableType | LiteralType | MapType;\n\nexport interface PrimitiveType {\n kind: 'primitive';\n type: 'string' | 'integer' | 'number' | 'boolean' | 'unknown';\n format?: string;\n}\n\nexport interface ArrayType {\n kind: 'array';\n items: TypeRef;\n}\n\nexport interface ModelRef {\n kind: 'model';\n name: string;\n}\n\nexport interface EnumRef {\n kind: 'enum';\n name: string;\n values?: (string | number)[];\n}\n\nexport interface LiteralType {\n kind: 'literal';\n value: string | number | boolean | null;\n}\n\nexport interface UnionType {\n kind: 'union';\n variants: TypeRef[];\n discriminator?: { property: string; mapping: Record<string, string> };\n /** Which OAS composition keyword produced this union. Emitters can use this to\n * distinguish inheritance (allOf) from exclusive union (oneOf) from open union (anyOf). */\n compositionKind?: 'allOf' | 'oneOf' | 'anyOf';\n}\n\nexport interface NullableType {\n kind: 'nullable';\n inner: TypeRef;\n}\n\nexport interface MapType {\n kind: 'map';\n valueType: TypeRef;\n keyType?: TypeRef;\n}\n\n/**\n * A generic type parameter on a model.\n * Example: `DirectoryUser<TCustomAttributes = Record<string, unknown>>`\n * → `{ name: 'TCustomAttributes', default: { kind: 'map', valueType: { kind: 'primitive', type: 'unknown' } } }`\n */\nexport interface TypeParam {\n name: string;\n /** Default type when the param is not specified. */\n default?: TypeRef;\n}\n\n/** Model definition (maps to an SDK model/data class) */\nexport interface Model {\n name: string;\n description?: string;\n fields: Field[];\n /** Generic type parameters. Empty/undefined for non-generic models. */\n typeParams?: TypeParam[];\n}\n\nexport interface Field {\n name: string;\n type: TypeRef;\n required: boolean;\n description?: string;\n readOnly?: boolean;\n writeOnly?: boolean;\n deprecated?: boolean;\n default?: unknown;\n example?: unknown;\n}\n\n/** Enum definition */\nexport interface Enum {\n name: string;\n values: EnumValue[];\n}\n\nexport interface EnumValue {\n name: string;\n value: string | number;\n description?: string;\n deprecated?: boolean;\n}\n\n/**\n * Exhaustive check helper for TypeRef switches.\n * If a new kind is added to TypeRef, any switch that doesn't handle it\n * will fail to compile because `never` won't accept the unhandled variant.\n *\n * Usage:\n * switch (ref.kind) {\n * case 'primitive': ...\n * case 'array': ...\n * // If you forget 'literal', TypeScript errors here:\n * default: assertNever(ref);\n * }\n */\nexport function assertNever(x: never): never {\n // Inline to avoid importing from ../errors.js (ir/ is layer 0, must not import upward)\n const kind = (x as TypeRef).kind;\n const err = new Error(\n `Unexpected TypeRef kind: ${kind}\\nHint: If you added a new TypeRef variant, handle it in every switch/case that calls assertNever.`,\n );\n err.name = 'InternalError';\n throw err;\n}\n\n/**\n * Generic depth-first walker for TypeRef trees.\n * Handles recursion into array/nullable/union/map children and provides\n * an exhaustive `assertNever` check so callers don't need to repeat the\n * switch boilerplate. Supply callbacks only for the leaf kinds you care about.\n */\nexport function walkTypeRef(\n ref: TypeRef,\n visitor: {\n model?: (ref: ModelRef) => void;\n enum?: (ref: EnumRef) => void;\n primitive?: (ref: PrimitiveType) => void;\n literal?: (ref: LiteralType) => void;\n },\n): void {\n switch (ref.kind) {\n case 'model':\n visitor.model?.(ref);\n break;\n case 'enum':\n visitor.enum?.(ref);\n break;\n case 'array':\n walkTypeRef(ref.items, visitor);\n break;\n case 'nullable':\n walkTypeRef(ref.inner, visitor);\n break;\n case 'union':\n for (const v of ref.variants) walkTypeRef(v, visitor);\n break;\n case 'map':\n if (ref.keyType) walkTypeRef(ref.keyType, visitor);\n walkTypeRef(ref.valueType, visitor);\n break;\n case 'literal':\n visitor.literal?.(ref);\n break;\n case 'primitive':\n visitor.primitive?.(ref);\n break;\n default:\n assertNever(ref);\n }\n}\n\n/**\n * Generic depth-first mapper for TypeRef trees.\n * Like walkTypeRef but returns a transformed value instead of void.\n * Handles recursion into array/nullable/union/map children, passing\n * already-mapped child values to the parent callback.\n */\nexport function mapTypeRef<T>(\n ref: TypeRef,\n mapper: {\n primitive: (ref: PrimitiveType) => T;\n array: (ref: ArrayType, mappedItems: T) => T;\n model: (ref: ModelRef) => T;\n enum: (ref: EnumRef) => T;\n union: (ref: UnionType, mappedVariants: T[]) => T;\n nullable: (ref: NullableType, mappedInner: T) => T;\n literal: (ref: LiteralType) => T;\n map: (ref: MapType, mappedValue: T, mappedKey?: T) => T;\n },\n): T {\n switch (ref.kind) {\n case 'primitive':\n return mapper.primitive(ref);\n case 'array':\n return mapper.array(ref, mapTypeRef(ref.items, mapper));\n case 'model':\n return mapper.model(ref);\n case 'enum':\n return mapper.enum(ref);\n case 'union':\n return mapper.union(\n ref,\n ref.variants.map((v) => mapTypeRef(v, mapper)),\n );\n case 'nullable':\n return mapper.nullable(ref, mapTypeRef(ref.inner, mapper));\n case 'literal':\n return mapper.literal(ref);\n case 'map':\n return mapper.map(\n ref,\n mapTypeRef(ref.valueType, mapper),\n ref.keyType ? mapTypeRef(ref.keyType, mapper) : undefined,\n );\n default:\n return assertNever(ref);\n }\n}\n\n// --- IR traversal utilities ---\n\n/**\n * Collect all model names referenced (directly or transitively) by a TypeRef.\n */\nexport function collectModelRefs(ref: TypeRef): string[] {\n const names: string[] = [];\n walkTypeRef(ref, { model: (r) => names.push(r.name) });\n return names;\n}\n\n/**\n * Collect all enum names referenced by a TypeRef.\n */\nexport function collectEnumRefs(ref: TypeRef): string[] {\n const names: string[] = [];\n walkTypeRef(ref, { enum: (r) => names.push(r.name) });\n return names;\n}\n\n/**\n * Collect all TypeRef-referenced model and enum names from a model's fields.\n * Returns { models, enums } sets for generating import statements.\n */\nexport function collectFieldDependencies(model: Model): {\n models: Set<string>;\n enums: Set<string>;\n} {\n const models = new Set<string>();\n const enums = new Set<string>();\n\n for (const field of model.fields) {\n for (const name of collectModelRefs(field.type)) {\n if (name !== model.name) models.add(name);\n }\n for (const name of collectEnumRefs(field.type)) {\n enums.add(name);\n }\n }\n\n return { models, enums };\n}\n\n/**\n * Assign each model to the service that first references it.\n * Models referenced by multiple services are assigned to the first.\n * Models not referenced by any service are unassigned (absent from the map).\n */\nexport function assignModelsToServices(models: Model[], services: Service[]): Map<string, string> {\n const modelToService = new Map<string, string>();\n const modelNames = new Set(models.map((m) => m.name));\n\n for (const service of services) {\n const referencedModels = new Set<string>();\n\n for (const op of service.operations) {\n if (op.requestBody) {\n for (const name of collectModelRefs(op.requestBody)) {\n referencedModels.add(name);\n }\n }\n for (const name of collectModelRefs(op.response)) {\n referencedModels.add(name);\n }\n for (const param of [...op.pathParams, ...op.queryParams, ...op.headerParams, ...(op.cookieParams ?? [])]) {\n for (const name of collectModelRefs(param.type)) {\n referencedModels.add(name);\n }\n }\n if (op.pagination) {\n for (const name of collectModelRefs(op.pagination.itemType)) {\n referencedModels.add(name);\n }\n }\n }\n\n // Transitively collect models referenced by the directly-referenced models\n const toVisit = [...referencedModels];\n while (toVisit.length > 0) {\n const name = toVisit.pop()!;\n const model = models.find((m) => m.name === name);\n if (!model) continue;\n for (const field of model.fields) {\n for (const ref of collectModelRefs(field.type)) {\n if (!referencedModels.has(ref) && modelNames.has(ref)) {\n referencedModels.add(ref);\n toVisit.push(ref);\n }\n }\n }\n }\n\n for (const name of referencedModels) {\n if (!modelToService.has(name)) {\n modelToService.set(name, service.name);\n }\n }\n }\n\n return modelToService;\n}\n\n/**\n * Collect all model names referenced as request bodies across all services.\n */\nexport function collectRequestBodyModels(services: Service[]): Set<string> {\n const result = new Set<string>();\n for (const service of services) {\n for (const op of service.operations) {\n if (op.requestBody) {\n for (const name of collectModelRefs(op.requestBody)) {\n result.add(name);\n }\n }\n }\n }\n return result;\n}\n\nexport interface ErrorResponse {\n statusCode: number;\n type?: TypeRef;\n}\n\n/** A successful response entry from a 2xx status code */\nexport interface SuccessResponse {\n statusCode: number;\n type: TypeRef;\n}\n","/**\n * Structured error hierarchy for oagen.\n *\n * Every error carries a human-readable `hint` that suggests a recovery action.\n * The hint is appended to the `message` so it appears in stack traces and logs.\n */\n\n/** Base class for all oagen errors. */\nexport class OagenError extends Error {\n readonly hint: string;\n\n constructor(message: string, hint: string) {\n const fullMessage = hint ? `${message}\\nHint: ${hint}` : message;\n super(fullMessage);\n this.name = 'OagenError';\n this.hint = hint;\n }\n}\n\n/** Thrown when a command should terminate with a specific exit code. */\nexport class CommandError extends OagenError {\n readonly exitCode: number;\n\n constructor(message: string, hint: string, exitCode: number) {\n super(message, hint);\n this.name = 'CommandError';\n this.exitCode = exitCode;\n }\n}\n\n/** Thrown when an OpenAPI spec cannot be parsed or has an unsupported version. */\nexport class SpecParseError extends OagenError {\n constructor(message: string, hint: string) {\n super(message, hint);\n this.name = 'SpecParseError';\n }\n}\n\n/** Thrown when configuration files (manifests, API surfaces, overlays) are missing or malformed. */\nexport class ConfigError extends OagenError {\n constructor(message: string, hint: string) {\n super(message, hint);\n this.name = 'ConfigError';\n }\n}\n\n/** Thrown when a config file exists but cannot be loaded or evaluated. */\nexport class ConfigLoadError extends ConfigError {\n constructor(message: string, hint: string) {\n super(message, hint);\n this.name = 'ConfigLoadError';\n }\n}\n\n/** Thrown when a config targets a different IR version than the installed package. */\nexport class ConfigVersionMismatchError extends ConfigError {\n constructor(message: string, hint: string) {\n super(message, hint);\n this.name = 'ConfigVersionMismatchError';\n }\n}\n\n/** Thrown when an API surface extractor encounters a problem. */\nexport class ExtractorError extends OagenError {\n constructor(message: string, hint: string) {\n super(message, hint);\n this.name = 'ExtractorError';\n }\n}\n\n/** Thrown when a requested language or extractor is not found in a registry. */\nexport class RegistryError extends OagenError {\n constructor(message: string, hint: string) {\n super(message, hint);\n this.name = 'RegistryError';\n }\n}\n\n/** Thrown for internal invariant violations that indicate a bug. */\nexport class InternalError extends OagenError {\n constructor(message: string, hint: string) {\n super(message, hint);\n this.name = 'InternalError';\n }\n}\n","/**\n * Known compound tokens that the regex-based splitter over-splits.\n * Each entry is [lowercase-word-sequence, canonical-form].\n * Sorted longest-first so greedy matching works correctly.\n */\nconst COMPOUND_WORDS: [string[], string][] = [\n [['m', '2', 'm'], 'M2M'],\n [['o', 'auth'], 'OAuth'],\n];\n\n/**\n * Recombine adjacent words that form a known compound token.\n */\nfunction recombineCompounds(words: string[]): string[] {\n const result: string[] = [];\n let i = 0;\n while (i < words.length) {\n let matched = false;\n for (const [pattern, canonical] of COMPOUND_WORDS) {\n if (i + pattern.length <= words.length) {\n const matches = pattern.every((p, j) => words[i + j].toLowerCase() === p);\n if (matches) {\n result.push(canonical);\n i += pattern.length;\n matched = true;\n break;\n }\n }\n }\n if (!matched) {\n result.push(words[i]);\n i++;\n }\n }\n return result;\n}\n\n/**\n * Split a string into words, handling:\n * - camelCase / PascalCase boundaries\n * - snake_case / kebab-case separators\n * - Consecutive capitals (e.g., \"HTTPClient\" → [\"HTTP\", \"Client\"])\n * - Numbers as word boundaries (e.g., \"OAuth2Token\" → [\"OAuth\", \"2\", \"Token\"])\n * - Known compounds are recombined (e.g., \"M2M\" stays as one word)\n */\nexport function splitWords(s: string): string[] {\n if (!s) return [];\n\n const words = s\n .replace(/[^a-zA-Z0-9_\\-\\s.]/g, '_') // replace non-alphanumeric chars with separator\n .replace(/([a-z])([A-Z])/g, '$1\\0$2') // camelCase boundary\n .replace(/([A-Z]+)([A-Z][a-z])/g, '$1\\0$2') // consecutive caps boundary\n .replace(/([a-zA-Z])(\\d)/g, '$1\\0$2') // letter to number\n .replace(/(\\d)([a-zA-Z])/g, '$1\\0$2') // number to letter\n .split(/[\\0_\\-\\s.]+/)\n .filter((w) => w.length > 0);\n\n return recombineCompounds(words);\n}\n\nexport const ACRONYM_SET = new Set(['SSO', 'FGA', 'SAML', 'SCIM', 'JWT', 'HMAC', 'M2M']);\n\nexport function toPascalCase(s: string, acronyms?: Set<string>): string {\n const merged = acronyms ? new Set([...ACRONYM_SET, ...acronyms]) : ACRONYM_SET;\n return splitWords(s)\n .map((w) => {\n const upper = w.toUpperCase();\n if (merged.has(upper)) return upper;\n // Special case: OAuth should stay as OAuth, not OAUTH\n if (upper === 'OAUTH') return 'OAuth';\n return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();\n })\n .join('');\n}\n\nexport function toCamelCase(s: string, acronyms?: Set<string>): string {\n const merged = acronyms ? new Set([...ACRONYM_SET, ...acronyms]) : ACRONYM_SET;\n const words = splitWords(s);\n if (words.length === 0) return '';\n return words\n .map((w, i) => {\n if (i === 0) return w.toLowerCase();\n const upper = w.toUpperCase();\n if (merged.has(upper)) return upper;\n if (upper === 'OAUTH') return 'OAuth';\n return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();\n })\n .join('');\n}\n\nexport function toSnakeCase(s: string): string {\n return splitWords(s)\n .map((w) => w.toLowerCase())\n .join('_');\n}\n\nexport function toKebabCase(s: string): string {\n return splitWords(s)\n .map((w) => w.toLowerCase())\n .join('-');\n}\n\nexport function toUpperSnakeCase(s: string): string {\n return splitWords(s)\n .map((w) => w.toUpperCase())\n .join('_');\n}\n\nconst BACKEND_SUFFIXES = ['Controller'];\n\nexport function stripBackendSuffixes(name: string): string {\n for (const suffix of BACKEND_SUFFIXES) {\n if (name.endsWith(suffix) && name.length > suffix.length) {\n return name.slice(0, -suffix.length);\n }\n }\n return name;\n}\n\nexport function stripBackendPrefixes(name: string): string {\n return name.replace(/Userland/g, '').replace(/Controller/g, '');\n}\n\n/**\n * Remove ListItem / ByExternalId / ByResourceId / ForResource markers from PascalCase names.\n * E.g. \"DirectoriesListItemState\" → \"DirectoriesState\"\n */\nexport function stripListItemMarkers(name: string): string {\n return name\n .replace(/ListItem/g, '')\n .replace(/ByExternalId/g, '')\n .replace(/ByResourceId/g, '')\n .replace(/ForResource/g, '');\n}\n\n/** Words that look plural but must NOT be singularized. */\nconst SINGULAR_SAFE_LIST = new Set([\n 'Status',\n 'Address',\n 'Access',\n 'Process',\n 'Progress',\n 'Success',\n 'Radius',\n 'Canvas',\n 'Alias',\n 'Basis',\n 'Bus',\n 'Species',\n 'Series',\n]);\n\n/**\n * Conservative singularize for the leading PascalCase word.\n * Only applied to the first word of a PascalCase name (the resource word).\n * - `ies` → `y` (e.g. Directories → Directory)\n * - trailing `s` for words >4 chars (e.g. Organizations → Organization)\n * Safe-listed words are never singularized.\n */\nexport function singularize(word: string): string {\n if (SINGULAR_SAFE_LIST.has(word)) return word;\n if (word.endsWith('ies') && word.length > 4) {\n return word.slice(0, -3) + 'y';\n }\n if (word.endsWith('ses') && word.length > 4) {\n // e.g. \"Processes\" — but \"Process\" is safe-listed, this handles non-safe ones\n return word.slice(0, -2);\n }\n if (word.endsWith('s') && !word.endsWith('ss') && word.length > 4) {\n return word.slice(0, -1);\n }\n return word;\n}\n\n/**\n * Compose all backend name cleaning transforms in order:\n * 1. Strip backend prefixes (Userland, Controller)\n * 2. Strip backend suffixes (Dto, DTO, Controller)\n * 3. Strip ListItem / ByExternalId markers\n * 4. Singularize leading resource word\n *\n * Must be idempotent: `cleanSchemaName(cleanSchemaName(x)) === cleanSchemaName(x)`\n */\n/** Strip `.js` extensions from import/export specifiers for dedup comparison. */\nexport function normalizeJsExtension(text: string): string {\n return text.replace(/\\.js(['\"])/g, '$1');\n}\n\nexport function cleanSchemaName(name: string): string {\n let result = stripBackendPrefixes(stripBackendSuffixes(name));\n result = stripListItemMarkers(result);\n\n // Singularize the leading PascalCase word\n const match = result.match(/^([A-Z][a-z]*)/);\n if (match) {\n const leadWord = match[1];\n const singular = singularize(leadWord);\n if (singular !== leadWord) {\n result = singular + result.slice(leadWord.length);\n }\n }\n\n return result;\n}\n","/**\n * Shared tree-sitter utilities.\n *\n * tree-sitter 0.21.x has a native binding bug: its internal UTF-16 buffer is\n * 32 768 uint16 units (32 768 characters for BMP text). When the JS input\n * callback returns a string >= 32 768 chars, `napi_get_value_string_utf16`\n * overflows and the C layer throws \"Invalid argument\". Work around this by\n * supplying a chunked callback for large sources instead of a raw string.\n */\n\nimport type Parser from 'tree-sitter';\n\nconst TS_SAFE_CHUNK = 32_767;\n\nexport function safeParse(parser: Parser, source: string): Parser.Tree {\n if (source.length < TS_SAFE_CHUNK) {\n return parser.parse(source);\n }\n return parser.parse((offset: number) => source.slice(offset, offset + TS_SAFE_CHUNK));\n}\n"],"mappings":"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"}
@@ -37,6 +37,8 @@ interface ApiInterface {
37
37
  sourceFile?: string;
38
38
  fields: Record<string, ApiField>;
39
39
  extends: string[];
40
+ /** True if the resource class defines its own constructFromResponse (not inherited). */
41
+ hasCustomConstructor?: boolean;
40
42
  }
41
43
  interface ApiField {
42
44
  name: string;
@@ -157,6 +159,158 @@ interface DiffResult {
157
159
  additions: Addition[];
158
160
  }
159
161
  //#endregion
162
+ //#region src/ir/sdk-behavior.d.ts
163
+ /**
164
+ * SDK Behavior IR — language-agnostic runtime policies for generated SDKs.
165
+ *
166
+ * These types capture the "what" of SDK behavior (retry, telemetry, errors, etc.)
167
+ * while emitters provide the "how" (language-specific mechanism).
168
+ *
169
+ * Emitters access these via `ctx.spec.sdk` — always populated, never undefined.
170
+ */
171
+ /** Retry policy — when and how to retry failed requests. */
172
+ interface RetryPolicy {
173
+ /** HTTP status codes that trigger a retry. */
174
+ retryableStatusCodes: number[];
175
+ /** Maximum number of retry attempts (0 = no retries). */
176
+ maxRetries: number;
177
+ /** Whether to retry on connection errors (DNS failure, TCP reset). */
178
+ retryOnConnectionError: boolean;
179
+ /** Whether to retry on timeout errors. */
180
+ retryOnTimeout: boolean;
181
+ /** Backoff strategy between retries. */
182
+ backoff: BackoffStrategy;
183
+ }
184
+ /** Exponential backoff configuration.
185
+ *
186
+ * Formula: `delay = min(initialDelay * multiplier^attempt, maxDelay)`
187
+ * With jitter: `delay += delay * jitterFactor * random(0, 1)`
188
+ */
189
+ interface BackoffStrategy {
190
+ /** Initial delay in seconds before the first retry. */
191
+ initialDelay: number;
192
+ /** Multiplier applied to the delay on each subsequent retry. */
193
+ multiplier: number;
194
+ /** Maximum delay in seconds (cap). */
195
+ maxDelay: number;
196
+ /** Jitter factor (0..1). 0.5 means up to 50% random jitter added. */
197
+ jitterFactor: number;
198
+ }
199
+ /** Error mapping — status codes to named exception kinds. */
200
+ interface ErrorPolicy {
201
+ /** Map from HTTP status code to a logical error kind name (e.g. 400 → 'BadRequest').
202
+ * Emitters append language-specific suffixes (e.g. 'BadRequestException'). */
203
+ statusCodeMap: Record<number, string>;
204
+ /** Catch-all error kind for 5xx status codes not in the map. */
205
+ serverErrorKind: string;
206
+ /** Catch-all error kind for unrecognized status codes. */
207
+ clientErrorKind: string;
208
+ /** URL template for error documentation. Use {code} as placeholder for the API error code.
209
+ * Example: 'https://workos.com/docs/errors/{code}' */
210
+ errorDocUrlTemplate?: string;
211
+ }
212
+ /** Telemetry — what request metrics to track and send. */
213
+ interface TelemetryPolicy {
214
+ /** Whether telemetry is enabled by default. Users can opt out via constructor param. */
215
+ enabledByDefault: boolean;
216
+ /** Header name for client telemetry data (previous request's ID + latency). */
217
+ headerName: string;
218
+ /** Response header name for the request ID. */
219
+ requestIdHeader: string;
220
+ }
221
+ /** Pagination behavior defaults. */
222
+ interface PaginationPolicy {
223
+ /** Delay in milliseconds between pages during auto-pagination. 0 = no delay. */
224
+ autoPageDelayMs: number;
225
+ }
226
+ /** Idempotency auto-generation rules. */
227
+ interface IdempotencyPolicy {
228
+ /** Header name for the idempotency key. */
229
+ headerName: string;
230
+ /** Whether to auto-generate a UUID v4 idempotency key for POST requests when retries > 0. */
231
+ autoGenerateForPost: boolean;
232
+ }
233
+ /** Logging contract — what to log at which level. */
234
+ interface LoggingPolicy {
235
+ /** Whether structured logging hooks are generated. */
236
+ enabled: boolean;
237
+ /** Log events that the generated client emits. */
238
+ events: LogEvent[];
239
+ }
240
+ /** A discrete loggable event in the request lifecycle. */
241
+ type LogEvent = 'request.start' | 'request.success' | 'request.retry' | 'request.rate_limited' | 'request.error' | 'request.connection_error';
242
+ /** User-Agent construction rules. */
243
+ interface UserAgentPolicy {
244
+ /** Template for the SDK identifier string.
245
+ * Placeholders: {name} = spec name, {lang} = emitter language, {version} = SDK version.
246
+ * Each emitter interpolates with its own language string and casing conventions.
247
+ * Example: '{name} {lang}/{version}' → 'WorkOS PHP/4.32.0' */
248
+ sdkIdentifierTemplate: string;
249
+ /** Whether to append the runtime/language version (e.g. PHP 8.2, Python 3.12). */
250
+ includeRuntimeVersion: boolean;
251
+ /** Whether to allow app info enrichment via setAppInfo(name, version, url). */
252
+ allowAppInfo: boolean;
253
+ /** AI agent environment variable detection entries. */
254
+ aiAgentEnvVars: AiAgentEnvVar[];
255
+ }
256
+ /** Maps an environment variable to an AI agent slug for User-Agent enrichment. */
257
+ interface AiAgentEnvVar {
258
+ /** Environment variable to check (e.g. 'CLAUDE_CODE'). */
259
+ envVar: string;
260
+ /** Agent name to append to User-Agent (e.g. 'ClaudeCode'). */
261
+ agentName: string;
262
+ }
263
+ /** Guards that validate request params before sending. */
264
+ interface RequestGuardPolicy {
265
+ /** Keys that should be in RequestOptions, not in params.
266
+ * If detected in the body or query params, the SDK throws an error. */
267
+ optionKeys: string[];
268
+ }
269
+ /** Timeout configuration. */
270
+ interface TimeoutPolicy {
271
+ /** Default HTTP request timeout in seconds. */
272
+ defaultTimeoutSeconds: number;
273
+ /** Environment variable name to override the timeout (e.g. 'WORKOS_REQUEST_TIMEOUT'). */
274
+ timeoutEnvVar?: string;
275
+ }
276
+ /** Language-agnostic runtime policies for generated SDKs.
277
+ * Attached to `ApiSpec.sdk` and consumed by emitters via `ctx.spec.sdk`. */
278
+ interface SdkBehavior {
279
+ retry: RetryPolicy;
280
+ errors: ErrorPolicy;
281
+ telemetry: TelemetryPolicy;
282
+ pagination: PaginationPolicy;
283
+ idempotency: IdempotencyPolicy;
284
+ logging: LoggingPolicy;
285
+ userAgent: UserAgentPolicy;
286
+ requestGuard: RequestGuardPolicy;
287
+ timeout: TimeoutPolicy;
288
+ }
289
+ /**
290
+ * Canonical SDK behavior defaults. These match the PHP emitter's current
291
+ * implementation (the most complete). Per-language overrides can be applied
292
+ * via `mergeSdkBehavior()` in each SDK's `oagen.config.ts`.
293
+ *
294
+ * - Retry: exponential backoff with jitter, retries on 429/5xx
295
+ * - Errors: standard HTTP status → exception kind mapping
296
+ * - Telemetry: enabled by default, tracks request ID + latency
297
+ * - Pagination: no inter-page delay (Node overrides to 350ms)
298
+ * - Idempotency: auto-generate UUID v4 for retryable POSTs
299
+ * - Logging: all lifecycle events enabled
300
+ * - User-Agent: SDK identifier + runtime version + app info + AI agent detection
301
+ * - Request guards: detect misplaced RequestOptions keys in params
302
+ * - Timeout: 60s default (Python overrides to 30s)
303
+ */
304
+ declare function defaultSdkBehavior(): SdkBehavior;
305
+ /** Recursive partial type for deep overrides. */
306
+ type DeepPartial<T> = { [K in keyof T]?: T[K] extends (infer U)[] ? U[] : T[K] extends object ? DeepPartial<T[K]> : T[K] };
307
+ /**
308
+ * Deep-merge partial overrides into the canonical defaults.
309
+ * Arrays replace entirely (so `retryableStatusCodes: [429]` replaces the full list,
310
+ * rather than appending). Object properties are merged recursively.
311
+ */
312
+ declare function mergeSdkBehavior(overrides: DeepPartial<SdkBehavior>): SdkBehavior;
313
+ //#endregion
160
314
  //#region src/ir/types.d.ts
161
315
  /** Authentication scheme extracted from OpenAPI securitySchemes */
162
316
  type AuthScheme = {
@@ -186,6 +340,8 @@ interface ApiSpec {
186
340
  models: Model[];
187
341
  enums: Enum[];
188
342
  auth?: AuthScheme[];
343
+ /** Language-agnostic runtime policies (retry, errors, telemetry, etc.). */
344
+ sdk: SdkBehavior;
189
345
  }
190
346
  /** A service groups related operations (maps to an SDK resource class) */
191
347
  interface Service {
@@ -194,6 +350,11 @@ interface Service {
194
350
  operations: Operation[];
195
351
  }
196
352
  /** A single API operation (maps to an SDK method) */
353
+ /** Per-operation security requirement: scheme name → scope list. */
354
+ interface SecurityRequirement {
355
+ schemeName: string;
356
+ scopes: string[];
357
+ }
197
358
  interface Operation {
198
359
  name: string;
199
360
  description?: string;
@@ -212,6 +373,8 @@ interface Operation {
212
373
  injectIdempotencyKey: boolean;
213
374
  deprecated?: boolean;
214
375
  async?: boolean;
376
+ /** Per-operation security overrides. When present, overrides the global spec-level security. */
377
+ security?: SecurityRequirement[];
215
378
  }
216
379
  /** Structured pagination metadata for auto-paging iterator generation */
217
380
  interface PaginationMeta {
@@ -393,5 +556,5 @@ interface SuccessResponse {
393
556
  type: TypeRef;
394
557
  }
395
558
  //#endregion
396
- export { collectRequestBodyModels as A, ApiProperty as B, TypeRef as C, collectEnumRefs as D, assignModelsToServices as E, ApiEnum as F, LanguageHints as G, ApiTypeAlias as H, ApiField as I, Violation as J, MethodOverlay as K, ApiInterface as L, walkTypeRef as M, Addition as N, collectFieldDependencies as O, ApiClass as P, ApiMethod as R, TypeParam as S, assertNever as T, DiffResult as U, ApiSurface as V, Extractor as W, ViolationSeverity as X, ViolationCategory as Y, Parameter as _, EnumRef as a, Service as b, Field as c, MapType as d, Model as f, PaginationMeta as g, Operation as h, Enum as i, mapTypeRef as j, collectModelRefs as k, HttpMethod as l, NullableType as m, ArrayType as n, EnumValue as o, ModelRef as p, OverlayLookup as q, AuthScheme as r, ErrorResponse as s, ApiSpec as t, LiteralType as u, PrimitiveType as v, UnionType as w, SuccessResponse as x, ServerEntry as y, ApiParam as z };
397
- //# sourceMappingURL=types-O25qZxMl.d.mts.map
559
+ export { ApiField as $, collectModelRefs as A, LoggingPolicy as B, TypeParam as C, assignModelsToServices as D, assertNever as E, BackoffStrategy as F, TelemetryPolicy as G, RequestGuardPolicy as H, DeepPartial as I, defaultSdkBehavior as J, TimeoutPolicy as K, ErrorPolicy as L, mapTypeRef as M, walkTypeRef as N, collectEnumRefs as O, AiAgentEnvVar as P, ApiEnum as Q, IdempotencyPolicy as R, SuccessResponse as S, UnionType as T, RetryPolicy as U, PaginationPolicy as V, SdkBehavior as W, Addition as X, mergeSdkBehavior as Y, ApiClass as Z, Parameter as _, EnumRef as a, ApiTypeAlias as at, ServerEntry as b, Field as c, LanguageHints as ct, MapType as d, Violation as dt, ApiInterface as et, Model as f, ViolationCategory as ft, PaginationMeta as g, Operation as h, Enum as i, ApiSurface as it, collectRequestBodyModels as j, collectFieldDependencies as k, HttpMethod as l, MethodOverlay as lt, NullableType as m, ArrayType as n, ApiParam as nt, EnumValue as o, DiffResult as ot, ModelRef as p, ViolationSeverity as pt, UserAgentPolicy as q, AuthScheme as r, ApiProperty as rt, ErrorResponse as s, Extractor as st, ApiSpec as t, ApiMethod as tt, LiteralType as u, OverlayLookup as ut, PrimitiveType as v, TypeRef as w, Service as x, SecurityRequirement as y, LogEvent as z };
560
+ //# sourceMappingURL=types-D1tMqLVB.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types-D1tMqLVB.d.mts","names":[],"sources":["../src/compat/types.ts","../src/ir/sdk-behavior.ts","../src/ir/types.ts"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import { J as Violation, U as DiffResult, V as ApiSurface, t as ApiSpec } from "../types-O25qZxMl.mjs";
1
+ import { dt as Violation, it as ApiSurface, ot as DiffResult, t as ApiSpec } from "../types-D1tMqLVB.mjs";
2
2
 
3
3
  //#region src/verify/types.d.ts
4
4
  interface VerifyDiagnostics {
@@ -1,6 +1,6 @@
1
- import { m as CommandError } from "../tree-sitter-DFRXfWg9.mjs";
2
- import { i as generate, n as getEmitter, t as diffSpecs } from "../diff-Cn6S74vV.mjs";
3
- import { c as buildOverlayLookup, g as getExtractor, i as specDerivedFieldPaths, l as patchOverlay, n as filterSurface, o as specDerivedMethodPaths, r as specDerivedEnumValues, s as specDerivedNames, t as diffSurfaces } from "../differ-B6472Yef.mjs";
1
+ import { m as CommandError } from "../tree-sitter-C53w_0WE.mjs";
2
+ import { i as generate, n as getEmitter, t as diffSpecs } from "../diff-DcY93sii.mjs";
3
+ import { c as buildOverlayLookup, g as getExtractor, i as specDerivedFieldPaths, l as patchOverlay, n as filterSurface, o as specDerivedMethodPaths, r as specDerivedEnumValues, s as specDerivedNames, t as diffSurfaces } from "../differ-CG5ZErKl.mjs";
4
4
  import * as path$1 from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { execFileSync } from "node:child_process";
@@ -244,7 +244,7 @@ function runStalenessCheck(baseline, oldSpec, newSpec, lang) {
244
244
  const __filename = fileURLToPath(import.meta.url);
245
245
  const __dirname = path$1.dirname(__filename);
246
246
  function resolveScript(scriptRelPath) {
247
- const distScript = path$1.resolve(__dirname, "..", "..", "dist", "scripts", scriptRelPath.replace(/\.ts$/, ".js"));
247
+ const distScript = path$1.resolve(__dirname, "..", "..", "dist", "scripts", scriptRelPath.replace(/\.ts$/, ".mjs"));
248
248
  if (existsSync(distScript)) return {
249
249
  bin: "node",
250
250
  script: distScript
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["path","path"],"sources":["../../src/verify/run-compat-check.ts","../../src/verify/run-overlay-retry-loop.ts","../../src/compat/staleness.ts","../../src/verify/run-staleness-check.ts","../../src/verify/run-smoke-check.ts"],"sourcesContent":["import { getExtractor } from '../compat/extractor-registry.js';\nimport {\n diffSurfaces,\n specDerivedNames,\n specDerivedFieldPaths,\n specDerivedMethodPaths,\n specDerivedEnumValues,\n filterSurface,\n} from '../compat/differ.js';\nimport type { ApiSpec } from '../ir/types.js';\nimport type { ApiSurface } from '../compat/types.js';\nimport type { CompatCheckResult } from './types.js';\n\nexport async function runCompatCheck(\n baseline: ApiSurface,\n outputDir: string,\n lang: string,\n spec?: ApiSpec,\n): Promise<CompatCheckResult> {\n const extractor = getExtractor(lang);\n const candidate = await extractor.extract(outputDir);\n\n let scopedBaseline = baseline;\n let scopedToSpec = false;\n let scopedSymbolCount: number | undefined;\n if (spec) {\n const allowed = specDerivedNames(spec, extractor.hints);\n const fieldPaths = specDerivedFieldPaths(spec, extractor.hints);\n const methodPaths = specDerivedMethodPaths(spec);\n const enumVals = specDerivedEnumValues(spec);\n scopedBaseline = filterSurface(baseline, allowed, { fieldPaths, methodPaths, enumValues: enumVals });\n scopedToSpec = true;\n scopedSymbolCount =\n Object.keys(scopedBaseline.interfaces).length +\n Object.keys(scopedBaseline.classes).length +\n Object.keys(scopedBaseline.typeAliases).length +\n Object.keys(scopedBaseline.enums).length;\n }\n\n const diff = diffSurfaces(scopedBaseline, candidate, extractor.hints);\n const passed = diff.violations.every((v) => v.severity !== 'breaking');\n\n return { passed, diff, scopedToSpec, scopedSymbolCount };\n}\n","import { existsSync, readFileSync, readdirSync, unlinkSync, statSync, openSync, readSync, closeSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { buildOverlayLookup, patchOverlay } from '../compat/overlay.js';\nimport type { ManifestEntry } from '../compat/overlay.js';\nimport { getExtractor } from '../compat/extractor-registry.js';\nimport type { ApiSurface, ViolationCategory } from '../compat/types.js';\nimport type { ApiSpec } from '../ir/types.js';\nimport { getEmitter } from '../engine/registry.js';\nimport { generate } from '../engine/orchestrator.js';\nimport { runCompatCheck } from './run-compat-check.js';\nimport type { OverlayRetryResult } from './types.js';\n\nconst PATCHABLE_CATEGORIES: Set<ViolationCategory> = new Set(['public-api', 'export-structure']);\n\n/** Generated file header markers used by various emitters. */\nconst GENERATED_MARKERS = ['Code generated by oagen', 'auto-generated by oagen', 'This file is auto-generated'];\n\n/**\n * Delete oagen-generated source files so the retry loop can regenerate them\n * with overlay-corrected names. Only deletes files that contain an oagen\n * generation marker, preserving hand-written files.\n */\nfunction cleanGeneratedFiles(dir: string): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = path.join(dir, entry);\n let stat;\n try {\n stat = statSync(full);\n } catch {\n continue;\n }\n if (stat.isDirectory()) {\n cleanGeneratedFiles(full);\n continue;\n }\n // Skip non-source files and manifests\n if (entry.endsWith('.json') || entry.endsWith('.lock')) continue;\n // Read only the first 200 bytes to check for generation marker\n try {\n const buf = Buffer.alloc(200);\n const fd = openSync(full, 'r');\n try {\n readSync(fd, buf, 0, 200, 0);\n } finally {\n closeSync(fd);\n }\n const header = buf.toString('utf-8');\n if (GENERATED_MARKERS.some((m) => header.includes(m))) {\n unlinkSync(full);\n }\n } catch {\n // ignore read/delete errors\n }\n }\n}\n\n/**\n * Load a smoke-manifest.json from the generated SDK directory and convert\n * it to ManifestEntry[] for the overlay builder.\n */\nfunction loadManifestFromDir(outputDir: string): ManifestEntry[] | undefined {\n const manifestPath = path.join(outputDir, 'smoke-manifest.json');\n if (!existsSync(manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(readFileSync(manifestPath, 'utf-8'));\n if (Array.isArray(parsed)) return parsed as ManifestEntry[];\n if (typeof parsed === 'object' && parsed !== null) {\n return Object.entries(parsed).map(([httpKey, value]) => {\n const spaceIdx = httpKey.indexOf(' ');\n const httpMethod = httpKey.slice(0, spaceIdx);\n const httpPath = httpKey.slice(spaceIdx + 1);\n const v = value as { sdkMethod?: string; service?: string };\n return {\n operationId: '',\n sdkResourceProperty: v.service ?? '',\n sdkMethodName: v.sdkMethod ?? '',\n httpMethod,\n path: httpPath,\n pathParams: [],\n bodyFields: [],\n queryFields: [],\n };\n });\n }\n } catch {\n // ignore parse errors\n }\n return undefined;\n}\n\nexport async function runOverlayRetryLoop(opts: {\n baseline: ApiSurface;\n parsedSpec: ApiSpec;\n outputDir: string;\n lang: string;\n maxRetries: number;\n namespace?: string;\n onRetry?: (attemptNumber: number, maxRetries: number, patchableCount: number) => void;\n}): Promise<OverlayRetryResult> {\n const { baseline, parsedSpec, outputDir, lang, maxRetries, namespace, onRetry } = opts;\n\n const manifest = loadManifestFromDir(outputDir);\n // Pass language-specific hints so property-to-class matching works correctly\n // (e.g., Ruby snake_case \"api_keys\" → PascalCase \"ApiKeys\").\n const extractor = getExtractor(lang);\n let overlay = buildOverlayLookup(baseline, manifest, parsedSpec, extractor.hints);\n let prevPreserved = -1;\n const patchedPerIteration: number[] = [];\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const compatResult = await runCompatCheck(baseline, outputDir, lang, parsedSpec);\n if (compatResult.passed) {\n return {\n status: 'passed',\n attempts: attempt,\n patchedPerIteration,\n compatResult,\n };\n }\n\n if (attempt === maxRetries) {\n return {\n status: 'max-retries',\n attempts: attempt,\n patchedPerIteration,\n compatResult,\n };\n }\n\n const patchable = compatResult.diff.violations.filter((v) => PATCHABLE_CATEGORIES.has(v.category));\n if (patchable.length === 0) {\n return {\n status: 'no-patchable',\n attempts: attempt,\n patchedPerIteration,\n compatResult,\n };\n }\n\n const currentPreserved = compatResult.diff.preservedSymbols;\n if (attempt > 0 && currentPreserved <= prevPreserved) {\n return {\n status: 'stalled',\n attempts: attempt,\n patchedPerIteration,\n compatResult,\n };\n }\n prevPreserved = currentPreserved;\n patchedPerIteration.push(patchable.length);\n onRetry?.(attempt + 1, maxRetries, patchable.length);\n\n overlay = patchOverlay(overlay, patchable, baseline);\n\n // Pre-delete generated source files so the writer creates them fresh\n // with overlay-corrected names. Without this, the AST merger would preserve\n // old method/class names from the existing files.\n // Only delete files that look like oagen-generated source files (have the header).\n cleanGeneratedFiles(outputDir);\n\n const emitter = getEmitter(lang);\n await generate(parsedSpec, emitter, {\n namespace: namespace ?? parsedSpec.name,\n outputDir,\n overlayLookup: overlay,\n apiSurface: baseline,\n });\n }\n\n const compatResult = await runCompatCheck(baseline, outputDir, lang, parsedSpec);\n return {\n status: 'max-retries',\n attempts: maxRetries,\n patchedPerIteration,\n compatResult,\n };\n}\n","import type { ApiSurface, LanguageHints, Violation } from './types.js';\nimport type { ApiSpec } from '../ir/types.js';\nimport { specDerivedNames, specDerivedFieldPaths } from './spec-filter.js';\nimport { diffSpecs } from '../differ/diff.js';\n\n/**\n * Detect symbols that exist in the live SDK surface but are no longer defined\n * in the current OpenAPI spec. These are \"stale\" — they compile and run but\n * represent dead code from a previous spec version.\n *\n * Requires both the old and new spec so we can distinguish hand-written SDK\n * symbols (never in any spec) from genuinely removed spec symbols.\n */\nexport function detectStaleSymbols(\n liveSurface: ApiSurface,\n oldSpec: ApiSpec,\n newSpec: ApiSpec,\n hints: LanguageHints,\n): Violation[] {\n const violations: Violation[] = [];\n\n // Step 1-3: Name-set difference for top-level symbols\n const oldNames = specDerivedNames(oldSpec, hints);\n const newNames = specDerivedNames(newSpec, hints);\n\n const removedNames = new Set<string>();\n for (const name of oldNames) {\n if (!newNames.has(name)) removedNames.add(name);\n }\n\n for (const name of removedNames) {\n if (liveSurface.classes[name]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: name,\n baseline: name,\n candidate: '(removed from spec)',\n message: `Class \"${name}\" is no longer defined in the OpenAPI spec`,\n });\n }\n if (liveSurface.interfaces[name]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: name,\n baseline: name,\n candidate: '(removed from spec)',\n message: `Interface \"${name}\" is no longer defined in the OpenAPI spec`,\n });\n }\n if (liveSurface.typeAliases[name]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: name,\n baseline: name,\n candidate: '(removed from spec)',\n message: `Type alias \"${name}\" is no longer defined in the OpenAPI spec`,\n });\n }\n if (liveSurface.enums[name]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: name,\n baseline: name,\n candidate: '(removed from spec)',\n message: `Enum \"${name}\" is no longer defined in the OpenAPI spec`,\n });\n }\n }\n\n // Step 4-6: Field-level staleness for models that still exist in new spec\n const oldFieldPaths = specDerivedFieldPaths(oldSpec, hints);\n const newFieldPaths = specDerivedFieldPaths(newSpec, hints);\n\n for (const fieldPath of oldFieldPaths) {\n if (newFieldPaths.has(fieldPath)) continue;\n\n const [modelName, fieldName] = fieldPath.split('.');\n // Only flag field-level staleness for models that still exist in the new spec\n // (fully removed models are already caught above)\n if (removedNames.has(modelName)) continue;\n\n const iface = liveSurface.interfaces[modelName];\n if (iface?.fields[fieldName]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: fieldPath,\n baseline: fieldName,\n candidate: '(removed from spec)',\n message: `Field \"${fieldPath}\" is no longer defined in the OpenAPI spec`,\n });\n }\n }\n\n // Step 7: Operation-level staleness via diffSpecs\n const diff = diffSpecs(oldSpec, newSpec);\n for (const change of diff.changes) {\n if (change.kind === 'operation-removed') {\n const cls = liveSurface.classes[change.serviceName];\n if (cls?.methods[change.operationName]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: `${change.serviceName}.${change.operationName}`,\n baseline: change.operationName,\n candidate: '(removed from spec)',\n message: `Method \"${change.serviceName}.${change.operationName}\" is no longer defined in the OpenAPI spec`,\n });\n }\n }\n }\n\n return violations;\n}\n","import { getExtractor } from '../compat/extractor-registry.js';\nimport { detectStaleSymbols } from '../compat/staleness.js';\nimport type { ApiSpec } from '../ir/types.js';\nimport type { ApiSurface } from '../compat/types.js';\nimport type { StalenessCheckResult } from './types.js';\n\nexport function runStalenessCheck(\n baseline: ApiSurface,\n oldSpec: ApiSpec,\n newSpec: ApiSpec,\n lang: string,\n): StalenessCheckResult {\n const extractor = getExtractor(lang);\n const violations = detectStaleSymbols(baseline, oldSpec, newSpec, extractor.hints);\n return { violations };\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport * as path from 'node:path';\nimport { CommandError } from '../errors.js';\nimport type { SmokeCheckResult } from './types.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction resolveScript(scriptRelPath: string): { bin: string; script: string } {\n const distScript = path.resolve(__dirname, '..', '..', 'dist', 'scripts', scriptRelPath.replace(/\\.ts$/, '.js'));\n if (existsSync(distScript)) {\n return { bin: 'node', script: distScript };\n }\n const srcScript = path.resolve(__dirname, '..', '..', 'scripts', scriptRelPath);\n return { bin: 'npx', script: srcScript };\n}\n\nfunction runPackagedScript(scriptRelPath: string, args: string[]): void {\n const { bin, script } = resolveScript(scriptRelPath);\n const fullArgs = bin === 'npx' ? ['tsx', script, ...args] : [script, ...args];\n execFileSync(bin, fullArgs, { stdio: 'inherit', env: process.env });\n}\n\nexport function runSmokeCheck(opts: {\n spec?: string;\n lang: string;\n output: string;\n rawResults?: string;\n smokeConfig?: string;\n smokeRunner?: string;\n}): SmokeCheckResult {\n const { spec, lang, output, rawResults, smokeConfig, smokeRunner } = opts;\n\n let baselinePath = rawResults ?? 'smoke-results-raw.json';\n let generatedBaseline = false;\n\n if (!rawResults && !existsSync('smoke-results-raw.json')) {\n if (!spec) {\n throw new CommandError(\n 'error: --spec <path> or OPENAPI_SPEC_PATH env var is required when no raw baseline exists',\n '',\n 1,\n );\n }\n\n try {\n runPackagedScript('smoke/baseline.ts', ['--spec', spec]);\n } catch {\n throw new CommandError('Baseline generation failed', '', 1);\n }\n baselinePath = 'smoke-results-spec-baseline.json';\n generatedBaseline = true;\n }\n\n const smokeArgs = ['--lang', lang, '--sdk-path', output, '--raw-results', baselinePath];\n if (spec) smokeArgs.push('--spec', spec);\n if (smokeConfig) smokeArgs.push('--smoke-config', smokeConfig);\n\n try {\n if (smokeRunner) {\n const bin = smokeRunner.endsWith('.ts') ? 'npx' : 'node';\n const fullArgs = bin === 'npx' ? ['tsx', smokeRunner, ...smokeArgs] : [smokeRunner, ...smokeArgs];\n execFileSync(bin, fullArgs, { stdio: 'inherit', env: process.env });\n } else {\n runPackagedScript('smoke/sdk-test.ts', smokeArgs);\n }\n\n return { passed: true, baselinePath, generatedBaseline };\n } catch {\n if (existsSync('smoke-compile-errors.json')) {\n return { passed: false, compileErrors: true, baselinePath, generatedBaseline };\n }\n\n const findingsCount = existsSync('smoke-diff-findings.json')\n ? (JSON.parse(readFileSync('smoke-diff-findings.json', 'utf-8')) as unknown[]).length\n : undefined;\n\n return { passed: false, findingsCount, baselinePath, generatedBaseline };\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["path","path"],"sources":["../../src/verify/run-compat-check.ts","../../src/verify/run-overlay-retry-loop.ts","../../src/compat/staleness.ts","../../src/verify/run-staleness-check.ts","../../src/verify/run-smoke-check.ts"],"sourcesContent":["import { getExtractor } from '../compat/extractor-registry.js';\nimport {\n diffSurfaces,\n specDerivedNames,\n specDerivedFieldPaths,\n specDerivedMethodPaths,\n specDerivedEnumValues,\n filterSurface,\n} from '../compat/differ.js';\nimport type { ApiSpec } from '../ir/types.js';\nimport type { ApiSurface } from '../compat/types.js';\nimport type { CompatCheckResult } from './types.js';\n\nexport async function runCompatCheck(\n baseline: ApiSurface,\n outputDir: string,\n lang: string,\n spec?: ApiSpec,\n): Promise<CompatCheckResult> {\n const extractor = getExtractor(lang);\n const candidate = await extractor.extract(outputDir);\n\n let scopedBaseline = baseline;\n let scopedToSpec = false;\n let scopedSymbolCount: number | undefined;\n if (spec) {\n const allowed = specDerivedNames(spec, extractor.hints);\n const fieldPaths = specDerivedFieldPaths(spec, extractor.hints);\n const methodPaths = specDerivedMethodPaths(spec);\n const enumVals = specDerivedEnumValues(spec);\n scopedBaseline = filterSurface(baseline, allowed, { fieldPaths, methodPaths, enumValues: enumVals });\n scopedToSpec = true;\n scopedSymbolCount =\n Object.keys(scopedBaseline.interfaces).length +\n Object.keys(scopedBaseline.classes).length +\n Object.keys(scopedBaseline.typeAliases).length +\n Object.keys(scopedBaseline.enums).length;\n }\n\n const diff = diffSurfaces(scopedBaseline, candidate, extractor.hints);\n const passed = diff.violations.every((v) => v.severity !== 'breaking');\n\n return { passed, diff, scopedToSpec, scopedSymbolCount };\n}\n","import { existsSync, readFileSync, readdirSync, unlinkSync, statSync, openSync, readSync, closeSync } from 'node:fs';\nimport * as path from 'node:path';\nimport { buildOverlayLookup, patchOverlay } from '../compat/overlay.js';\nimport type { ManifestEntry } from '../compat/overlay.js';\nimport { getExtractor } from '../compat/extractor-registry.js';\nimport type { ApiSurface, ViolationCategory } from '../compat/types.js';\nimport type { ApiSpec } from '../ir/types.js';\nimport { getEmitter } from '../engine/registry.js';\nimport { generate } from '../engine/orchestrator.js';\nimport { runCompatCheck } from './run-compat-check.js';\nimport type { OverlayRetryResult } from './types.js';\n\nconst PATCHABLE_CATEGORIES: Set<ViolationCategory> = new Set(['public-api', 'export-structure']);\n\n/** Generated file header markers used by various emitters. */\nconst GENERATED_MARKERS = ['Code generated by oagen', 'auto-generated by oagen', 'This file is auto-generated'];\n\n/**\n * Delete oagen-generated source files so the retry loop can regenerate them\n * with overlay-corrected names. Only deletes files that contain an oagen\n * generation marker, preserving hand-written files.\n */\nfunction cleanGeneratedFiles(dir: string): void {\n let entries: string[];\n try {\n entries = readdirSync(dir);\n } catch {\n return;\n }\n for (const entry of entries) {\n const full = path.join(dir, entry);\n let stat;\n try {\n stat = statSync(full);\n } catch {\n continue;\n }\n if (stat.isDirectory()) {\n cleanGeneratedFiles(full);\n continue;\n }\n // Skip non-source files and manifests\n if (entry.endsWith('.json') || entry.endsWith('.lock')) continue;\n // Read only the first 200 bytes to check for generation marker\n try {\n const buf = Buffer.alloc(200);\n const fd = openSync(full, 'r');\n try {\n readSync(fd, buf, 0, 200, 0);\n } finally {\n closeSync(fd);\n }\n const header = buf.toString('utf-8');\n if (GENERATED_MARKERS.some((m) => header.includes(m))) {\n unlinkSync(full);\n }\n } catch {\n // ignore read/delete errors\n }\n }\n}\n\n/**\n * Load a smoke-manifest.json from the generated SDK directory and convert\n * it to ManifestEntry[] for the overlay builder.\n */\nfunction loadManifestFromDir(outputDir: string): ManifestEntry[] | undefined {\n const manifestPath = path.join(outputDir, 'smoke-manifest.json');\n if (!existsSync(manifestPath)) return undefined;\n try {\n const parsed = JSON.parse(readFileSync(manifestPath, 'utf-8'));\n if (Array.isArray(parsed)) return parsed as ManifestEntry[];\n if (typeof parsed === 'object' && parsed !== null) {\n return Object.entries(parsed).map(([httpKey, value]) => {\n const spaceIdx = httpKey.indexOf(' ');\n const httpMethod = httpKey.slice(0, spaceIdx);\n const httpPath = httpKey.slice(spaceIdx + 1);\n const v = value as { sdkMethod?: string; service?: string };\n return {\n operationId: '',\n sdkResourceProperty: v.service ?? '',\n sdkMethodName: v.sdkMethod ?? '',\n httpMethod,\n path: httpPath,\n pathParams: [],\n bodyFields: [],\n queryFields: [],\n };\n });\n }\n } catch {\n // ignore parse errors\n }\n return undefined;\n}\n\nexport async function runOverlayRetryLoop(opts: {\n baseline: ApiSurface;\n parsedSpec: ApiSpec;\n outputDir: string;\n lang: string;\n maxRetries: number;\n namespace?: string;\n onRetry?: (attemptNumber: number, maxRetries: number, patchableCount: number) => void;\n}): Promise<OverlayRetryResult> {\n const { baseline, parsedSpec, outputDir, lang, maxRetries, namespace, onRetry } = opts;\n\n const manifest = loadManifestFromDir(outputDir);\n // Pass language-specific hints so property-to-class matching works correctly\n // (e.g., Ruby snake_case \"api_keys\" → PascalCase \"ApiKeys\").\n const extractor = getExtractor(lang);\n let overlay = buildOverlayLookup(baseline, manifest, parsedSpec, extractor.hints);\n let prevPreserved = -1;\n const patchedPerIteration: number[] = [];\n\n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n const compatResult = await runCompatCheck(baseline, outputDir, lang, parsedSpec);\n if (compatResult.passed) {\n return {\n status: 'passed',\n attempts: attempt,\n patchedPerIteration,\n compatResult,\n };\n }\n\n if (attempt === maxRetries) {\n return {\n status: 'max-retries',\n attempts: attempt,\n patchedPerIteration,\n compatResult,\n };\n }\n\n const patchable = compatResult.diff.violations.filter((v) => PATCHABLE_CATEGORIES.has(v.category));\n if (patchable.length === 0) {\n return {\n status: 'no-patchable',\n attempts: attempt,\n patchedPerIteration,\n compatResult,\n };\n }\n\n const currentPreserved = compatResult.diff.preservedSymbols;\n if (attempt > 0 && currentPreserved <= prevPreserved) {\n return {\n status: 'stalled',\n attempts: attempt,\n patchedPerIteration,\n compatResult,\n };\n }\n prevPreserved = currentPreserved;\n patchedPerIteration.push(patchable.length);\n onRetry?.(attempt + 1, maxRetries, patchable.length);\n\n overlay = patchOverlay(overlay, patchable, baseline);\n\n // Pre-delete generated source files so the writer creates them fresh\n // with overlay-corrected names. Without this, the AST merger would preserve\n // old method/class names from the existing files.\n // Only delete files that look like oagen-generated source files (have the header).\n cleanGeneratedFiles(outputDir);\n\n const emitter = getEmitter(lang);\n await generate(parsedSpec, emitter, {\n namespace: namespace ?? parsedSpec.name,\n outputDir,\n overlayLookup: overlay,\n apiSurface: baseline,\n });\n }\n\n const compatResult = await runCompatCheck(baseline, outputDir, lang, parsedSpec);\n return {\n status: 'max-retries',\n attempts: maxRetries,\n patchedPerIteration,\n compatResult,\n };\n}\n","import type { ApiSurface, LanguageHints, Violation } from './types.js';\nimport type { ApiSpec } from '../ir/types.js';\nimport { specDerivedNames, specDerivedFieldPaths } from './spec-filter.js';\nimport { diffSpecs } from '../differ/diff.js';\n\n/**\n * Detect symbols that exist in the live SDK surface but are no longer defined\n * in the current OpenAPI spec. These are \"stale\" — they compile and run but\n * represent dead code from a previous spec version.\n *\n * Requires both the old and new spec so we can distinguish hand-written SDK\n * symbols (never in any spec) from genuinely removed spec symbols.\n */\nexport function detectStaleSymbols(\n liveSurface: ApiSurface,\n oldSpec: ApiSpec,\n newSpec: ApiSpec,\n hints: LanguageHints,\n): Violation[] {\n const violations: Violation[] = [];\n\n // Step 1-3: Name-set difference for top-level symbols\n const oldNames = specDerivedNames(oldSpec, hints);\n const newNames = specDerivedNames(newSpec, hints);\n\n const removedNames = new Set<string>();\n for (const name of oldNames) {\n if (!newNames.has(name)) removedNames.add(name);\n }\n\n for (const name of removedNames) {\n if (liveSurface.classes[name]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: name,\n baseline: name,\n candidate: '(removed from spec)',\n message: `Class \"${name}\" is no longer defined in the OpenAPI spec`,\n });\n }\n if (liveSurface.interfaces[name]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: name,\n baseline: name,\n candidate: '(removed from spec)',\n message: `Interface \"${name}\" is no longer defined in the OpenAPI spec`,\n });\n }\n if (liveSurface.typeAliases[name]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: name,\n baseline: name,\n candidate: '(removed from spec)',\n message: `Type alias \"${name}\" is no longer defined in the OpenAPI spec`,\n });\n }\n if (liveSurface.enums[name]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: name,\n baseline: name,\n candidate: '(removed from spec)',\n message: `Enum \"${name}\" is no longer defined in the OpenAPI spec`,\n });\n }\n }\n\n // Step 4-6: Field-level staleness for models that still exist in new spec\n const oldFieldPaths = specDerivedFieldPaths(oldSpec, hints);\n const newFieldPaths = specDerivedFieldPaths(newSpec, hints);\n\n for (const fieldPath of oldFieldPaths) {\n if (newFieldPaths.has(fieldPath)) continue;\n\n const [modelName, fieldName] = fieldPath.split('.');\n // Only flag field-level staleness for models that still exist in the new spec\n // (fully removed models are already caught above)\n if (removedNames.has(modelName)) continue;\n\n const iface = liveSurface.interfaces[modelName];\n if (iface?.fields[fieldName]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: fieldPath,\n baseline: fieldName,\n candidate: '(removed from spec)',\n message: `Field \"${fieldPath}\" is no longer defined in the OpenAPI spec`,\n });\n }\n }\n\n // Step 7: Operation-level staleness via diffSpecs\n const diff = diffSpecs(oldSpec, newSpec);\n for (const change of diff.changes) {\n if (change.kind === 'operation-removed') {\n const cls = liveSurface.classes[change.serviceName];\n if (cls?.methods[change.operationName]) {\n violations.push({\n category: 'staleness',\n severity: 'warning',\n symbolPath: `${change.serviceName}.${change.operationName}`,\n baseline: change.operationName,\n candidate: '(removed from spec)',\n message: `Method \"${change.serviceName}.${change.operationName}\" is no longer defined in the OpenAPI spec`,\n });\n }\n }\n }\n\n return violations;\n}\n","import { getExtractor } from '../compat/extractor-registry.js';\nimport { detectStaleSymbols } from '../compat/staleness.js';\nimport type { ApiSpec } from '../ir/types.js';\nimport type { ApiSurface } from '../compat/types.js';\nimport type { StalenessCheckResult } from './types.js';\n\nexport function runStalenessCheck(\n baseline: ApiSurface,\n oldSpec: ApiSpec,\n newSpec: ApiSpec,\n lang: string,\n): StalenessCheckResult {\n const extractor = getExtractor(lang);\n const violations = detectStaleSymbols(baseline, oldSpec, newSpec, extractor.hints);\n return { violations };\n}\n","import { execFileSync } from 'node:child_process';\nimport { existsSync, readFileSync } from 'node:fs';\nimport { fileURLToPath } from 'node:url';\nimport * as path from 'node:path';\nimport { CommandError } from '../errors.js';\nimport type { SmokeCheckResult } from './types.js';\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\n\nfunction resolveScript(scriptRelPath: string): { bin: string; script: string } {\n const distScript = path.resolve(__dirname, '..', '..', 'dist', 'scripts', scriptRelPath.replace(/\\.ts$/, '.mjs'));\n if (existsSync(distScript)) {\n return { bin: 'node', script: distScript };\n }\n const srcScript = path.resolve(__dirname, '..', '..', 'scripts', scriptRelPath);\n return { bin: 'npx', script: srcScript };\n}\n\nfunction runPackagedScript(scriptRelPath: string, args: string[]): void {\n const { bin, script } = resolveScript(scriptRelPath);\n const fullArgs = bin === 'npx' ? ['tsx', script, ...args] : [script, ...args];\n execFileSync(bin, fullArgs, { stdio: 'inherit', env: process.env });\n}\n\nexport function runSmokeCheck(opts: {\n spec?: string;\n lang: string;\n output: string;\n rawResults?: string;\n smokeConfig?: string;\n smokeRunner?: string;\n}): SmokeCheckResult {\n const { spec, lang, output, rawResults, smokeConfig, smokeRunner } = opts;\n\n let baselinePath = rawResults ?? 'smoke-results-raw.json';\n let generatedBaseline = false;\n\n if (!rawResults && !existsSync('smoke-results-raw.json')) {\n if (!spec) {\n throw new CommandError(\n 'error: --spec <path> or OPENAPI_SPEC_PATH env var is required when no raw baseline exists',\n '',\n 1,\n );\n }\n\n try {\n runPackagedScript('smoke/baseline.ts', ['--spec', spec]);\n } catch {\n throw new CommandError('Baseline generation failed', '', 1);\n }\n baselinePath = 'smoke-results-spec-baseline.json';\n generatedBaseline = true;\n }\n\n const smokeArgs = ['--lang', lang, '--sdk-path', output, '--raw-results', baselinePath];\n if (spec) smokeArgs.push('--spec', spec);\n if (smokeConfig) smokeArgs.push('--smoke-config', smokeConfig);\n\n try {\n if (smokeRunner) {\n const bin = smokeRunner.endsWith('.ts') ? 'npx' : 'node';\n const fullArgs = bin === 'npx' ? ['tsx', smokeRunner, ...smokeArgs] : [smokeRunner, ...smokeArgs];\n execFileSync(bin, fullArgs, { stdio: 'inherit', env: process.env });\n } else {\n runPackagedScript('smoke/sdk-test.ts', smokeArgs);\n }\n\n return { passed: true, baselinePath, generatedBaseline };\n } catch {\n if (existsSync('smoke-compile-errors.json')) {\n return { passed: false, compileErrors: true, baselinePath, generatedBaseline };\n }\n\n const findingsCount = existsSync('smoke-diff-findings.json')\n ? (JSON.parse(readFileSync('smoke-diff-findings.json', 'utf-8')) as unknown[]).length\n : undefined;\n\n return { passed: false, findingsCount, baselinePath, generatedBaseline };\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  # IR Type System Reference
2
2
 
3
- Source: `src/ir/types.ts`
3
+ Source: `src/ir/types.ts`, `src/ir/sdk-behavior.ts`
4
4
 
5
5
  The intermediate representation (IR) is the contract between the parser and all language emitters. It uses plain TypeScript interfaces (no classes) with a discriminated union type system.
6
6
 
@@ -22,11 +22,34 @@ interface ApiSpec {
22
22
  models: Model[]; // Schema objects
23
23
  enums: Enum[]; // String enums
24
24
  auth?: AuthScheme[]; // Authentication schemes
25
+ sdk: SdkBehavior; // Runtime policies (retry, errors, telemetry, etc.)
25
26
  }
26
27
  ```
27
28
 
28
29
  The `servers` array contains all server entries from the OpenAPI spec. `baseUrl` is always set to the first server's URL for backward compatibility.
29
30
 
31
+ ### SdkBehavior
32
+
33
+ Source: `src/ir/sdk-behavior.ts`
34
+
35
+ Language-agnostic runtime policies consumed by emitters via `ctx.spec.sdk`. Always populated by the parser with `defaultSdkBehavior()`. Override per-SDK via `oagen.config.ts`.
36
+
37
+ ```typescript
38
+ interface SdkBehavior {
39
+ retry: RetryPolicy; // Retry status codes, max attempts, backoff strategy
40
+ errors: ErrorPolicy; // Status code → exception kind mapping, doc URL template
41
+ telemetry: TelemetryPolicy; // Request metrics header, request ID header
42
+ pagination: PaginationPolicy; // Auto-page delay
43
+ idempotency: IdempotencyPolicy; // Idempotency header name, auto-generate for POST
44
+ logging: LoggingPolicy; // Log events and levels
45
+ userAgent: UserAgentPolicy; // SDK identifier template, AI agent detection
46
+ requestGuard: RequestGuardPolicy; // Misplaced-options key detection
47
+ timeout: TimeoutPolicy; // Default timeout, env var override
48
+ }
49
+ ```
50
+
51
+ Use `defaultSdkBehavior()` for canonical defaults. Use `mergeSdkBehavior(overrides)` for partial overrides (arrays replace, objects merge recursively).
52
+
30
53
  ## TypeRef (discriminated union)
31
54
 
32
55
  Every type in the IR is a `TypeRef`, discriminated on the `kind` field:
@@ -267,6 +290,55 @@ interface ErrorResponse {
267
290
  }
268
291
  ```
269
292
 
293
+ ## Operation Resolution
294
+
295
+ Source: `src/ir/operation-hints.ts`
296
+
297
+ The operation resolver derives method names, mount targets, and wrapper metadata for all operations in the spec. It runs in `buildEmitterContext()` and produces `ResolvedOperation[]` on `ctx.resolvedOperations`.
298
+
299
+ ### Algorithm
300
+
301
+ `deriveMethodName(op, service)` derives a snake_case name from the HTTP method and path:
302
+
303
+ 1. Strip path-param segments (`{id}`, `{slug}`, etc.)
304
+ 2. If the terminal segment is an action verb (`verify`, `enroll`, `confirm`, `revoke`, etc.), use it as the verb with the preceding resource as the noun: `verify_challenge`
305
+ 3. Otherwise, use a CRUD verb from the HTTP method: GET → `list`/`get`, POST → `create`, PUT/PATCH → `update`, DELETE → `delete`
306
+ 4. Singularize the resource noun for single-resource operations
307
+
308
+ ### Hint types
309
+
310
+ Consumer-provided overrides keyed by `"METHOD /path"`:
311
+
312
+ ```typescript
313
+ interface OperationHint {
314
+ name?: string; // Override derived method name
315
+ mountOn?: string; // Remount to different service/namespace
316
+ split?: SplitHint[]; // Split union body into N typed wrappers
317
+ defaults?: Record<string, string | number | boolean>;
318
+ inferFromClient?: string[];
319
+ }
320
+ ```
321
+
322
+ ### Resolved output
323
+
324
+ ```typescript
325
+ interface ResolvedOperation {
326
+ operation: Operation; // Original IR
327
+ service: Service; // Original owning service
328
+ methodName: string; // Resolved snake_case name
329
+ mountOn: string; // Resolved target service (PascalCase)
330
+ wrappers?: ResolvedWrapper[];
331
+ }
332
+ ```
333
+
334
+ ### Mount rules
335
+
336
+ Service-level remounting maps an IR service name to a target namespace. All operations in the source service are mounted on the target unless overridden per-operation. Per-operation `mountOn` hints take precedence over service-level rules.
337
+
338
+ ### CLI
339
+
340
+ `oagen resolve --spec <path> --format table|json` outputs the full resolution table for review. Use `--format json` for programmatic consumption.
341
+
270
342
  ## Versioning
271
343
 
272
344
  The IR does not currently use a version constant. As a 0.x project with no external consumers yet, formal IR versioning is deferred until post-open-source when external emitters need a stable contract signal.
package/docs/cli.md CHANGED
@@ -122,6 +122,25 @@ oagen parse --spec openapi.yml
122
122
  | --------------- | -------- | ----------------------- | ------------------------------------------------ |
123
123
  | `--spec <path>` | No | `OPENAPI_SPEC_PATH` env | Path to an OpenAPI 3.x spec file (YAML or JSON). |
124
124
 
125
+ ## `oagen resolve`
126
+
127
+ Resolve operation names from the spec using the algorithm and optional hints. Outputs a review table (markdown) or JSON for programmatic use.
128
+
129
+ ```bash
130
+ # Markdown review table (default)
131
+ oagen resolve --spec openapi.yml
132
+
133
+ # JSON output for CI/scripting
134
+ oagen resolve --spec openapi.yml --format json
135
+ ```
136
+
137
+ | Argument | Required | Default | Description |
138
+ | ------------------- | -------- | ----------------------- | ------------------------------------------------ |
139
+ | `--spec <path>` | No | `OPENAPI_SPEC_PATH` env | Path to an OpenAPI 3.x spec file (YAML or JSON). |
140
+ | `--format <format>` | No | `table` | Output format: `table` (markdown) or `json`. |
141
+
142
+ When `oagen.config.ts` defines `operationHints` and/or `mountRules`, they are applied automatically. The table format flags operations that have no hint and are resolved purely by algorithm.
143
+
125
144
  ## Configuration (`oagen.config.ts`)
126
145
 
127
146
  Place an `oagen.config.ts` (or `.js`/`.mjs`) in your project root to register emitters, extractors, and customize pipeline behavior. The CLI loads this file at startup before any command runs.
@@ -142,11 +161,13 @@ export default config;
142
161
 
143
162
  ### Options
144
163
 
145
- | Key | Type | Description |
146
- | ---------------------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
147
- | `emitters` | `Emitter[]` | Language emitters to register. Each emitter implements the `Emitter` interface and generates SDK files for one target language. |
148
- | `extractors` | `Extractor[]` | API surface extractors to register. Each extractor parses a live SDK and produces an `ApiSurface` JSON used for compat verification. |
149
- | `emitterProject` | `string` | Path to the emitter project directory. Used by skills to scaffold new emitters, tests, and smoke runners in the correct location. |
150
- | `smokeRunners` | `Record<string, string>` | Map from language key to custom smoke runner script path. Overrides the built-in `sdk-test.ts` for `oagen verify`. Can also be set per-invocation with `--smoke-runner`. |
151
- | `operationIdTransform` | `(id: string) => string` | Custom transform for operation IDs. Receives the raw `operationId` from the spec; return the desired operation name. No additional casing conversion is applied. When omitted, `operationId` values are converted to `camelCase`. |
152
- | `docUrl` | `string` | Base URL for documentation links. When set, relative markdown paths in descriptions (e.g. `[User](/reference/authkit/user)`) are expanded to full URLs (e.g. `[User](https://workos.com/docs/reference/authkit/user)`). |
164
+ | Key | Type | Description |
165
+ | ---------------------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
166
+ | `emitters` | `Emitter[]` | Language emitters to register. Each emitter implements the `Emitter` interface and generates SDK files for one target language. |
167
+ | `extractors` | `Extractor[]` | API surface extractors to register. Each extractor parses a live SDK and produces an `ApiSurface` JSON used for compat verification. |
168
+ | `emitterProject` | `string` | Path to the emitter project directory. Used by skills to scaffold new emitters, tests, and smoke runners in the correct location. |
169
+ | `smokeRunners` | `Record<string, string>` | Map from language key to custom smoke runner script path. Overrides the built-in `sdk-test.ts` for `oagen verify`. Can also be set per-invocation with `--smoke-runner`. |
170
+ | `operationIdTransform` | `(id: string) => string` | Custom transform for operation IDs. Receives the raw `operationId` from the spec; return the desired operation name. No additional casing conversion is applied. When omitted, `operationId` values are converted to `camelCase`. |
171
+ | `docUrl` | `string` | Base URL for documentation links. When set, relative markdown paths in descriptions (e.g. `[User](/reference/authkit/user)`) are expanded to full URLs (e.g. `[User](https://workos.com/docs/reference/authkit/user)`). |
172
+ | `operationHints` | `Record<string, OperationHint>` | Per-operation overrides keyed by `"METHOD /path"`. Override derived method names, remount to a different service, or split union-body operations into typed wrappers. See [Operation Resolution](architecture/ir-types.md#operation-resolution). |
173
+ | `mountRules` | `Record<string, string>` | Service-level remounting. Maps IR service name to target service/namespace (PascalCase). All operations in the source service are mounted on the target unless overridden per-operation in `operationHints`. |