@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/memory/README.md
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# @tstdl/base/memory
|
|
2
|
+
|
|
3
|
+
Utilities for advanced memory management and garbage collection observation in TypeScript. This module provides enhanced wrappers around the native `FinalizationRegistry` API, enabling multiple cleanup handlers per object and reactive garbage collection events via RxJS.
|
|
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
|
+
- [Multiple Handlers](#multiple-handlers)
|
|
12
|
+
- [Observable Finalization Registry](#observable-finalization-registry)
|
|
13
|
+
- [Unregistering Handlers](#unregistering-handlers)
|
|
14
|
+
- [📚 API](#-api)
|
|
15
|
+
|
|
16
|
+
## ✨ Features
|
|
17
|
+
|
|
18
|
+
- **Multiple Finalization Handlers**: Attach multiple independent cleanup callbacks to a single object instance without conflicts.
|
|
19
|
+
- **Reactive GC Events**: Use `ObservableFinalizationRegistry` to receive RxJS notifications when objects are garbage collected.
|
|
20
|
+
- **Type-Safe Context**: Pass strongly-typed data to your finalization handlers to aid in cleanup (e.g., closing handles, logging IDs).
|
|
21
|
+
- **Abstraction**: Hides the complexity of managing `FinalizationRegistry` instances and tokens.
|
|
22
|
+
|
|
23
|
+
## Core Concepts
|
|
24
|
+
|
|
25
|
+
JavaScript's `FinalizationRegistry` allows you to request a callback when an object is garbage collected. However, managing these registries can be cumbersome, especially if different parts of your application need to attach different cleanup logic to the same object.
|
|
26
|
+
|
|
27
|
+
The **Memory** module solves this by:
|
|
28
|
+
|
|
29
|
+
1. **Centralizing Registry Management**: `registerFinalization` uses a shared internal registry and a mapping system to allow multiple handlers for one object.
|
|
30
|
+
2. **Bridging to RxJS**: `ObservableFinalizationRegistry` turns the callback-based API into a stream-based API, making it easier to integrate with reactive architectures.
|
|
31
|
+
|
|
32
|
+
> **Note**: Garbage collection is non-deterministic. There is no guarantee _when_ or _if_ the cleanup handlers will run. These tools should be used for auxiliary cleanup (like releasing external resources or debugging leaks), not for critical program logic.
|
|
33
|
+
|
|
34
|
+
## 🚀 Basic Usage
|
|
35
|
+
|
|
36
|
+
The most common use case is attaching a cleanup function to an object. This function will run after the object has been garbage collected.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { registerFinalization } from '@tstdl/base/memory';
|
|
40
|
+
|
|
41
|
+
class Resource {
|
|
42
|
+
id: number;
|
|
43
|
+
|
|
44
|
+
constructor(id: number) {
|
|
45
|
+
this.id = id;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 1. Create an object
|
|
50
|
+
let myResource: Resource | null = new Resource(123);
|
|
51
|
+
|
|
52
|
+
// 2. Register a cleanup handler
|
|
53
|
+
// We pass the ID as data because we can't access 'myResource' inside the handler
|
|
54
|
+
// (it will be gone by then).
|
|
55
|
+
registerFinalization(
|
|
56
|
+
myResource,
|
|
57
|
+
(id) => {
|
|
58
|
+
console.log(`Resource with ID ${id} was garbage collected.`);
|
|
59
|
+
},
|
|
60
|
+
123,
|
|
61
|
+
);
|
|
62
|
+
|
|
63
|
+
// 3. Dereference the object to make it eligible for GC
|
|
64
|
+
myResource = null;
|
|
65
|
+
|
|
66
|
+
// ... Sometime later, when GC runs:
|
|
67
|
+
// Output: "Resource with ID 123 was garbage collected."
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
> **Warning**: Never pass the object being registered as the `data` payload, nor any object that holds a strong reference to it. Doing so will create a strong reference cycle and prevent the object from ever being garbage collected.
|
|
71
|
+
|
|
72
|
+
## 🔧 Advanced Topics
|
|
73
|
+
|
|
74
|
+
### Multiple Handlers
|
|
75
|
+
|
|
76
|
+
Unlike the native `FinalizationRegistry`, which only allows one "held value" per registration, `registerFinalization` allows you to attach any number of handlers to the same object.
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { registerFinalization } from '@tstdl/base/memory';
|
|
80
|
+
|
|
81
|
+
const obj = { name: 'MultiHandler' };
|
|
82
|
+
|
|
83
|
+
registerFinalization(obj, () => console.log('Handler 1: Cleanup successful.'));
|
|
84
|
+
registerFinalization(obj, () => console.log('Handler 2: Notifying external system.'));
|
|
85
|
+
|
|
86
|
+
// Both handlers will run independently when 'obj' is collected.
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Observable Finalization Registry
|
|
90
|
+
|
|
91
|
+
If you prefer working with RxJS Observables, `ObservableFinalizationRegistry` emits values to a stream whenever a registered object is reclaimed.
|
|
92
|
+
|
|
93
|
+
```ts
|
|
94
|
+
import { ObservableFinalizationRegistry } from '@tstdl/base/memory';
|
|
95
|
+
|
|
96
|
+
// Create a registry that expects a string token
|
|
97
|
+
const registry = new ObservableFinalizationRegistry<string>();
|
|
98
|
+
|
|
99
|
+
// Subscribe to the stream
|
|
100
|
+
const subscription = registry.finalize$.subscribe((token) => {
|
|
101
|
+
console.log(`Object associated with token "${token}" was collected.`);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
// Register an object
|
|
105
|
+
let tempObject: object | null = {};
|
|
106
|
+
registry.register(tempObject, 'unique-token-abc');
|
|
107
|
+
|
|
108
|
+
// Dereference
|
|
109
|
+
tempObject = null;
|
|
110
|
+
|
|
111
|
+
// ... Sometime later:
|
|
112
|
+
// Output: "Object associated with token "unique-token-abc" was collected."
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Unregistering Handlers
|
|
116
|
+
|
|
117
|
+
You can remove a specific cleanup handler if it is no longer needed (e.g., if the resource was manually closed).
|
|
118
|
+
|
|
119
|
+
```ts
|
|
120
|
+
import { registerFinalization, unregisterFinalization } from '@tstdl/base/memory';
|
|
121
|
+
|
|
122
|
+
const data = { value: 'important' };
|
|
123
|
+
|
|
124
|
+
const cleanup = () => {
|
|
125
|
+
console.log('Cleanup data');
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// Register
|
|
129
|
+
registerFinalization(data, cleanup);
|
|
130
|
+
|
|
131
|
+
// Unregister specifically this handler
|
|
132
|
+
unregisterFinalization(data, cleanup);
|
|
133
|
+
|
|
134
|
+
// Even if 'data' is garbage collected now, 'cleanup' will NOT run.
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## 📚 API
|
|
138
|
+
|
|
139
|
+
| Export | Type | Description |
|
|
140
|
+
| :------------------------------- | :------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
141
|
+
| `registerFinalization` | Function | Registers a callback to be invoked after the target object is garbage collected. Supports multiple handlers and a data payload. |
|
|
142
|
+
| `unregisterFinalization` | Function | Unregisters a specific callback for a target object without affecting other registered handlers for the same object. |
|
|
143
|
+
| `ObservableFinalizationRegistry` | Class | Extends native `FinalizationRegistry`. Exposes a `finalize$` Observable that emits the `heldValue` when an object is collected. |
|
|
144
|
+
| `FinalizationHandler<D>` | Type | Type definition for the callback function: `(data: D) => any`. `D` defaults to `any` and is inferred when using `registerFinalization`. |
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
# Message Bus
|
|
2
|
+
|
|
3
|
+
A flexible, RxJS-based message bus module for decoupled in-process and cross-context communication, designed for seamless integration with dependency injection.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [Implementations](#implementations)
|
|
10
|
+
- [Message Observables](#message-observables)
|
|
11
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
12
|
+
- [1. Configuration](#1-configuration)
|
|
13
|
+
- [2. Injection & Usage](#2-injection--usage)
|
|
14
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
15
|
+
- [Cross-Context Communication (BroadcastChannel)](#cross-context-communication-broadcastchannel)
|
|
16
|
+
- [Fire and Forget](#fire-and-forget)
|
|
17
|
+
- [Resource Management](#resource-management)
|
|
18
|
+
- [📚 API](#-api)
|
|
19
|
+
|
|
20
|
+
## ✨ Features
|
|
21
|
+
|
|
22
|
+
- **Decoupled Communication**: Enables components to communicate via named channels without direct references.
|
|
23
|
+
- **Reactive Architecture**: Built on **RxJS**, providing powerful operators for filtering and transforming message streams.
|
|
24
|
+
- **Dual Modes & Cross-Platform**:
|
|
25
|
+
- **Local**: High-performance in-memory communication using RxJS Subjects.
|
|
26
|
+
- **BroadcastChannel**: Cross-context communication (tabs, windows, workers) using the standard Web API.
|
|
27
|
+
- **DI Integration**: Implements `Resolvable`, allowing direct injection with channel names as arguments.
|
|
28
|
+
- **Type Safety**: Fully supports generic types for message payloads.
|
|
29
|
+
- **Smart Filtering**: Distinguishes between messages from _other_ instances and _all_ messages (including self).
|
|
30
|
+
- **Memory Safe**: Implements `AsyncDisposable` for proper cleanup of subscriptions and channels. Uses `WeakRefMap` internally to prevent memory leaks on unused channels.
|
|
31
|
+
|
|
32
|
+
## Core Concepts
|
|
33
|
+
|
|
34
|
+
### Implementations
|
|
35
|
+
|
|
36
|
+
The module provides two strategies, selectable via configuration at application startup:
|
|
37
|
+
|
|
38
|
+
1. **`LocalMessageBus`**:
|
|
39
|
+
- **Scope**: Single JavaScript environment (e.g., one browser tab, one Node.js process).
|
|
40
|
+
- **Mechanism**: Shared RxJS `Subject`.
|
|
41
|
+
- **Use Case**: Component-to-component communication, global event bus within an app.
|
|
42
|
+
|
|
43
|
+
2. **`BroadcastChannelMessageBus`**:
|
|
44
|
+
- **Scope**: Same-origin browsing contexts (e.g., multiple tabs, iframes, workers).
|
|
45
|
+
- **Mechanism**: Wraps the `BroadcastChannel` API.
|
|
46
|
+
- **Use Case**: Synchronizing state across tabs (e.g., logout in one tab affects all others).
|
|
47
|
+
|
|
48
|
+
### Message Observables
|
|
49
|
+
|
|
50
|
+
Every `MessageBus` instance exposes two streams to handle different scenarios:
|
|
51
|
+
|
|
52
|
+
- **`messages$`**: Emits messages published by **other** instances on the same channel. It filters out messages sent by _this_ specific instance. Use this to react to external events.
|
|
53
|
+
- **`allMessages$`**: Emits **every** message on the channel, including those published by _this_ instance. Use this for logging, global state updates, or UI synchronization that must reflect local actions immediately.
|
|
54
|
+
|
|
55
|
+
## 🚀 Basic Usage
|
|
56
|
+
|
|
57
|
+
### 1. Configuration
|
|
58
|
+
|
|
59
|
+
Register the desired implementation provider in your application bootstrap.
|
|
60
|
+
|
|
61
|
+
**For in-process communication (Local):**
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { configureLocalMessageBus } from '@tstdl/base/message-bus';
|
|
65
|
+
|
|
66
|
+
// In your bootstrap/main file
|
|
67
|
+
configureLocalMessageBus();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 2. Injection & Usage
|
|
71
|
+
|
|
72
|
+
Inject the `MessageBus` by passing the channel name as the argument.
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
import { inject } from '@tstdl/base/injector';
|
|
76
|
+
import { MessageBus } from '@tstdl/base/message-bus';
|
|
77
|
+
|
|
78
|
+
// 1. Define your message type
|
|
79
|
+
type UserCreatedEvent = { id: string; email: string };
|
|
80
|
+
|
|
81
|
+
export class UserService {
|
|
82
|
+
// 2. Inject the bus for a specific channel ('user-events')
|
|
83
|
+
private readonly userBus = inject(MessageBus<UserCreatedEvent>, 'user-events');
|
|
84
|
+
|
|
85
|
+
async createUser(email: string) {
|
|
86
|
+
const id = '123';
|
|
87
|
+
// ... creation logic ...
|
|
88
|
+
|
|
89
|
+
// 3. Publish a message
|
|
90
|
+
await this.userBus.publish({ id, email });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export class NotificationService {
|
|
95
|
+
private readonly userBus = inject(MessageBus<UserCreatedEvent>, 'user-events');
|
|
96
|
+
|
|
97
|
+
constructor() {
|
|
98
|
+
// 4. Subscribe to messages
|
|
99
|
+
this.userBus.messages$.subscribe((event) => {
|
|
100
|
+
console.log(`New user created: ${event.email}`);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### 3. Alternative: Manual Resolution
|
|
107
|
+
|
|
108
|
+
If you're not using dependency injection for a specific class or need to resolve a channel dynamically, use the `MessageBusProvider`.
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
import { inject } from '@tstdl/base/injector';
|
|
112
|
+
import { MessageBusProvider } from '@tstdl/base/message-bus';
|
|
113
|
+
|
|
114
|
+
const provider = inject(MessageBusProvider);
|
|
115
|
+
const bus = provider.get<string>('dynamic-channel');
|
|
116
|
+
|
|
117
|
+
await bus.publish('Hello world');
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## 🔧 Advanced Topics
|
|
121
|
+
|
|
122
|
+
### Reactive Patterns
|
|
123
|
+
|
|
124
|
+
Since `MessageBus` is built on RxJS, you can use any operators to handle your message streams.
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
import { filter, bufferTime } from 'rxjs';
|
|
128
|
+
|
|
129
|
+
// Only handle specific events
|
|
130
|
+
this.userBus.messages$.pipe(
|
|
131
|
+
filter(event => event.email.endsWith('@example.com'))
|
|
132
|
+
).subscribe(event => { ... });
|
|
133
|
+
|
|
134
|
+
// Batch events for processing
|
|
135
|
+
this.userBus.messages$.pipe(
|
|
136
|
+
bufferTime(1000),
|
|
137
|
+
filter(batch => batch.length > 0)
|
|
138
|
+
).subscribe(batch => {
|
|
139
|
+
console.log(`Processed ${batch.length} events this second.`);
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Cross-Context Communication (BroadcastChannel)
|
|
144
|
+
|
|
145
|
+
To enable communication between different tabs or windows (browsers) or between multiple workers/processes (Node.js), use the `BroadcastChannel` configuration.
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
import { configureBroadcastChannelMessageBus } from '@tstdl/base/message-bus';
|
|
149
|
+
|
|
150
|
+
// Use this instead of configureLocalMessageBus
|
|
151
|
+
configureBroadcastChannelMessageBus();
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
> **Note:** `BroadcastChannel` is natively supported in modern browsers and Node.js 18+. For older environments, a polyfill may be required.
|
|
155
|
+
|
|
156
|
+
### Custom Injector Configuration
|
|
157
|
+
|
|
158
|
+
When configuring the `LocalMessageBus`, you can optionally specify which injector to use. This is useful in complex setups or when using multiple containers.
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
import { configureLocalMessageBus } from '@tstdl/base/message-bus';
|
|
162
|
+
import { myCustomInjector } from './my-context';
|
|
163
|
+
|
|
164
|
+
configureLocalMessageBus({ injector: myCustomInjector });
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
**Example: Logout Synchronization**
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
import { inject } from '@tstdl/base/injector';
|
|
171
|
+
import { MessageBus } from '@tstdl/base/message-bus';
|
|
172
|
+
|
|
173
|
+
type AuthEvent = { type: 'logout' };
|
|
174
|
+
|
|
175
|
+
class AuthService {
|
|
176
|
+
private readonly authBus = inject(MessageBus<AuthEvent>, 'auth');
|
|
177
|
+
|
|
178
|
+
async logout() {
|
|
179
|
+
// Perform logout
|
|
180
|
+
await this.authBus.publish({ type: 'logout' });
|
|
181
|
+
window.location.reload();
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
listenForExternalLogout() {
|
|
185
|
+
// 'messages$' only emits if *another* tab publishes the event
|
|
186
|
+
this.authBus.messages$.subscribe((event) => {
|
|
187
|
+
if (event.type === 'logout') {
|
|
188
|
+
console.log('Logout triggered in another tab.');
|
|
189
|
+
window.location.reload();
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### Fire and Forget
|
|
197
|
+
|
|
198
|
+
The standard `publish` method returns a `Promise` that resolves when the message has been dispatched (e.g., posted to the BroadcastChannel). If you do not need to wait for this, use `publishAndForget`.
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
// Non-blocking publish
|
|
202
|
+
this.messageBus.publishAndForget({ type: 'ping' });
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
### Resource Management
|
|
206
|
+
|
|
207
|
+
`MessageBus` implements `AsyncDisposable`. When a bus is no longer needed, it should be disposed to close underlying channels (like `BroadcastChannel`) and complete internal subjects.
|
|
208
|
+
|
|
209
|
+
If you are using the dependency injection system with scoped providers, this is handled automatically. If you create instances manually or hold them in long-lived services, ensure you dispose of them.
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
await bus[Symbol.asyncDispose]();
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## 📚 API
|
|
216
|
+
|
|
217
|
+
### Classes & Interfaces
|
|
218
|
+
|
|
219
|
+
| Name | Description |
|
|
220
|
+
| :------------------------------------ | :---------------------------------------------------- |
|
|
221
|
+
| `MessageBus<T>` | Abstract base class for a message bus instance. |
|
|
222
|
+
| `MessageBusBase<T>` | Helper class for implementing custom message buses. |
|
|
223
|
+
| `MessageBusProvider` | Abstract factory for creating `MessageBus` instances. |
|
|
224
|
+
| `LocalMessageBus<T>` | Implementation for in-process communication. |
|
|
225
|
+
| `LocalMessageBusProvider` | Provider implementation for `LocalMessageBus`. |
|
|
226
|
+
| `BroadcastChannelMessageBus<T>` | Implementation for cross-context communication. |
|
|
227
|
+
| `BroadcastChannelMessageBusProvider` | Provider implementation for `BroadcastChannelMessageBus`. |
|
|
228
|
+
|
|
229
|
+
### `MessageBus<T>` Members
|
|
230
|
+
|
|
231
|
+
| Member | Type | Description |
|
|
232
|
+
| :-------------------------- | :-------------- | :------------------------------------------- |
|
|
233
|
+
| `messages$` | `Observable<T>` | Stream of messages from **other** instances. |
|
|
234
|
+
| `allMessages$` | `Observable<T>` | Stream of **all** messages (including self). |
|
|
235
|
+
| `publish(message)` | `Promise<void>` | Publishes a message asynchronously. |
|
|
236
|
+
| `publishAndForget(message)` | `void` | Publishes a message without waiting. |
|
|
237
|
+
| `dispose()` | `Promise<void>` | Manually disposes the bus. |
|
|
238
|
+
|
|
239
|
+
### Configuration Functions
|
|
240
|
+
|
|
241
|
+
| Function | Description |
|
|
242
|
+
| :-------------------------------------- | :-------------------------------------------------------------------- |
|
|
243
|
+
| `configureLocalMessageBus()` | Registers `LocalMessageBus` as the default implementation. |
|
|
244
|
+
| `configureBroadcastChannelMessageBus()` | Registers `BroadcastChannelMessageBus` as the default implementation. |
|
|
@@ -13,7 +13,7 @@ export class MessageBusBase extends MessageBus {
|
|
|
13
13
|
this.logger = logger;
|
|
14
14
|
this.publishSubject = new Subject();
|
|
15
15
|
this.disposeToken = new CancellationToken();
|
|
16
|
-
this.messages$ = defer(() => this._messages$).pipe(takeUntil(this.disposeToken
|
|
16
|
+
this.messages$ = defer(() => this._messages$).pipe(takeUntil(this.disposeToken), share());
|
|
17
17
|
this.allMessages$ = merge(this.messages$, this.publishSubject);
|
|
18
18
|
}
|
|
19
19
|
publishAndForget(message) {
|
package/module/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# Application Modules
|
|
2
|
+
|
|
3
|
+
The `@tstdl/base/module` library provides a robust framework for building modular applications with managed lifecycles. It defines a standard contract for components that need to be started, monitored, and gracefully stopped, such as background workers, API servers, or custom services.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [The Module Class](#the-module-class)
|
|
10
|
+
- [Lifecycle States](#lifecycle-states)
|
|
11
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
12
|
+
- [Creating a Custom Module](#creating-a-custom-module)
|
|
13
|
+
- [Manual Execution](#manual-execution)
|
|
14
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
15
|
+
- [FunctionModule](#functionmodule)
|
|
16
|
+
- [WebServerModule](#webservermodule)
|
|
17
|
+
- [📚 API](#-api)
|
|
18
|
+
|
|
19
|
+
## ✨ Features
|
|
20
|
+
|
|
21
|
+
- **Standardized Lifecycle**: Uniform `run()` and `stop()` methods for all application components.
|
|
22
|
+
- **State Management**: Built-in tracking of module states (`Running`, `Stopping`, `Stopped`, `Erroneous`).
|
|
23
|
+
- **Graceful Shutdown**: Integrated `CancellationSignal` support for clean resource cleanup.
|
|
24
|
+
- **Resource Management**: Implemented `AsyncDisposable` for automatic cleanup when using the `using` keyword.
|
|
25
|
+
- **Ready-to-Use Modules**: Includes pre-built modules for common tasks like running functions or web servers.
|
|
26
|
+
- **Type Safety**: Fully typed with TypeScript for reliable development.
|
|
27
|
+
|
|
28
|
+
## Core Concepts
|
|
29
|
+
|
|
30
|
+
### The Module Class
|
|
31
|
+
|
|
32
|
+
The abstract `Module` class is the foundation of this library. It handles the internal state machine and synchronization required to start and stop services safely.
|
|
33
|
+
|
|
34
|
+
When you extend `Module`, you implement a single protected method: `_run(cancellationSignal)`. This method contains your main logic. The base class ensures that `_run` is called only when the module is stopped and handles the propagation of the cancellation signal when `stop()` is called.
|
|
35
|
+
|
|
36
|
+
### Lifecycle States
|
|
37
|
+
|
|
38
|
+
A module is always in one of the following states, defined by the `ModuleState` enum:
|
|
39
|
+
|
|
40
|
+
1. **Stopped**: The initial state. The module is ready to run.
|
|
41
|
+
2. **Running**: The `_run` method is currently executing.
|
|
42
|
+
3. **Stopping**: A stop has been requested, and the cancellation signal has been triggered, but the `_run` promise has not yet resolved.
|
|
43
|
+
4. **Erroneous**: The module threw an unhandled exception during execution.
|
|
44
|
+
|
|
45
|
+
## 🚀 Basic Usage
|
|
46
|
+
|
|
47
|
+
### Creating a Custom Module
|
|
48
|
+
|
|
49
|
+
To create a module, extend the `Module` class and implement the `_run` method. Use the provided `CancellationSignal` to detect when the module should stop.
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
import { CancellationSignal } from '@tstdl/base/cancellation';
|
|
53
|
+
import { Module } from '@tstdl/base/module';
|
|
54
|
+
import { cancelableTimeout } from '@tstdl/base/utils';
|
|
55
|
+
|
|
56
|
+
export class DataProcessorModule extends Module {
|
|
57
|
+
constructor() {
|
|
58
|
+
super('DataProcessor');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
protected async _run(cancellationSignal: CancellationSignal): Promise<void> {
|
|
62
|
+
console.log('DataProcessor started.');
|
|
63
|
+
|
|
64
|
+
// Run a loop until the signal is set (stop requested)
|
|
65
|
+
while (cancellationSignal.isUnset) {
|
|
66
|
+
console.log('Processing data batch...');
|
|
67
|
+
|
|
68
|
+
// Simulate work that can be cancelled
|
|
69
|
+
await cancelableTimeout(1000, cancellationSignal);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
console.log('DataProcessor stopping gracefully.');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Manual Execution
|
|
78
|
+
|
|
79
|
+
While modules are often managed by an `Application` runner (from `@tstdl/base/application`), you can run them manually.
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { DataProcessorModule } from './data-processor.module.js';
|
|
83
|
+
import { timeout } from '@tstdl/base/utils';
|
|
84
|
+
|
|
85
|
+
async function main() {
|
|
86
|
+
const module = new DataProcessorModule();
|
|
87
|
+
|
|
88
|
+
// Start the module (non-blocking)
|
|
89
|
+
const runPromise = module.run();
|
|
90
|
+
|
|
91
|
+
console.log(`Module state: ${module.state}`); // Running
|
|
92
|
+
|
|
93
|
+
// Let it run for a bit
|
|
94
|
+
await timeout(3000);
|
|
95
|
+
|
|
96
|
+
// Stop the module
|
|
97
|
+
console.log('Stopping module...');
|
|
98
|
+
await module.stop();
|
|
99
|
+
|
|
100
|
+
// Wait for the run promise to complete to ensure cleanup is done
|
|
101
|
+
await runPromise;
|
|
102
|
+
|
|
103
|
+
console.log(`Module state: ${module.state}`); // Stopped
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
main();
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 🔧 Advanced Topics
|
|
110
|
+
|
|
111
|
+
### FunctionModule
|
|
112
|
+
|
|
113
|
+
The `FunctionModule` is a generic wrapper that allows you to treat a simple asynchronous function as a full-fledged module. This is useful for simple background tasks or scripts that don't require a dedicated class.
|
|
114
|
+
|
|
115
|
+
It is typically used in conjunction with the dependency injection system or the `Application` runner (from `@tstdl/base/application`), which handles the instantiation and argument injection.
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
import { Application, provideModule } from '@tstdl/base/application';
|
|
119
|
+
import { CancellationSignal } from '@tstdl/base/cancellation';
|
|
120
|
+
|
|
121
|
+
async function myTask(signal: CancellationSignal) {
|
|
122
|
+
console.log('Function module running');
|
|
123
|
+
|
|
124
|
+
while (signal.isUnset) {
|
|
125
|
+
// ... logic
|
|
126
|
+
await timeout(1000);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Run the task as a module within an application
|
|
131
|
+
Application.run('MyApplication', [
|
|
132
|
+
provideModule(myTask)
|
|
133
|
+
]);
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### WebServerModule
|
|
137
|
+
|
|
138
|
+
The `WebServerModule` is a specialized module that integrates with `@tstdl/base/http` and `@tstdl/base/api` to run an HTTP server. It automatically registers API controllers and handles the server lifecycle.
|
|
139
|
+
|
|
140
|
+
It is designed to be used with the Dependency Injection system.
|
|
141
|
+
|
|
142
|
+
**Configuration:**
|
|
143
|
+
|
|
144
|
+
You can configure the port using `configureWebServerModule`.
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
import { configureWebServerModule, WebServerModule } from '@tstdl/base/module';
|
|
148
|
+
import { Application, provideModule } from '@tstdl/base/application';
|
|
149
|
+
import { configureNodeHttpServer } from '@tstdl/base/http/node';
|
|
150
|
+
import { configureApiServer } from '@tstdl/base/api/server';
|
|
151
|
+
|
|
152
|
+
// 1. Configure the environment
|
|
153
|
+
function bootstrap() {
|
|
154
|
+
configureNodeHttpServer(); // Select Node.js HTTP implementation
|
|
155
|
+
configureApiServer({
|
|
156
|
+
controllers: [
|
|
157
|
+
/* ... your controllers ... */
|
|
158
|
+
],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Configure the WebServerModule specific settings
|
|
162
|
+
configureWebServerModule({ port: 8080 });
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// 2. Run the application with the WebServerModule
|
|
166
|
+
Application.run('MyApiServer', [
|
|
167
|
+
provideModule(WebServerModule),
|
|
168
|
+
// ... other providers
|
|
169
|
+
]);
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 📚 API
|
|
173
|
+
|
|
174
|
+
| Type | Name | Description |
|
|
175
|
+
| :----------- | :----------------------------- | :----------------------------------------------------------------------------------------------------------- |
|
|
176
|
+
| **Class** | `Module` | Abstract base class for all modules. Handles state transitions and cancellation signals. Also implements `AsyncDisposable`. |
|
|
177
|
+
| **Enum** | `ModuleState` | Enumeration of module states: `Running`, `Stopping`, `Stopped`, `Erroneous`. |
|
|
178
|
+
| **Class** | `FunctionModule` | A generic module implementation that executes a provided function. |
|
|
179
|
+
| **Type** | `FunctionModuleFunction` | Signature for functions used with `FunctionModule`: `(signal: CancellationSignal) => void \| Promise<void>`. |
|
|
180
|
+
| **Class** | `WebServerModule` | A module that runs an HTTP server and serves registered API controllers. |
|
|
181
|
+
| **Type** | `WebServerModuleConfiguration` | Configuration object for `WebServerModule` (e.g., `{ port: number }`). |
|
|
182
|
+
| **Function** | `configureWebServerModule` | Helper function to set the global configuration for the `WebServerModule`. |
|
package/module/module.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export type ModuleState = EnumType<typeof ModuleState>;
|
|
|
11
11
|
export declare abstract class Module implements AsyncDisposable {
|
|
12
12
|
private runPromise;
|
|
13
13
|
private _state;
|
|
14
|
-
protected
|
|
14
|
+
protected cancellationToken: CancellationToken;
|
|
15
15
|
readonly name: string;
|
|
16
16
|
get state(): ModuleState;
|
|
17
17
|
private get stateString();
|