@tstdl/base 0.93.138 → 0.93.140
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.js +2 -2
- package/audit/README.md +267 -0
- 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/tests/authentication.client-error-handling.test.js +2 -1
- package/authentication/tests/authentication.client-service-refresh.test.js +5 -3
- 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.d.ts +1 -0
- package/cancellation/tests/leak.test.js +35 -0
- 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 -201
- 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/services/document-management.service.js +9 -7
- 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.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/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/injector/injector.d.ts +1 -0
- package/injector/injector.js +17 -5
- package/injector/tests/leak.test.d.ts +1 -0
- package/injector/tests/leak.test.js +45 -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/lock/README.md +249 -0
- package/lock/web/web-lock.js +119 -47
- package/logger/README.md +287 -0
- package/mail/README.md +256 -0
- 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 +1 -1
- 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/package.json +8 -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/reflection/README.md +305 -0
- 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 +300 -0
- package/task-queue/postgres/task-queue.d.ts +2 -1
- package/task-queue/postgres/task-queue.js +32 -2
- package/task-queue/task-context.js +1 -1
- package/task-queue/task-queue.d.ts +17 -0
- package/task-queue/task-queue.js +103 -44
- package/task-queue/tests/complex.test.js +4 -4
- package/task-queue/tests/dependencies.test.js +4 -2
- package/task-queue/tests/queue.test.js +111 -0
- package/task-queue/tests/worker.test.js +21 -13
- package/templates/README.md +287 -0
- package/testing/README.md +157 -0
- package/text/README.md +346 -0
- 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/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
|
@@ -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
|
+
```
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Signals Implementation
|
|
2
|
+
|
|
3
|
+
This module provides the core reactive implementation for the Tstdl library. It is based on Angular's Signals system, adapted for use in any environment (Node.js or Browser) without requiring the Angular runtime.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [Writable Signals](#writable-signals)
|
|
10
|
+
- [Computed Signals](#computed-signals)
|
|
11
|
+
- [Effects](#effects)
|
|
12
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
13
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
14
|
+
- [Untracked Reads](#untracked-reads)
|
|
15
|
+
- [RxJS Interop](#rxjs-interop)
|
|
16
|
+
- [Equality Functions](#equality-functions)
|
|
17
|
+
- [📚 API](#-api)
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **Granular Reactivity**: Only dependents of changed signals are re-evaluated.
|
|
22
|
+
- **Lazy Evaluation**: Computed signals only calculate their value when read.
|
|
23
|
+
- **Glitch-Free**: Eliminates intermediate inconsistent states during updates.
|
|
24
|
+
- **RxJS Integration**: Seamlessly bridge between reactive signals and asynchronous observables.
|
|
25
|
+
- **Environment Agnostic**: Works in Node.js, browsers, and other JavaScript environments.
|
|
26
|
+
|
|
27
|
+
## Core Concepts
|
|
28
|
+
|
|
29
|
+
### Writable Signals
|
|
30
|
+
|
|
31
|
+
A `WritableSignal` is the primary source of truth in the reactive graph. It holds a value that can be explicitly set or updated.
|
|
32
|
+
|
|
33
|
+
- `set(value)`: Overwrites the current value.
|
|
34
|
+
- `update(fn)`: Computes a new value based on the previous one.
|
|
35
|
+
- `asReadonly()`: Returns a version of the signal that cannot be modified.
|
|
36
|
+
|
|
37
|
+
### Computed Signals
|
|
38
|
+
|
|
39
|
+
A `computed` signal derives its value from other signals. It automatically tracks any signals accessed during its execution and re-evaluates only when those dependencies change. Computations are lazy and memoized.
|
|
40
|
+
|
|
41
|
+
### Effects
|
|
42
|
+
|
|
43
|
+
An `effect` is a reactive block of code that automatically re-runs whenever any signals it reads change. Effects are scheduled to run asynchronously using a microtask to batch updates and avoid redundant executions.
|
|
44
|
+
|
|
45
|
+
## 🚀 Basic Usage
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
import { signal, computed, effect } from './index.js';
|
|
49
|
+
|
|
50
|
+
// Create a writable signal
|
|
51
|
+
const count = signal(0);
|
|
52
|
+
|
|
53
|
+
// Create a computed signal
|
|
54
|
+
const isEven = computed(() => count() % 2 === 0);
|
|
55
|
+
|
|
56
|
+
// Create an effect
|
|
57
|
+
effect(() => {
|
|
58
|
+
console.log(`Count: ${count()}, isEven: ${isEven()}`);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Updating the signal triggers the effect
|
|
62
|
+
count.set(1); // Output: Count: 1, isEven: false
|
|
63
|
+
count.update(c => c + 1); // Output: Count: 2, isEven: true
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## 🔧 Advanced Topics
|
|
67
|
+
|
|
68
|
+
### Untracked Reads
|
|
69
|
+
|
|
70
|
+
Sometimes you want to read a signal's value without creating a dependency. Use `untracked` for this purpose.
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
import { signal, effect, untracked } from './index.js';
|
|
74
|
+
|
|
75
|
+
const count = signal(0);
|
|
76
|
+
const other = signal('A');
|
|
77
|
+
|
|
78
|
+
effect(() => {
|
|
79
|
+
console.log(count());
|
|
80
|
+
console.log(untracked(other)); // Changes to 'other' won't trigger this effect
|
|
81
|
+
});
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### RxJS Interop
|
|
85
|
+
|
|
86
|
+
The module provides utilities to convert between signals and observables.
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
import { signal, toObservable, toSignal } from './index.js';
|
|
90
|
+
import { interval } from 'rxjs';
|
|
91
|
+
|
|
92
|
+
// Signal to Observable
|
|
93
|
+
const count = signal(0);
|
|
94
|
+
const count$ = toObservable(count);
|
|
95
|
+
|
|
96
|
+
// Observable to Signal
|
|
97
|
+
const timer$ = interval(1000);
|
|
98
|
+
const timer = toSignal(timer$, { initialValue: 0 });
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Equality Functions
|
|
102
|
+
|
|
103
|
+
You can customize how signal changes are detected by providing a custom `equal` function.
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
import { signal } from './index.js';
|
|
107
|
+
|
|
108
|
+
const user = signal({ id: 1, name: 'John' }, {
|
|
109
|
+
equal: (a, b) => a.id === b.id
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// This update will NOT trigger dependents because the ID is the same
|
|
113
|
+
user.set({ id: 1, name: 'John Doe' });
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## 📚 API
|
|
117
|
+
|
|
118
|
+
### Functions
|
|
119
|
+
|
|
120
|
+
- `signal(initialValue, options?)`: Creates a `WritableSignal`.
|
|
121
|
+
- `computed(computation, options?)`: Creates a computed `Signal`.
|
|
122
|
+
- `effect(effectFn, options?)`: Creates an `EffectRef`.
|
|
123
|
+
- `untracked(fn)`: Runs `fn` in a non-reactive context.
|
|
124
|
+
- `isSignal(value)`: Returns `true` if the value is a Signal.
|
|
125
|
+
- `isWritableSignal(value)`: Returns `true` if the value is a WritableSignal.
|
|
126
|
+
- `toObservable(signal, options?)`: Converts a signal to an RxJS Observable.
|
|
127
|
+
- `toSignal(observable, options?)`: Converts an RxJS Observable to a signal.
|
|
128
|
+
- `configureDefaultSignalsImplementation()`: Configures the library to use this implementation by default.
|
|
129
|
+
|
|
130
|
+
### Interfaces
|
|
131
|
+
|
|
132
|
+
- `Signal<T>`: A readonly reactive value.
|
|
133
|
+
- `WritableSignal<T>`: A signal that can be changed.
|
|
134
|
+
- `EffectRef`: A handle to a running effect, allowing it to be destroyed.
|