@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 CHANGED
@@ -89,8 +89,8 @@ namespace Blog;
89
89
 
90
90
  @route("/posts")
91
91
  interface Posts {
92
- @get read(@path postId: string): Post | NotFound | Unauthorized;
93
- @post create(@body post: Create<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 list(@query status?: PostStatus, ...PageParameters): Page<Post>;
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 list(@path postId: string, ...CursorPageParameters): CursorPage<Comment> | BadRequest | NotFound;
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 delete(@path postId: string, @path commentId: string):
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 create(...IdempotencyKeyHeader, @body post: Create<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
- ### Resources
278
+ ### Sorting
279
279
 
280
- One line gives a resource its five operations, with `@operationIds` names:
280
+ Spread the sort into a list, beside its filters and its page:
281
281
 
282
282
  ```tsp
283
- model PostFilters {
284
- @query status?: PostStatus;
285
- @query authorId?: uuid;
283
+ model AuthorFilters {
284
+ @query name?: string;
286
285
  }
287
286
 
288
- @route("/posts")
289
- interface Posts extends Resource<Post, PostFilters, SortField = "createdAt" | "title"> {}
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
- | Operation | Route | Answers |
293
- | --- | --- | --- |
294
- | `listPosts` | `GET /posts`, with the filters, `page`, `pageSize`, `orderBy`, `direction` | `Page<Post>` |
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, `Posts_list`, and the
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 interface to name each operation after its method and
314
- the interface instead:
305
+ `@operationIds` on the service namespace, and each operation's id is its
306
+ name, as written:
315
307
 
316
308
  ```tsp
317
- import "@typespec/openapi"; // for @operationId
318
-
319
- using OpenAPI;
309
+ @service(#{ title: "Blog" })
310
+ @operationIds
311
+ namespace Blog;
320
312
 
321
313
  @route("/posts")
322
- @operationIds
323
314
  interface Posts {
324
- @get list(): Post[]; // listPosts
325
- @get @operationId("getPost") read(@path postId: string): Post | NotFound; // getPost
315
+ @get listPosts(...PageParameters): Page<Post>; // listPosts
316
+ @get getPost(@path postId: uuid): Post | NotFound; // getPost
326
317
  }
327
318
  ```
328
319
 
329
- An operation's own `@operationId` wins. An interface that extends a marked
330
- one, or an instance of a marked template, is named after itself. An operation
331
- of a marked interface that has another operation's id is an error,
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 | names each operation `<operation><Interface>`, unless it has an `@operationId` |
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 of an `@operationIds` interface has another operation's id |
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
- ### Resources
384
+ ### Sorting
394
385
 
395
- | Name | What it is |
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
- - [Resources](docs/guide/resources.md): the five operations of a resource
465
- in one line, with its filters and sort;
466
- - [Operation ids](docs/guide/operation-ids.md): `@operationIds`, and what
467
- the generated client calls each operation;
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 { itemOperation, operationIds } from './operation-ids';
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
@@ -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,EACN,aAAa,EACb,YAAY,EAEZ,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC;AAE7B,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAGlD;AAED,eAAO,MAAM,WAAW;;;;;;;CAGvB,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;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: { description: "The interfaces marked with @operationIds" },
33
- itemOperations: {
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
- getFriendlyName,
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 itemOperation(context, target, item) {
103
- context.program.stateMap($lib.stateKeys.itemOperations).set(target, item);
104
- }
105
- function nounOf(program, operation) {
106
- const item = program.stateMap($lib.stateKeys.itemOperations).get(operation);
107
- const named = item && (getFriendlyName(program, item) ?? (item.name !== "" && !isTemplateInstance(item) ? item.name : undefined));
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 isMarked(program, target) {
111
- return program.stateSet($lib.stateKeys.operationIds).has(target) || target.sourceInterfaces.some((source) => isMarked(program, 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
- return container !== undefined && !isTemplateInstance(container) && isMarked(program, container);
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 ? `${operation.name}${nounOf(program, operation)}` : resolveOperationId(program, operation));
124
- if (seen.has(id) && (named || seen.get(id))) {
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=D1BB8C6D97A7E43C64756E2164756E21
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: { description: 'The interfaces marked with @operationIds' },\n\t\titemOperations: {\n\t\t\tdescription: \"The operations named after an item, and the item's model\",\n\t\t},\n\t},\n});\n",
7
- "/**\n * `@operationIds`: names each operation of an interface after its method and\n * the interface, `<operation><Interface>`, such as `listPets`.\n */\nimport {\n\ttype DecoratorContext,\n\tgetFriendlyName,\n\ttype Interface,\n\tisTemplateInstance,\n\ttype Model,\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; its ids are set once the program is checked. On a\n * template, TypeSpec runs it on each instance, which `isMarked` follows to\n * the interfaces extending it.\n */\nexport function operationIds(\n\tcontext: DecoratorContext,\n\ttarget: Interface,\n): void {\n\tcontext.program.stateSet($lib.stateKeys.operationIds).add(target);\n}\n\n/**\n * Names the operation after one item, `<operation><Item>`, instead of its\n * interface, where `@operationIds` names it: `Resource` marks `read`,\n * `create`, `update` and `delete`, so `Authors` has `readAuthor`, but\n * `listAuthors`. Not public: `Resource`'s alone.\n */\nexport function itemOperation(\n\tcontext: DecoratorContext,\n\ttarget: Operation,\n\titem: Model,\n): void {\n\tcontext.program.stateMap($lib.stateKeys.itemOperations).set(target, item);\n}\n\n/**\n * The name `@operationIds` puts after the operation's: its item's, or its\n * interface's. An item without a name of its own, anonymous or an instance\n * such as `Versioned<Author>`, whose name would be `Versioned` alone, falls\n * back to the interface.\n */\nfunction nounOf(program: Program, operation: Operation): string | undefined {\n\tconst item: Model | undefined = program\n\t\t.stateMap($lib.stateKeys.itemOperations)\n\t\t.get(operation);\n\tconst named =\n\t\titem &&\n\t\t(getFriendlyName(program, item) ??\n\t\t\t(item.name !== '' && !isTemplateInstance(item) ? item.name : undefined));\n\treturn named ?? operation.interface?.name;\n}\n\n/** Marked itself, or extending a marked interface: an instance of a marked template included. */\nfunction isMarked(program: Program, target: Interface): boolean {\n\treturn (\n\t\tprogram.stateSet($lib.stateKeys.operationIds).has(target) ||\n\t\ttarget.sourceInterfaces.some((source) => isMarked(program, source))\n\t);\n}\n\n/**\n * Whether `@operationIds` names the operation's id, or checks it: its\n * interface is marked, and is not a template instance, which is never\n * emitted; only the interfaces extending it are.\n */\nfunction isNamed(program: Program, operation: Operation): boolean {\n\tconst container = operation.interface;\n\treturn (\n\t\tcontainer !== undefined &&\n\t\t!isTemplateInstance(container) &&\n\t\tisMarked(program, container)\n\t);\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 * one of them in a marked interface, are an error: two interfaces of one name\n * in two namespaces, an `@operationId` that `extends` copied, one written\n * elsewhere, or an operation in the service namespace, named after itself. `@typespec/openapi3` would emit both without a word.\n */\nexport function validateOperationIds(program: Program): void {\n\tconst seen = new Map<string, boolean>();\n\tnavigateProgram(program, {\n\t\toperation(operation) {\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; an operation in the service namespace is named\n\t\t\t// after itself.\n\t\t\tconst id =\n\t\t\t\town ??\n\t\t\t\t(named\n\t\t\t\t\t? `${operation.name}${nounOf(program, operation)}`\n\t\t\t\t\t: resolveOperationId(program, operation));\n\t\t\tif (seen.has(id) && (named || seen.get(id))) {\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\tseen.set(id, named || (seen.get(id) ?? false));\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 {\n\titemOperation,\n\toperationIds,\n\tvalidateOperationIds,\n} 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\t'Nxgt.Private': { itemOperation },\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: {\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": "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",
11
- "debugId": "D1BB8C6D97A7E43C64756E2164756E21",
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" | "itemOperations">;
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;;;;;;;;;;0DA4Bf,CAAC"}
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,IAAI;;;;;;;;;;uCA2Bf,CAAC"}
@@ -1,27 +1,24 @@
1
1
  /**
2
- * `@operationIds`: names each operation of an interface after its method and
3
- * the interface, `<operation><Interface>`, such as `listPets`.
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 Model, type Operation, type Program } from '@typespec/compiler';
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 checked. On a
8
- * template, TypeSpec runs it on each instance, which `isMarked` follows to
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
- * one of them in a marked interface, are an error: two interfaces of one name
23
- * in two namespaces, an `@operationId` that `extends` copied, one written
24
- * elsewhere, or an operation in the service namespace, named after itself. `@typespec/openapi3` would emit both without a word.
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;;;GAGG;AACH,OAAO,EACN,KAAK,gBAAgB,EAErB,KAAK,SAAS,EAEd,KAAK,KAAK,EAEV,KAAK,SAAS,EACd,KAAK,OAAO,EACZ,MAAM,oBAAoB,CAAC;AAQ5B;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,SAAS,GACf,IAAI,CAEN;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAC5B,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,SAAS,EACjB,IAAI,EAAE,KAAK,GACT,IAAI,CAEN;AAyCD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA2B3D"}
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
- | [Resources](guide/resources.md) | declaring a resource's five operations at once |
13
- | [Operation ids](guide/operation-ids.md) | naming the generated client's methods after the spec'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 |
@@ -16,10 +16,10 @@ namespace Blog;
16
16
 
17
17
  @route("/posts/{postId}/comments")
18
18
  interface Comments {
19
- @get list(@path postId: string): Comment[];
19
+ @get findPostComments(@path postId: string): Comment[];
20
20
 
21
21
  @useAuth(JanusAuth)
22
- @delete delete(@path postId: string, @path commentId: string):
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 create(@body post: Create<Post>): {
94
+ @post createPost(@body post: Create<Post>): {
95
95
  @statusCode _: 201;
96
96
  @body post: Post;
97
97
  } | BadRequest | AuthenticationRequired | Forbidden;
@@ -80,7 +80,7 @@ that version, and throws `OptimisticLockError` otherwise. Declare the
80
80
  `Conflict` it becomes:
81
81
 
82
82
  ```tsp
83
- @patch update(@path postId: uuid, @body post: MergePatchUpdate<Post>):
83
+ @patch updatePost(@path postId: uuid, @body post: MergePatchUpdate<Post>):
84
84
  Post | BadRequest | NotFound | Conflict;
85
85
  ```
86
86
 
@@ -54,7 +54,7 @@ namespace Blog;
54
54
 
55
55
  @route("/posts")
56
56
  interface Posts {
57
- @get read(@path postId: string): Post | NotFound;
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 update(@path postId: string, @body post: MergePatchUpdate<Post>): Post | BadRequest;
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 read(@path postId: string): Post | NotFound | Unauthorized | Forbidden;
173
+ @get getPost(@path postId: string): Post | NotFound | Unauthorized | Forbidden;
174
174
  ```
175
175
 
176
176
  ## Another status, named
@@ -19,7 +19,7 @@ namespace Blog;
19
19
 
20
20
  @route("/posts")
21
21
  interface Posts {
22
- @post create(...IdempotencyKeyHeader, @body post: Create<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 create(@body post: Create<Post>): {
51
+ @post createPost(@body post: Create<Post>): {
52
52
  @statusCode _: 201;
53
53
  ...RateLimitHeaders;
54
54
  @body post: Post;