@workos/oagen 0.0.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/.claude-plugin/plugin.json +12 -0
- package/LICENSE.txt +21 -0
- package/README.md +197 -0
- package/dist/chunk-CIX5NOBF.js +263 -0
- package/dist/chunk-IJ5E5AE7.js +1040 -0
- package/dist/chunk-P5XUDUGY.js +1748 -0
- package/dist/cli/index.js +5603 -0
- package/dist/compat/index.d.ts +195 -0
- package/dist/compat/index.js +4453 -0
- package/dist/index.d.ts +300 -0
- package/dist/index.js +1733 -0
- package/dist/scripts/smoke/baseline.js +1751 -0
- package/dist/scripts/smoke/sdk-test.js +93 -0
- package/dist/smoke.js +1963 -0
- package/dist/types-B5t9O9Wm.d.ts +367 -0
- package/dist/verify/index.d.ts +76 -0
- package/dist/verify/index.js +336 -0
- package/docs/advanced/index.md +11 -0
- package/docs/advanced/oagen-ignore.md +144 -0
- package/docs/agents/architecture.md +53 -0
- package/docs/agents/emitters.md +69 -0
- package/docs/agents/testing.md +60 -0
- package/docs/architecture/dependency-layers.md +75 -0
- package/docs/architecture/emitter-contract.md +160 -0
- package/docs/architecture/extractor-contract.md +227 -0
- package/docs/architecture/ir-types.md +275 -0
- package/docs/architecture/non-additive-changes.md +76 -0
- package/docs/architecture/pipeline.md +110 -0
- package/docs/architecture/workflows.md +215 -0
- package/docs/cli.md +156 -0
- package/docs/contributor/index.md +10 -0
- package/docs/contributor/public-api.md +59 -0
- package/docs/contributor/versioning.md +48 -0
- package/docs/core/index.md +12 -0
- package/docs/core/minimal-emitter.md +134 -0
- package/docs/core/quickstart.md +142 -0
- package/docs/diagrams/generate-sdk-workflow.md +50 -0
- package/docs/index.md +40 -0
- package/examples/reference-emitter/README.md +41 -0
- package/examples/reference-emitter/oagen.config.ts +7 -0
- package/examples/reference-emitter/spec/github-subset.yml +400 -0
- package/examples/reference-emitter/src/emitter.ts +52 -0
- package/examples/reference-emitter/src/generators/client.ts +37 -0
- package/examples/reference-emitter/src/generators/config.ts +19 -0
- package/examples/reference-emitter/src/generators/enums.ts +16 -0
- package/examples/reference-emitter/src/generators/errors.ts +39 -0
- package/examples/reference-emitter/src/generators/models.ts +29 -0
- package/examples/reference-emitter/src/generators/resources.ts +60 -0
- package/examples/reference-emitter/src/generators/tests.ts +7 -0
- package/examples/reference-emitter/src/index.ts +1 -0
- package/examples/reference-emitter/src/naming.ts +21 -0
- package/examples/reference-emitter/src/type-mapper.ts +39 -0
- package/examples/reference-emitter/tsconfig.json +19 -0
- package/package.json +102 -0
- package/skills/generate-emitter/SKILL.md +393 -0
- package/skills/generate-emitter/references/common-pitfalls.md +23 -0
- package/skills/generate-emitter/references/generator-guide.md +201 -0
- package/skills/generate-emitter/references/project-scaffold.md +138 -0
- package/skills/generate-extractor/SKILL.md +200 -0
- package/skills/generate-sdk/SKILL.md +155 -0
- package/skills/generate-smoke-test/SKILL.md +185 -0
- package/skills/integrate/SKILL.md +108 -0
- package/skills/verify-compat/SKILL.md +151 -0
- package/skills/verify-smoke-test/SKILL.md +176 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# Reference Emitter
|
|
2
|
+
|
|
3
|
+
A minimal but working TypeScript emitter for oagen, demonstrating the Emitter interface against a GitHub-flavored fixture spec.
|
|
4
|
+
|
|
5
|
+
## What it covers
|
|
6
|
+
|
|
7
|
+
- `generateModels` — TypeScript interfaces from IR models
|
|
8
|
+
- `generateEnums` — string literal unions from IR enums
|
|
9
|
+
- `generateResources` — resource classes using `planOperation`
|
|
10
|
+
- `generateClient` — top-level client with resource accessors
|
|
11
|
+
- `generateErrors` — error class hierarchy
|
|
12
|
+
- `generateConfig` — config types and base resource class
|
|
13
|
+
- Type mapping via `mapTypeRef`
|
|
14
|
+
|
|
15
|
+
## What it does NOT cover
|
|
16
|
+
|
|
17
|
+
Production emitters typically also handle:
|
|
18
|
+
|
|
19
|
+
- Overlay resolution for compat-aware naming
|
|
20
|
+
- Merge modes for incremental generation
|
|
21
|
+
- HTTP client implementation (fetch, axios, etc.)
|
|
22
|
+
- Pagination iterators
|
|
23
|
+
- Request/response serialization
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
From the oagen root:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx vitest run test/examples/reference-emitter/
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Or to generate output manually:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx tsx src/cli/index.ts generate \
|
|
37
|
+
--lang typescript \
|
|
38
|
+
--spec examples/reference-emitter/spec/github-subset.yml \
|
|
39
|
+
--namespace GitHub \
|
|
40
|
+
--output /tmp/github-sdk
|
|
41
|
+
```
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
openapi: 3.0.3
|
|
2
|
+
info:
|
|
3
|
+
title: GitHub-Flavored Subset API
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: >
|
|
6
|
+
A small GitHub-flavored OpenAPI spec for testing oagen's reference emitter.
|
|
7
|
+
Exercises: cursor pagination, bearer auth, models, enums, CRUD, nullable fields,
|
|
8
|
+
error responses, and generic envelope patterns.
|
|
9
|
+
|
|
10
|
+
servers:
|
|
11
|
+
- url: https://api.github-example.com/v1
|
|
12
|
+
|
|
13
|
+
security:
|
|
14
|
+
- bearerAuth: []
|
|
15
|
+
|
|
16
|
+
components:
|
|
17
|
+
securitySchemes:
|
|
18
|
+
bearerAuth:
|
|
19
|
+
type: http
|
|
20
|
+
scheme: bearer
|
|
21
|
+
|
|
22
|
+
schemas:
|
|
23
|
+
PaginationMeta:
|
|
24
|
+
type: object
|
|
25
|
+
properties:
|
|
26
|
+
next_cursor:
|
|
27
|
+
type: string
|
|
28
|
+
nullable: true
|
|
29
|
+
has_more:
|
|
30
|
+
type: boolean
|
|
31
|
+
|
|
32
|
+
Visibility:
|
|
33
|
+
type: string
|
|
34
|
+
enum: [public, private, internal]
|
|
35
|
+
|
|
36
|
+
Repository:
|
|
37
|
+
type: object
|
|
38
|
+
required: [id, name, full_name, visibility]
|
|
39
|
+
properties:
|
|
40
|
+
id:
|
|
41
|
+
type: integer
|
|
42
|
+
name:
|
|
43
|
+
type: string
|
|
44
|
+
full_name:
|
|
45
|
+
type: string
|
|
46
|
+
description:
|
|
47
|
+
type: string
|
|
48
|
+
nullable: true
|
|
49
|
+
visibility:
|
|
50
|
+
$ref: '#/components/schemas/Visibility'
|
|
51
|
+
owner:
|
|
52
|
+
$ref: '#/components/schemas/User'
|
|
53
|
+
|
|
54
|
+
Issue:
|
|
55
|
+
type: object
|
|
56
|
+
required: [id, title, state]
|
|
57
|
+
properties:
|
|
58
|
+
id:
|
|
59
|
+
type: integer
|
|
60
|
+
title:
|
|
61
|
+
type: string
|
|
62
|
+
body:
|
|
63
|
+
type: string
|
|
64
|
+
nullable: true
|
|
65
|
+
state:
|
|
66
|
+
type: string
|
|
67
|
+
enum: [open, closed]
|
|
68
|
+
labels:
|
|
69
|
+
type: array
|
|
70
|
+
items:
|
|
71
|
+
$ref: '#/components/schemas/Label'
|
|
72
|
+
assignee:
|
|
73
|
+
$ref: '#/components/schemas/User'
|
|
74
|
+
nullable: true
|
|
75
|
+
|
|
76
|
+
Label:
|
|
77
|
+
type: object
|
|
78
|
+
required: [id, name, color]
|
|
79
|
+
properties:
|
|
80
|
+
id:
|
|
81
|
+
type: integer
|
|
82
|
+
name:
|
|
83
|
+
type: string
|
|
84
|
+
color:
|
|
85
|
+
type: string
|
|
86
|
+
description:
|
|
87
|
+
type: string
|
|
88
|
+
nullable: true
|
|
89
|
+
|
|
90
|
+
User:
|
|
91
|
+
type: object
|
|
92
|
+
required: [id, login]
|
|
93
|
+
properties:
|
|
94
|
+
id:
|
|
95
|
+
type: integer
|
|
96
|
+
login:
|
|
97
|
+
type: string
|
|
98
|
+
name:
|
|
99
|
+
type: string
|
|
100
|
+
nullable: true
|
|
101
|
+
email:
|
|
102
|
+
type: string
|
|
103
|
+
nullable: true
|
|
104
|
+
|
|
105
|
+
ApiError:
|
|
106
|
+
type: object
|
|
107
|
+
required: [message]
|
|
108
|
+
properties:
|
|
109
|
+
message:
|
|
110
|
+
type: string
|
|
111
|
+
documentation_url:
|
|
112
|
+
type: string
|
|
113
|
+
|
|
114
|
+
paths:
|
|
115
|
+
/repos:
|
|
116
|
+
get:
|
|
117
|
+
operationId: listRepos
|
|
118
|
+
tags: [Repos]
|
|
119
|
+
summary: List repositories
|
|
120
|
+
parameters:
|
|
121
|
+
- name: after
|
|
122
|
+
in: query
|
|
123
|
+
schema:
|
|
124
|
+
type: string
|
|
125
|
+
- name: limit
|
|
126
|
+
in: query
|
|
127
|
+
schema:
|
|
128
|
+
type: integer
|
|
129
|
+
default: 30
|
|
130
|
+
- name: visibility
|
|
131
|
+
in: query
|
|
132
|
+
schema:
|
|
133
|
+
$ref: '#/components/schemas/Visibility'
|
|
134
|
+
responses:
|
|
135
|
+
'200':
|
|
136
|
+
description: A paginated list of repositories
|
|
137
|
+
content:
|
|
138
|
+
application/json:
|
|
139
|
+
schema:
|
|
140
|
+
type: object
|
|
141
|
+
properties:
|
|
142
|
+
results:
|
|
143
|
+
type: array
|
|
144
|
+
items:
|
|
145
|
+
$ref: '#/components/schemas/Repository'
|
|
146
|
+
meta:
|
|
147
|
+
$ref: '#/components/schemas/PaginationMeta'
|
|
148
|
+
'401':
|
|
149
|
+
description: Unauthorized
|
|
150
|
+
content:
|
|
151
|
+
application/json:
|
|
152
|
+
schema:
|
|
153
|
+
$ref: '#/components/schemas/ApiError'
|
|
154
|
+
|
|
155
|
+
/repos/{owner}/{repo}:
|
|
156
|
+
get:
|
|
157
|
+
operationId: getRepo
|
|
158
|
+
tags: [Repos]
|
|
159
|
+
summary: Get a repository
|
|
160
|
+
parameters:
|
|
161
|
+
- name: owner
|
|
162
|
+
in: path
|
|
163
|
+
required: true
|
|
164
|
+
schema:
|
|
165
|
+
type: string
|
|
166
|
+
- name: repo
|
|
167
|
+
in: path
|
|
168
|
+
required: true
|
|
169
|
+
schema:
|
|
170
|
+
type: string
|
|
171
|
+
responses:
|
|
172
|
+
'200':
|
|
173
|
+
description: Repository details
|
|
174
|
+
content:
|
|
175
|
+
application/json:
|
|
176
|
+
schema:
|
|
177
|
+
$ref: '#/components/schemas/Repository'
|
|
178
|
+
'404':
|
|
179
|
+
description: Not found
|
|
180
|
+
content:
|
|
181
|
+
application/json:
|
|
182
|
+
schema:
|
|
183
|
+
$ref: '#/components/schemas/ApiError'
|
|
184
|
+
|
|
185
|
+
/repos/{owner}/{repo}/issues:
|
|
186
|
+
get:
|
|
187
|
+
operationId: listIssues
|
|
188
|
+
tags: [Issues]
|
|
189
|
+
summary: List issues
|
|
190
|
+
parameters:
|
|
191
|
+
- name: owner
|
|
192
|
+
in: path
|
|
193
|
+
required: true
|
|
194
|
+
schema:
|
|
195
|
+
type: string
|
|
196
|
+
- name: repo
|
|
197
|
+
in: path
|
|
198
|
+
required: true
|
|
199
|
+
schema:
|
|
200
|
+
type: string
|
|
201
|
+
- name: after
|
|
202
|
+
in: query
|
|
203
|
+
schema:
|
|
204
|
+
type: string
|
|
205
|
+
- name: limit
|
|
206
|
+
in: query
|
|
207
|
+
schema:
|
|
208
|
+
type: integer
|
|
209
|
+
default: 30
|
|
210
|
+
- name: state
|
|
211
|
+
in: query
|
|
212
|
+
schema:
|
|
213
|
+
type: string
|
|
214
|
+
enum: [open, closed, all]
|
|
215
|
+
responses:
|
|
216
|
+
'200':
|
|
217
|
+
description: A paginated list of issues
|
|
218
|
+
content:
|
|
219
|
+
application/json:
|
|
220
|
+
schema:
|
|
221
|
+
type: object
|
|
222
|
+
properties:
|
|
223
|
+
results:
|
|
224
|
+
type: array
|
|
225
|
+
items:
|
|
226
|
+
$ref: '#/components/schemas/Issue'
|
|
227
|
+
meta:
|
|
228
|
+
$ref: '#/components/schemas/PaginationMeta'
|
|
229
|
+
|
|
230
|
+
post:
|
|
231
|
+
operationId: createIssue
|
|
232
|
+
tags: [Issues]
|
|
233
|
+
summary: Create an issue
|
|
234
|
+
parameters:
|
|
235
|
+
- name: owner
|
|
236
|
+
in: path
|
|
237
|
+
required: true
|
|
238
|
+
schema:
|
|
239
|
+
type: string
|
|
240
|
+
- name: repo
|
|
241
|
+
in: path
|
|
242
|
+
required: true
|
|
243
|
+
schema:
|
|
244
|
+
type: string
|
|
245
|
+
requestBody:
|
|
246
|
+
required: true
|
|
247
|
+
content:
|
|
248
|
+
application/json:
|
|
249
|
+
schema:
|
|
250
|
+
type: object
|
|
251
|
+
required: [title]
|
|
252
|
+
properties:
|
|
253
|
+
title:
|
|
254
|
+
type: string
|
|
255
|
+
body:
|
|
256
|
+
type: string
|
|
257
|
+
labels:
|
|
258
|
+
type: array
|
|
259
|
+
items:
|
|
260
|
+
type: string
|
|
261
|
+
responses:
|
|
262
|
+
'201':
|
|
263
|
+
description: Created issue
|
|
264
|
+
content:
|
|
265
|
+
application/json:
|
|
266
|
+
schema:
|
|
267
|
+
$ref: '#/components/schemas/Issue'
|
|
268
|
+
'422':
|
|
269
|
+
description: Validation error
|
|
270
|
+
content:
|
|
271
|
+
application/json:
|
|
272
|
+
schema:
|
|
273
|
+
$ref: '#/components/schemas/ApiError'
|
|
274
|
+
|
|
275
|
+
/repos/{owner}/{repo}/labels:
|
|
276
|
+
get:
|
|
277
|
+
operationId: listLabels
|
|
278
|
+
tags: [Labels]
|
|
279
|
+
summary: List labels
|
|
280
|
+
parameters:
|
|
281
|
+
- name: owner
|
|
282
|
+
in: path
|
|
283
|
+
required: true
|
|
284
|
+
schema:
|
|
285
|
+
type: string
|
|
286
|
+
- name: repo
|
|
287
|
+
in: path
|
|
288
|
+
required: true
|
|
289
|
+
schema:
|
|
290
|
+
type: string
|
|
291
|
+
- name: page
|
|
292
|
+
in: query
|
|
293
|
+
schema:
|
|
294
|
+
type: integer
|
|
295
|
+
- name: per_page
|
|
296
|
+
in: query
|
|
297
|
+
schema:
|
|
298
|
+
type: integer
|
|
299
|
+
default: 30
|
|
300
|
+
responses:
|
|
301
|
+
'200':
|
|
302
|
+
description: A paginated list of labels
|
|
303
|
+
content:
|
|
304
|
+
application/json:
|
|
305
|
+
schema:
|
|
306
|
+
type: object
|
|
307
|
+
properties:
|
|
308
|
+
items:
|
|
309
|
+
type: array
|
|
310
|
+
items:
|
|
311
|
+
$ref: '#/components/schemas/Label'
|
|
312
|
+
total_count:
|
|
313
|
+
type: integer
|
|
314
|
+
|
|
315
|
+
post:
|
|
316
|
+
operationId: createLabel
|
|
317
|
+
tags: [Labels]
|
|
318
|
+
summary: Create a label
|
|
319
|
+
parameters:
|
|
320
|
+
- name: owner
|
|
321
|
+
in: path
|
|
322
|
+
required: true
|
|
323
|
+
schema:
|
|
324
|
+
type: string
|
|
325
|
+
- name: repo
|
|
326
|
+
in: path
|
|
327
|
+
required: true
|
|
328
|
+
schema:
|
|
329
|
+
type: string
|
|
330
|
+
requestBody:
|
|
331
|
+
required: true
|
|
332
|
+
content:
|
|
333
|
+
application/json:
|
|
334
|
+
schema:
|
|
335
|
+
type: object
|
|
336
|
+
required: [name, color]
|
|
337
|
+
properties:
|
|
338
|
+
name:
|
|
339
|
+
type: string
|
|
340
|
+
color:
|
|
341
|
+
type: string
|
|
342
|
+
description:
|
|
343
|
+
type: string
|
|
344
|
+
responses:
|
|
345
|
+
'201':
|
|
346
|
+
description: Created label
|
|
347
|
+
content:
|
|
348
|
+
application/json:
|
|
349
|
+
schema:
|
|
350
|
+
$ref: '#/components/schemas/Label'
|
|
351
|
+
|
|
352
|
+
/repos/{owner}/{repo}/labels/{label_id}:
|
|
353
|
+
delete:
|
|
354
|
+
operationId: deleteLabel
|
|
355
|
+
tags: [Labels]
|
|
356
|
+
summary: Delete a label
|
|
357
|
+
parameters:
|
|
358
|
+
- name: owner
|
|
359
|
+
in: path
|
|
360
|
+
required: true
|
|
361
|
+
schema:
|
|
362
|
+
type: string
|
|
363
|
+
- name: repo
|
|
364
|
+
in: path
|
|
365
|
+
required: true
|
|
366
|
+
schema:
|
|
367
|
+
type: string
|
|
368
|
+
- name: label_id
|
|
369
|
+
in: path
|
|
370
|
+
required: true
|
|
371
|
+
schema:
|
|
372
|
+
type: integer
|
|
373
|
+
responses:
|
|
374
|
+
'204':
|
|
375
|
+
description: Deleted
|
|
376
|
+
|
|
377
|
+
/users/{username}:
|
|
378
|
+
get:
|
|
379
|
+
operationId: getUser
|
|
380
|
+
tags: [Users]
|
|
381
|
+
summary: Get a user
|
|
382
|
+
parameters:
|
|
383
|
+
- name: username
|
|
384
|
+
in: path
|
|
385
|
+
required: true
|
|
386
|
+
schema:
|
|
387
|
+
type: string
|
|
388
|
+
responses:
|
|
389
|
+
'200':
|
|
390
|
+
description: User details
|
|
391
|
+
content:
|
|
392
|
+
application/json:
|
|
393
|
+
schema:
|
|
394
|
+
$ref: '#/components/schemas/User'
|
|
395
|
+
'404':
|
|
396
|
+
description: Not found
|
|
397
|
+
content:
|
|
398
|
+
application/json:
|
|
399
|
+
schema:
|
|
400
|
+
$ref: '#/components/schemas/ApiError'
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { Emitter, EmitterContext, GeneratedFile, ApiSpec, Model, Enum, Service } from '@workos/oagen';
|
|
2
|
+
import { generateModels } from './generators/models.js';
|
|
3
|
+
import { generateEnums } from './generators/enums.js';
|
|
4
|
+
import { generateResources } from './generators/resources.js';
|
|
5
|
+
import { generateClient } from './generators/client.js';
|
|
6
|
+
import { generateErrors } from './generators/errors.js';
|
|
7
|
+
import { generateConfig } from './generators/config.js';
|
|
8
|
+
import { generateTests } from './generators/tests.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Reference TypeScript emitter for oagen.
|
|
12
|
+
*
|
|
13
|
+
* This emitter demonstrates how to implement the Emitter interface by
|
|
14
|
+
* generating TypeScript interfaces, literal union types, resource classes,
|
|
15
|
+
* and a top-level client. It is intentionally simple — production emitters
|
|
16
|
+
* typically handle overlay resolution, compat-aware naming, and merge modes.
|
|
17
|
+
*/
|
|
18
|
+
export const typescriptEmitter: Emitter = {
|
|
19
|
+
language: 'typescript',
|
|
20
|
+
|
|
21
|
+
generateModels(models: Model[], ctx: EmitterContext): GeneratedFile[] {
|
|
22
|
+
return generateModels(models, ctx);
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
generateEnums(enums: Enum[], ctx: EmitterContext): GeneratedFile[] {
|
|
26
|
+
return generateEnums(enums, ctx);
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
generateResources(services: Service[], ctx: EmitterContext): GeneratedFile[] {
|
|
30
|
+
return generateResources(services, ctx);
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
generateClient(spec: ApiSpec, ctx: EmitterContext): GeneratedFile[] {
|
|
34
|
+
return generateClient(spec, ctx);
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
generateErrors(ctx: EmitterContext): GeneratedFile[] {
|
|
38
|
+
return generateErrors(ctx);
|
|
39
|
+
},
|
|
40
|
+
|
|
41
|
+
generateConfig(ctx: EmitterContext): GeneratedFile[] {
|
|
42
|
+
return generateConfig(ctx);
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
generateTests(spec: ApiSpec, ctx: EmitterContext): GeneratedFile[] {
|
|
46
|
+
return generateTests(spec, ctx);
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
fileHeader(): string {
|
|
50
|
+
return '// Auto-generated by oagen reference emitter. Do not edit.';
|
|
51
|
+
},
|
|
52
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { ApiSpec, GeneratedFile, EmitterContext } from '@workos/oagen';
|
|
2
|
+
import { tsClassName } from '../naming.js';
|
|
3
|
+
|
|
4
|
+
export function generateClient(spec: ApiSpec, ctx: EmitterContext): GeneratedFile[] {
|
|
5
|
+
const lines: string[] = [];
|
|
6
|
+
const className = `${ctx.namespacePascal}Client`;
|
|
7
|
+
|
|
8
|
+
// Import resources
|
|
9
|
+
for (const service of spec.services) {
|
|
10
|
+
const lower = service.name.toLowerCase();
|
|
11
|
+
lines.push(`import { ${tsClassName(service.name)} } from './resources/${lower}.js';`);
|
|
12
|
+
}
|
|
13
|
+
lines.push(`import type { ClientConfig } from './config.js';`);
|
|
14
|
+
lines.push('');
|
|
15
|
+
|
|
16
|
+
lines.push(`export class ${className} {`);
|
|
17
|
+
lines.push(` private readonly config: ClientConfig;`);
|
|
18
|
+
lines.push('');
|
|
19
|
+
|
|
20
|
+
// Resource accessors
|
|
21
|
+
for (const service of spec.services) {
|
|
22
|
+
const propName = service.name.charAt(0).toLowerCase() + service.name.slice(1);
|
|
23
|
+
lines.push(` readonly ${propName}: ${tsClassName(service.name)};`);
|
|
24
|
+
}
|
|
25
|
+
lines.push('');
|
|
26
|
+
|
|
27
|
+
lines.push(` constructor(config: ClientConfig) {`);
|
|
28
|
+
lines.push(` this.config = config;`);
|
|
29
|
+
for (const service of spec.services) {
|
|
30
|
+
const propName = service.name.charAt(0).toLowerCase() + service.name.slice(1);
|
|
31
|
+
lines.push(` this.${propName} = new ${tsClassName(service.name)}(this.config);`);
|
|
32
|
+
}
|
|
33
|
+
lines.push(` }`);
|
|
34
|
+
lines.push('}');
|
|
35
|
+
|
|
36
|
+
return [{ path: 'client.ts', content: lines.join('\n') }];
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { GeneratedFile, EmitterContext } from '@workos/oagen';
|
|
2
|
+
|
|
3
|
+
export function generateConfig(_ctx: EmitterContext): GeneratedFile[] {
|
|
4
|
+
const content = `export interface ClientConfig {
|
|
5
|
+
apiKey: string;
|
|
6
|
+
baseUrl?: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export abstract class BaseResource {
|
|
10
|
+
protected readonly config: ClientConfig;
|
|
11
|
+
|
|
12
|
+
constructor(config: ClientConfig) {
|
|
13
|
+
this.config = config;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
`;
|
|
17
|
+
|
|
18
|
+
return [{ path: 'config.ts', content }];
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Enum, GeneratedFile, EmitterContext } from '@workos/oagen';
|
|
2
|
+
import { tsTypeName } from '../naming.js';
|
|
3
|
+
|
|
4
|
+
export function generateEnums(enums: Enum[], _ctx: EmitterContext): GeneratedFile[] {
|
|
5
|
+
if (enums.length === 0) return [];
|
|
6
|
+
|
|
7
|
+
const lines: string[] = [];
|
|
8
|
+
|
|
9
|
+
for (const e of enums) {
|
|
10
|
+
const values = e.values.map((v) => JSON.stringify(v.value)).join(' | ');
|
|
11
|
+
lines.push(`export type ${tsTypeName(e.name)} = ${values};`);
|
|
12
|
+
lines.push('');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return [{ path: 'enums.ts', content: lines.join('\n') }];
|
|
16
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { GeneratedFile, EmitterContext } from '@workos/oagen';
|
|
2
|
+
|
|
3
|
+
export function generateErrors(_ctx: EmitterContext): GeneratedFile[] {
|
|
4
|
+
const content = `export class ApiError extends Error {
|
|
5
|
+
readonly statusCode: number;
|
|
6
|
+
readonly body: unknown;
|
|
7
|
+
|
|
8
|
+
constructor(message: string, statusCode: number, body?: unknown) {
|
|
9
|
+
super(message);
|
|
10
|
+
this.name = 'ApiError';
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
this.body = body;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export class NotFoundError extends ApiError {
|
|
17
|
+
constructor(message: string, body?: unknown) {
|
|
18
|
+
super(message, 404, body);
|
|
19
|
+
this.name = 'NotFoundError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export class UnauthorizedError extends ApiError {
|
|
24
|
+
constructor(message: string, body?: unknown) {
|
|
25
|
+
super(message, 401, body);
|
|
26
|
+
this.name = 'UnauthorizedError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export class UnprocessableEntityError extends ApiError {
|
|
31
|
+
constructor(message: string, body?: unknown) {
|
|
32
|
+
super(message, 422, body);
|
|
33
|
+
this.name = 'UnprocessableEntityError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
return [{ path: 'errors.ts', content }];
|
|
39
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Model, GeneratedFile, EmitterContext } from '@workos/oagen';
|
|
2
|
+
import { tsTypeName, tsPropertyName } from '../naming.js';
|
|
3
|
+
import { toTsType } from '../type-mapper.js';
|
|
4
|
+
|
|
5
|
+
export function generateModels(models: Model[], _ctx: EmitterContext): GeneratedFile[] {
|
|
6
|
+
if (models.length === 0) return [];
|
|
7
|
+
|
|
8
|
+
const lines: string[] = [];
|
|
9
|
+
|
|
10
|
+
for (const model of models) {
|
|
11
|
+
if (model.description) {
|
|
12
|
+
lines.push(`/** ${model.description} */`);
|
|
13
|
+
}
|
|
14
|
+
lines.push(`export interface ${tsTypeName(model.name)} {`);
|
|
15
|
+
for (const field of model.fields) {
|
|
16
|
+
const tsName = tsPropertyName(field.name);
|
|
17
|
+
const tsType = toTsType(field.type);
|
|
18
|
+
const optional = field.required ? '' : '?';
|
|
19
|
+
if (field.description) {
|
|
20
|
+
lines.push(` /** ${field.description} */`);
|
|
21
|
+
}
|
|
22
|
+
lines.push(` ${tsName}${optional}: ${tsType};`);
|
|
23
|
+
}
|
|
24
|
+
lines.push('}');
|
|
25
|
+
lines.push('');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return [{ path: 'models.ts', content: lines.join('\n') }];
|
|
29
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Service, GeneratedFile, EmitterContext } from '@workos/oagen';
|
|
2
|
+
import { planOperation } from '@workos/oagen';
|
|
3
|
+
import { tsClassName, tsMethodName } from '../naming.js';
|
|
4
|
+
import { toTsType } from '../type-mapper.js';
|
|
5
|
+
|
|
6
|
+
export function generateResources(services: Service[], _ctx: EmitterContext): GeneratedFile[] {
|
|
7
|
+
const files: GeneratedFile[] = [];
|
|
8
|
+
|
|
9
|
+
for (const service of services) {
|
|
10
|
+
const lines: string[] = [];
|
|
11
|
+
const className = tsClassName(service.name);
|
|
12
|
+
|
|
13
|
+
lines.push(`import { BaseResource } from './config.js';`);
|
|
14
|
+
lines.push('');
|
|
15
|
+
lines.push(`export class ${className} extends BaseResource {`);
|
|
16
|
+
|
|
17
|
+
for (const op of service.operations) {
|
|
18
|
+
const plan = planOperation(op);
|
|
19
|
+
const method = tsMethodName(op.name);
|
|
20
|
+
const returnType = plan.isDelete ? 'void' : toTsType(op.response);
|
|
21
|
+
const params: string[] = [];
|
|
22
|
+
|
|
23
|
+
for (const p of op.pathParams) {
|
|
24
|
+
params.push(`${tsMethodName(p.name)}: ${toTsType(p.type)}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (op.requestBody) {
|
|
28
|
+
params.push(`body: ${toTsType(op.requestBody)}`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (op.queryParams.length > 0) {
|
|
32
|
+
const queryFields = op.queryParams
|
|
33
|
+
.map((p) => `${tsMethodName(p.name)}${p.required ? '' : '?'}: ${toTsType(p.type)}`)
|
|
34
|
+
.join('; ');
|
|
35
|
+
params.push(`options?: { ${queryFields} }`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const paramStr = params.join(', ');
|
|
39
|
+
const asyncPrefix = plan.isAsync ? 'async ' : '';
|
|
40
|
+
|
|
41
|
+
if (op.description) {
|
|
42
|
+
lines.push(` /** ${op.description} */`);
|
|
43
|
+
}
|
|
44
|
+
lines.push(` ${asyncPrefix}${method}(${paramStr}): Promise<${returnType}> {`);
|
|
45
|
+
lines.push(` // Production emitters generate real HTTP calls here`);
|
|
46
|
+
lines.push(` throw new Error('Not implemented');`);
|
|
47
|
+
lines.push(` }`);
|
|
48
|
+
lines.push('');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
lines.push('}');
|
|
52
|
+
|
|
53
|
+
files.push({
|
|
54
|
+
path: `resources/${service.name.toLowerCase()}.ts`,
|
|
55
|
+
content: lines.join('\n'),
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return files;
|
|
60
|
+
}
|