@workos/oagen-emitters 0.15.1 → 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/dist/plugin.mjs CHANGED
@@ -1,2 +1,2 @@
1
- import { t as workosEmittersPlugin } from "./plugin-C2Hp2Vs2.mjs";
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.15.1",
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.52.0",
46
- "oxlint": "^1.67.0",
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.21.1"
57
+ "@workos/oagen": "^0.22.0"
58
58
  }
59
59
  }
@@ -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';
@@ -1673,10 +1673,13 @@ function renderOptionsObjectMethod(
1673
1673
  (itemRawName ? resolveInterfaceName(itemRawName, ctx) : responseModel);
1674
1674
  if (!itemType) return false;
1675
1675
  const wireType = wireInterfaceName(itemType);
1676
- const returnType =
1677
- preferredBaselineReturnType(ctx, baselineMethod?.returnType) ?? `Promise<AutoPaginatable<${itemType}>>`;
1678
1676
  const extraParams = op.queryParams.filter((p) => !PAGINATION_PARAM_NAMES.has(p.name));
1679
1677
  const needsWireSerializer = extraParams.some((p) => fieldName(p.name) !== wireFieldName(p.name));
1678
+ const paginationType = needsWireSerializer ? 'PaginationOptions' : optionParam.type;
1679
+ const returnType = needsWireSerializer
1680
+ ? `Promise<AutoPaginatable<${itemType}, ${paginationType}>>`
1681
+ : (preferredBaselineReturnType(ctx, baselineMethod?.returnType) ??
1682
+ `Promise<AutoPaginatable<${itemType}, ${paginationType}>>`);
1680
1683
  const listOptionsExpr = needsWireSerializer
1681
1684
  ? `options ? serialize${optionParam.type}(options) : undefined`
1682
1685
  : 'paginationOptions';
@@ -1887,7 +1890,8 @@ function renderPaginatedMethod(
1887
1890
  const wireType = wireInterfaceName(itemType);
1888
1891
  const serializeCall = serializerArg ? `options ? serialize${optionsType}(options) : undefined` : 'options';
1889
1892
 
1890
- lines.push(` async ${method}(${allParams}): Promise<AutoPaginatable<${itemType}, ${optionsType}>> {`);
1893
+ const paginationType = needsWireSerializer ? 'PaginationOptions' : optionsType;
1894
+ lines.push(` async ${method}(${allParams}): Promise<AutoPaginatable<${itemType}, ${paginationType}>> {`);
1891
1895
  lines.push(` return new AutoPaginatable(`);
1892
1896
  lines.push(` await fetchAndDeserialize<${wireType}, ${itemType}>(`);
1893
1897
  lines.push(` this.workos,`);
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 { ApiSpec, Service, Operation, Model, TypeRef, EmitterContext, GeneratedFile } from '@workos/oagen';
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 skipFieldAssertions = Boolean(baselineItemType && generatedItemType && baselineItemType !== generatedItemType);
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 = fixtureValueForType(param.type, param.name, 'Options', modelMap) ?? "'test'";
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
+ }