@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,140 @@
|
|
|
1
|
+
# Decorators
|
|
2
|
+
|
|
3
|
+
A collection of TypeScript decorators to enhance classes and methods with declarative behavior. Currently provides a robust logging decorator for tracing method execution, arguments, and return values.
|
|
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
|
+
- [API](#-api)
|
|
12
|
+
|
|
13
|
+
## ✨ Features
|
|
14
|
+
|
|
15
|
+
- **Method Logging**: Decorate individual methods to log their execution.
|
|
16
|
+
- **Class Logging**: Decorate an entire class to automatically log all its methods.
|
|
17
|
+
- **Configurable**: Customize what gets logged (arguments, return values, errors, execution time).
|
|
18
|
+
- **Smart Application**: Prevents double-wrapping if a method is decorated individually within a decorated class.
|
|
19
|
+
|
|
20
|
+
## Core Concepts
|
|
21
|
+
|
|
22
|
+
This module leverages TypeScript decorators to implement cross-cutting concerns like logging without cluttering your business logic.
|
|
23
|
+
|
|
24
|
+
### The `@Log` Decorator
|
|
25
|
+
|
|
26
|
+
The `@Log` decorator wraps the target method(s) using the `wrapLog` utility from the `@tstdl/base/function` module. It intercepts the method call, logs the entry (optionally with arguments), executes the original method, and logs the exit (optionally with the result or error and execution duration).
|
|
27
|
+
|
|
28
|
+
It supports both:
|
|
29
|
+
|
|
30
|
+
1. **Method Decorator**: Wraps a single method.
|
|
31
|
+
2. **Class Decorator**: Iterates through the class prototype and static properties to wrap all functions found.
|
|
32
|
+
|
|
33
|
+
## 🚀 Basic Usage
|
|
34
|
+
|
|
35
|
+
### Decorating a Method
|
|
36
|
+
|
|
37
|
+
Apply `@Log` to a specific method to trace its execution.
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
import { Log } from '@tstdl/base/decorators';
|
|
41
|
+
|
|
42
|
+
class Calculator {
|
|
43
|
+
@Log()
|
|
44
|
+
add(a: number, b: number): number {
|
|
45
|
+
return a + b;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const calc = new Calculator();
|
|
50
|
+
calc.add(5, 3);
|
|
51
|
+
// Logs output similar to:
|
|
52
|
+
// Call: Calculator.add(5, 3)
|
|
53
|
+
// Return: Calculator.add => 8 (took 0.1ms)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Decorating a Class
|
|
57
|
+
|
|
58
|
+
Apply `@Log` to a class to automatically wrap all its static and instance methods.
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { Log } from '@tstdl/base/decorators';
|
|
62
|
+
|
|
63
|
+
@Log()
|
|
64
|
+
class UserService {
|
|
65
|
+
static create(name: string) {
|
|
66
|
+
console.log(`Creating user ${name}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
getUser(id: string) {
|
|
70
|
+
return { id, name: 'Alice' };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
deleteUser(id: string) {
|
|
74
|
+
// ...
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// All methods above will be logged when called.
|
|
79
|
+
UserService.create('Bob');
|
|
80
|
+
const service = new UserService();
|
|
81
|
+
service.getUser('123');
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## 🔧 Advanced Topics
|
|
85
|
+
|
|
86
|
+
### Configuration Options
|
|
87
|
+
|
|
88
|
+
The `Log` decorator accepts an options object (passed to `wrapLog`) to control the logging behavior.
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
import { Log } from '@tstdl/base/decorators';
|
|
92
|
+
|
|
93
|
+
class DataService {
|
|
94
|
+
@Log({
|
|
95
|
+
logArgs: true, // Log arguments passed to the function
|
|
96
|
+
logResult: false, // Do not log the return value (e.g. if it's huge)
|
|
97
|
+
logError: true, // Log errors if thrown
|
|
98
|
+
logTime: true, // Log execution duration
|
|
99
|
+
})
|
|
100
|
+
async fetchData(query: string): Promise<any> {
|
|
101
|
+
// ... complex logic
|
|
102
|
+
return {
|
|
103
|
+
/* large object */
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Mixing Class and Method Decorators
|
|
110
|
+
|
|
111
|
+
You can apply `@Log` to the class and provide specific configurations for individual methods. The decorator logic ensures methods are not wrapped twice; the method-level decorator takes precedence during the wrapping process.
|
|
112
|
+
|
|
113
|
+
```ts
|
|
114
|
+
import { Log } from '@tstdl/base/decorators';
|
|
115
|
+
|
|
116
|
+
@Log({ logResult: false }) // Default: don't log results for methods in this class
|
|
117
|
+
class ReportGenerator {
|
|
118
|
+
generateSummary() {
|
|
119
|
+
// Inherits class config: logs execution but not result
|
|
120
|
+
return 'Summary...';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
@Log({ logResult: true }) // Override: log result for this specific method
|
|
124
|
+
generateDetailedReport() {
|
|
125
|
+
return 'Detailed Report...';
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## 📚 API
|
|
131
|
+
|
|
132
|
+
| Export | Type | Description |
|
|
133
|
+
| :------------------------------ | :-------- | :--------------------------------------------------------------------------- |
|
|
134
|
+
| `Log(options?: WrapLogOptions)` | Decorator | A decorator factory that wraps a class or method with logging functionality. |
|
|
135
|
+
|
|
136
|
+
### Types
|
|
137
|
+
|
|
138
|
+
| Type | Description |
|
|
139
|
+
| :--------------- | :----------------------------------------------------------------------------------- |
|
|
140
|
+
| `WrapLogOptions` | Configuration object for the logging wrapper (imported from `@tstdl/base/function`). |
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Distributed Loop
|
|
2
|
+
|
|
3
|
+
A robust mechanism for running periodic tasks synchronized across distributed systems. It ensures that a specific loop executes on only one instance of your application at a time, preventing race conditions and duplicate processing in clustered environments.
|
|
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
|
+
- [Cancellation with Signals](#cancellation-with-signals)
|
|
12
|
+
- [Manual Control](#manual-control)
|
|
13
|
+
- [Error Handling](#error-handling)
|
|
14
|
+
- [📚 API](#-api)
|
|
15
|
+
|
|
16
|
+
## ✨ Features
|
|
17
|
+
|
|
18
|
+
- **Distributed Synchronization**: Uses a `LockProvider` to ensure at most one active execution across a cluster.
|
|
19
|
+
- **Interval Control**: Run tasks at precise intervals (e.g., every 5 seconds).
|
|
20
|
+
- **Flexible Timing**: Distinguishes between execution interval and polling accuracy.
|
|
21
|
+
- **Graceful Shutdown**: Native support for `CancellationSignal` and manual `stop()` via `LoopController`.
|
|
22
|
+
- **Dependency Injection**: Can be resolved via `DistributedLoopProvider` or injected directly with a key.
|
|
23
|
+
|
|
24
|
+
## Core Concepts
|
|
25
|
+
|
|
26
|
+
### The Problem
|
|
27
|
+
|
|
28
|
+
In a microservices or clustered architecture, you often need to run periodic background tasks (e.g., sending emails, cleaning up cache, fetching external data). If you simply use `setInterval` on every instance, the task will run multiple times simultaneously, leading to data corruption or wasted resources.
|
|
29
|
+
|
|
30
|
+
### The Solution
|
|
31
|
+
|
|
32
|
+
The `DistributedLoop` solves this by acquiring a distributed lock before executing the task.
|
|
33
|
+
|
|
34
|
+
1. **Lock Acquisition**: The loop attempts to acquire a lock (via `LockProvider`) associated with a unique key.
|
|
35
|
+
2. **Execution**: If the lock is acquired, the task runs.
|
|
36
|
+
3. **Wait**: If the lock is held by another instance, the current instance waits and retries.
|
|
37
|
+
4. **Release**: The lock is held only during the execution of the task logic (plus the interval wait time in the current implementation logic).
|
|
38
|
+
|
|
39
|
+
### Components
|
|
40
|
+
|
|
41
|
+
- **`DistributedLoopProvider`**: A factory service to create named loops.
|
|
42
|
+
- **`DistributedLoop`**: The class managing the run loop, timing, and locking.
|
|
43
|
+
- **`LoopController`**: An interface returned when starting a loop, allowing you to stop it or await its completion.
|
|
44
|
+
|
|
45
|
+
## 🚀 Basic Usage
|
|
46
|
+
|
|
47
|
+
### 1. Configure LockProvider
|
|
48
|
+
|
|
49
|
+
Ensure you have a `LockProvider` (e.g., `PostgresLockProvider`) configured in your dependency injection container. The `DistributedLoop` uses it to synchronize across instances.
|
|
50
|
+
|
|
51
|
+
### 2. Inject the Provider
|
|
52
|
+
|
|
53
|
+
Inject `DistributedLoopProvider` into your service for dynamic loop creation.
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
import { DistributedLoopProvider } from '@tstdl/base/distributed-loop';
|
|
57
|
+
import { Singleton, inject } from '@tstdl/base/injector';
|
|
58
|
+
|
|
59
|
+
@Singleton()
|
|
60
|
+
export class BackgroundWorker {
|
|
61
|
+
private readonly loopProvider = inject(DistributedLoopProvider);
|
|
62
|
+
|
|
63
|
+
start() {
|
|
64
|
+
// Create a named loop. The key 'data-sync' ensures uniqueness across the cluster.
|
|
65
|
+
const loop = this.loopProvider.get('data-sync');
|
|
66
|
+
|
|
67
|
+
// Run the loop
|
|
68
|
+
// Interval: 5000ms (5 seconds)
|
|
69
|
+
// Accuracy: 100ms (Check for stop/lock every 100ms)
|
|
70
|
+
loop.run(5000, 100, async (controller) => {
|
|
71
|
+
console.log('Running distributed task...');
|
|
72
|
+
await this.performSync();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private async performSync() {
|
|
77
|
+
// Your business logic here
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 3. Direct Injection
|
|
83
|
+
|
|
84
|
+
If your loop key is static, you can inject `DistributedLoop` directly.
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
import { DistributedLoop } from '@tstdl/base/distributed-loop';
|
|
88
|
+
import { Singleton, inject } from '@tstdl/base/injector';
|
|
89
|
+
|
|
90
|
+
@Singleton()
|
|
91
|
+
export class StaticWorker {
|
|
92
|
+
// Inject with a static key argument
|
|
93
|
+
private readonly loop = inject(DistributedLoop, 'static-worker-loop');
|
|
94
|
+
|
|
95
|
+
start() {
|
|
96
|
+
this.loop.run(60000, 1000, async () => {
|
|
97
|
+
console.log('Running static worker task...');
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
## 🔧 Advanced Topics
|
|
104
|
+
|
|
105
|
+
### Timing and Accuracy
|
|
106
|
+
|
|
107
|
+
The `run` method takes both an `interval` and an `accuracy`.
|
|
108
|
+
|
|
109
|
+
- **Interval**: The target time between the start of one iteration and the start of the next.
|
|
110
|
+
- **Accuracy**: The maximum time the application waits between checking for the lock or a stop signal. A smaller accuracy makes the loop more responsive to stops and other instances releasing the lock, but increases the load on the `LockProvider`.
|
|
111
|
+
|
|
112
|
+
**Note**: To maintain exclusive access between iterations, the loop holds the lock for most of the interval, releasing it only for a short window defined by the accuracy. This ensures that in a cluster, the same instance is likely to keep the lock unless it becomes unavailable.
|
|
113
|
+
|
|
114
|
+
### Cancellation with Signals
|
|
115
|
+
|
|
116
|
+
You can pass a `CancellationSignal` to the `run` method to tie the loop's lifecycle to a broader context (like the application shutdown signal).
|
|
117
|
+
|
|
118
|
+
```typescript
|
|
119
|
+
import { DistributedLoopProvider } from '@tstdl/base/distributed-loop';
|
|
120
|
+
import { Application } from '@tstdl/base/application';
|
|
121
|
+
import { inject } from '@tstdl/base/injector';
|
|
122
|
+
|
|
123
|
+
async function main() {
|
|
124
|
+
const app = inject(Application);
|
|
125
|
+
const loopProvider = inject(DistributedLoopProvider);
|
|
126
|
+
const loop = loopProvider.get('cleanup-job');
|
|
127
|
+
|
|
128
|
+
// Pass the application's shutdown signal
|
|
129
|
+
loop.run(
|
|
130
|
+
60000, // Run every minute
|
|
131
|
+
1000, // 1 second accuracy
|
|
132
|
+
async () => {
|
|
133
|
+
console.log('Cleaning up...');
|
|
134
|
+
},
|
|
135
|
+
app.shutdownSignal, // Loop stops automatically when app shuts down
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### Manual Control
|
|
141
|
+
|
|
142
|
+
The `run` method returns a `LoopController` which gives you manual control over the loop instance.
|
|
143
|
+
|
|
144
|
+
```typescript
|
|
145
|
+
import { DistributedLoopProvider } from '@tstdl/base/distributed-loop';
|
|
146
|
+
import { inject } from '@tstdl/base/injector';
|
|
147
|
+
import { timeout } from '@tstdl/base/utils';
|
|
148
|
+
|
|
149
|
+
async function testLoop() {
|
|
150
|
+
const provider = inject(DistributedLoopProvider);
|
|
151
|
+
const loop = provider.get('test-loop');
|
|
152
|
+
|
|
153
|
+
const controller = loop.run(1000, 100, async () => {
|
|
154
|
+
console.log('Tick');
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
// Let it run for 5 seconds
|
|
158
|
+
await timeout(5000);
|
|
159
|
+
|
|
160
|
+
// Stop the loop manually
|
|
161
|
+
await controller.stop();
|
|
162
|
+
console.log('Loop stopped');
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Error Handling
|
|
167
|
+
|
|
168
|
+
The `DistributedLoop` does not automatically trap errors thrown inside your loop function. If your function throws an unhandled error:
|
|
169
|
+
1. The current iteration stops immediately.
|
|
170
|
+
2. The loop terminates.
|
|
171
|
+
3. The `LoopController.$stopped` promise resolves.
|
|
172
|
+
4. The error propagates as an unhandled rejection.
|
|
173
|
+
|
|
174
|
+
**Best Practice**: Always wrap your business logic in a `try-catch` block within the loop function to prevent the entire loop from stopping on a single failure.
|
|
175
|
+
|
|
176
|
+
```typescript
|
|
177
|
+
loop.run(5000, 100, async () => {
|
|
178
|
+
try {
|
|
179
|
+
await riskyOperation();
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error('Error in distributed loop iteration:', error);
|
|
182
|
+
// Loop continues to next iteration since error is caught
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## 📚 API
|
|
188
|
+
|
|
189
|
+
### `DistributedLoopProvider`
|
|
190
|
+
|
|
191
|
+
The factory class for creating distributed loops.
|
|
192
|
+
|
|
193
|
+
| Method | Description |
|
|
194
|
+
| :---------------------------------- | :------------------------------------------------------------------------ |
|
|
195
|
+
| `get(key: string): DistributedLoop` | Creates and returns a new `DistributedLoop` instance identified by `key`. |
|
|
196
|
+
|
|
197
|
+
### `DistributedLoop`
|
|
198
|
+
|
|
199
|
+
The class responsible for executing the synchronized loop.
|
|
200
|
+
|
|
201
|
+
| Method | Description |
|
|
202
|
+
| :--------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
203
|
+
| `constructor(key: string, lockProvider: LockProvider)` | Creates a new loop instance. Usually handled by `DistributedLoopProvider` or DI. |
|
|
204
|
+
| `run(interval: number, accuracy: number, func: LoopFunction, cancellationSignal?: CancellationSignal): LoopController` | Starts the loop. <br>• `interval`: Time in ms between executions.<br>• `accuracy`: Polling interval in ms for lock/stop checks and release window.<br>• `func`: The function to execute.<br>• `cancellationSignal`: Optional signal to stop. |
|
|
205
|
+
|
|
206
|
+
### `LoopController`
|
|
207
|
+
|
|
208
|
+
Interface returned by `run()` to control the active loop.
|
|
209
|
+
|
|
210
|
+
| Property/Method | Type | Description |
|
|
211
|
+
| :-------------- | :-------------------- | :------------------------------------------------------- |
|
|
212
|
+
| `$stopped` | `Promise<void>` | A promise that resolves when the loop has fully stopped. |
|
|
213
|
+
| `stop()` | `() => Promise<void>` | Signals the loop to stop and waits for it to finish. |
|
|
214
|
+
|
|
215
|
+
### Types
|
|
216
|
+
|
|
217
|
+
#### `LoopFunction`
|
|
218
|
+
|
|
219
|
+
Type definition for the function executed by the loop.
|
|
220
|
+
|
|
221
|
+
```typescript
|
|
222
|
+
type LoopFunction = (controller: LoopController) => any | Promise<any>;
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### `DistributedLoopArgument`
|
|
226
|
+
|
|
227
|
+
The argument type used for injecting a `DistributedLoop` with a key.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
type DistributedLoopArgument = string;
|
|
231
|
+
```
|
|
@@ -42,7 +42,7 @@ let DistributedLoop = class DistributedLoop {
|
|
|
42
42
|
*/
|
|
43
43
|
run(interval, accuracy, func, cancellationSignal) {
|
|
44
44
|
const $stopped = new DeferredPromise();
|
|
45
|
-
const stopToken =
|
|
45
|
+
const stopToken = new CancellationToken(cancellationSignal);
|
|
46
46
|
const stopFunction = async () => {
|
|
47
47
|
if (!stopToken.isSet) {
|
|
48
48
|
stopToken.set();
|