tshex-cli 1.0.25 → 1.0.26
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 +35 -5
- package/docs/library-structure.md +23 -4
- package/docs/library-types.md +45 -3
- package/docs/shared/application/http.md +216 -67
- package/docs/shared/application/loggers.md +48 -24
- package/package.json +1 -1
- package/templates/lib/shared/application/http/http.ts +76 -0
- package/templates/lib/shared/application/http/json-api.ts +611 -0
- package/templates/lib/shared/application/http/json-web-token.ts +980 -0
- package/templates/lib/shared/application/http/opengraph.ts +533 -0
- package/templates/lib/shared/application/loggers.ts +25 -0
- package/templates/lib/types/cldr.d.ts +770 -0
- package/templates/lib/types/iana.d.ts +423 -0
- package/templates/lib/types/json.d.ts +9 -0
- package/templates/lib/types/objects.d.ts +1 -0
- package/templates/lib/index.d.ts +0 -1
- package/templates/lib/shared/application/http.ts +0 -35
|
@@ -9,12 +9,26 @@ The root contains the main entry points of the generated library.
|
|
|
9
9
|
|
|
10
10
|
| File | Responsibility |
|
|
11
11
|
| --- | --- |
|
|
12
|
-
| `index.d.ts` | Declares root-level shared types such as `Generic<T>`. |
|
|
13
12
|
| `main.ts` | Starts as a placeholder for the main implementation and root exports. |
|
|
14
13
|
|
|
15
|
-
`
|
|
16
|
-
|
|
17
|
-
|
|
14
|
+
`main.ts` is the place for the main runtime entry point when the library
|
|
15
|
+
starts exposing shared runtime components.
|
|
16
|
+
|
|
17
|
+
#### Types Files
|
|
18
|
+
|
|
19
|
+
The `types/` directory contains root-level ambient type declarations.
|
|
20
|
+
|
|
21
|
+
| File | Responsibility |
|
|
22
|
+
| --- | --- |
|
|
23
|
+
| `types/objects.d.ts` | Declares root-level shared types such as `Generic<T>`. |
|
|
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. |
|
|
27
|
+
|
|
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`.
|
|
18
32
|
|
|
19
33
|
#### Shared Domain Files
|
|
20
34
|
|
|
@@ -39,13 +53,29 @@ cases and integrations.
|
|
|
39
53
|
| --- | --- |
|
|
40
54
|
| `shared/application/validations.ts` | Declares the `Validatable` contract. |
|
|
41
55
|
| `shared/application/services.ts` | Declares the `Service` base class for use cases. |
|
|
42
|
-
| `shared/application/http.ts` | Declares framework-agnostic HTTP contracts. |
|
|
43
56
|
| `shared/application/loggers.ts` | Declares shared log levels and the `Logger` contract. |
|
|
44
57
|
| `shared/application/events.ts` | Declares `Event`, `EventHandler`, and `EventDispatcher`. |
|
|
45
58
|
|
|
46
59
|
These files do not implement frameworks or transports. They define the stable
|
|
47
60
|
contracts that adapters and services can share.
|
|
48
61
|
|
|
62
|
+
#### Shared HTTP Files
|
|
63
|
+
|
|
64
|
+
The `shared/application/http` directory groups the framework-agnostic HTTP
|
|
65
|
+
boundary and the type-only specifications for common web content formats.
|
|
66
|
+
|
|
67
|
+
| File | Responsibility |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| `shared/application/http/http.ts` | Declares `HttpRequestHandler`, `HttpMiddleware`, and `HttpError`. |
|
|
70
|
+
| `shared/application/http/json-api.ts` | Type-only JSON:API v1.1 document, resource, and Atomic Operations declarations. |
|
|
71
|
+
| `shared/application/http/json-web-token.ts` | Type-only JOSE/JWT declarations (JWK, JWS, JWE, JWT claims). |
|
|
72
|
+
| `shared/application/http/opengraph.ts` | Type-only Open Graph, Twitter Card, and social metadata declarations. |
|
|
73
|
+
|
|
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.
|
|
78
|
+
|
|
49
79
|
#### Shared Data Files
|
|
50
80
|
|
|
51
81
|
The generated template also includes a small set of data-access abstractions.
|
|
@@ -15,15 +15,34 @@ The root contains the entry points of the generated library.
|
|
|
15
15
|
|
|
16
16
|
```mermaid
|
|
17
17
|
flowchart TD
|
|
18
|
-
root["Library root"] -->
|
|
18
|
+
root["Library root"] --> types["types/"]
|
|
19
19
|
root --> main["main.ts"]
|
|
20
20
|
root --> shared["shared/"]
|
|
21
21
|
root --> users["users/"]
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
-
`
|
|
25
|
-
main runtime exports. The rest of the structure lives under
|
|
26
|
-
or more context directories.
|
|
24
|
+
`types/` groups the root-level type declarations. `main.ts` starts as a
|
|
25
|
+
placeholder for main runtime exports. The rest of the structure lives under
|
|
26
|
+
`shared/` and one or more context directories.
|
|
27
|
+
|
|
28
|
+
#### Types
|
|
29
|
+
|
|
30
|
+
The `types/` directory contains ambient type declarations shared by the whole
|
|
31
|
+
library.
|
|
32
|
+
|
|
33
|
+
```mermaid
|
|
34
|
+
flowchart TD
|
|
35
|
+
types["types/"] --> typesObjects["objects.d.ts"]
|
|
36
|
+
types --> json["json.d.ts"]
|
|
37
|
+
types --> cldr["cldr.d.ts"]
|
|
38
|
+
types --> iana["iana.d.ts"]
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`types/objects.d.ts` defines root-level types such as `Generic<T>`.
|
|
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
|
|
45
|
+
database.
|
|
27
46
|
|
|
28
47
|
#### Shared
|
|
29
48
|
|
package/docs/library-types.md
CHANGED
|
@@ -7,10 +7,10 @@ data but does not need a more specific shape yet.
|
|
|
7
7
|
|
|
8
8
|
#### Root Declaration
|
|
9
9
|
|
|
10
|
-
The root declaration lives in `
|
|
10
|
+
The root declaration lives in `types/objects.d.ts`.
|
|
11
11
|
|
|
12
|
-
```ts title="
|
|
13
|
-
type Generic<T = unknown> = Record<string, T>
|
|
12
|
+
```ts title="types/objects.d.ts"
|
|
13
|
+
export type Generic<T = unknown> = Record<string, T>
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
This alias expands to `Record<string, T>`. When no type argument is provided,
|
|
@@ -21,6 +21,8 @@ the values use `unknown`.
|
|
|
21
21
|
In the following example we use `Generic<string>` for a set of plain filters.
|
|
22
22
|
|
|
23
23
|
```ts
|
|
24
|
+
import { type Generic } from './types/objects.js'
|
|
25
|
+
|
|
24
26
|
const filters: Generic<string> = {
|
|
25
27
|
status: 'active',
|
|
26
28
|
sort: 'email',
|
|
@@ -35,6 +37,8 @@ value shape for the whole object.
|
|
|
35
37
|
Now consider the same pattern without providing a type argument.
|
|
36
38
|
|
|
37
39
|
```ts
|
|
40
|
+
import { type Generic } from './types/objects.js'
|
|
41
|
+
|
|
38
42
|
const metadata: Generic = {
|
|
39
43
|
retries: 2,
|
|
40
44
|
cached: true,
|
|
@@ -64,6 +68,44 @@ generic record.
|
|
|
64
68
|
> `Generic<T>` is intentionally small. It should support loose object contracts,
|
|
65
69
|
> not replace explicit domain or application types.
|
|
66
70
|
|
|
71
|
+
#### JSON Values
|
|
72
|
+
|
|
73
|
+
The generated root also declares a small family of types that describe plain,
|
|
74
|
+
serializable JSON data. They live in `types/json.d.ts`.
|
|
75
|
+
|
|
76
|
+
```ts title="types/json.d.ts"
|
|
77
|
+
export type JsonPrimitive = string | number | boolean | null
|
|
78
|
+
|
|
79
|
+
export type JsonValue = JsonPrimitive | JsonObject | JsonArray
|
|
80
|
+
|
|
81
|
+
export type JsonArray = readonly JsonValue[]
|
|
82
|
+
|
|
83
|
+
export type JsonObject = {
|
|
84
|
+
readonly [key: string]: JsonValue
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`JsonPrimitive` covers the scalar values allowed in JSON. `JsonValue` extends
|
|
89
|
+
that with nested objects and arrays, so it recursively describes any value that
|
|
90
|
+
survives a round trip through `JSON.stringify()`/`JSON.parse()`. `JsonObject`
|
|
91
|
+
and `JsonArray` name the two composite shapes so other declarations can refer
|
|
92
|
+
to them directly instead of repeating the union.
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
import { type JsonValue } from './types/json.js'
|
|
96
|
+
|
|
97
|
+
function toLogPayload(value: JsonValue): string {
|
|
98
|
+
return JSON.stringify(value)
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Use `JsonValue` and `JsonObject` when a contract must guarantee its data is
|
|
103
|
+
plain and serializable, such as request payloads, stored metadata, or wire
|
|
104
|
+
formats. Prefer `Generic<T>` instead when the value type is not required to be
|
|
105
|
+
JSON-safe. `shared/application/http/json-api.ts` and
|
|
106
|
+
`shared/application/http/json-web-token.ts` build on these types to describe
|
|
107
|
+
JSON:API documents and JOSE/JWT structures.
|
|
108
|
+
|
|
67
109
|
#### Next Step
|
|
68
110
|
|
|
69
111
|
For the rest of the generated shared abstractions, continue with the pages in
|
|
@@ -1,94 +1,64 @@
|
|
|
1
1
|
### HTTP
|
|
2
2
|
|
|
3
|
-
The HTTP contracts define a transport-facing boundary
|
|
4
|
-
generated structure to a specific framework.
|
|
3
|
+
The HTTP contracts define a framework-agnostic, transport-facing boundary.
|
|
5
4
|
They are used when an adapter needs to describe requests, responses, handlers,
|
|
6
5
|
or middleware in a consistent way.
|
|
7
6
|
|
|
8
|
-
The generated template
|
|
9
|
-
|
|
7
|
+
The generated template relies on the standard `Request` and `Response` types
|
|
8
|
+
from the Fetch API, so adapters work directly with the platform's own APIs.
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
The concern is split across four files under `shared/application/http/`:
|
|
12
11
|
|
|
13
|
-
`
|
|
14
|
-
|
|
12
|
+
1. `http.ts` for the request/response boundary contracts and `HttpError`;
|
|
13
|
+
2. `json-api.ts` for a type-only JSON:API v1.1 specification;
|
|
14
|
+
3. `json-web-token.ts` for a type-only JOSE/JWT specification;
|
|
15
|
+
4. `opengraph.ts` for a type-only Open Graph and social metadata specification.
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
readonly errors: string[] | null
|
|
20
|
-
readonly links: Record<string, URL> | null
|
|
21
|
-
}
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
This structure makes successful data, error messages, and related links
|
|
25
|
-
explicit without forcing a specific router or server implementation.
|
|
17
|
+
Only `http.ts` contains runtime code. The other three files describe
|
|
18
|
+
compile-time structure for widely used formats so adapters do not have to
|
|
19
|
+
redefine them; they do not implement parsing, validation, or serialization.
|
|
26
20
|
|
|
27
21
|
#### Request Handler
|
|
28
22
|
|
|
29
23
|
`HttpRequestHandler` is responsible for processing a request and returning a
|
|
30
24
|
response.
|
|
31
25
|
|
|
32
|
-
```ts title="shared/application/http.ts"
|
|
26
|
+
```ts title="shared/application/http/http.ts"
|
|
33
27
|
export interface HttpRequestHandler {
|
|
34
|
-
handle(request:
|
|
28
|
+
handle(request: Request): Response | Promise<Response>
|
|
35
29
|
}
|
|
36
30
|
```
|
|
37
31
|
|
|
38
|
-
In the following example we
|
|
39
|
-
|
|
32
|
+
In the following example we implement a handler using the standard `Request`
|
|
33
|
+
and `Response` objects.
|
|
40
34
|
|
|
41
35
|
```ts title="users/adapters/get-user-handler.ts"
|
|
42
|
-
import {
|
|
43
|
-
HttpRequest,
|
|
44
|
-
HttpRequestHandler,
|
|
45
|
-
HttpResponse,
|
|
46
|
-
HttpResponseBody,
|
|
47
|
-
} from '../../shared/application/http.js'
|
|
48
|
-
|
|
49
|
-
interface UserHttpRequest extends HttpRequest {
|
|
50
|
-
readonly params: {
|
|
51
|
-
id: string
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
interface UserHttpResponse extends HttpResponse {
|
|
56
|
-
readonly status: number
|
|
57
|
-
readonly body: HttpResponseBody
|
|
58
|
-
}
|
|
36
|
+
import { HttpRequestHandler } from '../../shared/application/http/http.js'
|
|
59
37
|
|
|
60
38
|
export class GetUserHandler implements HttpRequestHandler {
|
|
61
|
-
public handle(request:
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
return {
|
|
65
|
-
status: 200,
|
|
66
|
-
body: {
|
|
67
|
-
data: {
|
|
68
|
-
id: typedRequest.params.id,
|
|
69
|
-
},
|
|
70
|
-
errors: null,
|
|
71
|
-
links: null,
|
|
72
|
-
},
|
|
73
|
-
} as UserHttpResponse
|
|
39
|
+
public handle(request: Request): Response {
|
|
40
|
+
const id = new URL(request.url).pathname.split('/').at(-1)
|
|
41
|
+
|
|
42
|
+
return Response.json({ data: { id } }, { status: 200 })
|
|
74
43
|
}
|
|
75
44
|
}
|
|
76
45
|
```
|
|
77
46
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
47
|
+
Using the standard `Request` and `Response` types means the adapter relies
|
|
48
|
+
on the platform's own APIs, such as `request.url`, `request.headers`, and
|
|
49
|
+
`Response.json`. The shape of the JSON body itself, when the adapter follows
|
|
50
|
+
JSON:API, is described by the document types in `json-api.ts`.
|
|
81
51
|
|
|
82
52
|
#### Middleware
|
|
83
53
|
|
|
84
54
|
`HttpMiddleware` is responsible for running logic before or around the handler.
|
|
85
55
|
|
|
86
|
-
```ts title="shared/application/http.ts"
|
|
56
|
+
```ts title="shared/application/http/http.ts"
|
|
87
57
|
export interface HttpMiddleware {
|
|
88
58
|
process(
|
|
89
|
-
request:
|
|
59
|
+
request: Request,
|
|
90
60
|
handler: HttpRequestHandler,
|
|
91
|
-
):
|
|
61
|
+
): Response | Promise<Response>
|
|
92
62
|
}
|
|
93
63
|
```
|
|
94
64
|
|
|
@@ -97,29 +67,69 @@ Now that the handler exists, middleware can wrap it.
|
|
|
97
67
|
```ts title="users/adapters/request-logger.ts"
|
|
98
68
|
import {
|
|
99
69
|
HttpMiddleware,
|
|
100
|
-
HttpRequest,
|
|
101
70
|
HttpRequestHandler,
|
|
102
|
-
|
|
103
|
-
} from '../../shared/application/http.js'
|
|
71
|
+
} from '../../shared/application/http/http.js'
|
|
104
72
|
|
|
105
73
|
export class RequestLoggerMiddleware implements HttpMiddleware {
|
|
106
74
|
public async process(
|
|
107
|
-
request:
|
|
75
|
+
request: Request,
|
|
108
76
|
handler: HttpRequestHandler,
|
|
109
|
-
): Promise<
|
|
77
|
+
): Promise<Response> {
|
|
110
78
|
void request
|
|
111
79
|
return handler.handle(request)
|
|
112
80
|
}
|
|
113
81
|
}
|
|
114
82
|
```
|
|
115
83
|
|
|
116
|
-
This middleware
|
|
84
|
+
This middleware passes the request through unchanged, showing where
|
|
117
85
|
cross-cutting behavior belongs in the generated HTTP abstraction.
|
|
118
86
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
87
|
+
#### HTTP Errors
|
|
88
|
+
|
|
89
|
+
`HttpError` is responsible for carrying an HTTP status code alongside a
|
|
90
|
+
matching message.
|
|
91
|
+
|
|
92
|
+
```ts title="shared/application/http/http.ts"
|
|
93
|
+
export class HttpError extends Error {
|
|
94
|
+
public static readonly messages: { [code: number]: string } = Object.freeze({
|
|
95
|
+
400: 'Bad Request',
|
|
96
|
+
404: 'Not Found',
|
|
97
|
+
409: 'Conflict',
|
|
98
|
+
// ...remaining standard 4xx/5xx status codes
|
|
99
|
+
500: 'Internal Server Error',
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
public readonly code: number
|
|
103
|
+
|
|
104
|
+
constructor(code: number, message?: string) {
|
|
105
|
+
super(message ?? HttpError.messages[code] ?? 'Unknown Error')
|
|
106
|
+
this.code = code
|
|
107
|
+
this.name = 'HttpError'
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`HttpError.messages` maps every standard 4xx/5xx status code to its reason
|
|
113
|
+
phrase. A caller can throw `new HttpError(404)` to get the standard message for
|
|
114
|
+
free, or pass an explicit `message` to override it. Codes outside the map fall
|
|
115
|
+
back to `'Unknown Error'`.
|
|
116
|
+
|
|
117
|
+
```ts title="users/adapters/get-user-handler.ts"
|
|
118
|
+
import { HttpError } from '../../shared/application/http/http.js'
|
|
119
|
+
|
|
120
|
+
function assertFound<T>(value: T | null): T {
|
|
121
|
+
if (value === null) {
|
|
122
|
+
throw new HttpError(404)
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return value
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
> **Note**
|
|
130
|
+
> The shared HTTP contracts define the minimum boundary for adapters. Routing,
|
|
131
|
+
> serialization, and status code policies beyond `HttpError` are the
|
|
132
|
+
> responsibility of the concrete transport.
|
|
123
133
|
|
|
124
134
|
#### Example Flow
|
|
125
135
|
|
|
@@ -132,3 +142,142 @@ flowchart LR
|
|
|
132
142
|
|
|
133
143
|
This flow keeps the transport boundary explicit while leaving framework choices
|
|
134
144
|
to the adapter layer.
|
|
145
|
+
|
|
146
|
+
#### JSON:API
|
|
147
|
+
|
|
148
|
+
`json-api.ts` declares a type-only implementation of the
|
|
149
|
+
[JSON:API v1.1](https://jsonapi.org/format/) specification, including the
|
|
150
|
+
[Atomic Operations extension](https://jsonapi.org/ext/atomic/). It gives an
|
|
151
|
+
adapter a shared vocabulary for request and response bodies without forcing a
|
|
152
|
+
particular server framework.
|
|
153
|
+
|
|
154
|
+
The main building blocks are:
|
|
155
|
+
|
|
156
|
+
- `JsonApiResourceObject` / `JsonApiResourceIdentifier` for resources and
|
|
157
|
+
resource linkage, generic over the resource `type`, `attributes`, and
|
|
158
|
+
`relationships`;
|
|
159
|
+
- `JsonApiRelationship`, `JsonApiToOneRelationship`, and
|
|
160
|
+
`JsonApiToManyRelationship` for relationship objects;
|
|
161
|
+
- `JsonApiError` for the top-level error object;
|
|
162
|
+
- `JsonApiDocument` (and its narrower aliases such as
|
|
163
|
+
`JsonApiSingleResourceDocument` and `JsonApiResourceCollectionDocument`) for
|
|
164
|
+
the top-level document, discriminated between a data document, an error
|
|
165
|
+
document, and a meta-only document;
|
|
166
|
+
- `JsonApiAtomicOperationsDocument` / `JsonApiAtomicResultsDocument` for the
|
|
167
|
+
Atomic Operations extension request and response bodies.
|
|
168
|
+
|
|
169
|
+
```ts title="users/adapters/get-user-handler.ts"
|
|
170
|
+
import { HttpRequestHandler } from '../../shared/application/http/http.js'
|
|
171
|
+
import {
|
|
172
|
+
JsonApiSingleResourceDocument,
|
|
173
|
+
JsonApiResourceObject,
|
|
174
|
+
} from '../../shared/application/http/json-api.js'
|
|
175
|
+
|
|
176
|
+
type UserAttributes = { email: string }
|
|
177
|
+
type UserResource = JsonApiResourceObject<'users', UserAttributes>
|
|
178
|
+
|
|
179
|
+
export class GetUserHandler implements HttpRequestHandler {
|
|
180
|
+
public handle(request: Request): Response {
|
|
181
|
+
const id = new URL(request.url).pathname.split('/').at(-1) ?? ''
|
|
182
|
+
|
|
183
|
+
const body: JsonApiSingleResourceDocument<UserResource> = {
|
|
184
|
+
data: {
|
|
185
|
+
type: 'users',
|
|
186
|
+
id,
|
|
187
|
+
attributes: { email: 'ada@example.com' },
|
|
188
|
+
},
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
return Response.json(body, { status: 200 })
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
> **Hint**
|
|
197
|
+
> These declarations only provide compile-time structure. Rules that depend on
|
|
198
|
+
> runtime values, URI validity, document-wide uniqueness, or member-name
|
|
199
|
+
> character validation still require explicit checks in the adapter.
|
|
200
|
+
|
|
201
|
+
#### JSON Web Tokens
|
|
202
|
+
|
|
203
|
+
`json-web-token.ts` declares a type-only implementation of the JOSE and JWT
|
|
204
|
+
family of RFCs (JWS, JWE, JWK, JWT, and related extensions such as DPoP and
|
|
205
|
+
selective disclosure). It lets an adapter describe tokens and keys precisely
|
|
206
|
+
without depending on a specific JOSE library's own types.
|
|
207
|
+
|
|
208
|
+
The main building blocks are:
|
|
209
|
+
|
|
210
|
+
- branded wire-format primitives such as `Base64Url`, `NumericDate`, and
|
|
211
|
+
`CompactJwt`;
|
|
212
|
+
- `JsonWebKey` / `JsonWebKeySet` for keys, covering EC, RSA, `oct`, OKP, and
|
|
213
|
+
ML-DSA (`AKP`) key types;
|
|
214
|
+
- `JwsHeader` / `JweHeader` for protected header parameters, and
|
|
215
|
+
`JwsJsonSerialization` / `JweJsonSerialization` for the JSON serializations;
|
|
216
|
+
- `JwtClaims` (built on `IanaRegisteredJwtClaims`) for decoded payloads, plus
|
|
217
|
+
ready-made profiles such as `OpenIdConnectIdTokenClaims`,
|
|
218
|
+
`OAuth2JwtAccessTokenClaims`, `DpopProofClaims`, and `SdJwtClaims`;
|
|
219
|
+
- service contracts an adapter can implement against a concrete JOSE
|
|
220
|
+
library: `JwtDecoder`, `JwsSigner`, `JwsVerifier`, `JweEncrypter`,
|
|
221
|
+
`JweDecrypter`, `JwkThumbprinter`, and `JwksResolver`, plus
|
|
222
|
+
`JwtValidationResult` for the outcome of validating a token against a
|
|
223
|
+
`JwtValidationPolicy`.
|
|
224
|
+
|
|
225
|
+
```ts title="users/adapters/verify-access-token.ts"
|
|
226
|
+
import {
|
|
227
|
+
JwsVerifier,
|
|
228
|
+
OAuth2JwtAccessTokenClaims,
|
|
229
|
+
CompactJws,
|
|
230
|
+
JsonWebKey,
|
|
231
|
+
} from '../../shared/application/http/json-web-token.js'
|
|
232
|
+
|
|
233
|
+
export function verifyAccessToken(
|
|
234
|
+
verifier: JwsVerifier,
|
|
235
|
+
token: CompactJws,
|
|
236
|
+
key: JsonWebKey,
|
|
237
|
+
) {
|
|
238
|
+
return verifier.verify<OAuth2JwtAccessTokenClaims>(token, key)
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
> **Hint**
|
|
243
|
+
> This module has no runtime implementation. Pair it with a concrete JOSE
|
|
244
|
+
> library (for signing, encryption, or verification) and use these types to
|
|
245
|
+
> annotate its inputs and outputs.
|
|
246
|
+
|
|
247
|
+
#### Open Graph
|
|
248
|
+
|
|
249
|
+
`opengraph.ts` declares a type-only implementation of the
|
|
250
|
+
[Open Graph protocol](https://ogp.me/), the Twitter Card meta tags, and
|
|
251
|
+
Facebook's compatibility extensions. It is used when an adapter needs to build
|
|
252
|
+
or read the social-sharing metadata of a page.
|
|
253
|
+
|
|
254
|
+
The main building blocks are:
|
|
255
|
+
|
|
256
|
+
- `OpenGraphMetadata`, a union of every standard Open Graph object type
|
|
257
|
+
(`OpenGraphWebsite`, `OpenGraphArticle`, `OpenGraphBook`, `OpenGraphProfile`,
|
|
258
|
+
the `music.*` and `video.*` types, `OpenGraphPaymentLink`, and
|
|
259
|
+
`OpenGraphCustomObject` for CURIE-style custom types);
|
|
260
|
+
- `OpenGraphMetaTag` and `TwitterMetaTag`, the flat `property`/`content` and
|
|
261
|
+
`name`/`content` tag representations closer to the actual `<meta>` markup;
|
|
262
|
+
- `SocialMetadataDocument`, an aggregate of Open Graph, Twitter, Facebook, and
|
|
263
|
+
standard head metadata for a single page, and `RawSocialMetadataDocument`
|
|
264
|
+
for its rendered, tag-list form.
|
|
265
|
+
|
|
266
|
+
```ts title="users/adapters/user-profile-metadata.ts"
|
|
267
|
+
import { OpenGraphProfile } from '../../shared/application/http/opengraph.js'
|
|
268
|
+
|
|
269
|
+
export function buildProfileMetadata(username: string): OpenGraphProfile {
|
|
270
|
+
return {
|
|
271
|
+
type: 'profile',
|
|
272
|
+
title: username,
|
|
273
|
+
url: `https://example.com/users/${username}`,
|
|
274
|
+
images: [{ url: `https://example.com/users/${username}/avatar.png` }],
|
|
275
|
+
username,
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
> **Hint**
|
|
281
|
+
> This module has no runtime implementation, including no HTML rendering. Use
|
|
282
|
+
> `OpenGraphMetadata` to build the data and a separate template or renderer to
|
|
283
|
+
> emit the `<meta>` tags described by `OpenGraphMetaTag`/`TwitterMetaTag`.
|
|
@@ -28,22 +28,57 @@ 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/iana.js'
|
|
32
|
+
import { type Locale } from '../../types/cldr.js'
|
|
33
|
+
|
|
31
34
|
export abstract class Logger {
|
|
32
|
-
|
|
35
|
+
[property: string]: unknown
|
|
33
36
|
|
|
34
|
-
|
|
37
|
+
public name: string = 'main'
|
|
35
38
|
|
|
36
|
-
|
|
39
|
+
public level: number = 0
|
|
37
40
|
|
|
38
|
-
|
|
41
|
+
public datetimeLocales: Locale[] = ['en-GB']
|
|
39
42
|
|
|
40
|
-
|
|
41
|
-
|
|
43
|
+
public datetimeFormatOptions: Intl.DateTimeFormatOptions & { timeZone: TimeZone } = {
|
|
44
|
+
timeZone: 'UTC',
|
|
45
|
+
year: 'numeric',
|
|
46
|
+
month: '2-digit',
|
|
47
|
+
day: '2-digit',
|
|
48
|
+
hour: '2-digit',
|
|
49
|
+
minute: '2-digit',
|
|
50
|
+
second: '2-digit',
|
|
51
|
+
fractionalSecondDigits: 3,
|
|
52
|
+
hourCycle: 'h23'
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
public abstract debug(data: unknown): void
|
|
56
|
+
|
|
57
|
+
public abstract info(data: unknown): void
|
|
58
|
+
|
|
59
|
+
public abstract warning(data: unknown): void
|
|
60
|
+
|
|
61
|
+
public abstract error(data: unknown): void
|
|
62
|
+
|
|
63
|
+
public abstract critical(data: unknown): void
|
|
64
|
+
|
|
65
|
+
protected getCurrentDatetime(): string {
|
|
66
|
+
return new Date().toLocaleString(this.datetimeLocales, this.datetimeFormatOptions)
|
|
67
|
+
}
|
|
68
|
+
} //:: class
|
|
42
69
|
```
|
|
43
70
|
|
|
44
71
|
The contract is intentionally small. It defines the actions the application can
|
|
45
72
|
request, while the adapter decides how those actions are persisted or displayed.
|
|
46
73
|
|
|
74
|
+
`name` and `level` identify the logger instance and its minimum severity, so an
|
|
75
|
+
adapter can decide which logs to emit or route. `datetimeLocales` and
|
|
76
|
+
`datetimeFormatOptions` control how `getCurrentDatetime()` formats the current
|
|
77
|
+
moment, using the `Locale` type from `types/cldr.d.ts` and the `TimeZone` type
|
|
78
|
+
from `types/iana.d.ts`. Adapters can use `getCurrentDatetime()` to timestamp
|
|
79
|
+
log entries consistently, regardless of the runtime environment's own locale
|
|
80
|
+
or timezone.
|
|
81
|
+
|
|
47
82
|
#### First Adapter
|
|
48
83
|
|
|
49
84
|
In the following example we implement a console-based logger.
|
|
@@ -51,43 +86,32 @@ In the following example we implement a console-based logger.
|
|
|
51
86
|
```ts title="users/adapters/console-logger.ts"
|
|
52
87
|
import { Logger } from '../../shared/application/loggers.js'
|
|
53
88
|
|
|
54
|
-
type ExternalService = {
|
|
55
|
-
debug(data: unknown): void
|
|
56
|
-
info(data: unknown): void
|
|
57
|
-
warning(data: unknown): void
|
|
58
|
-
error(data: unknown): void
|
|
59
|
-
critical(data: unknown): void
|
|
60
|
-
}
|
|
61
|
-
|
|
62
89
|
export class ConsoleLogger extends Logger {
|
|
63
|
-
constructor(protected readonly externalService: ExternalService) {
|
|
64
|
-
super()
|
|
65
|
-
}
|
|
66
|
-
|
|
67
90
|
public debug(data: unknown): void {
|
|
68
|
-
this.
|
|
91
|
+
console.debug(this.getCurrentDatetime(), this.name, data)
|
|
69
92
|
}
|
|
70
93
|
|
|
71
94
|
public info(data: unknown): void {
|
|
72
|
-
this.
|
|
95
|
+
console.info(this.getCurrentDatetime(), this.name, data)
|
|
73
96
|
}
|
|
74
97
|
|
|
75
98
|
public warning(data: unknown): void {
|
|
76
|
-
this.
|
|
99
|
+
console.warn(this.getCurrentDatetime(), this.name, data)
|
|
77
100
|
}
|
|
78
101
|
|
|
79
102
|
public error(data: unknown): void {
|
|
80
|
-
this.
|
|
103
|
+
console.error(this.getCurrentDatetime(), this.name, data)
|
|
81
104
|
}
|
|
82
105
|
|
|
83
106
|
public critical(data: unknown): void {
|
|
84
|
-
this.
|
|
107
|
+
console.error(this.getCurrentDatetime(), this.name, data)
|
|
85
108
|
}
|
|
86
109
|
}
|
|
87
110
|
```
|
|
88
111
|
|
|
89
112
|
This adapter satisfies the generated contract without changing the application
|
|
90
|
-
layer.
|
|
113
|
+
layer. It reuses `getCurrentDatetime()` to prefix every entry with a
|
|
114
|
+
consistently formatted timestamp.
|
|
91
115
|
|
|
92
116
|
#### Service Integration
|
|
93
117
|
|