tshex-cli 1.0.27 → 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.
@@ -22,13 +22,14 @@ The `types/` directory contains root-level ambient type declarations.
22
22
  | --- | --- |
23
23
  | `types/objects.d.ts` | Declares root-level shared types such as `Generic<T>`. |
24
24
  | `types/json.d.ts` | Declares `JsonValue` and the other plain, serializable JSON shapes. |
25
- | `types/cldr.d.ts` | Declares the `Locale` union from Unicode CLDR. |
26
- | `types/iana.d.ts` | Declares the `TimeZone` union from the IANA time zone database. |
25
+ | `types/locales.d.ts` | Declares the `Locale` union from Unicode CLDR. |
26
+ | `types/timezones.d.ts` | Declares the `TimeZone` union from the IANA time zone database. |
27
27
 
28
28
  `types/objects.d.ts` and `types/json.d.ts` are the place for general-purpose
29
- root-level type declarations. `types/cldr.d.ts` and `types/iana.d.ts` are
30
- generated reference types consumed by other shared contracts, such as
31
- `shared/application/loggers.ts`.
29
+ root-level type declarations. `types/locales.d.ts` and `types/timezones.d.ts`
30
+ are generated reference types consumed by other shared contracts, such as
31
+ `shared/application/loggers.ts`. Each file is documented in its own page under
32
+ `types/*.md`.
32
33
 
33
34
  #### Shared Domain Files
34
35
 
@@ -66,15 +67,17 @@ boundary and the type-only specifications for common web content formats.
66
67
 
67
68
  | File | Responsibility |
68
69
  | --- | --- |
69
- | `shared/application/http/http.ts` | Declares `HttpRequestHandler`, `HttpMiddleware`, and `HttpError`. |
70
+ | `shared/application/http/handlers.ts` | Declares `HttpRequestHandler` and `HttpMiddleware`. |
71
+ | `shared/application/http/errors.ts` | Declares `HttpError`. |
70
72
  | `shared/application/http/json-api.ts` | Type-only JSON:API v1.1 document, resource, and Atomic Operations declarations. |
71
73
  | `shared/application/http/json-web-token.ts` | Type-only JOSE/JWT declarations (JWK, JWS, JWE, JWT claims). |
72
74
  | `shared/application/http/opengraph.ts` | Type-only Open Graph, Twitter Card, and social metadata declarations. |
73
75
 
74
- `http.ts` is the only file in this directory with runtime code. `json-api.ts`,
75
- `json-web-token.ts`, and `opengraph.ts` contain compile-time structure only;
76
- they describe the shape of external formats without implementing parsing,
77
- validation, or serialization.
76
+ `handlers.ts` and `errors.ts` are the only files in this directory with
77
+ runtime code. `json-api.ts`, `json-web-token.ts`, and `opengraph.ts` contain
78
+ compile-time structure only; they describe the shape of external formats
79
+ without implementing parsing, validation, or serialization. Each file is
80
+ documented in its own page under `shared/application/http/*.md`.
78
81
 
79
82
  #### Shared Data Files
80
83
 
@@ -34,14 +34,14 @@ library.
34
34
  flowchart TD
35
35
  types["types/"] --> typesObjects["objects.d.ts"]
36
36
  types --> json["json.d.ts"]
