@workos/oagen-emitters 0.2.1 → 0.4.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/.husky/pre-commit +1 -0
- package/.release-please-manifest.json +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +129 -0
- package/dist/index.d.mts +13 -1
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +14549 -3385
- package/dist/index.mjs.map +1 -1
- package/docs/sdk-architecture/dotnet.md +336 -0
- package/docs/sdk-architecture/go.md +338 -0
- package/docs/sdk-architecture/php.md +315 -0
- package/docs/sdk-architecture/python.md +511 -0
- package/oagen.config.ts +328 -2
- package/package.json +9 -5
- package/scripts/generate-php.js +13 -0
- package/scripts/git-push-with-published-oagen.sh +21 -0
- package/smoke/sdk-dotnet.ts +45 -12
- package/smoke/sdk-go.ts +116 -42
- package/smoke/sdk-php.ts +28 -26
- package/smoke/sdk-python.ts +5 -2
- package/src/dotnet/client.ts +89 -0
- package/src/dotnet/enums.ts +323 -0
- package/src/dotnet/fixtures.ts +236 -0
- package/src/dotnet/index.ts +246 -0
- package/src/dotnet/manifest.ts +36 -0
- package/src/dotnet/models.ts +344 -0
- package/src/dotnet/naming.ts +330 -0
- package/src/dotnet/resources.ts +622 -0
- package/src/dotnet/tests.ts +693 -0
- package/src/dotnet/type-map.ts +201 -0
- package/src/dotnet/wrappers.ts +186 -0
- package/src/go/client.ts +141 -0
- package/src/go/enums.ts +196 -0
- package/src/go/fixtures.ts +212 -0
- package/src/go/index.ts +84 -0
- package/src/go/manifest.ts +36 -0
- package/src/go/models.ts +254 -0
- package/src/go/naming.ts +179 -0
- package/src/go/resources.ts +827 -0
- package/src/go/tests.ts +751 -0
- package/src/go/type-map.ts +82 -0
- package/src/go/wrappers.ts +261 -0
- package/src/index.ts +4 -0
- package/src/kotlin/client.ts +53 -0
- package/src/kotlin/enums.ts +162 -0
- package/src/kotlin/index.ts +92 -0
- package/src/kotlin/manifest.ts +55 -0
- package/src/kotlin/models.ts +395 -0
- package/src/kotlin/naming.ts +223 -0
- package/src/kotlin/overrides.ts +25 -0
- package/src/kotlin/resources.ts +667 -0
- package/src/kotlin/tests.ts +1019 -0
- package/src/kotlin/type-map.ts +123 -0
- package/src/kotlin/wrappers.ts +168 -0
- package/src/node/client.ts +128 -115
- package/src/node/enums.ts +9 -0
- package/src/node/errors.ts +37 -232
- package/src/node/field-plan.ts +726 -0
- package/src/node/fixtures.ts +9 -1
- package/src/node/index.ts +3 -9
- package/src/node/models.ts +178 -21
- package/src/node/naming.ts +49 -111
- package/src/node/resources.ts +527 -397
- package/src/node/sdk-errors.ts +41 -0
- package/src/node/tests.ts +69 -19
- package/src/node/type-map.ts +4 -2
- package/src/node/utils.ts +13 -71
- package/src/node/wrappers.ts +151 -0
- package/src/php/client.ts +179 -0
- package/src/php/enums.ts +67 -0
- package/src/php/errors.ts +9 -0
- package/src/php/fixtures.ts +181 -0
- package/src/php/index.ts +96 -0
- package/src/php/manifest.ts +36 -0
- package/src/php/models.ts +310 -0
- package/src/php/naming.ts +279 -0
- package/src/php/resources.ts +636 -0
- package/src/php/tests.ts +609 -0
- package/src/php/type-map.ts +90 -0
- package/src/php/utils.ts +18 -0
- package/src/php/wrappers.ts +152 -0
- package/src/python/client.ts +345 -0
- package/src/python/enums.ts +313 -0
- package/src/python/fixtures.ts +196 -0
- package/src/python/index.ts +95 -0
- package/src/python/manifest.ts +38 -0
- package/src/python/models.ts +688 -0
- package/src/python/naming.ts +189 -0
- package/src/python/resources.ts +1322 -0
- package/src/python/tests.ts +1335 -0
- package/src/python/type-map.ts +93 -0
- package/src/python/wrappers.ts +191 -0
- package/src/shared/model-utils.ts +472 -0
- package/src/shared/naming-utils.ts +154 -0
- package/src/shared/non-spec-services.ts +54 -0
- package/src/shared/resolved-ops.ts +109 -0
- package/src/shared/wrapper-utils.ts +70 -0
- package/test/dotnet/client.test.ts +121 -0
- package/test/dotnet/enums.test.ts +193 -0
- package/test/dotnet/errors.test.ts +9 -0
- package/test/dotnet/manifest.test.ts +82 -0
- package/test/dotnet/models.test.ts +260 -0
- package/test/dotnet/resources.test.ts +255 -0
- package/test/dotnet/tests.test.ts +202 -0
- package/test/go/client.test.ts +92 -0
- package/test/go/enums.test.ts +132 -0
- package/test/go/errors.test.ts +9 -0
- package/test/go/models.test.ts +265 -0
- package/test/go/resources.test.ts +408 -0
- package/test/go/tests.test.ts +143 -0
- package/test/kotlin/models.test.ts +135 -0
- package/test/kotlin/tests.test.ts +176 -0
- package/test/node/client.test.ts +92 -12
- package/test/node/enums.test.ts +2 -0
- package/test/node/errors.test.ts +2 -41
- package/test/node/models.test.ts +2 -0
- package/test/node/naming.test.ts +23 -0
- package/test/node/resources.test.ts +315 -84
- package/test/node/serializers.test.ts +3 -1
- package/test/node/type-map.test.ts +11 -0
- package/test/php/client.test.ts +95 -0
- package/test/php/enums.test.ts +173 -0
- package/test/php/errors.test.ts +9 -0
- package/test/php/models.test.ts +497 -0
- package/test/php/resources.test.ts +682 -0
- package/test/php/tests.test.ts +185 -0
- package/test/python/client.test.ts +200 -0
- package/test/python/enums.test.ts +228 -0
- package/test/python/errors.test.ts +16 -0
- package/test/python/manifest.test.ts +74 -0
- package/test/python/models.test.ts +716 -0
- package/test/python/resources.test.ts +617 -0
- package/test/python/tests.test.ts +202 -0
- package/src/node/common.ts +0 -273
- package/src/node/config.ts +0 -71
- package/src/node/serializers.ts +0 -746
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { generateTests } from '../../src/python/tests.js';
|
|
3
|
+
import type { EmitterContext, ApiSpec, Service, Model } from '@workos/oagen';
|
|
4
|
+
import { defaultSdkBehavior } from '@workos/oagen';
|
|
5
|
+
|
|
6
|
+
const models: Model[] = [
|
|
7
|
+
{
|
|
8
|
+
name: 'Organization',
|
|
9
|
+
fields: [
|
|
10
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
11
|
+
{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
12
|
+
],
|
|
13
|
+
},
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
const services: Service[] = [
|
|
17
|
+
{
|
|
18
|
+
name: 'Organizations',
|
|
19
|
+
operations: [
|
|
20
|
+
{
|
|
21
|
+
name: 'getOrganization',
|
|
22
|
+
httpMethod: 'get',
|
|
23
|
+
path: '/organizations/{id}',
|
|
24
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
25
|
+
queryParams: [],
|
|
26
|
+
headerParams: [],
|
|
27
|
+
response: { kind: 'model', name: 'Organization' },
|
|
28
|
+
errors: [],
|
|
29
|
+
injectIdempotencyKey: false,
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: 'deleteOrganization',
|
|
33
|
+
httpMethod: 'delete',
|
|
34
|
+
path: '/organizations/{id}',
|
|
35
|
+
pathParams: [{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true }],
|
|
36
|
+
queryParams: [],
|
|
37
|
+
headerParams: [],
|
|
38
|
+
response: { kind: 'primitive', type: 'unknown' },
|
|
39
|
+
errors: [],
|
|
40
|
+
injectIdempotencyKey: false,
|
|
41
|
+
},
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const spec: ApiSpec = {
|
|
47
|
+
name: 'TestAPI',
|
|
48
|
+
version: '1.0.0',
|
|
49
|
+
baseUrl: 'https://api.workos.com',
|
|
50
|
+
services,
|
|
51
|
+
models,
|
|
52
|
+
enums: [],
|
|
53
|
+
sdk: defaultSdkBehavior(),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const ctx: EmitterContext = {
|
|
57
|
+
namespace: 'workos',
|
|
58
|
+
namespacePascal: 'WorkOS',
|
|
59
|
+
spec,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
describe('generateTests', () => {
|
|
63
|
+
it('does not generate conftest, helpers, client tests, or pagination tests (now @oagen-ignore-file)', () => {
|
|
64
|
+
const files = generateTests(spec, ctx);
|
|
65
|
+
expect(files.find((f) => f.path === 'tests/generated_helpers.py')).toBeUndefined();
|
|
66
|
+
expect(files.find((f) => f.path === 'tests/conftest.py')).toBeUndefined();
|
|
67
|
+
expect(files.find((f) => f.path === 'tests/test_generated_client.py')).toBeUndefined();
|
|
68
|
+
expect(files.find((f) => f.path === 'tests/test_pagination.py')).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('generates per-service test file', () => {
|
|
72
|
+
const files = generateTests(spec, ctx);
|
|
73
|
+
const testFile = files.find((f) => f.path === 'tests/test_organizations.py');
|
|
74
|
+
expect(testFile).toBeDefined();
|
|
75
|
+
|
|
76
|
+
const content = testFile!.content;
|
|
77
|
+
expect(content).toContain('class TestOrganizations:');
|
|
78
|
+
expect(content).toContain('def test_get_organization(');
|
|
79
|
+
expect(content).toContain('def test_delete_organization(');
|
|
80
|
+
expect(content).toContain('assert result is None');
|
|
81
|
+
expect(content).toContain('isinstance(result, Organization)');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('generates error test', () => {
|
|
85
|
+
const files = generateTests(spec, ctx);
|
|
86
|
+
const testFile = files.find((f) => f.path === 'tests/test_organizations.py');
|
|
87
|
+
expect(testFile!.content).toContain('def test_get_organization_unauthorized(');
|
|
88
|
+
expect(testFile!.content).toContain('def test_get_organization_not_found(');
|
|
89
|
+
expect(testFile!.content).toContain('def test_get_organization_rate_limited(');
|
|
90
|
+
expect(testFile!.content).toContain('def test_get_organization_server_error(');
|
|
91
|
+
expect(testFile!.content).toContain('pytest.raises(AuthenticationError)');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('still generates per-service and model round-trip tests', () => {
|
|
95
|
+
const files = generateTests(spec, ctx);
|
|
96
|
+
expect(files.find((f) => f.path === 'tests/test_organizations.py')).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('generates fixture JSON files', () => {
|
|
100
|
+
const files = generateTests(spec, ctx);
|
|
101
|
+
const fixture = files.find((f) => f.path === 'tests/fixtures/organization.json');
|
|
102
|
+
expect(fixture).toBeDefined();
|
|
103
|
+
expect(fixture!.headerPlacement).toBe('skip');
|
|
104
|
+
|
|
105
|
+
const data = JSON.parse(fixture!.content);
|
|
106
|
+
expect(data).toHaveProperty('id');
|
|
107
|
+
expect(data).toHaveProperty('name');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('generates model edge-case and query/pagination regression tests', () => {
|
|
111
|
+
const edgeModels: Model[] = [
|
|
112
|
+
{
|
|
113
|
+
name: 'Organization',
|
|
114
|
+
fields: [
|
|
115
|
+
{ name: 'id', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
116
|
+
{ name: 'name', type: { kind: 'primitive', type: 'string' }, required: true },
|
|
117
|
+
{ name: 'status', type: { kind: 'enum', name: 'OrganizationStatus' }, required: true },
|
|
118
|
+
{ name: 'nickname', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
119
|
+
{
|
|
120
|
+
name: 'external_id',
|
|
121
|
+
type: { kind: 'nullable', inner: { kind: 'primitive', type: 'string' } },
|
|
122
|
+
required: true,
|
|
123
|
+
},
|
|
124
|
+
],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
name: 'OrganizationList',
|
|
128
|
+
fields: [
|
|
129
|
+
{ name: 'data', type: { kind: 'array', items: { kind: 'model', name: 'Organization' } }, required: true },
|
|
130
|
+
{ name: 'list_metadata', type: { kind: 'model', name: 'ListMetadata' }, required: true },
|
|
131
|
+
],
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'ListMetadata',
|
|
135
|
+
fields: [
|
|
136
|
+
{ name: 'before', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
137
|
+
{ name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
138
|
+
],
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const edgeSpec: ApiSpec = {
|
|
143
|
+
...spec,
|
|
144
|
+
models: edgeModels,
|
|
145
|
+
enums: [
|
|
146
|
+
{
|
|
147
|
+
name: 'OrganizationStatus',
|
|
148
|
+
values: [
|
|
149
|
+
{ name: 'ACTIVE', value: 'active' },
|
|
150
|
+
{ name: 'INACTIVE', value: 'inactive' },
|
|
151
|
+
],
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
services: [
|
|
155
|
+
{
|
|
156
|
+
name: 'Organizations',
|
|
157
|
+
operations: [
|
|
158
|
+
{
|
|
159
|
+
name: 'listOrganizations',
|
|
160
|
+
httpMethod: 'get',
|
|
161
|
+
path: '/organizations',
|
|
162
|
+
pathParams: [],
|
|
163
|
+
queryParams: [
|
|
164
|
+
{ name: 'status', type: { kind: 'enum', name: 'OrganizationStatus' }, required: false },
|
|
165
|
+
{ name: 'email', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
166
|
+
{ name: 'limit', type: { kind: 'primitive', type: 'integer' }, required: false },
|
|
167
|
+
{ name: 'before', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
168
|
+
{ name: 'after', type: { kind: 'primitive', type: 'string' }, required: false },
|
|
169
|
+
],
|
|
170
|
+
headerParams: [],
|
|
171
|
+
response: { kind: 'model', name: 'OrganizationList' },
|
|
172
|
+
errors: [],
|
|
173
|
+
injectIdempotencyKey: false,
|
|
174
|
+
pagination: {
|
|
175
|
+
strategy: 'cursor',
|
|
176
|
+
param: 'after',
|
|
177
|
+
dataPath: 'data',
|
|
178
|
+
itemType: { kind: 'model', name: 'Organization' },
|
|
179
|
+
},
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
},
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const files = generateTests(edgeSpec, { ...ctx, spec: edgeSpec });
|
|
187
|
+
const serviceTest = files.find((f) => f.path === 'tests/test_organizations.py');
|
|
188
|
+
const roundTripTest = files.find((f) => f.path === 'tests/test_models_round_trip.py');
|
|
189
|
+
|
|
190
|
+
expect(serviceTest).toBeDefined();
|
|
191
|
+
expect(serviceTest!.content).toContain('def test_list_organizations_empty_page(');
|
|
192
|
+
expect(serviceTest!.content).toContain('def test_list_organizations_encodes_query_params(');
|
|
193
|
+
expect(serviceTest!.content).toContain('assert request.url.params["email"] == "value email/test"');
|
|
194
|
+
expect(serviceTest!.content).toContain('assert request.url.params["limit"] == "10"');
|
|
195
|
+
|
|
196
|
+
expect(roundTripTest).toBeDefined();
|
|
197
|
+
expect(roundTripTest!.content).toContain('def test_organization_minimal_payload(');
|
|
198
|
+
expect(roundTripTest!.content).toContain('def test_organization_omits_absent_optional_non_nullable_fields(');
|
|
199
|
+
expect(roundTripTest!.content).toContain('def test_organization_preserves_nullable_fields(');
|
|
200
|
+
expect(roundTripTest!.content).toContain('def test_organization_round_trips_unknown_enum_values(');
|
|
201
|
+
});
|
|
202
|
+
});
|
package/src/node/common.ts
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import type { GeneratedFile } from '@workos/oagen';
|
|
2
|
-
|
|
3
|
-
export function generateCommon(): GeneratedFile[] {
|
|
4
|
-
return [
|
|
5
|
-
{
|
|
6
|
-
path: 'src/common/utils/pagination.ts',
|
|
7
|
-
content: paginationContent(),
|
|
8
|
-
skipIfExists: true,
|
|
9
|
-
integrateTarget: false,
|
|
10
|
-
},
|
|
11
|
-
{
|
|
12
|
-
path: 'src/common/utils/fetch-and-deserialize.ts',
|
|
13
|
-
content: fetchAndDeserializeContent(),
|
|
14
|
-
skipIfExists: true,
|
|
15
|
-
integrateTarget: true,
|
|
16
|
-
},
|
|
17
|
-
{
|
|
18
|
-
path: 'src/common/serializers/list.serializer.ts',
|
|
19
|
-
content: listSerializerContent(),
|
|
20
|
-
skipIfExists: true,
|
|
21
|
-
integrateTarget: false,
|
|
22
|
-
},
|
|
23
|
-
{
|
|
24
|
-
path: 'src/common/utils/test-utils.ts',
|
|
25
|
-
content: testUtilsContent(),
|
|
26
|
-
skipIfExists: true,
|
|
27
|
-
integrateTarget: true,
|
|
28
|
-
},
|
|
29
|
-
];
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function paginationContent(): string {
|
|
33
|
-
return `import type { PaginationOptions } from '../interfaces/pagination-options.interface';
|
|
34
|
-
|
|
35
|
-
export interface ListMetadata {
|
|
36
|
-
before: string | null;
|
|
37
|
-
after: string | null;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface List<T> {
|
|
41
|
-
object: 'list';
|
|
42
|
-
data: T[];
|
|
43
|
-
listMetadata: ListMetadata;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface ListResponse<T> {
|
|
47
|
-
object: 'list';
|
|
48
|
-
data: T[];
|
|
49
|
-
list_metadata: {
|
|
50
|
-
before: string | null;
|
|
51
|
-
after: string | null;
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export class AutoPaginatable<
|
|
56
|
-
ResourceType,
|
|
57
|
-
ParametersType extends PaginationOptions = PaginationOptions,
|
|
58
|
-
> {
|
|
59
|
-
readonly object = 'list' as const;
|
|
60
|
-
readonly options: ParametersType;
|
|
61
|
-
|
|
62
|
-
constructor(
|
|
63
|
-
protected list: List<ResourceType>,
|
|
64
|
-
private apiCall: (params: PaginationOptions) => Promise<List<ResourceType>>,
|
|
65
|
-
options?: ParametersType,
|
|
66
|
-
) {
|
|
67
|
-
this.options = options ?? ({} as ParametersType);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
get data(): ResourceType[] {
|
|
71
|
-
return this.list.data;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
get listMetadata() {
|
|
75
|
-
return this.list.listMetadata;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private async *generatePages(
|
|
79
|
-
params: PaginationOptions,
|
|
80
|
-
): AsyncGenerator<ResourceType[]> {
|
|
81
|
-
const result = await this.apiCall({
|
|
82
|
-
...this.options,
|
|
83
|
-
limit: 100,
|
|
84
|
-
after: params.after,
|
|
85
|
-
});
|
|
86
|
-
yield result.data;
|
|
87
|
-
if (result.listMetadata.after) {
|
|
88
|
-
await new Promise((resolve) => setTimeout(resolve, 350));
|
|
89
|
-
yield* this.generatePages({ after: result.listMetadata.after });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
async autoPagination(): Promise<ResourceType[]> {
|
|
94
|
-
if (this.options.limit) {
|
|
95
|
-
return this.data;
|
|
96
|
-
}
|
|
97
|
-
const results: ResourceType[] = [];
|
|
98
|
-
for await (const page of this.generatePages({
|
|
99
|
-
after: this.options.after,
|
|
100
|
-
})) {
|
|
101
|
-
results.push(...page);
|
|
102
|
-
}
|
|
103
|
-
return results;
|
|
104
|
-
}
|
|
105
|
-
}`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function fetchAndDeserializeContent(): string {
|
|
109
|
-
return `import type { WorkOS } from '../../workos';
|
|
110
|
-
import type { PaginationOptions } from '../interfaces/pagination-options.interface';
|
|
111
|
-
import { AutoPaginatable, type List, type ListResponse } from './pagination';
|
|
112
|
-
|
|
113
|
-
function setDefaultOptions(
|
|
114
|
-
options?: PaginationOptions,
|
|
115
|
-
): Record<string, any> {
|
|
116
|
-
return {
|
|
117
|
-
order: 'desc',
|
|
118
|
-
...options,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
function deserializeList<T, U>(
|
|
123
|
-
data: ListResponse<T>,
|
|
124
|
-
deserializeFn: (item: T) => U,
|
|
125
|
-
): List<U> {
|
|
126
|
-
return {
|
|
127
|
-
data: data.data.map(deserializeFn),
|
|
128
|
-
listMetadata: {
|
|
129
|
-
before: data.list_metadata.before,
|
|
130
|
-
after: data.list_metadata.after,
|
|
131
|
-
},
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export const fetchAndDeserialize = async <T, U>(
|
|
136
|
-
workos: WorkOS,
|
|
137
|
-
endpoint: string,
|
|
138
|
-
deserializeFn: (data: T) => U,
|
|
139
|
-
options?: PaginationOptions,
|
|
140
|
-
): Promise<List<U>> => {
|
|
141
|
-
const { data } = await workos.get<ListResponse<T>>(endpoint, {
|
|
142
|
-
query: setDefaultOptions(options),
|
|
143
|
-
});
|
|
144
|
-
return deserializeList(data, deserializeFn);
|
|
145
|
-
};
|
|
146
|
-
|
|
147
|
-
export async function createPaginatedList<TResponse, TModel, TOptions extends PaginationOptions>(
|
|
148
|
-
workos: WorkOS,
|
|
149
|
-
endpoint: string,
|
|
150
|
-
deserializeFn: (r: TResponse) => TModel,
|
|
151
|
-
options?: TOptions,
|
|
152
|
-
): Promise<AutoPaginatable<TModel, TOptions>> {
|
|
153
|
-
return new AutoPaginatable(
|
|
154
|
-
await fetchAndDeserialize<TResponse, TModel>(workos, endpoint, deserializeFn, options),
|
|
155
|
-
(params) => fetchAndDeserialize<TResponse, TModel>(workos, endpoint, deserializeFn, params),
|
|
156
|
-
options,
|
|
157
|
-
);
|
|
158
|
-
}`;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function listSerializerContent(): string {
|
|
162
|
-
return `import type { ListMetadata, ListResponse } from '../utils/pagination';
|
|
163
|
-
|
|
164
|
-
export function deserializeListMetadata(
|
|
165
|
-
metadata: ListResponse<any>['list_metadata'],
|
|
166
|
-
): ListMetadata {
|
|
167
|
-
return {
|
|
168
|
-
before: metadata.before,
|
|
169
|
-
after: metadata.after,
|
|
170
|
-
};
|
|
171
|
-
}`;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
function testUtilsContent(): string {
|
|
175
|
-
return `import fetch from 'jest-fetch-mock';
|
|
176
|
-
|
|
177
|
-
interface MockParams {
|
|
178
|
-
status?: number;
|
|
179
|
-
headers?: Record<string, string>;
|
|
180
|
-
[key: string]: any;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
export function fetchOnce(
|
|
184
|
-
response: any = {},
|
|
185
|
-
{ status = 200, headers, ...rest }: MockParams = {},
|
|
186
|
-
) {
|
|
187
|
-
return fetch.once(JSON.stringify(response), {
|
|
188
|
-
status,
|
|
189
|
-
headers: {
|
|
190
|
-
'content-type': 'application/json;charset=UTF-8',
|
|
191
|
-
...headers,
|
|
192
|
-
},
|
|
193
|
-
...rest,
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export function fetchURL(): string {
|
|
198
|
-
return String(fetch.mock.calls[0][0]);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function fetchMethod(): string {
|
|
202
|
-
return String(fetch.mock.calls[0][1]?.method ?? 'GET');
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function fetchSearchParams(): Record<string, string> {
|
|
206
|
-
return Object.fromEntries(new URL(fetchURL()).searchParams);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function fetchHeaders(): Record<string, string> {
|
|
210
|
-
const headers = fetch.mock.calls[0][1]?.headers ?? {};
|
|
211
|
-
return headers as Record<string, string>;
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
export function fetchBody({ raw = false } = {}): any {
|
|
215
|
-
const body = fetch.mock.calls[0][1]?.body;
|
|
216
|
-
if (body instanceof URLSearchParams) return body.toString();
|
|
217
|
-
if (raw) return body;
|
|
218
|
-
return JSON.parse(String(body));
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
/**
|
|
222
|
-
* Shared test helper: asserts that the given async function throws when the
|
|
223
|
-
* server responds with 401 Unauthorized.
|
|
224
|
-
*/
|
|
225
|
-
export function testUnauthorized(fn: () => Promise<any>) {
|
|
226
|
-
it('throws on unauthorized', async () => {
|
|
227
|
-
fetchOnce({ message: 'Unauthorized' }, { status: 401 });
|
|
228
|
-
await expect(fn()).rejects.toThrow('Could not authorize the request');
|
|
229
|
-
});
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Shared test helper: asserts that a paginated list call returns the expected
|
|
234
|
-
* shape (data array + listMetadata) and hits the correct endpoint.
|
|
235
|
-
*/
|
|
236
|
-
export function testPaginatedList(
|
|
237
|
-
fn: () => Promise<any>,
|
|
238
|
-
pathContains: string,
|
|
239
|
-
) {
|
|
240
|
-
it('returns paginated results', async () => {
|
|
241
|
-
// Caller must have called fetchOnce with the list fixture before invoking fn
|
|
242
|
-
const { data, listMetadata } = await fn();
|
|
243
|
-
expect(fetchURL()).toContain(pathContains);
|
|
244
|
-
expect(fetchSearchParams()).toHaveProperty('order');
|
|
245
|
-
expect(Array.isArray(data)).toBe(true);
|
|
246
|
-
expect(listMetadata).toBeDefined();
|
|
247
|
-
});
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Shared test helper: asserts that a paginated list call returns empty data
|
|
252
|
-
* when the server responds with an empty list.
|
|
253
|
-
*/
|
|
254
|
-
export function testEmptyResults(fn: () => Promise<any>) {
|
|
255
|
-
it('handles empty results', async () => {
|
|
256
|
-
fetchOnce({ data: [], list_metadata: { before: null, after: null } });
|
|
257
|
-
const { data } = await fn();
|
|
258
|
-
expect(data).toEqual([]);
|
|
259
|
-
});
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Shared test helper: asserts that pagination params are forwarded correctly.
|
|
264
|
-
*/
|
|
265
|
-
export function testPaginationParams(fn: (opts: any) => Promise<any>, fixture: any) {
|
|
266
|
-
it('forwards pagination params', async () => {
|
|
267
|
-
fetchOnce(fixture);
|
|
268
|
-
await fn({ limit: 10, after: 'cursor_abc' });
|
|
269
|
-
expect(fetchSearchParams()['limit']).toBe('10');
|
|
270
|
-
expect(fetchSearchParams()['after']).toBe('cursor_abc');
|
|
271
|
-
});
|
|
272
|
-
}`;
|
|
273
|
-
}
|
package/src/node/config.ts
DELETED
|
@@ -1,71 +0,0 @@
|
|
|
1
|
-
import type { GeneratedFile } from '@workos/oagen';
|
|
2
|
-
|
|
3
|
-
export function generateConfig(): GeneratedFile[] {
|
|
4
|
-
return [
|
|
5
|
-
{
|
|
6
|
-
path: 'src/common/interfaces/workos-options.interface.ts',
|
|
7
|
-
content: `export interface WorkOSOptions {
|
|
8
|
-
apiKey?: string;
|
|
9
|
-
apiHostname?: string;
|
|
10
|
-
https?: boolean;
|
|
11
|
-
port?: number;
|
|
12
|
-
config?: RequestInit;
|
|
13
|
-
fetchFn?: typeof fetch;
|
|
14
|
-
clientId?: string;
|
|
15
|
-
timeout?: number;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export interface AppInfo {
|
|
19
|
-
name: string;
|
|
20
|
-
version?: string;
|
|
21
|
-
}`,
|
|
22
|
-
skipIfExists: true,
|
|
23
|
-
integrateTarget: false,
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
path: 'src/common/interfaces/post-options.interface.ts',
|
|
27
|
-
content: `export interface PostOptions {
|
|
28
|
-
query?: Record<string, any>;
|
|
29
|
-
idempotencyKey?: string;
|
|
30
|
-
warrantToken?: string;
|
|
31
|
-
skipApiKeyCheck?: boolean;
|
|
32
|
-
encoding?: 'json' | 'form-data' | 'form-urlencoded' | 'binary' | 'text';
|
|
33
|
-
}`,
|
|
34
|
-
skipIfExists: true,
|
|
35
|
-
integrateTarget: false,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
path: 'src/common/interfaces/get-options.interface.ts',
|
|
39
|
-
content: `export interface GetOptions {
|
|
40
|
-
query?: Record<string, any>;
|
|
41
|
-
accessToken?: string;
|
|
42
|
-
warrantToken?: string;
|
|
43
|
-
skipApiKeyCheck?: boolean;
|
|
44
|
-
}`,
|
|
45
|
-
skipIfExists: true,
|
|
46
|
-
integrateTarget: false,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
path: 'src/common/interfaces/pagination-options.interface.ts',
|
|
50
|
-
content: `export interface PaginationOptions {
|
|
51
|
-
limit?: number;
|
|
52
|
-
before?: string | null;
|
|
53
|
-
after?: string | null;
|
|
54
|
-
order?: 'asc' | 'desc';
|
|
55
|
-
}`,
|
|
56
|
-
skipIfExists: true,
|
|
57
|
-
integrateTarget: false,
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
path: 'src/common/interfaces/request-exception.interface.ts',
|
|
61
|
-
content: `export interface RequestException {
|
|
62
|
-
readonly status: number;
|
|
63
|
-
readonly name: string;
|
|
64
|
-
readonly requestID: string;
|
|
65
|
-
readonly code?: string;
|
|
66
|
-
}`,
|
|
67
|
-
skipIfExists: true,
|
|
68
|
-
integrateTarget: false,
|
|
69
|
-
},
|
|
70
|
-
];
|
|
71
|
-
}
|