@workos/oagen-emitters 0.2.1 → 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.
- package/.husky/pre-commit +1 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +129 -0
- package/dist/index.d.mts +13 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +14549 -3385
- package/dist/index.mjs.map +1 -1
- package/docs/sdk-architecture/dotnet.md +336 -0
- 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 +328 -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-dotnet.ts +45 -12
- package/smoke/sdk-go.ts +116 -42
- package/smoke/sdk-php.ts +28 -26
- package/smoke/sdk-python.ts +5 -2
- package/src/dotnet/client.ts +89 -0
- package/src/dotnet/enums.ts +323 -0
- package/src/dotnet/fixtures.ts +236 -0
- package/src/dotnet/index.ts +246 -0
- package/src/dotnet/manifest.ts +36 -0
- package/src/dotnet/models.ts +344 -0
- package/src/dotnet/naming.ts +330 -0
- package/src/dotnet/resources.ts +622 -0
- package/src/dotnet/tests.ts +693 -0
- package/src/dotnet/type-map.ts +201 -0
- package/src/dotnet/wrappers.ts +186 -0
- 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 +84 -0
- package/src/go/manifest.ts +36 -0
- package/src/go/models.ts +254 -0
- package/src/go/naming.ts +179 -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 +4 -0
- package/src/kotlin/client.ts +53 -0
- package/src/kotlin/enums.ts +162 -0
- package/src/kotlin/index.ts +92 -0
- package/src/kotlin/manifest.ts +55 -0
- package/src/kotlin/models.ts +395 -0
- package/src/kotlin/naming.ts +223 -0
- package/src/kotlin/overrides.ts +25 -0
- package/src/kotlin/resources.ts +667 -0
- package/src/kotlin/tests.ts +1019 -0
- package/src/kotlin/type-map.ts +123 -0
- package/src/kotlin/wrappers.ts +168 -0
- package/src/node/client.ts +128 -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 +3 -9
- package/src/node/models.ts +178 -21
- package/src/node/naming.ts +49 -111
- package/src/node/resources.ts +527 -397
- package/src/node/sdk-errors.ts +41 -0
- package/src/node/tests.ts +69 -19
- 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 +179 -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 +279 -0
- package/src/php/resources.ts +636 -0
- package/src/php/tests.ts +609 -0
- package/src/php/type-map.ts +90 -0
- package/src/php/utils.ts +18 -0
- package/src/php/wrappers.ts +152 -0
- package/src/python/client.ts +345 -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 +189 -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 +472 -0
- package/src/shared/naming-utils.ts +154 -0
- package/src/shared/non-spec-services.ts +54 -0
- package/src/shared/resolved-ops.ts +109 -0
- package/src/shared/wrapper-utils.ts +70 -0
- package/test/dotnet/client.test.ts +121 -0
- package/test/dotnet/enums.test.ts +193 -0
- package/test/dotnet/errors.test.ts +9 -0
- package/test/dotnet/manifest.test.ts +82 -0
- package/test/dotnet/models.test.ts +260 -0
- package/test/dotnet/resources.test.ts +255 -0
- package/test/dotnet/tests.test.ts +202 -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/kotlin/models.test.ts +135 -0
- package/test/kotlin/tests.test.ts +176 -0
- package/test/node/client.test.ts +92 -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 +315 -84
- package/test/node/serializers.test.ts +3 -1
- package/test/node/type-map.test.ts +11 -0
- package/test/php/client.test.ts +95 -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 +682 -0
- package/test/php/tests.test.ts +185 -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/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;
|
package/smoke/sdk-php.ts
CHANGED
|
@@ -184,8 +184,8 @@ function loadManifest(sdkPath: string): Map<string, ManifestEntry> | null {
|
|
|
184
184
|
// ---------------------------------------------------------------------------
|
|
185
185
|
|
|
186
186
|
interface MethodResolution {
|
|
187
|
-
|
|
188
|
-
method: string;
|
|
187
|
+
service: string; // camelCase accessor on client (e.g., "organizations")
|
|
188
|
+
method: string; // camelCase method name (e.g., "get")
|
|
189
189
|
tier: ExchangeProvenance['resolutionTier'];
|
|
190
190
|
confidence: number;
|
|
191
191
|
}
|
|
@@ -200,14 +200,12 @@ function resolveMethod(
|
|
|
200
200
|
if (manifest) {
|
|
201
201
|
const entry = manifest.get(httpKey);
|
|
202
202
|
if (entry) {
|
|
203
|
-
|
|
204
|
-
return { className, method: entry.sdkMethod, tier: 'manifest', confidence: 1.0 };
|
|
203
|
+
return { service: entry.service, method: entry.sdkMethod, tier: 'manifest', confidence: 1.0 };
|
|
205
204
|
}
|
|
206
205
|
}
|
|
207
206
|
|
|
208
207
|
const sdkProp = SERVICE_PROPERTY_MAP[irService] || toCamelCase(irService);
|
|
209
|
-
|
|
210
|
-
return { className, method: toCamelCase(op.name), tier: 'exact', confidence: 0.8 };
|
|
208
|
+
return { service: sdkProp, method: toCamelCase(op.name), tier: 'exact', confidence: 0.8 };
|
|
211
209
|
}
|
|
212
210
|
|
|
213
211
|
// ---------------------------------------------------------------------------
|
|
@@ -278,9 +276,14 @@ function buildBatchedPhpScript(
|
|
|
278
276
|
}
|
|
279
277
|
lines.push('');
|
|
280
278
|
|
|
281
|
-
// Configure SDK
|
|
282
|
-
lines.push(
|
|
283
|
-
lines.push(
|
|
279
|
+
// Configure SDK — generated SDK uses instance-based client with Guzzle handler
|
|
280
|
+
lines.push(`use GuzzleHttp\\HandlerStack;`);
|
|
281
|
+
lines.push(`use GuzzleHttp\\Handler\\CurlHandler;`);
|
|
282
|
+
lines.push('');
|
|
283
|
+
lines.push(`$client = new ${namespace}\\${namespace}(`);
|
|
284
|
+
lines.push(` apiKey: '${escapePhpString(apiKey)}',`);
|
|
285
|
+
lines.push(` baseUrl: 'http://127.0.0.1:${proxyPort}',`);
|
|
286
|
+
lines.push(');');
|
|
284
287
|
lines.push('');
|
|
285
288
|
|
|
286
289
|
for (const call of calls) {
|
|
@@ -289,7 +292,8 @@ function buildBatchedPhpScript(
|
|
|
289
292
|
// Marker: start
|
|
290
293
|
lines.push(`fwrite(STDERR, "OAGEN_CALL_START:${index}\\n");`);
|
|
291
294
|
|
|
292
|
-
// Build arguments
|
|
295
|
+
// Build arguments — generated PHP SDK takes positional path params,
|
|
296
|
+
// then named keyword args for body fields and query params
|
|
293
297
|
const phpArgs: string[] = [];
|
|
294
298
|
|
|
295
299
|
for (const p of op.pathParams) {
|
|
@@ -299,33 +303,31 @@ function buildBatchedPhpScript(
|
|
|
299
303
|
|
|
300
304
|
if (op.requestBody) {
|
|
301
305
|
const payload = generatePayload(op, spec);
|
|
302
|
-
if (payload &&
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
+
if (payload && typeof payload === 'object') {
|
|
307
|
+
// Pass as named arguments (the generated SDK uses promoted properties)
|
|
308
|
+
for (const [key, value] of Object.entries(payload)) {
|
|
309
|
+
phpArgs.push(`${toCamelCase(key)}: ${phpArrayLiteral(value)}`);
|
|
310
|
+
}
|
|
306
311
|
}
|
|
307
312
|
}
|
|
308
313
|
|
|
309
314
|
if (!op.requestBody && op.queryParams.some((p) => p.required)) {
|
|
310
315
|
const queryOpts = generateQueryParams(op, spec);
|
|
311
|
-
|
|
312
|
-
phpArgs.push(phpArrayLiteral(
|
|
316
|
+
for (const [key, value] of Object.entries(queryOpts)) {
|
|
317
|
+
phpArgs.push(`${toCamelCase(key)}: ${phpArrayLiteral(value)}`);
|
|
313
318
|
}
|
|
314
319
|
}
|
|
315
320
|
|
|
316
|
-
if (op.pagination
|
|
317
|
-
phpArgs.
|
|
318
|
-
|
|
319
|
-
const last = phpArgs[phpArgs.length - 1];
|
|
320
|
-
if (last && last.startsWith('[')) {
|
|
321
|
-
phpArgs[phpArgs.length - 1] = last.replace(/\]$/, ", 'limit' => 1]");
|
|
322
|
-
} else {
|
|
323
|
-
phpArgs.push("['limit' => 1]");
|
|
321
|
+
if (op.pagination) {
|
|
322
|
+
if (!phpArgs.some((a) => a.startsWith('limit:'))) {
|
|
323
|
+
phpArgs.push('limit: 1');
|
|
324
324
|
}
|
|
325
325
|
}
|
|
326
326
|
|
|
327
|
+
// The generated SDK uses $client->resource()->method(...) pattern
|
|
328
|
+
const serviceAccessor = resolution.service;
|
|
327
329
|
lines.push('try {');
|
|
328
|
-
lines.push(` $result = ${
|
|
330
|
+
lines.push(` $result = $client->${serviceAccessor}()->${resolution.method}(${phpArgs.join(', ')});`);
|
|
329
331
|
lines.push(` fwrite(STDERR, "OAGEN_CALL_OK:${index}\\n");`);
|
|
330
332
|
lines.push('} catch (\\Throwable $e) {');
|
|
331
333
|
lines.push(` fwrite(STDERR, "OAGEN_CALL_ERROR:${index}:" . $e->getMessage() . "\\n");`);
|
|
@@ -669,7 +671,7 @@ function buildExchange(
|
|
|
669
671
|
provenance: {
|
|
670
672
|
resolutionTier: resolution.tier,
|
|
671
673
|
resolutionConfidence: resolution.confidence,
|
|
672
|
-
sdkMethodName: `${resolution.
|
|
674
|
+
sdkMethodName: `${resolution.service}->${resolution.method}`,
|
|
673
675
|
captureIndex: 0,
|
|
674
676
|
totalCaptures: 1,
|
|
675
677
|
},
|
package/smoke/sdk-python.ts
CHANGED
|
@@ -284,7 +284,10 @@ function buildBatchedPythonScript(
|
|
|
284
284
|
calls: PlannedCall[],
|
|
285
285
|
spec: any,
|
|
286
286
|
): string {
|
|
287
|
-
|
|
287
|
+
// Use src/ subdirectory if it exists, otherwise use the SDK root directly.
|
|
288
|
+
// Generated SDKs use a flat layout (workos/ at root), while some hand-written
|
|
289
|
+
// SDKs nest under src/.
|
|
290
|
+
const srcPath = existsSync(resolve(sdkPath, 'src')) ? resolve(sdkPath, 'src') : resolve(sdkPath);
|
|
288
291
|
const lines: string[] = [];
|
|
289
292
|
|
|
290
293
|
// Preamble -- loaded once
|
|
@@ -504,7 +507,7 @@ async function main(): Promise<void> {
|
|
|
504
507
|
const child = spawn(python3Path, [scriptPath], {
|
|
505
508
|
env: {
|
|
506
509
|
...process.env,
|
|
507
|
-
PYTHONPATH: resolve(sdkPath, 'src'),
|
|
510
|
+
PYTHONPATH: existsSync(resolve(sdkPath, 'src')) ? resolve(sdkPath, 'src') : resolve(sdkPath),
|
|
508
511
|
PYTHONDONTWRITEBYTECODE: '1',
|
|
509
512
|
},
|
|
510
513
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ApiSpec, EmitterContext, GeneratedFile, Service } from '@workos/oagen';
|
|
2
|
+
import { toPascalCase, toSnakeCase } from '@workos/oagen';
|
|
3
|
+
import { resolveResourceClassName } from './resources.js';
|
|
4
|
+
import { className, serviceTypeName, humanize } from './naming.js';
|
|
5
|
+
import { getMountTarget } from '../shared/resolved-ops.js';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Generate the C# client file with service accessors.
|
|
9
|
+
* Produces: WorkOSClient.Generated.cs (partial class with service properties).
|
|
10
|
+
*/
|
|
11
|
+
export function generateClient(spec: ApiSpec, ctx: EmitterContext): GeneratedFile[] {
|
|
12
|
+
return [generateClientFile(spec, ctx)];
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Deduplicate services by mount target.
|
|
17
|
+
*/
|
|
18
|
+
function deduplicateByMount(services: Service[], ctx: EmitterContext): Service[] {
|
|
19
|
+
const byTarget = new Map<string, Service>();
|
|
20
|
+
for (const s of services) {
|
|
21
|
+
const target = getMountTarget(s, ctx);
|
|
22
|
+
const existing = byTarget.get(target);
|
|
23
|
+
if (!existing || toPascalCase(s.name) === target) {
|
|
24
|
+
byTarget.set(target, s);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return [...byTarget.values()];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Build map of service name -> accessor property name.
|
|
32
|
+
*/
|
|
33
|
+
export function buildServiceAccessPaths(services: Service[], ctx: EmitterContext): Map<string, string> {
|
|
34
|
+
const topLevel = deduplicateByMount(services, ctx);
|
|
35
|
+
const paths = new Map<string, string>();
|
|
36
|
+
|
|
37
|
+
for (const service of topLevel) {
|
|
38
|
+
const resolvedName = resolveResourceClassName(service, ctx);
|
|
39
|
+
const prop = toSnakeCase(resolvedName);
|
|
40
|
+
paths.set(service.name, prop);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Also map mount targets
|
|
44
|
+
for (const service of services) {
|
|
45
|
+
const target = getMountTarget(service, ctx);
|
|
46
|
+
if (!paths.has(target)) {
|
|
47
|
+
const existing = paths.get(service.name);
|
|
48
|
+
if (existing) paths.set(target, existing);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return paths;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function generateClientFile(spec: ApiSpec, ctx: EmitterContext): GeneratedFile {
|
|
56
|
+
const topLevel = deduplicateByMount(spec.services, ctx);
|
|
57
|
+
const lines: string[] = [];
|
|
58
|
+
|
|
59
|
+
lines.push(`namespace ${ctx.namespacePascal}`);
|
|
60
|
+
lines.push('{');
|
|
61
|
+
lines.push(' /// <summary>');
|
|
62
|
+
lines.push(' /// Generated service accessors for WorkOSClient.');
|
|
63
|
+
lines.push(' /// </summary>');
|
|
64
|
+
lines.push(' public partial class WorkOSClient');
|
|
65
|
+
lines.push(' {');
|
|
66
|
+
|
|
67
|
+
// Service properties with lazy initialization
|
|
68
|
+
for (const service of topLevel) {
|
|
69
|
+
const resolvedName = resolveResourceClassName(service, ctx);
|
|
70
|
+
const propName = className(resolvedName);
|
|
71
|
+
const svcType = serviceTypeName(resolvedName);
|
|
72
|
+
const backingField = propName.charAt(0).toLowerCase() + propName.slice(1);
|
|
73
|
+
const human = humanize(resolvedName);
|
|
74
|
+
lines.push(` private ${svcType} ${backingField};`);
|
|
75
|
+
lines.push('');
|
|
76
|
+
lines.push(` /// <summary>Gets the <see cref="${svcType}"/> for ${human} API operations.</summary>`);
|
|
77
|
+
lines.push(` public virtual ${svcType} ${propName} => this.${backingField} ??= new ${svcType}(this);`);
|
|
78
|
+
lines.push('');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
lines.push(' }');
|
|
82
|
+
lines.push('}');
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
path: 'Client/WorkOSClient.Generated.cs',
|
|
86
|
+
content: lines.join('\n'),
|
|
87
|
+
overwriteExisting: true,
|
|
88
|
+
};
|
|
89
|
+
}
|