tshex-cli 1.0.26 → 1.0.28
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/docs/generated-file-reference.md +13 -10
- package/docs/library-structure.md +4 -4
- package/docs/shared/application/http/errors.md +96 -0
- package/docs/shared/application/http/handlers.md +102 -0
- package/docs/shared/application/http/json-api.md +235 -0
- package/docs/shared/application/http/json-web-token.md +209 -0
- package/docs/shared/application/http/opengraph.md +161 -0
- package/docs/shared/application/loggers.md +5 -4
- package/docs/types/json.md +82 -0
- package/docs/types/locales.md +77 -0
- package/docs/types/objects.md +70 -0
- package/docs/types/timezones.md +75 -0
- package/package.json +1 -1
- package/readme.md +23 -4
- package/templates/lib/shared/application/http/{http.ts → errors.ts} +0 -14
- package/templates/lib/shared/application/http/handlers.ts +13 -0
- package/templates/lib/shared/application/loggers.ts +2 -2
- package/docs/library-types.md +0 -112
- package/docs/shared/application/http.md +0 -283
- /package/templates/lib/types/{cldr.d.ts → locales.d.ts} +0 -0
- /package/templates/lib/types/{iana.d.ts → timezones.d.ts} +0 -0
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
### JSON Web Tokens
|
|
2
|
+
|
|
3
|
+
`json-web-token.ts` declares a type-only implementation of the JOSE and JWT
|
|
4
|
+
family of RFCs (JWS, JWE, JWK, JWT, and related extensions such as DPoP and
|
|
5
|
+
selective disclosure). It lets an adapter describe tokens and keys precisely
|
|
6
|
+
without depending on a specific JOSE library's own types.
|
|
7
|
+
|
|
8
|
+
This module has no runtime implementation. Pair it with a concrete JOSE
|
|
9
|
+
library (for signing, encryption, or verification) and use these types to
|
|
10
|
+
annotate its inputs and outputs.
|
|
11
|
+
|
|
12
|
+
#### Branded Wire Values
|
|
13
|
+
|
|
14
|
+
Wire-format primitives are branded so a plain `string` cannot be passed where
|
|
15
|
+
an encoded value is expected: `Base64Url`, `Base64`, `NumericDate`,
|
|
16
|
+
`StringOrURI`, `UriString`, `MediaType`, and `KeyId`. `BinaryData` is the
|
|
17
|
+
union `ArrayBuffer | Uint8Array` used wherever a JOSE library accepts raw
|
|
18
|
+
bytes.
|
|
19
|
+
|
|
20
|
+
#### JSON Web Keys
|
|
21
|
+
|
|
22
|
+
Every JWK shares `JwkCommonParameters` (`kty`, `use`, `key_ops`, `alg`, `kid`,
|
|
23
|
+
the `x5*` certificate members, and the OpenID Federation `iat`/`nbf`/`exp`/
|
|
24
|
+
`revoked` members). `JwkKeyType` (`kty`) then selects one of five key-family
|
|
25
|
+
options, each with a public and a private variant where the family supports
|
|
26
|
+
one:
|
|
27
|
+
|
|
28
|
+
| `kty` | Curve/size parameter | Public type | Private type |
|
|
29
|
+
| --- | --- | --- | --- |
|
|
30
|
+
| `'EC'` | `EcCurve` (`P-256`, `P-384`, `P-521`, `secp256k1`) | `EcPublicJwk` (`x`, `y`) | `EcPrivateJwk` (adds `d`) |
|
|
31
|
+
| `'RSA'` | — | `RsaPublicJwk` (`n`, `e`) | `RsaPrivateJwk` (adds `d`, `p`, `q`, `dp`, `dq`, `qi`, `oth`) |
|
|
32
|
+
| `'oct'` | — | — (symmetric only) | `OctJwk` (`k`) |
|
|
33
|
+
| `'OKP'` | `OkpCurve` (`Ed25519`, `Ed448`, `X25519`, `X448`) | `OkpPublicJwk` (`x`) | `OkpPrivateJwk` (adds `d`) |
|
|
34
|
+
| `'AKP'` | `MldsaAlgorithm` (`ML-DSA-44`/`65`/`87`) | `AkpPublicJwk` (`pub`) | `AkpPrivateJwk` (adds `priv`) |
|
|
35
|
+
|
|
36
|
+
`PublicJwk` and `PrivateJwk` are the unions across all five families;
|
|
37
|
+
`JsonWebKey` is `PublicJwk | PrivateJwk`.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import type { EcPublicJwk, RsaPrivateJwk, OctJwk } from '../../../shared/application/http/json-web-token.js'
|
|
41
|
+
|
|
42
|
+
const ecKey: EcPublicJwk = { kty: 'EC', crv: 'P-256', x: '...' as Base64Url, y: '...' as Base64Url }
|
|
43
|
+
const rsaKey: RsaPrivateJwk = { kty: 'RSA', n: '...' as Base64Url, e: '...' as Base64Url, d: '...' as Base64Url }
|
|
44
|
+
const symmetricKey: OctJwk = { kty: 'oct', k: '...' as Base64Url }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`JsonWebKeySet` wraps a `keys` array of mixed key types; `PublicJsonWebKeySet`
|
|
48
|
+
narrows that array to public keys only, for a key set safe to publish. A
|
|
49
|
+
thumbprint (`JwkThumbprint`) is a `Base64Url`; `JwkThumbprintUri` is the
|
|
50
|
+
`urn:ietf:params:oauth:jwk-thumbprint:` URI form.
|
|
51
|
+
|
|
52
|
+
#### Headers
|
|
53
|
+
|
|
54
|
+
`JoseCommonHeaderParameters` covers the parameters shared by both header
|
|
55
|
+
kinds (`jku`, `jwk`, `kid`, the `x5*` members, `typ`, `cty`, `crit`).
|
|
56
|
+
`JwsHeaderParameters` and `JweHeaderParameters` extend it with the two
|
|
57
|
+
distinct options:
|
|
58
|
+
|
|
59
|
+
| Type | Required members | Extra members |
|
|
60
|
+
| --- | --- | --- |
|
|
61
|
+
| `JwsHeaderParameters` | `alg: JwsAlgorithm` | `b64`, `ppt`, `url`, `nonce`, `svt`, `jwt`, `client_id`, `trust_chain`, `peer_trust_chain` |
|
|
62
|
+
| `JweHeaderParameters` | `alg: JweKeyManagementAlgorithm`, `enc: JweContentEncryptionAlgorithm` | `zip`, `epk`, `apu`, `apv`, `iv`, `tag`, `p2s`, `p2c`, replicated `iss`/`sub`/`aud`, `url`, `nonce` |
|
|
63
|
+
|
|
64
|
+
`JwsHeader<CustomHeader>` and `JweHeader<CustomHeader>` intersect the
|
|
65
|
+
respective parameters with a caller-supplied custom header shape.
|
|
66
|
+
|
|
67
|
+
#### Serializations
|
|
68
|
+
|
|
69
|
+
A JWS or a JWE can be represented four ways: as a single delimited string
|
|
70
|
+
(compact), or as JSON with either one signer/recipient inlined (flattened) or
|
|
71
|
+
several (general).
|
|
72
|
+
|
|
73
|
+
| Type | Kind | Shape |
|
|
74
|
+
| --- | --- | --- |
|
|
75
|
+
| `CompactJws` | JWS, compact | `` `${string}.${string}.${string}` `` branded string |
|
|
76
|
+
| `GeneralJwsJsonSerialization` | JWS, JSON, general | `payload` + `signatures[]` |
|
|
77
|
+
| `FlattenedJwsJsonSerialization` | JWS, JSON, flattened | `payload` + one signature inlined |
|
|
78
|
+
| `CompactJwe` | JWE, compact | `` `${string}.${string}.${string}.${string}.${string}` `` branded string |
|
|
79
|
+
| `GeneralJweJsonSerialization` | JWE, JSON, general | shared members + `recipients[]` |
|
|
80
|
+
| `FlattenedJweJsonSerialization` | JWE, JSON, flattened | shared members + one recipient inlined |
|
|
81
|
+
|
|
82
|
+
`JwsJsonSerialization` and `JweJsonSerialization` are the general/flattened
|
|
83
|
+
unions for each. `UnsecuredCompactJws` types the `alg: 'none'` compact form
|
|
84
|
+
(`` `${string}.${string}.` `` — an empty signature segment).
|
|
85
|
+
|
|
86
|
+
```ts
|
|
87
|
+
import type { CompactJws, FlattenedJwsJsonSerialization } from '../../../shared/application/http/json-web-token.js'
|
|
88
|
+
|
|
89
|
+
const compact = 'eyJhbGciOiJFUzI1NiJ9.eyJzdWIiOiIxIn0.c2ln' as CompactJws
|
|
90
|
+
|
|
91
|
+
const flattened: FlattenedJwsJsonSerialization = {
|
|
92
|
+
payload: 'eyJzdWIiOiIxIn0' as Base64Url,
|
|
93
|
+
protected: 'eyJhbGciOiJFUzI1NiJ9' as Base64Url,
|
|
94
|
+
signature: 'c2ln' as Base64Url,
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`DecodedJws` and `DecodedJwe` describe a decoded token's structure;
|
|
99
|
+
`DecodedJwt` is their union, discriminated by a `kind: 'JWS' | 'JWE'` tag.
|
|
100
|
+
`CompactJwt` is `CompactJws | CompactJwe`, matching how RFC 7519 allows a JWT
|
|
101
|
+
to use either serialization.
|
|
102
|
+
|
|
103
|
+
#### JWT Claims
|
|
104
|
+
|
|
105
|
+
`JwtRegisteredClaims` covers the seven RFC 7519 claims (`iss`, `sub`, `aud`,
|
|
106
|
+
`exp`, `nbf`, `iat`, `jti`). `IanaRegisteredJwtClaims` extends it with every
|
|
107
|
+
claim name currently registered by IANA (OpenID Connect profile claims, SIP,
|
|
108
|
+
CDNI, GNAP, EAT, RATS, RFC 9901 selective-disclosure claims, OpenID
|
|
109
|
+
Federation claims, and more), so `JwtClaims<CustomClaims>` covers arbitrary
|
|
110
|
+
custom claims through intersection while still typing every standard one.
|
|
111
|
+
|
|
112
|
+
Four ready-made profiles fix `JwtRegisteredClaims` members to `required`
|
|
113
|
+
where the profile mandates them, as distinct implementation options for
|
|
114
|
+
common token kinds:
|
|
115
|
+
|
|
116
|
+
| Type | Profile | Required beyond the base seven |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| `OpenIdConnectIdTokenClaims` | OIDC ID Token | `iss`, `sub`, `aud`, `exp`, `iat` |
|
|
119
|
+
| `OAuth2JwtAccessTokenClaims` | RFC 9068 OAuth 2.0 access token | `iss`, `exp`, `aud`, `sub`, `client_id`, `iat`, `jti` |
|
|
120
|
+
| `DpopProofClaims` | RFC 9449 DPoP proof | `jti`, `htm`, `htu`, `iat` |
|
|
121
|
+
| `SdJwtClaims` | RFC 9901 SD-JWT | none beyond `IanaRegisteredJwtClaims`, adds `_sd`/`_sd_alg` |
|
|
122
|
+
|
|
123
|
+
```ts title="users/adapters/verify-access-token.ts"
|
|
124
|
+
import {
|
|
125
|
+
JwsVerifier,
|
|
126
|
+
OAuth2JwtAccessTokenClaims,
|
|
127
|
+
CompactJws,
|
|
128
|
+
JsonWebKey,
|
|
129
|
+
} from '../../shared/application/http/json-web-token.js'
|
|
130
|
+
|
|
131
|
+
export function verifyAccessToken(
|
|
132
|
+
verifier: JwsVerifier,
|
|
133
|
+
token: CompactJws,
|
|
134
|
+
key: JsonWebKey,
|
|
135
|
+
) {
|
|
136
|
+
return verifier.verify<OAuth2JwtAccessTokenClaims>(token, key)
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`SdJwtDisclosure`, `SdJwt`, and `SdJwtPresentation` brand the three distinct
|
|
141
|
+
string forms used by selective disclosure: a single disclosure, an issued
|
|
142
|
+
SD-JWT, and a presentation with disclosures appended.
|
|
143
|
+
|
|
144
|
+
#### Service Contracts
|
|
145
|
+
|
|
146
|
+
An adapter implements these interfaces against a concrete JOSE library. Each
|
|
147
|
+
one exposes a distinct operation:
|
|
148
|
+
|
|
149
|
+
| Contract | Method(s) | Responsibility |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| `JwtDecoder` | `decode()` | Parse a compact token into a `DecodedJwt` without necessarily validating it. |
|
|
152
|
+
| `JwsSigner` | `signCompact()`, `signFlattened()` | Produce a JWS in either serialization. |
|
|
153
|
+
| `JwsVerifier` | `verify()` | Validate a JWS and return a `JwtValidationResult`. |
|
|
154
|
+
| `JweEncrypter` | `encryptCompact()`, `encryptFlattened()` | Produce a JWE in either serialization. |
|
|
155
|
+
| `JweDecrypter` | `decrypt()` | Decrypt a JWE back into `BinaryData`. |
|
|
156
|
+
| `JwkThumbprinter` | `thumbprint()`, `thumbprintUri()` | Compute an RFC 7638 thumbprint, plain or as a URI. |
|
|
157
|
+
| `JwksResolver` | `resolve()`, `select()` | Fetch a JWK Set and pick the key matching a header's `kid`/`x5t`/`x5t#S256`. |
|
|
158
|
+
|
|
159
|
+
```ts
|
|
160
|
+
import type { JwkThumbprinter, JsonWebKey } from '../../../shared/application/http/json-web-token.js'
|
|
161
|
+
|
|
162
|
+
function fingerprint(thumbprinter: JwkThumbprinter, key: JsonWebKey) {
|
|
163
|
+
return thumbprinter.thumbprintUri(key)
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
#### Validating A Token
|
|
168
|
+
|
|
169
|
+
`JwtValidationResult` is the outcome of checking a token against a
|
|
170
|
+
`JwtValidationPolicy` (`issuers`, `audiences`, `subjects`, `algorithms`,
|
|
171
|
+
`requiredClaims`, `clockToleranceSeconds`, `maxTokenAgeSeconds`, `typ`). It
|
|
172
|
+
has exactly two implementation options, discriminated by `valid`:
|
|
173
|
+
|
|
174
|
+
| Type | `valid` | Payload |
|
|
175
|
+
| --- | --- | --- |
|
|
176
|
+
| `JwtValidationSuccess` | `true` | `value: DecodedJws<Claims>` |
|
|
177
|
+
| `JwtValidationFailure` | `false` | `code`, `message`, optional `cause` |
|
|
178
|
+
|
|
179
|
+
`JwtValidationFailure['code']` enumerates the recognized rejection reasons:
|
|
180
|
+
`'malformed'`, `'unsupported_serialization'`, `'unsupported_algorithm'`,
|
|
181
|
+
`'invalid_signature'`, `'decryption_failed'`, `'expired'`, `'not_active'`,
|
|
182
|
+
`'issued_in_future'`, `'issuer_mismatch'`, `'audience_mismatch'`,
|
|
183
|
+
`'subject_mismatch'`, `'missing_claim'`, `'critical_header_unsupported'`, and
|
|
184
|
+
`'policy_rejected'` (plus an open string for adapter-specific codes).
|
|
185
|
+
|
|
186
|
+
```ts
|
|
187
|
+
import type { JwtValidationResult } from '../../../shared/application/http/json-web-token.js'
|
|
188
|
+
|
|
189
|
+
function describeResult(result: JwtValidationResult): string {
|
|
190
|
+
if (result.valid) {
|
|
191
|
+
return `ok, subject=${result.value.claims.sub}`
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return `rejected: ${result.code}`
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
#### Registry Metadata
|
|
199
|
+
|
|
200
|
+
`JoseAlgorithmRegistryEntry`, `JoseHeaderParameterRegistryEntry`, and
|
|
201
|
+
`JwtClaimRegistryEntry` type the shape of a row describing an algorithm, a
|
|
202
|
+
header parameter, or a claim, for adapters that want to render or validate
|
|
203
|
+
against IANA's own registry data instead of hardcoding the unions above.
|
|
204
|
+
|
|
205
|
+
> **Hint**
|
|
206
|
+
> This module has no runtime implementation. Choose a JOSE library, then use
|
|
207
|
+
> `JwsSigner`/`JwsVerifier`/`JweEncrypter`/`JweDecrypter`/`JwtDecoder` to
|
|
208
|
+
> annotate its inputs and outputs so the rest of the codebase stays independent
|
|
209
|
+
> from that library's own types.
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
### Open Graph
|
|
2
|
+
|
|
3
|
+
`opengraph.ts` declares a type-only implementation of the
|
|
4
|
+
[Open Graph protocol](https://ogp.me/), the Twitter Card meta tags, and
|
|
5
|
+
Facebook's compatibility extensions. It is used when an adapter needs to build
|
|
6
|
+
or read the social-sharing metadata of a page.
|
|
7
|
+
|
|
8
|
+
This module has no runtime values or implementations, including no HTML
|
|
9
|
+
rendering. Use it to build the data and a separate template or renderer to
|
|
10
|
+
emit the actual `<meta>` tags.
|
|
11
|
+
|
|
12
|
+
#### Base Shape
|
|
13
|
+
|
|
14
|
+
Every Open Graph object shares `OpenGraphBase<TType>`: the required `title`,
|
|
15
|
+
`type`, `images` (`OneOrMore<OpenGraphImage>`, first has precedence), and
|
|
16
|
+
`url`, plus the optional `audio`, `description`, `determiner`, `locale`,
|
|
17
|
+
`alternateLocales`, `siteName`, and `videos`. `OpenGraphObjectExtensions`
|
|
18
|
+
adds an optional `extensions` map for CURIE-namespaced custom properties
|
|
19
|
+
(`'product:color'`, etc.), kept as opaque strings.
|
|
20
|
+
|
|
21
|
+
#### Standard Object Types
|
|
22
|
+
|
|
23
|
+
`OpenGraphType` selects one of thirteen standard types
|
|
24
|
+
(`OpenGraphStandardType`) or a custom CURIE type
|
|
25
|
+
(`OpenGraphCustomType`, e.g. `'product:item'`). Each standard type has its
|
|
26
|
+
own interface adding the fields that type requires:
|
|
27
|
+
|
|
28
|
+
| `type` | Interface | Extra fields |
|
|
29
|
+
| --- | --- | --- |
|
|
30
|
+
| `'website'` | `OpenGraphWebsite` | none |
|
|
31
|
+
| `'article'` | `OpenGraphArticle` | `publishedTime`, `modifiedTime`, `expirationTime`, `authors`, `section`, `tags` |
|
|
32
|
+
| `'book'` | `OpenGraphBook` | `authors`, `isbn`, `releaseDate`, `tags` |
|
|
33
|
+
| `'profile'` | `OpenGraphProfile` | `firstName`, `lastName`, `username`, `gender` |
|
|
34
|
+
| `'music.song'` | `OpenGraphMusicSong` | `duration`, `albums`, `musicians` |
|
|
35
|
+
| `'music.album'` | `OpenGraphMusicAlbum` | `songs`, `musicians`, `releaseDate` |
|
|
36
|
+
| `'music.playlist'` | `OpenGraphMusicPlaylist` | `songs`, `creator` |
|
|
37
|
+
| `'music.radio_station'` | `OpenGraphMusicRadioStation` | `creator` |
|
|
38
|
+
| `'video.movie'` | `OpenGraphVideoMovie` | `actors`, `directors`, `writers`, `duration`, `releaseDate`, `tags` (`OpenGraphVideoMetadata`) |
|
|
39
|
+
| `'video.episode'` | `OpenGraphVideoEpisode` | `OpenGraphVideoMetadata` + `series` |
|
|
40
|
+
| `'video.tv_show'` | `OpenGraphVideoTvShow` | `OpenGraphVideoMetadata` |
|
|
41
|
+
| `'video.other'` | `OpenGraphVideoOther` | `OpenGraphVideoMetadata` |
|
|
42
|
+
| `'payment.link'` | `OpenGraphPaymentLink` | `paymentDescription`, `currency`, `amount`, `expiresAt`, `paymentStatus`, `paymentId`, `successUrl` (marked beta by the protocol) |
|
|
43
|
+
| CURIE type | `OpenGraphCustomObject` | none beyond the base shape |
|
|
44
|
+
|
|
45
|
+
`OpenGraphMetadata` is the union of all fourteen. Building one selects the
|
|
46
|
+
type through the discriminant and, from there, TypeScript narrows to that
|
|
47
|
+
type's own extra fields.
|
|
48
|
+
|
|
49
|
+
```ts title="users/adapters/user-profile-metadata.ts"
|
|
50
|
+
import { OpenGraphProfile } from '../../shared/application/http/opengraph.js'
|
|
51
|
+
|
|
52
|
+
export function buildProfileMetadata(username: string): OpenGraphProfile {
|
|
53
|
+
return {
|
|
54
|
+
type: 'profile',
|
|
55
|
+
title: username,
|
|
56
|
+
url: `https://example.com/users/${username}`,
|
|
57
|
+
images: [{ url: `https://example.com/users/${username}/avatar.png` }],
|
|
58
|
+
username,
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
```ts title="users/adapters/article-metadata.ts"
|
|
64
|
+
import { OpenGraphArticle } from '../../shared/application/http/opengraph.js'
|
|
65
|
+
|
|
66
|
+
export function buildArticleMetadata(slug: string): OpenGraphArticle {
|
|
67
|
+
return {
|
|
68
|
+
type: 'article',
|
|
69
|
+
title: 'How context ports work',
|
|
70
|
+
url: `https://example.com/blog/${slug}`,
|
|
71
|
+
images: [{ url: `https://example.com/blog/${slug}/cover.png` }],
|
|
72
|
+
publishedTime: new Date().toISOString(),
|
|
73
|
+
tags: ['architecture', 'typescript'],
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
`OpenGraphPaymentStatus` (`'PENDING' | 'PAID' | 'FAILED' | 'EXPIRED'`) types
|
|
79
|
+
`OpenGraphPaymentLink['paymentStatus']`.
|
|
80
|
+
|
|
81
|
+
#### Twitter Cards
|
|
82
|
+
|
|
83
|
+
`TwitterCardType` selects one of four card layouts. `TwitterCardBase<TCard>`
|
|
84
|
+
carries the members every card shares (`site`, `siteId`, `creator`,
|
|
85
|
+
`creatorId`, `title`, `description`); each concrete card adds the fields that
|
|
86
|
+
layout needs:
|
|
87
|
+
|
|
88
|
+
| `card` | Interface | Extra/required fields |
|
|
89
|
+
| --- | --- | --- |
|
|
90
|
+
| `'summary'` | `TwitterSummaryCard` | optional `image` |
|
|
91
|
+
| `'summary_large_image'` | `TwitterSummaryLargeImageCard` | optional `image` |
|
|
92
|
+
| `'player'` | `TwitterPlayerCard` | required `image`, `player`, `playerWidth`, `playerHeight`; optional `playerStream`, `playerStreamContentType` |
|
|
93
|
+
| `'app'` | `TwitterAppCard` | optional `country`, `iphone`, `ipad`, `googlePlay` (each `TwitterAppPlatform`) |
|
|
94
|
+
|
|
95
|
+
`TwitterCardMetadata` is the union of all four.
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import type { TwitterPlayerCard } from '../../../shared/application/http/opengraph.js'
|
|
99
|
+
|
|
100
|
+
const playerCard: TwitterPlayerCard = {
|
|
101
|
+
card: 'player',
|
|
102
|
+
image: { url: 'https://example.com/videos/1/thumb.png' },
|
|
103
|
+
player: 'https://example.com/videos/1/embed',
|
|
104
|
+
playerWidth: 640,
|
|
105
|
+
playerHeight: 360,
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
#### Raw Meta Tag Representation
|
|
110
|
+
|
|
111
|
+
`OpenGraphMetaTag`/`TwitterMetaTag` are the flat `property`/`content` and
|
|
112
|
+
`name`/`content` tag forms closer to the actual `<meta>` markup, for a
|
|
113
|
+
renderer that emits tags directly instead of consuming the structured
|
|
114
|
+
objects above.
|
|
115
|
+
|
|
116
|
+
| Tag family | Type | Attribute pair |
|
|
117
|
+
| --- | --- | --- |
|
|
118
|
+
| Open Graph (standard) | `OpenGraphStandardMetaTag` | `property`/`content`, e.g. `'og:title'` |
|
|
119
|
+
| Open Graph (custom) | `OpenGraphCustomMetaTag` | any `` `${string}:${string}` `` property |
|
|
120
|
+
| Twitter | `TwitterMetaTag` | `name`/`content`, e.g. `'twitter:card'` |
|
|
121
|
+
| Facebook | `FacebookMetaTag` | `PropertyMetaTag<'fb:app_id'>` |
|
|
122
|
+
| Standard HTML | `StandardHtmlMetaTag` | `'description'` \| `'theme-color'` |
|
|
123
|
+
|
|
124
|
+
`SocialMetaTag` is the union of all five families. `CanonicalLinkTag` types
|
|
125
|
+
the `<link rel="canonical">` element separately, since it is not a `<meta>`
|
|
126
|
+
tag.
|
|
127
|
+
|
|
128
|
+
#### Aggregate Document
|
|
129
|
+
|
|
130
|
+
Two document shapes cover the two stages of building a page's social
|
|
131
|
+
metadata:
|
|
132
|
+
|
|
133
|
+
| Type | Shape | Use |
|
|
134
|
+
| --- | --- | --- |
|
|
135
|
+
| `SocialMetadataDocument` | `head?`, `openGraph` (required), `twitter?`, `facebook?` | Structured data a service builds before rendering |
|
|
136
|
+
| `RawSocialMetadataDocument` | `title?`, `meta: SocialMetaTag[]`, `links?: CanonicalLinkTag[]` | The rendered, tag-list form a template consumes |
|
|
137
|
+
|
|
138
|
+
```ts title="users/adapters/social-metadata-document.ts"
|
|
139
|
+
import {
|
|
140
|
+
SocialMetadataDocument,
|
|
141
|
+
OpenGraphProfile,
|
|
142
|
+
} from '../../shared/application/http/opengraph.js'
|
|
143
|
+
|
|
144
|
+
export function buildSocialMetadata(profile: OpenGraphProfile): SocialMetadataDocument {
|
|
145
|
+
return {
|
|
146
|
+
head: { title: profile.title, description: profile.description },
|
|
147
|
+
openGraph: profile,
|
|
148
|
+
twitter: { card: 'summary', title: profile.title },
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Converting a `SocialMetadataDocument` into a `RawSocialMetadataDocument` is
|
|
154
|
+
the responsibility of a renderer, which flattens each structured field into
|
|
155
|
+
its corresponding `SocialMetaTag` entries; that conversion is not implemented
|
|
156
|
+
by this module.
|
|
157
|
+
|
|
158
|
+
> **Hint**
|
|
159
|
+
> This module has no runtime implementation, including no HTML rendering. Use
|
|
160
|
+
> `OpenGraphMetadata` to build the data and a separate template or renderer to
|
|
161
|
+
> emit the `<meta>` tags described by `OpenGraphMetaTag`/`TwitterMetaTag`.
|
|
@@ -28,8 +28,8 @@ any adapter to use a particular logger implementation.
|
|
|
28
28
|
`Logger` is responsible for receiving log data from the application layer.
|
|
29
29
|
|
|
30
30
|
```ts title="shared/application/loggers.ts"
|
|
31
|
-
import { type TimeZone } from '../../types/
|
|
32
|
-
import { type Locale } from '../../types/
|
|
31
|
+
import { type TimeZone } from '../../types/timezones.js'
|
|
32
|
+
import { type Locale } from '../../types/locales.js'
|
|
33
33
|
|
|
34
34
|
export abstract class Logger {
|
|
35
35
|
[property: string]: unknown
|
|
@@ -74,8 +74,9 @@ request, while the adapter decides how those actions are persisted or displayed.
|
|
|
74
74
|
`name` and `level` identify the logger instance and its minimum severity, so an
|
|
75
75
|
adapter can decide which logs to emit or route. `datetimeLocales` and
|
|
76
76
|
`datetimeFormatOptions` control how `getCurrentDatetime()` formats the current
|
|
77
|
-
moment, using the `Locale` type from `types/
|
|
78
|
-
from `types/
|
|
77
|
+
moment, using the `Locale` type from `types/locales.d.ts` (see
|
|
78
|
+
`types/locales.md`) and the `TimeZone` type from `types/timezones.d.ts` (see
|
|
79
|
+
`types/timezones.md`). Adapters can use `getCurrentDatetime()` to timestamp
|
|
79
80
|
log entries consistently, regardless of the runtime environment's own locale
|
|
80
81
|
or timezone.
|
|
81
82
|
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
### JSON
|
|
2
|
+
|
|
3
|
+
The generated root also declares a small family of types that describe plain,
|
|
4
|
+
serializable JSON data.
|
|
5
|
+
They are used when a contract must guarantee that a value survives a round
|
|
6
|
+
trip through `JSON.stringify()` / `JSON.parse()`.
|
|
7
|
+
|
|
8
|
+
#### Declaration
|
|
9
|
+
|
|
10
|
+
These types live in `types/json.d.ts`.
|
|
11
|
+
|
|
12
|
+
```ts title="types/json.d.ts"
|
|
13
|
+
export type JsonPrimitive = string | number | boolean | null
|
|
14
|
+
|
|
15
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonArray
|
|
16
|
+
|
|
17
|
+
export type JsonArray = readonly JsonValue[]
|
|
18
|
+
|
|
19
|
+
export type JsonObject = {
|
|
20
|
+
readonly [key: string]: JsonValue
|
|
21
|
+
}
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
`JsonPrimitive` covers the scalar values allowed in JSON. `JsonValue` extends
|
|
25
|
+
that with nested objects and arrays, so it recursively describes any JSON-safe
|
|
26
|
+
value. `JsonObject` and `JsonArray` name the two composite shapes so other
|
|
27
|
+
declarations can refer to them directly instead of repeating the union.
|
|
28
|
+
|
|
29
|
+
#### Implementation Options
|
|
30
|
+
|
|
31
|
+
A `JsonValue` is always one of four shapes. Each one is a distinct option a
|
|
32
|
+
consumer must be ready to handle.
|
|
33
|
+
|
|
34
|
+
| Shape | Type | Example |
|
|
35
|
+
| --- | --- | --- |
|
|
36
|
+
| Primitive | `JsonPrimitive` | `'active'`, `42`, `true`, `null` |
|
|
37
|
+
| Object | `JsonObject` | `{ id: '1', active: true }` |
|
|
38
|
+
| Array | `JsonArray` | `[1, 2, 3]`, `[{ id: '1' }]` |
|
|
39
|
+
| Nested composite | `JsonValue` | `{ tags: ['a', 'b'], meta: { retries: 2 } }` |
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { type JsonValue } from './types/json.js'
|
|
43
|
+
|
|
44
|
+
const primitive: JsonValue = 'ada@example.com'
|
|
45
|
+
const object: JsonValue = { id: '1', active: true }
|
|
46
|
+
const array: JsonValue = [1, 2, 3]
|
|
47
|
+
const nested: JsonValue = { tags: ['a', 'b'], meta: { retries: 2 } }
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
All four are valid `JsonValue` values because the type is a recursive union;
|
|
51
|
+
there is no separate constructor or runtime check to opt into a shape.
|
|
52
|
+
|
|
53
|
+
#### Basic Usage
|
|
54
|
+
|
|
55
|
+
```ts
|
|
56
|
+
import { type JsonValue } from './types/json.js'
|
|
57
|
+
|
|
58
|
+
function toLogPayload(value: JsonValue): string {
|
|
59
|
+
return JSON.stringify(value)
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Because `JsonValue` excludes functions, `undefined`, symbols, and other
|
|
64
|
+
non-serializable values, `toLogPayload()` can call `JSON.stringify()` without
|
|
65
|
+
guarding against values that would silently disappear or throw.
|
|
66
|
+
|
|
67
|
+
#### JsonObject Versus Generic
|
|
68
|
+
|
|
69
|
+
Use `JsonObject`/`JsonValue` when a contract must guarantee its data is plain
|
|
70
|
+
and serializable, such as request payloads, stored metadata, or wire formats.
|
|
71
|
+
Prefer `types/objects.md`'s `Generic<T>` instead when the value type is not
|
|
72
|
+
required to be JSON-safe.
|
|
73
|
+
|
|
74
|
+
`shared/application/http/json-api.ts` and
|
|
75
|
+
`shared/application/http/json-web-token.ts` build on these types to describe
|
|
76
|
+
JSON:API documents and JOSE/JWT structures; see `shared/application/http/json-api.md`
|
|
77
|
+
and `shared/application/http/json-web-token.md`.
|
|
78
|
+
|
|
79
|
+
> **Hint**
|
|
80
|
+
> These declarations only provide compile-time structure. They do not validate
|
|
81
|
+
> that a runtime value is actually JSON-safe; a value typed as `JsonValue` can
|
|
82
|
+
> still contain a `Date` or a class instance if it was cast into the type.
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
### Locales
|
|
2
|
+
|
|
3
|
+
`Locale` is a literal string union of every locale identifier available in
|
|
4
|
+
Unicode CLDR.
|
|
5
|
+
It is used when a contract needs to accept only valid locale tags instead of
|
|
6
|
+
an open `string`.
|
|
7
|
+
|
|
8
|
+
#### Declaration
|
|
9
|
+
|
|
10
|
+
`Locale` lives in `types/locales.d.ts` and is generated from Unicode CLDR
|
|
11
|
+
48.2.1.
|
|
12
|
+
|
|
13
|
+
```ts title="types/locales.d.ts"
|
|
14
|
+
export type Locale =
|
|
15
|
+
| 'aa'
|
|
16
|
+
| 'af'
|
|
17
|
+
| 'am'
|
|
18
|
+
| 'ar'
|
|
19
|
+
| 'ar-EG'
|
|
20
|
+
| 'de'
|
|
21
|
+
| 'de-AT'
|
|
22
|
+
| 'en'
|
|
23
|
+
| 'en-GB'
|
|
24
|
+
| 'en-US'
|
|
25
|
+
| 'es'
|
|
26
|
+
| 'es-419'
|
|
27
|
+
| 'fr'
|
|
28
|
+
| 'ja'
|
|
29
|
+
| 'zh-Hans'
|
|
30
|
+
// ...every other CLDR locale identifier
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
The generated file lists every language identifier and every regional variant
|
|
34
|
+
registered by CLDR, from bare language tags such as `'en'` to script- and
|
|
35
|
+
region-qualified tags such as `'zh-Hant-HK'` or `'ca-ES-valencia'`.
|
|
36
|
+
|
|
37
|
+
#### Basic Usage
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { type Locale } from './types/locales.js'
|
|
41
|
+
|
|
42
|
+
function formatCount(value: number, locale: Locale): string {
|
|
43
|
+
return new Intl.NumberFormat(locale).format(value)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
formatCount(1200, 'en-US')
|
|
47
|
+
formatCount(1200, 'es-419')
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Because `Locale` only accepts identifiers CLDR actually defines, a typo such
|
|
51
|
+
as `'en-USA'` fails at compile time instead of silently reaching
|
|
52
|
+
`Intl.NumberFormat`.
|
|
53
|
+
|
|
54
|
+
#### Accepting Multiple Locales
|
|
55
|
+
|
|
56
|
+
`Intl` APIs commonly accept a locale or a list of locales in priority order.
|
|
57
|
+
`Locale[]` expresses that same option without widening to `string[]`.
|
|
58
|
+
|
|
59
|
+
```ts
|
|
60
|
+
import { type Locale } from './types/locales.js'
|
|
61
|
+
|
|
62
|
+
const preferredLocales: Locale[] = ['fr-CA', 'fr', 'en']
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
The runtime resolves the first supported locale from the list; `Locale[]`
|
|
66
|
+
only guarantees that every candidate is a real CLDR identifier.
|
|
67
|
+
|
|
68
|
+
#### Where It Is Used
|
|
69
|
+
|
|
70
|
+
`shared/application/loggers.ts` uses `Locale[]` for `Logger.datetimeLocales`,
|
|
71
|
+
the locale list passed to `Date.prototype.toLocaleString()` when formatting a
|
|
72
|
+
log timestamp. See `shared/application/loggers.md`.
|
|
73
|
+
|
|
74
|
+
> **Hint**
|
|
75
|
+
> `Locale` is a compile-time contract only. It does not validate that the
|
|
76
|
+
> runtime's ICU data actually supports every listed locale; `Intl` APIs fall
|
|
77
|
+
> back to a default when a requested locale is unsupported at runtime.
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
### Objects
|
|
2
|
+
|
|
3
|
+
The generated root declares `Generic<T>`, a plain object whose keys are strings
|
|
4
|
+
and whose values share the same type.
|
|
5
|
+
It provides a small common building block for code that works with object-like
|
|
6
|
+
data but does not need a more specific shape yet.
|
|
7
|
+
|
|
8
|
+
#### Declaration
|
|
9
|
+
|
|
10
|
+
`Generic<T>` lives in `types/objects.d.ts`.
|
|
11
|
+
|
|
12
|
+
```ts title="types/objects.d.ts"
|
|
13
|
+
export type Generic<T = unknown> = Record<string, T>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
This alias expands to `Record<string, T>`. When no type argument is provided,
|
|
17
|
+
the values use `unknown`.
|
|
18
|
+
|
|
19
|
+
#### With A Type Argument
|
|
20
|
+
|
|
21
|
+
In the following example we use `Generic<string>` for a set of plain filters.
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
import { type Generic } from './types/objects.js'
|
|
25
|
+
|
|
26
|
+
const filters: Generic<string> = {
|
|
27
|
+
status: 'active',
|
|
28
|
+
sort: 'email',
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
`filters` can only store string values because the type argument fixes the
|
|
33
|
+
value shape for the whole object.
|
|
34
|
+
|
|
35
|
+
#### Without A Type Argument
|
|
36
|
+
|
|
37
|
+
Now consider the same pattern without providing a type argument.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { type Generic } from './types/objects.js'
|
|
41
|
+
|
|
42
|
+
const metadata: Generic = {
|
|
43
|
+
retries: 2,
|
|
44
|
+
cached: true,
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
In this case the values use `unknown`. This is useful when the object is plain
|
|
49
|
+
and open-ended, but the caller must narrow each value before using it in a
|
|
50
|
+
specific way.
|
|
51
|
+
|
|
52
|
+
#### When To Use It
|
|
53
|
+
|
|
54
|
+
Use `Generic<T>` when the code needs a simple object contract and the exact set
|
|
55
|
+
of keys is not the main concern.
|
|
56
|
+
|
|
57
|
+
Typical uses include:
|
|
58
|
+
|
|
59
|
+
1. filter objects;
|
|
60
|
+
2. metadata objects;
|
|
61
|
+
3. plain configuration maps;
|
|
62
|
+
4. transport-neutral dictionaries.
|
|
63
|
+
|
|
64
|
+
When the object has a stable business meaning, prefer a named type instead of a
|
|
65
|
+
generic record.
|
|
66
|
+
|
|
67
|
+
> **Hint**
|
|
68
|
+
> `Generic<T>` is intentionally small. It should support loose object contracts,
|
|
69
|
+
> not replace explicit domain or application types. It is also the base shape
|
|
70
|
+
> used by `types/json.md` when a value only needs to be JSON-safe.
|