@workos/oagen-emitters 0.8.0 → 0.8.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 +7 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-bCMdV7KX.mjs → plugin-DOE0FqrZ.mjs} +19 -13
- package/dist/plugin-DOE0FqrZ.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +1 -1
- package/src/dotnet/index.ts +6 -2
- package/src/dotnet/models.ts +19 -7
- package/src/go/fixtures.ts +10 -2
- package/src/python/client.ts +4 -6
- package/src/python/models.ts +30 -1
- package/dist/plugin-bCMdV7KX.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-DOE0FqrZ.mjs";
|
|
2
2
|
export { workosEmittersPlugin };
|
package/package.json
CHANGED
package/src/dotnet/index.ts
CHANGED
|
@@ -133,8 +133,11 @@ export const dotnetEmitter: Emitter = {
|
|
|
133
133
|
lines.push(' {');
|
|
134
134
|
lines.push(' public override bool CanConvert(Type objectType) => objectType == typeof(object);');
|
|
135
135
|
lines.push('');
|
|
136
|
+
// Override returns `object?` to match Newtonsoft.Json 13+'s nullable
|
|
137
|
+
// signature; `JToken.ToObject<T>` is itself `T?`, so a non-nullable
|
|
138
|
+
// override would trigger CS8603 under <Nullable>enable</Nullable>.
|
|
136
139
|
lines.push(
|
|
137
|
-
' public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)',
|
|
140
|
+
' public override object? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)',
|
|
138
141
|
);
|
|
139
142
|
lines.push(' {');
|
|
140
143
|
lines.push(' var jObject = JObject.Load(reader);');
|
|
@@ -194,8 +197,9 @@ export const dotnetEmitter: Emitter = {
|
|
|
194
197
|
` public override bool CanConvert(Type objectType) => typeof(${baseClass}).IsAssignableFrom(objectType);`,
|
|
195
198
|
);
|
|
196
199
|
lines.push('');
|
|
200
|
+
// See first converter — `object?` matches Newtonsoft 13+ to avoid CS8603.
|
|
197
201
|
lines.push(
|
|
198
|
-
' public override object ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)',
|
|
202
|
+
' public override object? ReadJson(Newtonsoft.Json.JsonReader reader, Type objectType, object? existingValue, Newtonsoft.Json.JsonSerializer serializer)',
|
|
199
203
|
);
|
|
200
204
|
lines.push(' {');
|
|
201
205
|
lines.push(' var jObject = JObject.Load(reader);');
|
package/src/dotnet/models.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
emitJsonPropertyAttributes,
|
|
8
8
|
setModelAliases,
|
|
9
9
|
isModelAlias,
|
|
10
|
+
resolveModelName,
|
|
10
11
|
} from './type-map.js';
|
|
11
12
|
import {
|
|
12
13
|
articleFor,
|
|
@@ -54,6 +55,17 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
const files: GeneratedFile[] = [];
|
|
59
|
+
|
|
60
|
+
// Compute and publish model aliases so mapTypeRef rewrites references.
|
|
61
|
+
// Must run BEFORE collectRequestBodyOnlyModelNames so the body/non-body
|
|
62
|
+
// tally collapses aliased pairs onto their canonical name — otherwise a
|
|
63
|
+
// model that's only a request body in name (e.g. `AddRolePermissionDto`)
|
|
64
|
+
// but is the canonical for a field-referenced alias (e.g. `SlimRole`)
|
|
65
|
+
// would be wrongly classified as body-only and skipped from emission,
|
|
66
|
+
// leaving every alias-rewritten field reference dangling.
|
|
67
|
+
primeModelAliases(models);
|
|
68
|
+
|
|
57
69
|
// Models that are referenced ONLY as an operation request body (not by any
|
|
58
70
|
// response, field, or other operation type) are dead surface in .NET because
|
|
59
71
|
// the wrapper generator emits a per-operation `*Options` class containing
|
|
@@ -63,11 +75,6 @@ export function generateModels(models: Model[], ctx: EmitterContext, discCtx?: D
|
|
|
63
75
|
// `UserManagementCreateApiKeyOptions`). Skip emission for those.
|
|
64
76
|
const requestBodyOnlyNames = collectRequestBodyOnlyModelNames(ctx.spec.services, models);
|
|
65
77
|
|
|
66
|
-
const files: GeneratedFile[] = [];
|
|
67
|
-
|
|
68
|
-
// Compute and publish model aliases so mapTypeRef rewrites references.
|
|
69
|
-
primeModelAliases(models);
|
|
70
|
-
|
|
71
78
|
// Build a lookup of base model field C# names → C# types for inheritance.
|
|
72
79
|
// Variant models skip inherited fields and use `new` for type-divergent ones.
|
|
73
80
|
const baseFieldLookup = new Map<string, Map<string, string>>();
|
|
@@ -453,17 +460,22 @@ function collectRequestBodyOnlyModelNames(services: Service[], models: Model[]):
|
|
|
453
460
|
const requestBodyNames = new Set<string>();
|
|
454
461
|
const otherReferences = new Set<string>();
|
|
455
462
|
|
|
463
|
+
// Resolve every reference through the alias map so structurally-identical
|
|
464
|
+
// models share a body/non-body classification. Without this, an alias being
|
|
465
|
+
// used as a field would only mark the alias name as non-body — leaving its
|
|
466
|
+
// canonical (which carries the same shape and gets emitted) wrongly tagged
|
|
467
|
+
// as body-only and skipped.
|
|
456
468
|
const collect = (ref: TypeRef | undefined, into: Set<string>): void => {
|
|
457
469
|
if (!ref) return;
|
|
458
470
|
walkTypeRef(ref, {
|
|
459
|
-
model: (r) => into.add(r.name),
|
|
471
|
+
model: (r) => into.add(resolveModelName(r.name)),
|
|
460
472
|
});
|
|
461
473
|
};
|
|
462
474
|
|
|
463
475
|
for (const service of services) {
|
|
464
476
|
for (const op of service.operations) {
|
|
465
477
|
if (op.requestBody?.kind === 'model') {
|
|
466
|
-
requestBodyNames.add(op.requestBody.name);
|
|
478
|
+
requestBodyNames.add(resolveModelName(op.requestBody.name));
|
|
467
479
|
}
|
|
468
480
|
collect(op.response, otherReferences);
|
|
469
481
|
if (op.pagination) collect(op.pagination.itemType, otherReferences);
|
package/src/go/fixtures.ts
CHANGED
|
@@ -46,7 +46,12 @@ export function generateFixtures(spec: { models: Model[]; enums: Enum[]; service
|
|
|
46
46
|
});
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
// Generate list fixtures for paginated responses
|
|
49
|
+
// Generate list fixtures for paginated responses. Multiple operations may
|
|
50
|
+
// share the same item model (e.g. several role-assignment list endpoints all
|
|
51
|
+
// returning UserRoleAssignmentList) — emit each fixture path once so the
|
|
52
|
+
// content-dedup pass below doesn't see N copies of the same path and drop
|
|
53
|
+
// the file entirely.
|
|
54
|
+
const seenListPaths = new Set<string>();
|
|
50
55
|
for (const service of spec.services) {
|
|
51
56
|
for (const op of service.operations) {
|
|
52
57
|
if (op.pagination) {
|
|
@@ -55,6 +60,9 @@ export function generateFixtures(spec: { models: Model[]; enums: Enum[]; service
|
|
|
55
60
|
const unwrapped = unwrapListModel(itemModel, modelMap);
|
|
56
61
|
if (unwrapped) itemModel = unwrapped;
|
|
57
62
|
if (itemModel.fields.length === 0) continue;
|
|
63
|
+
const path = `testdata/list_${fileName(itemModel.name)}.json`;
|
|
64
|
+
if (seenListPaths.has(path)) continue;
|
|
65
|
+
seenListPaths.add(path);
|
|
58
66
|
const fixture = generateModelFixture(itemModel, modelMap, enumMap);
|
|
59
67
|
const listFixture = {
|
|
60
68
|
data: [fixture],
|
|
@@ -64,7 +72,7 @@ export function generateFixtures(spec: { models: Model[]; enums: Enum[]; service
|
|
|
64
72
|
},
|
|
65
73
|
};
|
|
66
74
|
files.push({
|
|
67
|
-
path
|
|
75
|
+
path,
|
|
68
76
|
content: JSON.stringify(listFixture, null, 2),
|
|
69
77
|
});
|
|
70
78
|
}
|
package/src/python/client.ts
CHANGED
|
@@ -286,12 +286,10 @@ function generateServiceInits(spec: ApiSpec, ctx: EmitterContext): GeneratedFile
|
|
|
286
286
|
overwriteExisting: true,
|
|
287
287
|
});
|
|
288
288
|
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
skipIfExists: true,
|
|
294
|
-
});
|
|
289
|
+
// models/__init__.py is emitted unconditionally by `models.ts` — including
|
|
290
|
+
// an empty barrel for services with no models — so we don't need a safety
|
|
291
|
+
// net here. (A `skipIfExists` safety net previously caused stale imports
|
|
292
|
+
// to survive when the underlying module was pruned.)
|
|
295
293
|
}
|
|
296
294
|
|
|
297
295
|
return files;
|
package/src/python/models.ts
CHANGED
|
@@ -447,6 +447,24 @@ export function generateModels(models: Model[], ctx: EmitterContext): GeneratedF
|
|
|
447
447
|
serviceDirModelPaths.add(`src/${ctx.namespace}/${dirName}/models`);
|
|
448
448
|
}
|
|
449
449
|
|
|
450
|
+
// Emit an empty barrel for every service-models dir that has no symbols of
|
|
451
|
+
// its own (e.g. a service whose models live in another package via
|
|
452
|
+
// cross-domain aliases). Otherwise the live SDK can keep a stale
|
|
453
|
+
// `__init__.py` from a previous spec revision — when the underlying module
|
|
454
|
+
// gets pruned the dangling re-export survives and breaks pyright. Done here
|
|
455
|
+
// (not in client.ts) so a subsequent emission for the same path with real
|
|
456
|
+
// content always wins last-write-wins.
|
|
457
|
+
for (const dirPath of serviceDirModelPaths) {
|
|
458
|
+
if (!symbolsByDir.has(dirPath)) {
|
|
459
|
+
files.push({
|
|
460
|
+
path: `${dirPath}/__init__.py`,
|
|
461
|
+
content: '',
|
|
462
|
+
integrateTarget: true,
|
|
463
|
+
overwriteExisting: true,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
450
468
|
for (const [dirPath, names] of symbolsByDir) {
|
|
451
469
|
// Use `import X as X` syntax for explicit re-exports (required by pyright strict)
|
|
452
470
|
const uniqueNames = [...new Set(names)].sort();
|
|
@@ -719,7 +737,12 @@ function deserializeField(ref: any, accessor: string, isRequired: boolean, walru
|
|
|
719
737
|
const dispatchMap = entries.map(([value, modelName]) => `"${value}": ${className(modelName)}`).join(', ');
|
|
720
738
|
const dataExpr = isRequired ? accessor : walrusVar;
|
|
721
739
|
const dataCast = `cast(Dict[str, Any], ${dataExpr})`;
|
|
722
|
-
|
|
740
|
+
// The dispatch dict has `str` keys, so pyright (strict) rejects the
|
|
741
|
+
// raw `Any | None` returned by `.get(prop)` even though `dict.get`
|
|
742
|
+
// accepts any hashable. Cast through `str` to satisfy the parameter
|
|
743
|
+
// type — runtime semantics are unchanged because a missing/`None`
|
|
744
|
+
// discriminator simply misses the dispatch and falls through.
|
|
745
|
+
const lookupExpr = `{${dispatchMap}}.get(cast(str, ${dataCast}.get("${ref.discriminator.property}")))`;
|
|
723
746
|
const branch = `(_disc.from_dict(${dataCast}) if (_disc := ${lookupExpr}) is not None else ${dataExpr})`;
|
|
724
747
|
if (isRequired) return branch;
|
|
725
748
|
return `(${branch}) if (${walrusVar} := ${accessor}) is not None else None`;
|
|
@@ -760,6 +783,12 @@ function serializeField(ref: any, accessor: string): string {
|
|
|
760
783
|
if (uniqueModels.length === 1) {
|
|
761
784
|
return `${accessor}.to_dict()`;
|
|
762
785
|
}
|
|
786
|
+
// Discriminated union: deserialize produced a dataclass instance for
|
|
787
|
+
// known discriminator values and the raw dict for unknowns. Round-trip
|
|
788
|
+
// both — call `.to_dict()` if it exists, otherwise pass through.
|
|
789
|
+
if (ref.discriminator && ref.discriminator.mapping && modelVariants.length > 0) {
|
|
790
|
+
return `${accessor}.to_dict() if hasattr(${accessor}, "to_dict") else ${accessor}`;
|
|
791
|
+
}
|
|
763
792
|
return accessor;
|
|
764
793
|
}
|
|
765
794
|
default:
|