@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/rpc/README.md
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
# RPC Module
|
|
2
|
+
|
|
3
|
+
A powerful, type-safe Remote Procedure Call (RPC) framework for seamless communication between JavaScript environments, such as the main thread, Web Workers, SharedWorkers, and Node.js worker threads. It abstracts the complexity of message passing into intuitive local function calls and object interactions.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
11
|
+
- [Streaming Data](#streaming-data)
|
|
12
|
+
- [Transferring Data Efficiently](#transferring-data-efficiently)
|
|
13
|
+
- [Serialization vs. Proxying](#serialization-vs-proxying)
|
|
14
|
+
- [📚 API](#-api)
|
|
15
|
+
|
|
16
|
+
## ✨ Features
|
|
17
|
+
|
|
18
|
+
- **Type-Safe Proxies**: Interact with remote objects and functions with full TypeScript support, as if they were local.
|
|
19
|
+
- **Environment Agnostic**: Works with `Worker`, `SharedWorker`, `MessagePort`, `Window`, and Node.js `worker_threads`.
|
|
20
|
+
- **Efficient Data Transfer**: Built-in support for `Transferable` objects (like `ArrayBuffer`) to avoid copying overhead.
|
|
21
|
+
- **Custom Adapters**: Extensible architecture to handle complex non-serializable types like `ReadableStream`.
|
|
22
|
+
- **Flexible Serialization**: Control whether objects are sent by reference (proxy) or by value (serialized).
|
|
23
|
+
|
|
24
|
+
## Core Concepts
|
|
25
|
+
|
|
26
|
+
The RPC module simplifies cross-context communication by establishing a structured connection.
|
|
27
|
+
|
|
28
|
+
### Endpoint
|
|
29
|
+
|
|
30
|
+
An **Endpoint** (`RpcEndpoint`) wraps the underlying transport mechanism (like a `Worker` or `MessagePort`). It manages the lifecycle of the connection and handles the routing of messages. The primary implementation provided is `MessagePortRpcEndpoint`.
|
|
31
|
+
|
|
32
|
+
### Channel
|
|
33
|
+
|
|
34
|
+
A **Channel** (`RpcChannel`) is a logical subdivision of an endpoint. While the endpoint manages the physical connection, channels allow multiple independent conversations to happen simultaneously without interference. The system uses a control channel to negotiate connections and dynamic channels for specific object proxies.
|
|
35
|
+
|
|
36
|
+
### Expose & Connect
|
|
37
|
+
|
|
38
|
+
The fundamental pattern is **Expose** and **Connect**:
|
|
39
|
+
|
|
40
|
+
- **Expose**: One side (e.g., a Worker) registers an object or function under a specific name using `Rpc.expose()`.
|
|
41
|
+
- **Connect**: The other side (e.g., the Main Thread) requests access to that named resource using `Rpc.connect()`. This returns a **Proxy**.
|
|
42
|
+
|
|
43
|
+
### Proxies
|
|
44
|
+
|
|
45
|
+
When you connect to a remote object, you receive a **Proxy**. Accessing properties or calling methods on this proxy sends a message to the remote side, executes the operation on the original object, and returns the result. This makes remote interactions feel synchronous or promise-based, hiding the asynchronous nature of message passing.
|
|
46
|
+
|
|
47
|
+
The system also supports class constructors. If you expose a class (constructor), you can `new` it on the client side, and it will return a proxy to a remote instance.
|
|
48
|
+
|
|
49
|
+
### Automatic Cleanup & Lifecycle
|
|
50
|
+
|
|
51
|
+
The RPC module uses `FinalizationRegistry` to manage the lifecycle of channels automatically. When a proxy is garbage collected on the receiving side, the corresponding channel on both sides is closed.
|
|
52
|
+
|
|
53
|
+
However, garbage collection is non-deterministic. For scenarios requiring explicit control (like unit tests), you can use:
|
|
54
|
+
|
|
55
|
+
- **`Rpc.isAlive(proxy)`**: Checks if the proxy's underlying channel is still open.
|
|
56
|
+
- **`Rpc.release(proxy)`**: Immediately closes the channel associated with the proxy. Subsequent calls to the proxy will throw an `RpcConnectionClosedError`.
|
|
57
|
+
|
|
58
|
+
### Async Property Assignments, `has`, and `delete`
|
|
59
|
+
|
|
60
|
+
While you can assign properties directly (`proxy.foo = 42`), check for existence (`'foo' in proxy`), or delete properties (`delete proxy.foo`), standard JavaScript expects these operations to be synchronous. However, remote operations are inherently asynchronous.
|
|
61
|
+
|
|
62
|
+
When using these operators on a proxy:
|
|
63
|
+
|
|
64
|
+
- **Assignment**: Returns the value assigned immediately, while the remote operation happens in the background.
|
|
65
|
+
- **`in` operator**: Returns a `Promise` (which is truthy), not the actual boolean result.
|
|
66
|
+
- **`delete` operator**: Returns a `Promise` (which is truthy), not the actual boolean result.
|
|
67
|
+
|
|
68
|
+
**Recommendation**: Use the `Rpc` helpers for awaitable and correct results:
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Awaitable assignment
|
|
72
|
+
await Rpc.set(remoteService, 'status', 'active');
|
|
73
|
+
|
|
74
|
+
// Awaitable 'in' check
|
|
75
|
+
if (await Rpc.has(remoteService, 'status')) { ... }
|
|
76
|
+
|
|
77
|
+
// Awaitable 'delete'
|
|
78
|
+
const deleted = await Rpc.delete(remoteService, 'status');
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Enhanced Error Handling
|
|
82
|
+
|
|
83
|
+
When a connection is lost or manually released, pending and future requests throw an `RpcConnectionClosedError`.
|
|
84
|
+
|
|
85
|
+
Remote errors are wrapped in `RpcRemoteError`. This implementation is now enhanced to:
|
|
86
|
+
|
|
87
|
+
1. Preserve the original error name, message, and stack.
|
|
88
|
+
2. Attempt to reconstruct the original error prototype if the error type was registered with the `@tstdl/base/serializer` system.
|
|
89
|
+
|
|
90
|
+
### Proxy Trap Support
|
|
91
|
+
|
|
92
|
+
The RPC proxies now support additional standard traps, allowing more natural interaction with remote objects:
|
|
93
|
+
|
|
94
|
+
- **`has`**: Works with the `in` operator (e.g., `'foo' in proxy`).
|
|
95
|
+
- **`deleteProperty`**: Works with the `delete` operator (e.g., `delete proxy.foo`).
|
|
96
|
+
- **`defineProperty`**: Supports remote definition of properties with full descriptor support.
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## 🚀 Basic Usage
|
|
101
|
+
|
|
102
|
+
This example demonstrates how to expose a service in a Web Worker and consume it from the main thread.
|
|
103
|
+
|
|
104
|
+
### 1. Define the Service (Shared)
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
// pet.service.ts
|
|
108
|
+
export type Pet = {
|
|
109
|
+
name: string;
|
|
110
|
+
species: 'dog' | 'cat';
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export class PetService {
|
|
114
|
+
private pets: Pet[] = [{ name: 'Fido', species: 'dog' }];
|
|
115
|
+
|
|
116
|
+
async getPets(): Promise<Pet[]> {
|
|
117
|
+
return this.pets;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async addPet(pet: Pet): Promise<void> {
|
|
121
|
+
this.pets.push(pet);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 2. Expose in Worker
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// worker.ts
|
|
130
|
+
import { Rpc } from '@tstdl/base/rpc';
|
|
131
|
+
import { MessagePortRpcEndpoint } from '@tstdl/base/rpc/endpoints';
|
|
132
|
+
import { PetService } from './pet.service.js';
|
|
133
|
+
|
|
134
|
+
// Create an endpoint wrapping the worker's global scope
|
|
135
|
+
const endpoint = new MessagePortRpcEndpoint(self as any);
|
|
136
|
+
|
|
137
|
+
// Start listening for incoming connections
|
|
138
|
+
Rpc.listen(endpoint);
|
|
139
|
+
|
|
140
|
+
// Instantiate and expose the service
|
|
141
|
+
const petService = new PetService();
|
|
142
|
+
Rpc.expose(petService, 'pet-service');
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3. Connect from Main Thread
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// main.ts
|
|
149
|
+
import { Rpc } from '@tstdl/base/rpc';
|
|
150
|
+
import { MessagePortRpcEndpoint } from '@tstdl/base/rpc/endpoints';
|
|
151
|
+
import type { PetService } from './pet.service.js';
|
|
152
|
+
|
|
153
|
+
async function main() {
|
|
154
|
+
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
|
|
155
|
+
const endpoint = new MessagePortRpcEndpoint(worker);
|
|
156
|
+
|
|
157
|
+
// Connect to the remote service.
|
|
158
|
+
// The generic type argument ensures type safety for the returned proxy.
|
|
159
|
+
const remotePetService = await Rpc.connect<PetService>(endpoint, 'pet-service');
|
|
160
|
+
|
|
161
|
+
// Call methods as if they were local
|
|
162
|
+
const allPets = await remotePetService.getPets();
|
|
163
|
+
console.log('Initial pets:', allPets);
|
|
164
|
+
|
|
165
|
+
await remotePetService.addPet({ name: 'Whiskers', species: 'cat' });
|
|
166
|
+
|
|
167
|
+
const updatedPets = await remotePetService.getPets();
|
|
168
|
+
console.log('Updated pets:', updatedPets);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
main();
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 🔧 Advanced Topics
|
|
175
|
+
|
|
176
|
+
### Streaming Data
|
|
177
|
+
|
|
178
|
+
Standard serialization cannot handle streams. The `ReadableStreamRpcAdapter` allows you to stream data transparently between environments.
|
|
179
|
+
|
|
180
|
+
**Worker (Source):**
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
import { Rpc } from '@tstdl/base/rpc';
|
|
184
|
+
import { ReadableStreamRpcAdapter } from '@tstdl/base/rpc/adapters';
|
|
185
|
+
import { MessagePortRpcEndpoint } from '@tstdl/base/rpc/endpoints';
|
|
186
|
+
import { timeout } from '@tstdl/base/utils';
|
|
187
|
+
|
|
188
|
+
// 1. Register the adapter
|
|
189
|
+
Rpc.registerAdapter(new ReadableStreamRpcAdapter());
|
|
190
|
+
|
|
191
|
+
const endpoint = new MessagePortRpcEndpoint(self as any);
|
|
192
|
+
Rpc.listen(endpoint);
|
|
193
|
+
|
|
194
|
+
function getLogStream(): ReadableStream<string> {
|
|
195
|
+
let count = 0;
|
|
196
|
+
return new ReadableStream({
|
|
197
|
+
async pull(controller) {
|
|
198
|
+
if (count++ >= 5) {
|
|
199
|
+
controller.close();
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
await timeout(500);
|
|
203
|
+
controller.enqueue(`Log entry #${count}`);
|
|
204
|
+
},
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// 2. Use Rpc.adapt to wrap the stream
|
|
209
|
+
const getAdaptedLogStream = () => Rpc.adapt(getLogStream(), new ReadableStreamRpcAdapter());
|
|
210
|
+
|
|
211
|
+
Rpc.expose(getAdaptedLogStream, 'log-service');
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Main Thread (Target):**
|
|
215
|
+
|
|
216
|
+
```typescript
|
|
217
|
+
import { Rpc } from '@tstdl/base/rpc';
|
|
218
|
+
import { ReadableStreamRpcAdapter } from '@tstdl/base/rpc/adapters';
|
|
219
|
+
import { MessagePortRpcEndpoint } from '@tstdl/base/rpc/endpoints';
|
|
220
|
+
|
|
221
|
+
async function main() {
|
|
222
|
+
// 1. Register the adapter here as well
|
|
223
|
+
Rpc.registerAdapter(new ReadableStreamRpcAdapter());
|
|
224
|
+
|
|
225
|
+
const worker = new Worker(new URL('./worker.ts', import.meta.url), { type: 'module' });
|
|
226
|
+
const endpoint = new MessagePortRpcEndpoint(worker);
|
|
227
|
+
|
|
228
|
+
const getRemoteLogStream = await Rpc.connect<() => ReadableStream<string>>(endpoint, 'log-service');
|
|
229
|
+
|
|
230
|
+
// 2. Receive the proxy stream
|
|
231
|
+
const stream = await getRemoteLogStream();
|
|
232
|
+
const reader = stream.getReader();
|
|
233
|
+
|
|
234
|
+
while (true) {
|
|
235
|
+
const { done, value } = await reader.read();
|
|
236
|
+
if (done) break;
|
|
237
|
+
console.log('Received:', value);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Transferring Data Efficiently
|
|
243
|
+
|
|
244
|
+
To avoid cloning large objects (like `ArrayBuffer`, `MessagePort`, `ImageBitmap`), use `Rpc.transfer()`. This moves ownership of the object to the receiving context.
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
// In Main Thread
|
|
248
|
+
const buffer = new Uint8Array(1024 * 1024 * 10).buffer; // 10MB
|
|
249
|
+
|
|
250
|
+
// Mark 'buffer' to be transferred in the second argument
|
|
251
|
+
await remoteService.processData(Rpc.transfer(buffer, [buffer]));
|
|
252
|
+
|
|
253
|
+
console.log(buffer.byteLength); // 0 (ownership transferred)
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Serialization vs. Proxying
|
|
257
|
+
|
|
258
|
+
By default:
|
|
259
|
+
|
|
260
|
+
- **Primitives** are copied.
|
|
261
|
+
- **Objects** are proxied (a reference is kept, and a proxy is sent).
|
|
262
|
+
|
|
263
|
+
You can change this behavior:
|
|
264
|
+
|
|
265
|
+
- **`Rpc.serialize(obj)`**: Forces an object to be serialized (deep copied) instead of proxied. Useful for DTOs or configuration objects.
|
|
266
|
+
- **`Rpc.proxy(obj)`**: Explicitly marks an object to be proxied. Useful if the default heuristic doesn't catch it or if you are nesting objects.
|
|
267
|
+
|
|
268
|
+
```typescript
|
|
269
|
+
const config = { mode: 'dark', settings: { ... } };
|
|
270
|
+
|
|
271
|
+
// Send a copy of 'config', not a proxy to it
|
|
272
|
+
await remoteService.updateConfig(Rpc.serialize(config));
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Error Handling
|
|
276
|
+
|
|
277
|
+
When an error occurs on the remote side, it is caught, serialized, and re-thrown on the calling side.
|
|
278
|
+
|
|
279
|
+
- **`RpcError`**: The base error thrown by the RPC system itself (e.g., connection lost, method not found).
|
|
280
|
+
- **`RpcRemoteError`**: Wraps an error that occurred on the remote implementation. It preserves the original error's message, stack, and name where possible.
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
try {
|
|
284
|
+
await remoteService.doSomethingRisky();
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (error instanceof RpcError) {
|
|
287
|
+
console.error('RPC communication failed:', error.message);
|
|
288
|
+
if (error.cause instanceof RpcRemoteError) {
|
|
289
|
+
console.error('Original remote error:', error.cause.message);
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
### Implementing Custom Adapters
|
|
296
|
+
|
|
297
|
+
You can handle types that aren't natively serializable by implementing the `RpcAdapter` interface.
|
|
298
|
+
|
|
299
|
+
```typescript
|
|
300
|
+
import { RpcAdapter, RpcChannel, RpcEndpoint } from '@tstdl/base/rpc';
|
|
301
|
+
|
|
302
|
+
class MyCustomAdapter implements RpcAdapter<MyType, MyData> {
|
|
303
|
+
name = 'MyCustom';
|
|
304
|
+
|
|
305
|
+
adaptSource(value: MyType, channel: RpcChannel): { data: MyData } {
|
|
306
|
+
// Setup communication on the source side
|
|
307
|
+
return {
|
|
308
|
+
data: {
|
|
309
|
+
/* ... */
|
|
310
|
+
},
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
adaptTarget(data: MyData, channel: RpcChannel): MyType {
|
|
315
|
+
// Reconstruct the object on the target side using the channel
|
|
316
|
+
return new MyType(/* ... */);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Don't forget to register it on both sides!
|
|
321
|
+
Rpc.registerAdapter(new MyCustomAdapter());
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## 📚 API
|
|
325
|
+
|
|
326
|
+
### Rpc
|
|
327
|
+
|
|
328
|
+
The main entry point for the library.
|
|
329
|
+
|
|
330
|
+
| Method | Arguments | Returns | Description |
|
|
331
|
+
| :---------------- | :----------------------- | :---------------------- | :----------------------------------------------------------------------------------------------------------- |
|
|
332
|
+
| `listen` | `endpoint: RpcEndpoint` | `void` | Starts listening for incoming connections on the provided endpoint. |
|
|
333
|
+
| `connect` | `endpoint, name?` | `Promise<RpcRemote<T>>` | Connects to a remote object exposed with the given name (default: `'default'`). |
|
|
334
|
+
| `expose` | `object, name?` | `void` | Makes an object or function available for remote connections (default: `'default'`). |
|
|
335
|
+
| `registerAdapter` | `adapter: RpcAdapter` | `void` | Registers a custom adapter for handling specific types (e.g., streams). |
|
|
336
|
+
| `proxy` | `object, root?` | `T` | Explicitly marks an object to be transmitted as a remote proxy. |
|
|
337
|
+
| `transfer` | `object, transfer` | `T` | Marks an object and specifies associated transferable data (e.g., ArrayBuffers). |
|
|
338
|
+
| `serialize` | `object, options?` | `T` | Forces an object to be transmitted by value (serialized) instead of by proxy using `@tstdl/base/serializer`. |
|
|
339
|
+
| `adapt` | `object, adapter, root?` | `T` | Marks an object to be handled by a specific adapter instance. |
|
|
340
|
+
| `isProxied` | `object: object` | `boolean` | Returns `true` if the object has been marked for proxying via `Rpc.proxy()`. |
|
|
341
|
+
| `release` | `proxy: object` | `void` | Manually closes the channel associated with the proxy (deterministic cleanup). |
|
|
342
|
+
| `isAlive` | `proxy: object` | `boolean` | Returns `true` if the proxy's underlying channel is still open. |
|
|
343
|
+
| `set` | `proxy, property, value` | `Promise<void>` | Sets a property on the remote object and awaits completion. |
|
|
344
|
+
| `has` | `proxy, property` | `Promise<boolean>` | Checks if a property exists on the remote object and awaits the result. |
|
|
345
|
+
| `delete` | `proxy, property` | `Promise<boolean>` | Deletes a property on the remote object and awaits the result. |
|
|
346
|
+
| `reset` | - | `void` | Clears all exposed objects. |
|
|
347
|
+
|
|
348
|
+
### MessagePortRpcEndpoint
|
|
349
|
+
|
|
350
|
+
Implementation of `RpcEndpoint` for message-passing environments.
|
|
351
|
+
|
|
352
|
+
| Method | Arguments | Returns | Description |
|
|
353
|
+
| :------------ | :----------------------------------- | :----------------------- | :------------------------------------------------------------------------------------------------------------------------- |
|
|
354
|
+
| `constructor` | `source: MessagePortRpcTransport` | `MessagePortRpcEndpoint` | Creates an endpoint. `source` can be `Worker`, `MessagePort`, `Window`, `SharedWorker`, or Node.js `Worker`/`MessagePort`. |
|
|
355
|
+
| `static from` | `transport: MessagePortRpcTransport` | `MessagePortRpcEndpoint` | Static factory method to create an endpoint. |
|
|
356
|
+
| `close` | - | `void` | Closes the endpoint and the underlying transport. |
|
|
357
|
+
|
|
358
|
+
### RpcChannel
|
|
359
|
+
|
|
360
|
+
Low-level communication channel.
|
|
361
|
+
|
|
362
|
+
| Property / Method | Returns | Description |
|
|
363
|
+
| :---------------- | :----------------------- | :---------------------------------------------- |
|
|
364
|
+
| `id` | `string` | The unique identifier for this channel. |
|
|
365
|
+
| `request$` | `Observable<RpcRequest>` | Emits incoming requests for this channel. |
|
|
366
|
+
| `message$` | `Observable<any>` | Emits incoming data messages for this channel. |
|
|
367
|
+
| `request(data)` | `Promise<any>` | Sends a request and waits for a response. |
|
|
368
|
+
| `respond(id, ok)` | `Promise<void>` | Sends a response to a specific request. |
|
|
369
|
+
| `send(data)` | `Promise<void>` | Sends a one-way data message. |
|
|
370
|
+
| `close()` | `void` | Closes the channel and stops all subscriptions. |
|
|
371
|
+
|
|
372
|
+
### ReadableStreamRpcAdapter
|
|
373
|
+
|
|
374
|
+
Adapter for transmitting `ReadableStream` objects.
|
|
375
|
+
|
|
376
|
+
| Method | Arguments | Description |
|
|
377
|
+
| :------------ | :---------------------- | :----------------------------------------------------------------------------------------------- |
|
|
378
|
+
| `constructor` | `maxChunkSize?: number` | Creates a new adapter. `maxChunkSize` controls how many items are pulled at once (default 1000). |
|
|
379
|
+
|
|
380
|
+
## 💡 Best Practices
|
|
381
|
+
|
|
382
|
+
- **Explicit Release**: While `FinalizationRegistry` provides automatic cleanup, it is non-deterministic. For resources that are frequently created and destroyed, use `Rpc.release(proxy)` to free up communication channels immediately.
|
|
383
|
+
- **Register Adapters on Both Sides**: Custom adapters (like `ReadableStreamRpcAdapter`) MUST be registered on both the exposing and the consuming side.
|
|
384
|
+
- **Prefer `Rpc.serialize` for DTOs**: If you are passing plain data objects that don't need to be interactive, use `Rpc.serialize(obj)`. This is more efficient as it avoids the overhead of creating and managing a proxy channel.
|
|
385
|
+
- **Error Handling**: Always wrap remote calls in `try...catch`. Be aware that `RpcRemoteError` preserves the original error's properties, which is useful for structured error reporting.
|
|
386
|
+
- **Type Safety**: Always provide the generic type argument to `Rpc.connect<T>(...)` to ensure full TypeScript support for the returned proxy.
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# rxjs-utils
|
|
2
|
+
|
|
3
|
+
A collection of custom RxJS operators, Observable creators, and utilities designed to enhance reactive programming workflows. This module provides tools for type safety, advanced retry strategies, timing and scheduling, progressive data emission, and integration with reactive signals.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
10
|
+
- [Type Casting](#type-casting)
|
|
11
|
+
- [Retry with Backoff](#retry-with-backoff)
|
|
12
|
+
- [Timing Observables](#timing-observables)
|
|
13
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
14
|
+
- [Progressive Array Emission](#progressive-array-emission)
|
|
15
|
+
- [Lifecycle Teardown](#lifecycle-teardown)
|
|
16
|
+
- [Signal Integration](#signal-integration)
|
|
17
|
+
- [📚 API](#-api)
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **Type Safety Operators**: Explicitly cast observable types for better TypeScript inference.
|
|
22
|
+
- **Retry Strategies**: Robust `retryBackoff` operators supporting exponential backoff and custom handling logic.
|
|
23
|
+
- **Timing Sources**: Observables wrapping `requestAnimationFrame`, `requestIdleCallback`, `setImmediate`, and `process.nextTick`.
|
|
24
|
+
- **Progressive Loading**: `slowArray` operators to emit array chunks over time (useful for rendering large lists).
|
|
25
|
+
- **Lifecycle Hooks**: `teardown` operator to access the last emitted value or error when a subscription ends.
|
|
26
|
+
- **Signal Integration**: `untrack` operator to prevent RxJS subscriptions from tracking dependencies within Signal effects.
|
|
27
|
+
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
### Robust Retries
|
|
31
|
+
|
|
32
|
+
Standard RxJS `retry` is often too aggressive (immediate retry). `retryBackoff` implements delays (constant, linear, or exponential) between retries to prevent overwhelming failing services.
|
|
33
|
+
|
|
34
|
+
### Scheduling & Timing
|
|
35
|
+
|
|
36
|
+
Instead of using `interval` or `timer` for everything, this module provides specific observables for the browser's render loop (`animationFrame$`), idle periods (`idle$`), and Node.js event loop phases (`nextTick$`, `immediate$`).
|
|
37
|
+
|
|
38
|
+
### Progressive Data
|
|
39
|
+
|
|
40
|
+
When dealing with large datasets in a UI, emitting the entire array at once can freeze the interface. `slowArray` allows you to emit an initial chunk and then progressively add more items, smoothing out the rendering process.
|
|
41
|
+
|
|
42
|
+
## 🚀 Basic Usage
|
|
43
|
+
|
|
44
|
+
### Type Casting
|
|
45
|
+
|
|
46
|
+
Use `cast` to assert a more specific type for an Observable, or `forceCast` to cast from `unknown`.
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
import { cast, forceCast } from '@tstdl/base/rxjs-utils';
|
|
50
|
+
import { of } from 'rxjs';
|
|
51
|
+
|
|
52
|
+
interface User {
|
|
53
|
+
id: string;
|
|
54
|
+
name: string;
|
|
55
|
+
}
|
|
56
|
+
interface Admin extends User {
|
|
57
|
+
permissions: string[];
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const user$ = of({ id: '1', name: 'Alice', permissions: ['root'] } as User);
|
|
61
|
+
const admin$ = of({ id: '1', name: 'Alice', permissions: ['root'] } as Admin);
|
|
62
|
+
|
|
63
|
+
// Safe upcast: Admin to User (Admin extends User)
|
|
64
|
+
const userFromAdmin$ = admin$.pipe(cast<Admin, User>());
|
|
65
|
+
|
|
66
|
+
// Force cast: unknown to User
|
|
67
|
+
const unknown$ = of<unknown>({ id: '2', name: 'Bob' });
|
|
68
|
+
const forcedUser$ = unknown$.pipe(forceCast<User>());
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Retry with Backoff
|
|
72
|
+
|
|
73
|
+
Handle temporary failures with exponential backoff.
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
import { retryBackoff } from '@tstdl/base/rxjs-utils';
|
|
77
|
+
import { HttpClient } from '@tstdl/base/http/client'; // Example http client
|
|
78
|
+
import { inject } from '@tstdl/base/injector';
|
|
79
|
+
|
|
80
|
+
const http = inject(HttpClient);
|
|
81
|
+
|
|
82
|
+
http
|
|
83
|
+
.get('https://api.example.com/data')
|
|
84
|
+
.pipe(
|
|
85
|
+
// Retry up to 3 times
|
|
86
|
+
// Initial delay 500ms, increasing exponentially
|
|
87
|
+
retryBackoff(3, {
|
|
88
|
+
strategy: 'exponential',
|
|
89
|
+
initialDelay: 500,
|
|
90
|
+
maximumDelay: 5000,
|
|
91
|
+
}),
|
|
92
|
+
)
|
|
93
|
+
.subscribe({
|
|
94
|
+
next: (response) => console.log('Success:', response),
|
|
95
|
+
error: (err) => console.error('Failed after retries:', err),
|
|
96
|
+
});
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Timing Observables
|
|
100
|
+
|
|
101
|
+
Use specific schedulers for performance-critical tasks.
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
import { animationFrame$, idle$ } from '@tstdl/base/rxjs-utils';
|
|
105
|
+
|
|
106
|
+
// Run logic on every animation frame (recursive)
|
|
107
|
+
const renderLoop$ = animationFrame$.subscribe(() => {
|
|
108
|
+
// Update canvas or DOM
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Run logic only when the browser is idle
|
|
112
|
+
const backgroundTask$ = idle$.subscribe((deadline) => {
|
|
113
|
+
console.log('Browser is idle, time remaining:', deadline.timeRemaining());
|
|
114
|
+
});
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## 🔧 Advanced Topics
|
|
118
|
+
|
|
119
|
+
### Progressive Array Emission
|
|
120
|
+
|
|
121
|
+
Use `slowArray` to emit a large array in chunks. This is useful for "infinite scroll" behavior or rendering large lists without blocking the main thread.
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { slowArray } from '@tstdl/base/rxjs-utils';
|
|
125
|
+
|
|
126
|
+
const largeData = Array.from({ length: 1000 }, (_, i) => i);
|
|
127
|
+
|
|
128
|
+
slowArray(largeData, {
|
|
129
|
+
delay: 0, // Start immediately
|
|
130
|
+
initialSize: 20, // Emit first 20 items immediately
|
|
131
|
+
interval: 50, // Add more items every 50ms
|
|
132
|
+
size: 10, // Add 10 items per interval
|
|
133
|
+
}).subscribe((currentArray) => {
|
|
134
|
+
// currentArray grows: [0..19] -> [0..29] -> [0..39] ...
|
|
135
|
+
console.log(`Rendered ${currentArray.length} items`);
|
|
136
|
+
});
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
To handle a stream of incoming arrays (e.g., from a search result that updates), use the `persistentSlowArray` operator. It maintains the current "chunk size" even when the underlying source array changes.
|
|
140
|
+
|
|
141
|
+
```ts
|
|
142
|
+
import { persistentSlowArray } from '@tstdl/base/rxjs-utils';
|
|
143
|
+
import { fromEvent, map, debounceTime } from 'rxjs';
|
|
144
|
+
|
|
145
|
+
// Imagine a search input emitting new results
|
|
146
|
+
const searchResults$ = fromEvent(input, 'input').pipe(
|
|
147
|
+
debounceTime(300),
|
|
148
|
+
switchMap(q => fetchResults(q)), // returns Observable<Item[]>
|
|
149
|
+
persistentSlowArray({
|
|
150
|
+
interval: 100,
|
|
151
|
+
size: 5,
|
|
152
|
+
initialSize: 10
|
|
153
|
+
})
|
|
154
|
+
);
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Lazy Initial Values
|
|
158
|
+
|
|
159
|
+
Use `startWithProvider` when you need to emit an initial value that must be calculated at the exact moment of subscription (e.g., a timestamp or a value from another service).
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { startWithProvider } from '@tstdl/base/rxjs-utils';
|
|
163
|
+
import { of } from 'rxjs';
|
|
164
|
+
|
|
165
|
+
const source$ = of('event 1', 'event 2').pipe(
|
|
166
|
+
startWithProvider(() => `Started at ${new Date().toISOString()}`)
|
|
167
|
+
);
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Lifecycle Teardown
|
|
171
|
+
|
|
172
|
+
The `teardown` operator allows you to execute logic when the stream ends (completes, errors, or unsubscribes), with access to the last emitted value and the error (if any).
|
|
173
|
+
|
|
174
|
+
```ts
|
|
175
|
+
import { teardown } from '@tstdl/base/rxjs-utils';
|
|
176
|
+
import { interval } from 'rxjs';
|
|
177
|
+
|
|
178
|
+
const sub = interval(1000)
|
|
179
|
+
.pipe(
|
|
180
|
+
teardown((lastValue, error) => {
|
|
181
|
+
if (error) {
|
|
182
|
+
console.error('Stream crashed at:', lastValue, error);
|
|
183
|
+
} else {
|
|
184
|
+
console.log('Stream cleaned up. Last value was:', lastValue);
|
|
185
|
+
}
|
|
186
|
+
}),
|
|
187
|
+
)
|
|
188
|
+
.subscribe();
|
|
189
|
+
|
|
190
|
+
setTimeout(() => sub.unsubscribe(), 2500);
|
|
191
|
+
// Output after 2.5s: "Stream cleaned up. Last value was: 1"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Signal Integration
|
|
195
|
+
|
|
196
|
+
When using RxJS inside a reactive context (like `@tstdl/base/signals` effects), subscriptions might accidentally become dependencies. `untrack` wraps the subscription callbacks to prevent this.
|
|
197
|
+
|
|
198
|
+
```ts
|
|
199
|
+
import { untrack } from '@tstdl/base/rxjs-utils';
|
|
200
|
+
import { effect } from '@tstdl/base/signals';
|
|
201
|
+
import { interval } from 'rxjs';
|
|
202
|
+
|
|
203
|
+
effect(() => {
|
|
204
|
+
// This subscription will NOT cause the effect to re-run
|
|
205
|
+
// when the observable emits, because the subscriber logic is untracked.
|
|
206
|
+
const sub = interval(1000)
|
|
207
|
+
.pipe(untrack())
|
|
208
|
+
.subscribe((val) => {
|
|
209
|
+
console.log(val);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
return () => sub.unsubscribe();
|
|
213
|
+
});
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## 📚 API
|
|
217
|
+
|
|
218
|
+
### Operators
|
|
219
|
+
|
|
220
|
+
| Function | Description |
|
|
221
|
+
| :------------------------------------------------ | :-------------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| `cast<Input, Output>()` | Casts an Observable from `Input` type to `Output` type (checked). |
|
|
223
|
+
| `forceCast<Output>()` | Casts an Observable from `unknown` to `Output` type. |
|
|
224
|
+
| `noopOperator<T>()` | An operator that does nothing and passes values through unchanged. |
|
|
225
|
+
| `rejectErrors<T>()` | Suppresses errors from the source observable (stops emission on error without notifying downstream). |
|
|
226
|
+
| `retryBackoff<T>(count, options)` | Retries the source observable on error with a specified backoff strategy. |
|
|
227
|
+
| `retryBackoffHandled<T>(count, options, handler)` | Similar to `retryBackoff`, but allows a custom handler function to determine behavior or side effects on retry. |
|
|
228
|
+
| `persistentSlowArray<T>(options)` | Operator version of `slowArray`. Emits growing arrays from a source array emission. |
|
|
229
|
+
| `startWithProvider<T, D>(provider)` | Like `startWith`, but accepts a function to lazily generate the initial value. |
|
|
230
|
+
| `teardown<T>(fn)` | Registers a callback to run on finalization, receiving the last value and error. |
|
|
231
|
+
| `untrack<T>()` | Wraps subscription handlers in `untracked()` to prevent signal dependency tracking. |
|
|
232
|
+
|
|
233
|
+
### Observable Creators
|
|
234
|
+
|
|
235
|
+
| Function | Description |
|
|
236
|
+
| :----------------------------- | :------------------------------------------------------------------------------- |
|
|
237
|
+
| `noop<T>(source)` | Returns the source observable as is. |
|
|
238
|
+
| `slowArray<T>(array, options)` | Creates an observable that emits the provided array in growing chunks over time. |
|
|
239
|
+
| `singleIdle$` | Emits once when the browser is idle (via `requestIdleCallback`). |
|
|
240
|
+
| `idle$` | Emits repeatedly when the browser is idle. |
|
|
241
|
+
| `singleAnimationFrame$` | Emits once on the next animation frame. |
|
|
242
|
+
| `animationFrame$` | Emits repeatedly on every animation frame. |
|
|
243
|
+
| `singleImmediate$` | Emits once via `setImmediate` (Node.js/Polyfill). |
|
|
244
|
+
| `immediate$` | Emits repeatedly via `setImmediate`. |
|
|
245
|
+
| `microtask$` | Emits once via `queueMicrotask`. |
|
|
246
|
+
| `singleNextTick$` | Emits once via `process.nextTick`. |
|
|
247
|
+
| `nextTick$` | Emits repeatedly via `process.nextTick`. |
|
|
248
|
+
|
|
249
|
+
### Types
|
|
250
|
+
|
|
251
|
+
| Type | Description |
|
|
252
|
+
| :----------------- | :------------------------------------------------------------------------------------------------------------ |
|
|
253
|
+
| `SlowArrayOptions` | Configuration for `slowArray` (`delay`, `initialSize`, `interval`, `size`). |
|
|
254
|
+
| `BackoffOptions` | Configuration for retry backoff (`strategy`, `initialDelay`, `maximumDelay`, `increase`, `jitter`). |
|
|
255
|
+
| `BackoffStrategy` | Either `'linear'` or `'exponential'`. |
|
|
256
|
+
| `IdleDeadline` | The object passed to `idle$` subscribers, containing `timeRemaining()` and `didTimeout`. (native JS type) |
|
|
257
|
+
|
|
258
|
+
## 🏁 Platform Compatibility
|
|
259
|
+
|
|
260
|
+
- **Browser**: Full support for all operators and observables, including `animationFrame$` and `idle$`.
|
|
261
|
+
- **Node.js**: Full support, though `animationFrame$` and `idle$` require polyfills if used. `nextTick$` and `immediate$` are native to Node.js.
|
|
262
|
+
- **Workers**: Support varies depending on the availability of `requestAnimationFrame` and `requestIdleCallback`.
|