@workos/oagen-emitters 0.15.2 → 0.16.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/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +12 -0
- package/README.md +48 -1
- package/dist/index.d.mts +51 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +852 -2
- package/dist/index.mjs.map +1 -0
- package/dist/{plugin-Xkr83G9A.mjs → plugin-DuB1UozS.mjs} +56 -4
- package/dist/plugin-DuB1UozS.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +4 -4
- package/src/dotnet/naming.ts +1 -1
- package/src/go/naming.ts +1 -1
- package/src/index.ts +15 -0
- package/src/node/tests.ts +29 -3
- package/src/snippets/dotnet.ts +159 -0
- package/src/snippets/go.ts +148 -0
- package/src/snippets/index.ts +8 -0
- package/src/snippets/kotlin.ts +144 -0
- package/src/snippets/php.ts +149 -0
- package/src/snippets/plugin.ts +36 -0
- package/src/snippets/python.ts +135 -0
- package/src/snippets/ruby.ts +152 -0
- package/src/snippets/rust.ts +189 -0
- package/test/snippets/_helpers.ts +67 -0
- package/test/snippets/dotnet.test.ts +49 -0
- package/test/snippets/go.test.ts +94 -0
- package/test/snippets/kotlin.test.ts +53 -0
- package/test/snippets/php.test.ts +48 -0
- package/test/snippets/python.test.ts +73 -0
- package/test/snippets/ruby.test.ts +339 -0
- package/test/snippets/rust.test.ts +76 -0
- package/dist/plugin-Xkr83G9A.mjs.map +0 -1
package/dist/plugin.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as workosEmittersPlugin } from "./plugin-
|
|
1
|
+
import { t as workosEmittersPlugin } from "./plugin-DuB1UozS.mjs";
|
|
2
2
|
export { workosEmittersPlugin };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@workos/oagen-emitters",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "WorkOS' oagen emitters",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "WorkOS",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
"@commitlint/config-conventional": "^21.0.2",
|
|
43
43
|
"@types/node": "^25.9.1",
|
|
44
44
|
"husky": "^9.1.7",
|
|
45
|
-
"oxfmt": "^0.
|
|
46
|
-
"oxlint": "^1.
|
|
45
|
+
"oxfmt": "^0.53.0",
|
|
46
|
+
"oxlint": "^1.68.0",
|
|
47
47
|
"prettier": "^3.8.3",
|
|
48
48
|
"tsdown": "^0.22.1",
|
|
49
49
|
"tsx": "^4.22.4",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"node": ">=24.10.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@workos/oagen": "^0.
|
|
57
|
+
"@workos/oagen": "^0.22.0"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/src/dotnet/naming.ts
CHANGED
|
@@ -149,7 +149,7 @@ function wordsMatch(left: string, right: string): boolean {
|
|
|
149
149
|
return singularize(left.toLowerCase()) === singularize(right.toLowerCase());
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
-
function trimMountedResourceFromMethod(method: string, mountName: string): string {
|
|
152
|
+
export function trimMountedResourceFromMethod(method: string, mountName: string): string {
|
|
153
153
|
const methodWords = splitPascalWords(method);
|
|
154
154
|
if (methodWords.length < 2) return method;
|
|
155
155
|
|
package/src/go/naming.ts
CHANGED
|
@@ -157,7 +157,7 @@ function wordsMatch(left: string, right: string): boolean {
|
|
|
157
157
|
return singularize(left.toLowerCase()) === singularize(right.toLowerCase());
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
function trimMountedResourceFromMethod(method: string, mountName: string): string {
|
|
160
|
+
export function trimMountedResourceFromMethod(method: string, mountName: string): string {
|
|
161
161
|
const methodWords = splitPascalWords(method);
|
|
162
162
|
if (methodWords.length < 2) return method;
|
|
163
163
|
|
package/src/index.ts
CHANGED
|
@@ -18,3 +18,18 @@ export { dotnetExtractor } from './compat/extractors/dotnet.js';
|
|
|
18
18
|
export { elixirExtractor } from './compat/extractors/elixir.js';
|
|
19
19
|
|
|
20
20
|
export { workosEmittersPlugin } from './plugin.js';
|
|
21
|
+
|
|
22
|
+
// Language-specific snippet emitters. The framework primitives
|
|
23
|
+
// (SnippetEmitter, runSnippetEmitters, snippetResultsToFiles,
|
|
24
|
+
// createExampleBuilder, collectSnippetArgs, collectWrapperArgs, etc.)
|
|
25
|
+
// live upstream in @workos/oagen — consumers import those from there.
|
|
26
|
+
export {
|
|
27
|
+
rubySnippetEmitter,
|
|
28
|
+
pythonSnippetEmitter,
|
|
29
|
+
phpSnippetEmitter,
|
|
30
|
+
goSnippetEmitter,
|
|
31
|
+
dotnetSnippetEmitter,
|
|
32
|
+
kotlinSnippetEmitter,
|
|
33
|
+
rustSnippetEmitter,
|
|
34
|
+
workosSnippetsPlugin,
|
|
35
|
+
} from './snippets/index.js';
|
package/src/node/tests.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
ApiSpec,
|
|
5
|
+
Service,
|
|
6
|
+
Operation,
|
|
7
|
+
Model,
|
|
8
|
+
TypeRef,
|
|
9
|
+
Parameter,
|
|
10
|
+
EmitterContext,
|
|
11
|
+
GeneratedFile,
|
|
12
|
+
} from '@workos/oagen';
|
|
4
13
|
import { planOperation, toCamelCase, toPascalCase } from '@workos/oagen';
|
|
5
14
|
import { unwrapListModel, ID_PREFIXES } from './fixtures.js';
|
|
6
15
|
import {
|
|
@@ -333,6 +342,20 @@ function pathParamTestValue(param: { type: TypeRef; name?: string } | undefined,
|
|
|
333
342
|
return 'test_id';
|
|
334
343
|
}
|
|
335
344
|
|
|
345
|
+
function queryParamTestValue(param: Parameter, modelMap?: Map<string, Model>): string {
|
|
346
|
+
if (param.example !== undefined) {
|
|
347
|
+
if (Array.isArray(param.example)) {
|
|
348
|
+
return `[${param.example.map((v: unknown) => (typeof v === 'string' ? `'${v}'` : String(v))).join(', ')}]`;
|
|
349
|
+
}
|
|
350
|
+
const isDateTime = param.type.kind === 'primitive' && param.type.format === 'date-time';
|
|
351
|
+
if (isDateTime && typeof param.example === 'string') {
|
|
352
|
+
return `new Date('${param.example}')`;
|
|
353
|
+
}
|
|
354
|
+
return typeof param.example === 'string' ? `'${param.example}'` : String(param.example);
|
|
355
|
+
}
|
|
356
|
+
return fixtureValueForType(param.type, param.name, 'Options', modelMap) ?? "'test'";
|
|
357
|
+
}
|
|
358
|
+
|
|
336
359
|
/** Build test arguments for all path params (handles multiple path params). */
|
|
337
360
|
function buildTestPathArgs(op: Operation): string {
|
|
338
361
|
// Detect path template variables (may be more than op.pathParams if spec is incomplete)
|
|
@@ -372,7 +395,10 @@ function renderPaginatedTest(
|
|
|
372
395
|
const optionsArg = buildOptionsObjectTestArg(op, plan, baselineMethod, modelMap, ctx);
|
|
373
396
|
const baselineItemType = autoPaginatableItemType(baselineMethod?.returnType);
|
|
374
397
|
const generatedItemType = ctx ? resolveInterfaceName(itemModelName, ctx) : null;
|
|
375
|
-
const
|
|
398
|
+
const baselineHasNonPaginatableReturn = Boolean(baselineMethod?.returnType && !baselineItemType);
|
|
399
|
+
const skipFieldAssertions =
|
|
400
|
+
baselineHasNonPaginatableReturn ||
|
|
401
|
+
Boolean(baselineItemType && generatedItemType && baselineItemType !== generatedItemType);
|
|
376
402
|
|
|
377
403
|
lines.push(" it('returns paginated results', async () => {");
|
|
378
404
|
lines.push(` fetchOnce(list${itemModelName}Fixture);`);
|
|
@@ -649,7 +675,7 @@ function buildOptionsObjectTestArg(
|
|
|
649
675
|
: op.queryParams;
|
|
650
676
|
for (const param of queryParams) {
|
|
651
677
|
const localName = fieldName(param.name);
|
|
652
|
-
const value =
|
|
678
|
+
const value = queryParamTestValue(param, modelMap);
|
|
653
679
|
entries.push(`${localName}: ${value}`);
|
|
654
680
|
}
|
|
655
681
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import type { EmitterContext, ExampleBuilder, ResolvedOperation, SnippetArg, SnippetEmitter } from '@workos/oagen';
|
|
2
|
+
import { collectSnippetArgs, collectWrapperArgs } from '@workos/oagen';
|
|
3
|
+
import {
|
|
4
|
+
appendAsyncSuffix,
|
|
5
|
+
className,
|
|
6
|
+
fieldName,
|
|
7
|
+
methodName,
|
|
8
|
+
trimMountedResourceFromMethod,
|
|
9
|
+
} from '../dotnet/naming.js';
|
|
10
|
+
|
|
11
|
+
const INDENT = ' ';
|
|
12
|
+
|
|
13
|
+
export const dotnetSnippetEmitter: SnippetEmitter = {
|
|
14
|
+
language: 'dotnet',
|
|
15
|
+
fileExtension: 'cs',
|
|
16
|
+
|
|
17
|
+
renderOperation(resolved, ctx, examples) {
|
|
18
|
+
if (resolved.urlBuilder) return null;
|
|
19
|
+
|
|
20
|
+
// Mirror the .NET SDK's naming pipeline directly from the resolved op:
|
|
21
|
+
// PascalCase → trim mount-target resource → append Async. We avoid
|
|
22
|
+
// calling back into resolveMethodName so the snippet emitter does not
|
|
23
|
+
// re-validate the whole resolved-ops set on every invocation.
|
|
24
|
+
const rawMethodName =
|
|
25
|
+
resolved.wrappers && resolved.wrappers.length > 0 ? resolved.wrappers[0]!.name : resolved.methodName;
|
|
26
|
+
const stem = trimMountedResourceFromMethod(methodName(rawMethodName), resolved.mountOn);
|
|
27
|
+
const method = appendAsyncSuffix(stem);
|
|
28
|
+
|
|
29
|
+
return renderCall(resolved, ctx, examples, method);
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
function renderCall(
|
|
34
|
+
resolved: ResolvedOperation,
|
|
35
|
+
ctx: EmitterContext,
|
|
36
|
+
examples: ExampleBuilder,
|
|
37
|
+
method: string,
|
|
38
|
+
): string {
|
|
39
|
+
const accessor = className(resolved.mountOn);
|
|
40
|
+
// Options class follows the `{Resource}{Method}Options` convention used by
|
|
41
|
+
// the SDK (e.g. OrganizationsCreateOptions). The method here already had its
|
|
42
|
+
// resource prefix trimmed and Async suffix appended, so reconstruct.
|
|
43
|
+
const stem = method.replace(/Async$/, '');
|
|
44
|
+
const optsType = `${accessor}${stem}Options`;
|
|
45
|
+
|
|
46
|
+
let args: SnippetArg[];
|
|
47
|
+
let pathArgs: SnippetArg[];
|
|
48
|
+
let optionsArgs: SnippetArg[];
|
|
49
|
+
|
|
50
|
+
if (resolved.wrappers && resolved.wrappers.length > 0) {
|
|
51
|
+
args = collectWrapperArgs(resolved.wrappers[0]!, ctx, examples);
|
|
52
|
+
pathArgs = [];
|
|
53
|
+
optionsArgs = args;
|
|
54
|
+
} else {
|
|
55
|
+
const collected = collectSnippetArgs(resolved, ctx, examples);
|
|
56
|
+
args = collected.args;
|
|
57
|
+
pathArgs = args.filter((a) => a.source === 'path');
|
|
58
|
+
optionsArgs = args.filter((a) => a.source !== 'path');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const lines: string[] = [];
|
|
62
|
+
lines.push('using WorkOS;');
|
|
63
|
+
lines.push('');
|
|
64
|
+
lines.push('var client = new WorkOSClient(new WorkOSOptions');
|
|
65
|
+
lines.push('{');
|
|
66
|
+
lines.push(`${INDENT}ApiKey = "sk_example_123456789",`);
|
|
67
|
+
lines.push(`${INDENT}ClientId = "client_123456789",`);
|
|
68
|
+
lines.push('});');
|
|
69
|
+
lines.push('');
|
|
70
|
+
|
|
71
|
+
const callParts: string[] = [];
|
|
72
|
+
for (const p of pathArgs) callParts.push(renderValue(p.value));
|
|
73
|
+
if (optionsArgs.length > 0) callParts.push(renderOptions(optsType, optionsArgs));
|
|
74
|
+
|
|
75
|
+
const lhs = `await client.${accessor}.${method}`;
|
|
76
|
+
if (callParts.length === 0) {
|
|
77
|
+
lines.push(`${lhs}();`);
|
|
78
|
+
} else if (callParts.length === 1) {
|
|
79
|
+
lines.push(`${lhs}(${indentContinuationLines(callParts[0]!, INDENT)});`);
|
|
80
|
+
} else {
|
|
81
|
+
lines.push(`${lhs}(`);
|
|
82
|
+
for (let i = 0; i < callParts.length; i++) {
|
|
83
|
+
const trailing = i < callParts.length - 1 ? ',' : '';
|
|
84
|
+
lines.push(`${INDENT}${indentContinuationLines(callParts[i]!, INDENT)}${trailing}`);
|
|
85
|
+
}
|
|
86
|
+
lines.push(');');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function renderOptions(typeName: string, args: SnippetArg[]): string {
|
|
93
|
+
const lines: string[] = [`new ${typeName}`, '{'];
|
|
94
|
+
for (let i = 0; i < args.length; i++) {
|
|
95
|
+
const a = args[i]!;
|
|
96
|
+
const field = fieldName(a.wireName);
|
|
97
|
+
const value = renderValue(a.value);
|
|
98
|
+
const trailing = i < args.length - 1 ? ',' : ',';
|
|
99
|
+
lines.push(`${INDENT}${field} = ${indentContinuationLines(value, INDENT)}${trailing}`);
|
|
100
|
+
}
|
|
101
|
+
lines.push('}');
|
|
102
|
+
return lines.join('\n');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// C# literal rendering
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
function renderValue(value: unknown): string {
|
|
110
|
+
if (value === null || value === undefined) return 'null';
|
|
111
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
112
|
+
if (typeof value === 'number') return String(value);
|
|
113
|
+
if (typeof value === 'string') return csharpString(value);
|
|
114
|
+
if (Array.isArray(value)) return renderArray(value);
|
|
115
|
+
if (typeof value === 'object') return renderDict(value as Record<string, unknown>);
|
|
116
|
+
return 'null';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function csharpString(s: string): string {
|
|
120
|
+
return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function renderArray(items: unknown[]): string {
|
|
124
|
+
if (items.length === 0) return 'new[] { }';
|
|
125
|
+
const rendered = items.map((v) => renderValue(v));
|
|
126
|
+
const oneline = `new[] { ${rendered.join(', ')} }`;
|
|
127
|
+
if (oneline.length <= 80 && rendered.every((r) => !r.includes('\n'))) return oneline;
|
|
128
|
+
const lines: string[] = ['new[]', '{'];
|
|
129
|
+
for (let i = 0; i < rendered.length; i++) {
|
|
130
|
+
const trailing = i < rendered.length - 1 ? ',' : ',';
|
|
131
|
+
lines.push(`${INDENT}${indentContinuationLines(rendered[i]!, INDENT)}${trailing}`);
|
|
132
|
+
}
|
|
133
|
+
lines.push('}');
|
|
134
|
+
return lines.join('\n');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function renderDict(obj: Record<string, unknown>): string {
|
|
138
|
+
// Plain dictionary literal — the dotnet SDK exposes models, but the snippet
|
|
139
|
+
// doesn't know which model class corresponds to each nested object. Emit a
|
|
140
|
+
// Dictionary<string, object> initializer that the SDK serializer can handle
|
|
141
|
+
// or a developer can replace with the typed model.
|
|
142
|
+
const entries = Object.entries(obj);
|
|
143
|
+
if (entries.length === 0) return 'new Dictionary<string, object>()';
|
|
144
|
+
const rendered = entries.map(([k, v]) => ({ key: k, value: renderValue(v) }));
|
|
145
|
+
const lines: string[] = ['new Dictionary<string, object>', '{'];
|
|
146
|
+
for (let i = 0; i < rendered.length; i++) {
|
|
147
|
+
const e = rendered[i]!;
|
|
148
|
+
const trailing = i < rendered.length - 1 ? ',' : ',';
|
|
149
|
+
lines.push(`${INDENT}{ "${e.key}", ${indentContinuationLines(e.value, INDENT)} }${trailing}`);
|
|
150
|
+
}
|
|
151
|
+
lines.push('}');
|
|
152
|
+
return lines.join('\n');
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function indentContinuationLines(s: string, indent: string): string {
|
|
156
|
+
if (!s.includes('\n')) return s;
|
|
157
|
+
const lines = s.split('\n');
|
|
158
|
+
return lines.map((line, i) => (i === 0 ? line : `${indent}${line}`)).join('\n');
|
|
159
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { EmitterContext, ExampleBuilder, ResolvedOperation, SnippetArg, SnippetEmitter } from '@workos/oagen';
|
|
2
|
+
import { collectSnippetArgs, collectWrapperArgs } from '@workos/oagen';
|
|
3
|
+
import { className, fieldName, methodName, trimMountedResourceFromMethod } from '../go/naming.js';
|
|
4
|
+
|
|
5
|
+
const INDENT = '\t';
|
|
6
|
+
|
|
7
|
+
export const goSnippetEmitter: SnippetEmitter = {
|
|
8
|
+
language: 'go',
|
|
9
|
+
fileExtension: 'go',
|
|
10
|
+
|
|
11
|
+
renderOperation(resolved, ctx, examples) {
|
|
12
|
+
if (resolved.urlBuilder) return null;
|
|
13
|
+
|
|
14
|
+
// Mirror the Go SDK's naming pipeline: PascalCase the resolved method
|
|
15
|
+
// name (or the wrapper name for split ops), then trim any mount-target
|
|
16
|
+
// resource words from the end so `CreateOrganization` on `Organizations`
|
|
17
|
+
// becomes `Create`. We compute these directly from the resolved op so
|
|
18
|
+
// the snippet emitter does not depend on a fresh `buildResolvedLookup`
|
|
19
|
+
// (which would re-validate the entire spec for uniqueness).
|
|
20
|
+
const rawMethodName =
|
|
21
|
+
resolved.wrappers && resolved.wrappers.length > 0 ? resolved.wrappers[0]!.name : resolved.methodName;
|
|
22
|
+
const method = trimMountedResourceFromMethod(methodName(rawMethodName), resolved.mountOn);
|
|
23
|
+
|
|
24
|
+
return renderCall(resolved, ctx, examples, method);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function renderCall(
|
|
29
|
+
resolved: ResolvedOperation,
|
|
30
|
+
ctx: EmitterContext,
|
|
31
|
+
examples: ExampleBuilder,
|
|
32
|
+
method: string,
|
|
33
|
+
): string {
|
|
34
|
+
const accessorMethod = className(resolved.mountOn);
|
|
35
|
+
const optsTypeName = `${accessorMethod}${method}Params`;
|
|
36
|
+
|
|
37
|
+
let args: SnippetArg[];
|
|
38
|
+
let pathArgs: SnippetArg[];
|
|
39
|
+
let bodyAndQueryArgs: SnippetArg[];
|
|
40
|
+
|
|
41
|
+
if (resolved.wrappers && resolved.wrappers.length > 0) {
|
|
42
|
+
args = collectWrapperArgs(resolved.wrappers[0]!, ctx, examples);
|
|
43
|
+
pathArgs = [];
|
|
44
|
+
bodyAndQueryArgs = args;
|
|
45
|
+
} else {
|
|
46
|
+
const collected = collectSnippetArgs(resolved, ctx, examples);
|
|
47
|
+
args = collected.args;
|
|
48
|
+
pathArgs = args.filter((a) => a.source === 'path');
|
|
49
|
+
bodyAndQueryArgs = args.filter((a) => a.source !== 'path');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const lines: string[] = [];
|
|
53
|
+
lines.push('package main');
|
|
54
|
+
lines.push('');
|
|
55
|
+
lines.push('import (');
|
|
56
|
+
lines.push(`${INDENT}"context"`);
|
|
57
|
+
lines.push('');
|
|
58
|
+
lines.push(`${INDENT}"github.com/workos/workos-go/v9"`);
|
|
59
|
+
lines.push(')');
|
|
60
|
+
lines.push('');
|
|
61
|
+
lines.push('func main() {');
|
|
62
|
+
lines.push(`${INDENT}client := workos.NewClient("sk_example_123456789")`);
|
|
63
|
+
lines.push('');
|
|
64
|
+
|
|
65
|
+
// Build the method call. Go SDK methods take (ctx, positional path params..., *OptsStruct).
|
|
66
|
+
const callParts: string[] = ['context.Background()'];
|
|
67
|
+
for (const p of pathArgs) {
|
|
68
|
+
callParts.push(renderValue(p.value));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (bodyAndQueryArgs.length === 0) {
|
|
72
|
+
lines.push(`${INDENT}_, err := client.${accessorMethod}().${method}(${callParts.join(', ')})`);
|
|
73
|
+
} else {
|
|
74
|
+
const optsLines = buildOptsStruct(optsTypeName, bodyAndQueryArgs);
|
|
75
|
+
callParts.push(optsLines);
|
|
76
|
+
const joined = callParts.length === 2 ? callParts.join(', ') : callParts.join(', ');
|
|
77
|
+
lines.push(`${INDENT}_, err := client.${accessorMethod}().${method}(${joined})`);
|
|
78
|
+
}
|
|
79
|
+
lines.push(`${INDENT}if err != nil {`);
|
|
80
|
+
lines.push(`${INDENT}${INDENT}panic(err)`);
|
|
81
|
+
lines.push(`${INDENT}}`);
|
|
82
|
+
lines.push('}');
|
|
83
|
+
|
|
84
|
+
return lines.join('\n');
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function buildOptsStruct(typeName: string, args: SnippetArg[]): string {
|
|
88
|
+
const lines: string[] = [`&workos.${typeName}{`];
|
|
89
|
+
for (const a of args) {
|
|
90
|
+
const field = fieldName(a.wireName);
|
|
91
|
+
const value = renderValue(a.value);
|
|
92
|
+
const indentedValue = indentContinuationLines(value, `${INDENT}${INDENT}`);
|
|
93
|
+
lines.push(`${INDENT}${INDENT}${field}: ${indentedValue},`);
|
|
94
|
+
}
|
|
95
|
+
lines.push(`${INDENT}}`);
|
|
96
|
+
return lines.join('\n');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// ---------------------------------------------------------------------------
|
|
100
|
+
// Go literal rendering
|
|
101
|
+
// ---------------------------------------------------------------------------
|
|
102
|
+
|
|
103
|
+
function renderValue(value: unknown): string {
|
|
104
|
+
if (value === null || value === undefined) return 'nil';
|
|
105
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
106
|
+
if (typeof value === 'number') return String(value);
|
|
107
|
+
if (typeof value === 'string') return goString(value);
|
|
108
|
+
if (Array.isArray(value)) return renderSlice(value);
|
|
109
|
+
if (typeof value === 'object') return renderStruct(value as Record<string, unknown>);
|
|
110
|
+
return 'nil';
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function goString(s: string): string {
|
|
114
|
+
return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function renderSlice(items: unknown[]): string {
|
|
118
|
+
if (items.length === 0) return '[]any{}';
|
|
119
|
+
const rendered = items.map((v) => renderValue(v));
|
|
120
|
+
const oneline = `[]any{${rendered.join(', ')}}`;
|
|
121
|
+
if (oneline.length <= 80 && rendered.every((r) => !r.includes('\n'))) return oneline;
|
|
122
|
+
const lines: string[] = ['[]any{'];
|
|
123
|
+
for (const r of rendered) {
|
|
124
|
+
lines.push(`${INDENT}${indentContinuationLines(r, INDENT)},`);
|
|
125
|
+
}
|
|
126
|
+
lines.push('}');
|
|
127
|
+
return lines.join('\n');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function renderStruct(obj: Record<string, unknown>): string {
|
|
131
|
+
const entries = Object.entries(obj);
|
|
132
|
+
if (entries.length === 0) return 'map[string]any{}';
|
|
133
|
+
const rendered = entries.map(([k, v]) => ({ key: k, value: renderValue(v) }));
|
|
134
|
+
const oneline = `map[string]any{${rendered.map((e) => `"${e.key}": ${e.value}`).join(', ')}}`;
|
|
135
|
+
if (oneline.length <= 80 && rendered.every((e) => !e.value.includes('\n'))) return oneline;
|
|
136
|
+
const lines: string[] = ['map[string]any{'];
|
|
137
|
+
for (const e of rendered) {
|
|
138
|
+
lines.push(`${INDENT}"${e.key}": ${indentContinuationLines(e.value, INDENT)},`);
|
|
139
|
+
}
|
|
140
|
+
lines.push('}');
|
|
141
|
+
return lines.join('\n');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function indentContinuationLines(s: string, indent: string): string {
|
|
145
|
+
if (!s.includes('\n')) return s;
|
|
146
|
+
const lines = s.split('\n');
|
|
147
|
+
return lines.map((line, i) => (i === 0 ? line : `${indent}${line}`)).join('\n');
|
|
148
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { rubySnippetEmitter } from './ruby.js';
|
|
2
|
+
export { pythonSnippetEmitter } from './python.js';
|
|
3
|
+
export { phpSnippetEmitter } from './php.js';
|
|
4
|
+
export { goSnippetEmitter } from './go.js';
|
|
5
|
+
export { dotnetSnippetEmitter } from './dotnet.js';
|
|
6
|
+
export { kotlinSnippetEmitter } from './kotlin.js';
|
|
7
|
+
export { rustSnippetEmitter } from './rust.js';
|
|
8
|
+
export { workosSnippetsPlugin } from './plugin.js';
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import type { EmitterContext, ExampleBuilder, ResolvedOperation, SnippetArg, SnippetEmitter } from '@workos/oagen';
|
|
2
|
+
import { collectSnippetArgs, collectWrapperArgs } from '@workos/oagen';
|
|
3
|
+
import { apiClassName, methodName, packageSegment, propertyName } from '../kotlin/naming.js';
|
|
4
|
+
|
|
5
|
+
const INDENT = ' ';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Emits Java-syntax snippets (rendered as `.java`) backed by the Kotlin SDK's
|
|
9
|
+
* naming so JVM callers see the same method and options names whether they
|
|
10
|
+
* write Kotlin or Java. The WorkOS docs render the `.java` extension because
|
|
11
|
+
* Java is the most common JVM consumer; Kotlin call sites would look almost
|
|
12
|
+
* identical apart from `var`/`val` and named-argument syntax.
|
|
13
|
+
*/
|
|
14
|
+
export const kotlinSnippetEmitter: SnippetEmitter = {
|
|
15
|
+
language: 'java',
|
|
16
|
+
fileExtension: 'java',
|
|
17
|
+
|
|
18
|
+
renderOperation(resolved, ctx, examples) {
|
|
19
|
+
if (resolved.urlBuilder) return null;
|
|
20
|
+
|
|
21
|
+
const method =
|
|
22
|
+
resolved.wrappers && resolved.wrappers.length > 0
|
|
23
|
+
? methodName(resolved.wrappers[0]!.name)
|
|
24
|
+
: methodName(resolved.methodName);
|
|
25
|
+
|
|
26
|
+
return renderCall(resolved, ctx, examples, method);
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function renderCall(
|
|
31
|
+
resolved: ResolvedOperation,
|
|
32
|
+
ctx: EmitterContext,
|
|
33
|
+
examples: ExampleBuilder,
|
|
34
|
+
method: string,
|
|
35
|
+
): string {
|
|
36
|
+
const apiClass = apiClassName(resolved.mountOn);
|
|
37
|
+
const accessor = propertyName(resolved.mountOn);
|
|
38
|
+
const optionsType = `${methodName(
|
|
39
|
+
resolved.wrappers && resolved.wrappers.length > 0 ? resolved.wrappers[0]!.name : resolved.methodName,
|
|
40
|
+
)}Options`;
|
|
41
|
+
const optionsTypeCapitalized = optionsType[0]!.toUpperCase() + optionsType.slice(1);
|
|
42
|
+
const apiSubPackage = packageSegment(resolved.mountOn);
|
|
43
|
+
|
|
44
|
+
let args: SnippetArg[];
|
|
45
|
+
let pathArgs: SnippetArg[];
|
|
46
|
+
let optionsArgs: SnippetArg[];
|
|
47
|
+
|
|
48
|
+
if (resolved.wrappers && resolved.wrappers.length > 0) {
|
|
49
|
+
args = collectWrapperArgs(resolved.wrappers[0]!, ctx, examples);
|
|
50
|
+
pathArgs = [];
|
|
51
|
+
optionsArgs = args;
|
|
52
|
+
} else {
|
|
53
|
+
const collected = collectSnippetArgs(resolved, ctx, examples);
|
|
54
|
+
args = collected.args;
|
|
55
|
+
pathArgs = args.filter((a) => a.source === 'path');
|
|
56
|
+
optionsArgs = args.filter((a) => a.source !== 'path');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const lines: string[] = [];
|
|
60
|
+
lines.push('import com.workos.WorkOS;');
|
|
61
|
+
if (optionsArgs.length > 0) {
|
|
62
|
+
lines.push(`import com.workos.${apiSubPackage}.${apiClass}Api.${optionsTypeCapitalized};`);
|
|
63
|
+
}
|
|
64
|
+
lines.push('');
|
|
65
|
+
lines.push('WorkOS workos = new WorkOS("sk_example_123456789");');
|
|
66
|
+
lines.push('');
|
|
67
|
+
|
|
68
|
+
const callParts: string[] = pathArgs.map((p) => renderValue(p.value));
|
|
69
|
+
|
|
70
|
+
if (optionsArgs.length > 0) {
|
|
71
|
+
lines.push(`${optionsTypeCapitalized} options = ${optionsTypeCapitalized}.builder()`);
|
|
72
|
+
for (const a of optionsArgs) {
|
|
73
|
+
const prop = propertyName(a.wireName);
|
|
74
|
+
const value = renderValue(a.value);
|
|
75
|
+
const indentedValue = indentContinuationLines(value, INDENT);
|
|
76
|
+
lines.push(`${INDENT}.${prop}(${indentedValue})`);
|
|
77
|
+
}
|
|
78
|
+
lines.push(`${INDENT}.build();`);
|
|
79
|
+
lines.push('');
|
|
80
|
+
callParts.push('options');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (callParts.length === 0) {
|
|
84
|
+
lines.push(`workos.${accessor}.${method}();`);
|
|
85
|
+
} else {
|
|
86
|
+
lines.push(`workos.${accessor}.${method}(${callParts.join(', ')});`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// ---------------------------------------------------------------------------
|
|
93
|
+
// Java literal rendering
|
|
94
|
+
// ---------------------------------------------------------------------------
|
|
95
|
+
|
|
96
|
+
function renderValue(value: unknown): string {
|
|
97
|
+
if (value === null || value === undefined) return 'null';
|
|
98
|
+
if (typeof value === 'boolean') return value ? 'true' : 'false';
|
|
99
|
+
if (typeof value === 'number') return String(value);
|
|
100
|
+
if (typeof value === 'string') return javaString(value);
|
|
101
|
+
if (Array.isArray(value)) return renderList(value);
|
|
102
|
+
if (typeof value === 'object') return renderMap(value as Record<string, unknown>);
|
|
103
|
+
return 'null';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function javaString(s: string): string {
|
|
107
|
+
return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function renderList(items: unknown[]): string {
|
|
111
|
+
if (items.length === 0) return 'List.of()';
|
|
112
|
+
const rendered = items.map((v) => renderValue(v));
|
|
113
|
+
const oneline = `List.of(${rendered.join(', ')})`;
|
|
114
|
+
if (oneline.length <= 80 && rendered.every((r) => !r.includes('\n'))) return oneline;
|
|
115
|
+
const lines: string[] = ['List.of('];
|
|
116
|
+
for (let i = 0; i < rendered.length; i++) {
|
|
117
|
+
const trailing = i < rendered.length - 1 ? ',' : '';
|
|
118
|
+
lines.push(`${INDENT}${indentContinuationLines(rendered[i]!, INDENT)}${trailing}`);
|
|
119
|
+
}
|
|
120
|
+
lines.push(')');
|
|
121
|
+
return lines.join('\n');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function renderMap(obj: Record<string, unknown>): string {
|
|
125
|
+
const entries = Object.entries(obj);
|
|
126
|
+
if (entries.length === 0) return 'Map.of()';
|
|
127
|
+
const rendered = entries.map(([k, v]) => ({ key: k, value: renderValue(v) }));
|
|
128
|
+
const oneline = `Map.of(${rendered.map((e) => `"${e.key}", ${e.value}`).join(', ')})`;
|
|
129
|
+
if (oneline.length <= 80 && rendered.every((e) => !e.value.includes('\n'))) return oneline;
|
|
130
|
+
const lines: string[] = ['Map.of('];
|
|
131
|
+
for (let i = 0; i < rendered.length; i++) {
|
|
132
|
+
const e = rendered[i]!;
|
|
133
|
+
const trailing = i < rendered.length - 1 ? ',' : '';
|
|
134
|
+
lines.push(`${INDENT}"${e.key}", ${indentContinuationLines(e.value, INDENT)}${trailing}`);
|
|
135
|
+
}
|
|
136
|
+
lines.push(')');
|
|
137
|
+
return lines.join('\n');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function indentContinuationLines(s: string, indent: string): string {
|
|
141
|
+
if (!s.includes('\n')) return s;
|
|
142
|
+
const lines = s.split('\n');
|
|
143
|
+
return lines.map((line, i) => (i === 0 ? line : `${indent}${line}`)).join('\n');
|
|
144
|
+
}
|