@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/lock/README.md
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Lock Module
|
|
2
|
+
|
|
3
|
+
A robust, provider-based resource locking mechanism designed to prevent race conditions in both distributed server-side environments and client-side browser contexts.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [✨ Features](#-features)
|
|
8
|
+
- [Core Concepts](#core-concepts)
|
|
9
|
+
- [Lock](#lock)
|
|
10
|
+
- [LockProvider](#lockprovider)
|
|
11
|
+
- [Backends](#backends)
|
|
12
|
+
- [🚀 Basic Usage](#-basic-usage)
|
|
13
|
+
- [🔧 Advanced Topics](#-advanced-topics)
|
|
14
|
+
- [Manual Acquisition](#manual-acquisition)
|
|
15
|
+
- [Non-Throwing Attempts](#non-throwing-attempts)
|
|
16
|
+
- [Scoped Providers (Prefixing)](#scoped-providers-prefixing)
|
|
17
|
+
- [Checking Lock Existence](#checking-lock-existence)
|
|
18
|
+
- [PostgreSQL Backend Configuration](#postgresql-backend-configuration)
|
|
19
|
+
- [Web Locks Backend Configuration](#web-locks-backend-configuration)
|
|
20
|
+
- [📚 API](#-api)
|
|
21
|
+
|
|
22
|
+
## ✨ Features
|
|
23
|
+
|
|
24
|
+
- **Pluggable Backends**: Seamlessly switch between distributed server-side locking (PostgreSQL) and client-side locking (Web Locks API).
|
|
25
|
+
- **Automatic Renewal**: The PostgreSQL backend automatically renews locks (heartbeat) to prevent expiration during long-running tasks while ensuring deadlocks are cleared if a process crashes.
|
|
26
|
+
- **Safe Execution**: The `use` pattern guarantees locks are released even if the critical section throws an error.
|
|
27
|
+
- **Timeout Support**: Configurable timeouts for acquiring locks.
|
|
28
|
+
- **Scoped Providers**: Easily namespace locks using prefixes to prevent collisions between different modules.
|
|
29
|
+
- **Cancellation Support**: Integrated with `@tstdl/base/cancellation` for aborting lock acquisition.
|
|
30
|
+
|
|
31
|
+
## Core Concepts
|
|
32
|
+
|
|
33
|
+
### Lock
|
|
34
|
+
|
|
35
|
+
A `Lock` instance represents a mutual exclusion lock for a specific resource string. It provides methods to acquire the lock, execute code within the lock, and release it. Many methods support a `timeout` parameter:
|
|
36
|
+
- `undefined` or `<= 0`: Tries to acquire the lock once and returns/throws immediately if unsuccessful.
|
|
37
|
+
- `> 0`: Retries to acquire the lock for the specified duration in milliseconds.
|
|
38
|
+
- `Infinity`: Retries indefinitely until the lock is acquired or the operation is canceled.
|
|
39
|
+
|
|
40
|
+
### LockProvider
|
|
41
|
+
|
|
42
|
+
The `LockProvider` is an injectable factory service. It creates `Lock` instances for specific resources. It is the primary entry point for consuming the module in your services.
|
|
43
|
+
|
|
44
|
+
### Backends
|
|
45
|
+
|
|
46
|
+
1. **PostgresLock**: Designed for distributed backend systems. It uses a database table to coordinate locks between multiple server instances. It features an automatic "heartbeat" mechanism to keep the lock alive while the process is running.
|
|
47
|
+
2. **WebLock**: Designed for browser environments. It wraps the native [Web Locks API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Locks_API), allowing coordination between tabs, windows, and workers on the same origin.
|
|
48
|
+
|
|
49
|
+
## 🚀 Basic Usage
|
|
50
|
+
|
|
51
|
+
The recommended way to use a lock is via the `use` method. It handles acquisition, error propagation, and ensures the lock is released automatically.
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
import { inject } from '@tstdl/base/injector';
|
|
55
|
+
import { LockProvider } from '@tstdl/base/lock';
|
|
56
|
+
|
|
57
|
+
export class UserService {
|
|
58
|
+
readonly #lockProvider = inject(LockProvider);
|
|
59
|
+
|
|
60
|
+
async updateUserBalance(userId: string, amount: number): Promise<void> {
|
|
61
|
+
const lock = this.#lockProvider.get(`user-balance:${userId}`);
|
|
62
|
+
|
|
63
|
+
// Try to acquire the lock for up to 5000ms.
|
|
64
|
+
// If acquired, runs the function and automatically releases the lock afterwards.
|
|
65
|
+
// If timeout is reached, it throws an Error.
|
|
66
|
+
await lock.use(5000, async (controller) => {
|
|
67
|
+
// Critical section: only one process can be here for this resource string
|
|
68
|
+
|
|
69
|
+
// Optional: Check if lock was lost (e.g. network partition or renewal failure)
|
|
70
|
+
if (controller.lost) {
|
|
71
|
+
throw new Error('Lock lost during operation');
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
console.log(`Updating balance for ${userId}...`);
|
|
75
|
+
// await database.update(...)
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 🔧 Advanced Topics
|
|
82
|
+
|
|
83
|
+
### Dependency Injection
|
|
84
|
+
|
|
85
|
+
You can inject `LockProvider` or even specific `Lock` instances directly using the `inject` function or decorators.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
import { inject } from '@tstdl/base/injector';
|
|
89
|
+
import { Lock, LockProvider } from '@tstdl/base/lock';
|
|
90
|
+
|
|
91
|
+
// 1. Injecting a prefixed provider
|
|
92
|
+
const billingLocks = inject(LockProvider, 'billing');
|
|
93
|
+
const lock1 = billingLocks.get('user:123'); // resource: "billing:user:123"
|
|
94
|
+
|
|
95
|
+
// 2. Injecting a specific lock directly
|
|
96
|
+
const userLock = inject(Lock, 'user:123');
|
|
97
|
+
const prefixedUserLock = inject(Lock, { prefix: 'billing', resource: 'user:123' });
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Manual Acquisition
|
|
101
|
+
|
|
102
|
+
For scenarios where the `use` block structure doesn't fit (e.g., lock spans across multiple function calls), you can manually acquire and release the lock.
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
import { inject } from '@tstdl/base/injector';
|
|
106
|
+
import { LockProvider } from '@tstdl/base/lock';
|
|
107
|
+
|
|
108
|
+
class JobProcessor {
|
|
109
|
+
readonly #lockProvider = inject(LockProvider);
|
|
110
|
+
|
|
111
|
+
async processJob(jobId: string) {
|
|
112
|
+
const lock = this.#lockProvider.get(`job:${jobId}`);
|
|
113
|
+
|
|
114
|
+
// Acquire lock, waiting for up to 10 seconds.
|
|
115
|
+
// Throws if acquisition fails within the timeout.
|
|
116
|
+
const controller = await lock.acquire(10000);
|
|
117
|
+
|
|
118
|
+
try {
|
|
119
|
+
console.log('Lock acquired');
|
|
120
|
+
// Do work...
|
|
121
|
+
} finally {
|
|
122
|
+
// Always release in finally block
|
|
123
|
+
await controller.release();
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### Non-Throwing Attempts
|
|
130
|
+
|
|
131
|
+
If you want to attempt to acquire a lock but simply return `false` or a failure result instead of throwing an error upon timeout/failure, use `tryAcquire` or `tryUse`.
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
const lock = lockProvider.get('daily-report');
|
|
135
|
+
|
|
136
|
+
// Try to acquire for 100ms
|
|
137
|
+
const result = await lock.tryUse(100, async () => {
|
|
138
|
+
return 'Report Generated';
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
if (result.success) {
|
|
142
|
+
console.log(result.result);
|
|
143
|
+
} else {
|
|
144
|
+
console.log('Could not acquire lock, skipping report generation.');
|
|
145
|
+
}
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Scoped Providers (Prefixing)
|
|
149
|
+
|
|
150
|
+
To avoid naming collisions between different parts of your application (e.g., "user:123" in the billing module vs "user:123" in the notification module), use `prefix()`.
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { inject } from '@tstdl/base/injector';
|
|
154
|
+
import { LockProvider } from '@tstdl/base/lock';
|
|
155
|
+
|
|
156
|
+
const rootProvider = inject(LockProvider);
|
|
157
|
+
|
|
158
|
+
// Creates a provider that prefixes all keys with "billing:"
|
|
159
|
+
const billingLocks = rootProvider.prefix('billing');
|
|
160
|
+
const lock1 = billingLocks.get('user:123'); // Actual resource: "billing:user:123"
|
|
161
|
+
|
|
162
|
+
// Creates a provider that prefixes all keys with "notifications:"
|
|
163
|
+
const notificationLocks = rootProvider.prefix('notifications');
|
|
164
|
+
const lock2 = notificationLocks.get('user:123'); // Actual resource: "notifications:user:123"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Checking Lock Existence
|
|
168
|
+
|
|
169
|
+
You can check if a resource is currently locked without trying to acquire it.
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
const lock = lockProvider.get('maintenance-mode');
|
|
173
|
+
|
|
174
|
+
if (await lock.exists()) {
|
|
175
|
+
console.log('System is currently in maintenance mode (locked).');
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
### PostgreSQL Backend Configuration
|
|
180
|
+
|
|
181
|
+
To use the PostgreSQL backend, ensure the ORM module is configured, then configure the lock module in your application bootstrap.
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { configurePostgresLock, migratePostgresLockSchema } from '@tstdl/base/lock/postgres';
|
|
185
|
+
import { configureOrm } from '@tstdl/base/orm/server';
|
|
186
|
+
import { runInInjectionContext, inject, Injector } from '@tstdl/base/injector';
|
|
187
|
+
|
|
188
|
+
async function bootstrap() {
|
|
189
|
+
// 1. Configure ORM
|
|
190
|
+
configureOrm({
|
|
191
|
+
/* ... default connection details ... */
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
// 2. Configure Lock Module.
|
|
195
|
+
// Optionally provide a different database configuration for locks.
|
|
196
|
+
configurePostgresLock({
|
|
197
|
+
// database: { connection: { ... } }
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// 3. Run Migrations (creates the 'lock' schema and table)
|
|
201
|
+
const injector = inject(Injector);
|
|
202
|
+
await runInInjectionContext(injector, migratePostgresLockSchema);
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Web Locks Backend Configuration
|
|
207
|
+
|
|
208
|
+
For browser environments, use the Web Lock configuration.
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import { configureWebLock } from '@tstdl/base/lock/web';
|
|
212
|
+
|
|
213
|
+
function bootstrap() {
|
|
214
|
+
configureWebLock();
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## 📚 API
|
|
219
|
+
|
|
220
|
+
### `LockProvider`
|
|
221
|
+
|
|
222
|
+
| Method | Signature | Description |
|
|
223
|
+
| :------- | :------------------------------------- | :-------------------------------------------------------------------------- |
|
|
224
|
+
| `get` | `get(resource: string): Lock` | Creates a `Lock` instance for the specified resource. |
|
|
225
|
+
| `prefix` | `prefix(prefix: string): LockProvider` | Returns a new `LockProvider` that automatically prefixes all resource keys. |
|
|
226
|
+
|
|
227
|
+
### `Lock`
|
|
228
|
+
|
|
229
|
+
| Method | Signature | Description |
|
|
230
|
+
| :----------- | :----------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------ |
|
|
231
|
+
| `acquire` | `acquire(timeout?: number): Promise<LockController>` | Acquires the lock. Throws if the lock cannot be acquired within the timeout. |
|
|
232
|
+
| `tryAcquire` | `tryAcquire(timeout?: number): Promise<LockController \| false>` | Tries to acquire the lock. Returns `false` if it fails/times out. |
|
|
233
|
+
| `use` | `use<R>(timeout: number \| undefined, func: LockedFunction<R>): Promise<R>` | Acquires lock, runs function, releases lock. Throws on acquisition failure. |
|
|
234
|
+
| `tryUse` | `tryUse<R>(timeout: number \| undefined, func: LockedFunction<R>): Promise<LockTryUseResult<R>>` | Tries to acquire lock and run function. Returns a result object indicating success/failure. |
|
|
235
|
+
| `exists` | `exists(): Promise<boolean>` | Checks if the lock is currently held by any process. |
|
|
236
|
+
|
|
237
|
+
### `LockController`
|
|
238
|
+
|
|
239
|
+
| Property/Method | Type | Description |
|
|
240
|
+
| :-------------- | :-------------------- | :----------------------------------------------------------------------------------- |
|
|
241
|
+
| `lost` | `boolean` | Indicates if the lock has been lost (e.g., due to failed renewal or network issues). |
|
|
242
|
+
| `release` | `() => Promise<void>` | Manually releases the lock. |
|
|
243
|
+
|
|
244
|
+
### `LockTryUseResult<R>`
|
|
245
|
+
|
|
246
|
+
| Property | Type | Description |
|
|
247
|
+
| :-------- | :--------------- | :------------------------------------------------------- |
|
|
248
|
+
| `success` | `boolean` | `true` if lock was acquired and function executed. |
|
|
249
|
+
| `result` | `R \| undefined` | The return value of the function (if `success` is true). |
|
package/lock/postgres/module.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { inject, Injector } from '../../injector/index.js';
|
|
2
|
-
import { Database, migrate } from '../../orm/server/index.js';
|
|
2
|
+
import { Database, migrate, registerDatabaseMigration } from '../../orm/server/index.js';
|
|
3
3
|
import { isDefined } from '../../utils/type-guards.js';
|
|
4
4
|
import { Lock } from '../lock.js';
|
|
5
5
|
import { LockProvider } from '../provider.js';
|
|
@@ -7,6 +7,7 @@ import { PostgresLockAdapter } from './lock.js';
|
|
|
7
7
|
import { PostgresLockProvider } from './provider.js';
|
|
8
8
|
export class PostgresLockModuleConfig {
|
|
9
9
|
database;
|
|
10
|
+
autoMigrate;
|
|
10
11
|
}
|
|
11
12
|
/**
|
|
12
13
|
* configure lock module
|
|
@@ -18,6 +19,9 @@ export function configurePostgresLock({ injector, ...config } = {}) {
|
|
|
18
19
|
}
|
|
19
20
|
targetInjector.registerSingleton(LockProvider, { useToken: PostgresLockProvider });
|
|
20
21
|
targetInjector.registerSingleton(Lock, { useToken: PostgresLockAdapter });
|
|
22
|
+
if (config.autoMigrate != false) {
|
|
23
|
+
registerDatabaseMigration('PostgresLock', migratePostgresLockSchema, { injector });
|
|
24
|
+
}
|
|
21
25
|
}
|
|
22
26
|
export async function migratePostgresLockSchema() {
|
|
23
27
|
const connection = inject(PostgresLockModuleConfig, undefined, { optional: true })?.database?.connection;
|
package/lock/web/web-lock.js
CHANGED
|
@@ -4,6 +4,58 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key,
|
|
|
4
4
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
5
5
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
6
6
|
};
|
|
7
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
8
|
+
if (value !== null && value !== void 0) {
|
|
9
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
10
|
+
var dispose, inner;
|
|
11
|
+
if (async) {
|
|
12
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
13
|
+
dispose = value[Symbol.asyncDispose];
|
|
14
|
+
}
|
|
15
|
+
if (dispose === void 0) {
|
|
16
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
17
|
+
dispose = value[Symbol.dispose];
|
|
18
|
+
if (async) inner = dispose;
|
|
19
|
+
}
|
|
20
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
21
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
22
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
23
|
+
}
|
|
24
|
+
else if (async) {
|
|
25
|
+
env.stack.push({ async: true });
|
|
26
|
+
}
|
|
27
|
+
return value;
|
|
28
|
+
};
|
|
29
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
30
|
+
return function (env) {
|
|
31
|
+
function fail(e) {
|
|
32
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
33
|
+
env.hasError = true;
|
|
34
|
+
}
|
|
35
|
+
var r, s = 0;
|
|
36
|
+
function next() {
|
|
37
|
+
while (r = env.stack.pop()) {
|
|
38
|
+
try {
|
|
39
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
40
|
+
if (r.dispose) {
|
|
41
|
+
var result = r.dispose.call(r.value);
|
|
42
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
43
|
+
}
|
|
44
|
+
else s |= 1;
|
|
45
|
+
}
|
|
46
|
+
catch (e) {
|
|
47
|
+
fail(e);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
51
|
+
if (env.hasError) throw env.error;
|
|
52
|
+
}
|
|
53
|
+
return next();
|
|
54
|
+
};
|
|
55
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
56
|
+
var e = new Error(message);
|
|
57
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
58
|
+
});
|
|
7
59
|
import { Injectable } from '../../injector/index.js';
|
|
8
60
|
import { DeferredPromise } from '../../promise/deferred-promise.js';
|
|
9
61
|
import { assertStringPass, isNull, isObject, isUndefined } from '../../utils/type-guards.js';
|
|
@@ -14,58 +66,78 @@ let WebLock = class WebLock extends Lock {
|
|
|
14
66
|
return await this.tryAcquire(0);
|
|
15
67
|
}
|
|
16
68
|
async tryAcquire(timeout) {
|
|
17
|
-
const
|
|
18
|
-
const releasePromise = new DeferredPromise();
|
|
19
|
-
const ifAvailable = isUndefined(timeout) || (timeout <= 0);
|
|
20
|
-
const signal = ifAvailable ? undefined : AbortSignal.any([AbortSignal.timeout(timeout), this.cancellationSignal.asAbortSignal()]);
|
|
21
|
-
await navigator.locks
|
|
22
|
-
.request(this.resource, { signal, ifAvailable }, async (lock) => {
|
|
23
|
-
if (isNull(lock)) {
|
|
24
|
-
throw new Error('Failed to acquire lock.');
|
|
25
|
-
}
|
|
26
|
-
let lost = false;
|
|
27
|
-
controllerPromise.resolve({
|
|
28
|
-
get lost() {
|
|
29
|
-
return lost;
|
|
30
|
-
},
|
|
31
|
-
async release() {
|
|
32
|
-
releasePromise.resolve();
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
await releasePromise;
|
|
36
|
-
lost = true;
|
|
37
|
-
})
|
|
38
|
-
.catch(() => controllerPromise.resolve(false));
|
|
39
|
-
return await controllerPromise;
|
|
40
|
-
}
|
|
41
|
-
async tryUse(timeout, func) {
|
|
42
|
-
const ifAvailable = isUndefined(timeout) || (timeout <= 0);
|
|
43
|
-
const signal = ifAvailable ? undefined : AbortSignal.any([AbortSignal.timeout(timeout), this.cancellationSignal.asAbortSignal()]);
|
|
44
|
-
let result;
|
|
69
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
45
70
|
try {
|
|
46
|
-
|
|
71
|
+
const controllerPromise = new DeferredPromise();
|
|
72
|
+
const releasePromise = new DeferredPromise();
|
|
73
|
+
const ifAvailable = isUndefined(timeout) || (timeout <= 0);
|
|
74
|
+
const signal = __addDisposableResource(env_1, ifAvailable ? undefined : this.cancellationSignal.withTimeout(timeout), false);
|
|
75
|
+
await navigator.locks
|
|
76
|
+
.request(this.resource, { signal: signal?.abortSignal, ifAvailable }, async (lock) => {
|
|
47
77
|
if (isNull(lock)) {
|
|
48
|
-
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const returnValue = await func({ lost: false });
|
|
52
|
-
return { type: 'result', value: returnValue };
|
|
53
|
-
}
|
|
54
|
-
catch (error) {
|
|
55
|
-
return { type: 'error', error };
|
|
78
|
+
throw new Error('Failed to acquire lock.');
|
|
56
79
|
}
|
|
57
|
-
|
|
80
|
+
let lost = false;
|
|
81
|
+
controllerPromise.resolve({
|
|
82
|
+
get lost() {
|
|
83
|
+
return lost;
|
|
84
|
+
},
|
|
85
|
+
async release() {
|
|
86
|
+
releasePromise.resolve();
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
await releasePromise;
|
|
90
|
+
lost = true;
|
|
91
|
+
})
|
|
92
|
+
.catch(() => controllerPromise.resolve(false));
|
|
93
|
+
return await controllerPromise;
|
|
94
|
+
}
|
|
95
|
+
catch (e_1) {
|
|
96
|
+
env_1.error = e_1;
|
|
97
|
+
env_1.hasError = true;
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
__disposeResources(env_1);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
async tryUse(timeout, func) {
|
|
104
|
+
const env_2 = { stack: [], error: void 0, hasError: false };
|
|
105
|
+
try {
|
|
106
|
+
const ifAvailable = isUndefined(timeout) || (timeout <= 0);
|
|
107
|
+
const signal = __addDisposableResource(env_2, ifAvailable ? undefined : this.cancellationSignal.withTimeout(timeout), false);
|
|
108
|
+
let result;
|
|
109
|
+
try {
|
|
110
|
+
result = await navigator.locks.request(this.resource, { signal: signal?.abortSignal, ifAvailable }, async (lock) => {
|
|
111
|
+
if (isNull(lock)) {
|
|
112
|
+
return { type: 'already-locked' };
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
const returnValue = await func({ lost: false });
|
|
116
|
+
return { type: 'result', value: returnValue };
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
return { type: 'error', error };
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
catch {
|
|
124
|
+
return { success: false }; // navigator.locks.request throws a DOMException on timeout
|
|
125
|
+
}
|
|
126
|
+
switch (result.type) {
|
|
127
|
+
case 'result':
|
|
128
|
+
return { success: true, result: result.value };
|
|
129
|
+
case 'already-locked':
|
|
130
|
+
return { success: false };
|
|
131
|
+
case 'error':
|
|
132
|
+
throw result.error;
|
|
133
|
+
}
|
|
58
134
|
}
|
|
59
|
-
catch {
|
|
60
|
-
|
|
135
|
+
catch (e_2) {
|
|
136
|
+
env_2.error = e_2;
|
|
137
|
+
env_2.hasError = true;
|
|
61
138
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return { success: true, result: result.value };
|
|
65
|
-
case 'already-locked':
|
|
66
|
-
return { success: false };
|
|
67
|
-
case 'error':
|
|
68
|
-
throw result.error;
|
|
139
|
+
finally {
|
|
140
|
+
__disposeResources(env_2);
|
|
69
141
|
}
|
|
70
142
|
}
|
|
71
143
|
async exists() {
|