@nxgt/typespec 0.2.0 → 0.3.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 CHANGED
@@ -292,10 +292,14 @@ interface Posts extends Resource<Post, PostFilters, SortField = "createdAt" | "t
292
292
  | Operation | Route | Answers |
293
293
  | --- | --- | --- |
294
294
  | `listPosts` | `GET /posts`, with the filters, `page`, `pageSize`, `orderBy`, `direction` | `Page<Post>` |
295
- | `readPosts` | `GET /posts/{id}` | `Post \| NotFound` |
296
- | `createPosts` | `POST /posts`, `Create<Post>` | 201 `Post \| BadRequest \| Conflict` |
297
- | `updatePosts` | `PATCH /posts/{id}`, `MergePatchUpdate<Post>` | `Post \| BadRequest \| NotFound \| Conflict` |
298
- | `deletePosts` | `DELETE /posts/{id}` | 204, `NotFound` |
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)).
299
303
 
300
304
  `orderBy` takes one of the `SortField`s (`SortField` is `"id"` by default), and `direction`
301
305
  `asc` (the default) or `desc`, as `@nxgt/drizzle` sorts a page. `{id}`
package/dist/index.d.ts CHANGED
@@ -7,12 +7,15 @@
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 { operationIds } from './operation-ids';
10
+ import { itemOperation, 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
+ };
17
20
  };
18
21
  //# 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,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"}
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"}
package/dist/index.js CHANGED
@@ -29,7 +29,10 @@ var $lib = createTypeSpecLibrary({
29
29
  }
30
30
  },
31
31
  state: {
32
- operationIds: { description: "The interfaces marked with @operationIds" }
32
+ operationIds: { description: "The interfaces marked with @operationIds" },
33
+ itemOperations: {
34
+ description: "The operations named after an item, and the item's model"
35
+ }
33
36
  }
34
37
  });
35
38
 
