@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
|
@@ -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.
|
package/sse/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# @tstdl/base/sse
|
|
2
|
+
|
|
3
|
+
A comprehensive module for Server-Sent Events (SSE) that provides both a low-level reactive client for discrete events and a high-level, delta-capable data streaming abstraction for synchronizing complex objects efficiently.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [High-Level: Data Streaming](#high-level-data-streaming)
|
|
10
|
+
- [Low-Level: Event Pushing](#low-level-event-pushing)
|
|
11
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
12
|
+
- [Server-Side Data Streaming](#server-side-data-streaming)
|
|
13
|
+
- [Client-Side Data Consumption](#client-side-data-consumption)
|
|
14
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
15
|
+
- [Low-Level Event Pushing](#low-level-event-pushing-1)
|
|
16
|
+
- [Handling Errors](#handling-errors)
|
|
17
|
+
- [📚 API](#-api)
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **Isomorphic Design**: Type-safe utilities for both server (Node.js/Edge) and client (Browser) environments.
|
|
22
|
+
- **Smart Data Synchronization**: `DataStreamSource` automatically calculates JSON diffs (deltas) between updates to minimize bandwidth usage. For optimal array synchronization, ensure your objects have an `id` property.
|
|
23
|
+
- **Automatic State Reconstruction**: The client-side `DataStream` utility automatically patches the local state with incoming deltas, exposing a seamless RxJS Observable of the full object.
|
|
24
|
+
- **Reactive Client**: A robust RxJS wrapper around the native `EventSource` API (`ServerSentEvents`) for handling connection states and events.
|
|
25
|
+
- **Web Streams Support**: Server-side implementation uses standard `ReadableStream`, making it compatible with modern runtimes.
|
|
26
|
+
- **Full SSE Specification**: Supports named events, custom IDs, retry intervals, and comments.
|
|
27
|
+
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
### High-Level: Data Streaming
|
|
31
|
+
|
|
32
|
+
The **Data Streaming** abstraction is designed for synchronizing a single, potentially complex state object between the server and client.
|
|
33
|
+
|
|
34
|
+
- **Server (`DataStreamSource`)**: You simply pass the full object to the source whenever it changes. The source uses `jsondiffpatch` to calculate the difference between the current and previous object. It sends the full object initially, and then only the small deltas for subsequent updates.
|
|
35
|
+
- **Client (`DataStream`)**: Consumes the stream, listening for `data` (full snapshot) and `delta` (patch) events. It maintains the local state by applying patches automatically and emits the fully updated object to subscribers.
|
|
36
|
+
|
|
37
|
+
### Low-Level: Event Pushing
|
|
38
|
+
|
|
39
|
+
The **Event Pushing** layer provides direct control over the SSE protocol, suitable for sending discrete notifications (e.g., "OrderShipped", "NewMessage").
|
|
40
|
+
|
|
41
|
+
- **Server (`ServerSentEventsSource`)**: A wrapper around a `TransformStream` that formats messages according to the SSE spec.
|
|
42
|
+
- **Client (`ServerSentEvents`)**: Wraps the browser's `EventSource` to provide RxJS Observables for messages, connection status, and errors.
|
|
43
|
+
|
|
44
|
+
## 🚀 Basic Usage
|
|
45
|
+
|
|
46
|
+
### Server-Side Data Streaming
|
|
47
|
+
|
|
48
|
+
Use `DataStreamSource` to stream state changes. The most convenient way is `DataStreamSource.fromIterable`.
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { apiController, type ApiServerResult } from '@tstdl/base/api/server';
|
|
52
|
+
import { defineApi } from '@tstdl/base/api';
|
|
53
|
+
import { DataStream, DataStreamSource } from '@tstdl/base/sse';
|
|
54
|
+
import { CancellationSignal } from '@tstdl/base/cancellation';
|
|
55
|
+
import { inject } from '@tstdl/base/injector';
|
|
56
|
+
import { timeout } from '@tstdl/base/utils/timing';
|
|
57
|
+
import { object, string, number } from '@tstdl/base/schema';
|
|
58
|
+
|
|
59
|
+
// 1. Define the data shape
|
|
60
|
+
const StockPrice = object({
|
|
61
|
+
symbol: string(),
|
|
62
|
+
price: number(),
|
|
63
|
+
timestamp: number(),
|
|
64
|
+
});
|
|
65
|
+
type StockPrice = typeof StockPrice.T;
|
|
66
|
+
|
|
67
|
+
// 2. Define the API
|
|
68
|
+
const stockApi = defineApi({
|
|
69
|
+
resource: 'stocks',
|
|
70
|
+
endpoints: {
|
|
71
|
+
track: {
|
|
72
|
+
resource: ':symbol/stream',
|
|
73
|
+
method: 'GET',
|
|
74
|
+
parameters: object({ symbol: string() }),
|
|
75
|
+
result: DataStream<StockPrice>, // Type hint for the client
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// 3. Implement the Controller
|
|
81
|
+
@apiController(stockApi)
|
|
82
|
+
class StockApiController {
|
|
83
|
+
#cancellationSignal = inject(CancellationSignal);
|
|
84
|
+
|
|
85
|
+
track({ parameters }: any): ApiServerResult<typeof stockApi, 'track'> {
|
|
86
|
+
// Create an async generator that yields data updates
|
|
87
|
+
const ticker = this.createTicker(parameters.symbol, this.#cancellationSignal);
|
|
88
|
+
|
|
89
|
+
// Create a source that automatically handles diffing and serialization
|
|
90
|
+
return DataStreamSource.fromIterable(ticker, { delta: true });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async *createTicker(symbol: string, signal: CancellationSignal): AsyncIterable<StockPrice> {
|
|
94
|
+
let price = 100;
|
|
95
|
+
while (signal.isUnset) {
|
|
96
|
+
price += (Math.random() - 0.5) * 2;
|
|
97
|
+
yield { symbol, price, timestamp: Date.now() };
|
|
98
|
+
await timeout(1000, signal);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Client-Side Data Consumption
|
|
105
|
+
|
|
106
|
+
Use `DataStream.parse` to transform the raw SSE connection into a stream of your data objects.
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
import { DataStream, ServerSentEvents } from '@tstdl/base/sse';
|
|
110
|
+
import { type Observable } from 'rxjs';
|
|
111
|
+
|
|
112
|
+
type StockPrice = {
|
|
113
|
+
symbol: string;
|
|
114
|
+
price: number;
|
|
115
|
+
timestamp: number;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// 1. Establish the connection
|
|
119
|
+
const sse = new ServerSentEvents('/api/stocks/AAPL/stream');
|
|
120
|
+
|
|
121
|
+
// 2. Parse the data stream
|
|
122
|
+
// This handles the initial full load and all subsequent delta patches automatically
|
|
123
|
+
const stock$: Observable<StockPrice> = DataStream.parse<StockPrice>(sse);
|
|
124
|
+
|
|
125
|
+
// 3. Subscribe to updates
|
|
126
|
+
const subscription = stock$.subscribe({
|
|
127
|
+
next: (stock) => {
|
|
128
|
+
console.log(`Update for ${stock.symbol}: $${stock.price.toFixed(2)}`);
|
|
129
|
+
},
|
|
130
|
+
error: (err) => console.error('Stream error:', err),
|
|
131
|
+
complete: () => console.log('Stream closed'),
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// Cleanup when done
|
|
135
|
+
// subscription.unsubscribe();
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## 🔧 Advanced Topics
|
|
139
|
+
|
|
140
|
+
### Low-Level Event Pushing
|
|
141
|
+
|
|
142
|
+
If you don't need object synchronization and just want to send named events, use `ServerSentEventsSource` directly.
|
|
143
|
+
|
|
144
|
+
**Server:**
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { ServerSentEventsSource } from '@tstdl/base/sse';
|
|
148
|
+
import { timeout } from '@tstdl/base/utils/timing';
|
|
149
|
+
|
|
150
|
+
async function notificationStream(): Promise<ServerSentEventsSource> {
|
|
151
|
+
const source = new ServerSentEventsSource();
|
|
152
|
+
|
|
153
|
+
// Run in background
|
|
154
|
+
(async () => {
|
|
155
|
+
await timeout(1000);
|
|
156
|
+
if (source.closed()) return;
|
|
157
|
+
|
|
158
|
+
// Send a named JSON event
|
|
159
|
+
await source.sendJson({
|
|
160
|
+
name: 'notification',
|
|
161
|
+
id: '1',
|
|
162
|
+
data: { message: 'Hello World' },
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
await timeout(2000);
|
|
166
|
+
if (source.closed()) return;
|
|
167
|
+
|
|
168
|
+
// Send a text event
|
|
169
|
+
await source.sendText({
|
|
170
|
+
name: 'ping',
|
|
171
|
+
data: 'keep-alive',
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
await source.close();
|
|
175
|
+
})();
|
|
176
|
+
|
|
177
|
+
return source;
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
**Client:**
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { ServerSentEvents } from '@tstdl/base/sse';
|
|
185
|
+
import { filter, map } from 'rxjs';
|
|
186
|
+
|
|
187
|
+
const sse = new ServerSentEvents('/api/notifications');
|
|
188
|
+
|
|
189
|
+
// Listen specifically for 'notification' events
|
|
190
|
+
const notifications$ = sse.message$('notification').pipe(map((event) => JSON.parse(event.data)));
|
|
191
|
+
|
|
192
|
+
notifications$.subscribe((data) => console.log('New notification:', data));
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Handling Errors
|
|
196
|
+
|
|
197
|
+
The `DataStreamSource` has a built-in mechanism to send errors to the client before closing the stream.
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
// Server
|
|
201
|
+
const source = new DataStreamSource();
|
|
202
|
+
try {
|
|
203
|
+
// ... logic
|
|
204
|
+
} catch (err) {
|
|
205
|
+
// Sends an 'error' event with the formatted error and closes the connection
|
|
206
|
+
await source.error(err);
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
On the client side, `DataStream.parse` will listen for these `error` events and error out the Observable stream accordingly.
|
|
211
|
+
|
|
212
|
+
## 📚 API
|
|
213
|
+
|
|
214
|
+
### `DataStreamSource<T>`
|
|
215
|
+
|
|
216
|
+
Server-side class for streaming data objects with optional delta compression.
|
|
217
|
+
|
|
218
|
+
| Member | Signature | Description |
|
|
219
|
+
| :------------- | :----------------------------------------------------------- | :---------------------------------------------------------------------------------- |
|
|
220
|
+
| `constructor` | `(options?: DataStreamSourceOptions)` | Creates a new source. Options include `delta` (boolean) and `errorFormatter`. |
|
|
221
|
+
| `fromIterable` | `static fromIterable<T>(iterable: AnyIterable<T>, options?)` | Creates a source that automatically pulls from an async iterable and sends updates. |
|
|
222
|
+
| `send` | `send(data: T): Promise<void>` | Sends a data update. If `delta` is enabled, calculates diff from previous data. |
|
|
223
|
+
| `error` | `error(error: unknown): Promise<void>` | Sends an error event to the client and closes the stream. |
|
|
224
|
+
| `close` | `close(): Promise<void>` | Closes the underlying stream. |
|
|
225
|
+
| `closed` | `ReadonlySignal<boolean>` | Signal indicating if the stream is closed. |
|
|
226
|
+
| `eventSource` | `ServerSentEventsSource` | Access to the underlying SSE source. |
|
|
227
|
+
|
|
228
|
+
### `DataStream<T>`
|
|
229
|
+
|
|
230
|
+
Client-side utility for reconstructing data streams.
|
|
231
|
+
|
|
232
|
+
| Member | Signature | Description |
|
|
233
|
+
| :------ | :-------------------------------------------------------------- | :----------------------------------------------------------------------------------- |
|
|
234
|
+
| `parse` | `static parse<T>(eventSource: ServerSentEvents): Observable<T>` | Transforms an SSE connection into an Observable of the synchronized data object `T`. |
|
|
235
|
+
|
|
236
|
+
### `ServerSentEventsSource`
|
|
237
|
+
|
|
238
|
+
Low-level server-side SSE writer.
|
|
239
|
+
|
|
240
|
+
| Member | Signature | Description |
|
|
241
|
+
| :------------ | :---------------------------------------------------- | :------------------------------------------------------------------------ |
|
|
242
|
+
| `readable` | `ReadableStream<string>` | The Web Stream to return in the HTTP response body. |
|
|
243
|
+
| `sendJson` | `sendJson(event: ServerSentJsonEvent): Promise<void>` | Sends a named event with JSON data. |
|
|
244
|
+
| `sendText` | `sendText(event: ServerSentTextEvent): Promise<void>` | Sends a named event with raw text data. |
|
|
245
|
+
| `sendComment` | `sendComment(comment: string): Promise<void>` | Sends an SSE comment (ignored by browser clients, useful for keep-alive). |
|
|
246
|
+
| `close` | `close(): Promise<void>` | Closes the stream. |
|
|
247
|
+
| `closed` | `ReadonlySignal<boolean>` | Signal indicating if the stream is closed. |
|
|
248
|
+
| `error` | `ReadonlySignal<Error \| undefined>` | Signal containing the error if the stream failed. |
|
|
249
|
+
|
|
250
|
+
### `ServerSentEvents`
|
|
251
|
+
|
|
252
|
+
Client-side reactive wrapper for `EventSource`.
|
|
253
|
+
|
|
254
|
+
| Member | Signature | Description |
|
|
255
|
+
| :-------------- | :--------------------------------------------------- | :------------------------------------------------------------------------------------- |
|
|
256
|
+
| `constructor` | `(url: string, options?: EventSourceInit)` | Initiates the connection. |
|
|
257
|
+
| `message$` | `message$(event?: string): Observable<MessageEvent>` | Returns an Observable of messages. If `event` is provided, filters by that event name. |
|
|
258
|
+
| `state$` | `Observable<ServerSentEventsState>` | Emits connection state changes (`Connecting`, `Open`, `Closed`). |
|
|
259
|
+
| `open$` | `Observable<void>` | Emits when the connection is open. |
|
|
260
|
+
| `close$` | `Observable<void>` | Emits when the connection is closed. |
|
|
261
|
+
| `error$` | `Observable<Event>` | Emits on connection errors. |
|
|
262
|
+
| `isOpen$` | `Observable<boolean>` | Emits `true` when connected. |
|
|
263
|
+
| `isConnecting$` | `Observable<boolean>` | Emits `true` when connecting. |
|
|
264
|
+
| `isClosed$` | `Observable<boolean>` | Emits `true` when closed. |
|
|
265
|
+
| `state` | `ServerSentEventsState` | Gets the current connection state. |
|
|
266
|
+
| `close` | `close(): void` | Manually closes the connection. |
|
|
267
|
+
|
|
268
|
+
### Types
|
|
269
|
+
|
|
270
|
+
| Type | Description |
|
|
271
|
+
| :-------------------------- | :-------------------------------------------------------------------------------- |
|
|
272
|
+
| `DataStreamSourceOptions` | Options for `DataStreamSource`: `delta` (boolean) and `errorFormatter` (function). |
|
|
273
|
+
| `DataStreamErrorFormatter` | Function type: `(error: unknown) => UndefinableJson`. |
|
|
274
|
+
| `ServerSentEventBase<Data>` | Base interface for events with `name`, `data`, `id`, and `retry`. |
|
|
275
|
+
| `ServerSentJsonEvent` | Event where `data` is any JSON-serializable value. |
|
|
276
|
+
| `ServerSentTextEvent` | Event where `data` is a string. |
|
|
277
|
+
| `ServerSentEvent` | Union of `ServerSentTextEvent` and `ServerSentJsonEvent`. |
|
|
278
|
+
| `ServerSentEventsState` | Enum: `Connecting` (0), `Open` (1), `Closed` (2). |
|