@workos/oagen-emitters 0.16.0 → 0.17.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/.github/workflows/ci.yml +1 -1
- package/.github/workflows/lint.yml +1 -1
- package/.github/workflows/release.yml +1 -1
- package/.node-version +1 -1
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +20 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/{plugin-DuB1UozS.mjs → plugin-BLnR-FMi.mjs} +3687 -2393
- package/dist/plugin-BLnR-FMi.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +7 -7
- package/src/go/index.ts +6 -1
- package/src/kotlin/index.ts +9 -3
- package/src/node/enums.ts +17 -4
- package/src/node/index.ts +271 -5
- package/src/node/live-surface.ts +309 -0
- package/src/node/models.ts +69 -3
- package/src/node/naming.ts +204 -23
- package/src/node/resources.ts +166 -3
- package/src/node/utils.ts +140 -22
- package/src/rust/resources.ts +78 -29
- package/src/rust/tests.ts +15 -4
- package/src/shared/union-flatten.ts +201 -0
- package/test/node/enums.test.ts +239 -2
- package/test/node/live-surface.test.ts +771 -1
- package/test/node/models.test.ts +738 -3
- package/test/node/naming.test.ts +159 -0
- package/test/node/resources.test.ts +611 -0
- package/test/node/utils.test.ts +157 -2
- package/test/rust/resources.test.ts +143 -3
- package/test/shared/union-flatten.test.ts +174 -0
- package/dist/plugin-DuB1UozS.mjs.map +0 -1
package/test/node/naming.test.ts
CHANGED
|
@@ -84,6 +84,165 @@ describe('resolveInterfaceName', () => {
|
|
|
84
84
|
});
|
|
85
85
|
});
|
|
86
86
|
|
|
87
|
+
// ---------------------------------------------------------------------------
|
|
88
|
+
// Structural name resolution must be INJECTIVE: a live-surface name may be
|
|
89
|
+
// claimed by at most one IR model per run. Reconstructs the workos-node
|
|
90
|
+
// AuditLogs incident: the spec has two near-identical models
|
|
91
|
+
// (AuditLogEventActor / AuditLogEventTarget) and the live SDK declares a
|
|
92
|
+
// hand-written AuditLogActor with the same shape. The structural fallback
|
|
93
|
+
// mapped BOTH IR models onto AuditLogActor, so
|
|
94
|
+
// audit-log-event-target.interface.ts was emitted declaring
|
|
95
|
+
// `export interface AuditLogActor` (file stem and declaration disagree),
|
|
96
|
+
// with duplicate imports/describe blocks and two serializeAuditLogActor
|
|
97
|
+
// definitions downstream.
|
|
98
|
+
// ---------------------------------------------------------------------------
|
|
99
|
+
describe('resolveInterfaceName structural injectivity', () => {
|
|
100
|
+
const field = (name: string, required = false) => ({
|
|
101
|
+
name,
|
|
102
|
+
type: { kind: 'primitive', type: 'string' },
|
|
103
|
+
required,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Shape ~ { id?, name, type?, metadata? } — matches the live AuditLogActor.
|
|
107
|
+
const eventShape = (extra: string) => [
|
|
108
|
+
field('id'),
|
|
109
|
+
field('name', true),
|
|
110
|
+
field('type'),
|
|
111
|
+
field('metadata'),
|
|
112
|
+
field(extra),
|
|
113
|
+
];
|
|
114
|
+
|
|
115
|
+
const liveActorFields = {
|
|
116
|
+
id: { type: 'string', optional: true },
|
|
117
|
+
name: { type: 'string', optional: false },
|
|
118
|
+
type: { type: 'string', optional: true },
|
|
119
|
+
metadata: { type: 'string', optional: true },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
function auditCtx(opts: {
|
|
123
|
+
models?: { name: string; fields: unknown[] }[];
|
|
124
|
+
modelNameByIR: [string, string][];
|
|
125
|
+
interfaceByName?: [string, string][];
|
|
126
|
+
extraInterfaces?: Record<string, unknown>;
|
|
127
|
+
}): EmitterContext {
|
|
128
|
+
const models = opts.models ?? [
|
|
129
|
+
{ name: 'AuditLogEventActor', fields: eventShape('ip_address') },
|
|
130
|
+
{ name: 'AuditLogEventTarget', fields: eventShape('domain') },
|
|
131
|
+
];
|
|
132
|
+
return {
|
|
133
|
+
...ctx,
|
|
134
|
+
spec: { ...ctx.spec, models },
|
|
135
|
+
apiSurface: {
|
|
136
|
+
language: 'node',
|
|
137
|
+
extractedFrom: '/tmp/workos-node',
|
|
138
|
+
extractedAt: '2026-06-10T00:00:00Z',
|
|
139
|
+
classes: {},
|
|
140
|
+
interfaces: {
|
|
141
|
+
AuditLogActor: { fields: liveActorFields },
|
|
142
|
+
...opts.extraInterfaces,
|
|
143
|
+
},
|
|
144
|
+
typeAliases: {},
|
|
145
|
+
enums: {},
|
|
146
|
+
exports: {},
|
|
147
|
+
},
|
|
148
|
+
overlayLookup: {
|
|
149
|
+
methodByOperation: new Map(),
|
|
150
|
+
interfaceByName: new Map(opts.interfaceByName ?? []),
|
|
151
|
+
modelNameByIR: new Map(opts.modelNameByIR),
|
|
152
|
+
},
|
|
153
|
+
} as unknown as EmitterContext;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
it('never lets two IR models collapse onto one live name', () => {
|
|
157
|
+
const c = auditCtx({
|
|
158
|
+
modelNameByIR: [
|
|
159
|
+
['AuditLogEventActor', 'AuditLogActor'],
|
|
160
|
+
['AuditLogEventTarget', 'AuditLogActor'],
|
|
161
|
+
],
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
const actor = resolveInterfaceName('AuditLogEventActor', c);
|
|
165
|
+
const target = resolveInterfaceName('AuditLogEventTarget', c);
|
|
166
|
+
|
|
167
|
+
// The closer name wins the contested live name; the loser keeps its
|
|
168
|
+
// canonical IR-derived name — it must NEVER unify onto AuditLogActor.
|
|
169
|
+
expect(actor).toBe('AuditLogActor');
|
|
170
|
+
expect(target).toBe('AuditLogEventTarget');
|
|
171
|
+
expect(actor).not.toBe(target);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it('awards a contested name independently of overlay insertion order', () => {
|
|
175
|
+
const c = auditCtx({
|
|
176
|
+
models: [
|
|
177
|
+
{ name: 'AuditLogEventTarget', fields: eventShape('domain') },
|
|
178
|
+
{ name: 'AuditLogEventActor', fields: eventShape('ip_address') },
|
|
179
|
+
],
|
|
180
|
+
modelNameByIR: [
|
|
181
|
+
['AuditLogEventTarget', 'AuditLogActor'],
|
|
182
|
+
['AuditLogEventActor', 'AuditLogActor'],
|
|
183
|
+
],
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
expect(resolveInterfaceName('AuditLogEventActor', c)).toBe('AuditLogActor');
|
|
187
|
+
expect(resolveInterfaceName('AuditLogEventTarget', c)).toBe('AuditLogEventTarget');
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
it('stays injective when Serialized* normalization collapses two distinct raw matches', () => {
|
|
191
|
+
// The engine overlay itself is injective on raw names (actor →
|
|
192
|
+
// AuditLogActor, target → SerializedAuditLogActor), but the resolver
|
|
193
|
+
// normalizes Serialized* down to the bare name — that post-processing
|
|
194
|
+
// must not re-introduce a collision.
|
|
195
|
+
const c = auditCtx({
|
|
196
|
+
modelNameByIR: [
|
|
197
|
+
['AuditLogEventActor', 'AuditLogActor'],
|
|
198
|
+
['AuditLogEventTarget', 'SerializedAuditLogActor'],
|
|
199
|
+
],
|
|
200
|
+
extraInterfaces: { SerializedAuditLogActor: { fields: liveActorFields } },
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(resolveInterfaceName('AuditLogEventActor', c)).toBe('AuditLogActor');
|
|
204
|
+
expect(resolveInterfaceName('AuditLogEventTarget', c)).toBe('AuditLogEventTarget');
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('prefers the structurally closer claimant over the closer name', () => {
|
|
208
|
+
// Target matches the live shape exactly; actor only shares two fields.
|
|
209
|
+
// Similarity outranks name distance, so target wins even though
|
|
210
|
+
// "AuditLogEventActor" is the closer name.
|
|
211
|
+
const c = auditCtx({
|
|
212
|
+
models: [
|
|
213
|
+
{ name: 'AuditLogEventActor', fields: [field('id'), field('name', true), field('ip'), field('agent')] },
|
|
214
|
+
{ name: 'AuditLogEventTarget', fields: [field('id'), field('name', true), field('type'), field('metadata')] },
|
|
215
|
+
],
|
|
216
|
+
modelNameByIR: [
|
|
217
|
+
['AuditLogEventActor', 'AuditLogActor'],
|
|
218
|
+
['AuditLogEventTarget', 'AuditLogActor'],
|
|
219
|
+
],
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
expect(resolveInterfaceName('AuditLogEventTarget', c)).toBe('AuditLogActor');
|
|
223
|
+
expect(resolveInterfaceName('AuditLogEventActor', c)).toBe('AuditLogEventActor');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('blocks structural claims on names already claimed by an exact-name override', () => {
|
|
227
|
+
const c = auditCtx({
|
|
228
|
+
interfaceByName: [['AuditLogEventActor', 'AuditLogActor']],
|
|
229
|
+
modelNameByIR: [['AuditLogEventTarget', 'AuditLogActor']],
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
expect(resolveInterfaceName('AuditLogEventActor', c)).toBe('AuditLogActor');
|
|
233
|
+
expect(resolveInterfaceName('AuditLogEventTarget', c)).toBe('AuditLogEventTarget');
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it('still applies a single-model structural rename (the legitimate overlay case)', () => {
|
|
237
|
+
const c = auditCtx({
|
|
238
|
+
models: [{ name: 'AuditLogEventActor', fields: eventShape('ip_address') }],
|
|
239
|
+
modelNameByIR: [['AuditLogEventActor', 'AuditLogActor']],
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
expect(resolveInterfaceName('AuditLogEventActor', c)).toBe('AuditLogActor');
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
87
246
|
describe('wireInterfaceName', () => {
|
|
88
247
|
it('emits *Wire for a fresh `*Response`-named IR model with an empty baseline', () => {
|
|
89
248
|
expect(wireInterfaceName('CreateDataKeyResponse')).toBe('CreateDataKeyResponseWire');
|