@nxgt/typespec 0.10.0 → 0.12.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/README.md +61 -8
- package/dist/http-operations.d.ts +9 -0
- package/dist/http-operations.d.ts.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +87 -57
- package/dist/index.js.map +10 -8
- package/dist/lib.d.ts +4 -1
- package/dist/lib.d.ts.map +1 -1
- package/dist/one-reply-per-status.d.ts +1 -5
- package/dist/one-reply-per-status.d.ts.map +1 -1
- package/dist/query-method.d.ts +13 -0
- package/dist/query-method.d.ts.map +1 -0
- package/dist/verb-methods.d.ts.map +1 -1
- package/dist/verbs.d.ts +2 -2
- package/docs/README.md +1 -0
- package/docs/guide/auth.md +1 -1
- package/docs/guide/columns.md +4 -4
- package/docs/guide/errors.md +1 -1
- package/docs/guide/getting-started.md +16 -7
- package/docs/guide/headers.md +2 -2
- package/docs/guide/operation-ids.md +60 -6
- package/docs/guide/requests.md +145 -0
- package/docs/roadmap.md +20 -4
- package/docs/troubleshooting.md +43 -1
- package/lib/main.tsp +2 -0
- package/lib/query-method.tsp +26 -0
- package/lib/requests.tsp +36 -0
- package/package.json +6 -6
- package/templates/nxgt/main.tsp +11 -4
package/README.md
CHANGED
|
@@ -125,7 +125,7 @@ namespace Blog;
|
|
|
125
125
|
@route("/posts")
|
|
126
126
|
interface Posts {
|
|
127
127
|
@get getPost(@path postId: string): Post | NotFound | Unauthorized;
|
|
128
|
-
@post createPost(@body post:
|
|
128
|
+
@post createPost(@body post: CreateRequest<Post>): {
|
|
129
129
|
@statusCode _: 201;
|
|
130
130
|
@body post: Post;
|
|
131
131
|
} | BadRequest | Conflict | ErrorResponse<503>;
|
|
@@ -175,8 +175,9 @@ an alias leaves out:
|
|
|
175
175
|
interface Users {
|
|
176
176
|
@get list(...PageParameters): Page<User> | ListErrors;
|
|
177
177
|
@get get(@path id: uuid): User | GetErrors;
|
|
178
|
-
@post create(@body user: User): User | CreateErrors | AuthErrors | TooManyRequests;
|
|
179
|
-
@
|
|
178
|
+
@post create(@body user: CreateRequest<User>): User | CreateErrors | AuthErrors | TooManyRequests;
|
|
179
|
+
@put update(@path id: uuid, @body user: UpdateRequest<User>): User | UpdateErrors;
|
|
180
|
+
@patch patch(@path id: uuid, @body user: PatchRequest<User>): User | UpdateErrors;
|
|
180
181
|
@delete delete(@path id: uuid): NoContentResponse | DeleteErrors;
|
|
181
182
|
}
|
|
182
183
|
```
|
|
@@ -184,6 +185,26 @@ interface Users {
|
|
|
184
185
|
An alias is exactly its union, so the OpenAPI it emits is the same as
|
|
185
186
|
writing the responses out.
|
|
186
187
|
|
|
188
|
+
### Request bodies
|
|
189
|
+
|
|
190
|
+
`CreateRequest<T>`, `UpdateRequest<T>` and `PatchRequest<T>` are TypeSpec's own
|
|
191
|
+
`Create`, `Update` and `MergePatchUpdate`, named so the generated types and
|
|
192
|
+
schemas read `CreatePostRequest`, `UpdatePostRequest` and `PatchPostRequest`:
|
|
193
|
+
|
|
194
|
+
```tsp
|
|
195
|
+
@post createPost(@body post: CreateRequest<Post>): {
|
|
196
|
+
@statusCode _: 201;
|
|
197
|
+
@body post: Post;
|
|
198
|
+
} | BadRequest;
|
|
199
|
+
@put updatePost(@path postId: uuid, @body post: UpdateRequest<Post>): Post | BadRequest | NotFound;
|
|
200
|
+
@patch patchPost(@path postId: uuid, @body post: PatchRequest<Post>): Post | BadRequest | NotFound;
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
A `PUT` body keeps each property as optional as the model declares it; a
|
|
204
|
+
`PATCH` body is a JSON Merge Patch, `application/merge-patch+json`, with
|
|
205
|
+
every property optional. `Create<Post, "New{name}">` names a body your own
|
|
206
|
+
way. See [Request bodies](docs/guide/requests.md).
|
|
207
|
+
|
|
187
208
|
### Pagination
|
|
188
209
|
|
|
189
210
|
Spread the query of a page into the operation's parameters, and answer the
|
|
@@ -293,9 +314,9 @@ model Post {
|
|
|
293
314
|
| --- | --- | --- | --- |
|
|
294
315
|
| `uuid` | the schema `UUID`, `format: uuid`, a pattern for versions 1–8 | `UUID`, `z.guid().regex(…)` | `id()` |
|
|
295
316
|
| `email` | `emailAddress`, the schema `EmailAddress`, `format: email` | `EmailAddress`, `z.string().regex(…)` | |
|
|
296
|
-
| `...Timestamps` | `createdAt`, `updatedAt`, read-only | left out of `
|
|
317
|
+
| `...Timestamps` | `createdAt`, `updatedAt`, read-only | left out of `CreateRequest<T>` and updates | `timestamps()` |
|
|
297
318
|
| `...SoftDelete` | `deletedAt: utcDateTime \| null`, read-only | | `softDelete()` |
|
|
298
|
-
| `...Versioned` | `version: integer`, read and update | kept in `
|
|
319
|
+
| `...Versioned` | `version: integer`, read and update | kept in `UpdateRequest<T>` and `PatchRequest<T>` | `version()` |
|
|
299
320
|
| `...Actors<Id = uuid>` | `createdBy`, `updatedBy`, `deletedBy`, `Id \| null`, read-only | | `actors()` |
|
|
300
321
|
|
|
301
322
|
Every scalar of [`@nxgt/graphql-scalars`](https://www.npmjs.com/package/@nxgt/graphql-scalars)
|
|
@@ -326,7 +347,7 @@ Spread the headers of an idempotent write and of a rate limit into an
|
|
|
326
347
|
operation and its replies:
|
|
327
348
|
|
|
328
349
|
```tsp
|
|
329
|
-
@post createPost(...IdempotencyKeyHeader, @body post:
|
|
350
|
+
@post createPost(...IdempotencyKeyHeader, @body post: CreateRequest<Post>): {
|
|
330
351
|
@statusCode _: 201;
|
|
331
352
|
...IdempotentReplayedHeader;
|
|
332
353
|
...RateLimitHeaders;
|
|
@@ -435,6 +456,29 @@ The verbs `verbs` adds are not checked, nor an operation with its own
|
|
|
435
456
|
`@operationId`. More, with each verb's methods, in
|
|
436
457
|
[Operation ids](docs/guide/operation-ids.md).
|
|
437
458
|
|
|
459
|
+
### A search with a body
|
|
460
|
+
|
|
461
|
+
`QUERY` is the HTTP method for a safe request with a body, and
|
|
462
|
+
`@typespec/http` 1.17 declares none. Mark a `@post` with `@queryMethod`: it is
|
|
463
|
+
still sent as a `POST`, which every server, proxy, `fetch` and CORS policy
|
|
464
|
+
accepts, and the document says what it is with `x-nxgt-method: query`, which
|
|
465
|
+
`@nxgt/openapi-codegen` reads:
|
|
466
|
+
|
|
467
|
+
```tsp
|
|
468
|
+
@route("/authors")
|
|
469
|
+
interface Authors {
|
|
470
|
+
@post @queryMethod @route("/search")
|
|
471
|
+
search(@body criteria: AuthorCriteria): Page<Author> | BadRequest; // searchAuthors
|
|
472
|
+
}
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
`search` and `query` accept a `GET`, a `POST` or a marked `POST` without the
|
|
476
|
+
`verb-method-mismatch` warning; `findById` or `create` marked warn, as a
|
|
477
|
+
`QUERY` is not their method. The decorator on anything but a `@post` is the
|
|
478
|
+
error `query-method-not-post`. When `@typespec/http` declares `QUERY`, the
|
|
479
|
+
decorator is what changes, not your spec. More, in
|
|
480
|
+
[Operation ids](docs/guide/operation-ids.md#a-query-sent-as-a-post).
|
|
481
|
+
|
|
438
482
|
### Linter
|
|
439
483
|
|
|
440
484
|
Extend the library's ruleset in `tspconfig.yaml`, and `tsp compile` warns
|
|
@@ -469,12 +513,15 @@ passing example of each rule, in [Linter](docs/guide/linter.md).
|
|
|
469
513
|
|
|
470
514
|
| Decorator | On | What it does |
|
|
471
515
|
| --- | --- | --- |
|
|
516
|
+
| `@queryMethod` | an operation | marks a `@post` as a `QUERY`: it stays a `POST` on the wire and the document gets `x-nxgt-method: query`; `@operationIds` checks its verb as a `QUERY`'s |
|
|
472
517
|
| `@operationIds(options?: OperationIdsOptions)` | a namespace or an interface | makes each operation's id its name, as written, or a known verb with the interface's resource (`list` in `Users` is `listUsers`), in the namespace however deep or in the interface, unless it has an `@operationId` |
|
|
473
518
|
|
|
474
519
|
`OperationIdsOptions` is `#{ singular?: string, plural?: string, verbs?: Record<"singular" | "plural"> }`:
|
|
475
520
|
`singular` and `plural` name an interface's resource, and `verbs` adds or
|
|
476
521
|
overrides verbs, on a namespace or an interface.
|
|
477
522
|
|
|
523
|
+
`@queryMethod` has no options.
|
|
524
|
+
|
|
478
525
|
### Diagnostics
|
|
479
526
|
|
|
480
527
|
| Code | Reported when |
|
|
@@ -483,7 +530,8 @@ overrides verbs, on a namespace or an interface.
|
|
|
483
530
|
| `resource-name-on-namespace` | error: `@operationIds` on a namespace is given `singular` or `plural`, which name an interface's resource |
|
|
484
531
|
| `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 |
|
|
485
532
|
| `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 |
|
|
486
|
-
| `verb-method-mismatch` | warning: an operation `@operationIds` names after one of the library's verbs, in an interface, is sent with a method that verb does not name: `create` with a `GET` |
|
|
533
|
+
| `verb-method-mismatch` | warning: an operation `@operationIds` names after one of the library's verbs, in an interface, is sent with a method that verb does not name: `create` with a `GET`, or marked `@queryMethod` |
|
|
534
|
+
| `query-method-not-post` | error: an operation marked `@queryMethod` is not a `@post` |
|
|
487
535
|
|
|
488
536
|
### Linter rules
|
|
489
537
|
|
|
@@ -507,6 +555,9 @@ In the ruleset `@nxgt/typespec/recommended`; each is a warning, and named
|
|
|
507
555
|
| `Page<Item>` | `{ items: Item[], total, page, pageSize, pageCount }`, the schema `<Item>Page` |
|
|
508
556
|
| `CursorPageParameters` | the query `after` and `limit` |
|
|
509
557
|
| `CursorPage<Item>` | `{ items: Item[], nextCursor: string \| null }`, the schema `<Item>CursorPage` |
|
|
558
|
+
| `CreateRequest<T>` | `Create<T, "Create{name}Request">`: the body of a create, `CreatePostRequest` |
|
|
559
|
+
| `UpdateRequest<T>` | `Update<T, "Update{name}Request">`: the body of a `PUT`, `UpdatePostRequest` |
|
|
560
|
+
| `PatchRequest<T>` | `MergePatchUpdate<T, "Patch{name}Request">`: a merge patch, all optional, `PatchPostRequest` |
|
|
510
561
|
| `ValidationIssue` | `{ target: ValidationTarget, path: (string \| integer)[], code: string, message: string }` |
|
|
511
562
|
| `ValidationTarget` | `"param" \| "query" \| "header" \| "json" \| "form" \| "body" \| "response"` |
|
|
512
563
|
|
|
@@ -653,9 +704,11 @@ envelope and the rate limit's headers.
|
|
|
653
704
|
`Retry-After`;
|
|
654
705
|
- [Sorting](docs/guide/sorting.md): `orderBy` and `direction` beside a
|
|
655
706
|
list's filters, and the `@nxgt/drizzle` call they map to;
|
|
707
|
+
- [Request bodies](docs/guide/requests.md): `CreateRequest`, `UpdateRequest`
|
|
708
|
+
and `PatchRequest`, and naming your own;
|
|
656
709
|
- [Operation ids](docs/guide/operation-ids.md): `@operationIds`, each
|
|
657
710
|
operation named as written or a verb with its resource, the method each
|
|
658
|
-
verb is sent with, the options, and the ids it refuses;
|
|
711
|
+
verb is sent with, `@queryMethod`, the options, and the ids it refuses;
|
|
659
712
|
- [Linter](docs/guide/linter.md): the ruleset `@nxgt/typespec/recommended`,
|
|
660
713
|
each rule with a failing and a passing spec, and how to turn one off;
|
|
661
714
|
- [troubleshooting](docs/troubleshooting.md);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Each HTTP operation of each service, once, as the emitter sends it: a
|
|
3
|
+
* service nested in another lists its operations in both. The services' own
|
|
4
|
+
* diagnostics are the emitter's to report.
|
|
5
|
+
*/
|
|
6
|
+
import type { Program } from '@typespec/compiler';
|
|
7
|
+
import { type HttpOperation } from '@typespec/http';
|
|
8
|
+
export declare function eachHttpOperation(program: Program, visit: (operation: HttpOperation) => void): void;
|
|
9
|
+
//# sourceMappingURL=http-operations.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"http-operations.d.ts","sourceRoot":"","sources":["../src/http-operations.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAsB,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAExE,wBAAgB,iBAAiB,CAChC,OAAO,EAAE,OAAO,EAChB,KAAK,EAAE,CAAC,SAAS,EAAE,aAAa,KAAK,IAAI,GACvC,IAAI,CAUN"}
|
package/dist/index.d.ts
CHANGED
|
@@ -8,12 +8,14 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import type { Program } from '@typespec/compiler';
|
|
10
10
|
import { operationIds } from './operation-ids';
|
|
11
|
+
import { queryMethod } from './query-method';
|
|
11
12
|
export { $lib } from './lib';
|
|
12
13
|
export { $linter } from './linter';
|
|
13
14
|
export declare function $onValidate(program: Program): void;
|
|
14
15
|
export declare const $decorators: {
|
|
15
16
|
Nxgt: {
|
|
16
17
|
operationIds: typeof operationIds;
|
|
18
|
+
queryMethod: typeof queryMethod;
|
|
17
19
|
};
|
|
18
20
|
};
|
|
19
21
|
//# 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,EAAE,YAAY,EAAwB,MAAM,iBAAiB,CAAC;
|
|
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;AACrE,OAAO,EAAE,WAAW,EAAwB,MAAM,gBAAgB,CAAC;AAGnE,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAC7B,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AAEnC,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAOlD;AAED,eAAO,MAAM,WAAW;;;;;CAEvB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
1
|
// src/one-reply-per-status.ts
|
|
2
2
|
import { isVoidType } from "@typespec/compiler";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "@typespec/http";
|
|
3
|
+
|
|
4
|
+
// src/http-operations.ts
|
|
5
|
+
import { getAllHttpServices } from "@typespec/http";
|
|
6
|
+
function eachHttpOperation(program, visit) {
|
|
7
|
+
const [services] = getAllHttpServices(program);
|
|
8
|
+
const visited = new Set;
|
|
9
|
+
for (const service of services) {
|
|
10
|
+
for (const operation of service.operations) {
|
|
11
|
+
if (visited.has(operation.operation))
|
|
12
|
+
continue;
|
|
13
|
+
visited.add(operation.operation);
|
|
14
|
+
visit(operation);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
6
18
|
|
|
7
19
|
// src/lib.ts
|
|
8
20
|
import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler";
|
|
@@ -27,6 +39,12 @@ var $lib = createTypeSpecLibrary({
|
|
|
27
39
|
default: paramMessage`${"operation"} is sent with ${"method"}, where its verb ${"verb"} is sent with ${"expected"}. Give it that method, or a name that is not a verb.`
|
|
28
40
|
}
|
|
29
41
|
},
|
|
42
|
+
"query-method-not-post": {
|
|
43
|
+
severity: "error",
|
|
44
|
+
messages: {
|
|
45
|
+
default: paramMessage`${"operation"} is marked @queryMethod and sent with ${"method"}: a QUERY is sent as a POST until @typespec/http declares it. Make it a @post.`
|
|
46
|
+
}
|
|
47
|
+
},
|
|
30
48
|
"duplicate-status-reply": {
|
|
31
49
|
severity: "error",
|
|
32
50
|
messages: {
|
|
@@ -41,6 +59,9 @@ var $lib = createTypeSpecLibrary({
|
|
|
41
59
|
}
|
|
42
60
|
},
|
|
43
61
|
state: {
|
|
62
|
+
queryMethod: {
|
|
63
|
+
description: "The operations marked with @queryMethod"
|
|
64
|
+
},
|
|
44
65
|
operationIds: {
|
|
45
66
|
description: "The interfaces and namespaces marked with @operationIds"
|
|
46
67
|
},
|
|
@@ -75,28 +96,21 @@ function mergeOf({
|
|
|
75
96
|
return;
|
|
76
97
|
}
|
|
77
98
|
function validateOneReplyPerStatus(program) {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
if (checked.has(operation.operation))
|
|
99
|
+
eachHttpOperation(program, (operation) => {
|
|
100
|
+
for (const response of operation.responses) {
|
|
101
|
+
const code = mergeOf(response);
|
|
102
|
+
if (code === undefined)
|
|
83
103
|
continue;
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
operation: operation.operation.name,
|
|
93
|
-
status: statusOf(response.statusCodes)
|
|
94
|
-
},
|
|
95
|
-
target: operation.operation
|
|
96
|
-
});
|
|
97
|
-
}
|
|
104
|
+
$lib.reportDiagnostic(program, {
|
|
105
|
+
code,
|
|
106
|
+
format: {
|
|
107
|
+
operation: operation.operation.name,
|
|
108
|
+
status: statusOf(response.statusCodes)
|
|
109
|
+
},
|
|
110
|
+
target: operation.operation
|
|
111
|
+
});
|
|
98
112
|
}
|
|
99
|
-
}
|
|
113
|
+
});
|
|
100
114
|
}
|
|
101
115
|
|
|
102
116
|
// src/operation-ids.ts
|
|
@@ -137,8 +151,8 @@ var METHODS = {
|
|
|
137
151
|
find: ["get", "head"],
|
|
138
152
|
count: ["get", "head"],
|
|
139
153
|
get: ["get", "head"],
|
|
140
|
-
search: ["get", "post"],
|
|
141
|
-
query: ["get", "post"],
|
|
154
|
+
search: ["get", "post", "query"],
|
|
155
|
+
query: ["get", "post", "query"],
|
|
142
156
|
create: ["post"],
|
|
143
157
|
createMany: ["post"],
|
|
144
158
|
update: ["put", "patch"],
|
|
@@ -314,42 +328,57 @@ function validateOperationIds(program) {
|
|
|
314
328
|
});
|
|
315
329
|
}
|
|
316
330
|
|
|
331
|
+
// src/query-method.ts
|
|
332
|
+
import { setExtension } from "@typespec/openapi";
|
|
333
|
+
function queryMethod(context, target) {
|
|
334
|
+
const { program } = context;
|
|
335
|
+
program.stateSet($lib.stateKeys.queryMethod).add(target);
|
|
336
|
+
setExtension(program, target, "x-nxgt-method", "query");
|
|
337
|
+
}
|
|
338
|
+
function isQueryMethod(program, operation) {
|
|
339
|
+
return program.stateSet($lib.stateKeys.queryMethod).has(operation);
|
|
340
|
+
}
|
|
341
|
+
function validateQueryMethods(program) {
|
|
342
|
+
eachHttpOperation(program, ({ operation, verb: method }) => {
|
|
343
|
+
if (!isQueryMethod(program, operation) || method === "post")
|
|
344
|
+
return;
|
|
345
|
+
$lib.reportDiagnostic(program, {
|
|
346
|
+
code: "query-method-not-post",
|
|
347
|
+
format: { operation: operation.name, method: method.toUpperCase() },
|
|
348
|
+
target: operation
|
|
349
|
+
});
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
317
353
|
// src/verb-methods.ts
|
|
318
|
-
import { getAllHttpServices as getAllHttpServices2 } from "@typespec/http";
|
|
319
354
|
import { getOperationId as getOperationId2 } from "@typespec/openapi";
|
|
320
355
|
function methodsOf(methods) {
|
|
321
356
|
return methods.map((method) => method.toUpperCase()).join(" or ");
|
|
322
357
|
}
|
|
323
358
|
function validateVerbMethods(program) {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
expected: methodsOf(expected)
|
|
348
|
-
},
|
|
349
|
-
target: operation
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
359
|
+
eachHttpOperation(program, ({ operation, verb: sent }) => {
|
|
360
|
+
if (!isNamed(program, operation))
|
|
361
|
+
return;
|
|
362
|
+
if (getOperationId2(program, operation) !== undefined)
|
|
363
|
+
return;
|
|
364
|
+
const verb = verbOf(program, operation);
|
|
365
|
+
if (verb === undefined || !Object.hasOwn(METHODS, verb))
|
|
366
|
+
return;
|
|
367
|
+
const method = sent === "post" && isQueryMethod(program, operation) ? "query" : sent;
|
|
368
|
+
const expected = METHODS[verb];
|
|
369
|
+
if (expected.includes(method))
|
|
370
|
+
return;
|
|
371
|
+
$lib.reportDiagnostic(program, {
|
|
372
|
+
code: "verb-method-mismatch",
|
|
373
|
+
format: {
|
|
374
|
+
operation: operation.name,
|
|
375
|
+
method: method.toUpperCase(),
|
|
376
|
+
verb,
|
|
377
|
+
expected: methodsOf(expected)
|
|
378
|
+
},
|
|
379
|
+
target: operation
|
|
380
|
+
});
|
|
381
|
+
});
|
|
353
382
|
}
|
|
354
383
|
// src/linter.ts
|
|
355
384
|
import { defineLinter } from "@typespec/compiler";
|
|
@@ -475,12 +504,13 @@ var $linter = defineLinter({
|
|
|
475
504
|
|
|
476
505
|
// src/index.ts
|
|
477
506
|
function $onValidate(program) {
|
|
507
|
+
validateQueryMethods(program);
|
|
478
508
|
validateVerbMethods(program);
|
|
479
509
|
validateOperationIds(program);
|
|
480
510
|
validateOneReplyPerStatus(program);
|
|
481
511
|
}
|
|
482
512
|
var $decorators = {
|
|
483
|
-
Nxgt: { operationIds }
|
|
513
|
+
Nxgt: { operationIds, queryMethod }
|
|
484
514
|
};
|
|
485
515
|
export {
|
|
486
516
|
$decorators,
|
|
@@ -489,5 +519,5 @@ export {
|
|
|
489
519
|
$onValidate
|
|
490
520
|
};
|
|
491
521
|
|
|
492
|
-
//# debugId=
|
|
522
|
+
//# debugId=B949BF3E72439B1464756E2164756E21
|
|
493
523
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/one-reply-per-status.ts", "../src/lib.ts", "../src/operation-ids.ts", "../src/verbs.ts", "../src/resource.ts", "../src/verb-methods.ts", "../src/linter.ts", "../src/rules/error-body-shape.ts", "../src/rules/written.ts", "../src/rules/list-returns-page.ts", "../src/rules/service-operation-ids.ts", "../src/index.ts"],
|
|
3
|
+
"sources": ["../src/one-reply-per-status.ts", "../src/http-operations.ts", "../src/lib.ts", "../src/operation-ids.ts", "../src/verbs.ts", "../src/resource.ts", "../src/query-method.ts", "../src/verb-methods.ts", "../src/linter.ts", "../src/rules/error-body-shape.ts", "../src/rules/written.ts", "../src/rules/list-returns-page.ts", "../src/rules/service-operation-ids.ts", "../src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
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
|
|
6
|
-
"
|
|
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 Program } from '@typespec/compiler';\nimport type {\n\tHttpOperationResponse,\n\tHttpStatusCodesEntry,\n} from '@typespec/http';\nimport { eachHttpOperation } from './http-operations';\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/** Checks each operation of each service, as the emitter sees it, once. */\nexport function validateOneReplyPerStatus(program: Program): void {\n\teachHttpOperation(program, (operation) => {\n\t\tfor (const response of operation.responses) {\n\t\t\tconst code = mergeOf(response);\n\t\t\tif (code === undefined) continue;\n\t\t\t$lib.reportDiagnostic(program, {\n\t\t\t\tcode,\n\t\t\t\tformat: {\n\t\t\t\t\toperation: operation.operation.name,\n\t\t\t\t\tstatus: statusOf(response.statusCodes),\n\t\t\t\t},\n\t\t\t\ttarget: operation.operation,\n\t\t\t});\n\t\t}\n\t});\n}\n",
|
|
6
|
+
"/**\n * Each HTTP operation of each service, once, as the emitter sends it: a\n * service nested in another lists its operations in both. The services' own\n * diagnostics are the emitter's to report.\n */\nimport type { Program } from '@typespec/compiler';\nimport { getAllHttpServices, type HttpOperation } from '@typespec/http';\n\nexport function eachHttpOperation(\n\tprogram: Program,\n\tvisit: (operation: HttpOperation) => void,\n): void {\n\tconst [services] = getAllHttpServices(program);\n\tconst visited = new Set<HttpOperation['operation']>();\n\tfor (const service of services) {\n\t\tfor (const operation of service.operations) {\n\t\t\tif (visited.has(operation.operation)) continue;\n\t\t\tvisited.add(operation.operation);\n\t\t\tvisit(operation);\n\t\t}\n\t}\n}\n",
|
|
7
|
+
"/** 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'resource-name-on-namespace': {\n\t\t\tseverity: 'error',\n\t\t\tmessages: {\n\t\t\t\tdefault:\n\t\t\t\t\t\"`singular` and `plural` name an interface's resource: a namespace has none. Put them on the interface.\",\n\t\t\t},\n\t\t},\n\t\t'verb-method-mismatch': {\n\t\t\tseverity: 'warning',\n\t\t\tmessages: {\n\t\t\t\tdefault: paramMessage`${'operation'} is sent with ${'method'}, where its verb ${'verb'} is sent with ${'expected'}. Give it that method, or a name that is not a verb.`,\n\t\t\t},\n\t\t},\n\t\t'query-method-not-post': {\n\t\t\tseverity: 'error',\n\t\t\tmessages: {\n\t\t\t\tdefault: paramMessage`${'operation'} is marked @queryMethod and sent with ${'method'}: a QUERY is sent as a POST until @typespec/http declares it. Make it a @post.`,\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\tqueryMethod: {\n\t\t\tdescription: 'The operations marked with @queryMethod',\n\t\t},\n\t\toperationIds: {\n\t\t\tdescription: 'The interfaces and namespaces marked with @operationIds',\n\t\t},\n\t\toperationIdsOptions: {\n\t\t\tdescription:\n\t\t\t\t\"Each marked interface's or namespace's @operationIds options\",\n\t\t},\n\t},\n});\n",
|
|
7
8
|
"/**\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`. In an interface, a known verb takes\n * the interface's resource: `create` in `Users` is `createUser` (`verbs.ts`).\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';\nimport { idOf, type OperationIdsOptions } from './resource';\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. A namespace\n * has no resource to name: `singular` and `plural` are an interface's.\n */\nexport function operationIds(\n\tcontext: DecoratorContext,\n\ttarget: Interface | Namespace,\n\toptions: OperationIdsOptions = {},\n): void {\n\tconst { program } = context;\n\tprogram.stateSet($lib.stateKeys.operationIds).add(target);\n\tprogram.stateMap($lib.stateKeys.operationIdsOptions).set(target, options);\n\tif (\n\t\ttarget.kind === 'Namespace' &&\n\t\t(options.singular !== undefined || options.plural !== undefined)\n\t) {\n\t\t$lib.reportDiagnostic(program, {\n\t\t\tcode: 'resource-name-on-namespace',\n\t\t\ttarget: context.decoratorTarget,\n\t\t});\n\t}\n}\n\n/** Marked itself, or in a marked namespace, however deep. */\nexport function 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 */\nexport function 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 */\nexport function 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\n\t\t\t\t\t? idOf(program, operation)\n\t\t\t\t\t: 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 verbs `@operationIds` completes with an interface's resource: `create`\n * in `Users` is `createUser`, `list` is `listUsers`, and `findById` is\n * `findUserById`. Any other name is the id as written.\n */\n\n/** Whether a verb takes the resource's singular or its plural. */\nexport type Grammar = 'singular' | 'plural';\n\n/** A resource's two names: `User` and `Users`. */\nexport interface Resource {\n\treadonly singular: string;\n\treadonly plural: string;\n}\n\n/** The library's own verbs. */\nexport const VERBS: Readonly<Record<string, Grammar>> = {\n\tlist: 'plural',\n\tread: 'plural',\n\tfind: 'plural',\n\tsearch: 'plural',\n\tquery: 'plural',\n\tcount: 'plural',\n\tcreateMany: 'plural',\n\tupdateMany: 'plural',\n\tdeleteMany: 'plural',\n\tget: 'singular',\n\tcreate: 'singular',\n\tupdate: 'singular',\n\tpatch: 'singular',\n\treplace: 'singular',\n\tupsert: 'singular',\n\tdelete: 'singular',\n};\n\n/**\n * The HTTP methods each of the library's verbs is sent with: a read is a\n * `GET` (or a `HEAD`), a search may carry its criteria in a `POST` body, a\n * bulk delete its ids. `QUERY
|
|
9
|
+
"/**\n * The verbs `@operationIds` completes with an interface's resource: `create`\n * in `Users` is `createUser`, `list` is `listUsers`, and `findById` is\n * `findUserById`. Any other name is the id as written.\n */\n\n/** Whether a verb takes the resource's singular or its plural. */\nexport type Grammar = 'singular' | 'plural';\n\n/** A resource's two names: `User` and `Users`. */\nexport interface Resource {\n\treadonly singular: string;\n\treadonly plural: string;\n}\n\n/** The library's own verbs. */\nexport const VERBS: Readonly<Record<string, Grammar>> = {\n\tlist: 'plural',\n\tread: 'plural',\n\tfind: 'plural',\n\tsearch: 'plural',\n\tquery: 'plural',\n\tcount: 'plural',\n\tcreateMany: 'plural',\n\tupdateMany: 'plural',\n\tdeleteMany: 'plural',\n\tget: 'singular',\n\tcreate: 'singular',\n\tupdate: 'singular',\n\tpatch: 'singular',\n\treplace: 'singular',\n\tupsert: 'singular',\n\tdelete: 'singular',\n};\n\n/**\n * The HTTP methods each of the library's verbs is sent with: a read is a\n * `GET` (or a `HEAD`), a search may carry its criteria in a `POST` body, a\n * bulk delete its ids. A `QUERY`, a `POST` marked `@queryMethod`, is\n * a search too.\n */\nexport const METHODS: Readonly<Record<string, readonly string[]>> = {\n\tlist: ['get', 'head'],\n\tread: ['get', 'head'],\n\tfind: ['get', 'head'],\n\tcount: ['get', 'head'],\n\tget: ['get', 'head'],\n\tsearch: ['get', 'post', 'query'],\n\tquery: ['get', 'post', 'query'],\n\tcreate: ['post'],\n\tcreateMany: ['post'],\n\tupdate: ['put', 'patch'],\n\tupdateMany: ['put', 'patch'],\n\tpatch: ['patch'],\n\treplace: ['put'],\n\tupsert: ['put'],\n\tdelete: ['delete'],\n\tdeleteMany: ['delete', 'post'],\n};\n\n/** Plurals the rules below get wrong, by their singular. */\nconst IRREGULAR: Readonly<Record<string, string>> = {\n\tpeople: 'person',\n\tchildren: 'child',\n\tmen: 'man',\n\twomen: 'woman',\n\tmice: 'mouse',\n\tfeet: 'foot',\n\tteeth: 'tooth',\n\tgeese: 'goose',\n\tmovies: 'movie',\n\tcookies: 'cookie',\n\tseries: 'series',\n\tspecies: 'species',\n\tnews: 'news',\n};\n\n/** `-es` plurals of a singular in `-us`: `Statuses`, not `Houses`. */\nconst US_ES = /(stat|bon|camp|vir|b|foc|cens|corp|radi|syllab)uses$/i;\n\n/** `-es` plurals of a singular in a sibilant: `Boxes`, `Matches`, `Wishes`. */\nconst SIBILANT_ES = /(ss|x|zz|sh|tch|nch|rch|oach)es$/i;\n\n/**\n * The singular of a plural name, by a short English rule on its last word:\n * `Users`, `BlogPosts`, `Categories`, `Addresses`, `Statuses`, `People`. A\n * guess: it misses some (`Heroes`, `Analysis`), which `singular` names.\n */\nexport function singularOf(plural: string): string {\n\tconst word = /[A-Z]?[a-z0-9]*$/.exec(plural)?.[0] ?? '';\n\tconst head = plural.slice(0, plural.length - word.length);\n\tconst key = word.toLowerCase();\n\tconst irregular = Object.hasOwn(IRREGULAR, key) ? IRREGULAR[key] : undefined;\n\tif (irregular !== undefined) {\n\t\tconst cased = word[0] === word[0]?.toUpperCase();\n\t\treturn `${head}${cased ? irregular[0]?.toUpperCase() + irregular.slice(1) : irregular}`;\n\t}\n\tif (/ies$/.test(word) && word.length > 4)\n\t\treturn `${head}${word.slice(0, -3)}y`;\n\tif (US_ES.test(word) || SIBILANT_ES.test(word)) {\n\t\treturn `${head}${word.slice(0, -2)}`;\n\t}\n\tif (/[^su]s$/.test(word)) return `${head}${word.slice(0, -1)}`;\n\treturn plural;\n}\n\n/**\n * The verb a name starts with, and the rest: the whole name, `list`, or the\n * verb before a `By`, `findById`. `undefined` when the name is neither.\n */\nexport function verbIn(\n\tname: string,\n\tverbs: Readonly<Record<string, Grammar>>,\n): { readonly verb: string; readonly rest: string } | undefined {\n\tif (Object.hasOwn(verbs, name)) return { verb: name, rest: '' };\n\tfor (const by of name.matchAll(/By[A-Z]/g)) {\n\t\tconst verb = name.slice(0, by.index);\n\t\tif (verb !== '' && Object.hasOwn(verbs, verb)) {\n\t\t\treturn { verb, rest: name.slice(by.index) };\n\t\t}\n\t}\n\treturn undefined;\n}\n\n/**\n * The id of an operation of a resource: the verb and the resource's singular\n * or plural, or the verb, the resource and the rest of a `By` name:\n * `findById` to `findUserById`, the singular, but `deleteManyByTeam` to\n * `deleteManyUsersByTeam`, the plural of a `*Many` verb. `undefined` when\n * the name is none of them. The resource is taken only then.\n */\nexport function idWithVerb(\n\tname: string,\n\tresource: () => Resource,\n\tverbs: Readonly<Record<string, Grammar>>,\n): string | undefined {\n\tconst found = verbIn(name, verbs);\n\tif (found === undefined) return undefined;\n\tconst { verb, rest } = found;\n\tconst names = resource();\n\tconst noun =\n\t\trest === ''\n\t\t\t? names[verbs[verb] as Grammar]\n\t\t\t: verb.endsWith('Many')\n\t\t\t\t? names.plural\n\t\t\t\t: names.singular;\n\treturn `${verb}${noun}${rest}`;\n}\n",
|
|
9
10
|
"/**\n * What `@operationIds` names an operation of an interface after: the\n * interface's resource, its singular and plural, and the verbs that take\n * them, layered from the library's, the namespaces', and the interface's.\n */\nimport type {\n\tInterface,\n\tNamespace,\n\tOperation,\n\tProgram,\n} from '@typespec/compiler';\nimport { $lib } from './lib';\nimport {\n\ttype Grammar,\n\tidWithVerb,\n\ttype Resource,\n\tsingularOf,\n\tVERBS,\n\tverbIn,\n} from './verbs';\n\n/** `@operationIds`' options, as `OperationIdsOptions` declares them. */\nexport interface OperationIdsOptions {\n\treadonly singular?: string;\n\treadonly plural?: string;\n\treadonly verbs?: Readonly<Record<string, Grammar>>;\n}\n\nfunction optionsOf(\n\tprogram: Program,\n\ttarget: Interface | Namespace,\n): OperationIdsOptions | undefined {\n\treturn program.stateMap($lib.stateKeys.operationIdsOptions).get(target);\n}\n\n/** The interface, then those it extends, however deep. */\nfunction lineageOf(target: Interface): Interface[] {\n\treturn [target, ...target.sourceInterfaces.flatMap(lineageOf)];\n}\n\n/**\n * The verbs an interface's operations take: the library's, then each marked\n * namespace's around it, outermost first, then its lineage's, its own last,\n * each overriding the one before.\n */\nfunction verbsOf(program: Program, target: Interface): Record<string, Grammar> {\n\tconst namespaces: Namespace[] = [];\n\tfor (let at = target.namespace; at !== undefined; at = at.namespace) {\n\t\tnamespaces.unshift(at);\n\t}\n\tconst layers = [...namespaces, ...lineageOf(target).reverse()];\n\treturn Object.assign(\n\t\t{},\n\t\tVERBS,\n\t\t...layers.map((layer) => optionsOf(program, layer)?.verbs ?? {}),\n\t);\n}\n\n/**\n * An interface's resource: its own `plural`, or its name, and its own\n * `singular`, or the plural's singular. Never an interface it extends: two\n * interfaces extending one would share its names, and collide.\n */\nfunction resourceOf(program: Program, target: Interface): Resource {\n\tconst options = optionsOf(program, target);\n\tconst plural = options?.plural || target.name;\n\treturn { plural, singular: options?.singular || singularOf(plural) };\n}\n\n/** The id `@operationIds` gives: the name as written, or a verb with its resource. */\nexport function idOf(program: Program, operation: Operation): string {\n\tconst container = operation.interface;\n\tif (container === undefined) return operation.name;\n\treturn (\n\t\tidWithVerb(\n\t\t\toperation.name,\n\t\t\t() => resourceOf(program, container),\n\t\t\tverbsOf(program, container),\n\t\t) ?? operation.name\n\t);\n}\n\n/** The verb an operation of an interface is named with, `find` in `findById`. */\nexport function verbOf(\n\tprogram: Program,\n\toperation: Operation,\n): string | undefined {\n\tconst container = operation.interface;\n\tif (container === undefined) return undefined;\n\treturn verbIn(operation.name, verbsOf(program, container))?.verb;\n}\n",
|
|
10
|
-
"/**\n *
|
|
11
|
+
"/**\n * `@queryMethod`: a `POST` that is a `QUERY`, a safe request with its\n * criteria in the body. `@typespec/http` declares no `QUERY` yet, so the\n * operation is sent as a `POST` and the document marks it,\n * `x-nxgt-method: query`, for `@nxgt/openapi-codegen`.\n */\nimport type { DecoratorContext, Operation, Program } from '@typespec/compiler';\nimport { setExtension } from '@typespec/openapi';\nimport { eachHttpOperation } from './http-operations';\nimport { $lib } from './lib';\n\nexport function queryMethod(\n\tcontext: DecoratorContext,\n\ttarget: Operation,\n): void {\n\tconst { program } = context;\n\tprogram.stateSet($lib.stateKeys.queryMethod).add(target);\n\tsetExtension(program, target, 'x-nxgt-method', 'query');\n}\n\n/** Marked with `@queryMethod`. */\nexport function isQueryMethod(program: Program, operation: Operation): boolean {\n\treturn program.stateSet($lib.stateKeys.queryMethod).has(operation);\n}\n\n/** Each marked operation is a `POST`, as the emitter sends it. */\nexport function validateQueryMethods(program: Program): void {\n\teachHttpOperation(program, ({ operation, verb: method }) => {\n\t\tif (!isQueryMethod(program, operation) || method === 'post') return;\n\t\t$lib.reportDiagnostic(program, {\n\t\t\tcode: 'query-method-not-post',\n\t\t\tformat: { operation: operation.name, method: method.toUpperCase() },\n\t\t\ttarget: operation,\n\t\t});\n\t});\n}\n",
|
|
12
|
+
"/**\n * A verb names what an operation does, and its method says the same:\n * `create` in `Users` is `createUser`, and a `GET` would tell a cache, a\n * proxy or a retry it is safe. An operation `@operationIds` names after one\n * of the library's verbs is sent with that verb's methods (`verbs.ts`), or\n * warned of. A verb `verbs` adds has no method to check, and an operation's\n * own `@operationId` is an id that is no verb. It runs before\n * `validateOperationIds` sets the others.\n */\nimport type { Program } from '@typespec/compiler';\nimport { getOperationId } from '@typespec/openapi';\nimport { eachHttpOperation } from './http-operations';\nimport { $lib } from './lib';\nimport { isNamed } from './operation-ids';\nimport { isQueryMethod } from './query-method';\nimport { verbOf } from './resource';\nimport { METHODS } from './verbs';\n\n/** `PUT or PATCH`. */\nfunction methodsOf(methods: readonly string[]): string {\n\treturn methods.map((method) => method.toUpperCase()).join(' or ');\n}\n\n/** Checks each operation of each service once, as the emitter sends it. */\nexport function validateVerbMethods(program: Program): void {\n\teachHttpOperation(program, ({ operation, verb: sent }) => {\n\t\tif (!isNamed(program, operation)) return;\n\t\tif (getOperationId(program, operation) !== undefined) return;\n\t\tconst verb = verbOf(program, operation);\n\t\tif (verb === undefined || !Object.hasOwn(METHODS, verb)) return;\n\t\t// A `POST` marked `@queryMethod` is checked as the `QUERY` it is.\n\t\tconst method =\n\t\t\tsent === 'post' && isQueryMethod(program, operation) ? 'query' : sent;\n\t\tconst expected = METHODS[verb] as readonly string[];\n\t\tif (expected.includes(method)) return;\n\t\t$lib.reportDiagnostic(program, {\n\t\t\tcode: 'verb-method-mismatch',\n\t\t\tformat: {\n\t\t\t\toperation: operation.name,\n\t\t\t\tmethod: method.toUpperCase(),\n\t\t\t\tverb,\n\t\t\t\texpected: methodsOf(expected),\n\t\t\t},\n\t\t\ttarget: operation,\n\t\t});\n\t});\n}\n",
|
|
11
13
|
"/**\n * The library's linter: rules a spec opts into in its `tspconfig.yaml`,\n * each a warning, each one it can turn off.\n *\n * ```yaml\n * linter:\n * extends:\n * - \"@nxgt/typespec/recommended\"\n * ```\n */\nimport { defineLinter } from '@typespec/compiler';\nimport { errorBodyShape } from './rules/error-body-shape';\nimport { listReturnsPage } from './rules/list-returns-page';\nimport { serviceOperationIds } from './rules/service-operation-ids';\n\nconst rules = [listReturnsPage, serviceOperationIds, errorBodyShape];\n\nexport const $linter = defineLinter({\n\trules,\n\truleSets: {\n\t\trecommended: {\n\t\t\tenable: Object.fromEntries(\n\t\t\t\trules.map(({ name }) => [`@nxgt/typespec/${name}`, true]),\n\t\t\t),\n\t\t},\n\t},\n});\n",
|
|
12
14
|
"/**\n * An error reply's body is the envelope `@nxgt/openapi-hono` sends: a\n * `status`, a `message` and a `timestamp`, as `ErrorBody<Status>` declares\n * them. A reply without a body passes: `AuthenticationRequired` has none.\n */\nimport {\n\tcreateRule,\n\tisVoidType,\n\ttype Model,\n\tparamMessage,\n\twalkPropertiesInherited,\n} from '@typespec/compiler';\nimport { getHttpOperation } from '@typespec/http';\nimport { isWritten } from './written';\n\nconst ENVELOPE = ['status', 'message', 'timestamp'];\n\n/** Its own properties and those of the models it extends. */\nfunction isEnvelope(body: Model): boolean {\n\tconst names = new Set(\n\t\t[...walkPropertiesInherited(body)].map(({ name }) => name),\n\t);\n\treturn ENVELOPE.every((name) => names.has(name));\n}\n\nfunction statusOf(code: number | { start: number; end: number } | '*'): string {\n\treturn typeof code === 'object' ? `${code.start}-${code.end}` : `${code}`;\n}\n\nexport const errorBodyShape = createRule({\n\tname: 'error-body-shape',\n\tseverity: 'warning',\n\tdescription:\n\t\t'An error reply has the nxgt envelope: status, message and timestamp.',\n\tmessages: {\n\t\tdefault: paramMessage`${'operation'} answers ${'status'} with a body that is not the nxgt envelope: declare BadRequest, NotFound or another of the library's errors, or a body that spreads or extends ErrorBody<Status>.`,\n\t},\n\tcreate: (context) => ({\n\t\toperation(operation) {\n\t\t\tif (!isWritten(operation)) return;\n\t\t\tconst [http] = getHttpOperation(context.program, operation);\n\t\t\tfor (const { statusCodes, responses } of http.responses) {\n\t\t\t\tconst error =\n\t\t\t\t\tstatusCodes === '*' ||\n\t\t\t\t\t(typeof statusCodes === 'number'\n\t\t\t\t\t\t? statusCodes >= 400\n\t\t\t\t\t\t: statusCodes.start >= 400);\n\t\t\t\tif (!error) continue;\n\t\t\t\tconst wrong = responses.some(({ body }) => {\n\t\t\t\t\tif (body === undefined || isVoidType(body.type)) return false;\n\t\t\t\t\treturn body.type.kind !== 'Model' || !isEnvelope(body.type);\n\t\t\t\t});\n\t\t\t\tif (!wrong) continue;\n\t\t\t\tcontext.reportDiagnostic({\n\t\t\t\t\tformat: { operation: operation.name, status: statusOf(statusCodes) },\n\t\t\t\t\ttarget: operation,\n\t\t\t\t});\n\t\t\t}\n\t\t},\n\t}),\n});\n",
|
|
13
15
|
"/**\n * Whether the emitter writes an operation: not a template's declaration,\n * nor its instance (`Read<Pet>`), nor an operation of an interface\n * template's. Only the operations declared from them are checked.\n */\nimport { isTemplateDeclaration, type Operation } from '@typespec/compiler';\nimport { isEmitted } from '../operation-ids';\n\nexport function isWritten(operation: Operation): boolean {\n\tconst container = operation.interface;\n\treturn (\n\t\tisEmitted(operation) &&\n\t\t!isTemplateDeclaration(operation) &&\n\t\t(container === undefined || !isTemplateDeclaration(container))\n\t);\n}\n",
|
|
14
16
|
"/**\n * A list returns a page: `Page<Item>` or `CursorPage<Item>`, never a bare\n * array, which cannot grow a `total` or a `nextCursor` without breaking its\n * clients. A list is an operation named `list`, `search` or `query`, alone\n * or followed by more: `listUsers`, `searchPosts`.\n */\nimport { createRule, isArrayModelType, paramMessage } from '@typespec/compiler';\nimport { getHttpOperation } from '@typespec/http';\nimport { isWritten } from './written';\n\nconst LIST = /^(list|search|query)(?=[A-Z]|$)/;\n\nexport const listReturnsPage = createRule({\n\tname: 'list-returns-page',\n\tseverity: 'warning',\n\tdescription:\n\t\t'A list, search or query returns Page<Item> or CursorPage<Item>.',\n\tmessages: {\n\t\tdefault: paramMessage`${'operation'} returns an array: return Page<Item> or CursorPage<Item>, which can carry a total or a next cursor.`,\n\t},\n\tcreate: (context) => ({\n\t\toperation(operation) {\n\t\t\tif (!isWritten(operation) || !LIST.test(operation.name)) return;\n\t\t\tconst [http] = getHttpOperation(context.program, operation);\n\t\t\tconst array = http.responses.some(\n\t\t\t\t({ statusCodes, responses }) =>\n\t\t\t\t\ttypeof statusCodes === 'number' &&\n\t\t\t\t\tstatusCodes >= 200 &&\n\t\t\t\t\tstatusCodes < 300 &&\n\t\t\t\t\tresponses.some(\n\t\t\t\t\t\t({ body }) =>\n\t\t\t\t\t\t\tbody?.type.kind === 'Model' && isArrayModelType(body.type),\n\t\t\t\t\t),\n\t\t\t);\n\t\t\tif (!array) return;\n\t\t\tcontext.reportDiagnostic({\n\t\t\t\tformat: { operation: operation.name },\n\t\t\t\ttarget: operation,\n\t\t\t});\n\t\t},\n\t}),\n});\n",
|
|
15
17
|
"/**\n * A service without `@operationIds` gets the emitter's ids: `Users_list`,\n * which the generated client's methods are named after.\n */\nimport { createRule, isService, paramMessage } from '@typespec/compiler';\nimport { isMarkedNamespace } from '../operation-ids';\n\nexport const serviceOperationIds = createRule({\n\tname: 'service-operation-ids',\n\tseverity: 'warning',\n\tdescription:\n\t\t'A service namespace marks its operation ids with @operationIds.',\n\tmessages: {\n\t\tdefault: paramMessage`The service ${'service'} has no @operationIds: its ids are the emitter's, such as Users_list. Mark the namespace with @operationIds.`,\n\t},\n\tcreate: (context) => ({\n\t\tnamespace(namespace) {\n\t\t\tconst { program } = context;\n\t\t\tif (!isService(program, namespace)) return;\n\t\t\tif (isMarkedNamespace(program, namespace)) return;\n\t\t\tcontext.reportDiagnostic({\n\t\t\t\tformat: { service: namespace.name },\n\t\t\t\ttarget: namespace,\n\t\t\t});\n\t\t},\n\t}),\n});\n",
|
|
16
|
-
"/**\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';\nimport { validateVerbMethods } from './verb-methods';\n\nexport { $lib } from './lib';\nexport { $linter } from './linter';\n\nexport function $onValidate(program: Program): void {\n\t// Before the ids are set: only an `@operationId` written in the spec is\n\t// one then, and the operation it names is not checked.\n\tvalidateVerbMethods(program);\n\tvalidateOperationIds(program);\n\tvalidateOneReplyPerStatus(program);\n}\n\nexport const $decorators = {\n\tNxgt: { operationIds },\n};\n"
|
|
18
|
+
"/**\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';\nimport { queryMethod, validateQueryMethods } from './query-method';\nimport { validateVerbMethods } from './verb-methods';\n\nexport { $lib } from './lib';\nexport { $linter } from './linter';\n\nexport function $onValidate(program: Program): void {\n\tvalidateQueryMethods(program);\n\t// Before the ids are set: only an `@operationId` written in the spec is\n\t// one then, and the operation it names is not checked.\n\tvalidateVerbMethods(program);\n\tvalidateOperationIds(program);\n\tvalidateOneReplyPerStatus(program);\n}\n\nexport const $decorators = {\n\tNxgt: { operationIds, queryMethod },\n};\n"
|
|
17
19
|
],
|
|
18
|
-
"mappings": "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",
|
|
19
|
-
"debugId": "
|
|
20
|
+
"mappings": "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",
|
|
21
|
+
"debugId": "B949BF3E72439B1464756E2164756E21",
|
|
20
22
|
"names": []
|
|
21
23
|
}
|
package/dist/lib.d.ts
CHANGED
|
@@ -8,11 +8,14 @@ export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{
|
|
|
8
8
|
'verb-method-mismatch': {
|
|
9
9
|
readonly default: import("@typespec/compiler").CallableMessage<["operation", "method", "verb", "expected"]>;
|
|
10
10
|
};
|
|
11
|
+
'query-method-not-post': {
|
|
12
|
+
readonly default: import("@typespec/compiler").CallableMessage<["operation", "method"]>;
|
|
13
|
+
};
|
|
11
14
|
'duplicate-status-reply': {
|
|
12
15
|
readonly default: import("@typespec/compiler").CallableMessage<["operation", "status"]>;
|
|
13
16
|
};
|
|
14
17
|
'merged-status-reply': {
|
|
15
18
|
readonly default: import("@typespec/compiler").CallableMessage<["operation", "status"]>;
|
|
16
19
|
};
|
|
17
|
-
}, Record<string, any>, "operationIds" | "operationIdsOptions">;
|
|
20
|
+
}, Record<string, any>, "queryMethod" | "operationIds" | "operationIdsOptions">;
|
|
18
21
|
//# 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;;;;;;;;;;;;;;;;;;;+EAqDf,CAAC"}
|
|
@@ -8,10 +8,6 @@
|
|
|
8
8
|
* Bodies of different content types are one reply, negotiated, and pass.
|
|
9
9
|
*/
|
|
10
10
|
import { type Program } from '@typespec/compiler';
|
|
11
|
-
/**
|
|
12
|
-
* Checks each operation of each service, as the emitter sees it, once: a
|
|
13
|
-
* service nested in another is listed in both. The services' own
|
|
14
|
-
* diagnostics are the emitter's to report.
|
|
15
|
-
*/
|
|
11
|
+
/** Checks each operation of each service, as the emitter sees it, once. */
|
|
16
12
|
export declare function validateOneReplyPerStatus(program: Program): void;
|
|
17
13
|
//# sourceMappingURL=one-reply-per-status.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"one-reply-per-status.d.ts","sourceRoot":"","sources":["../src/one-reply-per-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,
|
|
1
|
+
{"version":3,"file":"one-reply-per-status.d.ts","sourceRoot":"","sources":["../src/one-reply-per-status.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAc,KAAK,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAqC9D,2EAA2E;AAC3E,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAehE"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@queryMethod`: a `POST` that is a `QUERY`, a safe request with its
|
|
3
|
+
* criteria in the body. `@typespec/http` declares no `QUERY` yet, so the
|
|
4
|
+
* operation is sent as a `POST` and the document marks it,
|
|
5
|
+
* `x-nxgt-method: query`, for `@nxgt/openapi-codegen`.
|
|
6
|
+
*/
|
|
7
|
+
import type { DecoratorContext, Operation, Program } from '@typespec/compiler';
|
|
8
|
+
export declare function queryMethod(context: DecoratorContext, target: Operation): void;
|
|
9
|
+
/** Marked with `@queryMethod`. */
|
|
10
|
+
export declare function isQueryMethod(program: Program, operation: Operation): boolean;
|
|
11
|
+
/** Each marked operation is a `POST`, as the emitter sends it. */
|
|
12
|
+
export declare function validateQueryMethods(program: Program): void;
|
|
13
|
+
//# sourceMappingURL=query-method.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query-method.d.ts","sourceRoot":"","sources":["../src/query-method.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,gBAAgB,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAK/E,wBAAgB,WAAW,CAC1B,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,SAAS,GACf,IAAI,CAIN;AAED,kCAAkC;AAClC,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,GAAG,OAAO,CAE7E;AAED,kEAAkE;AAClE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAS3D"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verb-methods.d.ts","sourceRoot":"","sources":["../src/verb-methods.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"verb-methods.d.ts","sourceRoot":"","sources":["../src/verb-methods.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAclD,2EAA2E;AAC3E,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsB1D"}
|
package/dist/verbs.d.ts
CHANGED
|
@@ -15,8 +15,8 @@ export declare const VERBS: Readonly<Record<string, Grammar>>;
|
|
|
15
15
|
/**
|
|
16
16
|
* The HTTP methods each of the library's verbs is sent with: a read is a
|
|
17
17
|
* `GET` (or a `HEAD`), a search may carry its criteria in a `POST` body, a
|
|
18
|
-
* bulk delete its ids. `QUERY
|
|
19
|
-
*
|
|
18
|
+
* bulk delete its ids. A `QUERY`, a `POST` marked `@queryMethod`, is
|
|
19
|
+
* a search too.
|
|
20
20
|
*/
|
|
21
21
|
export declare const METHODS: Readonly<Record<string, readonly string[]>>;
|
|
22
22
|
/**
|
package/docs/README.md
CHANGED
|
@@ -5,6 +5,7 @@ The [package README](../README.md) is the short version.
|
|
|
5
5
|
| Page | Read it when |
|
|
6
6
|
| --- | --- |
|
|
7
7
|
| [Getting started](guide/getting-started.md) | starting a new API project from the `tsp init` template, up to a Hono route |
|
|
8
|
+
| [Request bodies](guide/requests.md) | naming the body of a create, a `PUT` and a `PATCH`: `CreatePostRequest`, `UpdatePostRequest`, `PatchPostRequest` |
|
|
8
9
|
| [Error replies](guide/errors.md) | declaring what an operation answers when it fails |
|
|
9
10
|
| [Pagination](guide/pagination.md) | listing a collection a page at a time |
|
|
10
11
|
| [Authentication](guide/auth.md) | protecting an operation with a `@nxgt/janus` session |
|
package/docs/guide/auth.md
CHANGED
|
@@ -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 createPost(@body post:
|
|
94
|
+
@post createPost(@body post: CreateRequest<Post>): {
|
|
95
95
|
@statusCode _: 201;
|
|
96
96
|
@body post: Post;
|
|
97
97
|
} | BadRequest | AuthenticationRequired | Forbidden;
|
package/docs/guide/columns.md
CHANGED
|
@@ -80,9 +80,9 @@ model Post {
|
|
|
80
80
|
| `Versioned` | `version: integer`, at least 0 | read, update | `version()` |
|
|
81
81
|
| `Actors<Id = uuid>` | `createdBy`, `updatedBy`, `deletedBy: Id \| null` | read | `actors()` |
|
|
82
82
|
|
|
83
|
-
The server sets every read-only column, so `
|
|
84
|
-
`
|
|
85
|
-
`
|
|
83
|
+
The server sets every read-only column, so `CreateRequest<Post>` and
|
|
84
|
+
`PatchRequest<Post>` leave them out, and the generated `CreatePostRequest` and
|
|
85
|
+
`PatchPostRequest` have no `createdAt` a client could send.
|
|
86
86
|
`Actors<string>` or `Actors<integer>` match `actors('text')` or
|
|
87
87
|
`actors('integer')`.
|
|
88
88
|
|
|
@@ -94,7 +94,7 @@ that version, and throws `OptimisticLockError` otherwise. Declare the
|
|
|
94
94
|
`Conflict` it becomes:
|
|
95
95
|
|
|
96
96
|
```tsp
|
|
97
|
-
@patch
|
|
97
|
+
@patch patchPost(@path postId: uuid, @body post: PatchRequest<Post>):
|
|
98
98
|
Post | BadRequest | NotFound | Conflict;
|
|
99
99
|
```
|
|
100
100
|
|
package/docs/guide/errors.md
CHANGED
|
@@ -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 patchPost(@path postId: string, @body post: PatchRequest<Post>): Post | BadRequest;
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
The generator declares the validators' 400 itself, as `ValidationErrorBody`,
|
|
@@ -99,13 +99,16 @@ interface Users {
|
|
|
99
99
|
get(@path id: uuid): User | GetErrors;
|
|
100
100
|
|
|
101
101
|
@post
|
|
102
|
-
create(@body user:
|
|
102
|
+
create(@body user: CreateRequest<User>): {
|
|
103
103
|
@statusCode _: 201;
|
|
104
104
|
@body user: User;
|
|
105
105
|
} | CreateErrors;
|
|
106
106
|
|
|
107
|
+
@put
|
|
108
|
+
update(@path id: uuid, @body user: UpdateRequest<User>): User | UpdateErrors;
|
|
109
|
+
|
|
107
110
|
@patch
|
|
108
|
-
|
|
111
|
+
patch(@path id: uuid, @body user: PatchRequest<User>): User | UpdateErrors;
|
|
109
112
|
|
|
110
113
|
@delete
|
|
111
114
|
delete(@path id: uuid): NoContentResponse | DeleteErrors;
|
|
@@ -113,7 +116,10 @@ interface Users {
|
|
|
113
116
|
```
|
|
114
117
|
|
|
115
118
|
`@operationIds` names the operations `listUsers`, `getUser`, `createUser`,
|
|
116
|
-
`updateUser` and `deleteUser` ([Operation ids](operation-ids.md)).
|
|
119
|
+
`updateUser`, `patchUser` and `deleteUser` ([Operation ids](operation-ids.md)).
|
|
120
|
+
`update` replaces the whole user with a `PUT` (`UpdateRequest`), `patch`
|
|
121
|
+
changes some of it with a merge patch (`PatchRequest`); see
|
|
122
|
+
[Request bodies](requests.md).
|
|
117
123
|
|
|
118
124
|
```ts
|
|
119
125
|
// openapi-codegen.config.ts
|
|
@@ -250,13 +256,16 @@ interface Pets {
|
|
|
250
256
|
get(@path id: uuid): Pet | GetErrors;
|
|
251
257
|
|
|
252
258
|
@post
|
|
253
|
-
create(@body pet:
|
|
259
|
+
create(@body pet: CreateRequest<Pet>): {
|
|
254
260
|
@statusCode _: 201;
|
|
255
261
|
@body pet: Pet;
|
|
256
262
|
} | CreateErrors;
|
|
257
263
|
|
|
264
|
+
@put
|
|
265
|
+
update(@path id: uuid, @body pet: UpdateRequest<Pet>): Pet | UpdateErrors;
|
|
266
|
+
|
|
258
267
|
@patch
|
|
259
|
-
|
|
268
|
+
patch(@path id: uuid, @body pet: PatchRequest<Pet>): Pet | UpdateErrors;
|
|
260
269
|
|
|
261
270
|
@delete
|
|
262
271
|
delete(@path id: uuid): NoContentResponse | DeleteErrors;
|
|
@@ -264,8 +273,8 @@ interface Pets {
|
|
|
264
273
|
```
|
|
265
274
|
|
|
266
275
|
The operation names stay; `@operationIds` takes the resource from the
|
|
267
|
-
interface, so the ids become `listPets`, `getPet`, `createPet`, `updatePet
|
|
268
|
-
and `deletePet`. Run `tsp compile .` and `nxgt-openapi generate` again, and
|
|
276
|
+
interface, so the ids become `listPets`, `getPet`, `createPet`, `updatePet`,
|
|
277
|
+
`patchPet` and `deletePet`. Run `tsp compile .` and `nxgt-openapi generate` again, and
|
|
269
278
|
the generated types, `Pet` and `PetPage`, follow. A name the singular rule
|
|
270
279
|
does not know takes `@operationIds(#{ singular: "…" })` on the interface
|
|
271
280
|
([Operation ids](operation-ids.md)).
|
package/docs/guide/headers.md
CHANGED
|
@@ -19,7 +19,7 @@ namespace Blog;
|
|
|
19
19
|
|
|
20
20
|
@route("/posts")
|
|
21
21
|
interface Posts {
|
|
22
|
-
@post createPost(...IdempotencyKeyHeader, @body post:
|
|
22
|
+
@post createPost(...IdempotencyKeyHeader, @body post: CreateRequest<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 createPost(@body post:
|
|
51
|
+
@post createPost(@body post: CreateRequest<Post>): {
|
|
52
52
|
@statusCode _: 201;
|
|
53
53
|
...RateLimitHeaders;
|
|
54
54
|
@body post: Post;
|
|
@@ -116,7 +116,7 @@ tells a cache, a proxy or a retry that the request is safe. An operation
|
|
|
116
116
|
| Verb | Sent with |
|
|
117
117
|
| --- | --- |
|
|
118
118
|
| `list`, `read`, `find`, `count`, `get` | `GET` or `HEAD` |
|
|
119
|
-
| `search`, `query` | `GET` or `POST
|
|
119
|
+
| `search`, `query` | `GET`, `POST`, or a `POST` marked [`@queryMethod`](#a-query-sent-as-a-post), for criteria too large for a query string |
|
|
120
120
|
| `create`, `createMany` | `POST` |
|
|
121
121
|
| `update`, `updateMany` | `PUT` or `PATCH` |
|
|
122
122
|
| `patch` | `PATCH` |
|
|
@@ -147,7 +147,7 @@ Give the operation its verb's method, or a name that is not a verb, which
|
|
|
147
147
|
is then its id as written:
|
|
148
148
|
|
|
149
149
|
```tsp
|
|
150
|
-
@
|
|
150
|
+
@put @route("/{id}") update(@path id: uuid, @body user: User): User; // updateUser
|
|
151
151
|
@post @route("/lookup") lookUpUser(@body id: uuid): User; // lookUpUser
|
|
152
152
|
```
|
|
153
153
|
|
|
@@ -171,10 +171,64 @@ Not checked:
|
|
|
171
171
|
whatever it says;
|
|
172
172
|
- an operation `@operationIds` does not name.
|
|
173
173
|
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
`
|
|
177
|
-
|
|
174
|
+
## A QUERY sent as a POST
|
|
175
|
+
|
|
176
|
+
`QUERY` is the HTTP method for a safe request with a body, and
|
|
177
|
+
`@typespec/http` 1.17 declares only `GET`, `PUT`, `POST`, `PATCH`, `DELETE`
|
|
178
|
+
and `HEAD`. `@queryMethod` marks a `@post` as the `QUERY` it stands for:
|
|
179
|
+
|
|
180
|
+
```tsp
|
|
181
|
+
import "@typespec/http";
|
|
182
|
+
import "@nxgt/typespec";
|
|
183
|
+
|
|
184
|
+
using Http;
|
|
185
|
+
using Nxgt;
|
|
186
|
+
|
|
187
|
+
@service
|
|
188
|
+
@operationIds
|
|
189
|
+
namespace Shop;
|
|
190
|
+
|
|
191
|
+
model UserCriteria {
|
|
192
|
+
names: string[];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@route("/users")
|
|
196
|
+
interface Users {
|
|
197
|
+
@post @queryMethod @route("/search")
|
|
198
|
+
search(@body criteria: UserCriteria): string[]; // searchUsers
|
|
199
|
+
}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
On the wire it is a `POST`, which every server, proxy, `fetch` and CORS
|
|
203
|
+
policy accepts. The OpenAPI document says what it is, on the operation:
|
|
204
|
+
|
|
205
|
+
```yaml
|
|
206
|
+
/users/search:
|
|
207
|
+
post:
|
|
208
|
+
operationId: searchUsers
|
|
209
|
+
x-nxgt-method: query
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
`@nxgt/openapi-codegen` reads `x-nxgt-method` and marks the operation in the
|
|
213
|
+
generated `operations` table
|
|
214
|
+
([TypeSpec](https://github.com/softistx/nxgt-http/blob/develop/packages/openapi-codegen/docs/guide/typespec.md)). When
|
|
215
|
+
`@typespec/http` declares `QUERY`, the decorator is what changes, not your
|
|
216
|
+
spec ([roadmap](../roadmap.md#later)).
|
|
217
|
+
|
|
218
|
+
| Case | Result |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| `search` or `query` marked | no warning: a `QUERY` is one of their methods |
|
|
221
|
+
| `findById`, `create` or another verb marked | `verb-method-mismatch`, with the method `QUERY`: `findById is sent with QUERY, where its verb find is sent with GET or HEAD` |
|
|
222
|
+
| a name that is not a verb, marked | nothing to check |
|
|
223
|
+
| marked and not a `@post` | the error `query-method-not-post` |
|
|
224
|
+
|
|
225
|
+
```text
|
|
226
|
+
error @nxgt/typespec/query-method-not-post: list is marked @queryMethod and sent with GET: a QUERY is sent as a POST until @typespec/http declares it. Make it a @post.
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
A search that answers an array also meets the linter's `list-returns-page`;
|
|
230
|
+
return a `Page<Item>`, as `search` in the blog fixture does
|
|
231
|
+
(`Page<Author> | BadRequest`).
|
|
178
232
|
|
|
179
233
|
## The resource
|
|
180
234
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# Request bodies
|
|
2
|
+
|
|
3
|
+
`CreateRequest<T>`, `UpdateRequest<T>` and `PatchRequest<T>` are TypeSpec's own
|
|
4
|
+
`Create`, `Update` and `MergePatchUpdate` with a name template, so the body of
|
|
5
|
+
each write is a component with a name you can read: `CreatePostRequest`.
|
|
6
|
+
|
|
7
|
+
## Why names
|
|
8
|
+
|
|
9
|
+
TypeSpec's templates name what they emit from the model: `Create<Post>` is
|
|
10
|
+
the component `CreatePost` and `MergePatchUpdate<Post>` is
|
|
11
|
+
`PostMergePatchUpdate`. Two conventions in one spec, and a type in the
|
|
12
|
+
generated client that reads unlike the rest. The aliases give each body the
|
|
13
|
+
name `<Verb><Model>Request`, and `@nxgt/openapi-codegen` generates the types
|
|
14
|
+
and the Zod schemas under it.
|
|
15
|
+
|
|
16
|
+
## The smallest example
|
|
17
|
+
|
|
18
|
+
```tsp
|
|
19
|
+
import "@typespec/http";
|
|
20
|
+
import "@nxgt/typespec";
|
|
21
|
+
|
|
22
|
+
using Http;
|
|
23
|
+
using Nxgt;
|
|
24
|
+
|
|
25
|
+
namespace Blog;
|
|
26
|
+
|
|
27
|
+
model Post {
|
|
28
|
+
@visibility(Lifecycle.Read) id: uuid;
|
|
29
|
+
@minLength(1) title: string;
|
|
30
|
+
body: string;
|
|
31
|
+
tags?: string[];
|
|
32
|
+
...Timestamps;
|
|
33
|
+
...Versioned;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@route("/posts")
|
|
37
|
+
interface Posts {
|
|
38
|
+
@post createPost(@body post: CreateRequest<Post>): {
|
|
39
|
+
@statusCode _: 201;
|
|
40
|
+
@body post: Post;
|
|
41
|
+
} | BadRequest;
|
|
42
|
+
|
|
43
|
+
@put updatePost(@path postId: uuid, @body post: UpdateRequest<Post>):
|
|
44
|
+
Post | BadRequest | NotFound | Conflict;
|
|
45
|
+
|
|
46
|
+
@patch patchPost(@path postId: uuid, @body post: PatchRequest<Post>):
|
|
47
|
+
Post | BadRequest | NotFound | Conflict;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
| Alias | Is | Component | Content type | Properties |
|
|
52
|
+
| --- | --- | --- | --- | --- |
|
|
53
|
+
| `CreateRequest<T>` | `Create<T, "Create{name}Request">` | `CreatePostRequest` | `application/json` | those visible on create, as `T` declares them |
|
|
54
|
+
| `UpdateRequest<T>` | `Update<T, "Update{name}Request">` | `UpdatePostRequest` | `application/json` | those visible on update, as `T` declares them |
|
|
55
|
+
| `PatchRequest<T>` | `MergePatchUpdate<T, "Patch{name}Request">` | `PatchPostRequest` | `application/merge-patch+json` | those visible on update, all optional |
|
|
56
|
+
|
|
57
|
+
```ts
|
|
58
|
+
import type {
|
|
59
|
+
CreatePostRequest,
|
|
60
|
+
PatchPostRequest,
|
|
61
|
+
UpdatePostRequest,
|
|
62
|
+
} from './generated/types';
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```tsp
|
|
66
|
+
alias CreateRequest<T extends Reflection.Model> = Create<T, "Create{name}Request">;
|
|
67
|
+
alias UpdateRequest<T extends Reflection.Model> = Update<T, "Update{name}Request">;
|
|
68
|
+
alias PatchRequest<T extends Reflection.Model> = MergePatchUpdate<T, "Patch{name}Request">;
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Read-only properties (`id`, `Timestamps`, `Actors`) are in none of the three.
|
|
72
|
+
`version` from `Versioned` is read and update, so it is in
|
|
73
|
+
`UpdatePostRequest` and `PatchPostRequest`, and not in `CreatePostRequest`.
|
|
74
|
+
|
|
75
|
+
## `PUT` replaces, `PATCH` merges
|
|
76
|
+
|
|
77
|
+
`UpdateRequest<Post>` is the whole resource: each property is as optional as
|
|
78
|
+
`Post` declares it, so `title` is required and `tags?` is not. Send it with
|
|
79
|
+
`@put`.
|
|
80
|
+
|
|
81
|
+
`PatchRequest<Post>` is a JSON Merge Patch: every property is optional, and a
|
|
82
|
+
nullable one is sent as `null` to clear it. The body's content type is
|
|
83
|
+
`application/merge-patch+json`, where the others are `application/json`.
|
|
84
|
+
|
|
85
|
+
```yaml
|
|
86
|
+
# openapi.yaml (excerpt)
|
|
87
|
+
PUT:
|
|
88
|
+
requestBody:
|
|
89
|
+
content:
|
|
90
|
+
application/json:
|
|
91
|
+
schema:
|
|
92
|
+
$ref: '#/components/schemas/UpdatePostRequest'
|
|
93
|
+
PATCH:
|
|
94
|
+
requestBody:
|
|
95
|
+
content:
|
|
96
|
+
application/merge-patch+json:
|
|
97
|
+
schema:
|
|
98
|
+
$ref: '#/components/schemas/PatchPostRequest'
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
A client sending a patch sets the header `Content-Type:
|
|
102
|
+
application/merge-patch+json`. The component `UpdatePostRequest` requires
|
|
103
|
+
`title`; `PatchPostRequest` requires nothing.
|
|
104
|
+
|
|
105
|
+
## Nested models
|
|
106
|
+
|
|
107
|
+
A nested model the template filters is named by the same template; one it
|
|
108
|
+
leaves whole keeps its name.
|
|
109
|
+
|
|
110
|
+
```tsp
|
|
111
|
+
model Address {
|
|
112
|
+
@visibility(Lifecycle.Read) id: uuid;
|
|
113
|
+
street: string;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
model Author {
|
|
117
|
+
@visibility(Lifecycle.Read) id: uuid;
|
|
118
|
+
name: string;
|
|
119
|
+
address: Address;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
@post createAuthor(@body author: CreateRequest<Author>): Author;
|
|
123
|
+
// CreateAuthorRequest.address is CreateAddressRequest, without `id`
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
A model with no property the filter changes, such as a `Tag` with only
|
|
127
|
+
`name`, is referenced under its own name, `Tag`.
|
|
128
|
+
|
|
129
|
+
## Your own name template
|
|
130
|
+
|
|
131
|
+
The aliases fix the convention. Another one is the same template, inline, and
|
|
132
|
+
`Create<T>` and `MergePatchUpdate<T>` stay usable:
|
|
133
|
+
|
|
134
|
+
```tsp
|
|
135
|
+
@post createPost(@body post: Create<Post, "New{name}">): Post; // NewPost
|
|
136
|
+
@patch patchPost(@path postId: uuid, @body post: MergePatchUpdate<Post, "{name}Changes">): Post;
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
`{name}` is the model's name. Without a template, TypeSpec names the
|
|
140
|
+
components `CreatePost` and `PostMergePatchUpdate`.
|
|
141
|
+
|
|
142
|
+
## Next
|
|
143
|
+
|
|
144
|
+
The columns a request leaves out are on [Scalars and columns](columns.md); the
|
|
145
|
+
errors a write answers on [Error replies](errors.md).
|
package/docs/roadmap.md
CHANGED
|
@@ -16,16 +16,32 @@ Nothing in progress.
|
|
|
16
16
|
|
|
17
17
|
## Later
|
|
18
18
|
|
|
19
|
-
-
|
|
20
|
-
|
|
21
|
-
`
|
|
22
|
-
already reads an OpenAPI 3.2 `query` operation.
|
|
19
|
+
- **A real `QUERY`.** `@queryMethod` is sent as a `POST` and `search` and
|
|
20
|
+
`query` accept it; once `@typespec/http` declares the HTTP `QUERY`
|
|
21
|
+
method, the decorator sends a `QUERY` and a spec does not change.
|
|
22
|
+
`@nxgt/openapi-codegen` already reads an OpenAPI 3.2 `query` operation.
|
|
23
23
|
- **`ETag` and conditional requests.** `ETag`, with `If-None-Match` and
|
|
24
24
|
`If-Match`, once the `@nxgt/*` packages send one the same way. The
|
|
25
25
|
optimistic lock is the body's `version` today (`Versioned`).
|
|
26
26
|
|
|
27
27
|
## Shipped
|
|
28
28
|
|
|
29
|
+
### 0.12.0
|
|
30
|
+
|
|
31
|
+
- **`@queryMethod`, a `QUERY` sent as a `POST`.** The decorator marks a
|
|
32
|
+
`@post` with `x-nxgt-method: query`, which `@nxgt/openapi-codegen` reads,
|
|
33
|
+
and `search` and `query` accept it: a safe request with a body, today, with
|
|
34
|
+
every server, proxy and `fetch`. On anything but a `@post` it is the error
|
|
35
|
+
`query-method-not-post`.
|
|
36
|
+
|
|
37
|
+
### 0.11.0
|
|
38
|
+
|
|
39
|
+
- **Request body aliases.** `CreateRequest<T>`, `UpdateRequest<T>` and
|
|
40
|
+
`PatchRequest<T>` are TypeSpec's `Create`, `Update` and `MergePatchUpdate`
|
|
41
|
+
named `Create<T>Request`, `Update<T>Request` and `Patch<T>Request`, so the
|
|
42
|
+
generated types and schemas share one convention. `Create<T>` and
|
|
43
|
+
`MergePatchUpdate<T>` stay usable.
|
|
44
|
+
|
|
29
45
|
### 0.10.0
|
|
30
46
|
|
|
31
47
|
- **Every scalar of `@nxgt/graphql-scalars`.** The scalars, under the same
|
package/docs/troubleshooting.md
CHANGED
|
@@ -13,7 +13,7 @@ one name with `name_collision`.
|
|
|
13
13
|
optional, and the generator adds its own 400 beside it.
|
|
14
14
|
|
|
15
15
|
```tsp
|
|
16
|
-
@patch
|
|
16
|
+
@patch patchPost(@path postId: string, @body post: PatchRequest<Post>): Post | BadRequest;
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
## `Couldn't resolve import "@nxgt/typespec"`
|
|
@@ -293,6 +293,48 @@ Or silence that one operation with a reason:
|
|
|
293
293
|
@post @route("/find") findById(@body id: uuid): User;
|
|
294
294
|
```
|
|
295
295
|
|
|
296
|
+
## `list is marked @queryMethod and sent with GET: a QUERY is sent as a POST until @typespec/http declares it. Make it a @post.`
|
|
297
|
+
|
|
298
|
+
**When:** an error, `query-method-not-post`: an operation marked
|
|
299
|
+
`@queryMethod` is sent with a method other than `POST`. The method may be
|
|
300
|
+
implicit, `@get` or none.
|
|
301
|
+
|
|
302
|
+
```tsp
|
|
303
|
+
@route("/users")
|
|
304
|
+
interface Users {
|
|
305
|
+
@get @queryMethod list(): string[];
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
**Why:** `@queryMethod` stands for a `QUERY` sent as a `POST`, because
|
|
310
|
+
`@typespec/http` declares no `QUERY`; on a `GET`, a `PUT` or a `DELETE` it
|
|
311
|
+
has nothing to stand for.
|
|
312
|
+
|
|
313
|
+
**Fix:** make it a `@post`, with the criteria in the body:
|
|
314
|
+
|
|
315
|
+
```tsp
|
|
316
|
+
@post @queryMethod @route("/search") search(@body names: string[]): string[];
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
Or drop `@queryMethod`: a `GET` needs no mark. More, in
|
|
320
|
+
[Operation ids](guide/operation-ids.md#a-query-sent-as-a-post).
|
|
321
|
+
|
|
322
|
+
## `findById is sent with QUERY, where its verb find is sent with GET or HEAD`
|
|
323
|
+
|
|
324
|
+
**When:** a warning, `verb-method-mismatch`: an operation named after one of
|
|
325
|
+
the library's verbs other than `search` and `query` is marked `@queryMethod`.
|
|
326
|
+
|
|
327
|
+
**Why:** a marked `POST` is checked as the `QUERY` it is, and only `search`
|
|
328
|
+
and `query` are sent with `QUERY`.
|
|
329
|
+
|
|
330
|
+
**Fix:** name it `search` or `query`, a name that is not a verb, or take off
|
|
331
|
+
`@queryMethod` and give the verb its own method, as in the entry on
|
|
332
|
+
[`findById is sent with POST`](#findbyid-is-sent-with-post-where-its-verb-find-is-sent-with-get-or-head).
|
|
333
|
+
|
|
334
|
+
```tsp
|
|
335
|
+
@post @queryMethod @route("/search") searchByName(@body names: string[]): User[]; // searchUserByName
|
|
336
|
+
```
|
|
337
|
+
|
|
296
338
|
## `declares a reply without a body and one with a body of status …`
|
|
297
339
|
|
|
298
340
|
**When:** an operation declares, for one status, a reply without a body and
|
package/lib/main.tsp
CHANGED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.Reflection;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Marks a `POST` as a `QUERY`: a safe request, which changes nothing on the
|
|
9
|
+
* server, with its criteria in the body. It is sent as a `POST`, which every
|
|
10
|
+
* server, proxy and `fetch` accepts, and the OpenAPI document says what it is
|
|
11
|
+
* with `x-nxgt-method: query`: `@nxgt/openapi-codegen` reads it. Once
|
|
12
|
+
* `@typespec/http` declares `QUERY`, this is the decorator that changes, not
|
|
13
|
+
* the spec. Only on a `POST`: anything else is the error
|
|
14
|
+
* `query-method-not-post`. `@operationIds` checks its verb as a `QUERY`'s:
|
|
15
|
+
* `search` and `query` take one.
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typespec
|
|
19
|
+
* @route("/users")
|
|
20
|
+
* interface Users {
|
|
21
|
+
* @post @queryMethod @route("/search")
|
|
22
|
+
* search(@body criteria: UserCriteria): User[]; // searchUsers
|
|
23
|
+
* }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
extern dec queryMethod(target: Operation);
|
package/lib/requests.tsp
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import "@typespec/http";
|
|
2
|
+
|
|
3
|
+
using Http;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The body of a create: the properties of `T` visible on create, named
|
|
9
|
+
* `Create<T>Request` (`CreatePostRequest`). A nested model filtered the same
|
|
10
|
+
* way is named alike (`CreateAddressRequest`); one left whole keeps its name.
|
|
11
|
+
*/
|
|
12
|
+
alias CreateRequest<T extends Reflection.Model> = Create<
|
|
13
|
+
T,
|
|
14
|
+
"Create{name}Request"
|
|
15
|
+
>;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The body of a full update, as a `PUT` replaces the resource: the properties
|
|
19
|
+
* of `T` visible on update, each as optional as `T` declares it, named
|
|
20
|
+
* `Update<T>Request` (`UpdatePostRequest`).
|
|
21
|
+
*/
|
|
22
|
+
alias UpdateRequest<T extends Reflection.Model> = Update<
|
|
23
|
+
T,
|
|
24
|
+
"Update{name}Request"
|
|
25
|
+
>;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The body of a partial update, as a JSON Merge Patch
|
|
29
|
+
* (`application/merge-patch+json`): the properties of `T` visible on update,
|
|
30
|
+
* all optional, a nullable one sent as `null` to clear it, named
|
|
31
|
+
* `Patch<T>Request` (`PatchPostRequest`).
|
|
32
|
+
*/
|
|
33
|
+
alias PatchRequest<T extends Reflection.Model> = MergePatchUpdate<
|
|
34
|
+
T,
|
|
35
|
+
"Patch{name}Request"
|
|
36
|
+
>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/typespec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -52,13 +52,13 @@
|
|
|
52
52
|
"typescript": "^6.0.3 || ^7.0.0"
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
|
-
"@nxgt/openapi-codegen": "^0.
|
|
55
|
+
"@nxgt/openapi-codegen": "^0.9.0",
|
|
56
56
|
"@nxgt/openapi-hono": "^0.3.2",
|
|
57
57
|
"@types/bun": "^1.4.0",
|
|
58
|
-
"@typespec/compiler": "1.
|
|
59
|
-
"@typespec/http": "1.
|
|
60
|
-
"@typespec/openapi": "1.
|
|
61
|
-
"@typespec/openapi3": "1.
|
|
58
|
+
"@typespec/compiler": "1.17.0",
|
|
59
|
+
"@typespec/http": "1.17.0",
|
|
60
|
+
"@typespec/openapi": "1.17.0",
|
|
61
|
+
"@typespec/openapi3": "1.17.0",
|
|
62
62
|
"hono": "^4.13.4",
|
|
63
63
|
"zod": "^4.5.4"
|
|
64
64
|
}
|
package/templates/nxgt/main.tsp
CHANGED
|
@@ -50,16 +50,23 @@ interface Users {
|
|
|
50
50
|
|
|
51
51
|
/** Creates a user. */
|
|
52
52
|
@post
|
|
53
|
-
create(@body user:
|
|
53
|
+
create(@body user: CreateRequest<User>): {
|
|
54
54
|
@statusCode _: 201;
|
|
55
55
|
@body user: User;
|
|
56
56
|
} | CreateErrors;
|
|
57
57
|
|
|
58
|
-
/**
|
|
59
|
-
@
|
|
58
|
+
/** Replaces a user: the whole body, as a `PUT` sends it. */
|
|
59
|
+
@put
|
|
60
60
|
update(
|
|
61
61
|
@path id: uuid,
|
|
62
|
-
@body user:
|
|
62
|
+
@body user: UpdateRequest<User>,
|
|
63
|
+
): User | UpdateErrors;
|
|
64
|
+
|
|
65
|
+
/** Updates some of a user's properties, with a merge patch. */
|
|
66
|
+
@patch
|
|
67
|
+
patch(
|
|
68
|
+
@path id: uuid,
|
|
69
|
+
@body user: PatchRequest<User>,
|
|
63
70
|
): User | UpdateErrors;
|
|
64
71
|
|
|
65
72
|
/** Deletes a user. */
|