37
- types --> cldr["cldr.d.ts"]
38
- types --> iana["iana.d.ts"]
37
+ types --> locales["locales.d.ts"]
38
+ types --> timezones["timezones.d.ts"]
39
39
  ```
40
40
 
41
41
  `types/objects.d.ts` defines root-level types such as `Generic<T>`.
42
42
  `types/json.d.ts` defines `JsonValue` and the other plain, serializable JSON
43
- shapes. `types/cldr.d.ts` declares the `Locale` union from Unicode CLDR.
44
- `types/iana.d.ts` declares the `TimeZone` union from the IANA time zone
43
+ shapes. `types/locales.d.ts` declares the `Locale` union from Unicode CLDR.
44
+ `types/timezones.d.ts` declares the `TimeZone` union from the IANA time zone
45
45
  database.
46
46
 
47
47
  #### Shared
@@ -0,0 +1,96 @@
1
+ ### HTTP Errors
2
+
3
+ `HttpError` carries an HTTP status code alongside a matching message.
4
+ It is used when a use case or adapter needs to signal a specific HTTP outcome
5
+ without depending on a transport framework's own error type.
6
+
7
+ #### Declaration
8
+
9
+ ```ts title="shared/application/http/errors.ts"
10
+ export class HttpError extends Error {
11
+ public static readonly messages: { [code: number]: string } = Object.freeze({
12
+ 400: 'Bad Request',
13
+ 401: 'Unauthorized',
14
+ 404: 'Not Found',
15
+ 409: 'Conflict',
16
+ // ...remaining standard 4xx/5xx status codes
17
+ 500: 'Internal Server Error',
18
+ })
19
+
20
+ public readonly code: number
21
+
22
+ constructor(code: number, message?: string) {
23
+ super(message ?? HttpError.messages[code] ?? 'Unknown Error')
24
+ this.code = code
25
+ this.name = 'HttpError'
26
+ }
27
+ }
28
+ ```
29
+
30
+ `HttpError.messages` maps every standard 4xx/5xx status code registered by the
31
+ HTTP specification, from `400` to `511`, to its reason phrase.
32
+
33
+ #### Implementation Options
34
+
35
+ Constructing an `HttpError` supports three distinct outcomes, depending on
36
+ what arguments are passed.
37
+
38
+ **1. Known code, default message.** The message is looked up from
39
+ `HttpError.messages`.
40
+
41
+ ```ts
42
+ import { HttpError } from '../../shared/application/http/errors.js'
43
+
44
+ const error = new HttpError(404)
45
+
46
+ error.code // 404
47
+ error.message // 'Not Found'
48
+ ```
49
+
50
+ **2. Known code, explicit message.** The explicit message always takes
51
+ precedence over the table.
52
+
53
+ ```ts
54
+ import { HttpError } from '../../shared/application/http/errors.js'
55
+
56
+ const error = new HttpError(409, 'Email is already registered')
57
+
58
+ error.code // 409
59
+ error.message // 'Email is already registered'
60
+ ```
61
+
62
+ **3. Unrecognized code, no message.** Codes outside `HttpError.messages` fall
63
+ back to `'Unknown Error'` instead of throwing.
64
+
65
+ ```ts
66
+ import { HttpError } from '../../shared/application/http/errors.js'
67
+
68
+ const error = new HttpError(499)
69
+
70
+ error.code // 499
71
+ error.message // 'Unknown Error'
72
+ ```
73
+
74
+ #### Usage In A Handler
75
+
76
+ ```ts title="users/adapters/get-user-handler.ts"
77
+ import { HttpError } from '../../shared/application/http/errors.js'
78
+
79
+ function assertFound<T>(value: T | null): T {
80
+ if (value === null) {
81
+ throw new HttpError(404)
82
+ }
83
+
84
+ return value
85
+ }
86
+ ```
87
+
88
+ `assertFound()` throws the standard `404` message for free. The caller of the
89
+ handler decides how a thrown `HttpError` is turned into a `Response`, since
90
+ `errors.ts` only declares the error shape and not the response translation.
91
+
92
+ > **Note**
93
+ > `HttpError` only carries the status code and message. Serializing it into a
94
+ > `Response` body, including it in a JSON:API error document (see
95
+ > `shared/application/http/json-api.md`), and logging it are the
96
+ > responsibility of the concrete adapter.
@@ -0,0 +1,102 @@
1
+ ### HTTP Handlers
2
+
3
+ The handler contracts define a framework-agnostic, transport-facing boundary.
4
+ They are used when an adapter needs to describe request processing and
5
+ cross-cutting request logic in a consistent way, without depending on a
6
+ specific server framework.
7
+
8
+ The generated template relies on the standard `Request` and `Response` types
9
+ from the Fetch API, so adapters work directly with the platform's own APIs.
10
+
11
+ #### Request Handler
12
+
13
+ `HttpRequestHandler` is responsible for processing a request and returning a
14
+ response.
15
+
16
+ ```ts title="shared/application/http/handlers.ts"
17
+ export interface HttpRequestHandler {
18
+ handle(request: Request): Response | Promise<Response>
19
+ }
20
+ ```
21
+
22
+ In the following example we implement a handler using the standard `Request`
23
+ and `Response` objects.
24
+
25
+ ```ts title="users/adapters/get-user-handler.ts"
26
+ import { HttpRequestHandler } from '../../shared/application/http/handlers.js'
27
+
28
+ export class GetUserHandler implements HttpRequestHandler {
29
+ public handle(request: Request): Response {
30
+ const id = new URL(request.url).pathname.split('/').at(-1)
31
+
32
+ return Response.json({ data: { id } }, { status: 200 })
33
+ }
34
+ }
35
+ ```
36
+
37
+ Using the standard `Request` and `Response` types means the adapter relies on
38
+ the platform's own APIs, such as `request.url`, `request.headers`, and
39
+ `Response.json`. The shape of the JSON body itself, when the adapter follows
40
+ JSON:API, is described by the document types in
41
+ `shared/application/http/json-api.md`.
42
+
43
+ `handle()` can be synchronous or asynchronous; both `Response` and
44
+ `Promise<Response>` are valid return types, so an adapter that awaits a
45
+ database call satisfies the same contract as one that returns immediately.
46
+
47
+ #### Middleware
48
+
49
+ `HttpMiddleware` is responsible for running logic before or around the
50
+ handler.
51
+
52
+ ```ts title="shared/application/http/handlers.ts"
53
+ export interface HttpMiddleware {
54
+ process(
55
+ request: Request,
56
+ handler: HttpRequestHandler,
57
+ ): Response | Promise<Response>
58
+ }
59
+ ```
60
+
61
+ Now that the handler exists, middleware can wrap it.
62
+
63
+ ```ts title="users/adapters/request-logger.ts"
64
+ import {
65
+ HttpMiddleware,
66
+ HttpRequestHandler,
67
+ } from '../../shared/application/http/handlers.js'
68
+
69
+ export class RequestLoggerMiddleware implements HttpMiddleware {
70
+ public async process(
71
+ request: Request,
72
+ handler: HttpRequestHandler,
73
+ ): Promise<Response> {
74
+ void request
75
+ return handler.handle(request)
76
+ }
77
+ }
78
+ ```
79
+
80
+ This middleware passes the request through unchanged, showing where
81
+ cross-cutting behavior belongs in the generated HTTP abstraction. A
82
+ short-circuiting middleware, such as an authentication guard, follows the same
83
+ shape but returns a `Response` (for example built from
84
+ `shared/application/http/errors.md`'s `HttpError`) without calling
85
+ `handler.handle()`.
86
+
87
+ > **Note**
88
+ > The handler contracts define the minimum boundary for adapters. Routing,
89
+ > middleware chaining/composition, and status code policies beyond
90
+ > `HttpError` are the responsibility of the concrete transport.
91
+
92
+ #### Example Flow
93
+
94
+ ```mermaid
95
+ flowchart LR
96
+ request[Request] --> middleware[Middleware]
97
+ middleware --> handler[Handler]
98
+ handler --> response["Response body"]
99
+ ```
100
+
101
+ This flow keeps the transport boundary explicit while leaving framework
102
+ choices to the adapter layer.
@@ -0,0 +1,235 @@
1
+ ### JSON:API
2
+
3
+ `json-api.ts` declares a type-only implementation of the
4
+ [JSON:API v1.1](https://jsonapi.org/format/) specification, including the
5
+ [Atomic Operations extension](https://jsonapi.org/ext/atomic/). It gives an
6
+ adapter a shared vocabulary for request and response bodies without forcing a
7
+ particular server framework.
8
+
9
+ This module has no runtime code. It only describes compile-time structure;
10
+ rules that depend on runtime values, URI validity, document-wide uniqueness,
11
+ full linkage, HTTP semantics, or member-name character validation still
12
+ require explicit checks in the adapter.
13
+
14
+ #### Resource Identifiers
15
+
16
+ A resource is referenced in one of two ways, depending on whether the server
17
+ has assigned it a permanent id yet.
18
+
19
+ | Type | Option | Required members |
20
+ | --- | --- | --- |
21
+ | `JsonApiPersistedResourceIdentifier` | Server-assigned id | `type`, `id` |
22
+ | `JsonApiLocalResourceIdentifier` | Client-assigned local id, not yet persisted | `type`, `lid` |
23
+
24
+ `JsonApiResourceIdentifier` is the union of both.
25
+
26
+ ```ts
27
+ import type { JsonApiResourceIdentifier } from '../../../shared/application/http/json-api.js'
28
+
29
+ const persisted: JsonApiResourceIdentifier<'users'> = { type: 'users', id: '1' }
30
+ const local: JsonApiResourceIdentifier<'users'> = { type: 'users', lid: 'tmp-1' }
31
+ ```
32
+
33
+ Resource linkage (`JsonApiResourceLinkage`) is either a single identifier or
34
+ `null` (`JsonApiToOneLinkage`), or an array of identifiers
35
+ (`JsonApiToManyLinkage`).
36
+
37
+ #### Resource Objects
38
+
39
+ A resource object has the same persisted-versus-local split as its
40
+ identifier.
41
+
42
+ | Type | Option | `id` | `lid` |
43
+ | --- | --- | --- | --- |
44
+ | `JsonApiResourceObject` | Server-originated resource | required | optional |
45
+ | `JsonApiNewResourceObject` | Client-originated resource without a server id | never | optional |
46
+
47
+ `JsonApiCreateResourceObject` is the union accepted by create requests, since
48
+ a client is allowed to submit a client-generated id.
49
+
50
+ ```ts title="users/adapters/get-user-handler.ts"
51
+ import { HttpRequestHandler } from '../../shared/application/http/handlers.js'
52
+ import {
53
+ JsonApiSingleResourceDocument,
54
+ JsonApiResourceObject,
55
+ } from '../../shared/application/http/json-api.js'
56
+
57
+ type UserAttributes = { email: string }
58
+ type UserResource = JsonApiResourceObject<'users', UserAttributes>
59
+
60
+ export class GetUserHandler implements HttpRequestHandler {
61
+ public handle(request: Request): Response {
62
+ const id = new URL(request.url).pathname.split('/').at(-1) ?? ''
63
+
64
+ const body: JsonApiSingleResourceDocument<UserResource> = {
65
+ data: {
66
+ type: 'users',
67
+ id,
68
+ attributes: { email: 'ada@example.com' },
69
+ },
70
+ }
71
+
72
+ return Response.json(body, { status: 200 })
73
+ }
74
+ }
75
+ ```
76
+
77
+ #### Relationships
78
+
79
+ `JsonApiRelationship` requires at least one of `links`, `data`, or `meta`, per
80
+ the base specification. Two narrower aliases fix the shape of `data`:
81
+
82
+ | Type | `data` shape |
83
+ | --- | --- |
84
+ | `JsonApiToOneRelationship` | single identifier or `null` |
85
+ | `JsonApiToManyRelationship` | array of identifiers |
86
+
87
+ ```ts
88
+ import type {
89
+ JsonApiToOneRelationship,
90
+ JsonApiToManyRelationship,
91
+ } from '../../../shared/application/http/json-api.js'
92
+
93
+ const author: JsonApiToOneRelationship<{ type: 'authors' }> = {
94
+ data: { type: 'authors', id: '9' },
95
+ }
96
+
97
+ const comments: JsonApiToManyRelationship<{ type: 'comments' }> = {
98
+ data: [{ type: 'comments', id: '1' }, { type: 'comments', id: '2' }],
99
+ }
100
+ ```
101
+
102
+ #### Documents
103
+
104
+ `JsonApiDocument` is a discriminated union over which top-level member is
105
+ required. Exactly one of these three shapes is valid at a time:
106
+
107
+ | Type | Required top-level member | Use |
108
+ | --- | --- | --- |
109
+ | `JsonApiDataDocument` | `data` | success responses returning resources |
110
+ | `JsonApiErrorDocument` | `errors` (non-empty) | failure responses |
111
+ | `JsonApiMetaDocument` | `meta` | responses with no primary data, such as a heartbeat |
112
+
113
+ `JsonApiDataDocument` itself is narrowed by four ready-made aliases depending
114
+ on what `data` contains:
115
+
116
+ | Alias | `data` shape |
117
+ | --- | --- |
118
+ | `JsonApiSingleResourceDocument` | resource or `null` |
119
+ | `JsonApiResourceCollectionDocument` | array of resources |
120
+ | `JsonApiSingleIdentifierDocument` | identifier or `null` |
121
+ | `JsonApiIdentifierCollectionDocument` | array of identifiers |
122
+ | `JsonApiRelationshipDocument` | any resource linkage |
123
+
124
+ ```ts
125
+ import type {
126
+ JsonApiErrorDocument,
127
+ JsonApiMetaDocument,
128
+ } from '../../../shared/application/http/json-api.js'
129
+
130
+ const notFound: JsonApiErrorDocument = {
131
+ errors: [{ status: '404', title: 'Not Found' }],
132
+ }
133
+
134
+ const heartbeat: JsonApiMetaDocument<{ status: string }> = {
135
+ meta: { status: 'ok' },
136
+ }
137
+ ```
138
+
139
+ #### Request Documents
140
+
141
+ Three request-only document shapes fix `data` to exactly what each operation
142
+ accepts:
143
+
144
+ | Type | `data` shape | Use |
145
+ | --- | --- | --- |
146
+ | `JsonApiCreateDocument` | `JsonApiCreateResourceObject` | `POST` requests |
147
+ | `JsonApiUpdateDocument` | `JsonApiResourceObject` | `PATCH` requests |
148
+ | `JsonApiRelationshipUpdateDocument` | resource linkage | relationship endpoints |
149
+
150
+ `JsonApiQueryParameters` describes the standard `include`, `sort`, `filter`,
151
+ and `page` query members, plus the `fields[TYPE]`, `filter[FIELD]`, and
152
+ `page[FIELD]` bracketed member-name patterns.
153
+
154
+ #### Errors
155
+
156
+ `JsonApiError` requires at least one of `id`, `links`, `status`, `code`,
157
+ `title`, `detail`, `source`, or `meta`.
158
+
159
+ ```ts
160
+ import type { JsonApiError } from '../../../shared/application/http/json-api.js'
161
+
162
+ const validationError: JsonApiError = {
163
+ status: '422',
164
+ title: 'Invalid Attribute',
165
+ detail: 'email must be a valid address',
166
+ source: { pointer: '/data/attributes/email' },
167
+ }
168
+ ```
169
+
170
+ #### Atomic Operations Extension
171
+
172
+ The Atomic Operations extension adds a request document that carries one or
173
+ more operations, and a response document that carries their results. Every
174
+ operation targets either a resource or a relationship, and every operation
175
+ uses exactly one of three op codes. `JsonApiAtomicStrictOperation` is the
176
+ union of the seven concrete shapes that combination produces:
177
+
178
+ | Type | `op` | Target | `data` |
179
+ | --- | --- | --- | --- |
180
+ | `JsonApiAtomicAddResourceOperation` | `'add'` | resource (`ref` optional, `href` optional) | resource to create |
181
+ | `JsonApiAtomicUpdateResourceOperation` | `'update'` | resource | resource to replace |
182
+ | `JsonApiAtomicRemoveResourceOperation` | `'remove'` | resource (required) | none |
183
+ | `JsonApiAtomicUpdateToOneRelationshipOperation` | `'update'` | relationship (required) | identifier or `null` |
184
+ | `JsonApiAtomicAddToManyRelationshipOperation` | `'add'` | relationship (required) | identifier array |
185
+ | `JsonApiAtomicUpdateToManyRelationshipOperation` | `'update'` | relationship (required) | identifier array |
186
+ | `JsonApiAtomicRemoveFromManyRelationshipOperation` | `'remove'` | relationship (required) | identifier array |
187
+
188
+ A target is expressed through `ref` (a `JsonApiAtomicResourceRef` or
189
+ `JsonApiAtomicRelationshipRef`) or through `href`, never both at once
190
+ (`JsonApiExclusive`).
191
+
192
+ ```ts title="users/adapters/atomic-operations-handler.ts"
193
+ import {
194
+ JsonApiAtomicOperationsDocument,
195
+ JsonApiAtomicResultsDocument,
196
+ JsonApiAtomicStrictOperation,
197
+ } from '../../shared/application/http/json-api.js'
198
+
199
+ const request: JsonApiAtomicOperationsDocument<JsonApiAtomicStrictOperation> = {
200
+ 'atomic:operations': [
201
+ { op: 'add', href: '/users', data: { type: 'users', attributes: { email: 'ada@example.com' } } },
202
+ { op: 'update', ref: { type: 'users', id: '1' }, data: { type: 'users', id: '1', attributes: { email: 'ada@lovelace.dev' } } },
203
+ { op: 'remove', ref: { type: 'users', id: '2' } },
204
+ { op: 'update', ref: { type: 'users', id: '1', relationship: 'team' }, data: { type: 'teams', id: '9' } },
205
+ { op: 'add', ref: { type: 'teams', id: '9', relationship: 'members' }, data: [{ type: 'users', id: '1' }] },
206
+ ],
207
+ }
208
+
209
+ const response: JsonApiAtomicResultsDocument = {
210
+ 'atomic:results': [{ data: { type: 'users', id: '1' } }, {}, {}, {}, {}],
211
+ }
212
+ ```
213
+
214
+ `JsonApiAtomicRequestDocument` and `JsonApiAtomicResponseDocument` are the
215
+ named aliases for the request and response side of the extension; the latter
216
+ is a union of `JsonApiAtomicResultsDocument` and `JsonApiErrorDocument`, since
217
+ a batch of operations can still fail as a whole.
218
+
219
+ #### Extension And `@`-Members
220
+
221
+ `JsonApiExtensionMembers` and `JsonApiAtMembers` compose extension-namespaced
222
+ (`'ext:member'`) and meta (`'@member'`) properties through intersections, for
223
+ custom or third-party JSON:API extensions beyond Atomic Operations.
224
+
225
+ ```ts
226
+ import type { JsonApiExtensionMembers, JsonApiResourceObject } from '../../../shared/application/http/json-api.js'
227
+
228
+ type VersionedResource = JsonApiResourceObject<'users'> &
229
+ JsonApiExtensionMembers<'version', { id: string }>
230
+ ```
231
+
232
+ > **Hint**
233
+ > These declarations only provide compile-time structure. Rules that depend on
234
+ > runtime values, URI validity, document-wide uniqueness, or member-name
235
+ > character validation still require explicit checks in the adapter.