@workos/oagen-emitters 0.10.0 → 0.11.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 +16 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-H0KhxbN7.mjs → plugin-DW3cnedr.mjs} +549 -202
- package/dist/plugin-DW3cnedr.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +2 -2
- package/src/go/models.ts +48 -3
- package/src/php/models.ts +27 -3
- package/src/php/resources.ts +16 -16
- package/src/python/enums.ts +11 -54
- package/src/python/models.ts +204 -219
- package/src/python/resources.ts +19 -35
- package/src/python/shared-schemas.ts +488 -0
- package/src/python/tests.ts +9 -7
- package/src/ruby/resources.ts +13 -1
- package/test/go/models.test.ts +116 -1
- package/test/go/resources.test.ts +70 -0
- package/test/php/models.test.ts +77 -0
- package/test/php/resources.test.ts +95 -0
- package/test/python/enums.test.ts +91 -0
- package/test/python/models.test.ts +225 -0
- package/test/python/resources.test.ts +45 -0
- package/test/ruby/resources.test.ts +58 -0
- package/dist/plugin-H0KhxbN7.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-DW3cnedr.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.11.0",
|
|
4
4
|
"description": "WorkOS' oagen emitters",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "WorkOS",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"node": ">=24.10.0"
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
|
-
"@workos/oagen": "^0.18.
|
|
57
|
+
"@workos/oagen": "^0.18.1"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/src/go/models.ts
CHANGED
|
@@ -198,7 +198,16 @@ export function generateModels(models: Model[], ctx: EmitterContext): GeneratedF
|
|
|
198
198
|
lines.push('');
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
// Emit shared PaginationParams struct for list operations to embed
|
|
201
|
+
// Emit shared PaginationParams struct for list operations to embed.
|
|
202
|
+
//
|
|
203
|
+
// The Order field's type is derived from the spec rather than hardcoded:
|
|
204
|
+
// when every paginated `order` query parameter $refs the same top-level
|
|
205
|
+
// enum (typically `PaginationOrder` in the WorkOS spec), we emit the typed
|
|
206
|
+
// enum so callers get compile-time validation. Otherwise we fall back to
|
|
207
|
+
// *string. The fallback handles older specs that don't lift the enum into a
|
|
208
|
+
// named component schema.
|
|
209
|
+
const orderEnumType = detectSharedOrderEnum(ctx.spec.services);
|
|
210
|
+
const orderGoType = orderEnumType ? `*${className(orderEnumType)}` : '*string';
|
|
202
211
|
lines.push('// PaginationParams contains common pagination parameters for list operations.');
|
|
203
212
|
lines.push('type PaginationParams struct {');
|
|
204
213
|
lines.push('\t// Before is a cursor for reverse pagination.');
|
|
@@ -207,8 +216,8 @@ export function generateModels(models: Model[], ctx: EmitterContext): GeneratedF
|
|
|
207
216
|
lines.push('\tAfter *string `url:"after,omitempty" json:"-"`');
|
|
208
217
|
lines.push('\t// Limit is the maximum number of items to return per page.');
|
|
209
218
|
lines.push('\tLimit *int `url:"limit,omitempty" json:"-"`');
|
|
210
|
-
lines.push('\t// Order is the sort order for results
|
|
211
|
-
lines.push(
|
|
219
|
+
lines.push('\t// Order is the sort order for results.');
|
|
220
|
+
lines.push(`\tOrder ${orderGoType} \`url:"order,omitempty" json:"-"\``);
|
|
212
221
|
lines.push('}');
|
|
213
222
|
lines.push('');
|
|
214
223
|
|
|
@@ -311,6 +320,42 @@ function lowerFirst(s: string): string {
|
|
|
311
320
|
return lowerFirstForDoc(s);
|
|
312
321
|
}
|
|
313
322
|
|
|
323
|
+
/**
|
|
324
|
+
* If every paginated list operation's `order` query parameter $refs the same
|
|
325
|
+
* top-level enum, return that enum's IR name. Otherwise return null. When
|
|
326
|
+
* the spec is consistent this lifts `PaginationParams.Order` from `*string`
|
|
327
|
+
* to `*PaginationOrder` (or whatever the spec calls it), giving callers
|
|
328
|
+
* compile-time validation.
|
|
329
|
+
*
|
|
330
|
+
* We require strict consistency: if any operation uses a primitive string for
|
|
331
|
+
* `order`, or two operations reference different enums, we conservatively
|
|
332
|
+
* stay on `*string` so the shared struct doesn't lie about its accepted
|
|
333
|
+
* values.
|
|
334
|
+
*/
|
|
335
|
+
function detectSharedOrderEnum(services: Service[]): string | null {
|
|
336
|
+
let candidate: string | null = null;
|
|
337
|
+
let sawAny = false;
|
|
338
|
+
for (const service of services) {
|
|
339
|
+
for (const op of service.operations) {
|
|
340
|
+
if (!op.pagination) continue;
|
|
341
|
+
const orderParam = op.queryParams.find((p) => p.name === 'order');
|
|
342
|
+
if (!orderParam) continue;
|
|
343
|
+
sawAny = true;
|
|
344
|
+
const enumName = unwrapEnumName(orderParam.type);
|
|
345
|
+
if (!enumName) return null;
|
|
346
|
+
if (candidate === null) candidate = enumName;
|
|
347
|
+
else if (candidate !== enumName) return null;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return sawAny ? candidate : null;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function unwrapEnumName(ref: TypeRef): string | null {
|
|
354
|
+
if (ref.kind === 'enum') return ref.name;
|
|
355
|
+
if (ref.kind === 'nullable') return unwrapEnumName(ref.inner);
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
|
|
314
359
|
/**
|
|
315
360
|
* Extract a deprecation reason from a field description.
|
|
316
361
|
* Looks for patterns like "Use X instead", "Replaced by Y", etc.
|
package/src/php/models.ts
CHANGED
|
@@ -221,15 +221,19 @@ function generateFromArrayValue(ref: TypeRef, accessor: string): string {
|
|
|
221
221
|
return generateFromArrayValue(ref.inner, accessor);
|
|
222
222
|
case 'union': {
|
|
223
223
|
// Discriminated union: dispatch via match() on the discriminator
|
|
224
|
-
// property to call the matching variant's fromArray.
|
|
225
|
-
//
|
|
224
|
+
// property to call the matching variant's fromArray. An unknown
|
|
225
|
+
// discriminator value would otherwise assign a raw array to a typed
|
|
226
|
+
// property and crash later with a confusing TypeError, so throw
|
|
227
|
+
// immediately with the offending value.
|
|
226
228
|
if (ref.discriminator && ref.discriminator.mapping) {
|
|
227
229
|
const entries = Object.entries(ref.discriminator.mapping);
|
|
228
230
|
if (entries.length > 0) {
|
|
229
231
|
const arms = entries
|
|
230
232
|
.map(([value, modelName]) => `'${value}' => ${className(modelName)}::fromArray(${accessor})`)
|
|
231
233
|
.join(', ');
|
|
232
|
-
|
|
234
|
+
const discProp = ref.discriminator.property;
|
|
235
|
+
const throwArm = `default => throw new \\UnexpectedValueException(sprintf('Unknown ${discProp}: %s', json_encode(${accessor}['${discProp}'] ?? null)))`;
|
|
236
|
+
return `match (${accessor}['${discProp}'] ?? null) { ${arms}, ${throwArm} }`;
|
|
233
237
|
}
|
|
234
238
|
}
|
|
235
239
|
const resolved = resolveDegenerateUnion(ref);
|
|
@@ -313,6 +317,26 @@ function generateToArrayValue(ref: TypeRef, accessor: string, nullable = false):
|
|
|
313
317
|
case 'union': {
|
|
314
318
|
const resolved = resolveDegenerateUnion(ref);
|
|
315
319
|
if (resolved) return generateToArrayValue(resolved, accessor, nullable);
|
|
320
|
+
// Polymorphic union of model variants: PHP dispatches to the concrete
|
|
321
|
+
// instance's toArray() at runtime, so a single ->toArray() call serializes
|
|
322
|
+
// any branch correctly without a match here.
|
|
323
|
+
if (ref.variants.every((v) => v.kind === 'model')) {
|
|
324
|
+
return `${accessor}${ns}->toArray()`;
|
|
325
|
+
}
|
|
326
|
+
// Heterogeneous unions involving models or enums have no uniform
|
|
327
|
+
// serialization strategy (->toArray() vs ->value vs raw scalar), so fail
|
|
328
|
+
// at codegen time rather than silently emitting a raw object that breaks
|
|
329
|
+
// the toArray contract. Pure scalar unions (e.g. string|int) fall
|
|
330
|
+
// through to the bare accessor below — that is correct.
|
|
331
|
+
if (ref.variants.some((v) => v.kind === 'model' || v.kind === 'enum')) {
|
|
332
|
+
const summary = ref.variants
|
|
333
|
+
.map((v) => (v.kind === 'model' ? `model:${v.name}` : v.kind === 'enum' ? `enum:${v.name}` : v.kind))
|
|
334
|
+
.join(' | ');
|
|
335
|
+
throw new Error(
|
|
336
|
+
`[php emitter] Cannot generate toArray for heterogeneous union: ${summary}. ` +
|
|
337
|
+
`Unions must be all-model or all-scalar; mixed and all-enum unions are not yet supported.`,
|
|
338
|
+
);
|
|
339
|
+
}
|
|
316
340
|
return accessor;
|
|
317
341
|
}
|
|
318
342
|
case 'literal':
|
package/src/php/resources.ts
CHANGED
|
@@ -301,9 +301,10 @@ function generateMethod(
|
|
|
301
301
|
const phpName = fieldName(q.name);
|
|
302
302
|
if (seenDocParams.has(phpName)) continue;
|
|
303
303
|
seenDocParams.add(phpName);
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
const
|
|
304
|
+
// Spec-defaulted enum params are non-nullable (the signature default is the
|
|
305
|
+
// enum case, never null). Without a spec default, the param is nullable.
|
|
306
|
+
const hasEnumDefault = q.default != null && q.type.kind === 'enum';
|
|
307
|
+
const nullSuffix = !q.required && !hasEnumDefault && !docType.endsWith('|null') ? '|null' : '';
|
|
307
308
|
const prefix = q.deprecated ? '(deprecated) ' : '';
|
|
308
309
|
let desc = q.description ? ` ${prefix}${q.description}` : q.deprecated ? ' (deprecated)' : '';
|
|
309
310
|
if (q.default != null) desc += ` Defaults to ${JSON.stringify(q.default)}.`;
|
|
@@ -682,16 +683,14 @@ function buildMethodParams(
|
|
|
682
683
|
usedNames.add(phpName);
|
|
683
684
|
if (q.required) {
|
|
684
685
|
required.push(`${phpType} $${phpName}`);
|
|
685
|
-
} else if (q.
|
|
686
|
-
//
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
optional.push(`${nullableType} $${phpName} = 'desc'`);
|
|
694
|
-
}
|
|
686
|
+
} else if (q.default != null && q.type.kind === 'enum') {
|
|
687
|
+
// Spec-provided default for an enum-typed param: emit a non-nullable
|
|
688
|
+
// typed default (e.g. PaginationOrder $order = PaginationOrder::Desc).
|
|
689
|
+
// Only enums are safe to default this way — primitives stay nullable so
|
|
690
|
+
// callers can distinguish "unset" from "explicit value".
|
|
691
|
+
const enumType = mapTypeRef(q.type, { qualified: true });
|
|
692
|
+
const caseName = toPascalCase(String(q.default));
|
|
693
|
+
optional.push(`${enumType} $${phpName} = ${enumType}::${caseName}`);
|
|
695
694
|
} else {
|
|
696
695
|
const nullableType = phpType.startsWith('?') ? phpType : `?${phpType}`;
|
|
697
696
|
optional.push(`${nullableType} $${phpName} = null`);
|
|
@@ -756,9 +755,10 @@ function buildQueryArray(op: Operation, hiddenParams?: Set<string>): string[] {
|
|
|
756
755
|
.map((q) => {
|
|
757
756
|
const phpName = fieldName(q.name);
|
|
758
757
|
if (isEnumType(q.type)) {
|
|
759
|
-
//
|
|
760
|
-
|
|
761
|
-
const
|
|
758
|
+
// Mirrors the signature: enum params with a spec default are
|
|
759
|
+
// non-nullable, so we can dereference ->value without the nullsafe op.
|
|
760
|
+
const hasEnumDefault = q.default != null && q.type.kind === 'enum';
|
|
761
|
+
const nullsafe = q.required || hasEnumDefault ? '' : '?';
|
|
762
762
|
return `'${q.name}' => $${phpName}${nullsafe}->value,`;
|
|
763
763
|
}
|
|
764
764
|
return `'${q.name}' => $${phpName},`;
|
package/src/python/enums.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { Enum, EmitterContext, GeneratedFile
|
|
2
|
-
import { toUpperSnakeCase
|
|
1
|
+
import type { Enum, EmitterContext, GeneratedFile } from '@workos/oagen';
|
|
2
|
+
import { toUpperSnakeCase } from '@workos/oagen';
|
|
3
3
|
import { className, fileName, buildMountDirMap, dirToModule } from './naming.js';
|
|
4
|
+
import { computeSchemaPlacement } from './shared-schemas.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Convert a PascalCase class name to a human-readable lowercase string,
|
|
@@ -21,14 +22,18 @@ function humanizeClassName(name: string): string {
|
|
|
21
22
|
export function generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile[] {
|
|
22
23
|
if (enums.length === 0) return [];
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
// Tests sometimes pass enums that aren't in ctx.spec.enums, so synthesize a
|
|
26
|
+
// spec view with the passed-in enums to keep the placement logic accurate.
|
|
27
|
+
const placementSpec = enums === ctx.spec.enums ? ctx.spec : { ...ctx.spec, enums };
|
|
28
|
+
const placement = computeSchemaPlacement(placementSpec, ctx);
|
|
29
|
+
const enumToService = placement.enumToService;
|
|
25
30
|
const mountDirMap = buildMountDirMap(ctx);
|
|
26
31
|
const resolveDir = (irService: string | undefined) =>
|
|
27
32
|
irService ? (mountDirMap.get(irService) ?? 'common') : 'common';
|
|
28
33
|
const files: GeneratedFile[] = [];
|
|
29
34
|
const compatAliases = collectCompatEnumAliases(enums, ctx);
|
|
30
35
|
|
|
31
|
-
const aliasOf =
|
|
36
|
+
const aliasOf = placement.enumAliases;
|
|
32
37
|
|
|
33
38
|
for (const enumDef of enums) {
|
|
34
39
|
const service = enumToService.get(enumDef.name);
|
|
@@ -260,31 +265,9 @@ export function collectCompatEnumAliases(enums: Enum[], ctx: EmitterContext): Ma
|
|
|
260
265
|
return aliases;
|
|
261
266
|
}
|
|
262
267
|
|
|
263
|
-
function collectEnumAliasOf(enums: Enum[]): Map<string, string> {
|
|
264
|
-
const hashGroups = new Map<string, string[]>();
|
|
265
|
-
for (const enumDef of enums) {
|
|
266
|
-
const hash = [...enumDef.values]
|
|
267
|
-
.map((v) => String(v.value))
|
|
268
|
-
.sort()
|
|
269
|
-
.join('|');
|
|
270
|
-
if (!hashGroups.has(hash)) hashGroups.set(hash, []);
|
|
271
|
-
hashGroups.get(hash)!.push(enumDef.name);
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
const aliasOf = new Map<string, string>();
|
|
275
|
-
for (const [, names] of hashGroups) {
|
|
276
|
-
if (names.length <= 1) continue;
|
|
277
|
-
const sorted = [...names].sort();
|
|
278
|
-
const canonical = sorted[0];
|
|
279
|
-
for (let i = 1; i < sorted.length; i++) {
|
|
280
|
-
aliasOf.set(sorted[i], canonical);
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
return aliasOf;
|
|
284
|
-
}
|
|
285
|
-
|
|
286
268
|
export function collectGeneratedEnumSymbolsByDir(enums: Enum[], ctx: EmitterContext): Map<string, string[]> {
|
|
287
|
-
const
|
|
269
|
+
const placementSpec = enums === ctx.spec.enums ? ctx.spec : { ...ctx.spec, enums };
|
|
270
|
+
const enumToService = computeSchemaPlacement(placementSpec, ctx).enumToService;
|
|
288
271
|
const mountDirMap = buildMountDirMap(ctx);
|
|
289
272
|
const resolveDir = (irService: string | undefined) =>
|
|
290
273
|
irService ? (mountDirMap.get(irService) ?? 'common') : 'common';
|
|
@@ -310,29 +293,3 @@ function enumValueHash(enumDef: Enum): string {
|
|
|
310
293
|
.sort()
|
|
311
294
|
.join('|');
|
|
312
295
|
}
|
|
313
|
-
|
|
314
|
-
export function assignEnumsToServices(enums: Enum[], services: Service[]): Map<string, string> {
|
|
315
|
-
const enumToService = new Map<string, string>();
|
|
316
|
-
const enumNames = new Set(enums.map((e) => e.name));
|
|
317
|
-
|
|
318
|
-
for (const service of services) {
|
|
319
|
-
for (const op of service.operations) {
|
|
320
|
-
const refs = new Set<string>();
|
|
321
|
-
const collect = (ref: any) => {
|
|
322
|
-
walkTypeRef(ref, { enum: (r: any) => refs.add(r.name) });
|
|
323
|
-
};
|
|
324
|
-
if (op.requestBody) collect(op.requestBody);
|
|
325
|
-
collect(op.response);
|
|
326
|
-
for (const p of [...op.pathParams, ...op.queryParams, ...op.headerParams, ...(op.cookieParams ?? [])]) {
|
|
327
|
-
collect(p.type);
|
|
328
|
-
}
|
|
329
|
-
for (const name of refs) {
|
|
330
|
-
if (enumNames.has(name) && !enumToService.has(name)) {
|
|
331
|
-
enumToService.set(name, service.name);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
return enumToService;
|
|
338
|
-
}
|