@nxgt/typespec 0.7.0 → 0.9.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 +81 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +123 -1
- package/dist/index.js.map +10 -5
- package/dist/linter.d.ts +2 -0
- package/dist/linter.d.ts.map +1 -0
- package/dist/operation-ids.d.ts +9 -0
- package/dist/operation-ids.d.ts.map +1 -1
- package/dist/rules/error-body-shape.d.ts +4 -0
- package/dist/rules/error-body-shape.d.ts.map +1 -0
- package/dist/rules/list-returns-page.d.ts +4 -0
- package/dist/rules/list-returns-page.d.ts.map +1 -0
- package/dist/rules/service-operation-ids.d.ts +4 -0
- package/dist/rules/service-operation-ids.d.ts.map +1 -0
- package/dist/rules/written.d.ts +8 -0
- package/dist/rules/written.d.ts.map +1 -0
- package/docs/README.md +2 -0
- package/docs/guide/getting-started.md +277 -0
- package/docs/guide/linter.md +257 -0
- package/docs/roadmap.md +24 -4
- package/docs/troubleshooting.md +194 -0
- package/package.json +2 -1
- package/templates/nxgt/README.md +33 -0
- package/templates/nxgt/main.tsp +68 -0
- package/templates/nxgt/openapi-codegen.config.ts +7 -0
- package/templates/scaffolding.json +37 -0
package/docs/troubleshooting.md
CHANGED
|
@@ -27,6 +27,67 @@ no `package.json` to resolve it from.
|
|
|
27
27
|
devDependencies, with the command in the
|
|
28
28
|
[README's Install section](../README.md#install).
|
|
29
29
|
|
|
30
|
+
## `init-template-invalid-json: Unable to parse …/scaffolding.json: Unexpected token … is not valid JSON`
|
|
31
|
+
|
|
32
|
+
**When:** `tsp init` with the package's template, before it writes anything.
|
|
33
|
+
|
|
34
|
+
```text
|
|
35
|
+
error init-template-invalid-json: Unable to parse https://unpkg.com/@nxgt/typespec@0.8.0/templates/scaffolding.json: Unexpected token 'N', "Not found:"... is not valid JSON. Check that the template URL is correct.
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Why:** the URL names no template, so unpkg answers a page of text, `Not
|
|
39
|
+
found: …` or `Package version not found: …`, which `tsp init` cannot parse.
|
|
40
|
+
The template ships from 0.9.0 on: a version before it in the URL, a version
|
|
41
|
+
that is not published, or a typo in the path all answer that page.
|
|
42
|
+
|
|
43
|
+
**Fix:** drop the version, or name 0.9.0 or later, and keep the path as it is:
|
|
44
|
+
|
|
45
|
+
```sh
|
|
46
|
+
npm view @nxgt/typespec version # 0.9.0 or later
|
|
47
|
+
npx --package=@typespec/compiler tsp init https://unpkg.com/@nxgt/typespec/templates/scaffolding.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## `init-template-download-failed: Failed to download template from …: fetch failed`
|
|
51
|
+
|
|
52
|
+
**When:** `tsp init` with the package's template, before it writes anything.
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
error init-template-download-failed: Failed to download template from https://unpkg.com/@nxgt/typespec/templates/scaffolding.json: fetch failed. Check that the template URL is correct.
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Why:** `tsp init` could not reach the host: no network, a firewall or
|
|
59
|
+
proxy in the way, or a host name mistyped. Nothing answered, unlike the entry
|
|
60
|
+
above, where unpkg answers a page that is not the template.
|
|
61
|
+
|
|
62
|
+
**Fix:** check that the host answers from that machine, then run `tsp init`
|
|
63
|
+
again:
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
curl -fsSI https://unpkg.com/@nxgt/typespec/templates/scaffolding.json
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## `env: 'bun': No such file or directory`
|
|
70
|
+
|
|
71
|
+
**When:** `nxgt-openapi generate` runs, through `npx`, an npm script such as
|
|
72
|
+
the template's `api`, or a CI step, on a machine without Bun. A shell in
|
|
73
|
+
another locale quotes it `‘bun’`; macOS prints `env: bun: No such file or
|
|
74
|
+
directory`. The exit code is 127.
|
|
75
|
+
|
|
76
|
+
**Why:** `@nxgt/openapi-codegen`'s CLI, `nxgt-openapi`, runs on
|
|
77
|
+
[Bun](https://bun.sh): its first line is `#!/usr/bin/env bun`. `npx` and
|
|
78
|
+
`npm run` find the script, then the system looks for `bun` to run it.
|
|
79
|
+
`tsp compile` runs on Node and is not affected.
|
|
80
|
+
|
|
81
|
+
**Fix:** install Bun, and make sure `bun` is on the PATH of the shell or the
|
|
82
|
+
CI job that runs the generator:
|
|
83
|
+
|
|
84
|
+
```sh
|
|
85
|
+
curl -fsSL https://bun.sh/install | bash
|
|
86
|
+
bunx nxgt-openapi generate
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
In GitHub Actions, add `oven-sh/setup-bun@v2` before the step.
|
|
90
|
+
|
|
30
91
|
## `Duplicate type name: 'NotFoundBody'`
|
|
31
92
|
|
|
32
93
|
**When:** `tsp compile` runs on a spec that declares its own model named like
|
|
@@ -295,6 +356,139 @@ Two plain bodies, `Post | Draft`, are one reply whose body is either; two
|
|
|
295
356
|
description; bodies of different content types are negotiated. All three
|
|
296
357
|
pass.
|
|
297
358
|
|
|
359
|
+
## `returns an array: return Page<Item> or CursorPage<Item>`
|
|
360
|
+
|
|
361
|
+
**When:** a warning, with the ruleset `@nxgt/typespec/recommended` on: an
|
|
362
|
+
operation named `list`, `search` or `query`, alone or before a capital
|
|
363
|
+
letter, has a success reply whose body is an array.
|
|
364
|
+
|
|
365
|
+
```tsp
|
|
366
|
+
@route("/users")
|
|
367
|
+
interface Users {
|
|
368
|
+
@get list(): User[];
|
|
369
|
+
}
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
```text
|
|
373
|
+
warning @nxgt/typespec/list-returns-page: list returns an array: return Page<Item> or CursorPage<Item>, which can carry a total or a next cursor.
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**Why:** an array cannot grow a `total` or a `nextCursor` later without
|
|
377
|
+
breaking its clients. A page can.
|
|
378
|
+
|
|
379
|
+
**Fix:** return a page ([Pagination](guide/pagination.md)):
|
|
380
|
+
|
|
381
|
+
```tsp
|
|
382
|
+
@get list(...PageParameters): Page<User>;
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Or, for a list that will never page, silence that one operation:
|
|
386
|
+
|
|
387
|
+
```tsp
|
|
388
|
+
#suppress "@nxgt/typespec/list-returns-page" "a fixed set of ten"
|
|
389
|
+
@get @route("/top") listTop(): User[];
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
## `The service … has no @operationIds: its ids are the emitter's, such as Users_list`
|
|
393
|
+
|
|
394
|
+
**When:** a warning, with the ruleset `@nxgt/typespec/recommended` on: a
|
|
395
|
+
`@service` namespace is not marked with `@operationIds`, neither itself nor
|
|
396
|
+
a namespace around it. `@operationIds` on its interfaces alone does not
|
|
397
|
+
count.
|
|
398
|
+
|
|
399
|
+
```tsp
|
|
400
|
+
@service
|
|
401
|
+
namespace Shop {
|
|
402
|
+
@route("/health") @get op health(): void;
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
```text
|
|
407
|
+
warning @nxgt/typespec/service-operation-ids: The service Shop has no @operationIds: its ids are the emitter's, such as Users_list. Mark the namespace with @operationIds.
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
**Why:** the emitter names each operation after its interface,
|
|
411
|
+
`Users_list`, and the generated client's methods and types take that name.
|
|
412
|
+
|
|
413
|
+
**Fix:** mark the namespace ([Operation ids](guide/operation-ids.md)):
|
|
414
|
+
|
|
415
|
+
```tsp
|
|
416
|
+
@service
|
|
417
|
+
@operationIds
|
|
418
|
+
namespace Shop {
|
|
419
|
+
@route("/health") @get op health(): void;
|
|
420
|
+
}
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Marking an existing service renames its operations, and the generated
|
|
424
|
+
client's methods with them. To keep the old ids, silence the rule on that
|
|
425
|
+
namespace:
|
|
426
|
+
|
|
427
|
+
```tsp
|
|
428
|
+
#suppress "@nxgt/typespec/service-operation-ids" "its clients already call Health_check"
|
|
429
|
+
@service
|
|
430
|
+
namespace Health {
|
|
431
|
+
@route("/health") @get op check(): void;
|
|
432
|
+
}
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
## `answers … with a body that is not the nxgt envelope`
|
|
436
|
+
|
|
437
|
+
**When:** a warning, with the ruleset `@nxgt/typespec/recommended` on: a
|
|
438
|
+
reply of status 400 or above, of a range from 400 up, or `default` (an
|
|
439
|
+
`@error` model without `@statusCode`) has a body that is not a model with
|
|
440
|
+
`status`, `message` and `timestamp`.
|
|
441
|
+
|
|
442
|
+
```tsp
|
|
443
|
+
model Teapot {
|
|
444
|
+
@statusCode _: 418;
|
|
445
|
+
@body body: { reason: string };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
@route("/users")
|
|
449
|
+
interface Users {
|
|
450
|
+
@post create(@body user: User): User | Teapot;
|
|
451
|
+
}
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
```text
|
|
455
|
+
warning @nxgt/typespec/error-body-shape: create answers 418 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>.
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
A range is named `500-599`, and `default` `*`.
|
|
459
|
+
|
|
460
|
+
**Why:** `@nxgt/openapi-hono` answers its own 400 and 500 with the envelope,
|
|
461
|
+
and a client reads `message` from every error. Another shape is one more
|
|
462
|
+
case for each client.
|
|
463
|
+
|
|
464
|
+
**Fix:** declare one of the library's errors, `ErrorResponse<Status>`, or a
|
|
465
|
+
body that spreads or extends `ErrorBody<Status>` ([Error replies](guide/errors.md)):
|
|
466
|
+
|
|
467
|
+
```tsp
|
|
468
|
+
model TeapotBody {
|
|
469
|
+
...ErrorBody<418>;
|
|
470
|
+
reason: string;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
@error
|
|
474
|
+
model Teapot {
|
|
475
|
+
@statusCode _: 418;
|
|
476
|
+
@body body: TeapotBody;
|
|
477
|
+
}
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
A reply without a body, such as `AuthenticationRequired`, passes. For a
|
|
481
|
+
spec whose errors have another shape on purpose, turn the rule off in
|
|
482
|
+
`tspconfig.yaml` ([Linter](guide/linter.md#turn-a-rule-off)):
|
|
483
|
+
|
|
484
|
+
```yaml
|
|
485
|
+
linter:
|
|
486
|
+
extends:
|
|
487
|
+
- '@nxgt/typespec/recommended'
|
|
488
|
+
disable:
|
|
489
|
+
'@nxgt/typespec/error-body-shape': 'the legacy routes answer their own errors'
|
|
490
|
+
```
|
|
491
|
+
|
|
298
492
|
## `Unknown decorator @operationId`
|
|
299
493
|
|
|
300
494
|
**When:** a spec writes `@operationId` next to `@operationIds`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nxgt/typespec",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"dist",
|
|
11
11
|
"lib",
|
|
12
|
+
"templates",
|
|
12
13
|
"docs",
|
|
13
14
|
"README.md",
|
|
14
15
|
"package.json",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
The API is written in TypeSpec, in `main.tsp`, with the
|
|
4
|
+
[`@nxgt/typespec`](https://www.npmjs.com/package/@nxgt/typespec) conventions.
|
|
5
|
+
|
|
6
|
+
Compile it to `openapi/openapi.yaml`, then generate the types, validators
|
|
7
|
+
and routes into `src/generated`. The generator, `nxgt-openapi`, runs on
|
|
8
|
+
[Bun](https://bun.sh): it needs `bun` on the PATH, even through `npx` or an
|
|
9
|
+
npm script.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npx tsp compile .
|
|
13
|
+
npx nxgt-openapi generate
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Chain both in `package.json`, so the code never lags the spec:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"scripts": {
|
|
21
|
+
"api": "tsp compile . && nxgt-openapi generate",
|
|
22
|
+
"api:check": "tsp compile . && nxgt-openapi generate --check"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
`tspconfig.yaml` turns on the linter's recommended rules, which warn of a
|
|
28
|
+
list without a page, a service without `@operationIds`, and an error reply
|
|
29
|
+
without the envelope.
|
|
30
|
+
|
|
31
|
+
The next steps, serving the routes with Hono and renaming the `Users`
|
|
32
|
+
resource, are in
|
|
33
|
+
[Getting started](https://github.com/softistx/nxgt-http/blob/develop/packages/typespec/docs/guide/getting-started.md).
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
// The API's spec. `tsp compile .` writes openapi/openapi.yaml, and
|
|
2
|
+
// `nxgt-openapi generate` the types, validators and routes from it.
|
|
3
|
+
import "@typespec/http";
|
|
4
|
+
import "@nxgt/typespec";
|
|
5
|
+
|
|
6
|
+
using Http;
|
|
7
|
+
using Nxgt;
|
|
8
|
+
|
|
9
|
+
/** The {{name}} API. */
|
|
10
|
+
@service(#{ title: "{{name}}" })
|
|
11
|
+
@operationIds
|
|
12
|
+
namespace Api;
|
|
13
|
+
|
|
14
|
+
/** Someone who uses the API. */
|
|
15
|
+
model User {
|
|
16
|
+
@visibility(Lifecycle.Read)
|
|
17
|
+
id: uuid;
|
|
18
|
+
|
|
19
|
+
@minLength(1)
|
|
20
|
+
@maxLength(80)
|
|
21
|
+
name: string;
|
|
22
|
+
|
|
23
|
+
email: email;
|
|
24
|
+
|
|
25
|
+
...Timestamps;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** What the user list filters on. */
|
|
29
|
+
model UserFilters {
|
|
30
|
+
/** Users of this name. */
|
|
31
|
+
@query
|
|
32
|
+
name?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Verbs @operationIds completes: list is listUsers, get is getUser.
|
|
36
|
+
@route("/users")
|
|
37
|
+
@tag("Users")
|
|
38
|
+
interface Users {
|
|
39
|
+
/** One page of the users, filtered and sorted. */
|
|
40
|
+
@get
|
|
41
|
+
list(
|
|
42
|
+
...UserFilters,
|
|
43
|
+
...PageParameters,
|
|
44
|
+
...SortParameters<"name" | "createdAt">,
|
|
45
|
+
): Page<User> | ListErrors;
|
|
46
|
+
|
|
47
|
+
/** One user. */
|
|
48
|
+
@get
|
|
49
|
+
get(@path id: uuid): User | GetErrors;
|
|
50
|
+
|
|
51
|
+
/** Creates a user. */
|
|
52
|
+
@post
|
|
53
|
+
create(@body user: Create<User>): {
|
|
54
|
+
@statusCode _: 201;
|
|
55
|
+
@body user: User;
|
|
56
|
+
} | CreateErrors;
|
|
57
|
+
|
|
58
|
+
/** Updates a user with a merge patch. */
|
|
59
|
+
@patch
|
|
60
|
+
update(
|
|
61
|
+
@path id: uuid,
|
|
62
|
+
@body user: MergePatchUpdate<User>,
|
|
63
|
+
): User | UpdateErrors;
|
|
64
|
+
|
|
65
|
+
/** Deletes a user. */
|
|
66
|
+
@delete
|
|
67
|
+
delete(@path id: uuid): NoContentResponse | DeleteErrors;
|
|
68
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nxgt": {
|
|
3
|
+
"title": "nxgt REST API",
|
|
4
|
+
"description": "A REST API with the @nxgt conventions: ids as written, pages, the error envelope and the linter, generated with @nxgt/openapi-codegen.",
|
|
5
|
+
"compilerVersion": "1.16.0",
|
|
6
|
+
"libraries": [
|
|
7
|
+
"@typespec/http",
|
|
8
|
+
"@typespec/openapi",
|
|
9
|
+
"@nxgt/typespec",
|
|
10
|
+
"@nxgt/openapi-codegen"
|
|
11
|
+
],
|
|
12
|
+
"config": {
|
|
13
|
+
"linter": {
|
|
14
|
+
"extends": ["@nxgt/typespec/recommended"]
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"emitters": {
|
|
18
|
+
"@typespec/openapi3": {
|
|
19
|
+
"selected": true,
|
|
20
|
+
"label": "OpenAPI 3.1 document",
|
|
21
|
+
"options": {
|
|
22
|
+
"emitter-output-dir": "{project-root}/openapi",
|
|
23
|
+
"openapi-versions": ["3.1.0"],
|
|
24
|
+
"output-file": "openapi.yaml"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
{ "path": "nxgt/main.tsp", "destination": "main.tsp" },
|
|
30
|
+
{
|
|
31
|
+
"path": "nxgt/openapi-codegen.config.ts",
|
|
32
|
+
"destination": "openapi-codegen.config.ts"
|
|
33
|
+
},
|
|
34
|
+
{ "path": "nxgt/README.md", "destination": "README.md" }
|
|
35
|
+
]
|
|
36
|
+
}
|
|
37
|
+
}
|