alepha 0.9.4 → 0.10.0
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/LICENSE +21 -21
- package/README.md +47 -93
- package/batch.d.ts +491 -16
- package/bucket.d.ts +449 -12
- package/cache.d.ts +132 -1
- package/command.d.ts +32 -8
- package/core.d.ts +462 -369
- package/email.cjs +8 -0
- package/email.d.ts +328 -0
- package/email.js +1 -0
- package/lock.d.ts +412 -23
- package/logger.d.ts +83 -45
- package/package.json +62 -44
- package/postgres.d.ts +1731 -205
- package/queue/redis.d.ts +3 -3
- package/queue.d.ts +591 -8
- package/react/auth.d.ts +101 -112
- package/react/form.d.ts +14 -4
- package/react/head.d.ts +1 -1
- package/react/i18n.d.ts +13 -5
- package/react.d.ts +318 -180
- package/redis.d.ts +11 -11
- package/router.d.ts +1 -0
- package/security.d.ts +29 -29
- package/server/cache.d.ts +1 -0
- package/server/health.d.ts +6 -6
- package/server/links.d.ts +40 -29
- package/server/proxy.d.ts +192 -0
- package/server.d.ts +645 -88
- package/topic.d.ts +803 -12
- package/vite.d.ts +32 -4
package/lock.d.ts
CHANGED
|
@@ -30,13 +30,244 @@ declare abstract class LockProvider {
|
|
|
30
30
|
//#endregion
|
|
31
31
|
//#region src/descriptors/$lock.d.ts
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
33
|
+
* Creates a distributed lock descriptor for ensuring single-instance execution across processes.
|
|
34
34
|
*
|
|
35
|
-
*
|
|
35
|
+
* This descriptor provides a powerful distributed locking mechanism that prevents multiple instances
|
|
36
|
+
* of the same operation from running simultaneously. It's essential for maintaining data consistency
|
|
37
|
+
* and preventing race conditions in distributed applications, scheduled tasks, and critical sections
|
|
38
|
+
* that must execute atomically.
|
|
36
39
|
*
|
|
37
|
-
*
|
|
40
|
+
* **Key Features**
|
|
38
41
|
*
|
|
39
|
-
*
|
|
42
|
+
* - **Distributed Coordination**: Works across multiple processes, servers, and containers
|
|
43
|
+
* - **Automatic Expiration**: Locks expire automatically to prevent deadlocks
|
|
44
|
+
* - **Graceful Handling**: Configurable wait behavior for different use cases
|
|
45
|
+
* - **Grace Periods**: Optional lock extension after completion for additional safety
|
|
46
|
+
* - **Topic Integration**: Uses pub/sub for efficient lock release notifications
|
|
47
|
+
* - **Unique Instance IDs**: Prevents lock conflicts between different instances
|
|
48
|
+
* - **Timeout Management**: Configurable durations with intelligent retry logic
|
|
49
|
+
*
|
|
50
|
+
* **Use Cases**
|
|
51
|
+
*
|
|
52
|
+
* Perfect for ensuring single execution in distributed environments:
|
|
53
|
+
* - Database migrations and schema updates
|
|
54
|
+
* - Scheduled job execution (cron-like tasks)
|
|
55
|
+
* - File processing and batch operations
|
|
56
|
+
* - Critical section protection
|
|
57
|
+
* - Resource initialization and cleanup
|
|
58
|
+
* - Singleton service operations
|
|
59
|
+
* - Cache warming and maintenance tasks
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* **Basic lock for scheduled tasks:**
|
|
63
|
+
* ```ts
|
|
64
|
+
* import { $lock } from "alepha/lock";
|
|
65
|
+
*
|
|
66
|
+
* class ScheduledTaskService {
|
|
67
|
+
* dailyReport = $lock({
|
|
68
|
+
* handler: async () => {
|
|
69
|
+
* // This will only run on one server even if multiple servers
|
|
70
|
+
* // trigger the task simultaneously
|
|
71
|
+
* console.log('Generating daily report...');
|
|
72
|
+
*
|
|
73
|
+
* const report = await this.generateDailyReport();
|
|
74
|
+
* await this.sendReportToManagement(report);
|
|
75
|
+
*
|
|
76
|
+
* console.log('Daily report completed');
|
|
77
|
+
* }
|
|
78
|
+
* });
|
|
79
|
+
*
|
|
80
|
+
* async runDailyReport() {
|
|
81
|
+
* // Multiple servers can call this, but only one will execute
|
|
82
|
+
* await this.dailyReport.run();
|
|
83
|
+
* }
|
|
84
|
+
* }
|
|
85
|
+
* ```
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* **Migration lock with wait behavior:**
|
|
89
|
+
* ```ts
|
|
90
|
+
* class DatabaseService {
|
|
91
|
+
* migration = $lock({
|
|
92
|
+
* wait: true, // Wait for other instances to complete migration
|
|
93
|
+
* maxDuration: [10, "minutes"], // Migration timeout
|
|
94
|
+
* handler: async (version: string) => {
|
|
95
|
+
* console.log(`Running migration to version ${version}`);
|
|
96
|
+
*
|
|
97
|
+
* const currentVersion = await this.getCurrentSchemaVersion();
|
|
98
|
+
* if (currentVersion >= version) {
|
|
99
|
+
* console.log(`Already at version ${version}, skipping`);
|
|
100
|
+
* return;
|
|
101
|
+
* }
|
|
102
|
+
*
|
|
103
|
+
* await this.runMigrationScripts(version);
|
|
104
|
+
* await this.updateSchemaVersion(version);
|
|
105
|
+
*
|
|
106
|
+
* console.log(`Migration to ${version} completed`);
|
|
107
|
+
* }
|
|
108
|
+
* });
|
|
109
|
+
*
|
|
110
|
+
* async migrateToVersion(version: string) {
|
|
111
|
+
* // All instances will wait for the first one to complete
|
|
112
|
+
* // before continuing with their startup process
|
|
113
|
+
* await this.migration.run(version);
|
|
114
|
+
* }
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* **Dynamic lock keys with grace periods:**
|
|
120
|
+
* ```ts
|
|
121
|
+
* class FileProcessor {
|
|
122
|
+
* processFile = $lock({
|
|
123
|
+
* name: (filePath: string) => `file-processing:${filePath}`,
|
|
124
|
+
* wait: false, // Don't wait, skip if already processing
|
|
125
|
+
* maxDuration: [30, "minutes"],
|
|
126
|
+
* gracePeriod: [5, "minutes"], // Keep lock for 5min after completion
|
|
127
|
+
* handler: async (filePath: string) => {
|
|
128
|
+
* console.log(`Processing file: ${filePath}`);
|
|
129
|
+
*
|
|
130
|
+
* try {
|
|
131
|
+
* const fileData = await this.readFile(filePath);
|
|
132
|
+
* const processedData = await this.processData(fileData);
|
|
133
|
+
* await this.saveProcessedData(filePath, processedData);
|
|
134
|
+
* await this.moveToCompleted(filePath);
|
|
135
|
+
*
|
|
136
|
+
* console.log(`File processing completed: ${filePath}`);
|
|
137
|
+
* } catch (error) {
|
|
138
|
+
* console.error(`File processing failed: ${filePath}`, error);
|
|
139
|
+
* await this.moveToError(filePath, error.message);
|
|
140
|
+
* throw error;
|
|
141
|
+
* }
|
|
142
|
+
* }
|
|
143
|
+
* });
|
|
144
|
+
*
|
|
145
|
+
* async processUploadedFile(filePath: string) {
|
|
146
|
+
* // Each file gets its own lock, preventing duplicate processing
|
|
147
|
+
* // Grace period prevents immediate reprocessing of the same file
|
|
148
|
+
* await this.processFile.run(filePath);
|
|
149
|
+
* }
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*
|
|
153
|
+
* @example
|
|
154
|
+
* **Resource initialization with conditional grace periods:**
|
|
155
|
+
* ```ts
|
|
156
|
+
* class CacheService {
|
|
157
|
+
* warmCache = $lock({
|
|
158
|
+
* name: (cacheKey: string) => `cache-warming:${cacheKey}`,
|
|
159
|
+
* wait: true, // Wait for cache to be warmed before continuing
|
|
160
|
+
* maxDuration: [15, "minutes"],
|
|
161
|
+
* gracePeriod: (cacheKey: string) => {
|
|
162
|
+
* // Dynamic grace period based on cache importance
|
|
163
|
+
* const criticalCaches = ['user-sessions', 'product-catalog'];
|
|
164
|
+
* return criticalCaches.includes(cacheKey)
|
|
165
|
+
* ? [30, "minutes"] // Longer grace for critical caches
|
|
166
|
+
* : [5, "minutes"]; // Shorter grace for regular caches
|
|
167
|
+
* },
|
|
168
|
+
* handler: async (cacheKey: string, force: boolean = false) => {
|
|
169
|
+
* console.log(`Warming cache: ${cacheKey}`);
|
|
170
|
+
*
|
|
171
|
+
* if (!force && await this.isCacheWarm(cacheKey)) {
|
|
172
|
+
* console.log(`Cache ${cacheKey} is already warm`);
|
|
173
|
+
* return;
|
|
174
|
+
* }
|
|
175
|
+
*
|
|
176
|
+
* const startTime = Date.now();
|
|
177
|
+
*
|
|
178
|
+
* switch (cacheKey) {
|
|
179
|
+
* case 'user-sessions':
|
|
180
|
+
* await this.warmUserSessionsCache();
|
|
181
|
+
* break;
|
|
182
|
+
* case 'product-catalog':
|
|
183
|
+
* await this.warmProductCatalogCache();
|
|
184
|
+
* break;
|
|
185
|
+
* case 'configuration':
|
|
186
|
+
* await this.warmConfigurationCache();
|
|
187
|
+
* break;
|
|
188
|
+
* default:
|
|
189
|
+
* throw new Error(`Unknown cache key: ${cacheKey}`);
|
|
190
|
+
* }
|
|
191
|
+
*
|
|
192
|
+
* const duration = Date.now() - startTime;
|
|
193
|
+
* console.log(`Cache warming completed for ${cacheKey} in ${duration}ms`);
|
|
194
|
+
*
|
|
195
|
+
* await this.markCacheAsWarm(cacheKey);
|
|
196
|
+
* }
|
|
197
|
+
* });
|
|
198
|
+
*
|
|
199
|
+
* async ensureCacheWarmed(cacheKey: string, force: boolean = false) {
|
|
200
|
+
* // Multiple instances can call this, but cache warming happens only once
|
|
201
|
+
* // All instances wait for completion before proceeding
|
|
202
|
+
* await this.warmCache.run(cacheKey, force);
|
|
203
|
+
* }
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* **Critical section protection with custom timeout handling:**
|
|
209
|
+
* ```ts
|
|
210
|
+
* class InventoryService {
|
|
211
|
+
* updateInventory = $lock({
|
|
212
|
+
* name: (productId: string) => `inventory-update:${productId}`,
|
|
213
|
+
* wait: true, // Ensure all inventory updates are sequential
|
|
214
|
+
* maxDuration: [2, "minutes"],
|
|
215
|
+
* gracePeriod: [30, "seconds"], // Brief grace to prevent immediate conflicts
|
|
216
|
+
* handler: async (productId: string, quantity: number, operation: 'add' | 'subtract') => {
|
|
217
|
+
* console.log(`Updating inventory for product ${productId}: ${operation} ${quantity}`);
|
|
218
|
+
*
|
|
219
|
+
* try {
|
|
220
|
+
* // Start transaction for inventory update
|
|
221
|
+
* await this.db.transaction(async (tx) => {
|
|
222
|
+
* const currentInventory = await tx.getInventory(productId);
|
|
223
|
+
*
|
|
224
|
+
* if (operation === 'subtract' && currentInventory.quantity < quantity) {
|
|
225
|
+
* throw new Error(`Insufficient inventory for product ${productId}. Available: ${currentInventory.quantity}, Requested: ${quantity}`);
|
|
226
|
+
* }
|
|
227
|
+
*
|
|
228
|
+
* const newQuantity = operation === 'add'
|
|
229
|
+
* ? currentInventory.quantity + quantity
|
|
230
|
+
* : currentInventory.quantity - quantity;
|
|
231
|
+
*
|
|
232
|
+
* await tx.updateInventory(productId, newQuantity);
|
|
233
|
+
*
|
|
234
|
+
* // Log inventory change for audit
|
|
235
|
+
* await tx.logInventoryChange({
|
|
236
|
+
* productId,
|
|
237
|
+
* operation,
|
|
238
|
+
* quantity,
|
|
239
|
+
* previousQuantity: currentInventory.quantity,
|
|
240
|
+
* newQuantity,
|
|
241
|
+
* timestamp: new Date()
|
|
242
|
+
* });
|
|
243
|
+
*
|
|
244
|
+
* console.log(`Inventory updated for product ${productId}: ${currentInventory.quantity} -> ${newQuantity}`);
|
|
245
|
+
* });
|
|
246
|
+
*
|
|
247
|
+
* // Notify other services about inventory change
|
|
248
|
+
* await this.inventoryChangeNotifier.notify({
|
|
249
|
+
* productId,
|
|
250
|
+
* operation,
|
|
251
|
+
* quantity,
|
|
252
|
+
* timestamp: new Date()
|
|
253
|
+
* });
|
|
254
|
+
*
|
|
255
|
+
* } catch (error) {
|
|
256
|
+
* console.error(`Inventory update failed for product ${productId}`, error);
|
|
257
|
+
* throw error;
|
|
258
|
+
* }
|
|
259
|
+
* }
|
|
260
|
+
* });
|
|
261
|
+
*
|
|
262
|
+
* async addInventory(productId: string, quantity: number) {
|
|
263
|
+
* await this.updateInventory.run(productId, quantity, 'add');
|
|
264
|
+
* }
|
|
265
|
+
*
|
|
266
|
+
* async subtractInventory(productId: string, quantity: number) {
|
|
267
|
+
* await this.updateInventory.run(productId, quantity, 'subtract');
|
|
268
|
+
* }
|
|
269
|
+
* }
|
|
270
|
+
* ```
|
|
40
271
|
*/
|
|
41
272
|
declare const $lock: {
|
|
42
273
|
<TFunc extends AsyncFn>(options: LockDescriptorOptions<TFunc>): LockDescriptor<TFunc>;
|
|
@@ -44,46 +275,204 @@ declare const $lock: {
|
|
|
44
275
|
};
|
|
45
276
|
interface LockDescriptorOptions<TFunc extends AsyncFn> {
|
|
46
277
|
/**
|
|
47
|
-
*
|
|
278
|
+
* The function to execute when the lock is successfully acquired.
|
|
279
|
+
*
|
|
280
|
+
* This function:
|
|
281
|
+
* - Only executes on the instance that successfully acquires the lock
|
|
282
|
+
* - Has exclusive access to the protected resource during execution
|
|
283
|
+
* - Should contain the critical section logic that must not run concurrently
|
|
284
|
+
* - Can be async and perform any operations needed
|
|
285
|
+
* - Will automatically release the lock upon completion or error
|
|
286
|
+
* - Has access to the full Alepha dependency injection container
|
|
287
|
+
*
|
|
288
|
+
* **Handler Design Guidelines**:
|
|
289
|
+
* - Keep critical sections as short as possible to minimize lock contention
|
|
290
|
+
* - Include proper error handling to ensure locks are released
|
|
291
|
+
* - Use timeouts for external operations to prevent deadlocks
|
|
292
|
+
* - Log important operations for debugging and monitoring
|
|
293
|
+
* - Consider idempotency for handlers that might be retried
|
|
294
|
+
*
|
|
295
|
+
* @param ...args - The arguments passed to the lock execution
|
|
296
|
+
* @returns Promise that resolves when the protected operation is complete
|
|
297
|
+
*
|
|
298
|
+
* @example
|
|
299
|
+
* ```ts
|
|
300
|
+
* handler: async (batchId: string) => {
|
|
301
|
+
* console.log(`Processing batch ${batchId} - only one instance will run this`);
|
|
302
|
+
*
|
|
303
|
+
* const batch = await this.getBatchData(batchId);
|
|
304
|
+
* const results = await this.processBatchItems(batch.items);
|
|
305
|
+
* await this.saveBatchResults(batchId, results);
|
|
306
|
+
*
|
|
307
|
+
* console.log(`Batch ${batchId} completed successfully`);
|
|
308
|
+
* }
|
|
309
|
+
* ```
|
|
48
310
|
*/
|
|
49
311
|
handler: TFunc;
|
|
50
312
|
/**
|
|
51
|
-
*
|
|
313
|
+
* Whether the lock should wait for other instances to complete before giving up.
|
|
52
314
|
*
|
|
53
|
-
*
|
|
315
|
+
* **wait = false (default)**:
|
|
316
|
+
* - Non-blocking behavior - if lock is held, immediately return without executing
|
|
317
|
+
* - Perfect for scheduled tasks where you only want one execution per trigger
|
|
318
|
+
* - Use when multiple triggers are acceptable but concurrent execution is not
|
|
319
|
+
* - Examples: periodic cleanup, cron jobs, background maintenance
|
|
54
320
|
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
321
|
+
* **wait = true**:
|
|
322
|
+
* - Blocking behavior - wait for the current lock holder to finish
|
|
323
|
+
* - All instances will eventually execute (one after another)
|
|
324
|
+
* - Perfect for initialization tasks where all instances need the work completed
|
|
325
|
+
* - Examples: database migrations, cache warming, resource initialization
|
|
58
326
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
327
|
+
* **Trade-offs**:
|
|
328
|
+
* - Non-waiting: Better performance, may miss executions if timing is off
|
|
329
|
+
* - Waiting: Guaranteed execution order, slower overall throughput
|
|
62
330
|
*
|
|
63
|
-
*
|
|
64
|
-
* You have 3 servers running a migration script on startup, migration script must be run only once.
|
|
65
|
-
* You want to wait the end of migration for each server as they all require the migration to be completed.
|
|
331
|
+
* @default false
|
|
66
332
|
*
|
|
333
|
+
* @example
|
|
334
|
+
* ```ts
|
|
335
|
+
* // Scheduled task - don't wait, just skip if already running
|
|
336
|
+
* scheduledCleanup = $lock({
|
|
337
|
+
* wait: false, // Skip if cleanup already running
|
|
338
|
+
* handler: async () => { } // perform cleanup
|
|
339
|
+
* });
|
|
67
340
|
*
|
|
68
|
-
*
|
|
341
|
+
* // Migration - wait for completion before proceeding
|
|
342
|
+
* migration = $lock({
|
|
343
|
+
* wait: true, // All instances wait for migration to complete
|
|
344
|
+
* handler: async () => { } // perform migration
|
|
345
|
+
* });
|
|
346
|
+
* ```
|
|
69
347
|
*/
|
|
70
348
|
wait?: boolean;
|
|
71
349
|
/**
|
|
72
|
-
*
|
|
350
|
+
* The unique identifier for the lock.
|
|
351
|
+
*
|
|
352
|
+
* Can be either:
|
|
353
|
+
* - **Static string**: A fixed identifier for the lock
|
|
354
|
+
* - **Dynamic function**: A function that generates the lock key based on arguments
|
|
355
|
+
*
|
|
356
|
+
* **Dynamic Lock Keys**:
|
|
357
|
+
* - Enable per-resource locking (e.g., per-user, per-file, per-product)
|
|
358
|
+
* - Allow fine-grained concurrency control
|
|
359
|
+
* - Prevent unnecessary blocking between unrelated operations
|
|
360
|
+
*
|
|
361
|
+
* **Key Design Guidelines**:
|
|
362
|
+
* - Use descriptive names that indicate the protected resource
|
|
363
|
+
* - Include relevant identifiers for dynamic keys
|
|
364
|
+
* - Keep keys reasonably short but unique
|
|
365
|
+
* - Consider using hierarchical naming (e.g., "service:operation:resource")
|
|
366
|
+
*
|
|
367
|
+
* If not provided, defaults to `{serviceName}:{propertyKey}`.
|
|
368
|
+
*
|
|
369
|
+
* @example "user-migration"
|
|
370
|
+
* @example "daily-report-generation"
|
|
371
|
+
* @example (userId: string) => `user-profile-update:${userId}`
|
|
372
|
+
* @example (fileId: string, operation: string) => `file-${operation}:${fileId}`
|
|
373
|
+
*
|
|
374
|
+
* @example
|
|
375
|
+
* ```ts
|
|
376
|
+
* // Static lock key - all instances compete for the same lock
|
|
377
|
+
* globalCleanup = $lock({
|
|
378
|
+
* name: "system-cleanup",
|
|
379
|
+
* handler: async () => { } // perform cleanup
|
|
380
|
+
* });
|
|
381
|
+
*
|
|
382
|
+
* // Dynamic lock key - per-user locks, users don't block each other
|
|
383
|
+
* updateUserProfile = $lock({
|
|
384
|
+
* name: (userId: string) => `user-update:${userId}`,
|
|
385
|
+
* handler: async (userId: string, data: UserData) => {
|
|
386
|
+
* // Only one update per user at a time, but different users can update concurrently
|
|
387
|
+
* }
|
|
388
|
+
* });
|
|
389
|
+
* ```
|
|
73
390
|
*/
|
|
74
391
|
name?: string | ((...args: Parameters<TFunc>) => string);
|
|
75
392
|
/**
|
|
76
|
-
*
|
|
393
|
+
* Maximum duration the lock can be held before it expires automatically.
|
|
394
|
+
*
|
|
395
|
+
* This prevents deadlocks when a process dies while holding a lock or when
|
|
396
|
+
* operations take longer than expected. The lock will be automatically released
|
|
397
|
+
* after this duration, allowing other instances to proceed.
|
|
398
|
+
*
|
|
399
|
+
* **Duration Guidelines**:
|
|
400
|
+
* - Set based on expected operation duration plus safety margin
|
|
401
|
+
* - Too short: Operations may be interrupted by early expiration
|
|
402
|
+
* - Too long: Failed processes block others for extended periods
|
|
403
|
+
* - Consider worst-case scenarios and external dependency timeouts
|
|
404
|
+
*
|
|
405
|
+
* **Typical Values**:
|
|
406
|
+
* - Quick operations: 30 seconds - 2 minutes
|
|
407
|
+
* - Database operations: 5 - 15 minutes
|
|
408
|
+
* - File processing: 10 - 30 minutes
|
|
409
|
+
* - Large migrations: 30 minutes - 2 hours
|
|
410
|
+
*
|
|
411
|
+
* @default [5, "minutes"]
|
|
77
412
|
*
|
|
78
|
-
* @
|
|
413
|
+
* @example [30, "seconds"] // Quick operations
|
|
414
|
+
* @example [10, "minutes"] // Database migrations
|
|
415
|
+
* @example [1, "hour"] // Long-running batch jobs
|
|
416
|
+
*
|
|
417
|
+
* @example
|
|
418
|
+
* ```ts
|
|
419
|
+
* quickTask = $lock({
|
|
420
|
+
* maxDuration: [2, "minutes"], // Quick timeout for fast operations
|
|
421
|
+
* handler: async () => { } // perform quick task
|
|
422
|
+
* });
|
|
423
|
+
*
|
|
424
|
+
* heavyProcessing = $lock({
|
|
425
|
+
* maxDuration: [30, "minutes"], // Longer timeout for heavy work
|
|
426
|
+
* handler: async () => { } // perform heavy processing
|
|
427
|
+
* });
|
|
428
|
+
* ```
|
|
79
429
|
*/
|
|
80
430
|
maxDuration?: DurationLike;
|
|
81
431
|
/**
|
|
82
|
-
* Additional
|
|
432
|
+
* Additional time to keep the lock active after the handler completes successfully.
|
|
433
|
+
*
|
|
434
|
+
* This provides a "cooling off" period that can be useful for:
|
|
435
|
+
* - Preventing immediate re-execution of the same operation
|
|
436
|
+
* - Giving time for related systems to process the results
|
|
437
|
+
* - Avoiding race conditions with dependent operations
|
|
438
|
+
* - Providing a buffer for cleanup operations
|
|
439
|
+
*
|
|
440
|
+
* Can be either:
|
|
441
|
+
* - **Static duration**: Fixed grace period for all executions
|
|
442
|
+
* - **Dynamic function**: Grace period determined by execution arguments
|
|
443
|
+
* - **undefined**: No grace period, lock released immediately after completion
|
|
444
|
+
*
|
|
445
|
+
* **Grace Period Use Cases**:
|
|
446
|
+
* - File processing: Prevent immediate reprocessing of uploaded files
|
|
447
|
+
* - Cache updates: Allow time for cache propagation
|
|
448
|
+
* - Batch operations: Prevent overlapping batch processing
|
|
449
|
+
* - External API calls: Respect rate limiting requirements
|
|
450
|
+
*
|
|
451
|
+
* @default undefined (no grace period)
|
|
452
|
+
*
|
|
453
|
+
* @example [5, "minutes"] // Fixed 5-minute grace period
|
|
454
|
+
* @example [30, "seconds"] // Short grace for quick operations
|
|
455
|
+
* @example (userId: string) => userId.startsWith("premium") ? [10, "minutes"] : [2, "minutes"]
|
|
83
456
|
*
|
|
84
|
-
*
|
|
457
|
+
* @example
|
|
458
|
+
* ```ts
|
|
459
|
+
* fileProcessor = $lock({
|
|
460
|
+
* gracePeriod: [10, "minutes"], // Prevent reprocessing same file immediately
|
|
461
|
+
* handler: async (filePath: string) => {
|
|
462
|
+
* await this.processFile(filePath);
|
|
463
|
+
* }
|
|
464
|
+
* });
|
|
85
465
|
*
|
|
86
|
-
*
|
|
466
|
+
* userOperation = $lock({
|
|
467
|
+
* gracePeriod: (userId: string, operation: string) => {
|
|
468
|
+
* // Dynamic grace based on operation type
|
|
469
|
+
* return operation === 'migration' ? [30, "minutes"] : [5, "minutes"];
|
|
470
|
+
* },
|
|
471
|
+
* handler: async (userId: string, operation: string) => {
|
|
472
|
+
* await this.performUserOperation(userId, operation);
|
|
473
|
+
* }
|
|
474
|
+
* });
|
|
475
|
+
* ```
|
|
87
476
|
*/
|
|
88
477
|
gracePeriod?: ((...args: Parameters<TFunc>) => DurationLike | undefined) | DurationLike;
|
|
89
478
|
}
|
package/logger.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as _alepha_core0 from "alepha";
|
|
2
2
|
import { Alepha, KIND, LogLevel, LoggerInterface, Static } from "alepha";
|
|
3
|
-
import * as
|
|
3
|
+
import * as typebox0 from "typebox";
|
|
4
4
|
|
|
5
5
|
//#region src/providers/LogDestinationProvider.d.ts
|
|
6
6
|
declare abstract class LogDestinationProvider {
|
|
@@ -27,6 +27,7 @@ declare class Logger implements LoggerInterface {
|
|
|
27
27
|
get context(): string | undefined;
|
|
28
28
|
get level(): string;
|
|
29
29
|
parseLevel(level: string, app: string): LogLevel;
|
|
30
|
+
private matchesPattern;
|
|
30
31
|
asLogLevel(something: string): LogLevel;
|
|
31
32
|
error(message: string, data?: unknown): void;
|
|
32
33
|
warn(message: string, data?: unknown): void;
|
|
@@ -59,10 +60,10 @@ interface LogEntry {
|
|
|
59
60
|
* class MyService {
|
|
60
61
|
* log = $logger();
|
|
61
62
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
*
|
|
63
|
+
* constructor() {
|
|
64
|
+
* this.log.info("Service initialized");
|
|
65
|
+
* // print something like '[23:45:53.326] INFO <app.MyService>: Service initialized'
|
|
66
|
+
* }
|
|
66
67
|
* }
|
|
67
68
|
* ```
|
|
68
69
|
*/
|
|
@@ -76,11 +77,7 @@ interface LoggerDescriptorOptions {
|
|
|
76
77
|
//#endregion
|
|
77
78
|
//#region src/providers/ConsoleColorProvider.d.ts
|
|
78
79
|
declare class ConsoleColorProvider {
|
|
79
|
-
|
|
80
|
-
NO_COLOR?: string | undefined;
|
|
81
|
-
FORCE_COLOR?: string | undefined;
|
|
82
|
-
};
|
|
83
|
-
readonly colors: {
|
|
80
|
+
static readonly COLORS: {
|
|
84
81
|
reset: string;
|
|
85
82
|
grey: string;
|
|
86
83
|
red: string;
|
|
@@ -97,9 +94,15 @@ declare class ConsoleColorProvider {
|
|
|
97
94
|
debug: string;
|
|
98
95
|
trace: string;
|
|
99
96
|
};
|
|
97
|
+
protected readonly env: {
|
|
98
|
+
NO_COLOR?: string | undefined;
|
|
99
|
+
FORCE_COLOR?: string | undefined;
|
|
100
|
+
};
|
|
101
|
+
protected readonly alepha: Alepha;
|
|
100
102
|
protected enabled: boolean;
|
|
101
103
|
constructor();
|
|
102
|
-
|
|
104
|
+
isEnabled(): boolean;
|
|
105
|
+
colorize(color: keyof typeof ConsoleColorProvider.COLORS, text: string, reset?: string): string;
|
|
103
106
|
}
|
|
104
107
|
//#endregion
|
|
105
108
|
//#region src/providers/ConsoleDestinationProvider.d.ts
|
|
@@ -131,6 +134,7 @@ declare class MemoryDestinationProvider extends LogDestinationProvider {
|
|
|
131
134
|
//#region src/providers/SimpleFormatterProvider.d.ts
|
|
132
135
|
declare class SimpleFormatterProvider extends LogFormatterProvider {
|
|
133
136
|
protected color: ConsoleColorProvider;
|
|
137
|
+
protected alepha: Alepha;
|
|
134
138
|
format(entry: LogEntry): string;
|
|
135
139
|
formatTimestamp(d: Date): string;
|
|
136
140
|
protected pad2: (n: number) => string;
|
|
@@ -139,38 +143,6 @@ declare class SimpleFormatterProvider extends LogFormatterProvider {
|
|
|
139
143
|
}
|
|
140
144
|
//#endregion
|
|
141
145
|
//#region src/index.d.ts
|
|
142
|
-
declare const envSchema: _alepha_core0.TObject<{
|
|
143
|
-
/**
|
|
144
|
-
* Default log level for the application.
|
|
145
|
-
*
|
|
146
|
-
* Default by environment:
|
|
147
|
-
* - dev = info
|
|
148
|
-
* - prod = info
|
|
149
|
-
* - test = error
|
|
150
|
-
*
|
|
151
|
-
* Levels are: "trace" | "debug" | "info" | "warn" | "error" | "silent"
|
|
152
|
-
*
|
|
153
|
-
* Level can be set for a specific module:
|
|
154
|
-
*
|
|
155
|
-
* @example
|
|
156
|
-
* LOG_LEVEL=my.module.name:debug,info # Set debug level for my.module.name and info for all other modules
|
|
157
|
-
* LOG_LEVEL=alepha:trace, info # Set trace level for all alepha modules and info for all other modules
|
|
158
|
-
*/
|
|
159
|
-
LOG_LEVEL: _alepha_core0.TOptional<_alepha_core0.TString>;
|
|
160
|
-
/**
|
|
161
|
-
* Built-in log formats.
|
|
162
|
-
* - "json" - JSON format, useful for structured logging and log aggregation. {@link JsonFormatterProvider}
|
|
163
|
-
* - "text" - Simple text format, human-readable, with colors. {@link SimpleFormatterProvider}
|
|
164
|
-
* - "raw" - Raw format, no formatting, just the message. {@link RawFormatterProvider}
|
|
165
|
-
*/
|
|
166
|
-
LOG_FORMAT: _alepha_core0.TOptional<_sinclair_typebox0.TUnsafe<"json" | "text" | "raw">>;
|
|
167
|
-
}>;
|
|
168
|
-
declare module "alepha" {
|
|
169
|
-
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
170
|
-
interface State {
|
|
171
|
-
logLevel?: string;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
146
|
/**
|
|
175
147
|
* Minimalist logger module for Alepha.
|
|
176
148
|
*
|
|
@@ -197,6 +169,22 @@ declare module "alepha" {
|
|
|
197
169
|
* - SimpleFormatterProvider: formats logs as simple text (with colors when possible).
|
|
198
170
|
* - RawFormatterProvider: formats logs as raw text without any formatting.
|
|
199
171
|
*
|
|
172
|
+
* ### Event Emission
|
|
173
|
+
*
|
|
174
|
+
* The logger emits 'log' events that can be listened to by external code, allowing for custom log processing and destinations.
|
|
175
|
+
*
|
|
176
|
+
* ```ts
|
|
177
|
+
* class CustomDestination {
|
|
178
|
+
* onLog = $hook({
|
|
179
|
+
* on: "log",
|
|
180
|
+
* handler: (ev) => {
|
|
181
|
+
* // ev.message (formatted message)
|
|
182
|
+
* // ev.entry (level, raw message, ...)
|
|
183
|
+
* }
|
|
184
|
+
* });
|
|
185
|
+
* }
|
|
186
|
+
* ```
|
|
187
|
+
*
|
|
200
188
|
* ### Log Level
|
|
201
189
|
*
|
|
202
190
|
* You can configure the log level and format via environment variables:
|
|
@@ -210,13 +198,63 @@ declare module "alepha" {
|
|
|
210
198
|
*
|
|
211
199
|
* Log level is also available in the state as `logLevel`, which can be used to dynamically change the log level at runtime.
|
|
212
200
|
* ```ts
|
|
213
|
-
* alepha.state("logLevel", "debug");
|
|
201
|
+
* alepha.state.set("logLevel", "debug");
|
|
214
202
|
* ```
|
|
215
203
|
*
|
|
216
204
|
* Log level is $module aware, meaning you can set different log levels for different modules.
|
|
217
|
-
*
|
|
205
|
+
*
|
|
206
|
+
* **Module-specific configuration:**
|
|
207
|
+
* - `LOG_LEVEL=my.module.name:debug,info` - debug for `my.module.name` (and submodules), info for others
|
|
208
|
+
* - `LOG_LEVEL=alepha:trace,my.app:error,info` - trace for alepha modules, error for my.app modules, info for others
|
|
209
|
+
*
|
|
210
|
+
* **Wildcard patterns (NEW):**
|
|
211
|
+
* - `LOG_LEVEL=alepha.*:debug,info` - debug for all alepha submodules
|
|
212
|
+
* - `LOG_LEVEL=*.test:silent,*.core:trace,info` - silent for test modules, trace for core modules
|
|
213
|
+
*
|
|
214
|
+
* **Robust parsing:**
|
|
215
|
+
* - Empty parts are gracefully skipped: `LOG_LEVEL=",,debug,,"` works fine
|
|
216
|
+
* - Better error messages: "Invalid log level 'bad' for module pattern 'alepha'"
|
|
218
217
|
*/
|
|
219
218
|
declare const AlephaLogger: _alepha_core0.Service<_alepha_core0.Module>;
|
|
219
|
+
declare const envSchema: _alepha_core0.TObject<{
|
|
220
|
+
/**
|
|
221
|
+
* Default log level for the application.
|
|
222
|
+
*
|
|
223
|
+
* Default by environment:
|
|
224
|
+
* - dev = info
|
|
225
|
+
* - prod = info
|
|
226
|
+
* - test = error
|
|
227
|
+
*
|
|
228
|
+
* Levels are: "trace" | "debug" | "info" | "warn" | "error" | "silent"
|
|
229
|
+
*
|
|
230
|
+
* Level can be set for a specific module:
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* LOG_LEVEL=my.module.name:debug,info # Set debug level for my.module.name and info for all other modules
|
|
234
|
+
* LOG_LEVEL=alepha:trace, info # Set trace level for all alepha modules and info for all other modules
|
|
235
|
+
*/
|
|
236
|
+
LOG_LEVEL: _alepha_core0.TOptional<_alepha_core0.TString>;
|
|
237
|
+
/**
|
|
238
|
+
* Built-in log formats.
|
|
239
|
+
* - "json" - JSON format, useful for structured logging and log aggregation. {@link JsonFormatterProvider}
|
|
240
|
+
* - "text" - Simple text format, human-readable, with colors. {@link SimpleFormatterProvider}
|
|
241
|
+
* - "raw" - Raw format, no formatting, just the message. {@link RawFormatterProvider}
|
|
242
|
+
*/
|
|
243
|
+
LOG_FORMAT: _alepha_core0.TOptional<typebox0.TUnsafe<"json" | "text" | "raw">>;
|
|
244
|
+
}>;
|
|
245
|
+
declare module "alepha" {
|
|
246
|
+
interface Env extends Partial<Static<typeof envSchema>> {}
|
|
247
|
+
interface State {
|
|
248
|
+
logLevel?: string;
|
|
249
|
+
}
|
|
250
|
+
interface Hooks {
|
|
251
|
+
log: {
|
|
252
|
+
message: string;
|
|
253
|
+
entry: LogEntry;
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
//# sourceMappingURL=index.d.ts.map
|
|
220
258
|
//#endregion
|
|
221
259
|
export { $logger, AlephaLogger, ConsoleColorProvider, ConsoleDestinationProvider, JsonFormatterProvider, LogDestinationProvider, LogEntry, LogFormatterProvider, Logger, LoggerDescriptorOptions, MemoryDestinationProvider, SimpleFormatterProvider };
|
|
222
260
|
//# sourceMappingURL=index.d.ts.map
|