@nxgt/typespec 0.3.0 → 0.4.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/README.md +41 -51
- package/dist/index.d.ts +1 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -20
- package/dist/index.js.map +5 -5
- package/dist/lib.d.ts +1 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/operation-ids.d.ts +15 -18
- package/dist/operation-ids.d.ts.map +1 -1
- package/docs/README.md +2 -2
- package/docs/guide/auth.md +3 -3
- package/docs/guide/columns.md +1 -1
- package/docs/guide/errors.md +3 -3
- package/docs/guide/headers.md +2 -2
- package/docs/guide/operation-ids.md +154 -72
- package/docs/guide/pagination.md +3 -3
- package/docs/guide/sorting.md +131 -0
- package/docs/roadmap.md +17 -3
- package/docs/troubleshooting.md +44 -29
- package/lib/main.tsp +1 -1
- package/lib/operation-ids.tsp +13 -16
- package/lib/sorting.tsp +20 -0
- package/package.json +2 -2
- package/docs/guide/resources.md +0 -157
- package/lib/resources.tsp +0 -68
package/README.md
CHANGED
|
@@ -89,8 +89,8 @@ namespace Blog;
|
|
|
89
89
|
|
|
90
90
|
@route("/posts")
|
|
91
91
|
interface Posts {
|
|
92
|
-
@get
|
|
93
|
-
@post
|
|
92
|
+
@get getPost(@path postId: string): Post | NotFound | Unauthorized;
|
|
93
|
+
@post createPost(@body post: Create<Post>): {
|
|
94
94
|
@statusCode _: 201;
|
|
95
95
|
@body post: Post;
|
|
96
96
|
} | BadRequest | Conflict | ErrorResponse<503>;
|
|
@@ -139,12 +139,12 @@ page:
|
|
|
139
139
|
```tsp
|
|
140
140
|
@route("/posts")
|
|
141
141
|
interface Posts {
|
|
142
|
-
@get
|
|
142
|
+
@get listPosts(@query status?: PostStatus, ...PageParameters): Page<Post>;
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
@route("/posts/{postId}/comments")
|
|
146
146
|
interface Comments {
|
|
147
|
-
@get
|
|
147
|
+
@get findPostComments(@path postId: string, ...CursorPageParameters): CursorPage<Comment> | BadRequest | NotFound;
|
|
148
148
|
}
|
|
149
149
|
```
|
|
150
150
|
|
|
@@ -191,7 +191,7 @@ beside the success:
|
|
|
191
191
|
@route("/posts/{postId}/comments/{commentId}")
|
|
192
192
|
interface Comments {
|
|
193
193
|
@useAuth(JanusAuth)
|
|
194
|
-
@delete
|
|
194
|
+
@delete deletePostComment(@path postId: string, @path commentId: string):
|
|
195
195
|
NoContentResponse | AuthenticationRequired | AccessDenied | ErrorWithoutBody<404>;
|
|
196
196
|
}
|
|
197
197
|
```
|
|
@@ -254,7 +254,7 @@ Spread the headers of an idempotent write and of a rate limit into an
|
|
|
254
254
|
operation and its replies:
|
|
255
255
|
|
|
256
256
|
```tsp
|
|
257
|
-
@post
|
|
257
|
+
@post createPost(...IdempotencyKeyHeader, @body post: Create<Post>): {
|
|
258
258
|
@statusCode _: 201;
|
|
259
259
|
...IdempotentReplayedHeader;
|
|
260
260
|
...RateLimitHeaders;
|
|
@@ -275,61 +275,52 @@ operation and its replies:
|
|
|
275
275
|
generator checks the request's `Idempotency-Key`; a reply's headers are
|
|
276
276
|
documented, not checked. More in [Headers](docs/guide/headers.md).
|
|
277
277
|
|
|
278
|
-
###
|
|
278
|
+
### Sorting
|
|
279
279
|
|
|
280
|
-
|
|
280
|
+
Spread the sort into a list, beside its filters and its page:
|
|
281
281
|
|
|
282
282
|
```tsp
|
|
283
|
-
model
|
|
284
|
-
@query
|
|
285
|
-
@query authorId?: uuid;
|
|
283
|
+
model AuthorFilters {
|
|
284
|
+
@query name?: string;
|
|
286
285
|
}
|
|
287
286
|
|
|
288
|
-
@route("/
|
|
289
|
-
interface
|
|
287
|
+
@route("/authors")
|
|
288
|
+
interface Authors {
|
|
289
|
+
@get listAuthors(
|
|
290
|
+
...AuthorFilters,
|
|
291
|
+
...PageParameters,
|
|
292
|
+
...SortParameters<"name" | "createdAt">,
|
|
293
|
+
): Page<Author>;
|
|
294
|
+
}
|
|
290
295
|
```
|
|
291
296
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
| `readPost` | `GET /posts/{id}` | `Post \| NotFound` |
|
|
296
|
-
| `createPost` | `POST /posts`, `Create<Post>` | 201 `Post \| BadRequest \| Conflict` |
|
|
297
|
-
| `updatePost` | `PATCH /posts/{id}`, `MergePatchUpdate<Post>` | `Post \| BadRequest \| NotFound \| Conflict` |
|
|
298
|
-
| `deletePost` | `DELETE /posts/{id}` | 204, `NotFound` |
|
|
299
|
-
|
|
300
|
-
`list` is named after the interface, the four others after the item. Two resources of one item then share their ids,
|
|
301
|
-
which is an error, `duplicate-operation-id`: give the second its own
|
|
302
|
-
`@operationId`s ([troubleshooting](docs/troubleshooting.md)).
|
|
303
|
-
|
|
304
|
-
`orderBy` takes one of the `SortField`s (`SortField` is `"id"` by default), and `direction`
|
|
305
|
-
`asc` (the default) or `desc`, as `@nxgt/drizzle` sorts a page. `{id}`
|
|
306
|
-
is a `uuid`, or the `Id` you name. More in
|
|
307
|
-
[Resources](docs/guide/resources.md).
|
|
297
|
+
`orderBy` takes one of the fields named, and `direction` `asc` (the default)
|
|
298
|
+
or `desc`, as `@nxgt/drizzle` sorts a page: `?orderBy=email` is a 400. More,
|
|
299
|
+
with the handler, in [Sorting](docs/guide/sorting.md).
|
|
308
300
|
|
|
309
301
|
### Operation ids
|
|
310
302
|
|
|
311
|
-
TypeSpec names an operation after its interface, `
|
|
303
|
+
TypeSpec names an operation after its interface, `Posts_listPosts`, and the
|
|
312
304
|
generator turns that id into the client's method and its types' prefix. Put
|
|
313
|
-
`@operationIds` on the
|
|
314
|
-
|
|
305
|
+
`@operationIds` on the service namespace, and each operation's id is its
|
|
306
|
+
name, as written:
|
|
315
307
|
|
|
316
308
|
```tsp
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
309
|
+
@service(#{ title: "Blog" })
|
|
310
|
+
@operationIds
|
|
311
|
+
namespace Blog;
|
|
320
312
|
|
|
321
313
|
@route("/posts")
|
|
322
|
-
@operationIds
|
|
323
314
|
interface Posts {
|
|
324
|
-
@get
|
|
325
|
-
@get
|
|
315
|
+
@get listPosts(...PageParameters): Page<Post>; // listPosts
|
|
316
|
+
@get getPost(@path postId: uuid): Post | NotFound; // getPost
|
|
326
317
|
}
|
|
327
318
|
```
|
|
328
319
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
`duplicate-operation-id`. More in
|
|
320
|
+
On an interface, it names that interface's operations, and those of the
|
|
321
|
+
interfaces extending it. An operation's own `@operationId` wins; the others
|
|
322
|
+
keep the emitter's names. Two operations of one name in one service, in two
|
|
323
|
+
interfaces or copied by `extends`, are an error, `duplicate-operation-id`. More in
|
|
333
324
|
[Operation ids](docs/guide/operation-ids.md).
|
|
334
325
|
|
|
335
326
|
## API
|
|
@@ -338,13 +329,13 @@ of a marked interface that has another operation's id is an error,
|
|
|
338
329
|
|
|
339
330
|
| Decorator | On | What it does |
|
|
340
331
|
| --- | --- | --- |
|
|
341
|
-
| `@operationIds` | an interface |
|
|
332
|
+
| `@operationIds` | a namespace or an interface | makes each operation's id its name, as written, in the namespace however deep or in the interface, unless it has an `@operationId` |
|
|
342
333
|
|
|
343
334
|
### Diagnostics
|
|
344
335
|
|
|
345
336
|
| Code | Reported when |
|
|
346
337
|
| --- | --- |
|
|
347
|
-
| `duplicate-operation-id` | error: an operation
|
|
338
|
+
| `duplicate-operation-id` | error: an operation `@operationIds` names has the id of another operation in its service's document |
|
|
348
339
|
| `duplicate-status-reply` | error: an operation declares a reply without a body and one with a body of one status, which the emitter merges, losing the one without a body |
|
|
349
340
|
| `merged-status-reply` | warning: an operation declares two replies with a body of one status code, which the emitter merges under the first one's description |
|
|
350
341
|
|
|
@@ -390,11 +381,10 @@ of a marked interface that has another operation's id is an error,
|
|
|
390
381
|
| `RateLimitHeaders` | `RateLimit-Limit`, `RateLimit-Remaining`, `RateLimit-Reset`, integers, optional |
|
|
391
382
|
| `RetryAfterHeader` | `Retry-After`, seconds, optional |
|
|
392
383
|
|
|
393
|
-
###
|
|
384
|
+
### Sorting
|
|
394
385
|
|
|
395
|
-
|
|
|
386
|
+
| Model | What it is |
|
|
396
387
|
| --- | --- |
|
|
397
|
-
| `Resource<Item, Filters = {}, SortField = "id", Id = uuid>` | an interface template: `list`, `read`, `create`, `update`, `delete` |
|
|
398
388
|
| `SortParameters<Field>` | the query `orderBy?: Field` and `direction?: "asc" \| "desc" = "asc"` |
|
|
399
389
|
|
|
400
390
|
### Responses
|
|
@@ -461,10 +451,10 @@ envelope and the rate limit's headers.
|
|
|
461
451
|
columns `@nxgt/drizzle` stamps;
|
|
462
452
|
- [Headers](docs/guide/headers.md): `Idempotency-Key`, the rate limit and
|
|
463
453
|
`Retry-After`;
|
|
464
|
-
- [
|
|
465
|
-
|
|
466
|
-
- [Operation ids](docs/guide/operation-ids.md): `@operationIds`,
|
|
467
|
-
|
|
454
|
+
- [Sorting](docs/guide/sorting.md): `orderBy` and `direction` beside a
|
|
455
|
+
list's filters, and the `@nxgt/drizzle` call they map to;
|
|
456
|
+
- [Operation ids](docs/guide/operation-ids.md): `@operationIds`, each
|
|
457
|
+
operation named as written, and the ids it refuses;
|
|
468
458
|
- [troubleshooting](docs/troubleshooting.md);
|
|
469
459
|
- [the roadmap](docs/roadmap.md): what is coming.
|
|
470
460
|
|
package/dist/index.d.ts
CHANGED
|
@@ -7,15 +7,12 @@
|
|
|
7
7
|
* export would declare it a second time, in the global namespace.
|
|
8
8
|
*/
|
|
9
9
|
import type { Program } from '@typespec/compiler';
|
|
10
|
-
import {
|
|
10
|
+
import { operationIds } from './operation-ids';
|
|
11
11
|
export { $lib } from './lib';
|
|
12
12
|
export declare function $onValidate(program: Program): void;
|
|
13
13
|
export declare const $decorators: {
|
|
14
14
|
Nxgt: {
|
|
15
15
|
operationIds: typeof operationIds;
|
|
16
16
|
};
|
|
17
|
-
'Nxgt.Private': {
|
|
18
|
-
itemOperation: typeof itemOperation;
|
|
19
|
-
};
|
|
20
17
|
};
|
|
21
18
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAElD,OAAO,EAAE,YAAY,EAAwB,MAAM,iBAAiB,CAAC;AAErE,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAE7B,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAGlD;AAED,eAAO,MAAM,WAAW;;;;CAEvB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -29,9 +29,8 @@ var $lib = createTypeSpecLibrary({
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
state: {
|
|
32
|
-
operationIds: {
|
|
33
|
-
|
|
34
|
-
description: "The operations named after an item, and the item's model"
|
|
32
|
+
operationIds: {
|
|
33
|
+
description: "The interfaces and namespaces marked with @operationIds"
|
|
35
34
|
}
|
|
36
35
|
}
|
|
37
36
|
});
|
|
@@ -87,8 +86,9 @@ function validateOneReplyPerStatus(program) {
|
|
|
87
86
|
|
|
88
87
|
// src/operation-ids.ts
|
|
89
88
|
import {
|
|
90
|
-
|
|
89
|
+
isService,
|
|
91
90
|
isTemplateInstance,
|
|
91
|
+
listServices,
|
|
92
92
|
navigateProgram
|
|
93
93
|
} from "@typespec/compiler";
|
|
94
94
|
import {
|
|
@@ -99,36 +99,64 @@ import {
|
|
|
99
99
|
function operationIds(context, target) {
|
|
100
100
|
context.program.stateSet($lib.stateKeys.operationIds).add(target);
|
|
101
101
|
}
|
|
102
|
-
function
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
return named ?? operation.interface?.name;
|
|
102
|
+
function isMarkedNamespace(program, target) {
|
|
103
|
+
for (let at = target;at !== undefined; at = at.namespace) {
|
|
104
|
+
if (program.stateSet($lib.stateKeys.operationIds).has(at))
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
109
108
|
}
|
|
110
|
-
function
|
|
111
|
-
return program.stateSet($lib.stateKeys.operationIds).has(target) || target.sourceInterfaces.some((source) =>
|
|
109
|
+
function isMarkedInterface(program, target) {
|
|
110
|
+
return program.stateSet($lib.stateKeys.operationIds).has(target) || target.sourceInterfaces.some((source) => isMarkedInterface(program, source));
|
|
112
111
|
}
|
|
113
112
|
function isNamed(program, operation) {
|
|
114
113
|
const container = operation.interface;
|
|
115
|
-
|
|
114
|
+
if (container === undefined) {
|
|
115
|
+
return isMarkedNamespace(program, operation.namespace);
|
|
116
|
+
}
|
|
117
|
+
return !isTemplateInstance(container) && (isMarkedInterface(program, container) || isMarkedNamespace(program, container.namespace));
|
|
118
|
+
}
|
|
119
|
+
function isEmitted(operation) {
|
|
120
|
+
const container = operation.interface;
|
|
121
|
+
if (container === undefined)
|
|
122
|
+
return !isTemplateInstance(operation);
|
|
123
|
+
return !isTemplateInstance(container);
|
|
124
|
+
}
|
|
125
|
+
function servicesOf(program, operation) {
|
|
126
|
+
const services = [];
|
|
127
|
+
for (let at = operation.interface?.namespace ?? operation.namespace;at !== undefined; at = at.namespace) {
|
|
128
|
+
if (isService(program, at))
|
|
129
|
+
services.push(at);
|
|
130
|
+
}
|
|
131
|
+
return services;
|
|
116
132
|
}
|
|
117
133
|
function validateOperationIds(program) {
|
|
118
134
|
const seen = new Map;
|
|
135
|
+
const hasServices = listServices(program).length > 0;
|
|
119
136
|
navigateProgram(program, {
|
|
120
137
|
operation(operation) {
|
|
138
|
+
if (!isEmitted(operation))
|
|
139
|
+
return;
|
|
121
140
|
const named = isNamed(program, operation);
|
|
122
141
|
const own = getOperationId(program, operation);
|
|
123
|
-
const id = own ?? (named ?
|
|
124
|
-
|
|
142
|
+
const id = own ?? (named ? operation.name : resolveOperationId(program, operation));
|
|
143
|
+
const services = servicesOf(program, operation);
|
|
144
|
+
const emitted = services.length > 0 || !hasServices;
|
|
145
|
+
let duplicate = false;
|
|
146
|
+
for (const service of services.length > 0 ? services : [undefined]) {
|
|
147
|
+
const ids = seen.get(service) ?? new Map;
|
|
148
|
+
seen.set(service, ids);
|
|
149
|
+
if (emitted && ids.has(id) && (named || ids.get(id)))
|
|
150
|
+
duplicate = true;
|
|
151
|
+
ids.set(id, named || (ids.get(id) ?? false));
|
|
152
|
+
}
|
|
153
|
+
if (duplicate) {
|
|
125
154
|
$lib.reportDiagnostic(program, {
|
|
126
155
|
code: "duplicate-operation-id",
|
|
127
156
|
format: { id },
|
|
128
157
|
target: operation
|
|
129
158
|
});
|
|
130
159
|
}
|
|
131
|
-
seen.set(id, named || (seen.get(id) ?? false));
|
|
132
160
|
if (named && own === undefined)
|
|
133
161
|
setOperationId(program, operation, id);
|
|
134
162
|
}
|
|
@@ -141,8 +169,7 @@ function $onValidate(program) {
|
|
|
141
169
|
validateOneReplyPerStatus(program);
|
|
142
170
|
}
|
|
143
171
|
var $decorators = {
|
|
144
|
-
Nxgt: { operationIds }
|
|
145
|
-
"Nxgt.Private": { itemOperation }
|
|
172
|
+
Nxgt: { operationIds }
|
|
146
173
|
};
|
|
147
174
|
export {
|
|
148
175
|
$decorators,
|
|
@@ -150,5 +177,5 @@ export {
|
|
|
150
177
|
$onValidate
|
|
151
178
|
};
|
|
152
179
|
|
|
153
|
-
//# debugId=
|
|
180
|
+
//# debugId=EF940BB8E619B41E64756E2164756E21
|
|
154
181
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -3,11 +3,11 @@
|
|
|
3
3
|
"sources": ["../src/one-reply-per-status.ts", "../src/lib.ts", "../src/operation-ids.ts", "../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"/**\n * One reply per status: `@typespec/openapi3` merges two replies of one\n * status into one without a word. A reply without a body beside one with a\n * body is lost in the merge, an error; a `void` body is none, as the emitter\n * sees it. Two bodies of one content type are merged under the first reply's\n * description, a warning where the status is a code. Under `*` or a range,\n * where undocumented `@error` models share one description, they pass.\n * Bodies of different content types are one reply, negotiated, and pass.\n */\nimport { isVoidType, type Operation, type Program } from '@typespec/compiler';\nimport {\n\tgetAllHttpServices,\n\ttype HttpOperationResponse,\n\ttype HttpStatusCodesEntry,\n} from '@typespec/http';\nimport { $lib } from './lib';\n\nfunction statusOf(code: HttpStatusCodesEntry): string {\n\treturn typeof code === 'object' ? `${code.start}-${code.end}` : `${code}`;\n}\n\n/** What the emitter's merge costs: a reply, a description, or nothing. */\nfunction mergeOf({\n\tstatusCodes,\n\tresponses,\n}: HttpOperationResponse):\n\t| 'duplicate-status-reply'\n\t| 'merged-status-reply'\n\t| undefined {\n\tconst bodies = responses.flatMap(({ body }) =>\n\t\tbody && !isVoidType(body.type) ? [body] : [],\n\t);\n\tif (bodies.length > 0 && bodies.length < responses.length) {\n\t\treturn 'duplicate-status-reply';\n\t}\n\tif (typeof statusCodes !== 'number') return undefined;\n\tconst seen = new Set<string>();\n\tfor (const body of bodies) {\n\t\tif (body.contentTypes.some((type) => seen.has(type))) {\n\t\t\treturn 'merged-status-reply';\n\t\t}\n\t\tfor (const type of body.contentTypes) seen.add(type);\n\t}\n\treturn undefined;\n}\n\n/**\n * Checks each operation of each service, as the emitter sees it, once: a\n * service nested in another is listed in both. The services' own\n * diagnostics are the emitter's to report.\n */\nexport function validateOneReplyPerStatus(program: Program): void {\n\tconst [services] = getAllHttpServices(program);\n\tconst checked = new Set<Operation>();\n\tfor (const service of services) {\n\t\tfor (const operation of service.operations) {\n\t\t\tif (checked.has(operation.operation)) continue;\n\t\t\tchecked.add(operation.operation);\n\t\t\tfor (const response of operation.responses) {\n\t\t\t\tconst code = mergeOf(response);\n\t\t\t\tif (code === undefined) continue;\n\t\t\t\t$lib.reportDiagnostic(program, {\n\t\t\t\t\tcode,\n\t\t\t\t\tformat: {\n\t\t\t\t\t\toperation: operation.operation.name,\n\t\t\t\t\t\tstatus: statusOf(response.statusCodes),\n\t\t\t\t\t},\n\t\t\t\t\ttarget: operation.operation,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t}\n}\n",
|
|
6
|
-
"/** The library's definition: its name, diagnostics and state keys. */\nimport { createTypeSpecLibrary, paramMessage } from '@typespec/compiler';\n\nexport const $lib = createTypeSpecLibrary({\n\tname: '@nxgt/typespec',\n\tdiagnostics: {\n\t\t'duplicate-operation-id': {\n\t\t\tseverity: 'error',\n\t\t\tmessages: {\n\t\t\t\tdefault: paramMessage`Two operations are named ${'id'}: an OpenAPI operation id must be unique.`,\n\t\t\t},\n\t\t},\n\t\t'duplicate-status-reply': {\n\t\t\tseverity: 'error',\n\t\t\tmessages: {\n\t\t\t\tdefault: paramMessage`${'operation'} declares a reply without a body and one with a body of status ${'status'}: the emitter merges them into one, and the reply without a body is lost. Declare the one the route sends.`,\n\t\t\t},\n\t\t},\n\t\t'merged-status-reply': {\n\t\t\tseverity: 'warning',\n\t\t\tmessages: {\n\t\t\t\tdefault: paramMessage`${'operation'} declares two replies with a body of status ${'status'}: the emitter merges their bodies under the first one's description. Declare the one the route sends.`,\n\t\t\t},\n\t\t},\n\t},\n\tstate: {\n\t\toperationIds: {
|
|
7
|
-
"/**\n * `@operationIds`:
|
|
8
|
-
"/**\n * The JavaScript side of the `@nxgt/typespec` library, which `lib/main.tsp`\n * imports from `dist/`: its name and diagnostics, and the decorators a\n * template cannot express. The conventions themselves are in `lib/`.\n *\n * A decorator is exported only through `$decorators`: a top-level `$name`\n * export would declare it a second time, in the global namespace.\n */\nimport type { Program } from '@typespec/compiler';\nimport { validateOneReplyPerStatus } from './one-reply-per-status';\nimport {
|
|
6
|
+
"/** The library's definition: its name, diagnostics and state keys. */\nimport { createTypeSpecLibrary, paramMessage } from '@typespec/compiler';\n\nexport const $lib = createTypeSpecLibrary({\n\tname: '@nxgt/typespec',\n\tdiagnostics: {\n\t\t'duplicate-operation-id': {\n\t\t\tseverity: 'error',\n\t\t\tmessages: {\n\t\t\t\tdefault: paramMessage`Two operations are named ${'id'}: an OpenAPI operation id must be unique.`,\n\t\t\t},\n\t\t},\n\t\t'duplicate-status-reply': {\n\t\t\tseverity: 'error',\n\t\t\tmessages: {\n\t\t\t\tdefault: paramMessage`${'operation'} declares a reply without a body and one with a body of status ${'status'}: the emitter merges them into one, and the reply without a body is lost. Declare the one the route sends.`,\n\t\t\t},\n\t\t},\n\t\t'merged-status-reply': {\n\t\t\tseverity: 'warning',\n\t\t\tmessages: {\n\t\t\t\tdefault: paramMessage`${'operation'} declares two replies with a body of status ${'status'}: the emitter merges their bodies under the first one's description. Declare the one the route sends.`,\n\t\t\t},\n\t\t},\n\t},\n\tstate: {\n\t\toperationIds: {\n\t\t\tdescription: 'The interfaces and namespaces marked with @operationIds',\n\t\t},\n\t},\n});\n",
|
|
7
|
+
"/**\n * `@operationIds`: the id of each operation is its name, as written:\n * `@get findPostComments()` is `findPostComments`, where `@typespec/openapi3`\n * would write `Posts_findPostComments`.\n */\nimport {\n\ttype DecoratorContext,\n\ttype Interface,\n\tisService,\n\tisTemplateInstance,\n\tlistServices,\n\ttype Namespace,\n\tnavigateProgram,\n\ttype Operation,\n\ttype Program,\n} from '@typespec/compiler';\nimport {\n\tgetOperationId,\n\tresolveOperationId,\n\tsetOperationId,\n} from '@typespec/openapi';\nimport { $lib } from './lib';\n\n/**\n * Marks the interface or the namespace; its ids are set once the program is\n * checked. On a template, TypeSpec runs it on each instance, which\n * `isMarkedInterface` follows to the interfaces extending it.\n */\nexport function operationIds(\n\tcontext: DecoratorContext,\n\ttarget: Interface | Namespace,\n): void {\n\tcontext.program.stateSet($lib.stateKeys.operationIds).add(target);\n}\n\n/** Marked itself, or in a marked namespace, however deep. */\nfunction isMarkedNamespace(\n\tprogram: Program,\n\ttarget: Namespace | undefined,\n): boolean {\n\tfor (let at = target; at !== undefined; at = at.namespace) {\n\t\tif (program.stateSet($lib.stateKeys.operationIds).has(at)) return true;\n\t}\n\treturn false;\n}\n\n/** Marked itself, or extending a marked interface: an instance of a marked template included. */\nfunction isMarkedInterface(program: Program, target: Interface): boolean {\n\treturn (\n\t\tprogram.stateSet($lib.stateKeys.operationIds).has(target) ||\n\t\ttarget.sourceInterfaces.some((source) => isMarkedInterface(program, source))\n\t);\n}\n\n/**\n * Whether `@operationIds` names the operation's id, or checks it: its\n * interface or one of its namespaces is marked. An interface template's\n * instance is never emitted; only the interfaces extending it are.\n */\nfunction isNamed(program: Program, operation: Operation): boolean {\n\tconst container = operation.interface;\n\tif (container === undefined) {\n\t\treturn isMarkedNamespace(program, operation.namespace);\n\t}\n\treturn (\n\t\t!isTemplateInstance(container) &&\n\t\t(isMarkedInterface(program, container) ||\n\t\t\tisMarkedNamespace(program, container.namespace))\n\t);\n}\n\n/**\n * Whether the emitter writes the operation: not an instance of an operation\n * template, `Read<Pet>`, nor an operation of an interface template's\n * instance. An interface extending an instance copies its operations, which\n * are instances too, and emitted.\n */\nfunction isEmitted(operation: Operation): boolean {\n\tconst container = operation.interface;\n\tif (container === undefined) return !isTemplateInstance(operation);\n\treturn !isTemplateInstance(container);\n}\n\n/**\n * The services whose document the operation is in: each `@service`\n * namespace around it, a nested one and the one outside it both. None, in a\n * program without a service, is the one document the emitter writes; in a\n * program with one, no document at all.\n */\nfunction servicesOf(program: Program, operation: Operation): Namespace[] {\n\tconst services: Namespace[] = [];\n\tfor (\n\t\tlet at = operation.interface?.namespace ?? operation.namespace;\n\t\tat !== undefined;\n\t\tat = at.namespace\n\t) {\n\t\tif (isService(program, at)) services.push(at);\n\t}\n\treturn services;\n}\n\n/**\n * Sets the ids, after every decorator, so the order of `@operationId` and\n * `@operationIds` in the source does not matter. Two operations with one id\n * in one service's document, one of them named by `@operationIds`, are an\n * error: one name in two interfaces, an interface that `extends` another and\n * so copies its operations, or an `@operationId` written elsewhere.\n * `@typespec/openapi3` would emit both without a word. An operation\n * template's instance, such as `Read<Pet>`, is never emitted; only the\n * operations declared from it (`op readPet is Read<Pet>`) are.\n */\nexport function validateOperationIds(program: Program): void {\n\tconst seen = new Map<Namespace | undefined, Map<string, boolean>>();\n\tconst hasServices = listServices(program).length > 0;\n\tnavigateProgram(program, {\n\t\toperation(operation) {\n\t\t\tif (!isEmitted(operation)) return;\n\t\t\tconst named = isNamed(program, operation);\n\t\t\tconst own = getOperationId(program, operation);\n\t\t\t// Elsewhere, only checked: the id `@typespec/openapi3` gives under its\n\t\t\t// default strategy.\n\t\t\tconst id =\n\t\t\t\town ??\n\t\t\t\t(named ? operation.name : resolveOperationId(program, operation));\n\t\t\tconst services = servicesOf(program, operation);\n\t\t\t// Outside every service of a program that has one: in no document.\n\t\t\tconst emitted = services.length > 0 || !hasServices;\n\t\t\tlet duplicate = false;\n\t\t\tfor (const service of services.length > 0 ? services : [undefined]) {\n\t\t\t\tconst ids = seen.get(service) ?? new Map<string, boolean>();\n\t\t\t\tseen.set(service, ids);\n\t\t\t\tif (emitted && ids.has(id) && (named || ids.get(id))) duplicate = true;\n\t\t\t\tids.set(id, named || (ids.get(id) ?? false));\n\t\t\t}\n\t\t\tif (duplicate) {\n\t\t\t\t$lib.reportDiagnostic(program, {\n\t\t\t\t\tcode: 'duplicate-operation-id',\n\t\t\t\t\tformat: { id },\n\t\t\t\t\ttarget: operation,\n\t\t\t\t});\n\t\t\t}\n\t\t\t// Only the ids it gives: an explicit id on any other operation would\n\t\t\t// override the emitter's `operation-id-strategy`.\n\t\t\tif (named && own === undefined) setOperationId(program, operation, id);\n\t\t},\n\t});\n}\n",
|
|
8
|
+
"/**\n * The JavaScript side of the `@nxgt/typespec` library, which `lib/main.tsp`\n * imports from `dist/`: its name and diagnostics, and the decorators a\n * template cannot express. The conventions themselves are in `lib/`.\n *\n * A decorator is exported only through `$decorators`: a top-level `$name`\n * export would declare it a second time, in the global namespace.\n */\nimport type { Program } from '@typespec/compiler';\nimport { validateOneReplyPerStatus } from './one-reply-per-status';\nimport { operationIds, validateOperationIds } from './operation-ids';\n\nexport { $lib } from './lib';\n\nexport function $onValidate(program: Program): void {\n\tvalidateOperationIds(program);\n\tvalidateOneReplyPerStatus(program);\n}\n\nexport const $decorators = {\n\tNxgt: { operationIds },\n};\n"
|
|
9
9
|
],
|
|
10
|
-
"mappings": ";AASA;AACA;AAAA;AAAA;;;ACTA;AAEO,IAAM,OAAO,sBAAsB;AAAA,EACzC,MAAM;AAAA,EACN,aAAa;AAAA,IACZ,0BAA0B;AAAA,MACzB,UAAU;AAAA,MACV,UAAU;AAAA,QACT,SAAS,wCAAwC;AAAA,MAClD;AAAA,IACD;AAAA,IACA,0BAA0B;AAAA,MACzB,UAAU;AAAA,MACV,UAAU;AAAA,QACT,SAAS,eAAe,6EAA6E;AAAA,MACtG;AAAA,IACD;AAAA,IACA,uBAAuB;AAAA,MACtB,UAAU;AAAA,MACV,UAAU;AAAA,QACT,SAAS,eAAe,0DAA0D;AAAA,MACnF;AAAA,IACD;AAAA,EACD;AAAA,EACA,OAAO;AAAA,IACN,cAAc
|
|
11
|
-
"debugId": "
|
|
10
|
+
"mappings": "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",
|
|
11
|
+
"debugId": "EF940BB8E619B41E64756E2164756E21",
|
|
12
12
|
"names": []
|
|
13
13
|
}
|
package/dist/lib.d.ts
CHANGED
|
@@ -8,5 +8,5 @@ export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{
|
|
|
8
8
|
'merged-status-reply': {
|
|
9
9
|
readonly default: import("@typespec/compiler").CallableMessage<["operation", "status"]>;
|
|
10
10
|
};
|
|
11
|
-
}, Record<string, any>, "operationIds"
|
|
11
|
+
}, Record<string, any>, "operationIds">;
|
|
12
12
|
//# sourceMappingURL=lib.d.ts.map
|
package/dist/lib.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,IAAI;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,IAAI;;;;;;;;;;uCA2Bf,CAAC"}
|
package/dist/operation-ids.d.ts
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `@operationIds`:
|
|
3
|
-
*
|
|
2
|
+
* `@operationIds`: the id of each operation is its name, as written:
|
|
3
|
+
* `@get findPostComments()` is `findPostComments`, where `@typespec/openapi3`
|
|
4
|
+
* would write `Posts_findPostComments`.
|
|
4
5
|
*/
|
|
5
|
-
import { type DecoratorContext, type Interface, type
|
|
6
|
+
import { type DecoratorContext, type Interface, type Namespace, type Program } from '@typespec/compiler';
|
|
6
7
|
/**
|
|
7
|
-
* Marks the interface; its ids are set once the program is
|
|
8
|
-
* template, TypeSpec runs it on each instance, which
|
|
9
|
-
* the interfaces extending it.
|
|
8
|
+
* Marks the interface or the namespace; its ids are set once the program is
|
|
9
|
+
* checked. On a template, TypeSpec runs it on each instance, which
|
|
10
|
+
* `isMarkedInterface` follows to the interfaces extending it.
|
|
10
11
|
*/
|
|
11
|
-
export declare function operationIds(context: DecoratorContext, target: Interface): void;
|
|
12
|
-
/**
|
|
13
|
-
* Names the operation after one item, `<operation><Item>`, instead of its
|
|
14
|
-
* interface, where `@operationIds` names it: `Resource` marks `read`,
|
|
15
|
-
* `create`, `update` and `delete`, so `Authors` has `readAuthor`, but
|
|
16
|
-
* `listAuthors`. Not public: `Resource`'s alone.
|
|
17
|
-
*/
|
|
18
|
-
export declare function itemOperation(context: DecoratorContext, target: Operation, item: Model): void;
|
|
12
|
+
export declare function operationIds(context: DecoratorContext, target: Interface | Namespace): void;
|
|
19
13
|
/**
|
|
20
14
|
* Sets the ids, after every decorator, so the order of `@operationId` and
|
|
21
|
-
* `@operationIds` in the source does not matter. Two operations with one id
|
|
22
|
-
*
|
|
23
|
-
* in two
|
|
24
|
-
*
|
|
15
|
+
* `@operationIds` in the source does not matter. Two operations with one id
|
|
16
|
+
* in one service's document, one of them named by `@operationIds`, are an
|
|
17
|
+
* error: one name in two interfaces, an interface that `extends` another and
|
|
18
|
+
* so copies its operations, or an `@operationId` written elsewhere.
|
|
19
|
+
* `@typespec/openapi3` would emit both without a word. An operation
|
|
20
|
+
* template's instance, such as `Read<Pet>`, is never emitted; only the
|
|
21
|
+
* operations declared from it (`op readPet is Read<Pet>`) are.
|
|
25
22
|
*/
|
|
26
23
|
export declare function validateOperationIds(program: Program): void;
|
|
27
24
|
//# sourceMappingURL=operation-ids.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"operation-ids.d.ts","sourceRoot":"","sources":["../src/operation-ids.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"operation-ids.d.ts","sourceRoot":"","sources":["../src/operation-ids.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACN,KAAK,gBAAgB,EACrB,KAAK,SAAS,EAId,KAAK,SAAS,EAGd,KAAK,OAAO,EACZ,MAAM,oBAAoB,CAAC;AAQ5B;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,SAAS,GAAG,SAAS,GAC3B,IAAI,CAEN;AAoED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAmC3D"}
|
package/docs/README.md
CHANGED
|
@@ -9,7 +9,7 @@ The [package README](../README.md) is the short version.
|
|
|
9
9
|
| [Authentication](guide/auth.md) | protecting an operation with a `@nxgt/janus` session |
|
|
10
10
|
| [Scalars and columns](guide/columns.md) | typing ids and addresses, and the columns a row carries |
|
|
11
11
|
| [Headers](guide/headers.md) | making a write idempotent, or declaring a rate limit |
|
|
12
|
-
| [
|
|
13
|
-
| [Operation ids](guide/operation-ids.md) | naming the generated client's
|
|
12
|
+
| [Sorting](guide/sorting.md) | letting a client filter and sort a list |
|
|
13
|
+
| [Operation ids](guide/operation-ids.md) | naming each operation, and so the generated client's method, as written |
|
|
14
14
|
| [Troubleshooting](troubleshooting.md) | the compiler or the generator refused the spec |
|
|
15
15
|
| [Roadmap](roadmap.md) | wondering what is coming |
|
package/docs/guide/auth.md
CHANGED
|
@@ -16,10 +16,10 @@ namespace Blog;
|
|
|
16
16
|
|
|
17
17
|
@route("/posts/{postId}/comments")
|
|
18
18
|
interface Comments {
|
|
19
|
-
@get
|
|
19
|
+
@get findPostComments(@path postId: string): Comment[];
|
|
20
20
|
|
|
21
21
|
@useAuth(JanusAuth)
|
|
22
|
-
@delete
|
|
22
|
+
@delete deletePostComment(@path postId: string, @path commentId: string):
|
|
23
23
|
NoContentResponse | AuthenticationRequired | AccessDenied | ErrorWithoutBody<404>;
|
|
24
24
|
}
|
|
25
25
|
```
|
|
@@ -91,7 +91,7 @@ operation can declare the guard's 401 and the handler's 403:
|
|
|
91
91
|
|
|
92
92
|
```tsp
|
|
93
93
|
@useAuth(JanusAuth)
|
|
94
|
-
@post
|
|
94
|
+
@post createPost(@body post: Create<Post>): {
|
|
95
95
|
@statusCode _: 201;
|
|
96
96
|
@body post: Post;
|
|
97
97
|
} | BadRequest | AuthenticationRequired | Forbidden;
|
package/docs/guide/columns.md
CHANGED
|
@@ -80,7 +80,7 @@ that version, and throws `OptimisticLockError` otherwise. Declare the
|
|
|
80
80
|
`Conflict` it becomes:
|
|
81
81
|
|
|
82
82
|
```tsp
|
|
83
|
-
@patch
|
|
83
|
+
@patch updatePost(@path postId: uuid, @body post: MergePatchUpdate<Post>):
|
|
84
84
|
Post | BadRequest | NotFound | Conflict;
|
|
85
85
|
```
|
|
86
86
|
|
package/docs/guide/errors.md
CHANGED
|
@@ -54,7 +54,7 @@ namespace Blog;
|
|
|
54
54
|
|
|
55
55
|
@route("/posts")
|
|
56
56
|
interface Posts {
|
|
57
|
-
@get
|
|
57
|
+
@get getPost(@path postId: string): Post | NotFound;
|
|
58
58
|
}
|
|
59
59
|
```
|
|
60
60
|
|
|
@@ -143,7 +143,7 @@ A handler may send a 400 of its own, without `issues`. `BadRequestBody`
|
|
|
143
143
|
describes both, with `issues` optional:
|
|
144
144
|
|
|
145
145
|
```tsp
|
|
146
|
-
@patch
|
|
146
|
+
@patch updatePost(@path postId: string, @body post: MergePatchUpdate<Post>): Post | BadRequest;
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
The generator declares the validators' 400 itself, as `ValidationErrorBody`,
|
|
@@ -170,7 +170,7 @@ its own way, through `onValidationError`, turns that off with the generator's
|
|
|
170
170
|
Name them in an operation's return type, beside the success:
|
|
171
171
|
|
|
172
172
|
```tsp
|
|
173
|
-
@get
|
|
173
|
+
@get getPost(@path postId: string): Post | NotFound | Unauthorized | Forbidden;
|
|
174
174
|
```
|
|
175
175
|
|
|
176
176
|
## Another status, named
|
package/docs/guide/headers.md
CHANGED
|
@@ -19,7 +19,7 @@ namespace Blog;
|
|
|
19
19
|
|
|
20
20
|
@route("/posts")
|
|
21
21
|
interface Posts {
|
|
22
|
-
@post
|
|
22
|
+
@post createPost(...IdempotencyKeyHeader, @body post: Create<Post>): {
|
|
23
23
|
@statusCode _: 201;
|
|
24
24
|
...IdempotentReplayedHeader;
|
|
25
25
|
@body post: Post;
|
|
@@ -48,7 +48,7 @@ lock, gets one 409 with the description of the first, and the library warns,
|
|
|
48
48
|
## A rate limit
|
|
49
49
|
|
|
50
50
|
```tsp
|
|
51
|
-
@post
|
|
51
|
+
@post createPost(@body post: Create<Post>): {
|
|
52
52
|
@statusCode _: 201;
|
|
53
53
|
...RateLimitHeaders;
|
|
54
54
|
@body post: Post;
|