@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/dist/plugin.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { t as workosEmittersPlugin } from "./plugin-
|
|
1
|
+
import { t as workosEmittersPlugin } from "./plugin-DRGwxN88.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.14.1",
|
|
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.
|
|
57
|
+
"@workos/oagen": "^0.20.0"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/src/dotnet/models.ts
CHANGED
|
@@ -20,7 +20,11 @@ import {
|
|
|
20
20
|
} from './naming.js';
|
|
21
21
|
|
|
22
22
|
// Import and re-export shared model detection utilities
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
isListWrapperModel,
|
|
25
|
+
isListMetadataModel,
|
|
26
|
+
collectNonPaginatedResponseModelNames,
|
|
27
|
+
} from '../shared/model-utils.js';
|
|
24
28
|
export { isListWrapperModel, isListMetadataModel };
|
|
25
29
|
|
|
26
30
|
/**
|
|
@@ -74,6 +78,11 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
74
78
|
// (see workos-dotnet#248 with `CreateUserApiKey` /
|
|
75
79
|
// `UserManagementCreateApiKeyOptions`). Skip emission for those.
|
|
76
80
|
const requestBodyOnlyNames = collectRequestBodyOnlyModelNames(ctx.spec.services, models);
|
|
81
|
+
// Wrappers referenced as a non-paginated response (e.g. `VersionListResponse`
|
|
82
|
+
// for `GET /vault/v1/kv/{id}/versions`) must still be emitted — the resource
|
|
83
|
+
// code references them by name and the pagination iterator doesn't unwrap them.
|
|
84
|
+
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
|
|
85
|
+
const skipAsListWrapper = (m: Model): boolean => isListWrapperModel(m) && !nonPaginatedRefs.has(m.name);
|
|
77
86
|
|
|
78
87
|
// Build a lookup of base model field C# names → C# types for inheritance.
|
|
79
88
|
// Variant models skip inherited fields and use `new` for type-divergent ones.
|
|
@@ -81,9 +90,12 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
81
90
|
if (discCtx) {
|
|
82
91
|
for (const model of models) {
|
|
83
92
|
if (discCtx.discriminatorBases.has(model.name)) {
|
|
93
|
+
const baseClassName = modelClassName(model.name);
|
|
84
94
|
const fieldMap = new Map<string, string>();
|
|
85
95
|
for (const field of model.fields) {
|
|
86
|
-
|
|
96
|
+
let csName = fieldName(field.name);
|
|
97
|
+
if (csName === baseClassName) csName = `${csName}Value`;
|
|
98
|
+
fieldMap.set(csName, mapTypeRef(field.type));
|
|
87
99
|
}
|
|
88
100
|
baseFieldLookup.set(model.name, fieldMap);
|
|
89
101
|
}
|
|
@@ -91,7 +103,7 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
91
103
|
}
|
|
92
104
|
|
|
93
105
|
for (const model of models) {
|
|
94
|
-
if (
|
|
106
|
+
if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
|
|
95
107
|
if (requestBodyOnlyNames.has(model.name)) continue;
|
|
96
108
|
|
|
97
109
|
const csClassName = modelClassName(model.name);
|
|
@@ -104,7 +116,11 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
104
116
|
const fieldTypes = model.fields.map((f) => mapTypeRef(f.type));
|
|
105
117
|
const needsCollections = fieldTypes.some((t) => t.startsWith('List<') || t.startsWith('Dictionary<'));
|
|
106
118
|
const needsSystem = fieldTypes.some((t) => t.includes('DateTimeOffset'));
|
|
107
|
-
|
|
119
|
+
// Required enums need JsonProperty / STJS; a field whose PascalCase name
|
|
120
|
+
// collides with the enclosing class needs the same imports for the wire-
|
|
121
|
+
// name override emitted below.
|
|
122
|
+
const hasClassNameCollision = model.fields.some((f) => fieldName(f.name) === csClassName);
|
|
123
|
+
const needsJsonAttrs = hasClassNameCollision || model.fields.some((f) => f.required && isEnumRef(f.type));
|
|
108
124
|
|
|
109
125
|
lines.push(`namespace ${ctx.namespacePascal}`);
|
|
110
126
|
lines.push('{');
|
|
@@ -154,7 +170,14 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
154
170
|
// Deduplicate fields by C# property name
|
|
155
171
|
const seenFieldNames = new Set<string>();
|
|
156
172
|
for (const field of model.fields) {
|
|
157
|
-
|
|
173
|
+
// CS0542: a property can't share its enclosing class's name. Spec schemas
|
|
174
|
+
// like `Error.error` PascalCase into `Error.Error`, so suffix with `Value`
|
|
175
|
+
// when that happens. Track the rename so we emit an explicit
|
|
176
|
+
// `[JsonProperty]` attribute below — the SnakeCaseLower naming policy
|
|
177
|
+
// would otherwise serialize `ErrorValue` as `error_value`, not `error`.
|
|
178
|
+
let csFieldName = fieldName(field.name);
|
|
179
|
+
const collidesWithClassName = csFieldName === csClassName;
|
|
180
|
+
if (collidesWithClassName) csFieldName = `${csFieldName}Value`;
|
|
158
181
|
if (seenFieldNames.has(csFieldName)) continue;
|
|
159
182
|
seenFieldNames.add(csFieldName);
|
|
160
183
|
|
|
@@ -234,7 +257,9 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
234
257
|
}
|
|
235
258
|
|
|
236
259
|
const isRequiredEnum = field.required && isEnumRef(field.type) && constInit === null;
|
|
237
|
-
lines.push(
|
|
260
|
+
lines.push(
|
|
261
|
+
...emitJsonPropertyAttributes(field.name, { isRequiredEnum, explicitWireName: collidesWithClassName }),
|
|
262
|
+
);
|
|
238
263
|
// Discriminated-union-typed field: attach the variant-dispatching converter
|
|
239
264
|
// so Newtonsoft picks the right subtype on deserialization. The converter
|
|
240
265
|
// name is keyed off the first IR variant model name (matches how
|
package/src/dotnet/type-map.ts
CHANGED
|
@@ -156,7 +156,24 @@ export function isEnumRef(ref: TypeRef): boolean {
|
|
|
156
156
|
* omission so the API returns a clear `missing required field` error instead
|
|
157
157
|
* of a confusing 422.
|
|
158
158
|
*/
|
|
159
|
-
export function emitJsonPropertyAttributes(
|
|
159
|
+
export function emitJsonPropertyAttributes(
|
|
160
|
+
wireName: string,
|
|
161
|
+
options: { isRequiredEnum?: boolean; explicitWireName?: boolean } = {},
|
|
162
|
+
): string[] {
|
|
163
|
+
// When the C# property name doesn't naturally map back to the wire name via
|
|
164
|
+
// the SnakeCaseLower naming policy (e.g. we suffixed the property to avoid a
|
|
165
|
+
// CS0542 class/member collision like `Error.Error` → `Error.ErrorValue`), the
|
|
166
|
+
// caller pins the wire name explicitly so JSON round-trip still works.
|
|
167
|
+
if (options.explicitWireName) {
|
|
168
|
+
if (options.isRequiredEnum) {
|
|
169
|
+
return [
|
|
170
|
+
` [JsonProperty("${wireName}", DefaultValueHandling = DefaultValueHandling.Ignore)]`,
|
|
171
|
+
` [STJS.JsonIgnore(Condition = STJS.JsonIgnoreCondition.WhenWritingDefault)]`,
|
|
172
|
+
` [STJS.JsonPropertyName("${wireName}")]`,
|
|
173
|
+
];
|
|
174
|
+
}
|
|
175
|
+
return [` [JsonProperty("${wireName}")]`, ` [STJS.JsonPropertyName("${wireName}")]`];
|
|
176
|
+
}
|
|
160
177
|
if (options.isRequiredEnum) {
|
|
161
178
|
return [
|
|
162
179
|
` [JsonProperty(DefaultValueHandling = DefaultValueHandling.Ignore)]`,
|
package/src/go/models.ts
CHANGED
|
@@ -5,7 +5,11 @@ import { className, fieldName } from './naming.js';
|
|
|
5
5
|
import { lowerFirstForDoc, fieldDocComment, articleFor } from '../shared/naming-utils.js';
|
|
6
6
|
|
|
7
7
|
// Import and re-export shared model detection utilities
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
isListWrapperModel,
|
|
10
|
+
isListMetadataModel,
|
|
11
|
+
collectNonPaginatedResponseModelNames,
|
|
12
|
+
} from '../shared/model-utils.js';
|
|
9
13
|
export { isListWrapperModel, isListMetadataModel };
|
|
10
14
|
|
|
11
15
|
/**
|
|
@@ -83,12 +87,17 @@ export function generateModels(models: Model[], ctx: EmitterContext): GeneratedF
|
|
|
83
87
|
lines.push('');
|
|
84
88
|
|
|
85
89
|
const requestBodyOnly = collectRequestBodyOnlyModelNames(ctx.spec.services, models);
|
|
90
|
+
// Wrappers referenced as a non-paginated response (e.g. `VersionListResponse`
|
|
91
|
+
// for `GET /vault/v1/kv/{id}/versions`) must still be emitted — the resource
|
|
92
|
+
// code references them by name and the pagination iterator doesn't unwrap them.
|
|
93
|
+
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
|
|
94
|
+
const skipAsListWrapper = (m: Model): boolean => isListWrapperModel(m) && !nonPaginatedRefs.has(m.name);
|
|
86
95
|
|
|
87
96
|
// Build structural hash for deduplication
|
|
88
97
|
const modelHashMap = new Map<string, string>();
|
|
89
98
|
const hashGroups = new Map<string, string[]>();
|
|
90
99
|
for (const model of models) {
|
|
91
|
-
if (
|
|
100
|
+
if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
|
|
92
101
|
if (requestBodyOnly.has(model.name)) continue;
|
|
93
102
|
const hash = structuralHash(model);
|
|
94
103
|
modelHashMap.set(model.name, hash);
|
|
@@ -112,7 +121,7 @@ export function generateModels(models: Model[], ctx: EmitterContext): GeneratedF
|
|
|
112
121
|
|
|
113
122
|
const batchedAliases = new Set<string>();
|
|
114
123
|
for (const model of models) {
|
|
115
|
-
if (
|
|
124
|
+
if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
|
|
116
125
|
if (requestBodyOnly.has(model.name)) continue;
|
|
117
126
|
|
|
118
127
|
const structName = className(model.name);
|
package/src/kotlin/models.ts
CHANGED
|
@@ -2,7 +2,11 @@ import type { Model, EmitterContext, GeneratedFile, TypeRef, Field } from '@work
|
|
|
2
2
|
import { mapTypeRef, discriminatedUnions } from './type-map.js';
|
|
3
3
|
import { className, propertyName, ktStringLiteral, humanize } from './naming.js';
|
|
4
4
|
import { enumCanonicalMap } from './enums.js';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
isListWrapperModel,
|
|
7
|
+
isListMetadataModel,
|
|
8
|
+
collectNonPaginatedResponseModelNames,
|
|
9
|
+
} from '../shared/model-utils.js';
|
|
6
10
|
|
|
7
11
|
const KOTLIN_SRC_PREFIX = 'src/main/kotlin/';
|
|
8
12
|
const MODELS_PACKAGE = 'com.workos.models';
|
|
@@ -47,7 +51,7 @@ function promoteFieldType(f: Field): Field {
|
|
|
47
51
|
* List wrappers (`{ data, list_metadata }`) and the shared `ListMetadata`
|
|
48
52
|
* model are skipped — the hand-maintained runtime provides [Page]/[ListMetadata].
|
|
49
53
|
*/
|
|
50
|
-
export function generateModels(models: Model[],
|
|
54
|
+
export function generateModels(models: Model[], ctx: EmitterContext): GeneratedFile[] {
|
|
51
55
|
if (models.length === 0) return [];
|
|
52
56
|
|
|
53
57
|
// First pass: call mapTypeRef on every model field so discriminator info is
|
|
@@ -58,12 +62,18 @@ export function generateModels(models: Model[], _ctx: EmitterContext): Generated
|
|
|
58
62
|
|
|
59
63
|
const files: GeneratedFile[] = [];
|
|
60
64
|
|
|
65
|
+
// Wrappers referenced as a non-paginated response (e.g. `VersionListResponse`
|
|
66
|
+
// for `GET /vault/v1/kv/{id}/versions`) must still be emitted — the resource
|
|
67
|
+
// code references them by name and pagination iterators don't unwrap them.
|
|
68
|
+
const nonPaginatedRefs = collectNonPaginatedResponseModelNames(ctx.spec.services);
|
|
69
|
+
const skipAsListWrapper = (m: Model): boolean => isListWrapperModel(m) && !nonPaginatedRefs.has(m.name);
|
|
70
|
+
|
|
61
71
|
// Deduplication: identical structures become typealiases.
|
|
62
72
|
// Pass 1: hash without nested-alias resolution.
|
|
63
73
|
modelAliasMap = null;
|
|
64
74
|
const hashGroupsPass1 = new Map<string, string[]>();
|
|
65
75
|
for (const model of models) {
|
|
66
|
-
if (
|
|
76
|
+
if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
|
|
67
77
|
if (model.fields.length === 0 && discriminatedUnions.has(className(model.name))) continue;
|
|
68
78
|
const hash = structuralHash(model);
|
|
69
79
|
if (!hashGroupsPass1.has(hash)) hashGroupsPass1.set(hash, []);
|
|
@@ -86,7 +96,7 @@ export function generateModels(models: Model[], _ctx: EmitterContext): Generated
|
|
|
86
96
|
modelAliasMap = aliasOf;
|
|
87
97
|
const hashGroupsPass2 = new Map<string, string[]>();
|
|
88
98
|
for (const model of models) {
|
|
89
|
-
if (
|
|
99
|
+
if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
|
|
90
100
|
if (model.fields.length === 0 && discriminatedUnions.has(className(model.name))) continue;
|
|
91
101
|
if (aliasOf.has(model.name)) continue; // already aliased in pass 1
|
|
92
102
|
const hash = structuralHash(model);
|
|
@@ -105,7 +115,7 @@ export function generateModels(models: Model[], _ctx: EmitterContext): Generated
|
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
for (const model of models) {
|
|
108
|
-
if (
|
|
118
|
+
if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
|
|
109
119
|
const typeName = className(model.name);
|
|
110
120
|
|
|
111
121
|
// Parent of a discriminated union: emit a sealed class.
|
|
@@ -142,7 +152,7 @@ export function generateModels(models: Model[], _ctx: EmitterContext): Generated
|
|
|
142
152
|
// mapping so Jackson can deserialize directly to the correct concrete type.
|
|
143
153
|
const eventMapping: Array<{ wireValue: string; modelName: string }> = [];
|
|
144
154
|
for (const model of models) {
|
|
145
|
-
if (
|
|
155
|
+
if (skipAsListWrapper(model) || isListMetadataModel(model)) continue;
|
|
146
156
|
if (aliasOf.has(model.name)) continue;
|
|
147
157
|
if (!isEventEnvelopeModel(model)) continue;
|
|
148
158
|
const eventField = model.fields.find((f) => f.name === 'event');
|