@workos/openapi-spec 0.4.0 → 0.5.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.
package/README.md CHANGED
@@ -22,7 +22,9 @@ The WorkOS API provides a comprehensive platform for enterprise-ready features i
22
22
 
23
23
  ## SDK Generation
24
24
 
25
- This repository is the canonical source for SDK generation. The consumer config at `oagen.config.ts` defines spec interpretation policy (operation hints, mount rules, transforms) and imports the plugin bundle from `@workos/oagen-emitters`.
25
+ This repository is the canonical source for SDK generation. The consumer policy lives under [`src/policy/`](src/policy/) — split into focused modules ([`operation-hints.ts`](src/policy/operation-hints.ts), [`mount-rules.ts`](src/policy/mount-rules.ts), [`model-hints.ts`](src/policy/model-hints.ts), [`transforms.ts`](src/policy/transforms.ts) for the NestJS `operationId` transform / schema-name transform / pre-IR spec overlay, and [`types.ts`](src/policy/types.ts) for vendored type definitions). The barrel [`src/policy/index.ts`](src/policy/index.ts) re-exports the whole thing and is published as `@workos/openapi-spec/policy` so downstream consumers (the WorkOS docs build's snippet generators, partner tooling) can apply the exact same naming conventions the SDKs use.
26
+
27
+ `oagen.config.ts` at the repo root is a thin shim that combines that policy with the `workosEmittersPlugin` bundle from `@workos/oagen-emitters` and feeds the whole thing to `npx oagen generate`.
26
28
 
27
29
  ### Orchestration scripts
28
30
 
@@ -122,11 +124,13 @@ node scripts/sdk-compat-pr-comment.mjs \
122
124
  ### Typical workflow
123
125
 
124
126
  1. Edit `spec/open-api-spec.yaml`
125
- 2. Update `oagen.config.ts` if the change needs new hints, mount rules, or transforms
127
+ 2. Update the relevant file under [`src/policy/`](src/policy/) if the change needs new operation hints, mount rules, model hints, schema renames, or pre-IR transforms (only touch `oagen.config.ts` for changes that affect emitter wiring, e.g. `emitterOptions`)
126
128
  3. `npm run sdk:resolve` to inspect naming
127
129
  4. `npm run sdk:generate -- --lang <lang> --output .oagen/<lang>/sdk` to generate
128
130
  5. `npm run sdk:verify -- --lang <lang> --output .oagen/<lang>/sdk` to verify
129
131
 
132
+ Changes to any file under `src/policy/` (or to `operationOverrides.node.json` or `tsdown.config.ts`) trigger a new release of `@workos/openapi-spec` the same way changes to the spec YAML do — see the publish workflow at `.github/workflows/release.yml`.
133
+
130
134
  ## Grabbing from npm
131
135
 
132
136
  The spec is published to npm as [`@workos/openapi-spec`](https://www.npmjs.com/package/@workos/openapi-spec).
@@ -137,7 +141,14 @@ npm install @workos/openapi-spec
137
141
 
138
142
  ### Usage
139
143
 
140
- The package ships TypeScript types generated from the spec, plus the raw `open-api-spec.yaml` file.
144
+ The package ships TypeScript types generated from the spec, the raw `open-api-spec.yaml` file, the SDK resolution policy module, and the Node-specific operation overrides.
145
+
146
+ | Export | What it gives you |
147
+ | ------------------------------------- | ---------------------------------------------------------------------------------------------- |
148
+ | `@workos/openapi-spec` | Generated TypeScript `paths` / `components` / `operations` types (default), spec YAML as file. |
149
+ | `@workos/openapi-spec/spec` | Path to the bundled `open-api-spec.yaml`. |
150
+ | `@workos/openapi-spec/policy` | Operation hints, mount rules, model hints, and transform functions used by SDK generation. |
151
+ | `@workos/openapi-spec/operation-overrides/node` | JSON path for the Node-emitter-specific operation overrides. |
141
152
 
142
153
  **TypeScript types:**
143
154
 
@@ -189,6 +200,34 @@ const spec = yaml.load(readFileSync(specPath, "utf8"));
189
200
  import spec from "@workos/openapi-spec/spec";
190
201
  ```
191
202
 
203
+ **Applying the SDK resolution policy (e.g. for docs code snippets):**
204
+
205
+ The `/policy` export exposes the same hints, mount rules, and transforms that drive SDK generation, so downstream tooling can resolve operations against the WorkOS spec with method names, mount targets, and parameter shapes that match the published SDKs.
206
+
207
+ ```ts
208
+ import { parseSpec, resolveOperations } from "@workos/oagen";
209
+ import {
210
+ operationHints,
211
+ mountRules,
212
+ modelHints,
213
+ nestjsOperationIdTransform,
214
+ schemaNameTransform,
215
+ transformSpec,
216
+ } from "@workos/openapi-spec/policy";
217
+
218
+ const spec = await parseSpec("./open-api-spec.yaml", {
219
+ operationIdTransform: nestjsOperationIdTransform,
220
+ schemaNameTransform,
221
+ transformSpec,
222
+ });
223
+
224
+ const resolved = resolveOperations(spec, operationHints, mountRules);
225
+ ```
226
+
227
+ `@workos/oagen` is installed transitively when you install `@workos/openapi-spec` — no separate add needed.
228
+
229
+ The vendored type aliases (`OperationHint`, `SplitHint`, `OpenApiDocument`) are inlined into the published `policy.d.mts`, so TypeScript users get full autocomplete without having to import types from `@workos/oagen` themselves.
230
+
192
231
  ## Generating Postman Collections
193
232
 
194
233
  This repository includes scripts to generate Postman collections from the OpenAPI specification. See the `scripts/postman` folder for more information.
@@ -0,0 +1,122 @@
1
+ {
2
+ "POST /organizations/{organizationId}/groups": {
3
+ "methodName": "create_group",
4
+ "optionsType": "CreateGroupOptions"
5
+ },
6
+ "GET /organizations/{organizationId}/groups": {
7
+ "methodName": "list_groups",
8
+ "optionsType": "ListGroupsOptions"
9
+ },
10
+ "GET /organizations/{organizationId}/groups/{groupId}": {
11
+ "methodName": "get_group",
12
+ "optionsType": "GetGroupOptions"
13
+ },
14
+ "PATCH /organizations/{organizationId}/groups/{groupId}": {
15
+ "methodName": "update_group",
16
+ "optionsType": "UpdateGroupOptions"
17
+ },
18
+ "DELETE /organizations/{organizationId}/groups/{groupId}": {
19
+ "methodName": "delete_group",
20
+ "optionsType": "DeleteGroupOptions"
21
+ },
22
+ "POST /organizations/{organizationId}/groups/{groupId}/organization-memberships": {
23
+ "methodName": "add_organization_membership",
24
+ "optionsType": "AddGroupOrganizationMembershipOptions"
25
+ },
26
+ "GET /organizations/{organizationId}/groups/{groupId}/organization-memberships": {
27
+ "methodName": "list_organization_memberships",
28
+ "optionsType": "ListGroupOrganizationMembershipsOptions"
29
+ },
30
+ "DELETE /organizations/{organizationId}/groups/{groupId}/organization-memberships/{omId}": {
31
+ "methodName": "remove_organization_membership",
32
+ "optionsType": "RemoveGroupOrganizationMembershipOptions"
33
+ },
34
+ "GET /user_management/organization_memberships": {
35
+ "methodName": "list_organization_memberships",
36
+ "mountOn": "UserManagement"
37
+ },
38
+ "POST /user_management/organization_memberships": {
39
+ "methodName": "create_organization_membership",
40
+ "mountOn": "UserManagement"
41
+ },
42
+ "GET /user_management/organization_memberships/{id}": {
43
+ "methodName": "get_organization_membership",
44
+ "mountOn": "UserManagement"
45
+ },
46
+ "PUT /user_management/organization_memberships/{id}": {
47
+ "methodName": "update_organization_membership",
48
+ "mountOn": "UserManagement"
49
+ },
50
+ "DELETE /user_management/organization_memberships/{id}": {
51
+ "methodName": "delete_organization_membership",
52
+ "mountOn": "UserManagement"
53
+ },
54
+ "PUT /user_management/organization_memberships/{id}/deactivate": {
55
+ "methodName": "deactivate_organization_membership",
56
+ "mountOn": "UserManagement"
57
+ },
58
+ "PUT /user_management/organization_memberships/{id}/reactivate": {
59
+ "methodName": "reactivate_organization_membership",
60
+ "mountOn": "UserManagement"
61
+ },
62
+ "GET /user_management/organization_memberships/{omId}/groups": {
63
+ "methodName": "list_groups_for_organization_membership",
64
+ "mountOn": "UserManagement"
65
+ },
66
+ "POST /vault/v1/kv": {
67
+ "methodName": "create_object",
68
+ "optionsType": "CreateObjectOptions",
69
+ "bodyFieldMap": {
70
+ "context": "keyContext"
71
+ }
72
+ },
73
+ "GET /vault/v1/kv": {
74
+ "methodName": "list_objects"
75
+ },
76
+ "GET /vault/v1/kv/{id}": {
77
+ "methodName": "read_object",
78
+ "optionsType": "ReadObjectOptions"
79
+ },
80
+ "GET /vault/v1/kv/name/{name}": {
81
+ "methodName": "read_object_by_name"
82
+ },
83
+ "GET /vault/v1/kv/{id}/metadata": {
84
+ "methodName": "describe_object",
85
+ "optionsType": "ReadObjectOptions",
86
+ "returnType": "Promise<VaultObject>",
87
+ "returnExpression": "({ ...result, value: undefined })"
88
+ },
89
+ "PUT /vault/v1/kv/{id}": {
90
+ "methodName": "update_object",
91
+ "optionsType": "UpdateObjectOptions",
92
+ "returnType": "Promise<VaultObject>",
93
+ "returnExpression": "({ ...result, value: undefined })"
94
+ },
95
+ "DELETE /vault/v1/kv/{id}": {
96
+ "methodName": "delete_object",
97
+ "optionsType": "DeleteObjectOptions"
98
+ },
99
+ "GET /vault/v1/kv/{id}/versions": {
100
+ "methodName": "list_object_versions",
101
+ "optionsType": "ReadObjectOptions",
102
+ "returnType": "Promise<VersionListResponse['data']>",
103
+ "returnDataProperty": "data"
104
+ },
105
+ "POST /vault/v1/keys/data-key": {
106
+ "optionsType": "CreateDataKeyOptions",
107
+ "returnType": "Promise<DataKeyPair>",
108
+ "returnTypeImports": [
109
+ "DataKeyPair"
110
+ ],
111
+ "returnExpression": "({ context: result.context, dataKey: { key: result.dataKey!, id: result.id }, encryptedKeys: result.encryptedKeys! })"
112
+ },
113
+ "POST /vault/v1/keys/decrypt": {
114
+ "methodName": "decrypt_data_key",
115
+ "optionsType": "DecryptDataKeyOptions",
116
+ "returnType": "Promise<DataKey>",
117
+ "returnTypeImports": [
118
+ "DataKey"
119
+ ],
120
+ "returnExpression": "({ key: result.dataKey, id: result.id })"
121
+ }
122
+ }
@@ -0,0 +1,85 @@
1
+ //#region src/policy/types.d.ts
2
+ /**
3
+ * Vendored type definitions for the policy module.
4
+ *
5
+ * `OperationHint`, `SplitHint`, and `OpenApiDocument` are inlined from
6
+ * `@workos/oagen` so the published `@workos/openapi-spec/policy` .d.mts is
7
+ * self-contained — consumers get full autocomplete without resolving types
8
+ * through `@workos/oagen`. Keep these in sync with the upstream definitions
9
+ * in `oagen/src/ir/operation-hints.ts` and `oagen/src/parser/parse.ts`; bump
10
+ * when the upstream schema gains a field this repo wants to use.
11
+ */
12
+ type OpenApiDocument = Record<string, unknown>;
13
+ interface SplitHint {
14
+ /** Wrapper method name (snake_case). */
15
+ name: string;
16
+ /** The discriminated union variant model name (e.g. 'PasswordSessionAuthenticateRequest'). */
17
+ targetVariant: string;
18
+ /** Constant body fields injected by the wrapper. */
19
+ defaults?: Record<string, string | number | boolean>;
20
+ /** Fields the SDK reads from client config at runtime. */
21
+ inferFromClient?: string[];
22
+ /** Only these body fields are exposed as method params. */
23
+ exposedParams?: string[];
24
+ /** Subset of exposedParams that should be emitted as optional. */
25
+ optionalParams?: string[];
26
+ }
27
+ interface OperationHint {
28
+ /** Override the algorithm-derived method name. */
29
+ name?: string;
30
+ /** Remount this operation to a different service/namespace (PascalCase). */
31
+ mountOn?: string;
32
+ /** Split a union-body operation into N typed wrapper methods. */
33
+ split?: SplitHint[];
34
+ /** Inject constant body defaults (e.g. { grant_type: 'password' }). */
35
+ defaults?: Record<string, string | number | boolean>;
36
+ /** Fields the SDK reads from client config at runtime (e.g. ['client_id']). */
37
+ inferFromClient?: string[];
38
+ /**
39
+ * Marks this operation as a URL builder rather than a real HTTP call.
40
+ * Emitters generate a method that returns the constructed URL (typically
41
+ * as a string) without performing any I/O. Used for OAuth-style redirect
42
+ * endpoints such as /sso/authorize and /user_management/sessions/logout.
43
+ */
44
+ urlBuilder?: boolean;
45
+ }
46
+ //#endregion
47
+ //#region src/policy/operation-hints.d.ts
48
+ /**
49
+ * Per-operation overrides for the operation resolver. Keyed by `"METHOD /path"`.
50
+ * Only operations that need overrides are listed; the algorithm handles the rest.
51
+ */
52
+ declare const operationHints: Record<string, OperationHint>;
53
+ //#endregion
54
+ //#region src/policy/mount-rules.d.ts
55
+ /**
56
+ * Service-level remounting. Maps IR service name → target service/namespace
57
+ * (PascalCase). All operations in the source service are mounted on the
58
+ * target unless overridden per-operation in {@link operationHints}.
59
+ */
60
+ declare const mountRules: Record<string, string>;
61
+ //#endregion
62
+ //#region src/policy/model-hints.d.ts
63
+ /**
64
+ * Model placement hints — used by emitters that call
65
+ * `assignModelsToServices` so hinted models land in the configured service
66
+ * instead of the default first-reference winner.
67
+ */
68
+ declare const modelHints: Record<string, string>;
69
+ //#endregion
70
+ //#region src/policy/transforms.d.ts
71
+ /**
72
+ * NestJS-style operationId transform. Strips "Controller" and extracts the
73
+ * action after the first underscore: `FooController_bar` -> `bar`.
74
+ */
75
+ declare function nestjsOperationIdTransform(id: string): string;
76
+ declare function schemaNameTransform(name: string): string;
77
+ /**
78
+ * Pre-IR spec overlay — patch around upstream spec quirks that would otherwise
79
+ * emit breaking SDK changes. See docs/breaking-change-playbook.md and the
80
+ * oagen `transformSpec` docs for usage. Reach for this only when the upstream
81
+ * fix can't land in time AND the change is genuinely additive.
82
+ */
83
+ declare function transformSpec(spec: OpenApiDocument): OpenApiDocument;
84
+ //#endregion
85
+ export { type OpenApiDocument, type OperationHint, type SplitHint, modelHints, mountRules, nestjsOperationIdTransform, operationHints, schemaNameTransform, transformSpec };
@@ -0,0 +1,398 @@
1
+ import { toCamelCase } from "@workos/oagen";
2
+ //#region src/policy/operation-hints.ts
3
+ /**
4
+ * Per-operation overrides for the operation resolver. Keyed by `"METHOD /path"`.
5
+ * Only operations that need overrides are listed; the algorithm handles the rest.
6
+ */
7
+ const operationHints = {
8
+ "POST /radar/lists/{type}/{action}": { name: "add_list_entry" },
9
+ "DELETE /radar/lists/{type}/{action}": { name: "remove_list_entry" },
10
+ "GET /sso/authorize": {
11
+ name: "get_authorization_url",
12
+ defaults: { response_type: "code" },
13
+ inferFromClient: ["client_id"],
14
+ urlBuilder: true
15
+ },
16
+ "GET /sso/logout": {
17
+ name: "get_logout_url",
18
+ urlBuilder: true
19
+ },
20
+ "GET /sso/profile": { name: "get_profile" },
21
+ "POST /sso/token": {
22
+ name: "get_profile_and_token",
23
+ defaults: { grant_type: "authorization_code" },
24
+ inferFromClient: ["client_id", "client_secret"]
25
+ },
26
+ "GET /sso/jwks/{clientId}": { name: "get_jwks" },
27
+ "GET /user_management/authorize": {
28
+ name: "get_authorization_url",
29
+ defaults: { response_type: "code" },
30
+ inferFromClient: ["client_id"],
31
+ urlBuilder: true
32
+ },
33
+ "GET /user_management/sessions/logout": {
34
+ name: "get_logout_url",
35
+ urlBuilder: true
36
+ },
37
+ "POST /portal/generate_link": { name: "generate_link" },
38
+ "GET /organizations/{organizationId}/feature-flags": { name: "list_organization_feature_flags" },
39
+ "GET /user_management/users/{userId}/feature-flags": { name: "list_user_feature_flags" },
40
+ "GET /organizations/external_id/{external_id}": { name: "get_organization_by_external_id" },
41
+ "GET /user_management/users/external_id/{external_id}": { name: "get_user_by_external_id" },
42
+ "GET /authorization/roles": { name: "list_environment_roles" },
43
+ "POST /authorization/roles": { name: "create_environment_role" },
44
+ "GET /authorization/roles/{slug}": { name: "get_environment_role" },
45
+ "PATCH /authorization/roles/{slug}": { name: "update_environment_role" },
46
+ "PUT /authorization/roles/{slug}/permissions": { name: "set_environment_role_permissions" },
47
+ "POST /authorization/roles/{slug}/permissions": { name: "add_environment_role_permission" },
48
+ "POST /authorization/permissions": { name: "create_permission" },
49
+ "POST /authorization/resources": { name: "create_resource" },
50
+ "POST /authorization/organization_memberships/{organization_membership_id}/check": { name: "check" },
51
+ "GET /authorization/organization_memberships/{organization_membership_id}/resources": { name: "list_resources_for_membership" },
52
+ "GET /authorization/organization_memberships/{organization_membership_id}/resources/{resource_id}/permissions": { name: "list_effective_permissions" },
53
+ "GET /authorization/organization_memberships/{organization_membership_id}/resources/{resource_type_slug}/{external_id}/permissions": { name: "list_effective_permissions_by_external_id" },
54
+ "GET /authorization/organization_memberships/{organization_membership_id}/role_assignments": { name: "list_role_assignments" },
55
+ "POST /authorization/organization_memberships/{organization_membership_id}/role_assignments": { name: "assign_role" },
56
+ "DELETE /authorization/organization_memberships/{organization_membership_id}/role_assignments": { name: "remove_role" },
57
+ "DELETE /authorization/organization_memberships/{organization_membership_id}/role_assignments/{role_assignment_id}": { name: "remove_role_assignment" },
58
+ "POST /authorization/organizations/{organizationId}/roles": { name: "create_organization_role" },
59
+ "PUT /authorization/organizations/{organizationId}/roles/{slug}/permissions": { name: "set_organization_role_permissions" },
60
+ "POST /authorization/organizations/{organizationId}/roles/{slug}/permissions": { name: "add_organization_role_permission" },
61
+ "DELETE /authorization/organizations/{organizationId}/roles/{slug}/permissions/{permissionSlug}": { name: "remove_organization_role_permission" },
62
+ "GET /authorization/organizations/{organization_id}/resources/{resource_type_slug}/{external_id}": { name: "get_resource_by_external_id" },
63
+ "PATCH /authorization/organizations/{organization_id}/resources/{resource_type_slug}/{external_id}": { name: "update_resource_by_external_id" },
64
+ "DELETE /authorization/organizations/{organization_id}/resources/{resource_type_slug}/{external_id}": { name: "delete_resource_by_external_id" },
65
+ "GET /authorization/organizations/{organization_id}/resources/{resource_type_slug}/{external_id}/organization_memberships": { name: "list_memberships_for_resource_by_external_id" },
66
+ "GET /authorization/organizations/{organization_id}/resources/{resource_type_slug}/{external_id}/role_assignments": { name: "list_role_assignments_for_resource_by_external_id" },
67
+ "GET /authorization/resources/{resource_id}/organization_memberships": { name: "list_memberships_for_resource" },
68
+ "GET /authorization/resources/{resource_id}/role_assignments": { name: "list_role_assignments_for_resource" },
69
+ "POST /user_management/users": { name: "create_user" },
70
+ "POST /user_management/invitations": { name: "send_invitation" },
71
+ "GET /user_management/invitations/by_token/{token}": { name: "find_invitation_by_token" },
72
+ "POST /user_management/users/{id}/email_verification/send": { name: "send_verification_email" },
73
+ "POST /user_management/users/{id}/email_verification/confirm": { name: "verify_email" },
74
+ "POST /user_management/password_reset": { name: "reset_password" },
75
+ "POST /user_management/password_reset/confirm": { name: "confirm_password_reset" },
76
+ "GET /user_management/users/{id}/sessions": { name: "list_sessions" },
77
+ "GET /user_management/users/{id}/identities": { name: "get_user_identities" },
78
+ "POST /user_management/cors_origins": { name: "create_cors_origin" },
79
+ "POST /user_management/redirect_uris": { name: "create_redirect_uri" },
80
+ "POST /organizations": { name: "create_organization" },
81
+ "GET /directory_groups": { name: "list_groups" },
82
+ "GET /directory_groups/{id}": { name: "get_group" },
83
+ "GET /directory_users": { name: "list_users" },
84
+ "GET /directory_users/{id}": { name: "get_user" },
85
+ "POST /audit_logs/events": { name: "create_event" },
86
+ "POST /audit_logs/exports": { name: "create_export" },
87
+ "POST /audit_logs/actions/{actionName}/schemas": { name: "create_schema" },
88
+ "POST /feature-flags/{slug}/targets/{resourceId}": { name: "add_flag_target" },
89
+ "DELETE /feature-flags/{slug}/targets/{resourceId}": { name: "remove_flag_target" },
90
+ "GET /organizations/{id}/audit_log_configuration": { name: "get_audit_log_configuration" },
91
+ "GET /organizations/{id}/audit_logs_retention": {
92
+ name: "get_organization_audit_logs_retention",
93
+ mountOn: "AuditLogs"
94
+ },
95
+ "PUT /organizations/{id}/audit_logs_retention": { mountOn: "AuditLogs" },
96
+ "POST /user_management/authenticate": { split: [
97
+ {
98
+ name: "authenticate_with_password",
99
+ targetVariant: "PasswordSessionAuthenticateRequest",
100
+ defaults: { grant_type: "password" },
101
+ inferFromClient: ["client_id", "client_secret"],
102
+ exposedParams: [
103
+ "email",
104
+ "password",
105
+ "invitation_token",
106
+ "ip_address",
107
+ "device_id",
108
+ "user_agent"
109
+ ],
110
+ optionalParams: [
111
+ "invitation_token",
112
+ "ip_address",
113
+ "device_id",
114
+ "user_agent"
115
+ ]
116
+ },
117
+ {
118
+ name: "authenticate_with_code",
119
+ targetVariant: "AuthorizationCodeSessionAuthenticateRequest",
120
+ defaults: { grant_type: "authorization_code" },
121
+ inferFromClient: ["client_id", "client_secret"],
122
+ exposedParams: [
123
+ "code",
124
+ "code_verifier",
125
+ "invitation_token",
126
+ "ip_address",
127
+ "device_id",
128
+ "user_agent"
129
+ ],
130
+ optionalParams: [
131
+ "code_verifier",
132
+ "invitation_token",
133
+ "ip_address",
134
+ "device_id",
135
+ "user_agent"
136
+ ]
137
+ },
138
+ {
139
+ name: "authenticate_with_refresh_token",
140
+ targetVariant: "RefreshTokenSessionAuthenticateRequest",
141
+ defaults: { grant_type: "refresh_token" },
142
+ inferFromClient: ["client_id", "client_secret"],
143
+ exposedParams: [
144
+ "refresh_token",
145
+ "organization_id",
146
+ "ip_address",
147
+ "device_id",
148
+ "user_agent"
149
+ ],
150
+ optionalParams: [
151
+ "organization_id",
152
+ "ip_address",
153
+ "device_id",
154
+ "user_agent"
155
+ ]
156
+ },
157
+ {
158
+ name: "authenticate_with_magic_auth",
159
+ targetVariant: "MagicAuthCodeSessionAuthenticateRequest",
160
+ defaults: { grant_type: "urn:workos:oauth:grant-type:magic-auth:code" },
161
+ inferFromClient: ["client_id", "client_secret"],
162
+ exposedParams: [
163
+ "code",
164
+ "email",
165
+ "invitation_token",
166
+ "ip_address",
167
+ "device_id",
168
+ "user_agent"
169
+ ],
170
+ optionalParams: [
171
+ "invitation_token",
172
+ "ip_address",
173
+ "device_id",
174
+ "user_agent"
175
+ ]
176
+ },
177
+ {
178
+ name: "authenticate_with_email_verification",
179
+ targetVariant: "EmailVerificationCodeSessionAuthenticateRequest",
180
+ defaults: { grant_type: "urn:workos:oauth:grant-type:email-verification:code" },
181
+ inferFromClient: ["client_id", "client_secret"],
182
+ exposedParams: [
183
+ "code",
184
+ "pending_authentication_token",
185
+ "ip_address",
186
+ "device_id",
187
+ "user_agent"
188
+ ],
189
+ optionalParams: [
190
+ "ip_address",
191
+ "device_id",
192
+ "user_agent"
193
+ ]
194
+ },
195
+ {
196
+ name: "authenticate_with_totp",
197
+ targetVariant: "MFATotpSessionAuthenticateRequest",
198
+ defaults: { grant_type: "urn:workos:oauth:grant-type:mfa-totp" },
199
+ inferFromClient: ["client_id", "client_secret"],
200
+ exposedParams: [
201
+ "code",
202
+ "pending_authentication_token",
203
+ "authentication_challenge_id",
204
+ "ip_address",
205
+ "device_id",
206
+ "user_agent"
207
+ ],
208
+ optionalParams: [
209
+ "ip_address",
210
+ "device_id",
211
+ "user_agent"
212
+ ]
213
+ },
214
+ {
215
+ name: "authenticate_with_organization_selection",
216
+ targetVariant: "OrganizationSelectionSessionAuthenticateRequest",
217
+ defaults: { grant_type: "urn:workos:oauth:grant-type:organization-selection" },
218
+ inferFromClient: ["client_id", "client_secret"],
219
+ exposedParams: [
220
+ "pending_authentication_token",
221
+ "organization_id",
222
+ "ip_address",
223
+ "device_id",
224
+ "user_agent"
225
+ ],
226
+ optionalParams: [
227
+ "ip_address",
228
+ "device_id",
229
+ "user_agent"
230
+ ]
231
+ },
232
+ {
233
+ name: "authenticate_with_device_code",
234
+ targetVariant: "DeviceCodeSessionAuthenticateRequest",
235
+ defaults: { grant_type: "urn:ietf:params:oauth:grant-type:device_code" },
236
+ inferFromClient: ["client_id"],
237
+ exposedParams: [
238
+ "device_code",
239
+ "ip_address",
240
+ "device_id",
241
+ "user_agent"
242
+ ],
243
+ optionalParams: [
244
+ "ip_address",
245
+ "device_id",
246
+ "user_agent"
247
+ ]
248
+ }
249
+ ] },
250
+ "POST /connect/applications": { split: [{
251
+ name: "create_oauth_application",
252
+ targetVariant: "CreateOAuthApplication",
253
+ defaults: { application_type: "oauth" },
254
+ exposedParams: [
255
+ "name",
256
+ "is_first_party",
257
+ "description",
258
+ "scopes",
259
+ "redirect_uris",
260
+ "uses_pkce",
261
+ "organization_id"
262
+ ]
263
+ }, {
264
+ name: "create_m2m_application",
265
+ targetVariant: "CreateM2MApplication",
266
+ defaults: { application_type: "m2m" },
267
+ exposedParams: [
268
+ "name",
269
+ "organization_id",
270
+ "description",
271
+ "scopes"
272
+ ]
273
+ }] }
274
+ };
275
+ //#endregion
276
+ //#region src/policy/mount-rules.ts
277
+ /**
278
+ * Service-level remounting. Maps IR service name → target service/namespace
279
+ * (PascalCase). All operations in the source service are mounted on the
280
+ * target unless overridden per-operation in {@link operationHints}.
281
+ */
282
+ const mountRules = {
283
+ MultiFactorAuthChallenges: "MultiFactorAuth",
284
+ Permissions: "Authorization",
285
+ WorkosConnect: "Connect",
286
+ Applications: "Connect",
287
+ ApplicationClientSecrets: "Connect",
288
+ Connections: "SSO",
289
+ Directories: "DirectorySync",
290
+ DirectoryGroups: "DirectorySync",
291
+ DirectoryUsers: "DirectorySync",
292
+ FeatureFlagsTargets: "FeatureFlags",
293
+ OrganizationsFeatureFlags: "FeatureFlags",
294
+ UserManagementUsersFeatureFlags: "FeatureFlags",
295
+ OrganizationsApiKeys: "ApiKeys",
296
+ UserManagementSessionTokens: "UserManagement",
297
+ UserManagementAuthentication: "UserManagement",
298
+ UserManagementCorsOrigins: "UserManagement",
299
+ UserManagementUsers: "UserManagement",
300
+ UserManagementInvitations: "UserManagement",
301
+ UserManagementJWTTemplate: "UserManagement",
302
+ UserManagementMagicAuth: "UserManagement",
303
+ UserManagementOrganizationMembership: "OrganizationMembership",
304
+ UserManagementOrganizationMembershipGroups: "OrganizationMembership",
305
+ UserManagementRedirectUris: "UserManagement",
306
+ UserManagementUsersAuthorizedApplications: "UserManagement",
307
+ UserManagementDataProviders: "Pipes",
308
+ UserManagementMultiFactorAuthentication: "MultiFactorAuth"
309
+ };
310
+ //#endregion
311
+ //#region src/policy/model-hints.ts
312
+ /**
313
+ * Model placement hints — used by emitters that call
314
+ * `assignModelsToServices` so hinted models land in the configured service
315
+ * instead of the default first-reference winner.
316
+ */
317
+ const modelHints = {
318
+ User: "UserManagementUsers",
319
+ UserOrganizationMembershipBaseListData: "Groups",
320
+ UserApiKeyOwner: "UserManagementUsers"
321
+ };
322
+ //#endregion
323
+ //#region src/policy/transforms.ts
324
+ /**
325
+ * NestJS-style operationId transform. Strips "Controller" and extracts the
326
+ * action after the first underscore: `FooController_bar` -> `bar`.
327
+ */
328
+ function nestjsOperationIdTransform(id) {
329
+ const stripped = id.replace(/Controller/g, "");
330
+ const idx = stripped.indexOf("_");
331
+ return idx !== -1 ? toCamelCase(stripped.slice(idx + 1)) : toCamelCase(stripped);
332
+ }
333
+ /**
334
+ * Explicit renames plus suffix stripping applied during IR schema extraction.
335
+ * Keeps `Dto`/`Json`/`Urn…` schema names consistent with the names already
336
+ * exposed by the published SDKs.
337
+ */
338
+ const COLLISION_RENAMES = {
339
+ Error: "ErrorResponse",
340
+ Object: "VaultObject",
341
+ OrganizationDto: "OrganizationInput",
342
+ RedirectUriDto: "RedirectUriInput",
343
+ AuditLogSchemaDto: "AuditLogSchemaInput",
344
+ AuditLogSchemaActorDto: "AuditLogSchemaActorInput",
345
+ AuditLogSchemaTargetDto: "AuditLogSchemaTargetInput",
346
+ ListData: "Role",
347
+ ListModel: "RoleList",
348
+ EventListListMetadata: "EventListMetadata",
349
+ RadarAction: "RadarListAction",
350
+ RadarType: "RadarListType"
351
+ };
352
+ function schemaNameTransform(name) {
353
+ if (COLLISION_RENAMES[name]) return COLLISION_RENAMES[name];
354
+ return name.replace(/Dto/g, "").replace(/DTO/g, "").replace(/Json$/, "").replace(/^Urn(?:IetfParams|Workos)O[Aa]uthGrantType/, "");
355
+ }
356
+ /**
357
+ * Pre-IR spec overlay — patch around upstream spec quirks that would otherwise
358
+ * emit breaking SDK changes. See docs/breaking-change-playbook.md and the
359
+ * oagen `transformSpec` docs for usage. Reach for this only when the upstream
360
+ * fix can't land in time AND the change is genuinely additive.
361
+ */
362
+ function transformSpec(spec) {
363
+ const schemas = spec.components?.schemas;
364
+ const paths = spec.paths;
365
+ if (!schemas || !paths) return spec;
366
+ const forkedListRef = "#/components/schemas/UserlandUserOrganizationMembershipBaseWithUserList";
367
+ const originalListRef = "#/components/schemas/UserlandUserOrganizationMembershipBaseList";
368
+ for (const pathItem of Object.values(paths)) for (const op of Object.values(pathItem)) {
369
+ const schema = op.responses?.["200"]?.content?.["application/json"]?.schema;
370
+ if (schema?.$ref === forkedListRef) schema.$ref = originalListRef;
371
+ }
372
+ const itemProps = schemas["UserlandUserOrganizationMembershipBaseList"]?.properties?.data?.items;
373
+ if (itemProps?.properties && !itemProps.properties.user) {
374
+ itemProps.properties.user = {
375
+ $ref: "#/components/schemas/UserlandUser",
376
+ description: "The user that belongs to the organization through this membership."
377
+ };
378
+ if (itemProps.required && !itemProps.required.includes("user")) itemProps.required.push("user");
379
+ }
380
+ delete schemas["UserlandUserOrganizationMembershipBaseWithUser"];
381
+ delete schemas["UserlandUserOrganizationMembershipBaseWithUserList"];
382
+ if (schemas["JwtTemplate"] && !schemas["JwtTemplateResponse"]) {
383
+ schemas["JwtTemplateResponse"] = schemas["JwtTemplate"];
384
+ delete schemas["JwtTemplate"];
385
+ const oldRef = "#/components/schemas/JwtTemplate";
386
+ const newRef = "#/components/schemas/JwtTemplateResponse";
387
+ for (const pathItem of Object.values(paths)) for (const op of Object.values(pathItem)) {
388
+ const responses = op.responses;
389
+ for (const response of Object.values(responses ?? {})) {
390
+ const schema = response.content?.["application/json"]?.schema;
391
+ if (schema?.$ref === oldRef) schema.$ref = newRef;
392
+ }
393
+ }
394
+ }
395
+ return spec;
396
+ }
397
+ //#endregion
398
+ export { modelHints, mountRules, nestjsOperationIdTransform, operationHints, schemaNameTransform, transformSpec };
package/package.json CHANGED
@@ -1,23 +1,67 @@
1
1
  {
2
2
  "name": "@workos/openapi-spec",
3
- "version": "0.4.0",
4
- "description": "WorkOS OpenAPI specification and TypeScript types",
3
+ "version": "0.5.0",
4
+ "type": "module",
5
+ "description": "WorkOS OpenAPI specification, TypeScript types, and SDK resolution policy",
5
6
  "license": "MIT",
6
7
  "repository": {
7
8
  "type": "git",
8
9
  "url": "git+https://github.com/workos/openapi-spec.git"
9
10
  },
11
+ "types": "./dist/types.d.ts",
10
12
  "files": [
11
- "open-api-spec.yaml",
12
- "types.d.ts"
13
+ "dist/"
13
14
  ],
14
- "types": "types.d.ts",
15
15
  "exports": {
16
16
  ".": {
17
- "types": "./types.d.ts",
18
- "default": "./open-api-spec.yaml"
17
+ "types": "./dist/types.d.ts",
18
+ "default": "./dist/open-api-spec.yaml"
19
19
  },
20
- "./spec": "./open-api-spec.yaml",
20
+ "./spec": "./dist/open-api-spec.yaml",
21
+ "./policy": {
22
+ "types": "./dist/policy.d.mts",
23
+ "import": "./dist/policy.mjs"
24
+ },
25
+ "./operation-overrides/node": "./dist/operationOverrides.node.json",
21
26
  "./package.json": "./package.json"
27
+ },
28
+ "dependencies": {
29
+ "@workos/oagen": "^0.22.0"
30
+ },
31
+ "scripts": {
32
+ "build:policy": "tsdown",
33
+ "generate:postman": "tsx scripts/postman/generate.ts",
34
+ "generate:postman:local": "tsx scripts/postman/generate.ts --local",
35
+ "sdk:resolve": "npx oagen resolve --spec spec/open-api-spec.yaml",
36
+ "sdk:generate": "bash scripts/sdk-generate.sh",
37
+ "sdk:diff": "bash scripts/sdk-diff.sh",
38
+ "sdk:verify": "bash scripts/sdk-verify.sh",
39
+ "sdk:extract": "bash scripts/sdk-extract.sh",
40
+ "sdk:compat-extract": "bash scripts/sdk-compat-extract.sh",
41
+ "sdk:compat-diff": "bash scripts/sdk-compat-diff.sh",
42
+ "sdk:compat-summary": "bash scripts/sdk-compat-summary.sh",
43
+ "sdk:compat-report": "node scripts/sdk-compat-pr-comment.mjs --artifacts-root .oagen --output compat-report.md --build-result success",
44
+ "sdk:languages": "node -e \"console.log(JSON.parse(require('fs').readFileSync('.github/sdk-matrix.json','utf8')).map(e=>e.language).join('\\n'))\"",
45
+ "sdk:generate-all": "bash scripts/sdk-generate-all.sh --parent-dir",
46
+ "sdk:check": "npx oagen resolve --spec spec/open-api-spec.yaml --format json > /dev/null && echo 'Config loaded successfully'",
47
+ "dev:link": "npm link @workos/oagen @workos/oagen-emitters",
48
+ "dev:unlink": "npm install",
49
+ "prepare": "husky"
50
+ },
51
+ "overrides": {
52
+ "js-yaml": "^4.2.0",
53
+ "lodash": "^4.17.23"
54
+ },
55
+ "devDependencies": {
56
+ "@types/js-yaml": "^4.0.9",
57
+ "@types/node": "^25.9.1",
58
+ "@workos/oagen-emitters": "^0.16.0",
59
+ "diff2html": "^3.4.56",
60
+ "husky": "^9.1.7",
61
+ "js-yaml": "^4.2.0",
62
+ "openapi-to-postmanv2": "^6.0.1",
63
+ "tsdown": "^0.22.1",
64
+ "tsx": "^4.22.4",
65
+ "typescript": "^6.0.3"
22
66
  }
23
67
  }
File without changes
File without changes