@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.
Files changed (218) hide show
  1. package/README.md +166 -0
  2. package/ai/genkit/multi-region.plugin.js +5 -3
  3. package/ai/genkit/tests/multi-region.test.d.ts +1 -0
  4. package/ai/genkit/tests/multi-region.test.js +5 -2
  5. package/ai/parser/parser.js +2 -2
  6. package/ai/prompts/build.js +1 -0
  7. package/ai/prompts/instructions-formatter.d.ts +15 -2
  8. package/ai/prompts/instructions-formatter.js +36 -31
  9. package/ai/prompts/prompt-builder.js +5 -5
  10. package/ai/prompts/steering.d.ts +3 -2
  11. package/ai/prompts/steering.js +3 -1
  12. package/ai/tests/instructions-formatter.test.js +1 -0
  13. package/api/README.md +403 -0
  14. package/api/client/client.js +7 -13
  15. package/api/client/tests/api-client.test.js +10 -10
  16. package/api/default-error-handlers.js +1 -1
  17. package/api/response.d.ts +2 -2
  18. package/api/response.js +22 -33
  19. package/api/server/api-controller.d.ts +1 -1
  20. package/api/server/api-controller.js +3 -3
  21. package/api/server/api-request-token.provider.d.ts +1 -0
  22. package/api/server/api-request-token.provider.js +1 -0
  23. package/api/server/middlewares/allowed-methods.middleware.js +2 -1
  24. package/api/server/middlewares/content-type.middleware.js +2 -1
  25. package/api/types.d.ts +3 -2
  26. package/application/README.md +240 -0
  27. package/application/application.d.ts +1 -1
  28. package/application/application.js +3 -3
  29. package/application/providers.d.ts +20 -2
  30. package/application/providers.js +34 -7
  31. package/audit/README.md +267 -0
  32. package/audit/module.d.ts +5 -0
  33. package/audit/module.js +9 -1
  34. package/authentication/README.md +288 -0
  35. package/authentication/client/authentication.service.d.ts +12 -11
  36. package/authentication/client/authentication.service.js +21 -21
  37. package/authentication/client/http-client.middleware.js +2 -2
  38. package/authentication/server/module.d.ts +5 -0
  39. package/authentication/server/module.js +9 -1
  40. package/authentication/tests/authentication.api-controller.test.js +1 -1
  41. package/authentication/tests/authentication.api-request-token.provider.test.js +1 -1
  42. package/authentication/tests/authentication.client-error-handling.test.js +2 -1
  43. package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
  44. package/authentication/tests/authentication.client-service.test.js +1 -1
  45. package/browser/README.md +401 -0
  46. package/cancellation/README.md +156 -0
  47. package/cancellation/tests/coverage.test.d.ts +1 -0
  48. package/cancellation/tests/coverage.test.js +49 -0
  49. package/cancellation/tests/leak.test.js +24 -29
  50. package/cancellation/tests/token.test.d.ts +1 -0
  51. package/cancellation/tests/token.test.js +136 -0
  52. package/cancellation/token.d.ts +53 -177
  53. package/cancellation/token.js +132 -208
  54. package/circuit-breaker/postgres/module.d.ts +1 -0
  55. package/circuit-breaker/postgres/module.js +5 -1
  56. package/context/README.md +174 -0
  57. package/cookie/README.md +161 -0
  58. package/css/README.md +157 -0
  59. package/data-structures/README.md +320 -0
  60. package/decorators/README.md +140 -0
  61. package/distributed-loop/README.md +231 -0
  62. package/distributed-loop/distributed-loop.js +1 -1
  63. package/document-management/README.md +403 -0
  64. package/document-management/server/configure.js +5 -1
  65. package/document-management/server/module.d.ts +1 -1
  66. package/document-management/server/module.js +1 -1
  67. package/document-management/server/services/document-management-ancillary.service.js +1 -1
  68. package/document-management/server/services/document-management.service.js +9 -7
  69. package/document-management/tests/ai-config-hierarchy.test.js +0 -5
  70. package/document-management/tests/document-management-ai-overrides.test.js +0 -1
  71. package/document-management/tests/document-management-core.test.js +2 -7
  72. package/document-management/tests/document-management.api.test.js +6 -7
  73. package/document-management/tests/document-statistics.service.test.js +11 -12
  74. package/document-management/tests/document-validation-ai-overrides.test.js +0 -1
  75. package/document-management/tests/document.service.test.js +3 -3
  76. package/document-management/tests/enum-helpers.test.js +2 -3
  77. package/dom/README.md +213 -0
  78. package/enumerable/README.md +259 -0
  79. package/enumeration/README.md +121 -0
  80. package/errors/README.md +267 -0
  81. package/examples/document-management/main.d.ts +1 -0
  82. package/examples/document-management/main.js +14 -11
  83. package/file/README.md +191 -0
  84. package/formats/README.md +210 -0
  85. package/function/README.md +144 -0
  86. package/http/README.md +318 -0
  87. package/http/client/adapters/undici.adapter.js +1 -1
  88. package/http/client/http-client-request.d.ts +6 -5
  89. package/http/client/http-client-request.js +8 -9
  90. package/http/server/node/node-http-server.js +1 -2
  91. package/image-service/README.md +137 -0
  92. package/injector/README.md +491 -0
  93. package/intl/README.md +113 -0
  94. package/json-path/README.md +182 -0
  95. package/jsx/README.md +154 -0
  96. package/key-value-store/README.md +191 -0
  97. package/key-value-store/postgres/module.d.ts +1 -0
  98. package/key-value-store/postgres/module.js +5 -1
  99. package/lock/README.md +249 -0
  100. package/lock/postgres/module.d.ts +1 -0
  101. package/lock/postgres/module.js +5 -1
  102. package/lock/web/web-lock.js +119 -47
  103. package/logger/README.md +287 -0
  104. package/mail/README.md +256 -0
  105. package/mail/module.d.ts +5 -1
  106. package/mail/module.js +11 -6
  107. package/memory/README.md +144 -0
  108. package/message-bus/README.md +244 -0
  109. package/message-bus/message-bus-base.js +1 -1
  110. package/module/README.md +182 -0
  111. package/module/module.d.ts +1 -1
  112. package/module/module.js +77 -17
  113. package/module/modules/web-server.module.js +3 -4
  114. package/notification/server/module.d.ts +1 -0
  115. package/notification/server/module.js +5 -1
  116. package/notification/tests/notification-flow.test.js +2 -2
  117. package/notification/tests/notification-type.service.test.js +24 -15
  118. package/object-storage/README.md +300 -0
  119. package/openid-connect/README.md +274 -0
  120. package/orm/README.md +423 -0
  121. package/orm/decorators.d.ts +5 -1
  122. package/orm/decorators.js +1 -1
  123. package/orm/server/drizzle/schema-converter.js +17 -30
  124. package/orm/server/encryption.d.ts +0 -1
  125. package/orm/server/encryption.js +1 -4
  126. package/orm/server/index.d.ts +1 -6
  127. package/orm/server/index.js +1 -6
  128. package/orm/server/migration.d.ts +19 -0
  129. package/orm/server/migration.js +72 -0
  130. package/orm/server/repository.d.ts +1 -1
  131. package/orm/server/transaction.d.ts +5 -10
  132. package/orm/server/transaction.js +22 -26
  133. package/orm/server/transactional.js +3 -3
  134. package/orm/tests/database-migration.test.d.ts +1 -0
  135. package/orm/tests/database-migration.test.js +82 -0
  136. package/orm/tests/encryption.test.js +3 -4
  137. package/orm/utils.d.ts +17 -2
  138. package/orm/utils.js +49 -1
  139. package/package.json +9 -6
  140. package/password/README.md +164 -0
  141. package/pdf/README.md +246 -0
  142. package/polyfills.js +1 -0
  143. package/pool/README.md +198 -0
  144. package/process/README.md +237 -0
  145. package/promise/README.md +252 -0
  146. package/promise/cancelable-promise.js +1 -1
  147. package/random/README.md +193 -0
  148. package/rate-limit/postgres/module.d.ts +1 -0
  149. package/rate-limit/postgres/module.js +5 -1
  150. package/reflection/README.md +305 -0
  151. package/reflection/decorator-data.js +11 -12
  152. package/rpc/README.md +386 -0
  153. package/rxjs-utils/README.md +262 -0
  154. package/schema/README.md +342 -0
  155. package/serializer/README.md +342 -0
  156. package/signals/implementation/README.md +134 -0
  157. package/sse/README.md +278 -0
  158. package/task-queue/README.md +293 -0
  159. package/task-queue/postgres/drizzle/{0000_simple_invisible_woman.sql → 0000_wakeful_sunspot.sql} +22 -14
  160. package/task-queue/postgres/drizzle/meta/0000_snapshot.json +160 -82
  161. package/task-queue/postgres/drizzle/meta/_journal.json +2 -2
  162. package/task-queue/postgres/module.d.ts +1 -0
  163. package/task-queue/postgres/module.js +5 -1
  164. package/task-queue/postgres/schemas.d.ts +9 -6
  165. package/task-queue/postgres/schemas.js +4 -3
  166. package/task-queue/postgres/task-queue.d.ts +4 -13
  167. package/task-queue/postgres/task-queue.js +462 -355
  168. package/task-queue/postgres/task.model.d.ts +12 -5
  169. package/task-queue/postgres/task.model.js +51 -25
  170. package/task-queue/task-context.d.ts +2 -2
  171. package/task-queue/task-context.js +8 -8
  172. package/task-queue/task-queue.d.ts +53 -19
  173. package/task-queue/task-queue.js +121 -55
  174. package/task-queue/tests/cascading-cancellations.test.d.ts +1 -0
  175. package/task-queue/tests/cascading-cancellations.test.js +38 -0
  176. package/task-queue/tests/complex.test.js +45 -229
  177. package/task-queue/tests/coverage-branch.test.d.ts +1 -0
  178. package/task-queue/tests/coverage-branch.test.js +407 -0
  179. package/task-queue/tests/coverage-enhancement.test.d.ts +1 -0
  180. package/task-queue/tests/coverage-enhancement.test.js +144 -0
  181. package/task-queue/tests/dag-dependencies.test.d.ts +1 -0
  182. package/task-queue/tests/dag-dependencies.test.js +41 -0
  183. package/task-queue/tests/dependencies.test.js +28 -26
  184. package/task-queue/tests/extensive-dependencies.test.js +64 -139
  185. package/task-queue/tests/fan-out-spawning.test.d.ts +1 -0
  186. package/task-queue/tests/fan-out-spawning.test.js +53 -0
  187. package/task-queue/tests/idempotent-replacement.test.d.ts +1 -0
  188. package/task-queue/tests/idempotent-replacement.test.js +61 -0
  189. package/task-queue/tests/missing-idempotent-tasks.test.d.ts +1 -0
  190. package/task-queue/tests/missing-idempotent-tasks.test.js +38 -0
  191. package/task-queue/tests/queue.test.js +128 -8
  192. package/task-queue/tests/worker.test.js +39 -16
  193. package/task-queue/tests/zombie-parent.test.d.ts +1 -0
  194. package/task-queue/tests/zombie-parent.test.js +45 -0
  195. package/task-queue/tests/zombie-recovery.test.d.ts +1 -0
  196. package/task-queue/tests/zombie-recovery.test.js +51 -0
  197. package/templates/README.md +287 -0
  198. package/test5.js +5 -5
  199. package/testing/README.md +157 -0
  200. package/testing/integration-setup.d.ts +4 -4
  201. package/testing/integration-setup.js +54 -29
  202. package/text/README.md +346 -0
  203. package/text/localization.service.js +2 -2
  204. package/threading/README.md +238 -0
  205. package/types/README.md +311 -0
  206. package/utils/README.md +322 -0
  207. package/utils/async-iterable-helpers/observable-iterable.d.ts +1 -1
  208. package/utils/async-iterable-helpers/observable-iterable.js +4 -8
  209. package/utils/async-iterable-helpers/take-until.js +4 -4
  210. package/utils/backoff.js +89 -30
  211. package/utils/file-reader.js +1 -2
  212. package/utils/retry-with-backoff.js +1 -1
  213. package/utils/timer.d.ts +1 -1
  214. package/utils/timer.js +5 -7
  215. package/utils/timing.d.ts +1 -1
  216. package/utils/timing.js +2 -4
  217. package/utils/z-base32.d.ts +1 -0
  218. package/utils/z-base32.js +1 -0
@@ -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
+ ```