@workos/oagen-emitters 0.18.2 → 0.18.3
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 +8 -0
- package/dist/index.mjs +1 -1
- package/dist/{plugin-bqfwowQ3.mjs → plugin-1ckLMpgo.mjs} +15 -3
- package/dist/plugin-1ckLMpgo.mjs.map +1 -0
- package/dist/plugin.mjs +1 -1
- package/package.json +1 -1
- package/src/dotnet/fixtures.ts +12 -1
- package/src/node/resources.ts +21 -2
- package/test/node/resources.test.ts +31 -2
- package/dist/plugin-bqfwowQ3.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-1ckLMpgo.mjs";
|
|
2
2
|
export { workosEmittersPlugin };
|
package/package.json
CHANGED
package/src/dotnet/fixtures.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Model, TypeRef, Enum } from '@workos/oagen';
|
|
2
2
|
import { fixtureFileName, fieldName } from './naming.js';
|
|
3
3
|
import { isListMetadataModel, isListWrapperModel } from './models.js';
|
|
4
|
+
import { collectNonPaginatedResponseModelNames } from '../shared/model-utils.js';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Prefix mapping for generating realistic ID fixture values.
|
|
@@ -35,9 +36,19 @@ export function generateFixtures(spec: {
|
|
|
35
36
|
const enumMap = new Map(spec.enums.map((e) => [e.name, e]));
|
|
36
37
|
const files: { path: string; content: string }[] = [];
|
|
37
38
|
|
|
39
|
+
// List-wrappers are normally represented only by the per-operation
|
|
40
|
+
// `list_<item>.json` fixtures generated from paginated operations below. But
|
|
41
|
+
// a wrapper returned by a NON-paginated operation (e.g.
|
|
42
|
+
// `PUT /authorization/groups/{id}/role_assignments` -> GroupRoleAssignmentList)
|
|
43
|
+
// is emitted as a real model (see models.ts) and its generated test references
|
|
44
|
+
// `testdata/<type>.json` (tests.ts). Emit that envelope fixture too, mirroring
|
|
45
|
+
// the non-wrapper `VersionListResponse` precedent — otherwise the test loads a
|
|
46
|
+
// file that was never written.
|
|
47
|
+
const nonPaginatedWrapperRefs = collectNonPaginatedResponseModelNames(spec.services);
|
|
48
|
+
|
|
38
49
|
for (const model of spec.models) {
|
|
39
50
|
if (isListMetadataModel(model)) continue;
|
|
40
|
-
if (isListWrapperModel(model)) continue;
|
|
51
|
+
if (isListWrapperModel(model) && !nonPaginatedWrapperRefs.has(model.name)) continue;
|
|
41
52
|
|
|
42
53
|
const fixture = model.fields.length === 0 ? {} : generateModelFixture(model, modelMap, enumMap);
|
|
43
54
|
|
package/src/node/resources.ts
CHANGED
|
@@ -279,6 +279,24 @@ function operationHasOptionsInput(op: Operation, plan: OperationPlan, resolvedOp
|
|
|
279
279
|
);
|
|
280
280
|
}
|
|
281
281
|
|
|
282
|
+
// True only when the type is a SINGLE closed object literal (`{ ... }`), not
|
|
283
|
+
// the head of a compound type such as `{ ... } & X` or `{ ... } | Y`. Counting
|
|
284
|
+
// brace depth locates the literal's matching close brace (handling nesting like
|
|
285
|
+
// `{ a: { b: string } }`); any non-whitespace after it means the type is not a
|
|
286
|
+
// pure literal and must be preserved verbatim rather than replaced by a named
|
|
287
|
+
// request interface.
|
|
288
|
+
function isClosedObjectLiteral(type: string): boolean {
|
|
289
|
+
const t = type.trim();
|
|
290
|
+
if (!t.startsWith('{')) return false;
|
|
291
|
+
let depth = 0;
|
|
292
|
+
for (let i = 0; i < t.length; i++) {
|
|
293
|
+
const ch = t[i];
|
|
294
|
+
if (ch === '{') depth++;
|
|
295
|
+
else if (ch === '}' && --depth === 0) return i === t.length - 1;
|
|
296
|
+
}
|
|
297
|
+
return false;
|
|
298
|
+
}
|
|
299
|
+
|
|
282
300
|
function optionsObjectInfo(
|
|
283
301
|
service: Service,
|
|
284
302
|
method: string,
|
|
@@ -300,9 +318,10 @@ function optionsObjectInfo(
|
|
|
300
318
|
// operation owns a named request-body model, adopt that interface so the
|
|
301
319
|
// method signature, the serializer, and the request model all agree.
|
|
302
320
|
// Named baseline types (`CreateOrganizationApiKeyOptions`) and compound
|
|
303
|
-
// intersections (`X & { ... }`) are still preserved
|
|
321
|
+
// intersections (`X & { ... }` or `{ ... } & X`) are still preserved
|
|
322
|
+
// verbatim — only a single, closed object literal is eligible for adoption.
|
|
304
323
|
if (
|
|
305
|
-
baseline.type
|
|
324
|
+
isClosedObjectLiteral(baseline.type) &&
|
|
306
325
|
isNodeOwnedService(ctx, service.name, resolveResourceClassName(service, ctx))
|
|
307
326
|
) {
|
|
308
327
|
const body = extractRequestBodyType(op, ctx);
|
|
@@ -921,7 +921,7 @@ describe('inline object-literal baseline parameter types', () => {
|
|
|
921
921
|
],
|
|
922
922
|
});
|
|
923
923
|
|
|
924
|
-
const baselineCtx = (service: Service, models: any[]): EmitterContext => ({
|
|
924
|
+
const baselineCtx = (service: Service, models: any[], paramType: string = literalType): EmitterContext => ({
|
|
925
925
|
...ctx,
|
|
926
926
|
spec: { ...emptySpec, services: [service], models },
|
|
927
927
|
emitterOptions: { ownedServices: ['AdminPortal'] },
|
|
@@ -933,7 +933,7 @@ describe('inline object-literal baseline parameter types', () => {
|
|
|
933
933
|
generateLink: [
|
|
934
934
|
{
|
|
935
935
|
name: 'generateLink',
|
|
936
|
-
params: [{ name: 'options', type:
|
|
936
|
+
params: [{ name: 'options', type: paramType, passingStyle: 'options_object' }],
|
|
937
937
|
returnType: 'Promise<{ link: string }>',
|
|
938
938
|
async: true,
|
|
939
939
|
},
|
|
@@ -1007,6 +1007,35 @@ describe('inline object-literal baseline parameter types', () => {
|
|
|
1007
1007
|
expect(content).toContain(`async generateLink(options: ${literalType})`);
|
|
1008
1008
|
expect(content).not.toContain('import type { {');
|
|
1009
1009
|
});
|
|
1010
|
+
|
|
1011
|
+
it('keeps a `{ ... } & X` compound intersection inline even with a named request model', () => {
|
|
1012
|
+
// The adoption guard targets a PURE object literal. A baseline param that
|
|
1013
|
+
// LEADS with a literal but is actually a compound intersection
|
|
1014
|
+
// (`{ ... } & WithMetadata`) carries the hand-authored `& X` portion, which
|
|
1015
|
+
// a named-interface swap would silently drop. It must be preserved verbatim
|
|
1016
|
+
// even though the operation has a single named request-body model.
|
|
1017
|
+
const compoundType = `${literalType} & WithMetadata`;
|
|
1018
|
+
const service = adminPortalService({ kind: 'model', name: 'GenerateLinkBody' });
|
|
1019
|
+
const models = [
|
|
1020
|
+
{
|
|
1021
|
+
name: 'GenerateLinkBody',
|
|
1022
|
+
fields: [
|
|
1023
|
+
{ name: 'intent', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
1024
|
+
{ name: 'organization', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
1025
|
+
],
|
|
1026
|
+
},
|
|
1027
|
+
{ name: 'PortalLink', fields: [{ name: 'link', type: { kind: 'primitive', type: 'string' }, required: true }] },
|
|
1028
|
+
];
|
|
1029
|
+
|
|
1030
|
+
const result = generateResources([service], baselineCtx(service, models, compoundType));
|
|
1031
|
+
const content = result.find((f) => f.path === 'src/admin-portal/admin-portal.ts')!.content;
|
|
1032
|
+
|
|
1033
|
+
// The compound type (including the `& WithMetadata` tail) survives intact;
|
|
1034
|
+
// it is NOT replaced by the named `GenerateLinkBody` interface.
|
|
1035
|
+
expect(content).toContain(`async generateLink(options: ${compoundType})`);
|
|
1036
|
+
expect(content).not.toContain('async generateLink(options: GenerateLinkBody)');
|
|
1037
|
+
expect(content).not.toContain('import type { {');
|
|
1038
|
+
});
|
|
1010
1039
|
});
|
|
1011
1040
|
|
|
1012
1041
|
describe('@oagen-ignore region method filtering', () => {
|