Language emitters for oagen; generates SDK code from OpenAPI specs.
| Language | Emitter | Extractor | Smoke Test |
|---|---|---|---|
| Node/TypeScript | nodeEmitter |
nodeExtractor |
smoke/sdk-node.ts |
npm install
npm test # run emitter unit tests
npm run typecheck # verify typesThis repo depends on a published @workos/oagen by default.
If you are actively changing the sibling checkout at ../oagen, you can switch this repo to a local linked copy:
npm run oagen:use:localThat script:
- builds
../oagen - links it into this repo with
npm link
@workos/oagen exports from dist/, so rebuild the local repo after changes:
npm run oagen:build:localTo go back to the published package from npm:
npm run oagen:use:publishedGit does not provide a standard post-push hook, so the reliable way to push with the published package and then restore the local link is the wrapper script:
npm run git:push -- <git push args>Example:
npm run git:push -- origin HEADThat command:
- switches to the published
@workos/oagen - runs
git push ... - restores the local
../oagenlink on exit, even if the push fails
# 1. Extract the live SDK's public API surface
npm run sdk:extract:node
# 2. Generate and integrate
npm run sdk:generate:node
# 3. Verify
npm run sdk:verify:node# 1. Incremental generation from spec changes
npm run sdk:diff:node
# 2. Verify
npm run sdk:verify:nodeIf you rename methods, add exports, or change the live SDK's public API by hand:
# 1. Re-extract the baseline so the overlay stays in sync
npm run sdk:extract:node
# 2. Regenerate (the overlay will use the updated baseline)
npm run sdk:generate:nodeAll SDK commands are in package.json under scripts. Replace node with the target language for other SDKs.
oagen extract --lang node --sdk-path ../backend/workos-node --output ./sdk-node-surface.jsonExtracts the live SDK's public API surface (classes, interfaces, type aliases, enums, exports) into sdk-node-surface.json. This is per-language — each language has its own extractor that understands the language's public surface conventions (TypeScript exports, Ruby public methods, Python __all__, etc.).
When to run: Before the first generation, and whenever the live SDK's public API changes (hand-written additions, renamed methods, new exports).
oagen generate --lang node --output ./sdk --namespace workos \
--spec ../openapi-spec/spec/open-api-spec.yaml \
--api-surface ./sdk-node-surface.json \
--target ../backend/workos-nodeFull generation from the OpenAPI spec. Produces a standalone SDK at ./sdk/ and integrates new interface, serializer, enum, and fixture files into the live SDK at --target.
What gets integrated: New type definitions (interfaces, serializers, enums, fixtures) that don't already exist in the live SDK. Existing files are never modified.
What stays standalone: Resource classes, tests, client, barrel exports, error classes, and common utilities remain in ./sdk/ only. The developer wires up resource classes and client accessors manually.
When to run: First-time setup, or when you want a full regeneration (e.g., after a major spec overhaul).
oagen diff --old ./sdk/spec-snapshot.yaml --new ../openapi-spec/spec/open-api-spec.yaml \
--lang node --output ./sdk \
--target ../backend/workos-node \
--api-surface ./sdk-node-surface.jsonIncremental generation — compares the previous spec snapshot against the current spec and only regenerates files affected by the changes.
Requires a prior sdk:generate:node run which creates ./sdk/spec-snapshot.yaml.
When to run: After the OpenAPI spec is updated (new endpoints, changed models, etc.).
oagen verify --lang node --output ./sdk \
--spec ../openapi-spec/spec/open-api-spec.yaml \
--api-surface ./sdk-node-surface.jsonRuns compat verification (checks that generated types preserve the live SDK's public API surface) and smoke tests (checks wire-level HTTP behavior).
When to run: After any generation to verify correctness, or in CI.
Emitters read runtime policy (retry, errors, telemetry, pagination, etc.) from ctx.spec.sdk rather than hardcoding values. The ApiSpec.sdk field is always populated with defaults from defaultSdkBehavior().
function generateHttpClient(ctx: EmitterContext) {
const sdk = ctx.spec.sdk;
const retryCodes = sdk.retry.retryableStatusCodes; // [429, 500, 502, 503, 504]
const timeout = sdk.timeout.defaultTimeoutSeconds; // 60
}Per-language overrides go in the SDK's oagen.config.ts:
// Example: Python SDK overrides
export default {
sdkBehavior: {
retry: { backoff: { initialDelay: 0.5, maxDelay: 8.0 } },
timeout: {
defaultTimeoutSeconds: 30,
timeoutEnvVar: "WORKOS_REQUEST_TIMEOUT",
},
},
};See @workos/oagen's src/ir/sdk-behavior.ts for all interfaces and default values.
oagen.config.ts defines operationHints and mountRules that control how operations are named and organized across all SDKs.
When the spec adds a new endpoint and the algorithm-derived name is wrong, add an entry to operationHints:
const operationHints: Record<string, OperationHint> = {
// Override derived name
"GET /sso/authorize": { name: "get_authorization_url" },
// Remount to a different service
"GET /organizations/{id}/audit_logs_retention": { mountOn: "AuditLogs" },
};For endpoints that accept a discriminated union body (multiple request shapes), use split:
'POST /user_management/authenticate': {
split: [
{
name: 'authenticate_with_password',
targetVariant: 'PasswordSessionAuthenticateRequest',
defaults: { grant_type: 'password' },
inferFromClient: ['client_id', 'client_secret'],
exposedParams: ['email', 'password', 'invitation_token'],
},
// ... more variants
],
},Instead of adding mountOn to every operation individually, use mountRules for service-level remounting:
const mountRules: Record<string, string> = {
Connections: "SSO", // All Connections ops → SSO namespace
DirectoryGroups: "DirectorySync",
UserManagementUsers: "UserManagement",
};npx oagen resolve --spec ../openapi-spec/spec/open-api-spec.yaml --format tableUse the oagen skills:
claude --plugin-dir ../oagen
/oagen:generate-sdk <language>This orchestrates: emitter scaffolding → extractor → compat verification → smoke tests → integration.