tshex-cli 1.0.24 → 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/build/main.js +3 -3
- package/docs/context-ports.md +47 -100
- package/docs/generated-file-reference.md +41 -11
- package/docs/library-structure.md +42 -18
- package/docs/library-types.md +45 -3
- package/docs/shared/application/http.md +216 -67
- package/docs/shared/application/loggers.md +48 -24
- package/docs/shared/domain/aggregates.md +125 -25
- package/package.json +2 -1
- package/source/main.ts +9 -3
- 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
package/build/main.js
CHANGED
|
@@ -45,7 +45,7 @@ function executeCreateReactContext(templatesDir, contextDir) {
|
|
|
45
45
|
console.error(err);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
|
-
function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSourceDir) {
|
|
48
|
+
function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSourceDir, rootSourceDir = sourceDir) {
|
|
49
49
|
const entries = fs.readdirSync(sourceDir, { withFileTypes: true });
|
|
50
50
|
if (fs.existsSync(destinationDir) === false) {
|
|
51
51
|
fs.mkdirSync(destinationDir, { recursive: true });
|
|
@@ -54,7 +54,7 @@ function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSour
|
|
|
54
54
|
const sourcePath = path.join(sourceDir, entry.name);
|
|
55
55
|
const destinationPath = path.join(destinationDir, entry.name);
|
|
56
56
|
if (entry.isDirectory()) {
|
|
57
|
-
if (entry.name === 'shared') {
|
|
57
|
+
if (entry.name === 'shared' && sourceDir === rootSourceDir) {
|
|
58
58
|
continue;
|
|
59
59
|
}
|
|
60
60
|
if (ignoredSourceDir !== undefined && sourcePath.startsWith(ignoredSourceDir)) {
|
|
@@ -63,7 +63,7 @@ function executeCreateTests(sourceDir, destinationDir, fileContents, ignoredSour
|
|
|
63
63
|
if (fs.existsSync(destinationPath) && fs.statSync(destinationPath).isDirectory() === false) {
|
|
64
64
|
continue;
|
|
65
65
|
}
|
|
66
|
-
executeCreateTests(sourcePath, destinationPath, fileContents, ignoredSourceDir);
|
|
66
|
+
executeCreateTests(sourcePath, destinationPath, fileContents, ignoredSourceDir, rootSourceDir);
|
|
67
67
|
continue;
|
|
68
68
|
}
|
|
69
69
|
if (entry.isFile() && entry.name.endsWith('.ts') && fs.existsSync(destinationPath) === false) {
|
package/docs/context-ports.md
CHANGED
|
@@ -4,10 +4,10 @@ Context ports define the communication available at a context boundary.
|
|
|
4
4
|
They describe which concrete capability a context exposes, what data enters
|
|
5
5
|
that capability, and what data it returns.
|
|
6
6
|
|
|
7
|
-
In practice a port is
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
In practice a port is a specific boundary element of the system with identity:
|
|
8
|
+
a command handler, a query entry point, an event consumer, a published
|
|
9
|
+
endpoint, or another concrete interaction mechanism that exists because the
|
|
10
|
+
running system exposes it.
|
|
11
11
|
|
|
12
12
|
Types and interfaces still matter, but they are secondary. Their role is to
|
|
13
13
|
make the port explicit. The main concern is the port as a real executable
|
|
@@ -22,13 +22,19 @@ surface that another actor can call or observe.
|
|
|
22
22
|
The generated context starts with a single root file for ports.
|
|
23
23
|
|
|
24
24
|
```ts title="users/example-ports.ts"
|
|
25
|
-
export
|
|
26
|
-
|
|
25
|
+
export class ExamplePort {
|
|
26
|
+
public doSomething(): void {
|
|
27
|
+
// ...
|
|
28
|
+
}
|
|
27
29
|
}
|
|
28
30
|
```
|
|
29
31
|
|
|
30
|
-
This placeholder
|
|
31
|
-
|
|
32
|
+
This placeholder marks the context root as the place where boundary-facing
|
|
33
|
+
capabilities are declared. Replace it with the first module that defines a real
|
|
34
|
+
port of the context when that capability becomes clear.
|
|
35
|
+
|
|
36
|
+
Context root `.ts` files belong to this same boundary surface. Each one is
|
|
37
|
+
expected to be a module that defines one or more context ports.
|
|
32
38
|
|
|
33
39
|
#### First Port
|
|
34
40
|
|
|
@@ -36,113 +42,56 @@ In the following example we replace the placeholder with a concrete port for
|
|
|
36
42
|
creating a user.
|
|
37
43
|
|
|
38
44
|
```ts title="users/example-ports.ts"
|
|
39
|
-
export
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
active: boolean | null
|
|
44
|
-
}
|
|
45
|
+
export interface User {
|
|
46
|
+
id: string
|
|
47
|
+
email: string
|
|
48
|
+
active: boolean | null
|
|
45
49
|
}
|
|
46
50
|
|
|
47
|
-
export
|
|
48
|
-
user: {
|
|
49
|
-
|
|
50
|
-
email: string
|
|
51
|
-
active: boolean | null
|
|
51
|
+
export class UsersRegistry {
|
|
52
|
+
public async createUser(user: User): Promise<User> {
|
|
53
|
+
// ...
|
|
52
54
|
}
|
|
53
55
|
}
|
|
54
|
-
|
|
55
|
-
export interface CreateUserPort {
|
|
56
|
-
create(request: CreateUserRequest): Promise<CreateUserResponse>
|
|
57
|
-
}
|
|
58
56
|
```
|
|
59
57
|
|
|
60
|
-
`
|
|
61
|
-
the
|
|
62
|
-
|
|
58
|
+
`User` expresses the user data that crosses the boundary. `UsersRegistry` is a
|
|
59
|
+
boundary object of the context and `createUser()` is one concrete capability
|
|
60
|
+
that it exposes.
|
|
63
61
|
|
|
64
|
-
This definition
|
|
65
|
-
|
|
66
|
-
|
|
62
|
+
This definition focuses on the interaction the context makes available. The
|
|
63
|
+
port keeps its identity as one boundary capability while making its data and
|
|
64
|
+
action explicit.
|
|
67
65
|
|
|
68
|
-
|
|
66
|
+
The port is the executable object that this context exposes. Types help
|
|
67
|
+
describe it and make its boundary explicit.
|
|
69
68
|
|
|
70
|
-
|
|
71
|
-
delegate the work to an application service.
|
|
72
|
-
|
|
73
|
-
```ts title="users/adapters/create-user.ts"
|
|
74
|
-
import type {
|
|
75
|
-
CreateUserPort,
|
|
76
|
-
CreateUserRequest,
|
|
77
|
-
CreateUserResponse,
|
|
78
|
-
} from '../example-ports.js'
|
|
79
|
-
|
|
80
|
-
type CreateUserService = {
|
|
81
|
-
execute(data: {
|
|
82
|
-
id: string
|
|
83
|
-
email: string
|
|
84
|
-
active: boolean | null
|
|
85
|
-
}): Promise<CreateUserResponse>
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export class CreateUserAdapter implements CreateUserPort {
|
|
89
|
-
public constructor(
|
|
90
|
-
private readonly service: CreateUserService,
|
|
91
|
-
) {}
|
|
92
|
-
|
|
93
|
-
public async create(
|
|
94
|
-
request: CreateUserRequest,
|
|
95
|
-
): Promise<CreateUserResponse> {
|
|
96
|
-
return this.service.execute({
|
|
97
|
-
id: request.user.id,
|
|
98
|
-
email: request.user.email,
|
|
99
|
-
active: request.user.active,
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
```
|
|
104
|
-
|
|
105
|
-
The adapter implements `CreateUserPort`, so it materializes the boundary
|
|
106
|
-
capability and provides `create()`. Inside that method it translates the
|
|
107
|
-
root-level request into the input expected by the application service.
|
|
108
|
-
|
|
109
|
-
This is the normal flow of the generated structure:
|
|
69
|
+
This is the normal flow inside the context boundary:
|
|
110
70
|
|
|
111
71
|
```mermaid
|
|
112
72
|
flowchart LR
|
|
113
|
-
|
|
114
|
-
adapter --> port[Port]
|
|
73
|
+
caller["Caller"] --> port[Concrete port]
|
|
115
74
|
port --> application[Application]
|
|
116
75
|
application --> domain[Domain]
|
|
117
76
|
```
|
|
118
77
|
|
|
119
|
-
The port belongs to the boundary because it is part of what the context
|
|
120
|
-
exposes. The
|
|
121
|
-
|
|
78
|
+
The port belongs to the boundary because it is part of what the context
|
|
79
|
+
exposes. The application process executes the use case behind the exposed
|
|
80
|
+
capability and uses domain capabilities.
|
|
122
81
|
|
|
123
82
|
#### Multiple Port Files
|
|
124
83
|
|
|
125
|
-
As the context grows, you can keep several
|
|
126
|
-
|
|
127
|
-
```ts title="users/list-users.ts"
|
|
128
|
-
export type ListUsersRequest = {
|
|
129
|
-
active: boolean | null
|
|
130
|
-
}
|
|
84
|
+
As the context grows, you can keep several port modules at the context root.
|
|
131
85
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
}>
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
export interface ListUsersPort {
|
|
141
|
-
list(request: ListUsersRequest): Promise<ListUsersResponse>
|
|
86
|
+
```ts title="users/registry.ts"
|
|
87
|
+
export class UsersRegistry {
|
|
88
|
+
public async listUsers(): Promise<User[]> {
|
|
89
|
+
// ...
|
|
90
|
+
}
|
|
142
91
|
}
|
|
143
92
|
```
|
|
144
93
|
|
|
145
|
-
|
|
94
|
+
Another caller can then import the port from the file that owns it.
|
|
146
95
|
|
|
147
96
|
This arrangement is useful when one context exposes several independent
|
|
148
97
|
capabilities. A single file works well for a small context. Separate files
|
|
@@ -150,9 +99,9 @@ become easier to maintain when each port has its own identity and
|
|
|
150
99
|
responsibility.
|
|
151
100
|
|
|
152
101
|
> **Warning**
|
|
153
|
-
> A port should define an exposed boundary capability
|
|
154
|
-
>
|
|
155
|
-
>
|
|
102
|
+
> A port should define an exposed boundary capability.
|
|
103
|
+
> Keep business rules, repository logic, and infrastructure details in their
|
|
104
|
+
> corresponding layers.
|
|
156
105
|
|
|
157
106
|
#### Example Layout
|
|
158
107
|
|
|
@@ -161,16 +110,14 @@ in the generated folders.
|
|
|
161
110
|
|
|
162
111
|
```mermaid
|
|
163
112
|
flowchart TD
|
|
164
|
-
users["users/"] -->
|
|
165
|
-
users --> listUsers["list-users.ts"]
|
|
166
|
-
users --> adapters["adapters/"]
|
|
113
|
+
users["users/"] --> registry["registry.ts"]
|
|
167
114
|
users --> application["application/"]
|
|
168
115
|
users --> domain["domain/"]
|
|
169
116
|
```
|
|
170
117
|
|
|
171
118
|
This layout keeps the context boundary visible from the top level. It also
|
|
172
|
-
|
|
173
|
-
|
|
119
|
+
makes each exposed capability easy to locate because the port modules stay at
|
|
120
|
+
the root of the context.
|
|
174
121
|
|
|
175
122
|
#### Next Step
|
|
176
123
|
|
|
@@ -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.
|
|
@@ -61,14 +91,14 @@ application coordinates data access without forcing a specific driver.
|
|
|
61
91
|
|
|
62
92
|
#### Context Files
|
|
63
93
|
|
|
64
|
-
Each generated context starts with
|
|
94
|
+
Each generated context starts with an example port file and three directories.
|
|
65
95
|
|
|
66
96
|
| Path | Responsibility |
|
|
67
97
|
| --- | --- |
|
|
68
|
-
| `users/example-ports.ts` |
|
|
98
|
+
| `users/example-ports.ts` | Example module for context ports. All context root `.ts` files are expected to define context ports. |
|
|
69
99
|
| `users/domain/` | Domain capabilities and rules for the context. |
|
|
70
100
|
| `users/application/` | Processes that use domain capabilities to fulfill system purposes. |
|
|
71
|
-
| `users/adapters/` | Integrations that
|
|
101
|
+
| `users/adapters/` | Integrations that wraps third-party libraries or context ports. |
|
|
72
102
|
|
|
73
103
|
The `users/` path is an example context name. Your project can generate one or
|
|
74
104
|
more contexts with the same internal layout.
|
|
@@ -81,8 +111,8 @@ Use the following sequence when deciding where new code belongs.
|
|
|
81
111
|
2. Put reusable application contracts in `shared/application`.
|
|
82
112
|
3. Put context-specific rules in `<context>/domain`.
|
|
83
113
|
4. Put use cases in `<context>/application`.
|
|
84
|
-
5. Put
|
|
85
|
-
6. Put boundary
|
|
114
|
+
5. Put transport and infrastructure integrations in `<context>/adapters`.
|
|
115
|
+
6. Put concrete boundary ports in context root `.ts` files.
|
|
86
116
|
|
|
87
117
|
This reference explains placement. The architectural rationale is described in
|
|
88
|
-
`library-structure.md`.
|
|
118
|
+
`library-structure.md`.
|
|
@@ -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
|
|
|
@@ -72,9 +91,14 @@ flowchart TD
|
|
|
72
91
|
users --> domain["domain/"]
|
|
73
92
|
```
|
|
74
93
|
|
|
75
|
-
`example-ports.ts` is the root communication surface of
|
|
94
|
+
`example-ports.ts` is an example module in the root communication surface of
|
|
95
|
+
the context.
|
|
76
96
|
`domain/` contains capabilities and rules. `application/` contains processes.
|
|
77
|
-
`adapters/` contains
|
|
97
|
+
`adapters/` contains integrations that wrap third-party libraries or context
|
|
98
|
+
ports.
|
|
99
|
+
|
|
100
|
+
Context root `.ts` files belong to the boundary surface of the context. Each
|
|
101
|
+
one is expected to be a module that defines one or more context ports.
|
|
78
102
|
|
|
79
103
|
#### Domain
|
|
80
104
|
|
|
@@ -105,21 +129,23 @@ business rules themselves.
|
|
|
105
129
|
The adapters layer contains the integrations that connect a context to other
|
|
106
130
|
systems.
|
|
107
131
|
|
|
108
|
-
An adapter
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
An adapter wraps a third-party library or a context port so that the context
|
|
133
|
+
can interact with a concrete transport or infrastructure path. An adapter can
|
|
134
|
+
expose an HTTP handler, consume a message, call a remote API, implement a data
|
|
135
|
+
driver, or connect to an event bus.
|
|
111
136
|
|
|
112
137
|
#### Ports
|
|
113
138
|
|
|
114
139
|
Ports define the communication available at the context boundary.
|
|
115
140
|
|
|
116
141
|
They live at the context root because they describe how the context is used
|
|
117
|
-
from the outside.
|
|
118
|
-
work
|
|
142
|
+
from the outside. A port is the concrete object the context exposes. Adapters
|
|
143
|
+
or other callers can use that port object and route work into an application
|
|
144
|
+
process.
|
|
119
145
|
|
|
120
|
-
The generated template starts with `example-ports.ts
|
|
121
|
-
|
|
122
|
-
`context-ports.md`.
|
|
146
|
+
The generated template starts with `example-ports.ts`. As the context grows,
|
|
147
|
+
additional context root `.ts` modules can define more ports. The detailed
|
|
148
|
+
guidance for that layout lives in `context-ports.md`.
|
|
123
149
|
|
|
124
150
|
#### Dependency Direction
|
|
125
151
|
|
|
@@ -130,15 +156,13 @@ flowchart LR
|
|
|
130
156
|
adapter[Adapter] --> thirdParty["Third-party library"]
|
|
131
157
|
adapter --> port[Port]
|
|
132
158
|
port --> application[Application]
|
|
133
|
-
port --> domain[Domain]
|
|
134
159
|
application --> domain
|
|
135
160
|
```
|
|
136
161
|
|
|
137
162
|
This direction keeps the core model isolated from transport and infrastructure
|
|
138
163
|
details. Adapters integrate with external libraries and context ports. Ports
|
|
139
|
-
connect the context boundary to application processes
|
|
140
|
-
|
|
141
|
-
is, the less it should know about the outside.
|
|
164
|
+
connect the context boundary to application processes. The deeper a layer is,
|
|
165
|
+
the less it should know about the outside.
|
|
142
166
|
|
|
143
167
|
> **Warning**
|
|
144
168
|
> Avoid importing adapter-specific concerns into the domain layer. Once a domain
|
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
|