@@ -84,6 +87,7 @@ function validateOneReplyPerStatus(program) {
84
87
 
85
88
  // src/operation-ids.ts
86
89
  import {
90
+ getFriendlyName,
87
91
  isTemplateInstance,
88
92
  navigateProgram
89
93
  } from "@typespec/compiler";
@@ -95,6 +99,14 @@ import {
95
99
  function operationIds(context, target) {
96
100
  context.program.stateSet($lib.stateKeys.operationIds).add(target);
97
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;
109
+ }
98
110
  function isMarked(program, target) {
99
111
  return program.stateSet($lib.stateKeys.operationIds).has(target) || target.sourceInterfaces.some((source) => isMarked(program, source));
100
112
  }
@@ -108,7 +120,7 @@ function validateOperationIds(program) {
108
120
  operation(operation) {
109
121
  const named = isNamed(program, operation);
110
122
  const own = getOperationId(program, operation);
111
- const id = own ?? (named ? `${operation.name}${operation.interface?.name}` : resolveOperationId(program, operation));
123
+ const id = own ?? (named ? `${operation.name}${nounOf(program, operation)}` : resolveOperationId(program, operation));
112
124
  if (seen.has(id) && (named || seen.get(id))) {
113
125
  $lib.reportDiagnostic(program, {
114
126
  code: "duplicate-operation-id",
@@ -129,7 +141,8 @@ function $onValidate(program) {
129
141
  validateOneReplyPerStatus(program);
130
142
  }
131
143
  var $decorators = {
132
- Nxgt: { operationIds }
144
+ Nxgt: { operationIds },
145
+ "Nxgt.Private": { itemOperation }
133
146
  };
134
147
  export {
135
148
  $decorators,
@@ -137,5 +150,5 @@ export {
137
150
  $onValidate
138
151
  };
139
152
 
140
- //# debugId=CF9FA1522FD0E32664756E2164756E21
153
+ //# debugId=D1BB8C6D97A7E43C64756E2164756E21
141
154
  //# 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},\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\ttype Interface,\n\tisTemplateInstance,\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/** 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}${operation.interface?.name}`\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 { 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"
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"
9
9
  ],
10
- "mappings": "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",
11
- "debugId": "CF9FA1522FD0E32664756E2164756E21",
10
+ "mappings": "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",
11
+ "debugId": "D1BB8C6D97A7E43C64756E2164756E21",
12
12
  "names": []
13
13
  }
package/dist/lib.d.ts CHANGED
@@ -8,5 +8,5 @@ export declare const $lib: import("@typespec/compiler").TypeSpecLibrary<{
8
8
  'merged-status-reply': {
9
9
  readonly default: import("@typespec/compiler").CallableMessage<["operation", "status"]>;
10
10
  };
11
- }, Record<string, any>, "operationIds">;
11
+ }, Record<string, any>, "operationIds" | "itemOperations">;
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;;;;;;;;;;uCAyBf,CAAC"}
1
+ {"version":3,"file":"lib.d.ts","sourceRoot":"","sources":["../src/lib.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,IAAI;;;;;;;;;;0DA4Bf,CAAC"}
@@ -2,13 +2,20 @@
2
2
  * `@operationIds`: names each operation of an interface after its method and
3
3
  * the interface, `<operation><Interface>`, such as `listPets`.
4
4
  */
5
- import { type DecoratorContext, type Interface, type Program } from '@typespec/compiler';
5
+ import { type DecoratorContext, type Interface, type Model, type Operation, type Program } from '@typespec/compiler';
6
6
  /**
7
7
  * Marks the interface; its ids are set once the program is checked. On a
8
8
  * template, TypeSpec runs it on each instance, which `isMarked` follows to
9
9
  * the interfaces extending it.
10
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
19
  /**
13
20
  * Sets the ids, after every decorator, so the order of `@operationId` and
14
21
  * `@operationIds` in the source does not matter. Two operations with one id,
@@ -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,EACrB,KAAK,SAAS,EAId,KAAK,OAAO,EACZ,MAAM,oBAAoB,CAAC;AAQ5B;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,OAAO,EAAE,gBAAgB,EACzB,MAAM,EAAE,SAAS,GACf,IAAI,CAEN;AAwBD;;;;;;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;;;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"}
@@ -84,9 +84,13 @@ interface Pets extends Resource<Pet> {} // listPets
84
84
  interface Orders extends Resource<Order> {} // listOrders
85
85
  ```
86
86
 
87
+ The library's own `Resource` goes one step further: `list` is named after
88
+ the interface, and `read`, `create`, `update` and `delete` after the item,
89
+ `readPet` ([Resources](resources.md)).
90
+
87
91
  ## Two operations named alike
88
92
 
89
- An id must be unique, and four cases break that:
93
+ An id must be unique, and five cases break that:
90
94
 
91
95
  - the namespace is not part of the id, so `Store.Pets` and `Shelter.Pets`
92
96
  would both name their `list` `listPets`;
@@ -95,7 +99,10 @@ An id must be unique, and four cases break that:
95
99
  - an `@operationId` written on another operation, in any interface, takes
96
100
  the id `@operationIds` gives;
97
101
  - an operation declared in the service namespace is named after itself, so
98
- `op listPets()` takes the id of `Pets.list`.
102
+ `op listPets()` takes the id of `Pets.list`;
103
+ - two interfaces extend `Resource` with one item, so `Authors` and
104
+ `ArchivedAuthors` would both name their `read` `readAuthor`
105
+ ([Resources](resources.md#two-resources-of-one-item)).
99
106
 
100
107
  `@operationIds` refuses each with `duplicate-operation-id`, where
101
108
  `@typespec/openapi3` would emit both without a word. Give one of them its own
@@ -33,14 +33,55 @@ interface Authors extends Resource<Author, AuthorFilters, SortField = "name" | "
33
33
  | Operation | Route | Body | Answers |
34
34
  | --- | --- | --- | --- |
35
35
  | `listAuthors` | `GET /authors` | | `Page<Author>` |
36
- | `readAuthors` | `GET /authors/{id}` | | `Author`, `NotFound` |
37
- | `createAuthors` | `POST /authors` | `Create<Author>` | 201 `Author`, `BadRequest`, `Conflict` |
38
- | `updateAuthors` | `PATCH /authors/{id}` | `MergePatchUpdate<Author>` | `Author`, `BadRequest`, `NotFound`, `Conflict` |
39
- | `deleteAuthors` | `DELETE /authors/{id}` | | 204, `NotFound` |
36
+ | `readAuthor` | `GET /authors/{id}` | | `Author`, `NotFound` |
37
+ | `createAuthor` | `POST /authors` | `Create<Author>` | 201 `Author`, `BadRequest`, `Conflict` |
38
+ | `updateAuthor` | `PATCH /authors/{id}` | `MergePatchUpdate<Author>` | `Author`, `BadRequest`, `NotFound`, `Conflict` |
39
+ | `deleteAuthor` | `DELETE /authors/{id}` | | 204, `NotFound` |
40
+
41
+ The template carries `@operationIds`. `list` is named after the interface
42
+ that extends it, which holds the many, and `read`, `create`, `update` and
43
+ `delete` after `Item`, the one: `listAuthors`, `readAuthor`. The item's
44
+ name is its `@friendlyName`, if it has one. An item without a name of its
45
+ own, an anonymous model or an instance such as `Draft<Author>`, falls back to
46
+ the interface: `readDrafts`. An operation the extending interface declares
47
+ again is named after the interface too, unless it has its own `@operationId`. The generated `operations` has
48
+ one entry per id:
40
49
 
41
- The template carries `@operationIds`, so each operation is named after the
42
- interface that extends it. The generator's own 400 is on every operation
43
- that takes a parameter or a body, as always.
50
+ ```ts
51
+ import { operations } from './generated/operations';
52
+
53
+ Object.keys(operations);
54
+ // ['listAuthors', 'createAuthor', 'readAuthor', 'updateAuthor', 'deleteAuthor']
55
+ ```
56
+
57
+ The generator's own 400 is on every operation that takes a parameter or a
58
+ body, as always.
59
+
60
+ ## Two resources of one item
61
+
62
+ Two interfaces extending `Resource` with one `Item` share `readAuthor`,
63
+ `createAuthor`, `updateAuthor` and `deleteAuthor`, which is an error,
64
+ `duplicate-operation-id`. Declare the operations of the second again, each
65
+ with its own `@operationId`, as an interface that extends overrides the
66
+ template's. Use the resource's `Id` in place of `uuid`, if it names one:
67
+
68
+ ```tsp
69
+ import "@typespec/openapi";
70
+
71
+ using OpenAPI;
72
+
73
+ @route("/archived-authors")
74
+ interface ArchivedAuthors extends Resource<Author> {
75
+ @get @operationId("readArchivedAuthor") read(@path id: uuid): Author | NotFound;
76
+ @post @operationId("createArchivedAuthor") create(@body item: Create<Author>): {
77
+ @statusCode _: 201;
78
+ @body item: Author;
79
+ } | BadRequest | Conflict;
80
+ @patch @operationId("updateArchivedAuthor")
81
+ update(@path id: uuid, @body item: MergePatchUpdate<Author>): Author | BadRequest | NotFound | Conflict;
82
+ @delete @operationId("deleteArchivedAuthor") delete(@path id: uuid): NoContentResponse | NotFound;
83
+ }
84
+ ```
44
85
 
45
86
  ## The template's arguments
46
87
 
@@ -110,3 +151,7 @@ interface Comments extends Resource<Comment> {
110
151
  Add `@useAuth(JanusAuth)` on the interface to protect all five, and name the
111
152
  guards' replies in the operations you declare again
112
153
  ([Authentication](auth.md)).
154
+
155
+ A `read`, `create`, `update` or `delete` declared again is named after the
156
+ interface, `readComments`, not after the item: give it
157
+ `@operationId("readComment")` to keep the item's name.
package/docs/roadmap.md CHANGED
@@ -6,11 +6,9 @@ generated by `@nxgt/openapi-codegen` in CI before it ships.
6
6
 
7
7
  ## Now
8
8
 
9
- - **One reply per status.** An operation that declares a reply without a
10
- body beside one with a body, such as `AuthenticationRequired` and
11
- `Unauthorized`, is an error, `duplicate-status-reply`, and two replies with
12
- a body of one status code a warning, `merged-status-reply`, instead of
13
- replies the emitter merges without a word.
9
+ - **Resource ids in the singular.** `Resource` names `read`, `create`,
10
+ `update` and `delete` after its item, `readPost`, and `list` after the
11
+ interface, `listPosts`, instead of `readPosts`.
14
12
 
15
13
  ## Later
16
14
 
@@ -20,6 +18,14 @@ generated by `@nxgt/openapi-codegen` in CI before it ships.
20
18
 
21
19
  ## Shipped
22
20
 
21
+ ### 0.2.0
22
+
23
+ - **One reply per status.** An operation that declares a reply without a
24
+ body beside one with a body, such as `AuthenticationRequired` and
25
+ `Unauthorized`, is an error, `duplicate-status-reply`, and two replies with
26
+ a body of one status code a warning, `merged-status-reply`, instead of
27
+ replies the emitter merges without a word.
28
+
23
29
  ### 0.1.0
24
30
 
25
31
  - **Error replies.** The envelope `@nxgt/openapi-hono` sends, and the
@@ -60,17 +60,22 @@ operation:
60
60
  `extends` copies it as is;
61
61
  - an `@operationId` written elsewhere takes the id `@operationIds` gives;
62
62
  - an operation declared in the service namespace is named after itself:
63
- `op listPets()` takes the id of `Pets.list`.
63
+ `op listPets()` takes the id of `Pets.list`;
64
+ - two interfaces extend `Resource` with one item: `Authors` and
65
+ `ArchivedAuthors` both give `readAuthor`, as `Resource` names `read`,
66
+ `create`, `update` and `delete` after the item.
64
67
 
65
68
  ```text
66
69
  error @nxgt/typespec/duplicate-operation-id: Two operations are named listPets: an OpenAPI operation id must be unique.
67
70
  ```
68
71
 
69
- **Why:** `@operationIds` names each operation `<operation><Interface>`, without
70
- the namespace. `@typespec/openapi3` would emit both ids without a word, and
72
+ **Why:** `@operationIds` names each operation `<operation><Interface>`
73
+ (`<operation><Item>` for `Resource`'s `read`, `create`, `update` and
74
+ `delete`), without the namespace. `@typespec/openapi3` would emit both ids without a word, and
71
75
  the generator would then refuse the spec.
72
76
 
73
- **Fix:** give one of them its own `@operationId`. An inherited operation takes
77
+ **Fix:** give one of them its own `@operationId`, for a `Resource` in the
78
+ interface that extends it ([Resources](guide/resources.md#two-resources-of-one-item)). An inherited operation takes
74
79
  one by being declared again in the interface that extends:
75
80
 
76
81
  ```tsp
@@ -20,3 +20,11 @@ namespace Nxgt;
20
20
  * ```
21
21
  */
22
22
  extern dec operationIds(target: Interface);
23
+
24
+ namespace Private {
25
+ /**
26
+ * Names the operation after `item`, `<operation><Item>`, where
27
+ * `@operationIds` names it. `Resource`'s alone: not public.
28
+ */
29
+ extern dec itemOperation(target: Operation, item: Model);
30
+ }
package/lib/resources.tsp CHANGED
@@ -23,8 +23,9 @@ model SortParameters<Field extends string> {
23
23
  * The five operations of a resource, at `/<route>` and `/<route>/{id}`:
24
24
  * `list` pages by offset, with the `Filters` the spec writes and a sort by
25
25
  * `SortField`; `read`, `create`, `update` (a merge patch) and `delete`.
26
- * Extend it with `@route`; `@operationIds` names the operations after the
27
- * interface that extends it: `listPosts`, `readPosts`…
26
+ * Extend it with `@route`; `@operationIds` names `list` after the interface
27
+ * that extends it, and the others after `Item`: `listPosts`, `readPost`,
28
+ * `createPost`, `updatePost`, `deletePost`.
28
29
  *
29
30
  * @template Item The resource's model.
30
31
  * @template Filters A model of `@query` properties, spread into `list`.
@@ -43,10 +44,12 @@ interface Resource<
43
44
  list(...Filters, ...PageParameters, ...SortParameters<SortField>): Page<Item>;
44
45
 
45
46
  /** One resource. */
47
+ @Private.itemOperation(Item)
46
48
  @get
47
49
  read(@path id: Id): Item | NotFound;
48
50
 
49
51
  /** Creates a resource. */
52
+ @Private.itemOperation(Item)
50
53
  @post
51
54
  create(@body item: Create<Item>): {
52
55
  @statusCode _: 201;
@@ -54,10 +57,12 @@ interface Resource<
54
57
  } | BadRequest | Conflict;
55
58
 
56
59
  /** Updates a resource with a merge patch. */
60
+ @Private.itemOperation(Item)
57
61
  @patch
58
62
  update(@path id: Id, @body item: MergePatchUpdate<Item>): Item | BadRequest | NotFound | Conflict;
59
63
 
60
64
  /** Deletes a resource. */
65
+ @Private.itemOperation(Item)
61
66
  @delete
62
67
  delete(@path id: Id): NoContentResponse | NotFound;
63
68
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nxgt/typespec",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",