@workos/oagen-emitters 0.2.1 → 0.3.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/.husky/pre-commit +1 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +8 -0
- package/README.md +129 -0
- package/dist/index.d.mts +10 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +11893 -3226
- package/dist/index.mjs.map +1 -1
- package/docs/sdk-architecture/go.md +338 -0
- package/docs/sdk-architecture/php.md +315 -0
- package/docs/sdk-architecture/python.md +511 -0
- package/oagen.config.ts +298 -2
- package/package.json +9 -5
- package/scripts/generate-php.js +13 -0
- package/scripts/git-push-with-published-oagen.sh +21 -0
- package/smoke/sdk-go.ts +116 -42
- package/smoke/sdk-php.ts +28 -26
- package/smoke/sdk-python.ts +5 -2
- package/src/go/client.ts +141 -0
- package/src/go/enums.ts +196 -0
- package/src/go/fixtures.ts +212 -0
- package/src/go/index.ts +81 -0
- package/src/go/manifest.ts +36 -0
- package/src/go/models.ts +254 -0
- package/src/go/naming.ts +191 -0
- package/src/go/resources.ts +827 -0
- package/src/go/tests.ts +751 -0
- package/src/go/type-map.ts +82 -0
- package/src/go/wrappers.ts +261 -0
- package/src/index.ts +3 -0
- package/src/node/client.ts +78 -115
- package/src/node/enums.ts +9 -0
- package/src/node/errors.ts +37 -232
- package/src/node/field-plan.ts +726 -0
- package/src/node/fixtures.ts +9 -1
- package/src/node/index.ts +2 -9
- package/src/node/models.ts +178 -21
- package/src/node/naming.ts +49 -111
- package/src/node/resources.ts +374 -364
- package/src/node/sdk-errors.ts +41 -0
- package/src/node/tests.ts +32 -12
- package/src/node/type-map.ts +4 -2
- package/src/node/utils.ts +13 -71
- package/src/node/wrappers.ts +151 -0
- package/src/php/client.ts +171 -0
- package/src/php/enums.ts +67 -0
- package/src/php/errors.ts +9 -0
- package/src/php/fixtures.ts +181 -0
- package/src/php/index.ts +96 -0
- package/src/php/manifest.ts +36 -0
- package/src/php/models.ts +310 -0
- package/src/php/naming.ts +298 -0
- package/src/php/resources.ts +561 -0
- package/src/php/tests.ts +533 -0
- package/src/php/type-map.ts +90 -0
- package/src/php/utils.ts +18 -0
- package/src/php/wrappers.ts +151 -0
- package/src/python/client.ts +337 -0
- package/src/python/enums.ts +313 -0
- package/src/python/fixtures.ts +196 -0
- package/src/python/index.ts +95 -0
- package/src/python/manifest.ts +38 -0
- package/src/python/models.ts +688 -0
- package/src/python/naming.ts +209 -0
- package/src/python/resources.ts +1322 -0
- package/src/python/tests.ts +1335 -0
- package/src/python/type-map.ts +93 -0
- package/src/python/wrappers.ts +191 -0
- package/src/shared/model-utils.ts +255 -0
- package/src/shared/naming-utils.ts +107 -0
- package/src/shared/non-spec-services.ts +54 -0
- package/src/shared/resolved-ops.ts +109 -0
- package/src/shared/wrapper-utils.ts +59 -0
- package/test/go/client.test.ts +92 -0
- package/test/go/enums.test.ts +132 -0
- package/test/go/errors.test.ts +9 -0
- package/test/go/models.test.ts +265 -0
- package/test/go/resources.test.ts +408 -0
- package/test/go/tests.test.ts +143 -0
- package/test/node/client.test.ts +18 -12
- package/test/node/enums.test.ts +2 -0
- package/test/node/errors.test.ts +2 -41
- package/test/node/models.test.ts +2 -0
- package/test/node/naming.test.ts +23 -0
- package/test/node/resources.test.ts +99 -69
- package/test/node/serializers.test.ts +3 -1
- package/test/node/type-map.test.ts +11 -0
- package/test/php/client.test.ts +94 -0
- package/test/php/enums.test.ts +173 -0
- package/test/php/errors.test.ts +9 -0
- package/test/php/models.test.ts +497 -0
- package/test/php/resources.test.ts +644 -0
- package/test/php/tests.test.ts +118 -0
- package/test/python/client.test.ts +200 -0
- package/test/python/enums.test.ts +228 -0
- package/test/python/errors.test.ts +16 -0
- package/test/python/manifest.test.ts +74 -0
- package/test/python/models.test.ts +716 -0
- package/test/python/resources.test.ts +617 -0
- package/test/python/tests.test.ts +202 -0
- package/src/node/common.ts +0 -273
- package/src/node/config.ts +0 -71
- package/src/node/serializers.ts +0 -746
package/oagen.config.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import type { OagenConfig } from '@workos/oagen';
|
|
1
|
+
import type { OagenConfig, OperationHint } from '@workos/oagen';
|
|
2
2
|
import { toCamelCase } from '@workos/oagen';
|
|
3
3
|
import { nodeEmitter } from './src/node/index.js';
|
|
4
|
+
import { pythonEmitter } from './src/python/index.js';
|
|
5
|
+
import { phpEmitter } from './src/php/index.js';
|
|
6
|
+
import { goEmitter } from './src/go/index.js';
|
|
4
7
|
import { nodeExtractor } from './src/compat/extractors/node.js';
|
|
5
8
|
import { rubyExtractor } from './src/compat/extractors/ruby.js';
|
|
6
9
|
import { pythonExtractor } from './src/compat/extractors/python.js';
|
|
@@ -21,8 +24,282 @@ function nestjsOperationIdTransform(id: string): string {
|
|
|
21
24
|
return idx !== -1 ? toCamelCase(stripped.slice(idx + 1)) : toCamelCase(stripped);
|
|
22
25
|
}
|
|
23
26
|
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Operation hints — per-operation overrides for the operation resolver.
|
|
29
|
+
// Keyed by "METHOD /path". Only operations that need overrides are listed;
|
|
30
|
+
// the algorithm handles the rest.
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
const operationHints: Record<string, OperationHint> = {
|
|
33
|
+
// ── Radar ────────────────────────────────────────────────────────────────
|
|
34
|
+
'POST /radar/lists/{type}/{action}': { name: 'add_list_entry' },
|
|
35
|
+
'DELETE /radar/lists/{type}/{action}': { name: 'remove_list_entry' },
|
|
36
|
+
|
|
37
|
+
// ── SSO ──────────────────────────────────────────────────────────────────
|
|
38
|
+
'GET /sso/authorize': {
|
|
39
|
+
name: 'get_authorization_url',
|
|
40
|
+
defaults: { response_type: 'code' },
|
|
41
|
+
inferFromClient: ['client_id'],
|
|
42
|
+
},
|
|
43
|
+
'GET /sso/logout': { name: 'get_logout_url' },
|
|
44
|
+
'GET /sso/profile': { name: 'get_profile' },
|
|
45
|
+
'POST /sso/token': {
|
|
46
|
+
name: 'get_profile_and_token',
|
|
47
|
+
defaults: { grant_type: 'authorization_code' },
|
|
48
|
+
inferFromClient: ['client_id', 'client_secret'],
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
// ── SSO / JWKS (mounted on UserManagement via mountRules) ────────────────
|
|
52
|
+
'GET /sso/jwks/{clientId}': { name: 'get_jwks' },
|
|
53
|
+
|
|
54
|
+
// ── User Management — auth ──────────────────────────────────────────────
|
|
55
|
+
'GET /user_management/authorize': {
|
|
56
|
+
name: 'get_authorization_url',
|
|
57
|
+
defaults: { response_type: 'code' },
|
|
58
|
+
inferFromClient: ['client_id'],
|
|
59
|
+
},
|
|
60
|
+
'GET /user_management/sessions/logout': { name: 'get_logout_url' },
|
|
61
|
+
|
|
62
|
+
// ── User Management — org membership actions ────────────────────────────
|
|
63
|
+
'PUT /user_management/organization_memberships/{id}/deactivate': {
|
|
64
|
+
name: 'deactivate_organization_membership',
|
|
65
|
+
},
|
|
66
|
+
'PUT /user_management/organization_memberships/{id}/reactivate': {
|
|
67
|
+
name: 'reactivate_organization_membership',
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
// ── Admin Portal ────────────────────────────────────────────────────────
|
|
71
|
+
'POST /portal/generate_link': { name: 'generate_link' },
|
|
72
|
+
|
|
73
|
+
// ── Feature Flags — disambiguate co-mounted list operations ─────────────
|
|
74
|
+
'GET /organizations/{organizationId}/feature-flags': { name: 'list_organization_feature_flags' },
|
|
75
|
+
'GET /user_management/users/{userId}/feature-flags': { name: 'list_user_feature_flags' },
|
|
76
|
+
|
|
77
|
+
// ── External ID lookups (not derivable from path) ──────────────────────
|
|
78
|
+
'GET /organizations/external_id/{external_id}': { name: 'get_organization_by_external_id' },
|
|
79
|
+
'GET /user_management/users/external_id/{external_id}': { name: 'get_user_by_external_id' },
|
|
80
|
+
|
|
81
|
+
// ── Authorization — environment-scoped roles ─────────────────────────────
|
|
82
|
+
'GET /authorization/roles': { name: 'list_environment_roles' },
|
|
83
|
+
'POST /authorization/roles': { name: 'create_environment_role' },
|
|
84
|
+
'GET /authorization/roles/{slug}': { name: 'get_environment_role' },
|
|
85
|
+
'PATCH /authorization/roles/{slug}': { name: 'update_environment_role' },
|
|
86
|
+
'PUT /authorization/roles/{slug}/permissions': {
|
|
87
|
+
name: 'set_environment_role_permissions',
|
|
88
|
+
},
|
|
89
|
+
'POST /authorization/roles/{slug}/permissions': {
|
|
90
|
+
name: 'add_environment_role_permission',
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
// ── Authorization — singularized/shortened names ────────────────────────
|
|
94
|
+
'POST /authorization/permissions': { name: 'create_permission' },
|
|
95
|
+
'POST /authorization/resources': { name: 'create_resource' },
|
|
96
|
+
'POST /authorization/organization_memberships/{organization_membership_id}/check': {
|
|
97
|
+
name: 'check',
|
|
98
|
+
},
|
|
99
|
+
'POST /authorization/organization_memberships/{organization_membership_id}/role_assignments': {
|
|
100
|
+
name: 'assign_role',
|
|
101
|
+
},
|
|
102
|
+
'DELETE /authorization/organization_memberships/{organization_membership_id}/role_assignments': {
|
|
103
|
+
name: 'remove_role',
|
|
104
|
+
},
|
|
105
|
+
'POST /authorization/organizations/{organizationId}/roles': {
|
|
106
|
+
name: 'create_organization_role',
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
// ── Authorization — env-scoped resource memberships ────────────────────
|
|
110
|
+
'GET /authorization/resources/{resource_id}/organization_memberships': { name: 'list_memberships_for_resource' },
|
|
111
|
+
|
|
112
|
+
// ── User Management — singularized/shortened names ─────────────────────
|
|
113
|
+
'POST /user_management/users': { name: 'create_user' },
|
|
114
|
+
'POST /user_management/organization_memberships': {
|
|
115
|
+
name: 'create_organization_membership',
|
|
116
|
+
},
|
|
117
|
+
'POST /user_management/invitations': { name: 'send_invitation' },
|
|
118
|
+
'GET /user_management/invitations/by_token/{token}': {
|
|
119
|
+
name: 'find_invitation_by_token',
|
|
120
|
+
},
|
|
121
|
+
'POST /user_management/users/{id}/email_verification/send': {
|
|
122
|
+
name: 'send_verification_email',
|
|
123
|
+
},
|
|
124
|
+
'POST /user_management/users/{id}/email_verification/confirm': {
|
|
125
|
+
name: 'verify_email',
|
|
126
|
+
},
|
|
127
|
+
'POST /user_management/password_reset': { name: 'reset_password' },
|
|
128
|
+
'POST /user_management/password_reset/confirm': {
|
|
129
|
+
name: 'confirm_password_reset',
|
|
130
|
+
},
|
|
131
|
+
'GET /user_management/users/{id}/sessions': { name: 'list_sessions' },
|
|
132
|
+
'GET /user_management/users/{id}/identities': { name: 'get_user_identities' },
|
|
133
|
+
'POST /user_management/cors_origins': { name: 'create_cors_origin' },
|
|
134
|
+
'POST /user_management/redirect_uris': { name: 'create_redirect_uri' },
|
|
135
|
+
|
|
136
|
+
// ── Organizations — singularized names ─────────────────────────────────
|
|
137
|
+
'POST /organizations': { name: 'create_organization' },
|
|
138
|
+
|
|
139
|
+
// ── Directory Sync — shortened names ───────────────────────────────────
|
|
140
|
+
'GET /directory_groups': { name: 'list_groups' },
|
|
141
|
+
'GET /directory_groups/{id}': { name: 'get_group' },
|
|
142
|
+
'GET /directory_users': { name: 'list_users' },
|
|
143
|
+
'GET /directory_users/{id}': { name: 'get_user' },
|
|
144
|
+
|
|
145
|
+
// ── Audit Logs — singularized names ────────────────────────────────────
|
|
146
|
+
'POST /audit_logs/events': { name: 'create_event' },
|
|
147
|
+
'POST /audit_logs/exports': { name: 'create_export' },
|
|
148
|
+
'POST /audit_logs/actions/{actionName}/schemas': { name: 'create_schema' },
|
|
149
|
+
|
|
150
|
+
// ── Feature Flags — match SDK conventions ──────────────────────────────
|
|
151
|
+
'POST /feature-flags/{slug}/targets/{resourceId}': { name: 'add_flag_target' },
|
|
152
|
+
'DELETE /feature-flags/{slug}/targets/{resourceId}': {
|
|
153
|
+
name: 'remove_flag_target',
|
|
154
|
+
},
|
|
155
|
+
|
|
156
|
+
// ── Organizations — audit logs retention (mounted on AuditLogs) ─────────
|
|
157
|
+
'GET /organizations/{id}/audit_logs_retention': { mountOn: 'AuditLogs' },
|
|
158
|
+
'PUT /organizations/{id}/audit_logs_retention': { mountOn: 'AuditLogs' },
|
|
159
|
+
|
|
160
|
+
// ── Union split: POST /user_management/authenticate (8 variants) ────────
|
|
161
|
+
'POST /user_management/authenticate': {
|
|
162
|
+
split: [
|
|
163
|
+
{
|
|
164
|
+
name: 'authenticate_with_password',
|
|
165
|
+
targetVariant: 'PasswordSessionAuthenticateRequest',
|
|
166
|
+
defaults: { grant_type: 'password' },
|
|
167
|
+
inferFromClient: ['client_id', 'client_secret'],
|
|
168
|
+
exposedParams: ['email', 'password', 'invitation_token'],
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
name: 'authenticate_with_code',
|
|
172
|
+
targetVariant: 'CodeSessionAuthenticateRequest',
|
|
173
|
+
defaults: { grant_type: 'authorization_code' },
|
|
174
|
+
inferFromClient: ['client_id', 'client_secret'],
|
|
175
|
+
exposedParams: ['code'],
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'authenticate_with_refresh_token',
|
|
179
|
+
targetVariant: 'RefreshTokenSessionAuthenticateRequest',
|
|
180
|
+
defaults: { grant_type: 'refresh_token' },
|
|
181
|
+
inferFromClient: ['client_id', 'client_secret'],
|
|
182
|
+
exposedParams: ['refresh_token', 'organization_id'],
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: 'authenticate_with_magic_auth',
|
|
186
|
+
targetVariant: 'MagicAuthSessionAuthenticateRequest',
|
|
187
|
+
defaults: { grant_type: 'urn:workos:oauth:grant-type:magic-auth:code' },
|
|
188
|
+
inferFromClient: ['client_id', 'client_secret'],
|
|
189
|
+
exposedParams: ['code', 'email', 'invitation_token'],
|
|
190
|
+
},
|
|
191
|
+
{
|
|
192
|
+
name: 'authenticate_with_email_verification',
|
|
193
|
+
targetVariant: 'EmailVerificationSessionAuthenticateRequest',
|
|
194
|
+
defaults: { grant_type: 'urn:workos:oauth:grant-type:email-verification:code' },
|
|
195
|
+
inferFromClient: ['client_id', 'client_secret'],
|
|
196
|
+
exposedParams: ['code', 'pending_authentication_token'],
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
name: 'authenticate_with_totp',
|
|
200
|
+
targetVariant: 'TotpSessionAuthenticateRequest',
|
|
201
|
+
defaults: { grant_type: 'urn:workos:oauth:grant-type:mfa-totp' },
|
|
202
|
+
inferFromClient: ['client_id', 'client_secret'],
|
|
203
|
+
exposedParams: ['code', 'pending_authentication_token', 'authentication_challenge_id'],
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'authenticate_with_organization_selection',
|
|
207
|
+
targetVariant: 'OrganizationSelectionSessionAuthenticateRequest',
|
|
208
|
+
defaults: { grant_type: 'urn:workos:oauth:grant-type:organization-selection' },
|
|
209
|
+
inferFromClient: ['client_id', 'client_secret'],
|
|
210
|
+
exposedParams: ['pending_authentication_token', 'organization_id'],
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
name: 'authenticate_with_device_code',
|
|
214
|
+
targetVariant: 'DeviceCodeSessionAuthenticateRequest',
|
|
215
|
+
defaults: { grant_type: 'urn:ietf:params:oauth:grant-type:device_code' },
|
|
216
|
+
inferFromClient: ['client_id'],
|
|
217
|
+
exposedParams: ['device_code'],
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
},
|
|
221
|
+
|
|
222
|
+
// ── Union split: POST /connect/applications (2 variants) ────────────────
|
|
223
|
+
'POST /connect/applications': {
|
|
224
|
+
split: [
|
|
225
|
+
{
|
|
226
|
+
name: 'create_oauth_application',
|
|
227
|
+
targetVariant: 'CreateOAuthApplication',
|
|
228
|
+
defaults: { application_type: 'oauth' },
|
|
229
|
+
exposedParams: [
|
|
230
|
+
'name',
|
|
231
|
+
'is_first_party',
|
|
232
|
+
'description',
|
|
233
|
+
'scopes',
|
|
234
|
+
'redirect_uris',
|
|
235
|
+
'uses_pkce',
|
|
236
|
+
'organization_id',
|
|
237
|
+
],
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'create_m2m_application',
|
|
241
|
+
targetVariant: 'CreateM2MApplication',
|
|
242
|
+
defaults: { application_type: 'm2m' },
|
|
243
|
+
exposedParams: ['name', 'organization_id', 'description', 'scopes'],
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
// ---------------------------------------------------------------------------
|
|
250
|
+
// Mount rules — service-level remounting. Maps IR service name → target
|
|
251
|
+
// service/namespace (PascalCase). All operations in the source service are
|
|
252
|
+
// mounted on the target unless overridden per-operation in operationHints.
|
|
253
|
+
// ---------------------------------------------------------------------------
|
|
254
|
+
const mountRules: Record<string, string> = {
|
|
255
|
+
// MFA sub-services → MultiFactorAuth
|
|
256
|
+
MultiFactorAuthChallenges: 'MultiFactorAuth',
|
|
257
|
+
|
|
258
|
+
// RBAC permissions → Authorization
|
|
259
|
+
Permissions: 'Authorization',
|
|
260
|
+
|
|
261
|
+
// Connect sub-services → Connect
|
|
262
|
+
WorkosConnect: 'Connect',
|
|
263
|
+
Applications: 'Connect',
|
|
264
|
+
ApplicationClientSecrets: 'Connect',
|
|
265
|
+
|
|
266
|
+
// SSO connections → SSO
|
|
267
|
+
Connections: 'SSO',
|
|
268
|
+
|
|
269
|
+
// Directory Sync sub-services → DirectorySync
|
|
270
|
+
Directories: 'DirectorySync',
|
|
271
|
+
DirectoryGroups: 'DirectorySync',
|
|
272
|
+
DirectoryUsers: 'DirectorySync',
|
|
273
|
+
|
|
274
|
+
// Feature flag sub-services → FeatureFlags
|
|
275
|
+
FeatureFlagsTargets: 'FeatureFlags',
|
|
276
|
+
OrganizationsFeatureFlags: 'FeatureFlags',
|
|
277
|
+
UserManagementUsersFeatureFlags: 'FeatureFlags',
|
|
278
|
+
|
|
279
|
+
// Org API keys → ApiKeys
|
|
280
|
+
OrganizationsApiKeys: 'ApiKeys',
|
|
281
|
+
|
|
282
|
+
// User Management sub-services → UserManagement
|
|
283
|
+
UserManagementSessionTokens: 'UserManagement',
|
|
284
|
+
UserManagementAuthentication: 'UserManagement',
|
|
285
|
+
UserManagementCorsOrigins: 'UserManagement',
|
|
286
|
+
UserManagementUsers: 'UserManagement',
|
|
287
|
+
UserManagementInvitations: 'UserManagement',
|
|
288
|
+
UserManagementJWTTemplate: 'UserManagement',
|
|
289
|
+
UserManagementMagicAuth: 'UserManagement',
|
|
290
|
+
UserManagementOrganizationMembership: 'UserManagement',
|
|
291
|
+
UserManagementRedirectUris: 'UserManagement',
|
|
292
|
+
UserManagementUsersAuthorizedApplications: 'UserManagement',
|
|
293
|
+
|
|
294
|
+
// Pipes / Data Providers → Pipes
|
|
295
|
+
UserManagementDataProviders: 'Pipes',
|
|
296
|
+
|
|
297
|
+
// User Management MFA → MultiFactorAuth
|
|
298
|
+
UserManagementMultiFactorAuthentication: 'MultiFactorAuth',
|
|
299
|
+
};
|
|
300
|
+
|
|
24
301
|
const config: OagenConfig = {
|
|
25
|
-
emitters: [nodeEmitter],
|
|
302
|
+
emitters: [nodeEmitter, pythonEmitter, phpEmitter, goEmitter],
|
|
26
303
|
extractors: [
|
|
27
304
|
nodeExtractor,
|
|
28
305
|
rubyExtractor,
|
|
@@ -47,5 +324,24 @@ const config: OagenConfig = {
|
|
|
47
324
|
},
|
|
48
325
|
docUrl: 'https://workos.com/docs',
|
|
49
326
|
operationIdTransform: nestjsOperationIdTransform,
|
|
327
|
+
schemaNameTransform: (name: string) => {
|
|
328
|
+
// Explicit renames for Dto models that collide with response models
|
|
329
|
+
const COLLISION_RENAMES: Record<string, string> = {
|
|
330
|
+
OrganizationDto: 'OrganizationInput',
|
|
331
|
+
RedirectUriDto: 'RedirectUriInput',
|
|
332
|
+
// Generic list-derived names that need domain-specific identifiers
|
|
333
|
+
ListData: 'Role',
|
|
334
|
+
ListModel: 'RoleList',
|
|
335
|
+
// Double-List naming artifact
|
|
336
|
+
EventListListMetadata: 'EventListMetadata',
|
|
337
|
+
};
|
|
338
|
+
if (COLLISION_RENAMES[name]) return COLLISION_RENAMES[name];
|
|
339
|
+
return name
|
|
340
|
+
.replace(/Dto/g, '')
|
|
341
|
+
.replace(/DTO/g, '')
|
|
342
|
+
.replace(/^Urn(?:IetfParams|Workos)O[Aa]uthGrantType/, '');
|
|
343
|
+
},
|
|
344
|
+
operationHints,
|
|
345
|
+
mountRules,
|
|
50
346
|
};
|
|
51
347
|
export default config;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workos/oagen-emitters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "WorkOS' oagen emitters",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "WorkOS",
|
|
@@ -28,6 +28,10 @@
|
|
|
28
28
|
"test": "vitest run",
|
|
29
29
|
"test:watch": "vitest",
|
|
30
30
|
"typecheck": "tsc --noEmit",
|
|
31
|
+
"git:push": "sh ./scripts/git-push-with-published-oagen.sh",
|
|
32
|
+
"oagen:build:local": "npm --prefix ../oagen run build",
|
|
33
|
+
"oagen:use:local": "npm run oagen:build:local && npm link ../oagen",
|
|
34
|
+
"oagen:use:published": "sh -c 'npm unlink @workos/oagen >/dev/null 2>&1 || true; npm install --ignore-scripts @workos/oagen@^0.4.0'",
|
|
31
35
|
"prepare": "husky",
|
|
32
36
|
"sdk:generate:dotnet": "oagen generate --lang dotnet --output ./sdk-dotnet --namespace workos --api-surface ./sdk-dotnet-surface.json",
|
|
33
37
|
"sdk:verify:dotnet": "oagen verify --lang dotnet --output ./sdk-dotnet --api-surface ./sdk-dotnet-surface.json",
|
|
@@ -44,7 +48,7 @@
|
|
|
44
48
|
"sdk:generate:node": "oagen generate --lang node --output ./sdk-node --namespace workos --api-surface ./sdk-node-surface.json",
|
|
45
49
|
"sdk:verify:node": "oagen verify --lang node --output ./sdk-node --api-surface ./sdk-node-surface.json",
|
|
46
50
|
"sdk:extract:node": "oagen extract --lang node --sdk-path ../backend/workos-node --output ./sdk-node-surface.json",
|
|
47
|
-
"sdk:generate:php": "
|
|
51
|
+
"sdk:generate:php": "node scripts/generate-php.js",
|
|
48
52
|
"sdk:verify:php": "oagen verify --lang php --output ./sdk-php --api-surface ./sdk-php-surface.json",
|
|
49
53
|
"sdk:extract:php": "oagen extract --lang php --sdk-path ../backend/workos-php --output ./sdk-php-surface.json",
|
|
50
54
|
"sdk:generate:python": "oagen generate --lang python --output ./sdk-python --namespace workos --api-surface ./sdk-python-surface.json",
|
|
@@ -57,9 +61,6 @@
|
|
|
57
61
|
"sdk:verify:rust": "oagen verify --lang rust --output ./sdk-rust --api-surface ./sdk-rust-surface.json",
|
|
58
62
|
"sdk:extract:rust": "oagen extract --lang rust --sdk-path ../backend/workos-rust --output ./sdk-rust-surface.json"
|
|
59
63
|
},
|
|
60
|
-
"dependencies": {
|
|
61
|
-
"@workos/oagen": "^0.3.0"
|
|
62
|
-
},
|
|
63
64
|
"devDependencies": {
|
|
64
65
|
"@commitlint/cli": "^20.5.0",
|
|
65
66
|
"@commitlint/config-conventional": "^20.5.0",
|
|
@@ -75,5 +76,8 @@
|
|
|
75
76
|
},
|
|
76
77
|
"engines": {
|
|
77
78
|
"node": ">=24.10.0"
|
|
79
|
+
},
|
|
80
|
+
"dependencies": {
|
|
81
|
+
"@workos/oagen": "^0.5.0"
|
|
78
82
|
}
|
|
79
83
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PHP SDK generation wrapper.
|
|
4
|
+
*/
|
|
5
|
+
import { execSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
// Build oagen command with all passed arguments
|
|
8
|
+
const extraArgs = process.argv.slice(2).join(' ');
|
|
9
|
+
const baseCmd = 'oagen generate --lang php --output ./sdk-php --namespace WorkOS --api-surface ./sdk-php-surface.json';
|
|
10
|
+
const fullCmd = extraArgs ? `${baseCmd} ${extraArgs}` : baseCmd;
|
|
11
|
+
|
|
12
|
+
// Run oagen generate
|
|
13
|
+
execSync(fullCmd, { stdio: 'inherit' });
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
set -eu
|
|
3
|
+
|
|
4
|
+
repo_root="$(git rev-parse --show-toplevel)"
|
|
5
|
+
cd "$repo_root"
|
|
6
|
+
|
|
7
|
+
restore_local() {
|
|
8
|
+
echo "push wrapper: restoring local ../oagen link"
|
|
9
|
+
npm run oagen:use:local
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
cleanup() {
|
|
13
|
+
restore_local
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
trap cleanup EXIT INT TERM HUP
|
|
17
|
+
|
|
18
|
+
echo "push wrapper: switching @workos/oagen to the published package"
|
|
19
|
+
npm run oagen:use:published
|
|
20
|
+
|
|
21
|
+
git push "$@"
|
package/smoke/sdk-go.ts
CHANGED
|
@@ -140,6 +140,43 @@ function loadManifest(sdkPath: string): Map<string, ManifestEntry> | null {
|
|
|
140
140
|
return manifest;
|
|
141
141
|
}
|
|
142
142
|
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// Accessor map -- discover actual method names from the generated workos.go
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
|
|
147
|
+
function buildAccessorMap(sdkPath: string): Map<string, string> {
|
|
148
|
+
const map = new Map<string, string>();
|
|
149
|
+
// Find the main workos.go or *.go file that has Client methods
|
|
150
|
+
const candidates = ['workos.go'];
|
|
151
|
+
for (const fname of candidates) {
|
|
152
|
+
const fpath = resolve(sdkPath, fname);
|
|
153
|
+
if (!existsSync(fpath)) continue;
|
|
154
|
+
const content = readFileSync(fpath, 'utf-8');
|
|
155
|
+
// Match: func (c *Client) ServiceName() *serviceNameService {
|
|
156
|
+
const re = /func \(c \*Client\) (\w+)\(\)/g;
|
|
157
|
+
let m;
|
|
158
|
+
while ((m = re.exec(content)) !== null) {
|
|
159
|
+
const accessor = m[1];
|
|
160
|
+
// Map by lowercase key for case-insensitive matching
|
|
161
|
+
map.set(accessor.toLowerCase(), accessor);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
return map;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolve the Go service accessor name from the manifest service name.
|
|
169
|
+
* Uses the accessor map (built from the generated SDK) for exact matching.
|
|
170
|
+
* Falls back to PascalCase for services not found in the map.
|
|
171
|
+
*/
|
|
172
|
+
function resolveAccessorName(manifestService: string, accessorMap: Map<string, string>): string {
|
|
173
|
+
// Try case-insensitive lookup: "sso" -> "SSO", "api_keys" -> "ApiKeys"
|
|
174
|
+
const pascalized = toPascalCase(manifestService);
|
|
175
|
+
const found = accessorMap.get(pascalized.toLowerCase());
|
|
176
|
+
if (found) return found;
|
|
177
|
+
return pascalized;
|
|
178
|
+
}
|
|
179
|
+
|
|
143
180
|
// ---------------------------------------------------------------------------
|
|
144
181
|
// Method resolution
|
|
145
182
|
// ---------------------------------------------------------------------------
|
|
@@ -345,15 +382,16 @@ function detectModulePath(sdkPath: string): string {
|
|
|
345
382
|
const match = goMod.match(/^module\s+(\S+)/m);
|
|
346
383
|
if (match) return match[1];
|
|
347
384
|
}
|
|
348
|
-
return 'github.com/workos/workos-go/
|
|
385
|
+
return 'github.com/workos/workos-go/v2';
|
|
349
386
|
}
|
|
350
387
|
|
|
351
388
|
function generateGoImports(
|
|
352
389
|
modulePath: string,
|
|
353
|
-
|
|
390
|
+
_servicePackages: Set<string>,
|
|
354
391
|
needsJson: boolean,
|
|
355
|
-
|
|
392
|
+
_needsServicePkg: boolean,
|
|
356
393
|
): string {
|
|
394
|
+
// New emitter: flat package -- everything lives in the root module, no sub-packages.
|
|
357
395
|
const lines: string[] = [];
|
|
358
396
|
lines.push('import (');
|
|
359
397
|
lines.push('\t"context"');
|
|
@@ -363,20 +401,21 @@ function generateGoImports(
|
|
|
363
401
|
lines.push('\t"fmt"');
|
|
364
402
|
lines.push('\t"os"');
|
|
365
403
|
lines.push('');
|
|
366
|
-
lines.push(`\
|
|
367
|
-
if (needsServicePkg) {
|
|
368
|
-
for (const pkg of [...servicePackages].sort()) {
|
|
369
|
-
lines.push(`\t"${modulePath}/pkg/${pkg}"`);
|
|
370
|
-
}
|
|
371
|
-
}
|
|
404
|
+
lines.push(`\t"${modulePath}"`);
|
|
372
405
|
lines.push(')');
|
|
373
406
|
return lines.join('\n');
|
|
374
407
|
}
|
|
375
408
|
|
|
376
|
-
function generateGoPayloadStruct(payload: Record<string, unknown>,
|
|
409
|
+
function generateGoPayloadStruct(payload: Record<string, unknown>, paramsType: string): string {
|
|
410
|
+
// New emitter: all types in root workos package, params are pointers.
|
|
411
|
+
// Skip nested objects, arrays, and nil values since Go requires typed structs
|
|
412
|
+
// and primitive fields can't be nil.
|
|
377
413
|
const lines: string[] = [];
|
|
378
|
-
lines.push(
|
|
414
|
+
lines.push(`&workos.${paramsType}{`);
|
|
379
415
|
for (const [key, value] of Object.entries(payload)) {
|
|
416
|
+
// Skip nil, nested objects, and arrays
|
|
417
|
+
if (value === null || value === undefined) continue;
|
|
418
|
+
if (typeof value === 'object') continue;
|
|
380
419
|
const goField = goFieldName(key);
|
|
381
420
|
lines.push(`\t\t${goField}: ${goLiteral(value)},`);
|
|
382
421
|
}
|
|
@@ -414,9 +453,9 @@ function generateGoCallBlock(
|
|
|
414
453
|
pathParams: Record<string, string>,
|
|
415
454
|
spec: any,
|
|
416
455
|
callIndex: number,
|
|
456
|
+
accessorMap: Map<string, string>,
|
|
417
457
|
): string {
|
|
418
458
|
const lines: string[] = [];
|
|
419
|
-
const servicePackage = goServicePackageName(resolution.service);
|
|
420
459
|
const method = resolution.method;
|
|
421
460
|
|
|
422
461
|
// Build arguments
|
|
@@ -427,52 +466,63 @@ function generateGoCallBlock(
|
|
|
427
466
|
args.push(`"${pathParams[p.name] || ''}"`);
|
|
428
467
|
}
|
|
429
468
|
|
|
430
|
-
//
|
|
469
|
+
// Build service-prefixed params struct name (matches emitter's paramsStructName)
|
|
470
|
+
const servicePrefix = goExportedName(resolution.service);
|
|
471
|
+
const paramsTypeName = method.startsWith(servicePrefix) ? `${method}Params` : `${servicePrefix}${method}Params`;
|
|
472
|
+
|
|
473
|
+
// Request body params struct (emitter uses &workos.{ServicePrefix}{Method}Params{...})
|
|
474
|
+
const hasQueryParams = op.queryParams && op.queryParams.length > 0;
|
|
431
475
|
if (op.requestBody) {
|
|
432
476
|
const payload = generatePayload(op, spec);
|
|
433
477
|
if (payload && Object.keys(payload).length > 0) {
|
|
434
|
-
|
|
435
|
-
args.push(generateGoPayloadStruct(payload, optsType, servicePackage));
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
// Paginated operations: pass opts with Limit=1
|
|
440
|
-
if (op.pagination && !op.requestBody) {
|
|
441
|
-
const extraParams = op.queryParams.filter((p: any) => !['limit', 'before', 'after', 'order'].includes(p.name));
|
|
442
|
-
if (extraParams.length > 0) {
|
|
443
|
-
// Match the emitter convention: List → ListFilterOpts, others → ${method}Opts
|
|
444
|
-
const optsType = method === 'List' ? 'ListFilterOpts' : `${method}Opts`;
|
|
445
|
-
args.push(`${servicePackage}.${optsType}{Limit: 1}`);
|
|
478
|
+
args.push(generateGoPayloadStruct(payload, paramsTypeName));
|
|
446
479
|
} else {
|
|
447
|
-
|
|
480
|
+
// Even with empty payload, the method signature requires the params arg
|
|
481
|
+
args.push(`&workos.${paramsTypeName}{}`);
|
|
448
482
|
}
|
|
483
|
+
} else if (op.pagination || hasQueryParams) {
|
|
484
|
+
// Paginated or query-param operations need a params struct
|
|
485
|
+
args.push(`&workos.${paramsTypeName}{}`);
|
|
449
486
|
}
|
|
450
487
|
|
|
451
|
-
//
|
|
452
|
-
const serviceProp =
|
|
488
|
+
// Service accessor: resolve from the generated SDK's actual accessor names
|
|
489
|
+
const serviceProp = resolveAccessorName(resolution.service, accessorMap);
|
|
453
490
|
|
|
454
491
|
lines.push(`\t// Call ${callIndex}: ${op.httpMethod.toUpperCase()} ${op.path}`);
|
|
455
492
|
lines.push(`\tfmt.Fprintf(os.Stderr, "CALL_START:${callIndex}\\n")`);
|
|
456
493
|
|
|
457
|
-
// Determine return type: paginated and
|
|
458
|
-
// DELETE returns just error
|
|
494
|
+
// Determine return type: paginated returns Iterator, DELETE and void/redirect return just error
|
|
459
495
|
const isDelete = op.httpMethod === 'delete';
|
|
460
|
-
const
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
496
|
+
const isPaginated = !!op.pagination;
|
|
497
|
+
const isVoidResponse =
|
|
498
|
+
!isPaginated &&
|
|
499
|
+
!isDelete &&
|
|
500
|
+
((op.response.kind === 'primitive' && (op.response as any).type === 'unknown') ||
|
|
501
|
+
(op.successResponses && op.successResponses.some((r: any) => r.statusCode >= 300 && r.statusCode < 400)));
|
|
502
|
+
|
|
503
|
+
if (isPaginated) {
|
|
504
|
+
// Iterator-based: call Next() once to trigger the first HTTP request
|
|
505
|
+
lines.push(`\titer${callIndex} := client.${serviceProp}().${method}(${args.join(', ')})`);
|
|
506
|
+
lines.push(`\titer${callIndex}.Next()`);
|
|
507
|
+
lines.push(`\tif err${callIndex} := iter${callIndex}.Err(); err${callIndex} != nil {`);
|
|
508
|
+
lines.push(`\t\tfmt.Fprintf(os.Stderr, "CALL_ERROR:${callIndex}:%s\\n", err${callIndex}.Error())`);
|
|
509
|
+
lines.push('\t} else {');
|
|
510
|
+
lines.push(`\t\tfmt.Fprintf(os.Stderr, "CALL_OK:${callIndex}:\\n")`);
|
|
511
|
+
lines.push('\t}');
|
|
512
|
+
} else if (isDelete || isVoidResponse) {
|
|
513
|
+
lines.push(`\terr${callIndex} := client.${serviceProp}().${method}(${args.join(', ')})`);
|
|
464
514
|
lines.push(`\tif err${callIndex} != nil {`);
|
|
465
515
|
lines.push(`\t\tfmt.Fprintf(os.Stderr, "CALL_ERROR:${callIndex}:%s\\n", err${callIndex}.Error())`);
|
|
466
516
|
lines.push('\t} else {');
|
|
467
|
-
lines.push(`\t\
|
|
468
|
-
lines.push(`\t\tfmt.Fprintf(os.Stderr, "CALL_OK:${callIndex}:%s\\n", string(jsonResult${callIndex}))`);
|
|
517
|
+
lines.push(`\t\tfmt.Fprintf(os.Stderr, "CALL_OK:${callIndex}:\\n")`);
|
|
469
518
|
lines.push('\t}');
|
|
470
519
|
} else {
|
|
471
|
-
lines.push(`\
|
|
520
|
+
lines.push(`\tresult${callIndex}, err${callIndex} := client.${serviceProp}().${method}(${args.join(', ')})`);
|
|
472
521
|
lines.push(`\tif err${callIndex} != nil {`);
|
|
473
522
|
lines.push(`\t\tfmt.Fprintf(os.Stderr, "CALL_ERROR:${callIndex}:%s\\n", err${callIndex}.Error())`);
|
|
474
523
|
lines.push('\t} else {');
|
|
475
|
-
lines.push(`\t\
|
|
524
|
+
lines.push(`\t\tjsonResult${callIndex}, _ := json.Marshal(result${callIndex})`);
|
|
525
|
+
lines.push(`\t\tfmt.Fprintf(os.Stderr, "CALL_OK:${callIndex}:%s\\n", string(jsonResult${callIndex}))`);
|
|
476
526
|
lines.push('\t}');
|
|
477
527
|
}
|
|
478
528
|
|
|
@@ -523,6 +573,11 @@ async function main(): Promise<void> {
|
|
|
523
573
|
// Load manifest
|
|
524
574
|
const manifest = loadManifest(sdkPath);
|
|
525
575
|
|
|
576
|
+
// Build accessor name map by scanning the generated workos.go for
|
|
577
|
+
// `func (c *Client) XxxYyy()` patterns, then matching them to manifest
|
|
578
|
+
// service names via case-insensitive comparison.
|
|
579
|
+
const accessorMap = buildAccessorMap(sdkPath);
|
|
580
|
+
|
|
526
581
|
const baseUrl = process.env.WORKOS_BASE_URL || spec.baseUrl;
|
|
527
582
|
|
|
528
583
|
// Start capture proxy
|
|
@@ -633,7 +688,7 @@ async function main(): Promise<void> {
|
|
|
633
688
|
// Generate all call blocks for this wave
|
|
634
689
|
const callBlocks: string[] = [];
|
|
635
690
|
for (const call of plannedCalls) {
|
|
636
|
-
callBlocks.push(generateGoCallBlock(call.op, call.resolution, call.pathParams, spec, call.index));
|
|
691
|
+
callBlocks.push(generateGoCallBlock(call.op, call.resolution, call.pathParams, spec, call.index, accessorMap));
|
|
637
692
|
}
|
|
638
693
|
|
|
639
694
|
const imports = generateGoImports(modulePath, servicePackages, needsJson, needsServicePkg);
|
|
@@ -644,7 +699,7 @@ async function main(): Promise<void> {
|
|
|
644
699
|
imports,
|
|
645
700
|
'',
|
|
646
701
|
'func main() {',
|
|
647
|
-
`\tclient := workos.NewClient("${apiKey}", workos.
|
|
702
|
+
`\tclient := workos.NewClient("${apiKey}", workos.WithBaseURL("http://127.0.0.1:${proxyPort}"))`,
|
|
648
703
|
'\tctx := context.Background()',
|
|
649
704
|
'',
|
|
650
705
|
...callBlocks,
|
|
@@ -660,8 +715,10 @@ async function main(): Promise<void> {
|
|
|
660
715
|
|
|
661
716
|
// Step 1: Build (sync — no proxy needed during compilation)
|
|
662
717
|
let buildError: string | null = null;
|
|
718
|
+
|
|
719
|
+
// Run go mod tidy first to resolve dependencies
|
|
663
720
|
try {
|
|
664
|
-
execSync('go
|
|
721
|
+
execSync('go mod tidy', {
|
|
665
722
|
cwd: tmpDir,
|
|
666
723
|
timeout: 120_000,
|
|
667
724
|
env: {
|
|
@@ -673,9 +730,26 @@ async function main(): Promise<void> {
|
|
|
673
730
|
});
|
|
674
731
|
} catch (err: any) {
|
|
675
732
|
const stderr = typeof err.stderr === 'string' ? err.stderr : '';
|
|
676
|
-
buildError = stderr.trim().split('\n').slice(0,
|
|
733
|
+
buildError = `go mod tidy failed: ${stderr.trim().split('\n').slice(0, 3).join(' ')}`;
|
|
677
734
|
}
|
|
678
735
|
|
|
736
|
+
if (!buildError)
|
|
737
|
+
try {
|
|
738
|
+
execSync('go build -o smoke-driver main.go', {
|
|
739
|
+
cwd: tmpDir,
|
|
740
|
+
timeout: 120_000,
|
|
741
|
+
env: {
|
|
742
|
+
...process.env,
|
|
743
|
+
GOPATH: process.env.GOPATH || resolve(process.env.HOME || '~', 'go'),
|
|
744
|
+
},
|
|
745
|
+
encoding: 'utf-8',
|
|
746
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
747
|
+
});
|
|
748
|
+
} catch (err: any) {
|
|
749
|
+
const stderr = typeof err.stderr === 'string' ? err.stderr : '';
|
|
750
|
+
buildError = stderr.trim().split('\n').slice(0, 5).join(' ') || 'go build failed';
|
|
751
|
+
}
|
|
752
|
+
|
|
679
753
|
if (buildError) {
|
|
680
754
|
// Build failure affects entire wave
|
|
681
755
|
const elapsed = Date.now() - waveStart;
|