@tstdl/base 0.93.139 → 0.93.141
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +166 -0
- package/ai/genkit/multi-region.plugin.js +5 -3
- package/ai/genkit/tests/multi-region.test.d.ts +1 -0
- package/ai/genkit/tests/multi-region.test.js +5 -2
- package/ai/parser/parser.js +2 -2
- package/ai/prompts/build.js +1 -0
- package/ai/prompts/instructions-formatter.d.ts +15 -2
- package/ai/prompts/instructions-formatter.js +36 -31
- package/ai/prompts/prompt-builder.js +5 -5
- package/ai/prompts/steering.d.ts +3 -2
- package/ai/prompts/steering.js +3 -1
- package/ai/tests/instructions-formatter.test.js +1 -0
- package/api/README.md +403 -0
- package/api/client/client.js +7 -13
- package/api/client/tests/api-client.test.js +10 -10
- package/api/default-error-handlers.js +1 -1
- package/api/response.d.ts +2 -2
- package/api/response.js +22 -33
- package/api/server/api-controller.d.ts +1 -1
- package/api/server/api-controller.js +3 -3
- package/api/server/api-request-token.provider.d.ts +1 -0
- package/api/server/api-request-token.provider.js +1 -0
- package/api/server/middlewares/allowed-methods.middleware.js +2 -1
- package/api/server/middlewares/content-type.middleware.js +2 -1
- package/api/types.d.ts +3 -2
- package/application/README.md +240 -0
- package/application/application.d.ts +1 -1
- package/application/application.js +3 -3
- package/application/providers.d.ts +20 -2
- package/application/providers.js +34 -7
- package/audit/README.md +267 -0
- package/audit/module.d.ts +5 -0
- package/audit/module.js +9 -1
- package/authentication/README.md +288 -0
- package/authentication/client/authentication.service.d.ts +12 -11
- package/authentication/client/authentication.service.js +21 -21
- package/authentication/client/http-client.middleware.js +2 -2
- package/authentication/server/module.d.ts +5 -0
- package/authentication/server/module.js +9 -1
- package/authentication/tests/authentication.api-controller.test.js +1 -1
- package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
- package/authentication/tests/authentication.client-error-handling.test.js +2 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
- package/authentication/tests/authentication.client-service.test.js +1 -1
- package/browser/README.md +401 -0
- package/cancellation/README.md +156 -0
- package/cancellation/tests/coverage.test.d.ts +1 -0
- package/cancellation/tests/coverage.test.js +49 -0
- package/cancellation/tests/leak.test.js +24 -29
- package/cancellation/tests/token.test.d.ts +1 -0
- package/cancellation/tests/token.test.js +136 -0
- package/cancellation/token.d.ts +53 -177
- package/cancellation/token.js +132 -208
- package/circuit-breaker/postgres/module.d.ts +1 -0
- package/circuit-breaker/postgres/module.js +5 -1
- package/context/README.md +174 -0
- package/cookie/README.md +161 -0
- package/css/README.md +157 -0
- package/data-structures/README.md +320 -0
- package/decorators/README.md +140 -0
- package/distributed-loop/README.md +231 -0
- package/distributed-loop/distributed-loop.js +1 -1
- package/document-management/README.md +403 -0
- package/document-management/server/configure.js +5 -1
- package/document-management/server/module.d.ts +1 -1
- package/document-management/server/module.js +1 -1
- package/document-management/server/services/document-management-ancillary.service.js +1 -1
- package/document-management/server/services/document-management.service.js +9 -7
- package/document-management/tests/ai-config-hierarchy.test.js +0 -5
- package/document-management/tests/document-management-ai-overrides.test.js +0 -1
- package/document-management/tests/document-management-core.test.js +2 -7
- package/document-management/tests/document-management.api.test.js +6 -7
- package/document-management/tests/document-statistics.service.test.js +11 -12
- package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
- package/document-management/tests/document.service.test.js +3 -3
- package/document-management/tests/enum-helpers.test.js +2 -3
- package/dom/README.md +213 -0
- package/enumerable/README.md +259 -0
- package/enumeration/README.md +121 -0
- package/errors/README.md +267 -0
- package/examples/document-management/main.d.ts +1 -0
- package/examples/document-management/main.js +14 -11
- package/file/README.md +191 -0
- package/formats/README.md +210 -0
- package/function/README.md +144 -0
- package/http/README.md +318 -0
- package/http/client/adapters/undici.adapter.js +1 -1
- package/http/client/http-client-request.d.ts +6 -5
- package/http/client/http-client-request.js +8 -9
- package/http/server/node/node-http-server.js +1 -2
- package/image-service/README.md +137 -0
- package/injector/README.md +491 -0
- package/intl/README.md +113 -0
- package/json-path/README.md +182 -0
- package/jsx/README.md +154 -0
- package/key-value-store/README.md +191 -0
- package/key-value-store/postgres/module.d.ts +1 -0
- package/key-value-store/postgres/module.js +5 -1
- package/lock/README.md +249 -0
- package/lock/postgres/module.d.ts +1 -0
- package/lock/postgres/module.js +5 -1
- package/lock/web/web-lock.js +119 -47
- package/logger/README.md +287 -0
- package/mail/README.md +256 -0
- package/mail/module.d.ts +5 -1
- package/mail/module.js +11 -6
- package/memory/README.md +144 -0
- package/message-bus/README.md +244 -0
- package/message-bus/message-bus-base.js +1 -1
- package/module/README.md +182 -0
- package/module/module.d.ts +1 -1
- package/module/module.js +77 -17
- package/module/modules/web-server.module.js +3 -4
- package/notification/server/module.d.ts +1 -0
- package/notification/server/module.js +5 -1
- package/notification/tests/notification-flow.test.js +2 -2
- package/notification/tests/notification-type.service.test.js +24 -15
- package/object-storage/README.md +300 -0
- package/openid-connect/README.md +274 -0
- package/orm/README.md +423 -0
- package/orm/decorators.d.ts +5 -1
- package/orm/decorators.js +1 -1
- package/orm/server/drizzle/schema-converter.js +17 -30
- package/orm/server/encryption.d.ts +0 -1
- package/orm/server/encryption.js +1 -4
- package/orm/server/index.d.ts +1 -6
- package/orm/server/index.js +1 -6
- package/orm/server/migration.d.ts +19 -0
- package/orm/server/migration.js +72 -0
- package/orm/server/repository.d.ts +1 -1
- package/orm/server/transaction.d.ts +5 -10
- package/orm/server/transaction.js +22 -26
- package/orm/server/transactional.js +3 -3
- package/orm/tests/database-migration.test.d.ts +1 -0
- package/orm/tests/database-migration.test.js +82 -0
- package/orm/tests/encryption.test.js +3 -4
- package/orm/utils.d.ts +17 -2
- package/orm/utils.js +49 -1
- package/package.json +9 -6
- package/password/README.md +164 -0
- package/pdf/README.md +246 -0
- package/polyfills.js +1 -0
- package/pool/README.md +198 -0
- package/process/README.md +237 -0
- package/promise/README.md +252 -0
- package/promise/cancelable-promise.js +1 -1
- package/random/README.md +193 -0
- package/rate-limit/postgres/module.d.ts +1 -0
- package/rate-limit/postgres/module.js +5 -1
- package/reflection/README.md +305 -0
- package/reflection/decorator-data.js +11 -12
- package/rpc/README.md +386 -0
- package/rxjs-utils/README.md +262 -0
- package/schema/README.md +342 -0
- package/serializer/README.md +342 -0
- package/signals/implementation/README.md +134 -0
- package/sse/README.md +278 -0
- package/task-queue/README.md +293 -0
- package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
- package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
- package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
- package/task-queue/postgres/module.d.ts +1 -0
- package/task-queue/postgres/module.js +5 -1
- package/task-queue/postgres/schemas.d.ts +9 -6
- package/task-queue/postgres/schemas.js +4 -3
- package/task-queue/postgres/task-queue.d.ts +4 -13
- package/task-queue/postgres/task-queue.js +462 -355
- package/task-queue/postgres/task.model.d.ts +12 -5
- package/task-queue/postgres/task.model.js +51 -25
- package/task-queue/task-context.d.ts +2 -2
- package/task-queue/task-context.js +8 -8
- package/task-queue/task-queue.d.ts +53 -19
- package/task-queue/task-queue.js +121 -55
- package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
- package/task-queue/tests/cascading-cancellations.test.js +38 -0
- package/task-queue/tests/complex.test.js +45 -229
- package/task-queue/tests/coverage-branch.test.d.ts +1 -0
- package/task-queue/tests/coverage-branch.test.js +407 -0
- package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
- package/task-queue/tests/coverage-enhancement.test.js +144 -0
- package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
- package/task-queue/tests/dag-dependencies.test.js +41 -0
- package/task-queue/tests/dependencies.test.js +28 -26
- package/task-queue/tests/extensive-dependencies.test.js +64 -139
- package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
- package/task-queue/tests/fan-out-spawning.test.js +53 -0
- package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
- package/task-queue/tests/idempotent-replacement.test.js +61 -0
- package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
- package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
- package/task-queue/tests/queue.test.js +128 -8
- package/task-queue/tests/worker.test.js +39 -16
- package/task-queue/tests/zombie-parent.test.d.ts +1 -0
- package/task-queue/tests/zombie-parent.test.js +45 -0
- package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
- package/task-queue/tests/zombie-recovery.test.js +51 -0
- package/templates/README.md +287 -0
- package/test5.js +5 -5
- package/testing/README.md +157 -0
- package/testing/integration-setup.d.ts +4 -4
- package/testing/integration-setup.js +54 -29
- package/text/README.md +346 -0
- package/text/localization.service.js +2 -2
- package/threading/README.md +238 -0
- package/types/README.md +311 -0
- package/utils/README.md +322 -0
- package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
- package/utils/async-iterable-helpers/observable-iterable.js +4 -8
- package/utils/async-iterable-helpers/take-until.js +4 -4
- package/utils/backoff.js +89 -30
- package/utils/file-reader.js +1 -2
- package/utils/retry-with-backoff.js +1 -1
- package/utils/timer.d.ts +1 -1
- package/utils/timer.js +5 -7
- package/utils/timing.d.ts +1 -1
- package/utils/timing.js +2 -4
- package/utils/z-base32.d.ts +1 -0
- package/utils/z-base32.js +1 -0
package/schema/README.md
ADDED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# @tstdl/base/schema
|
|
2
|
+
|
|
3
|
+
A powerful, TypeScript-first schema declaration and validation library. Define data structures once using builder functions or class decorators and get static types, runtime validation, and OpenAPI specifications for free.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [Builder Functions](#builder-functions)
|
|
10
|
+
- [Class Decorators](#class-decorators)
|
|
11
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
12
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
13
|
+
- [Class-based Schemas](#class-based-schemas)
|
|
14
|
+
- [Type Coercion](#type-coercion)
|
|
15
|
+
- [Object Utilities](#object-utilities)
|
|
16
|
+
- [Recursive Schemas](#recursive-schemas)
|
|
17
|
+
- [OpenAPI Generation](#openapi-generation)
|
|
18
|
+
- [Function & Method Schemas](#function--method-schemas)
|
|
19
|
+
- [📚 API](#-api)
|
|
20
|
+
|
|
21
|
+
## ✨ Features
|
|
22
|
+
|
|
23
|
+
- **Type Safety**: Automatically infers TypeScript types directly from your schemas.
|
|
24
|
+
- **Dual API**: Define schemas using functional builders or class decorators.
|
|
25
|
+
- **Runtime Validation**: Robust validation for API payloads, configuration, and user input.
|
|
26
|
+
- **Type Coercion**: Automatically convert input data (e.g., strings to numbers) during parsing.
|
|
27
|
+
- **Detailed Error Reporting**: JSON-path based error reporting to pinpoint exactly where validation failed.
|
|
28
|
+
- **OpenAPI Support**: Generate OpenAPI v3 schema definitions from your code.
|
|
29
|
+
- **Reflection**: Leverages TypeScript metadata to infer schemas from class properties.
|
|
30
|
+
- **Extensible**: Supports custom transformations and complex types like `Uint8Array` and `ReadableStream`.
|
|
31
|
+
|
|
32
|
+
## Core Concepts
|
|
33
|
+
|
|
34
|
+
The library provides two primary ways to define schemas. Both produce a `Schema` object that can be used for validation.
|
|
35
|
+
|
|
36
|
+
### Builder Functions
|
|
37
|
+
|
|
38
|
+
Ideal for defining data shapes on the fly, for API contracts, or when you don't need a class instance.
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { object, string, number } from '@tstdl/base/schema';
|
|
42
|
+
|
|
43
|
+
const userSchema = object({
|
|
44
|
+
name: string(),
|
|
45
|
+
age: number(),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Class Decorators
|
|
50
|
+
|
|
51
|
+
Ideal for domain models where you want the class to serve as both the type definition and the validation schema. This requires `experimentalDecorators` and `emitDecoratorMetadata` in your `tsconfig.json`.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { Class, StringProperty, NumberProperty } from '@tstdl/base/schema';
|
|
55
|
+
|
|
56
|
+
@Class()
|
|
57
|
+
class User {
|
|
58
|
+
@StringProperty()
|
|
59
|
+
name: string;
|
|
60
|
+
|
|
61
|
+
@NumberProperty()
|
|
62
|
+
age: number;
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 🚀 Basic Usage
|
|
67
|
+
|
|
68
|
+
The `Schema` class provides static methods to validate and parse data.
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import { Schema, object, string, number, array, optional, SchemaError } from '@tstdl/base/schema';
|
|
72
|
+
|
|
73
|
+
// 1. Define a schema
|
|
74
|
+
const productSchema = object({
|
|
75
|
+
id: string(),
|
|
76
|
+
name: string(),
|
|
77
|
+
price: number({ minimum: 0 }),
|
|
78
|
+
tags: array(string()),
|
|
79
|
+
description: optional(string()),
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// 2. Infer the TypeScript type (optional, but useful)
|
|
83
|
+
type Product = Schema.Output<typeof productSchema>;
|
|
84
|
+
|
|
85
|
+
// 3. Validate data
|
|
86
|
+
const rawData = {
|
|
87
|
+
id: 'prod_123',
|
|
88
|
+
name: 'Super Widget',
|
|
89
|
+
price: 29.99,
|
|
90
|
+
tags: ['gadget', 'new'],
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// .parse() returns the typed value if valid, or throws SchemaError
|
|
95
|
+
const product = Schema.parse(productSchema, rawData);
|
|
96
|
+
console.log(`Validated product: ${product.name}`);
|
|
97
|
+
} catch (error) {
|
|
98
|
+
if (error instanceof SchemaError) {
|
|
99
|
+
console.error('Validation failed:', error.message);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// .validate() returns a boolean
|
|
104
|
+
const isValid = Schema.validate(productSchema, { id: 123 }); // false
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## 🔧 Advanced Topics
|
|
108
|
+
|
|
109
|
+
### Class-based Schemas
|
|
110
|
+
|
|
111
|
+
You can use decorators to define schemas directly on your classes. The class constructor itself becomes a valid schema object.
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
import { Schema, Class, StringProperty, NumberProperty, Array, Integer } from '@tstdl/base/schema';
|
|
115
|
+
|
|
116
|
+
@Class()
|
|
117
|
+
class User {
|
|
118
|
+
@StringProperty()
|
|
119
|
+
id: string;
|
|
120
|
+
|
|
121
|
+
@StringProperty()
|
|
122
|
+
username: string;
|
|
123
|
+
|
|
124
|
+
@Integer({ minimum: 18 })
|
|
125
|
+
age: number;
|
|
126
|
+
|
|
127
|
+
@Array(String)
|
|
128
|
+
roles: string[];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const input = {
|
|
132
|
+
id: 'u1',
|
|
133
|
+
username: 'admin',
|
|
134
|
+
age: 25,
|
|
135
|
+
roles: ['admin', 'editor'],
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
// Pass the Class constructor to Schema methods
|
|
139
|
+
const user = Schema.parse(User, input);
|
|
140
|
+
|
|
141
|
+
console.log(user instanceof User); // true
|
|
142
|
+
console.log(user.username); // 'admin'
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Type Coercion
|
|
146
|
+
|
|
147
|
+
When handling data from sources like query strings or form data, types are often strings. Enable `coerce` to automatically convert them.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { Schema, object, number, boolean, date } from '@tstdl/base/schema';
|
|
151
|
+
|
|
152
|
+
const querySchema = object({
|
|
153
|
+
page: number({ integer: true }),
|
|
154
|
+
active: boolean(),
|
|
155
|
+
from: date(),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const rawQuery = {
|
|
159
|
+
page: '5',
|
|
160
|
+
active: 'true',
|
|
161
|
+
from: '2023-10-27',
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
// Enable coercion in the options
|
|
165
|
+
const parsed = Schema.parse(querySchema, rawQuery, { coerce: true });
|
|
166
|
+
|
|
167
|
+
console.log(typeof parsed.page); // 'number'
|
|
168
|
+
console.log(typeof parsed.active); // 'boolean'
|
|
169
|
+
console.log(parsed.from instanceof Date); // true
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Object Utilities
|
|
173
|
+
|
|
174
|
+
Compose new schemas from existing ones using utilities similar to TypeScript's utility types.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
import { object, string, number, pick, omit, partial, assign } from '@tstdl/base/schema';
|
|
178
|
+
|
|
179
|
+
const baseUser = object({
|
|
180
|
+
id: string(),
|
|
181
|
+
name: string(),
|
|
182
|
+
email: string(),
|
|
183
|
+
age: number(),
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// Pick specific fields
|
|
187
|
+
const userSummary = pick(baseUser, ['id', 'name']);
|
|
188
|
+
|
|
189
|
+
// Omit sensitive fields
|
|
190
|
+
const publicUser = omit(baseUser, ['email']);
|
|
191
|
+
|
|
192
|
+
// Make all fields optional (e.g., for patch updates)
|
|
193
|
+
const patchUser = partial(baseUser);
|
|
194
|
+
|
|
195
|
+
// Merge schemas
|
|
196
|
+
const timestampedUser = assign(
|
|
197
|
+
baseUser,
|
|
198
|
+
object({
|
|
199
|
+
createdAt: number(),
|
|
200
|
+
updatedAt: number(),
|
|
201
|
+
}),
|
|
202
|
+
);
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Recursive Schemas
|
|
206
|
+
|
|
207
|
+
Use `deferred` to define schemas that reference themselves, useful for tree structures.
|
|
208
|
+
|
|
209
|
+
```typescript
|
|
210
|
+
import { Schema, object, string, array, optional, deferred } from '@tstdl/base/schema';
|
|
211
|
+
|
|
212
|
+
type Category = {
|
|
213
|
+
name: string;
|
|
214
|
+
children?: Category[];
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const categorySchema = object({
|
|
218
|
+
name: string(),
|
|
219
|
+
children: optional(array(deferred(() => categorySchema))),
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
const tree = {
|
|
223
|
+
name: 'Root',
|
|
224
|
+
children: [{ name: 'Child A' }, { name: 'Child B', children: [{ name: 'Grandchild B.1' }] }],
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
const validTree = Schema.parse(categorySchema, tree);
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
### OpenAPI Generation
|
|
231
|
+
|
|
232
|
+
Convert your schemas into OpenAPI v3 compatible JSON schema objects. This is perfect for generating API documentation automatically.
|
|
233
|
+
|
|
234
|
+
```typescript
|
|
235
|
+
import { object, string, integer, enumeration } from '@tstdl/base/schema';
|
|
236
|
+
import { convertToOpenApiSchema } from '@tstdl/base/schema/converters';
|
|
237
|
+
|
|
238
|
+
enum Status {
|
|
239
|
+
Active = 'active',
|
|
240
|
+
Inactive = 'inactive',
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const apiResponseSchema = object({
|
|
244
|
+
id: string({ description: 'Unique identifier' }),
|
|
245
|
+
count: integer({ minimum: 0 }),
|
|
246
|
+
status: enumeration(Status),
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const openApiSpec = convertToOpenApiSchema(apiResponseSchema);
|
|
250
|
+
console.log(JSON.stringify(openApiSpec, null, 2));
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### Function & Method Schemas
|
|
254
|
+
|
|
255
|
+
You can define schemas for functions and class methods, validating both arguments and return values.
|
|
256
|
+
|
|
257
|
+
```typescript
|
|
258
|
+
import { Method, StringProperty, Integer } from '@tstdl/base/schema';
|
|
259
|
+
|
|
260
|
+
class Calculator {
|
|
261
|
+
@Method([Integer(), Integer()], Integer())
|
|
262
|
+
add(a: number, b: number): number {
|
|
263
|
+
return a + b;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## 📚 API
|
|
269
|
+
|
|
270
|
+
### Static Methods
|
|
271
|
+
|
|
272
|
+
| Method | Description |
|
|
273
|
+
| :----------------------------------------- | :---------------------------------------------------------------------------------- |
|
|
274
|
+
| `Schema.parse(schema, value, options?)` | Validates and returns the typed value. Throws `SchemaError` on failure. |
|
|
275
|
+
| `Schema.validate(schema, value, options?)` | Returns `true` if valid, `false` otherwise. |
|
|
276
|
+
| `Schema.assert(schema, value, options?)` | Asserts that `value` matches the schema (TypeScript type guard). Throws on failure. |
|
|
277
|
+
| `Schema.test(schema, value, options?)` | Returns a result object `{ valid: boolean, value?: T, error?: SchemaError }`. |
|
|
278
|
+
|
|
279
|
+
### Schema Builders
|
|
280
|
+
|
|
281
|
+
| Function | Description |
|
|
282
|
+
| :------------------------ | :---------------------------------------------------------- |
|
|
283
|
+
| `string(options?)` | Schema for strings. Supports regex pattern, min/max length. |
|
|
284
|
+
| `number(options?)` | Schema for numbers. Supports min/max. |
|
|
285
|
+
| `integer(options?)` | Schema for integers. |
|
|
286
|
+
| `boolean(options?)` | Schema for booleans. |
|
|
287
|
+
| `date(options?)` | Schema for `Date` objects. |
|
|
288
|
+
| `bigint(options?)` | Schema for `bigint` values. |
|
|
289
|
+
| `symbol(options?)` | Schema for `symbol` values. |
|
|
290
|
+
| `regexp(options?)` | Schema for `RegExp` objects. |
|
|
291
|
+
| `object(props, options?)` | Schema for objects with defined properties. |
|
|
292
|
+
| `record(key, value)` | Schema for objects/dictionaries with arbitrary keys. |
|
|
293
|
+
| `array(item, options?)` | Schema for arrays. |
|
|
294
|
+
| `uint8Array(options?)` | Schema for `Uint8Array`. Supports base64/hex coercion. |
|
|
295
|
+
| `readableStream()` | Schema for `ReadableStream`. |
|
|
296
|
+
| `enumeration(enum)` | Schema for TypeScript enums or array of values. |
|
|
297
|
+
| `literal(value)` | Schema for exact primitive values. |
|
|
298
|
+
| `union(...schemas)` | Schema matching any one of the provided schemas. |
|
|
299
|
+
| `instance(Class)` | Schema checking `instanceof`. |
|
|
300
|
+
| `any()` | Allows any value (use carefully). |
|
|
301
|
+
| `unknown()` | Allows any value but types it as `unknown`. |
|
|
302
|
+
| `never()` | Allows no values. |
|
|
303
|
+
| `func(...)` | Schema for functions. |
|
|
304
|
+
|
|
305
|
+
### Modifiers & Utilities
|
|
306
|
+
|
|
307
|
+
| Function | Description |
|
|
308
|
+
| :----------------------- | :--------------------------------------------------- |
|
|
309
|
+
| `optional(schema)` | Allows `undefined`. |
|
|
310
|
+
| `nullable(schema)` | Allows `null`. |
|
|
311
|
+
| `defaulted(schema, val)` | Provides a default value if input is null/undefined. |
|
|
312
|
+
| `oneOrMany(schema)` | Accepts a single item or an array of items. |
|
|
313
|
+
| `deferred(() => schema)` | Defers schema resolution (for recursion). |
|
|
314
|
+
| `transform(schema, fn)` | Transforms the value after validation. |
|
|
315
|
+
| `pick(schema, keys)` | Creates a new object schema with picked keys. |
|
|
316
|
+
| `omit(schema, keys)` | Creates a new object schema without specified keys. |
|
|
317
|
+
| `partial(schema)` | Makes all object properties optional. |
|
|
318
|
+
| `assign(...schemas)` | Merges multiple object schemas. |
|
|
319
|
+
|
|
320
|
+
### Decorators
|
|
321
|
+
|
|
322
|
+
| Decorator | Description |
|
|
323
|
+
| :------------------- | :--------------------------------------------------- |
|
|
324
|
+
| `@Class(options?)` | Marks a class as a schema source. |
|
|
325
|
+
| `@Property(schema?)` | Defines a property schema. |
|
|
326
|
+
| `@StringProperty()` | Shortcut for `string()`. |
|
|
327
|
+
| `@NumberProperty()` | Shortcut for `number()`. |
|
|
328
|
+
| `@Integer()` | Shortcut for `integer()`. |
|
|
329
|
+
| `@BooleanProperty()` | Shortcut for `boolean()`. |
|
|
330
|
+
| `@DateProperty()` | Shortcut for `date()`. |
|
|
331
|
+
| `@Array(schema)` | Shortcut for `array()`. |
|
|
332
|
+
| `@Optional(schema?)` | Marks property as optional. |
|
|
333
|
+
| `@Nullable(schema?)` | Marks property as nullable. |
|
|
334
|
+
| `@Enumeration(enum)` | Shortcut for `enumeration()`. |
|
|
335
|
+
| `@Method(...)` | Defines schema for method arguments and return type. |
|
|
336
|
+
| `@Parameter(name)` | Defines schema/name for a parameter. |
|
|
337
|
+
|
|
338
|
+
### Converters
|
|
339
|
+
|
|
340
|
+
| Function | Description |
|
|
341
|
+
| :------------------------------- | :------------------------------------------------ |
|
|
342
|
+
| `convertToOpenApiSchema(schema)` | Converts a schema to an OpenAPI v3 schema object. |
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# @tstdl/base/serializer
|
|
2
|
+
|
|
3
|
+
A robust and extensible serialization library for TypeScript that transforms complex object graphs—including circular references, built-in types, and custom classes—into a JSON-compatible format and back.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
1. [✨ Features](#-features)
|
|
8
|
+
2. [Core Concepts](#core-concepts)
|
|
9
|
+
3. [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
4. [🔧 Advanced Topics](#-advanced-topics)
|
|
11
|
+
- [Handling Circular References](#handling-circular-references)
|
|
12
|
+
- [Custom Serializable Classes](#custom-serializable-classes)
|
|
13
|
+
- [Registering External Types](#registering-external-types)
|
|
14
|
+
- [Contextual Serialization](#contextual-serialization)
|
|
15
|
+
- [Raw Serialization](#raw-serialization)
|
|
16
|
+
5. [📚 API](#-api)
|
|
17
|
+
|
|
18
|
+
## ✨ Features
|
|
19
|
+
|
|
20
|
+
- **Circular Reference Handling**: Automatically detects cycles and serializes them as references to the original path.
|
|
21
|
+
- **Rich Type Support**: Out-of-the-box support for:
|
|
22
|
+
- `Map`, `Set`
|
|
23
|
+
- `Date`, `RegExp`, `Error`
|
|
24
|
+
- `BigInt`, `Symbol` (global via `Symbol.for`)
|
|
25
|
+
- `ArrayBuffer`, `TypedArray` (Int8, Uint8, etc.), `Buffer`
|
|
26
|
+
- `MessagePort` (serialized as raw if available)
|
|
27
|
+
- **JSON Compatible**: The output is a plain JavaScript object structure safe for `JSON.stringify`.
|
|
28
|
+
- **Extensible**: Add support for your own classes using decorators or registration functions.
|
|
29
|
+
- **Context Aware**: Pass shared state (context) during serialization/deserialization to avoid embedding large shared objects.
|
|
30
|
+
- **Type Safe**: Preserves type information for registered classes.
|
|
31
|
+
|
|
32
|
+
## Core Concepts
|
|
33
|
+
|
|
34
|
+
The serializer converts objects into a "Serialized Data" format. This format is composed entirely of JSON primitives (`string`, `number`, `boolean`, `null`) and plain objects/arrays.
|
|
35
|
+
|
|
36
|
+
- **Primitives**: Kept as-is. `undefined` becomes `{ "<undefined>": null }`.
|
|
37
|
+
- **Wrappers**: Complex types are wrapped in objects with specific keys, e.g., `{ "<Date>": 1672531200000 }` or `{ "<Map>": [...] }`.
|
|
38
|
+
- **References**: If an object is encountered more than once, subsequent occurrences are replaced with a reference path, e.g., `{ "<ref>": "$['users'][0]" }`.
|
|
39
|
+
- **Deserialization**: The `deserialize` function reconstructs the graph. It uses a multi-pass approach to resolve circular references (`ForwardRef`) ensuring the object graph identity is preserved.
|
|
40
|
+
|
|
41
|
+
## 🚀 Basic Usage
|
|
42
|
+
|
|
43
|
+
The most common use case is serializing an object to a JSON string and restoring it.
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { stringSerialize, stringDeserialize } from '@tstdl/base/serializer';
|
|
47
|
+
|
|
48
|
+
const data = {
|
|
49
|
+
id: 1,
|
|
50
|
+
created: new Date(),
|
|
51
|
+
meta: new Map<string, any>([['role', 'admin']]),
|
|
52
|
+
tags: new Set(['user', 'active']),
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// Serialize to JSON string
|
|
56
|
+
const json = stringSerialize(data);
|
|
57
|
+
console.log(json);
|
|
58
|
+
// Output: {"id":1,"created":{"<Date>":...},"meta":{"<Map>":[["role","admin"]]},"tags":{"<Set>":["user","active"]}}
|
|
59
|
+
|
|
60
|
+
// Deserialize back to object
|
|
61
|
+
const restored = stringDeserialize<typeof data>(json);
|
|
62
|
+
|
|
63
|
+
console.log(restored.created instanceof Date); // true
|
|
64
|
+
console.log(restored.meta instanceof Map); // true
|
|
65
|
+
console.log(restored.meta.get('role')); // 'admin'
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## 🔧 Advanced Topics
|
|
69
|
+
|
|
70
|
+
### Handling Circular References
|
|
71
|
+
|
|
72
|
+
You don't need to do anything special; the serializer handles this automatically.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { serialize, deserialize } from '@tstdl/base/serializer';
|
|
76
|
+
|
|
77
|
+
type Node = { id: number; next?: Node };
|
|
78
|
+
|
|
79
|
+
const nodeA: Node = { id: 1 };
|
|
80
|
+
const nodeB: Node = { id: 2 };
|
|
81
|
+
|
|
82
|
+
nodeA.next = nodeB;
|
|
83
|
+
nodeB.next = nodeA; // Cycle
|
|
84
|
+
|
|
85
|
+
const serialized = serialize(nodeA);
|
|
86
|
+
const restored = deserialize<Node>(serialized);
|
|
87
|
+
|
|
88
|
+
console.log(restored.next?.next === restored); // true
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### Custom Serializable Classes
|
|
92
|
+
|
|
93
|
+
To make your own classes serializable, implement the `Serializable` interface and use the `@serializable` decorator.
|
|
94
|
+
|
|
95
|
+
```typescript
|
|
96
|
+
import { Serializable, serializable, TryDereference, stringSerialize, stringDeserialize } from '@tstdl/base/serializer';
|
|
97
|
+
|
|
98
|
+
type UserData = { name: string; email: string };
|
|
99
|
+
|
|
100
|
+
@serializable('User') // Unique type name
|
|
101
|
+
class User implements Serializable<User, UserData> {
|
|
102
|
+
constructor(
|
|
103
|
+
public name: string,
|
|
104
|
+
public email: string,
|
|
105
|
+
) {}
|
|
106
|
+
|
|
107
|
+
// Define how to extract data.
|
|
108
|
+
// 'instance' is this, 'context' is from options.data
|
|
109
|
+
[Serializable.serialize](instance: User, context: any): UserData {
|
|
110
|
+
return { name: instance.name, email: instance.email };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Define how to reconstruct the instance.
|
|
114
|
+
// 'tryDereference' is used to resolve circular references in complex data.
|
|
115
|
+
[Serializable.deserialize](data: UserData, tryDereference: TryDereference, context: any): User {
|
|
116
|
+
return new User(data.name, data.email);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const user = new User('Alice', 'alice@example.com');
|
|
121
|
+
const json = stringSerialize(user);
|
|
122
|
+
const restored = stringDeserialize<User>(json);
|
|
123
|
+
|
|
124
|
+
console.log(restored instanceof User); // true
|
|
125
|
+
console.log(restored.name); // 'Alice'
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Advanced Deserialization (Circular References)
|
|
129
|
+
|
|
130
|
+
If your custom data structure contains other objects that might have circular references back to the object currently being deserialized, use the `tryDereference` callback.
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
[Serializable.deserialize](data: any, tryDereference: TryDereference): Folder {
|
|
134
|
+
const folder = new Folder();
|
|
135
|
+
|
|
136
|
+
for (const childData of data.children) {
|
|
137
|
+
const child = deserialize(childData);
|
|
138
|
+
folder.add(child);
|
|
139
|
+
|
|
140
|
+
// If 'child' is a forward reference (not yet fully deserialized),
|
|
141
|
+
// this will register a callback to be called once it is.
|
|
142
|
+
tryDereference(child, (dereferenced) => {
|
|
143
|
+
// logic to update the reference if needed
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return folder;
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Registering External Types
|
|
152
|
+
|
|
153
|
+
If you cannot modify the class (e.g., it comes from a third-party library), use `registerSerializer`.
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { registerSerializer, serialize, deserialize } from '@tstdl/base/serializer';
|
|
157
|
+
|
|
158
|
+
class Point {
|
|
159
|
+
constructor(
|
|
160
|
+
public x: number,
|
|
161
|
+
public y: number,
|
|
162
|
+
) {}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Register the serializer
|
|
166
|
+
registerSerializer(
|
|
167
|
+
Point,
|
|
168
|
+
'Point',
|
|
169
|
+
(instance) => ({ x: instance.x, y: instance.y }), // Serializer
|
|
170
|
+
(data) => new Point(data.x, data.y), // Deserializer
|
|
171
|
+
);
|
|
172
|
+
|
|
173
|
+
const point = new Point(10, 20);
|
|
174
|
+
const serialized = serialize(point);
|
|
175
|
+
const restored = deserialize<Point>(serialized);
|
|
176
|
+
|
|
177
|
+
console.log(restored instanceof Point); // true
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
### Contextual Serialization
|
|
181
|
+
|
|
182
|
+
Use `context` to reference shared objects that exist in both the serialization and deserialization environments, reducing payload size.
|
|
183
|
+
|
|
184
|
+
```typescript
|
|
185
|
+
import { serialize, deserialize } from '@tstdl/base/serializer';
|
|
186
|
+
|
|
187
|
+
const appConfig = { theme: 'dark', version: 2 };
|
|
188
|
+
|
|
189
|
+
const userSettings = {
|
|
190
|
+
notifications: true,
|
|
191
|
+
config: appConfig, // Reference to shared object
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
// Pass context during serialization
|
|
195
|
+
const serialized = serialize(userSettings, {
|
|
196
|
+
context: { appConfig },
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// The serialized output will reference appConfig via path, not copy it.
|
|
200
|
+
// { "notifications": true, "config": { "<ref>": "$['__context__']['appConfig']" } }
|
|
201
|
+
|
|
202
|
+
// Pass the SAME context during deserialization
|
|
203
|
+
const restored = deserialize<typeof userSettings>(serialized, {
|
|
204
|
+
context: { appConfig },
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
console.log(restored.config === appConfig); // true
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Raw Serialization
|
|
211
|
+
|
|
212
|
+
Sometimes you want an object to be passed through as-is (treated as a primitive), or you know it is already JSON-safe and don't want the serializer to traverse it.
|
|
213
|
+
|
|
214
|
+
```typescript
|
|
215
|
+
import { registerRawSerializable, serialize } from '@tstdl/base/serializer';
|
|
216
|
+
|
|
217
|
+
class Vector3 {
|
|
218
|
+
constructor(
|
|
219
|
+
public x: number,
|
|
220
|
+
public y: number,
|
|
221
|
+
public z: number,
|
|
222
|
+
) {}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Treat Vector3 as a raw object (properties are serialized directly, no type wrapper)
|
|
226
|
+
registerRawSerializable(Vector3);
|
|
227
|
+
|
|
228
|
+
const vec = new Vector3(1, 2, 3);
|
|
229
|
+
const serialized = serialize(vec);
|
|
230
|
+
|
|
231
|
+
console.log(serialized);
|
|
232
|
+
// Output: { x: 1, y: 2, z: 3 }
|
|
233
|
+
// Note: When deserialized, this will be a plain object, NOT an instance of Vector3.
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Replacers
|
|
237
|
+
|
|
238
|
+
Replacers allow you to transform values before they are processed by the serializer. This is useful for sanitizing data or handling types not known by the serializer.
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { serialize } from '@tstdl/base/serializer';
|
|
242
|
+
|
|
243
|
+
const data = { password: 'secret', name: 'Bob' };
|
|
244
|
+
|
|
245
|
+
const serialized = serialize(data, {
|
|
246
|
+
replacers: [
|
|
247
|
+
(value, context) => {
|
|
248
|
+
if (typeof value === 'object' && value !== null && 'password' in value) {
|
|
249
|
+
return { ...value, password: '***' };
|
|
250
|
+
}
|
|
251
|
+
return value;
|
|
252
|
+
}
|
|
253
|
+
]
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
console.log(serialized.password); // '***'
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
### ⚠️ Security (Unsafe Serialization)
|
|
260
|
+
|
|
261
|
+
By default, the serializer throws an error if it encounters a `function`. You can enable function serialization using `allowUnsafe`.
|
|
262
|
+
|
|
263
|
+
> [!WARNING]
|
|
264
|
+
> Enabling `allowUnsafe` is dangerous. It uses `eval()` during deserialization, which can lead to **Remote Code Execution (RCE)** if the source data is not trusted. Use this only with data from verified, secure sources.
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
import { stringSerialize, stringDeserialize } from '@tstdl/base/serializer';
|
|
268
|
+
|
|
269
|
+
const data = {
|
|
270
|
+
greet: (name: string) => `Hello, ${name}!`,
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const json = stringSerialize(data, { allowUnsafe: true });
|
|
274
|
+
const restored = stringDeserialize<typeof data>(json, { allowUnsafe: true });
|
|
275
|
+
|
|
276
|
+
console.log(restored.greet('World')); // 'Hello, World!'
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
## 📚 API
|
|
280
|
+
|
|
281
|
+
### Functions
|
|
282
|
+
|
|
283
|
+
| Function | Description |
|
|
284
|
+
| :------------------------------------- | :---------------------------------------------------------------------------- |
|
|
285
|
+
| `serialize<T>(value, options?)` | Serializes a value into a JSON-compatible object structure (`Serialized<T>`). |
|
|
286
|
+
| `deserialize<T>(data, options?)` | Reconstructs a value from its serialized structure. |
|
|
287
|
+
| `stringSerialize<T>(value, options?)` | Helper that calls `serialize` and then `JSON.stringify`. |
|
|
288
|
+
| `stringDeserialize<T>(json, options?)` | Helper that calls `JSON.parse` and then `deserialize`. |
|
|
289
|
+
| `registerSerializer(...)` | Manually registers a serializer/deserializer pair for a class constructor. |
|
|
290
|
+
| `registerSerializable(type, name?)` | Registers a class that implements the `Serializable` interface. |
|
|
291
|
+
| `registerRawSerializable(ctor)` | Registers a constructor to be treated as a raw object (passed through). |
|
|
292
|
+
|
|
293
|
+
### Decorators
|
|
294
|
+
|
|
295
|
+
| Decorator | Description |
|
|
296
|
+
| :------------------------- | :--------------------------------------------------------------- |
|
|
297
|
+
| `@serializable(typeName?)` | Class decorator to register a class implementing `Serializable`. |
|
|
298
|
+
|
|
299
|
+
### Interfaces & Types
|
|
300
|
+
|
|
301
|
+
| Type | Description |
|
|
302
|
+
| :---------------------- | :--------------------------------------------------------------------------------------- |
|
|
303
|
+
| `Serializable<T, Data>` | Interface requiring `[Serializable.serialize]` and `[Serializable.deserialize]` methods. |
|
|
304
|
+
| `SerializationOptions` | Options object for serialization/deserialization. |
|
|
305
|
+
|
|
306
|
+
### SerializationOptions
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
type SerializationOptions = {
|
|
310
|
+
/**
|
|
311
|
+
* Enable de/serialization of functions (unsafe!).
|
|
312
|
+
* @default false
|
|
313
|
+
*/
|
|
314
|
+
allowUnsafe?: boolean;
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Custom replacer functions to transform values before serialization.
|
|
318
|
+
*/
|
|
319
|
+
replacers?: SerializationReplacer[];
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Constructors to treat as raw (not serialized with type info).
|
|
323
|
+
*/
|
|
324
|
+
raws?: AbstractConstructor[];
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Context object for referencing shared data.
|
|
328
|
+
*/
|
|
329
|
+
context?: Record<string, any>;
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Data object passed to custom serializers/deserializers and replacers.
|
|
333
|
+
*/
|
|
334
|
+
data?: Record<string, any>;
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Disables dereferencing of ForwardRefs. Mostly useful for debugging custom serializers.
|
|
338
|
+
* @default false
|
|
339
|
+
*/
|
|
340
|
+
doNotDereferenceForwardRefs?: boolean;
|
|
341
|
+
};
|
|
342
|
+
```
|