@nxgt/typespec 0.9.1 → 0.10.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 +40 -9
- package/docs/README.md +1 -0
- package/docs/guide/columns.md +25 -11
- package/docs/guide/scalars.md +288 -0
- package/docs/roadmap.md +14 -0
- package/docs/troubleshooting.md +94 -2
- package/lib/scalars/color/hex-color-code.tsp +17 -0
- package/lib/scalars/color/hsl.tsp +18 -0
- package/lib/scalars/color/hsla.tsp +18 -0
- package/lib/scalars/color/rgb.tsp +18 -0
- package/lib/scalars/color/rgba.tsp +18 -0
- package/lib/scalars/color.tsp +6 -0
- package/lib/scalars/date-time/date-time.tsp +19 -0
- package/lib/scalars/date-time/date.tsp +17 -0
- package/lib/scalars/date-time/iso-duration.tsp +17 -0
- package/lib/scalars/date-time/local-date-time.tsp +15 -0
- package/lib/scalars/date-time/local-time.tsp +15 -0
- package/lib/scalars/date-time/time-zone.tsp +18 -0
- package/lib/scalars/date-time/time.tsp +17 -0
- package/lib/scalars/date-time/timestamp.tsp +15 -0
- package/lib/scalars/date-time/utc-offset.tsp +15 -0
- package/lib/scalars/date-time.tsp +10 -0
- package/lib/scalars/encoding/base64-url.tsp +17 -0
- package/lib/scalars/encoding/base64.tsp +18 -0
- package/lib/scalars/encoding/hexadecimal.tsp +15 -0
- package/lib/scalars/encoding/jwt.tsp +19 -0
- package/lib/scalars/encoding/sha256.tsp +16 -0
- package/lib/scalars/encoding/sha512.tsp +16 -0
- package/lib/scalars/encoding.tsp +7 -0
- package/lib/scalars/finance/currency.tsp +17 -0
- package/lib/scalars/finance/iban.tsp +19 -0
- package/lib/scalars/finance.tsp +3 -0
- package/lib/scalars/geo/latitude.tsp +14 -0
- package/lib/scalars/geo/longitude.tsp +14 -0
- package/lib/scalars/geo.tsp +3 -0
- package/lib/scalars/identifier/cuid2.tsp +17 -0
- package/lib/scalars/identifier/guid.tsp +12 -0
- package/lib/scalars/identifier/isbn.tsp +18 -0
- package/lib/scalars/identifier/ksuid.tsp +18 -0
- package/lib/scalars/identifier/nano-id.tsp +16 -0
- package/lib/scalars/identifier/object-id.tsp +16 -0
- package/lib/scalars/identifier/sem-ver.tsp +17 -0
- package/lib/scalars/identifier/ulid.tsp +17 -0
- package/lib/scalars/identifier/uuid-v4.tsp +16 -0
- package/lib/scalars/identifier/uuid-v7.tsp +16 -0
- package/lib/scalars/identifier/uuid.tsp +18 -0
- package/lib/scalars/identifier/xid.tsp +17 -0
- package/lib/scalars/identifier.tsp +13 -0
- package/lib/scalars/locale/country-code.tsp +17 -0
- package/lib/scalars/locale/locale.tsp +20 -0
- package/lib/scalars/locale.tsp +3 -0
- package/lib/scalars/network/cidr-v4.tsp +19 -0
- package/lib/scalars/network/cidr-v6.tsp +18 -0
- package/lib/scalars/network/email-address.tsp +20 -0
- package/lib/scalars/network/hostname.tsp +21 -0
- package/lib/scalars/network/http-url.tsp +21 -0
- package/lib/scalars/network/ip-v4.tsp +17 -0
- package/lib/scalars/network/ip-v6.tsp +18 -0
- package/lib/scalars/network/ip.tsp +17 -0
- package/lib/scalars/network/mac.tsp +16 -0
- package/lib/scalars/network/phone-number.tsp +18 -0
- package/lib/scalars/network.tsp +11 -0
- package/lib/scalars/number/big-int.tsp +15 -0
- package/lib/scalars/number/long.tsp +17 -0
- package/lib/scalars/number/negative-float.tsp +13 -0
- package/lib/scalars/number/negative-int.tsp +15 -0
- package/lib/scalars/number/non-negative-float.tsp +13 -0
- package/lib/scalars/number/non-negative-int.tsp +15 -0
- package/lib/scalars/number/non-positive-float.tsp +13 -0
- package/lib/scalars/number/non-positive-int.tsp +15 -0
- package/lib/scalars/number/port.tsp +14 -0
- package/lib/scalars/number/positive-float.tsp +13 -0
- package/lib/scalars/number/positive-int.tsp +15 -0
- package/lib/scalars/number/safe-int.tsp +16 -0
- package/lib/scalars/number.tsp +13 -0
- package/lib/scalars/string/emoji.tsp +21 -0
- package/lib/scalars/string/non-empty-string.tsp +15 -0
- package/lib/scalars/string.tsp +3 -0
- package/lib/scalars/value.tsp +2 -0
- package/lib/scalars.tsp +13 -12
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ bun add -d @nxgt/typespec @typespec/compiler @typespec/http @typespec/openapi @t
|
|
|
18
18
|
|
|
19
19
|
`@typespec/compiler`, `@typespec/http` and `@typespec/openapi` 1.16 or later
|
|
20
20
|
are peer dependencies,
|
|
21
|
-
and `typescript` 6 too, as for every `@nxgt` package. `@typespec/openapi3`
|
|
21
|
+
and `typescript` 6 or 7 too, as for every `@nxgt` package. `@typespec/openapi3`
|
|
22
22
|
compiles the spec, and `@nxgt/openapi-codegen` generates the code from it.
|
|
23
23
|
The generator's CLI, `nxgt-openapi`, runs on [Bun](https://bun.sh): it needs
|
|
24
24
|
`bun` on the PATH, even when called through `npx` or an npm script.
|
|
@@ -291,15 +291,34 @@ model Post {
|
|
|
291
291
|
|
|
292
292
|
| Name | Emitted as | Generated | drizzle |
|
|
293
293
|
| --- | --- | --- | --- |
|
|
294
|
-
| `uuid` | the schema `
|
|
295
|
-
| `email` | the schema `
|
|
294
|
+
| `uuid` | the schema `UUID`, `format: uuid`, a pattern for versions 1–8 | `UUID`, `z.guid().regex(…)` | `id()` |
|
|
295
|
+
| `email` | `emailAddress`, the schema `EmailAddress`, `format: email` | `EmailAddress`, `z.string().regex(…)` | |
|
|
296
296
|
| `...Timestamps` | `createdAt`, `updatedAt`, read-only | left out of `Create<T>` and updates | `timestamps()` |
|
|
297
297
|
| `...SoftDelete` | `deletedAt: utcDateTime \| null`, read-only | | `softDelete()` |
|
|
298
298
|
| `...Versioned` | `version: integer`, read and update | kept in `MergePatchUpdate<T>` | `version()` |
|
|
299
299
|
| `...Actors<Id = uuid>` | `createdBy`, `updatedBy`, `deletedBy`, `Id \| null`, read-only | | `actors()` |
|
|
300
300
|
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
Every scalar of [`@nxgt/graphql-scalars`](https://www.npmjs.com/package/@nxgt/graphql-scalars)
|
|
302
|
+
is declared too, as a camelCase scalar (`latitude`, `currency`, `positiveInt`,
|
|
303
|
+
`httpUrl`, `ipV4`, `isoDuration`):
|
|
304
|
+
|
|
305
|
+
```tsp
|
|
306
|
+
model Place {
|
|
307
|
+
location: { latitude: latitude; longitude: longitude };
|
|
308
|
+
currency: currency;
|
|
309
|
+
openedOn: date;
|
|
310
|
+
homepage?: httpUrl;
|
|
311
|
+
seats: positiveInt;
|
|
312
|
+
}
|
|
313
|
+
```
|
|
314
|
+
|
|
315
|
+
Each is an OpenAPI component named as in GraphQL (`Latitude`, `URL`), with a
|
|
316
|
+
`format`, a pattern or bounds approximating the rule, and `x-nxgt-scalar`
|
|
317
|
+
naming it. The exact rules (checksums, ISO and IANA lists) are outside the
|
|
318
|
+
pattern. TypeSpec's own `url` takes any scheme; `httpUrl` takes `http:` and
|
|
319
|
+
`https:`. Every scalar by category, in
|
|
320
|
+
[Scalars](docs/guide/scalars.md); the ids, the addresses and the columns, in
|
|
321
|
+
[Scalars and columns](docs/guide/columns.md).
|
|
303
322
|
|
|
304
323
|
### Headers
|
|
305
324
|
|
|
@@ -503,8 +522,18 @@ In the ruleset `@nxgt/typespec/recommended`; each is a warning, and named
|
|
|
503
522
|
|
|
504
523
|
| Name | What it is |
|
|
505
524
|
| --- | --- |
|
|
506
|
-
| `uuid` |
|
|
507
|
-
| `email` |
|
|
525
|
+
| `uuid` | an RFC 9562 UUID, `format: uuid` and a pattern; the schema `UUID` |
|
|
526
|
+
| `email` | an alias of `emailAddress`, `format: email` and a pattern; the schema `EmailAddress` |
|
|
527
|
+
| `guid`, `uuidV4`, `uuidV7` | a UUID of any shape, of version 4, of version 7; `GUID`, `UUIDv4`, `UUIDv7` |
|
|
528
|
+
| `ulid`, `cuid2`, `nanoId`, `ksuid`, `xid`, `objectId`, `isbn`, `semVer` | identifiers by shape: `ULID`, `Cuid2`, `NanoID`, `KSUID`, `XID`, `ObjectID`, `ISBN`, `SemVer` |
|
|
529
|
+
| `base64`, `base64Url`, `hexadecimal`, `jwt`, `sha256`, `sha512` | encodings and digests; `base64` has the format `byte` and is generated as `z.base64()` |
|
|
530
|
+
| `nonEmptyString`, `emoji` | a string with a visible character; one emoji |
|
|
531
|
+
| `positiveInt`, `nonNegativeInt`, `negativeInt`, `nonPositiveInt`, `safeInt`, `port`, `positiveFloat`, `nonNegativeFloat`, `negativeFloat`, `nonPositiveFloat`, `bigInt`, `long` | the numbers: `int32` bounds, JavaScript's safe integers, TCP ports, floats by sign, and 64-bit (`long`, `format: int64`) or larger (`bigInt`) integers as strings |
|
|
532
|
+
| `date`, `dateTime`, `time`, `localDateTime`, `localTime`, `isoDuration`, `utcOffset`, `timeZone`, `timestamp` | dates, times and durations as RFC 3339 and ISO 8601 write them |
|
|
533
|
+
| `latitude`, `longitude` | decimal degrees, as numbers |
|
|
534
|
+
| `hexColorCode`, `rgb`, `rgba`, `hsl`, `hsla` | CSS colors |
|
|
535
|
+
| `emailAddress`, `hostname`, `httpUrl`, `ipV4`, `ipV6`, `ip`, `cidrV4`, `cidrV6`, `mac`, `phoneNumber` | addresses; `httpUrl` is named so because `url` is TypeSpec's |
|
|
536
|
+
| `countryCode`, `locale`, `currency`, `iban` | ISO and BCP 47 codes, by shape |
|
|
508
537
|
| `Timestamps` | `createdAt`, `updatedAt: utcDateTime`, read-only |
|
|
509
538
|
| `SoftDelete` | `deletedAt: utcDateTime \| null`, read-only |
|
|
510
539
|
| `Versioned` | `version: integer`, read and update |
|
|
@@ -600,8 +629,8 @@ envelope and the rate limit's headers.
|
|
|
600
629
|
`3.1.0/openapi.yaml` and `3.2.0/openapi.yaml`: point the generator's input
|
|
601
630
|
at one of them.
|
|
602
631
|
- **The library's schema names are global.** `BadRequestBody` …
|
|
603
|
-
`InternalServerErrorBody`, `ValidationIssue`, `ValidationTarget`,
|
|
604
|
-
`
|
|
632
|
+
`InternalServerErrorBody`, `ValidationIssue`, `ValidationTarget`, the scalars' components
|
|
633
|
+
(`UUID`, `EmailAddress`, `Latitude`, …), and each `<Item>Page` and `<Item>CursorPage` are emitted under
|
|
605
634
|
those names, without a namespace, once the spec uses them. A model of the
|
|
606
635
|
same name in your spec then fails with `duplicate-type-name`
|
|
607
636
|
([troubleshooting](docs/troubleshooting.md)).
|
|
@@ -616,6 +645,8 @@ envelope and the rate limit's headers.
|
|
|
616
645
|
what a handler answers;
|
|
617
646
|
- [Authentication](docs/guide/auth.md): `JanusAuth`, and the guards'
|
|
618
647
|
replies without a body;
|
|
648
|
+
- [Scalars](docs/guide/scalars.md): every scalar of `@nxgt/graphql-scalars`
|
|
649
|
+
by category, with what each takes and what its pattern leaves out;
|
|
619
650
|
- [Scalars and columns](docs/guide/columns.md): `uuid`, `email`, and the
|
|
620
651
|
columns `@nxgt/drizzle` stamps;
|
|
621
652
|
- [Headers](docs/guide/headers.md): `Idempotency-Key`, the rate limit and
|
package/docs/README.md
CHANGED
|
@@ -8,6 +8,7 @@ The [package README](../README.md) is the short version.
|
|
|
8
8
|
| [Error replies](guide/errors.md) | declaring what an operation answers when it fails |
|
|
9
9
|
| [Pagination](guide/pagination.md) | listing a collection a page at a time |
|
|
10
10
|
| [Authentication](guide/auth.md) | protecting an operation with a `@nxgt/janus` session |
|
|
11
|
+
| [Scalars](guide/scalars.md) | typing a latitude, a currency, an IP address or a port, and knowing what each pattern leaves out |
|
|
11
12
|
| [Scalars and columns](guide/columns.md) | typing ids and addresses, and the columns a row carries |
|
|
12
13
|
| [Headers](guide/headers.md) | making a write idempotent, or declaring a rate limit |
|
|
13
14
|
| [Sorting](guide/sorting.md) | letting a client filter and sort a list |
|
package/docs/guide/columns.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Scalars and columns
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Scalars for the ids and addresses every API has, and the columns
|
|
4
4
|
[`@nxgt/drizzle`](https://www.npmjs.com/package/@nxgt/drizzle) stamps a row
|
|
5
|
-
with, as models to spread.
|
|
5
|
+
with, as models to spread. The other scalars, by category, are in
|
|
6
|
+
[Scalars](scalars.md).
|
|
6
7
|
|
|
7
8
|
## Scalars
|
|
8
9
|
|
|
@@ -18,7 +19,7 @@ namespace Blog;
|
|
|
18
19
|
model Author {
|
|
19
20
|
@visibility(Lifecycle.Read) id: uuid;
|
|
20
21
|
email: email;
|
|
21
|
-
site?:
|
|
22
|
+
site?: httpUrl;
|
|
22
23
|
}
|
|
23
24
|
|
|
24
25
|
@route("/authors/{authorId}")
|
|
@@ -27,12 +28,25 @@ model Author {
|
|
|
27
28
|
|
|
28
29
|
| Scalar | Emitted as | Generated | Refused |
|
|
29
30
|
| --- | --- | --- | --- |
|
|
30
|
-
| `uuid` | `$ref` to the schema `
|
|
31
|
-
| `email` | `$ref` to the schema `
|
|
32
|
-
| `
|
|
33
|
-
|
|
34
|
-
`
|
|
35
|
-
the
|
|
31
|
+
| `uuid` | `$ref` to the schema `UUID`: `type: string`, `format: uuid`, a pattern, `x-nxgt-scalar: UUID` | `type UUID = string`, `zUUID = z.guid().regex(…)` | `42`, a UUID with no hyphens or of version 9, in a path, a query or a body |
|
|
32
|
+
| `email` | an alias of `emailAddress`: `$ref` to the schema `EmailAddress`, `format: email`, a pattern, `x-nxgt-scalar: EmailAddress` | `type EmailAddress = string`, `zEmailAddress = z.string().regex(…)` | `not an address`, `ada@localhost` |
|
|
33
|
+
| `httpUrl` | `$ref` to the schema `URL`: `format: uri`, a pattern for `http:` and `https:`, `x-nxgt-scalar: URL` | `type URL = string`, `zURL = z.string().regex(…)` | `not a url`, `javascript:alert(1)` |
|
|
34
|
+
|
|
35
|
+
TypeSpec's own `url` takes any scheme, inline, as `z.url()`; `httpUrl` is
|
|
36
|
+
the `http:` and `https:` page address. `httpUrl` and `email` are generated as
|
|
37
|
+
`z.string().regex(…)`: the pattern is the rule, as the generator leaves out
|
|
38
|
+
`z.url()`, which trims its input, and `z.email()`, which refuses
|
|
39
|
+
`ada@x.xn--p1ai`, an internationalised top-level domain `email` accepts.
|
|
40
|
+
|
|
41
|
+
`uuid` takes an RFC 9562 UUID, any version 1 to 8 with the RFC variant, or
|
|
42
|
+
the nil or the max UUID, in any case, as `@nxgt/graphql-scalars`' `UUID`
|
|
43
|
+
does: the pattern adds those checks to the shape `z.guid()` checks. `uuidV4`,
|
|
44
|
+
`uuidV7` and `guid` (any 8-4-4-4-12 hexadecimal) are the narrower and the
|
|
45
|
+
looser ones. `email` is `emailAddress`, under the name it had first.
|
|
46
|
+
|
|
47
|
+
The schema names changed in 0.10.0: `Uuid` and `Email` became `UUID` and
|
|
48
|
+
`EmailAddress`, and a UUID of no known version is refused. A spec that needs
|
|
49
|
+
the old shape-only id writes `guid`.
|
|
36
50
|
|
|
37
51
|
## Columns
|
|
38
52
|
|
|
@@ -112,6 +126,6 @@ without it is not checked against the version.
|
|
|
112
126
|
|
|
113
127
|
## Names
|
|
114
128
|
|
|
115
|
-
`
|
|
116
|
-
model named `
|
|
129
|
+
`UUID` and `EmailAddress` are schemas of their own, and their names are global: a
|
|
130
|
+
model named `EmailAddress` in a spec that also uses `email` collides with it
|
|
117
131
|
([troubleshooting](../troubleshooting.md)).
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Scalars
|
|
2
|
+
|
|
3
|
+
The scalars of [`@nxgt/graphql-scalars`](https://www.npmjs.com/package/@nxgt/graphql-scalars)
|
|
4
|
+
as TypeSpec scalars, so a spec types a latitude, a currency or an IPv4 address
|
|
5
|
+
once and every generator and validator downstream agrees on it.
|
|
6
|
+
|
|
7
|
+
```tsp
|
|
8
|
+
import "@typespec/http";
|
|
9
|
+
import "@nxgt/typespec";
|
|
10
|
+
|
|
11
|
+
using Http;
|
|
12
|
+
using Nxgt;
|
|
13
|
+
|
|
14
|
+
@service(#{ title: "Places" })
|
|
15
|
+
namespace Places;
|
|
16
|
+
|
|
17
|
+
model Place {
|
|
18
|
+
name: string;
|
|
19
|
+
location: {
|
|
20
|
+
latitude: latitude;
|
|
21
|
+
longitude: longitude;
|
|
22
|
+
};
|
|
23
|
+
currency: currency;
|
|
24
|
+
openedOn: date;
|
|
25
|
+
homepage?: httpUrl;
|
|
26
|
+
contact?: emailAddress;
|
|
27
|
+
seats: positiveInt;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@route("/places") @post op createPlace(@body place: Place): Place;
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Each property becomes a `$ref` to a component named as the GraphQL scalar:
|
|
34
|
+
|
|
35
|
+
```yaml
|
|
36
|
+
components:
|
|
37
|
+
schemas:
|
|
38
|
+
Currency:
|
|
39
|
+
type: string
|
|
40
|
+
pattern: ^[A-Z]{3}$
|
|
41
|
+
format: currency
|
|
42
|
+
description: |-
|
|
43
|
+
An ISO 4217 currency code, uppercase: `EUR`, `USD`. ...
|
|
44
|
+
x-nxgt-scalar: Currency
|
|
45
|
+
Latitude:
|
|
46
|
+
type: number
|
|
47
|
+
format: double
|
|
48
|
+
minimum: -90
|
|
49
|
+
maximum: 90
|
|
50
|
+
x-nxgt-scalar: Latitude
|
|
51
|
+
PositiveInt:
|
|
52
|
+
type: integer
|
|
53
|
+
format: int32
|
|
54
|
+
minimum: 1
|
|
55
|
+
maximum: 2147483647
|
|
56
|
+
x-nxgt-scalar: PositiveInt
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## What a scalar emits
|
|
60
|
+
|
|
61
|
+
| Emitted | Value |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| The component | The GraphQL name (`@friendlyName`): `Latitude`, `CIDRv4`, `EmailAddress`. It is global: a model of that name in your spec collides with it. |
|
|
64
|
+
| `format` | JSON Schema's when one exists (`date`, `date-time`, `duration`, `email`, `hostname`, `ipv4`, `ipv6`, `uri`, `uuid`), otherwise the kebab-case name (`iban`, `local-time`, `cidr-v4`); `base64` has OpenAPI's `byte`. The numbers keep the format TypeSpec writes from the base type. |
|
|
65
|
+
| `pattern`, `minimum`, `maximum` | The closest the GraphQL rule goes in a pattern or in bounds. |
|
|
66
|
+
| `x-nxgt-scalar` | The GraphQL name, so a generator can tell the rule from the shape. |
|
|
67
|
+
| `description` | The rule in words, and what the pattern leaves out. |
|
|
68
|
+
|
|
69
|
+
A generator that knows nothing of `x-nxgt-scalar` still validates the pattern
|
|
70
|
+
and the bounds. A later `@nxgt/openapi-codegen` release will map it to the exact
|
|
71
|
+
Zod schema from `@nxgt/zod` ([roadmap](../roadmap.md)); until then, what a
|
|
72
|
+
scalar leaves out is not checked.
|
|
73
|
+
|
|
74
|
+
## Names
|
|
75
|
+
|
|
76
|
+
The identifiers are camelCase: `ipV4`, `cidrV6`, `uuidV7`, `hexColorCode`. The
|
|
77
|
+
component keeps the GraphQL spelling.
|
|
78
|
+
|
|
79
|
+
- `url` and `duration` are TypeSpec built-ins, and a scalar of that name makes
|
|
80
|
+
`url` ambiguous under `using Nxgt`. They are `httpUrl` and `isoDuration`.
|
|
81
|
+
- `JSON`, `JSONObject` and `Void` are not declared: write TypeSpec's own
|
|
82
|
+
`unknown`, `Record<unknown>` and `void`.
|
|
83
|
+
- A declaration of yours named like a scalar shadows it. In a spec with an
|
|
84
|
+
operation `locale`, write `Nxgt.locale`.
|
|
85
|
+
|
|
86
|
+
```tsp
|
|
87
|
+
@get op locale(): string; // yours
|
|
88
|
+
model Page { language: Nxgt.locale; } // the scalar
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Identifiers
|
|
92
|
+
|
|
93
|
+
`uuid`, with the id columns, is also in [Scalars and columns](columns.md).
|
|
94
|
+
|
|
95
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
96
|
+
| --- | --- | --- | --- | --- |
|
|
97
|
+
| `uuid` | `UUID` | `uuid` | `550e8400-e29b-41d4-a716-446655440000`, the nil and the max UUID | |
|
|
98
|
+
| `uuidV4` | `UUIDv4` | `uuid` | `123e4567-e89b-42d3-a456-426614174000` | |
|
|
99
|
+
| `uuidV7` | `UUIDv7` | `uuid` | `017f22e2-79b0-7cc3-98c4-dc0c0c07398f` | |
|
|
100
|
+
| `guid` | `GUID` | `uuid` | any 8-4-4-4-12 hexadecimal, `123e4567-e89b-12d3-c456-426614174000` | no version, no variant |
|
|
101
|
+
| `ulid` | `ULID` | `ulid` | `01ARZ3NDEKTSV4RRFFQ69G5FAV`: 26 Crockford base32 characters, the first 0 to 7, any case | |
|
|
102
|
+
| `cuid2` | `Cuid2` | `cuid2` | `tz4a98xxat96iws9zmbrgj3a`, `ab`: a lowercase letter, then lowercase letters and digits, 2 to 32 characters | |
|
|
103
|
+
| `nanoId` | `NanoID` | `nano-id` | `V1StGXR8_Z5jdHi6B-myT`: 21 characters of `A-Za-z0-9_-`; a custom length is refused | |
|
|
104
|
+
| `ksuid` | `KSUID` | `ksuid` | `0ujtsYcgvSTl8PAuAdqWYSMnLOv`: 27 base62 characters, up to `aWgEPTl1tmebfsQzFP4bxwgy80V` | |
|
|
105
|
+
| `xid` | `XID` | `xid` | `9m4e2mr0ui3e8a215n4g`: 20 lowercase base32hex characters, the last `0` or `g` | |
|
|
106
|
+
| `objectId` | `ObjectID` | `object-id` | `507f1f77bcf86cd799439011`: 24 hexadecimal digits, any case | |
|
|
107
|
+
| `isbn` | `ISBN` | `isbn` | `0306406152`, `080442957X`, `9780306406157`: digits only, no hyphen | the check digit (`9780306406158` passes) |
|
|
108
|
+
| `semVer` | `SemVer` | `sem-ver` | `1.2.3`, `1.0.0-rc.1+build.5`: no `v` prefix, no leading zero | |
|
|
109
|
+
|
|
110
|
+
`uuid` refuses a UUID of no known version (`...-91d4-...`); `guid` is the shape
|
|
111
|
+
only. Case is kept as sent.
|
|
112
|
+
|
|
113
|
+
## Encodings
|
|
114
|
+
|
|
115
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
116
|
+
| --- | --- | --- | --- | --- |
|
|
117
|
+
| `base64` | `Base64` | `byte` | `aGk=`, `a+b/`, `""`: RFC 4648 with padding, in its one canonical spelling (`YR==` is refused) | |
|
|
118
|
+
| `base64Url` | `Base64URL` | `base64-url` | `aGk`, `a-b_`: `-` and `_`, no padding, canonical | |
|
|
119
|
+
| `hexadecimal` | `Hexadecimal` | `hexadecimal` | `deadBEEF`, `abc`: one or more digits, any case, no `0x` | |
|
|
120
|
+
| `jwt` | `JWT` | `jwt` | three base64url parts joined by `.`, `eyJhbGci….eyJzdWIi….dozjgNry…` | that each part is canonical and the first two are JSON objects, `alg: none`, the signature |
|
|
121
|
+
| `sha256` | `SHA256` | `sha256` | 64 hexadecimal digits, any case | |
|
|
122
|
+
| `sha512` | `SHA512` | `sha512` | 128 hexadecimal digits, any case | |
|
|
123
|
+
|
|
124
|
+
`base64` carries the OpenAPI format `byte`, which the generator turns into
|
|
125
|
+
`z.base64()` in front of the pattern (`zBase64 = z.base64().regex(…)`); the
|
|
126
|
+
others are `z.string().regex(…)`. A `jwt` is a shape, not a verification: check
|
|
127
|
+
the token's signature in your code.
|
|
128
|
+
|
|
129
|
+
## Strings
|
|
130
|
+
|
|
131
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
132
|
+
| --- | --- | --- | --- | --- |
|
|
133
|
+
| `nonEmptyString` | `NonEmptyString` | `non-empty-string` | `a`, ` a `: at least one non-white-space character (ECMAScript `\s`); kept as sent, not trimmed; `""`, `" "` and a lone no-break space are refused | |
|
|
134
|
+
| `emoji` | `Emoji` | `emoji` | `😀`, `👍🏽`, `👨👩👧`, `🇫🇷`, `1️⃣`: exactly one emoji | tag sequences (the flags of England, Scotland, Wales), segmentation into user-perceived characters, so two emoji in a row may pass |
|
|
135
|
+
|
|
136
|
+
The `emoji` pattern uses Unicode property escapes and needs the `u` flag;
|
|
137
|
+
the generated Zod regex has it.
|
|
138
|
+
|
|
139
|
+
## Numbers
|
|
140
|
+
|
|
141
|
+
Numbers are JSON numbers, except `BigInt` and `Long`, which are decimal strings
|
|
142
|
+
because a JSON number loses precision past 2^53.
|
|
143
|
+
|
|
144
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
145
|
+
| --- | --- | --- | --- | --- |
|
|
146
|
+
| `positiveInt` | `PositiveInt` | `int32` | `1` to `2147483647` | `-0` |
|
|
147
|
+
| `nonNegativeInt` | `NonNegativeInt` | `int32` | `0` to `2147483647` | `-0` |
|
|
148
|
+
| `negativeInt` | `NegativeInt` | `int32` | `-2147483648` to `-1` | `-0` |
|
|
149
|
+
| `nonPositiveInt` | `NonPositiveInt` | `int32` | `-2147483648` to `0` | `-0` |
|
|
150
|
+
| `safeInt` | `SafeInt` | `int64` | the integers JavaScript holds exactly, `-9007199254740991` to `9007199254740991` | `-0` |
|
|
151
|
+
| `port` | `Port` | `int32` | `0` to `65535` | `-0` |
|
|
152
|
+
| `positiveFloat` | `PositiveFloat` | `double` | `0.5`, above 0 | |
|
|
153
|
+
| `nonNegativeFloat` | `NonNegativeFloat` | `double` | `0`, `1.5` | |
|
|
154
|
+
| `negativeFloat` | `NegativeFloat` | `double` | `-0.5`, below 0 | |
|
|
155
|
+
| `nonPositiveFloat` | `NonPositiveFloat` | `double` | `0`, `-1.5` | |
|
|
156
|
+
| `bigInt` | `BigInt` | `big-int` | `"123456789012345678901234567890"`: no leading zero, no `+`, no `-0` | |
|
|
157
|
+
| `long` | `Long` | `int64` | `"9223372036854775807"`, the signed 64-bit range | |
|
|
158
|
+
|
|
159
|
+
`long` is a string with `format: int64`, the OpenAPI idiom for an int64 sent as
|
|
160
|
+
a string; `timestamp` and `safeInt` are JSON numbers with the same format.
|
|
161
|
+
|
|
162
|
+
An integer scalar accepts `-0`, which GraphQL refuses and no JSON Schema can
|
|
163
|
+
express.
|
|
164
|
+
|
|
165
|
+
## Date and time
|
|
166
|
+
|
|
167
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
168
|
+
| --- | --- | --- | --- | --- |
|
|
169
|
+
| `date` | `Date` | `date` | `2024-02-29` (an impossible day is refused) | |
|
|
170
|
+
| `dateTime` | `DateTime` | `date-time` | `2024-03-10T12:00:00+02:00`: an offset is required, `-00:00` refused | the range 0000 to 9999 in UTC, the leap second |
|
|
171
|
+
| `time` | `Time` | `time` | `10:15:30Z`, `10:15:30.5+02:00` | |
|
|
172
|
+
| `localDateTime` | `LocalDateTime` | `local-date-time` | `2024-03-10T10:15`, `2024-03-10T10:15:30`: no offset | |
|
|
173
|
+
| `localTime` | `LocalTime` | `local-time` | `10:15`, `10:15:30.5`: no offset | |
|
|
174
|
+
| `isoDuration` | `Duration` | `duration` | `P1Y2M3DT4H5M6S`, `PT0.5S`, `P2W`: weeks alone, no sign | |
|
|
175
|
+
| `utcOffset` | `UtcOffset` | `utc-offset` | `+05:30`, from `-12:00` to `+14:00`; `-00:00` refused | |
|
|
176
|
+
| `timeZone` | `TimeZone` | `time-zone` | `Europe/Paris`, `UTC` | the list of IANA names |
|
|
177
|
+
| `timestamp` | `Timestamp` | `int64` | milliseconds since the epoch, `1710065730000`, within ±8640000000000000 | |
|
|
178
|
+
|
|
179
|
+
A well-shaped unknown time zone name (`Mars/Olympus`) passes
|
|
180
|
+
([Known gaps](#known-gaps)).
|
|
181
|
+
|
|
182
|
+
## Geo
|
|
183
|
+
|
|
184
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
185
|
+
| --- | --- | --- | --- | --- |
|
|
186
|
+
| `latitude` | `Latitude` | `double` | `48.8566`, from -90 to 90 | |
|
|
187
|
+
| `longitude` | `Longitude` | `double` | `2.3522`, from -180 to 180 | |
|
|
188
|
+
|
|
189
|
+
Both are numbers, never strings.
|
|
190
|
+
|
|
191
|
+
## Colors
|
|
192
|
+
|
|
193
|
+
CSS color syntax, in the comma form, one space after each comma.
|
|
194
|
+
|
|
195
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
196
|
+
| --- | --- | --- | --- | --- |
|
|
197
|
+
| `hexColorCode` | `HexColorCode` | `hex-color-code` | `#f00`, `#f008`, `#ff0000`, `#ff000080`, any case | |
|
|
198
|
+
| `rgb` | `RGB` | `rgb` | `rgb(255, 0, 0)`; no percentage, no leading zero | |
|
|
199
|
+
| `rgba` | `RGBA` | `rgba` | `rgba(255, 0, 0, 0.5)`: the alpha is `0`, `1` or a fraction with a leading `0` and no trailing zero | |
|
|
200
|
+
| `hsl` | `HSL` | `hsl` | `hsl(120, 100%, 50%)`: hue 0 to 359, no unit | |
|
|
201
|
+
| `hsla` | `HSLA` | `hsla` | `hsla(120, 100%, 50%, 0.5)` | |
|
|
202
|
+
|
|
203
|
+
`rgb(255,0,0)`, `rgb(255 0 0)` and `RGB(255, 0, 0)` are refused.
|
|
204
|
+
|
|
205
|
+
## Network
|
|
206
|
+
|
|
207
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
208
|
+
| --- | --- | --- | --- | --- |
|
|
209
|
+
| `emailAddress` (alias `email`) | `EmailAddress` | `email` | `ada@example.com`, `a.b+c@sub.example.org`: no quoted local part, at least two labels | whether the domain exists |
|
|
210
|
+
| `hostname` | `Hostname` | `hostname` | `api.example.com`, `localhost`, `xn--bcher-kva.example`; not `1.2.3.4`, not Unicode | nothing is resolved |
|
|
211
|
+
| `httpUrl` | `URL` | `uri` | `https://example.com/a?b=c`, `http://localhost:3000`, `https://[2001:db8::1]/`; no other scheme, no user info | |
|
|
212
|
+
| `ipV4` | `IPv4` | `ipv4` | `192.168.0.1`: no leading zero | |
|
|
213
|
+
| `ipV6` | `IPv6` | `ipv6` | `2001:db8::1`, `::1`, `::ffff:192.0.2.1`, any case; no zone | |
|
|
214
|
+
| `ip` | `IP` | `ip` | either of the two | |
|
|
215
|
+
| `cidrV4` | `CIDRv4` | `cidr-v4` | `10.0.0.0/8`, `0.0.0.0/0` | that the address starts its block |
|
|
216
|
+
| `cidrV6` | `CIDRv6` | `cidr-v6` | `2001:db8::/32`, `::/0` | that the address starts its block |
|
|
217
|
+
| `mac` | `MAC` | `mac` | `00:1a:2b:3c:4d:5e`; not the hyphen or dotted form | |
|
|
218
|
+
| `phoneNumber` | `PhoneNumber` | `phone-number` | `+33612345678`: E.164, 7 to 15 digits, no separator | whether the number exists |
|
|
219
|
+
|
|
220
|
+
`httpUrl` is for a page's address; it is generated as
|
|
221
|
+
`zURL = z.string().regex(…)`: the pattern is the rule, and keeps to `http:`
|
|
222
|
+
and `https:` (`javascript:alert(1)` is refused). The generator leaves out
|
|
223
|
+
`z.url()` for it, which trims its input. TypeSpec's own `url` takes any
|
|
224
|
+
scheme and is generated as a bare `z.url()`.
|
|
225
|
+
|
|
226
|
+
## Locale
|
|
227
|
+
|
|
228
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
229
|
+
| --- | --- | --- | --- | --- |
|
|
230
|
+
| `countryCode` | `CountryCode` | `country-code` | `FR`, `US`: ISO 3166-1 alpha-2, uppercase | the list of assigned codes (`ZZ` passes) |
|
|
231
|
+
| `locale` | `Locale` | `locale` | `fr`, `fr-FR`, `zh-Hant-TW`, `en-US-u-ca-buddhist`: BCP 47 in canonical case, up to 255 characters | the IANA registry, the order of extensions |
|
|
232
|
+
|
|
233
|
+
## Finance
|
|
234
|
+
|
|
235
|
+
| Scalar | Component | Format | Takes | Left out of the pattern |
|
|
236
|
+
| --- | --- | --- | --- | --- |
|
|
237
|
+
| `currency` | `Currency` | `currency` | `EUR`, `USD`: ISO 4217, uppercase | the list of codes (`ZZZ` passes) |
|
|
238
|
+
| `iban` | `IBAN` | `iban` | `FR1420041010050500013M02606`: uppercase, no spaces | the mod 97 checksum, the country and its length |
|
|
239
|
+
|
|
240
|
+
## Values
|
|
241
|
+
|
|
242
|
+
`JSON`, `JSONObject` and `Void` are not scalars here. Write `unknown`,
|
|
243
|
+
`Record<unknown>` and `void`.
|
|
244
|
+
|
|
245
|
+
## In a route
|
|
246
|
+
|
|
247
|
+
The scalars work anywhere a type does: a path, a query, a header, a body.
|
|
248
|
+
|
|
249
|
+
```tsp
|
|
250
|
+
import "@typespec/http";
|
|
251
|
+
import "@nxgt/typespec";
|
|
252
|
+
|
|
253
|
+
using Http;
|
|
254
|
+
using Nxgt;
|
|
255
|
+
|
|
256
|
+
@service(#{ title: "Inventory" })
|
|
257
|
+
namespace Inventory;
|
|
258
|
+
|
|
259
|
+
model Host {
|
|
260
|
+
@visibility(Lifecycle.Read) id: uuid;
|
|
261
|
+
name: hostname;
|
|
262
|
+
address: ip;
|
|
263
|
+
network: cidrV4;
|
|
264
|
+
port: port;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
@route("/hosts")
|
|
268
|
+
interface Hosts {
|
|
269
|
+
@get list(@query ipPrefix?: cidrV4): Host[];
|
|
270
|
+
@get @route("{hostId}") read(@path hostId: uuid): Host;
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
A request that breaks a pattern or a bound is answered `400` by the generated
|
|
275
|
+
validator, the same as any other schema.
|
|
276
|
+
|
|
277
|
+
## Known gaps
|
|
278
|
+
|
|
279
|
+
The pattern is an approximation. Three things a consumer meets:
|
|
280
|
+
|
|
281
|
+
- An integer scalar accepts `-0`.
|
|
282
|
+
- A scalar that depends on a list (`countryCode`, `currency`, `timeZone`, the
|
|
283
|
+
`iban` checksum, the `locale` registry) accepts a well-shaped unknown value
|
|
284
|
+
until the generator maps `x-nxgt-scalar` to `@nxgt/zod`.
|
|
285
|
+
- A declaration named like a scalar shadows it: write `Nxgt.locale`.
|
|
286
|
+
|
|
287
|
+
See also [Scalars and columns](columns.md) for `uuid`, `email` and the columns,
|
|
288
|
+
and the [troubleshooting](../troubleshooting.md) page.
|
package/docs/roadmap.md
CHANGED
|
@@ -8,6 +8,12 @@ generated by `@nxgt/openapi-codegen` in CI before it ships.
|
|
|
8
8
|
|
|
9
9
|
Nothing in progress.
|
|
10
10
|
|
|
11
|
+
## Next
|
|
12
|
+
|
|
13
|
+
- **Exact scalar rules in the generated code.** `@nxgt/openapi-codegen` maps
|
|
14
|
+
`x-nxgt-scalar` to the exact Zod schema from `@nxgt/zod`: the checksums,
|
|
15
|
+
the ISO and IANA lists and the `Intl` rules the patterns leave out.
|
|
16
|
+
|
|
11
17
|
## Later
|
|
12
18
|
|
|
13
19
|
- **`QUERY` for `search` and `query`.** The HTTP `QUERY` method, a safe
|
|
@@ -20,6 +26,14 @@ Nothing in progress.
|
|
|
20
26
|
|
|
21
27
|
## Shipped
|
|
22
28
|
|
|
29
|
+
### 0.10.0
|
|
30
|
+
|
|
31
|
+
- **Every scalar of `@nxgt/graphql-scalars`.** The scalars, under the same
|
|
32
|
+
names, as camelCase TypeSpec scalars: each emits a component named as in
|
|
33
|
+
GraphQL, a format, a pattern or bounds approximating the rule, and
|
|
34
|
+
`x-nxgt-scalar`. `uuid` and `email` take the GraphQL rules; the components
|
|
35
|
+
`Uuid` and `Email` are now `UUID` and `EmailAddress`.
|
|
36
|
+
|
|
23
37
|
### 0.9.0
|
|
24
38
|
|
|
25
39
|
- **A `tsp init` template.** `tsp init` with the package's template
|
package/docs/troubleshooting.md
CHANGED
|
@@ -92,8 +92,9 @@ In GitHub Actions, add `oven-sh/setup-bun@v2` before the step.
|
|
|
92
92
|
|
|
93
93
|
**When:** `tsp compile` runs on a spec that declares its own model named like
|
|
94
94
|
one of the library's: `BadRequestBody` … `InternalServerErrorBody`,
|
|
95
|
-
`ValidationIssue`, `ValidationTarget`, `
|
|
96
|
-
`uuid` or `email
|
|
95
|
+
`ValidationIssue`, `ValidationTarget`, `UUID` or `EmailAddress` when the spec
|
|
96
|
+
uses `uuid` or `email` (or any other scalar, under its GraphQL name: `Latitude`,
|
|
97
|
+
`Currency`), or the page of one of its models,
|
|
97
98
|
such as `PostPage` beside `Page<Post>`, or `CommentCursorPage` beside
|
|
98
99
|
`CursorPage<Comment>`.
|
|
99
100
|
|
|
@@ -489,6 +490,97 @@ linter:
|
|
|
489
490
|
'@nxgt/typespec/error-body-shape': 'the legacy routes answer their own errors'
|
|
490
491
|
```
|
|
491
492
|
|
|
493
|
+
## `@typespec/openapi3/invalid-model-property: 'Operation' cannot be specified as a model property.`
|
|
494
|
+
|
|
495
|
+
**When:** `tsp compile` runs on a spec that has `using Nxgt` and declares, in
|
|
496
|
+
its own namespace, something named like a scalar (`locale`, `date`, `time`,
|
|
497
|
+
`port`, `hostname`…) and also uses the scalar of that name.
|
|
498
|
+
|
|
499
|
+
```tsp
|
|
500
|
+
using Nxgt;
|
|
501
|
+
|
|
502
|
+
@get op locale(): string;
|
|
503
|
+
model Page { language: locale; }
|
|
504
|
+
```
|
|
505
|
+
|
|
506
|
+
```text
|
|
507
|
+
error @typespec/openapi3/invalid-model-property: 'Operation' cannot be specified as a model property.
|
|
508
|
+
```
|
|
509
|
+
|
|
510
|
+
**Why:** a declaration of the spec's own namespace shadows what `using Nxgt`
|
|
511
|
+
brings in, without a word. With an operation the name resolves to the
|
|
512
|
+
operation, and the emitter refuses it. With a model, `model date { … }` and
|
|
513
|
+
`day: date`, the property silently gets the model, a `$ref` to `date` where
|
|
514
|
+
`Date` was meant.
|
|
515
|
+
|
|
516
|
+
**Fix:** write the scalar with its namespace, or rename your declaration:
|
|
517
|
+
|
|
518
|
+
```tsp
|
|
519
|
+
model Page { language: Nxgt.locale; }
|
|
520
|
+
```
|
|
521
|
+
|
|
522
|
+
## `` A value the GraphQL scalar refuses is accepted: `ZZ`, `ZZZ`, `Mars/Olympus`, a bad IBAN checksum ``
|
|
523
|
+
|
|
524
|
+
**When:** a request carries a value that `@nxgt/graphql-scalars` refuses but
|
|
525
|
+
the generated validator accepts and answers `2xx`: a country code that is not
|
|
526
|
+
assigned (`ZZ`), a currency (`ZZZ`), an IBAN with a wrong checksum, an ISBN
|
|
527
|
+
with a wrong check digit, a time zone (`Mars/Olympus`) or a locale that is not
|
|
528
|
+
in the registry, a JWT whose segments are not a token, an `Emoji` that is not
|
|
529
|
+
one.
|
|
530
|
+
|
|
531
|
+
**Why:** the scalar's pattern checks the shape. A list (`countryCode`,
|
|
532
|
+
`currency`, `timeZone`, `locale`) or a checksum (`iban`, ISBN) does not fit in
|
|
533
|
+
a pattern. `x-nxgt-scalar` names the exact rule, but `@nxgt/openapi-codegen`
|
|
534
|
+
does not map it to the `@nxgt/zod` schema yet ([roadmap](roadmap.md)).
|
|
535
|
+
|
|
536
|
+
**Fix:** until it does, check the value in the handler against the exact
|
|
537
|
+
rule, the list or the checksum, from `@nxgt/zod` or your own table, and answer
|
|
538
|
+
`400` as the generated validator does:
|
|
539
|
+
|
|
540
|
+
```ts
|
|
541
|
+
if (!knownCountries.has(body.country)) {
|
|
542
|
+
return c.json({ status: 400, message: 'Unknown country code' }, 400);
|
|
543
|
+
}
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
## `An integer scalar accepts -0`
|
|
547
|
+
|
|
548
|
+
**When:** a body carries `-0` for `positiveInt`, `nonNegativeInt`, `port`,
|
|
549
|
+
`safeInt` and the other integer scalars, and the validator lets it through
|
|
550
|
+
where `@nxgt/graphql-scalars` refuses it.
|
|
551
|
+
|
|
552
|
+
**Why:** `-0` is a JSON number equal to `0`, which no JSON Schema keyword can
|
|
553
|
+
tell from `0`. For `nonNegativeInt`, `nonPositiveInt`, `port`, the bound holds,
|
|
554
|
+
so the value passes (`positiveInt` and `negativeInt` refuse it by their bound).
|
|
555
|
+
|
|
556
|
+
**Fix:** normalise in the handler when the sign matters:
|
|
557
|
+
|
|
558
|
+
```ts
|
|
559
|
+
const quantity = Object.is(body.quantity, -0) ? 0 : body.quantity;
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
## `UUID`, `EmailAddress`: generated `Uuid` and `Email` are gone, and an id is answered 400
|
|
563
|
+
|
|
564
|
+
**When:** after upgrading to 0.10.0, the generated code no longer exports
|
|
565
|
+
`Uuid`, `zUuid`, `Email` or `zEmail` (`Module has no exported member 'Uuid'`),
|
|
566
|
+
or a request whose id was accepted before is answered `400`.
|
|
567
|
+
|
|
568
|
+
**Why:** the schemas follow `@nxgt/graphql-scalars`' names, `UUID` and
|
|
569
|
+
`EmailAddress`, and `uuid` takes its rule: an RFC 9562 version 1 to 8 with the
|
|
570
|
+
RFC variant, or the nil or max UUID. An id of no known version
|
|
571
|
+
(`…-91d4-…`) is refused.
|
|
572
|
+
|
|
573
|
+
**Fix:** rename the imports. For an id that is a shape only, write `guid`
|
|
574
|
+
instead of `uuid`:
|
|
575
|
+
|
|
576
|
+
```ts
|
|
577
|
+
import { type UUID, zUUID, type EmailAddress, zEmailAddress } from './generated';
|
|
578
|
+
```
|
|
579
|
+
|
|
580
|
+
```tsp
|
|
581
|
+
model Legacy { id: guid; }
|
|
582
|
+
```
|
|
583
|
+
|
|
492
584
|
## `Unknown decorator @operationId`
|
|
493
585
|
|
|
494
586
|
**When:** a spec writes `@operationId` next to `@operationIds`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS hex color: `#` then 3, 4, 6 or 8 hexadecimal digits, in any case, kept
|
|
9
|
+
* as sent: `#ff0000`. The 4 and 8 digit forms carry an alpha.
|
|
10
|
+
*
|
|
11
|
+
* Specified by https://www.w3.org/TR/css-color-4/#hex-notation.
|
|
12
|
+
*/
|
|
13
|
+
@friendlyName("HexColorCode")
|
|
14
|
+
@format("hex-color-code")
|
|
15
|
+
@pattern("^#(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$")
|
|
16
|
+
@extension("x-nxgt-scalar", "HexColorCode")
|
|
17
|
+
scalar hexColorCode extends string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS `hsl()` color in the comma syntax: `hsl(120, 100%, 50%)`. A hue from 0
|
|
9
|
+
* to 359 with no unit (360 is written 0), then saturation and lightness as
|
|
10
|
+
* integer percentages from 0% to 100%.
|
|
11
|
+
*
|
|
12
|
+
* Specified by https://www.w3.org/TR/css-color-4/#the-hsl-notation.
|
|
13
|
+
*/
|
|
14
|
+
@friendlyName("HSL")
|
|
15
|
+
@format("hsl")
|
|
16
|
+
@pattern("^hsl\\((?:3[0-5]\\d|[12]\\d\\d|[1-9]?\\d), (?:100|[1-9]?\\d)%, (?:100|[1-9]?\\d)%\\)$")
|
|
17
|
+
@extension("x-nxgt-scalar", "HSL")
|
|
18
|
+
scalar hsl extends string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS `hsla()` color in the comma syntax: `hsla(120, 100%, 50%, 0.5)`. As
|
|
9
|
+
* `hsl` plus an alpha: `0`, `1`, or a fraction with no trailing zero and a
|
|
10
|
+
* leading `0`.
|
|
11
|
+
*
|
|
12
|
+
* Specified by https://www.w3.org/TR/css-color-4/#the-hsl-notation.
|
|
13
|
+
*/
|
|
14
|
+
@friendlyName("HSLA")
|
|
15
|
+
@format("hsla")
|
|
16
|
+
@pattern("^hsla\\((?:3[0-5]\\d|[12]\\d\\d|[1-9]?\\d), (?:100|[1-9]?\\d)%, (?:100|[1-9]?\\d)%, (?:0|1|0\\.\\d*[1-9])\\)$")
|
|
17
|
+
@extension("x-nxgt-scalar", "HSLA")
|
|
18
|
+
scalar hsla extends string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import "@typespec/openapi";
|
|
2
|
+
|
|
3
|
+
using TypeSpec.OpenAPI;
|
|
4
|
+
|
|
5
|
+
namespace Nxgt;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* A CSS `rgb()` color in the comma syntax: `rgb(255, 0, 0)`. Three integers
|
|
9
|
+
* from 0 to 255, no leading zero, no percentage, a `, ` between them, the
|
|
10
|
+
* function name in lower case.
|
|
11
|
+
*
|
|
12
|
+
* Specified by https://www.w3.org/TR/css-color-4/#rgb-functions.
|
|
13
|
+
*/
|
|
14
|
+
@friendlyName("RGB")
|
|
15
|
+
@format("rgb")
|
|
16
|
+
@pattern("^rgb\\((?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d), (?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d), (?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)\\)$")
|
|
17
|
+
@extension("x-nxgt-scalar", "RGB")
|
|
18
|
+
scalar rgb extends string;
|