@workos/oagen-emitters 0.13.0 → 0.14.1
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.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/{plugin-B9F2jmwy.mjs → plugin-DRGwxN88.mjs} +754 -49
- package/dist/plugin-DRGwxN88.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +2 -2
- package/src/dotnet/models.ts +31 -6
- package/src/dotnet/type-map.ts +18 -1
- package/src/go/models.ts +12 -3
- package/src/kotlin/models.ts +16 -6
- package/src/node/discriminated-models.ts +735 -0
- package/src/node/index.ts +134 -9
- package/src/node/models.ts +23 -3
- package/src/node/node-overrides.ts +49 -6
- package/src/node/utils.ts +5 -1
- package/src/php/index.ts +25 -2
- package/src/php/models.ts +11 -2
- package/src/python/models.ts +12 -2
- package/src/python/resources.ts +8 -2
- package/src/ruby/index.ts +27 -2
- package/src/ruby/models.ts +13 -3
- package/src/rust/index.ts +26 -2
- package/src/rust/models.ts +5 -1
- package/src/rust/resources.ts +4 -1
- package/src/shared/model-utils.ts +49 -7
- package/test/rust/models.test.ts +3 -3
- package/dist/plugin-B9F2jmwy.mjs.map +0 -1
package/src/rust/models.ts
CHANGED
|
@@ -140,7 +140,11 @@ function renderField(field: Field, rustField: string, modelName: string, registr
|
|
|
140
140
|
function renderModelsBarrel(modules: string[]): string {
|
|
141
141
|
const sorted = [...new Set(modules)].sort();
|
|
142
142
|
const lines: string[] = [];
|
|
143
|
-
|
|
143
|
+
// Declare the modules privately so `pub use crate::models::*` in lib.rs only
|
|
144
|
+
// re-exports the struct names, not the module names themselves. Otherwise a
|
|
145
|
+
// module like `models::organization_membership` collides with the same-named
|
|
146
|
+
// `resources::organization_membership` when both barrels are glob-re-exported.
|
|
147
|
+
for (const m of sorted) lines.push(`mod ${m};`);
|
|
144
148
|
lines.push('');
|
|
145
149
|
for (const m of sorted) lines.push(`pub use ${m}::*;`);
|
|
146
150
|
return lines.join('\n') + '\n';
|
package/src/rust/resources.ts
CHANGED
|
@@ -1317,7 +1317,10 @@ function renderResourcesBarrel(exports: { module: string; struct: string }[]): s
|
|
|
1317
1317
|
unique.sort((a, b) => a.module.localeCompare(b.module));
|
|
1318
1318
|
|
|
1319
1319
|
const lines: string[] = [];
|
|
1320
|
-
|
|
1320
|
+
// Declare modules privately — see the matching comment in `models.ts`.
|
|
1321
|
+
// `pub mod resources::organization_membership` would collide with the
|
|
1322
|
+
// same-named module re-exported via `pub use models::*` in lib.rs.
|
|
1323
|
+
for (const { module } of unique) lines.push(`mod ${module};`);
|
|
1321
1324
|
lines.push('');
|
|
1322
1325
|
for (const { module, struct } of unique) lines.push(`pub use ${module}::${struct};`);
|
|
1323
1326
|
return lines.join('\n') + '\n';
|
|
@@ -1,10 +1,31 @@
|
|
|
1
|
-
import type { Model, Field, TypeRef, Enum } from '@workos/oagen';
|
|
2
|
-
import { toSnakeCase } from '@workos/oagen';
|
|
1
|
+
import type { Model, Field, TypeRef, Enum, Service } from '@workos/oagen';
|
|
2
|
+
import { toSnakeCase, toUpperSnakeCase, walkTypeRef } from '@workos/oagen';
|
|
3
3
|
import { readFileSync, existsSync } from 'node:fs';
|
|
4
4
|
import { resolve } from 'node:path';
|
|
5
5
|
// @ts-ignore -- js-yaml has no type declarations in this project
|
|
6
6
|
import { load as yamlLoad } from 'js-yaml';
|
|
7
7
|
|
|
8
|
+
/**
|
|
9
|
+
* Collect model names referenced as the return type of any non-paginated
|
|
10
|
+
* operation. The list-wrapper skip rule below assumes a wrapper is always
|
|
11
|
+
* replaced by the SDK's pagination machinery — but a few endpoints
|
|
12
|
+
* (e.g. `GET /vault/v1/kv/{id}/versions`) have a list-envelope response
|
|
13
|
+
* shape with no pagination params, so the parser leaves them as a plain
|
|
14
|
+
* model reference. We must still emit those wrappers as regular models;
|
|
15
|
+
* otherwise the generated resource code references an undefined name.
|
|
16
|
+
*/
|
|
17
|
+
export function collectNonPaginatedResponseModelNames(services: Service[]): Set<string> {
|
|
18
|
+
const names = new Set<string>();
|
|
19
|
+
for (const service of services) {
|
|
20
|
+
for (const op of service.operations) {
|
|
21
|
+
if (op.pagination) continue;
|
|
22
|
+
walkTypeRef(op.response, { model: (r) => names.add(r.name) });
|
|
23
|
+
for (const sr of op.successResponses ?? []) walkTypeRef(sr.type, { model: (r) => names.add(r.name) });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return names;
|
|
27
|
+
}
|
|
28
|
+
|
|
8
29
|
/**
|
|
9
30
|
* Detect whether a model is a list wrapper -- the standard paginated
|
|
10
31
|
* list envelope with `data` (array), `list_metadata`, and optionally `object: 'list'`.
|
|
@@ -83,7 +104,7 @@ function discoverSpecPath(): string | null {
|
|
|
83
104
|
let _rawSpecCache: Record<string, any> | null = null;
|
|
84
105
|
let _rawSpecLoaded = false;
|
|
85
106
|
|
|
86
|
-
function loadRawSpec(): Record<string, any> | null {
|
|
107
|
+
export function loadRawSpec(): Record<string, any> | null {
|
|
87
108
|
if (_rawSpecLoaded) return _rawSpecCache;
|
|
88
109
|
_rawSpecLoaded = true;
|
|
89
110
|
const specPath = discoverSpecPath();
|
|
@@ -114,7 +135,10 @@ function lookupRawSchema(name: string): Record<string, any> | null {
|
|
|
114
135
|
*/
|
|
115
136
|
interface SyntheticCollector {
|
|
116
137
|
models: Model[];
|
|
117
|
-
enums: Array<{
|
|
138
|
+
enums: Array<{
|
|
139
|
+
name: string;
|
|
140
|
+
values: Array<{ value: string; description?: string }>;
|
|
141
|
+
}>;
|
|
118
142
|
/** Track names already used to avoid duplicates. */
|
|
119
143
|
usedNames: Set<string>;
|
|
120
144
|
}
|
|
@@ -500,7 +524,7 @@ export function detectDiscriminators(models: Model[]): Model[] {
|
|
|
500
524
|
* Returns a new array of enriched models (original models are not mutated).
|
|
501
525
|
* Synthetic enums are stored internally; retrieve them via `getSyntheticEnums()`.
|
|
502
526
|
*/
|
|
503
|
-
export function enrichModelsFromSpec(models: Model[]): Model[] {
|
|
527
|
+
export function enrichModelsFromSpec(models: Model[], enums: Enum[] = []): Model[] {
|
|
504
528
|
const spec = loadRawSpec();
|
|
505
529
|
if (!spec) {
|
|
506
530
|
_lastSyntheticEnums = [];
|
|
@@ -515,6 +539,17 @@ export function enrichModelsFromSpec(models: Model[]): Model[] {
|
|
|
515
539
|
collector.usedNames.add(m.name);
|
|
516
540
|
collector.usedNames.add(toSnakeCase(m.name));
|
|
517
541
|
}
|
|
542
|
+
// Seed existing IR enum names too. The parser already emits inline enums
|
|
543
|
+
// like `DataIntegrationAccessTokenResponseError` (PascalCase); without this
|
|
544
|
+
// seed, the synthetic path would emit a sibling enum named
|
|
545
|
+
// `DataIntegrationAccessTokenResponse_error`, and language emitters that
|
|
546
|
+
// PascalCase-normalize identifiers (Ruby, Go, PHP, Python) would collapse
|
|
547
|
+
// both onto the same class/file path — the second overwrites the first
|
|
548
|
+
// with a `X = X` self-alias.
|
|
549
|
+
for (const e of enums) {
|
|
550
|
+
collector.usedNames.add(e.name);
|
|
551
|
+
collector.usedNames.add(toSnakeCase(e.name));
|
|
552
|
+
}
|
|
518
553
|
|
|
519
554
|
const enriched = models.map((model) => {
|
|
520
555
|
const rawSchema = lookupRawSchema(model.name);
|
|
@@ -582,10 +617,17 @@ export function enrichModelsFromSpec(models: Model[]): Model[] {
|
|
|
582
617
|
return modified ? { ...model, fields: newFields } : model;
|
|
583
618
|
});
|
|
584
619
|
|
|
585
|
-
// Convert synthetic enum collector entries to proper Enum objects
|
|
620
|
+
// Convert synthetic enum collector entries to proper Enum objects. PHP's
|
|
621
|
+
// emitter (and others built on top of `EnumValue.name`) crash when this
|
|
622
|
+
// field is missing, so derive it from the value via the same upper-snake
|
|
623
|
+
// transform the parser uses for declared enums.
|
|
586
624
|
_lastSyntheticEnums = collector.enums.map((e) => ({
|
|
587
625
|
name: e.name,
|
|
588
|
-
values: e.values.map((v) => ({
|
|
626
|
+
values: e.values.map((v) => ({
|
|
627
|
+
name: toUpperSnakeCase(String(v.value)),
|
|
628
|
+
value: v.value,
|
|
629
|
+
description: v.description,
|
|
630
|
+
})),
|
|
589
631
|
})) as Enum[];
|
|
590
632
|
|
|
591
633
|
// Append synthetic models, skipping those whose snake_case name collides
|
package/test/rust/models.test.ts
CHANGED
|
@@ -110,7 +110,7 @@ describe('rust/models', () => {
|
|
|
110
110
|
const event = files.find((f) => f.path === 'src/models/event.rs')!;
|
|
111
111
|
expect(event.content).toContain('pub payload: EventPayloadOneOf,');
|
|
112
112
|
const barrel = files.find((f) => f.path === 'src/models/mod.rs')!;
|
|
113
|
-
expect(barrel.content).toContain('
|
|
113
|
+
expect(barrel.content).toContain('mod _unions;');
|
|
114
114
|
// The _unions.rs file is rendered by generateClient (the final structural
|
|
115
115
|
// pass) so resource-side body unions can join the same registry.
|
|
116
116
|
const clientFiles = generateClient(emptySpec, ctx, registry);
|
|
@@ -170,8 +170,8 @@ describe('rust/models', () => {
|
|
|
170
170
|
];
|
|
171
171
|
const files = generateModels(models, ctx, new UnionRegistry());
|
|
172
172
|
const barrel = files.find((f) => f.path === 'src/models/mod.rs')!;
|
|
173
|
-
expect(barrel.content).toContain('
|
|
174
|
-
expect(barrel.content).toContain('
|
|
173
|
+
expect(barrel.content).toContain('mod alpha;');
|
|
174
|
+
expect(barrel.content).toContain('mod beta;');
|
|
175
175
|
expect(barrel.content).toContain('pub use alpha::*;');
|
|
176
176
|
});
|
|
177
177
|
});
|