@buenojs/bueno 0.8.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.
Files changed (120) hide show
  1. package/.env.example +109 -0
  2. package/.github/workflows/ci.yml +31 -0
  3. package/LICENSE +21 -0
  4. package/README.md +892 -0
  5. package/architecture.md +652 -0
  6. package/bun.lock +70 -0
  7. package/dist/cli/index.js +3233 -0
  8. package/dist/index.js +9014 -0
  9. package/package.json +77 -0
  10. package/src/cache/index.ts +795 -0
  11. package/src/cli/ARCHITECTURE.md +837 -0
  12. package/src/cli/bin.ts +10 -0
  13. package/src/cli/commands/build.ts +425 -0
  14. package/src/cli/commands/dev.ts +248 -0
  15. package/src/cli/commands/generate.ts +541 -0
  16. package/src/cli/commands/help.ts +55 -0
  17. package/src/cli/commands/index.ts +112 -0
  18. package/src/cli/commands/migration.ts +355 -0
  19. package/src/cli/commands/new.ts +804 -0
  20. package/src/cli/commands/start.ts +208 -0
  21. package/src/cli/core/args.ts +283 -0
  22. package/src/cli/core/console.ts +349 -0
  23. package/src/cli/core/index.ts +60 -0
  24. package/src/cli/core/prompt.ts +424 -0
  25. package/src/cli/core/spinner.ts +265 -0
  26. package/src/cli/index.ts +135 -0
  27. package/src/cli/templates/deploy.ts +295 -0
  28. package/src/cli/templates/docker.ts +307 -0
  29. package/src/cli/templates/index.ts +24 -0
  30. package/src/cli/utils/fs.ts +428 -0
  31. package/src/cli/utils/index.ts +8 -0
  32. package/src/cli/utils/strings.ts +197 -0
  33. package/src/config/env.ts +408 -0
  34. package/src/config/index.ts +506 -0
  35. package/src/config/loader.ts +329 -0
  36. package/src/config/merge.ts +285 -0
  37. package/src/config/types.ts +320 -0
  38. package/src/config/validation.ts +441 -0
  39. package/src/container/forward-ref.ts +143 -0
  40. package/src/container/index.ts +386 -0
  41. package/src/context/index.ts +360 -0
  42. package/src/database/index.ts +1142 -0
  43. package/src/database/migrations/index.ts +371 -0
  44. package/src/database/schema/index.ts +619 -0
  45. package/src/frontend/api-routes.ts +640 -0
  46. package/src/frontend/bundler.ts +643 -0
  47. package/src/frontend/console-client.ts +419 -0
  48. package/src/frontend/console-stream.ts +587 -0
  49. package/src/frontend/dev-server.ts +846 -0
  50. package/src/frontend/file-router.ts +611 -0
  51. package/src/frontend/frameworks/index.ts +106 -0
  52. package/src/frontend/frameworks/react.ts +85 -0
  53. package/src/frontend/frameworks/solid.ts +104 -0
  54. package/src/frontend/frameworks/svelte.ts +110 -0
  55. package/src/frontend/frameworks/vue.ts +92 -0
  56. package/src/frontend/hmr-client.ts +663 -0
  57. package/src/frontend/hmr.ts +728 -0
  58. package/src/frontend/index.ts +342 -0
  59. package/src/frontend/islands.ts +552 -0
  60. package/src/frontend/isr.ts +555 -0
  61. package/src/frontend/layout.ts +475 -0
  62. package/src/frontend/ssr/react.ts +446 -0
  63. package/src/frontend/ssr/solid.ts +523 -0
  64. package/src/frontend/ssr/svelte.ts +546 -0
  65. package/src/frontend/ssr/vue.ts +504 -0
  66. package/src/frontend/ssr.ts +699 -0
  67. package/src/frontend/types.ts +2274 -0
  68. package/src/health/index.ts +604 -0
  69. package/src/index.ts +410 -0
  70. package/src/lock/index.ts +587 -0
  71. package/src/logger/index.ts +444 -0
  72. package/src/logger/transports/index.ts +969 -0
  73. package/src/metrics/index.ts +494 -0
  74. package/src/middleware/built-in.ts +360 -0
  75. package/src/middleware/index.ts +94 -0
  76. package/src/modules/filters.ts +458 -0
  77. package/src/modules/guards.ts +405 -0
  78. package/src/modules/index.ts +1256 -0
  79. package/src/modules/interceptors.ts +574 -0
  80. package/src/modules/lazy.ts +418 -0
  81. package/src/modules/lifecycle.ts +478 -0
  82. package/src/modules/metadata.ts +90 -0
  83. package/src/modules/pipes.ts +626 -0
  84. package/src/router/index.ts +339 -0
  85. package/src/router/linear.ts +371 -0
  86. package/src/router/regex.ts +292 -0
  87. package/src/router/tree.ts +562 -0
  88. package/src/rpc/index.ts +1263 -0
  89. package/src/security/index.ts +436 -0
  90. package/src/ssg/index.ts +631 -0
  91. package/src/storage/index.ts +456 -0
  92. package/src/telemetry/index.ts +1097 -0
  93. package/src/testing/index.ts +1586 -0
  94. package/src/types/index.ts +236 -0
  95. package/src/types/optional-deps.d.ts +219 -0
  96. package/src/validation/index.ts +276 -0
  97. package/src/websocket/index.ts +1004 -0
  98. package/tests/integration/cli.test.ts +1016 -0
  99. package/tests/integration/fullstack.test.ts +234 -0
  100. package/tests/unit/cache.test.ts +174 -0
  101. package/tests/unit/cli-commands.test.ts +892 -0
  102. package/tests/unit/cli.test.ts +1258 -0
  103. package/tests/unit/container.test.ts +279 -0
  104. package/tests/unit/context.test.ts +221 -0
  105. package/tests/unit/database.test.ts +183 -0
  106. package/tests/unit/linear-router.test.ts +280 -0
  107. package/tests/unit/lock.test.ts +336 -0
  108. package/tests/unit/middleware.test.ts +184 -0
  109. package/tests/unit/modules.test.ts +142 -0
  110. package/tests/unit/pubsub.test.ts +257 -0
  111. package/tests/unit/regex-router.test.ts +265 -0
  112. package/tests/unit/router.test.ts +373 -0
  113. package/tests/unit/rpc.test.ts +1248 -0
  114. package/tests/unit/security.test.ts +174 -0
  115. package/tests/unit/telemetry.test.ts +371 -0
  116. package/tests/unit/test-cache.test.ts +110 -0
  117. package/tests/unit/test-database.test.ts +282 -0
  118. package/tests/unit/tree-router.test.ts +325 -0
  119. package/tests/unit/validation.test.ts +794 -0
  120. package/tsconfig.json +27 -0
@@ -0,0 +1,587 @@
1
+ /**
2
+ * Distributed Locking
3
+ *
4
+ * Redis-based distributed locks with in-memory fallback.
5
+ * Uses Bun 1.3+ native Redis client for production.
6
+ *
7
+ * Implementation based on Redis SET NX PX pattern with Lua scripts
8
+ * for safe lock release and extension.
9
+ */
10
+
11
+ // ============= Types =============
12
+
13
+ export interface LockConfig {
14
+ driver?: "redis" | "memory";
15
+ url?: string; // Redis URL
16
+ keyPrefix?: string;
17
+ defaultTTL?: number; // Default TTL in milliseconds
18
+ retryCount?: number; // Number of retry attempts
19
+ retryDelay?: number; // Delay between retries in milliseconds
20
+ autoExtend?: boolean; // Auto-extend lock for long operations
21
+ }
22
+
23
+ export interface LockOptions {
24
+ ttl?: number; // Lock TTL in milliseconds
25
+ retryCount?: number; // Override retry count
26
+ retryDelay?: number; // Override retry delay
27
+ }
28
+
29
+ export interface Lock {
30
+ key: string;
31
+ value: string;
32
+ acquired: boolean;
33
+ acquiredAt: number;
34
+ ttl: number;
35
+ expiresAt: number;
36
+ }
37
+
38
+ export interface LockHandle {
39
+ /** Whether the lock was successfully acquired */
40
+ acquired: boolean;
41
+ /** Release the lock */
42
+ release: () => Promise<boolean>;
43
+ /** Extend the lock TTL */
44
+ extend: (ttl?: number) => Promise<boolean>;
45
+ /** Check if lock is still held */
46
+ isValid: () => Promise<boolean>;
47
+ /** Get remaining TTL in milliseconds */
48
+ getRemainingTTL: () => Promise<number>;
49
+ /** The lock key */
50
+ key: string;
51
+ /** The lock value (unique identifier) */
52
+ value: string;
53
+ }
54
+
55
+ // ============= In-Memory Lock Driver =============
56
+
57
+ // Shared lock store for all in-memory lock instances
58
+ const sharedLockStore: Map<string, { value: string; expiresAt: number }> =
59
+ new Map();
60
+ let cleanupInterval: ReturnType<typeof setInterval> | null = null;
61
+
62
+ function ensureCleanup(): void {
63
+ if (!cleanupInterval) {
64
+ cleanupInterval = setInterval(() => {
65
+ const now = Date.now();
66
+ for (const [key, lock] of sharedLockStore.entries()) {
67
+ if (now >= lock.expiresAt) {
68
+ sharedLockStore.delete(key);
69
+ }
70
+ }
71
+ }, 10000);
72
+ }
73
+ }
74
+
75
+ class MemoryLockDriver {
76
+ constructor() {
77
+ // Ensure cleanup is running
78
+ ensureCleanup();
79
+ }
80
+
81
+ async acquire(key: string, value: string, ttl: number): Promise<boolean> {
82
+ const now = Date.now();
83
+ const existing = sharedLockStore.get(key);
84
+
85
+ // Check if lock exists and is still valid
86
+ if (existing && now < existing.expiresAt) {
87
+ return false;
88
+ }
89
+
90
+ // Acquire the lock
91
+ sharedLockStore.set(key, {
92
+ value,
93
+ expiresAt: now + ttl,
94
+ });
95
+
96
+ return true;
97
+ }
98
+
99
+ async release(key: string, value: string): Promise<boolean> {
100
+ const lock = sharedLockStore.get(key);
101
+
102
+ // Only release if we own the lock
103
+ if (lock && lock.value === value) {
104
+ sharedLockStore.delete(key);
105
+ return true;
106
+ }
107
+
108
+ return false;
109
+ }
110
+
111
+ async extend(key: string, value: string, ttl: number): Promise<boolean> {
112
+ const lock = sharedLockStore.get(key);
113
+
114
+ // Only extend if we own the lock
115
+ if (lock && lock.value === value) {
116
+ const now = Date.now();
117
+
118
+ // Check if lock hasn't expired
119
+ if (now >= lock.expiresAt) {
120
+ return false;
121
+ }
122
+
123
+ lock.expiresAt = now + ttl;
124
+ return true;
125
+ }
126
+
127
+ return false;
128
+ }
129
+
130
+ async isValid(key: string, value: string): Promise<boolean> {
131
+ const lock = sharedLockStore.get(key);
132
+ const now = Date.now();
133
+
134
+ return lock !== undefined && lock.value === value && now < lock.expiresAt;
135
+ }
136
+
137
+ async getTTL(key: string, value: string): Promise<number> {
138
+ const lock = sharedLockStore.get(key);
139
+ const now = Date.now();
140
+
141
+ if (!lock || lock.value !== value) {
142
+ return -1;
143
+ }
144
+
145
+ const remaining = lock.expiresAt - now;
146
+ return remaining > 0 ? remaining : -1;
147
+ }
148
+
149
+ destroy(): void {
150
+ // Don't clear the shared store, just stop cleanup if no more instances
151
+ // For simplicity, we keep cleanup running
152
+ }
153
+ }
154
+
155
+ // ============= Redis Lock Driver =============
156
+
157
+ class RedisLockDriver {
158
+ private client: unknown = null;
159
+ private url: string;
160
+ private _isConnected = false;
161
+
162
+ // Lua script for safe lock release
163
+ // Only releases the lock if we own it (value matches)
164
+ private readonly releaseScript = `
165
+ if redis.call("get", KEYS[1]) == ARGV[1] then
166
+ return redis.call("del", KEYS[1])
167
+ else
168
+ return 0
169
+ end
170
+ `;
171
+
172
+ // Lua script for safe lock extension
173
+ // Only extends if we own the lock
174
+ private readonly extendScript = `
175
+ if redis.call("get", KEYS[1]) == ARGV[1] then
176
+ return redis.call("pexpire", KEYS[1], ARGV[2])
177
+ else
178
+ return 0
179
+ end
180
+ `;
181
+
182
+ constructor(url: string) {
183
+ this.url = url;
184
+ }
185
+
186
+ async connect(): Promise<void> {
187
+ try {
188
+ const { RedisClient } = await import("bun");
189
+ this.client = new RedisClient(this.url);
190
+ this._isConnected = true;
191
+ } catch (error) {
192
+ throw new Error(
193
+ `Failed to connect to Redis: ${error instanceof Error ? error.message : String(error)}`,
194
+ );
195
+ }
196
+ }
197
+
198
+ async disconnect(): Promise<void> {
199
+ const client = this.client as { close?: () => Promise<void> } | null;
200
+ if (client?.close) {
201
+ await client.close();
202
+ }
203
+ this._isConnected = false;
204
+ this.client = null;
205
+ }
206
+
207
+ get isConnected(): boolean {
208
+ return this._isConnected;
209
+ }
210
+
211
+ async acquire(key: string, value: string, ttl: number): Promise<boolean> {
212
+ const client = this.client as {
213
+ set: (
214
+ key: string,
215
+ value: string,
216
+ options?: { nx?: boolean; px?: number },
217
+ ) => Promise<string | null>;
218
+ };
219
+
220
+ // SET key value NX PX ttl
221
+ // NX = only set if not exists
222
+ // PX = set expiry in milliseconds
223
+ const result = await client.set(key, value, { nx: true, px: ttl });
224
+ return result === "OK";
225
+ }
226
+
227
+ async release(key: string, value: string): Promise<boolean> {
228
+ const client = this.client as {
229
+ eval: (script: string, keys: string[], args: string[]) => Promise<number>;
230
+ };
231
+
232
+ const result = await client.eval(this.releaseScript, [key], [value]);
233
+ return result === 1;
234
+ }
235
+
236
+ async extend(key: string, value: string, ttl: number): Promise<boolean> {
237
+ const client = this.client as {
238
+ eval: (script: string, keys: string[], args: string[]) => Promise<number>;
239
+ };
240
+
241
+ const result = await client.eval(
242
+ this.extendScript,
243
+ [key],
244
+ [value, String(ttl)],
245
+ );
246
+ return result === 1;
247
+ }
248
+
249
+ async isValid(key: string, value: string): Promise<boolean> {
250
+ const client = this.client as {
251
+ get: (key: string) => Promise<string | null>;
252
+ };
253
+
254
+ const stored = await client.get(key);
255
+ return stored === value;
256
+ }
257
+
258
+ async getTTL(key: string, value: string): Promise<number> {
259
+ const client = this.client as {
260
+ get: (key: string) => Promise<string | null>;
261
+ pttl: (key: string) => Promise<number>;
262
+ };
263
+
264
+ const stored = await client.get(key);
265
+ if (stored !== value) {
266
+ return -1;
267
+ }
268
+
269
+ return client.pttl(key);
270
+ }
271
+ }
272
+
273
+ // ============= Distributed Lock Class =============
274
+
275
+ export class DistributedLock {
276
+ private driver: MemoryLockDriver | RedisLockDriver;
277
+ private driverType: "redis" | "memory";
278
+ private keyPrefix: string;
279
+ private defaultTTL: number;
280
+ private defaultRetryCount: number;
281
+ private defaultRetryDelay: number;
282
+ private _isConnected = false;
283
+
284
+ constructor(config: LockConfig = {}) {
285
+ this.driverType = config.driver ?? "memory";
286
+ this.keyPrefix = config.keyPrefix ?? "lock:";
287
+ this.defaultTTL = config.defaultTTL ?? 30000; // 30 seconds
288
+ this.defaultRetryCount = config.retryCount ?? 3;
289
+ this.defaultRetryDelay = config.retryDelay ?? 200; // 200ms
290
+
291
+ if (this.driverType === "redis" && config.url) {
292
+ this.driver = new RedisLockDriver(config.url);
293
+ } else {
294
+ this.driver = new MemoryLockDriver();
295
+ this._isConnected = true;
296
+ }
297
+ }
298
+
299
+ /**
300
+ * Connect to the lock backend (Redis only)
301
+ */
302
+ async connect(): Promise<void> {
303
+ if (this.driver instanceof RedisLockDriver) {
304
+ await this.driver.connect();
305
+ }
306
+ this._isConnected = true;
307
+ }
308
+
309
+ /**
310
+ * Disconnect from the lock backend
311
+ */
312
+ async disconnect(): Promise<void> {
313
+ if (this.driver instanceof RedisLockDriver) {
314
+ await this.driver.disconnect();
315
+ } else {
316
+ this.driver.destroy();
317
+ }
318
+ this._isConnected = false;
319
+ }
320
+
321
+ /**
322
+ * Check if connected
323
+ */
324
+ get isConnected(): boolean {
325
+ return this._isConnected;
326
+ }
327
+
328
+ /**
329
+ * Get the driver type
330
+ */
331
+ getDriverType(): "redis" | "memory" {
332
+ return this.driverType;
333
+ }
334
+
335
+ /**
336
+ * Generate a unique lock value
337
+ */
338
+ private generateLockValue(): string {
339
+ // Generate a unique identifier using crypto
340
+ const bytes = new Uint8Array(16);
341
+ crypto.getRandomValues(bytes);
342
+ return Array.from(bytes)
343
+ .map((b) => b.toString(16).padStart(2, "0"))
344
+ .join("");
345
+ }
346
+
347
+ /**
348
+ * Try to acquire a lock without retry
349
+ */
350
+ private async tryAcquire(
351
+ key: string,
352
+ value: string,
353
+ ttl: number,
354
+ ): Promise<boolean> {
355
+ return this.driver.acquire(key, value, ttl);
356
+ }
357
+
358
+ /**
359
+ * Acquire a lock
360
+ * Returns a LockHandle that can be used to release or extend the lock
361
+ */
362
+ async acquire(key: string, options: LockOptions = {}): Promise<LockHandle> {
363
+ const fullKey = this.keyPrefix + key;
364
+ const ttl = options.ttl ?? this.defaultTTL;
365
+ const retryCount = options.retryCount ?? this.defaultRetryCount;
366
+ const retryDelay = options.retryDelay ?? this.defaultRetryDelay;
367
+ const value = this.generateLockValue();
368
+
369
+ let acquired = false;
370
+ let attempts = 0;
371
+
372
+ // Try to acquire with retries
373
+ while (!acquired && attempts <= retryCount) {
374
+ acquired = await this.tryAcquire(fullKey, value, ttl);
375
+
376
+ if (!acquired && attempts < retryCount) {
377
+ await this.sleep(retryDelay);
378
+ }
379
+
380
+ attempts++;
381
+ }
382
+
383
+ const acquiredAt = Date.now();
384
+
385
+ return {
386
+ acquired,
387
+ key: fullKey,
388
+ value,
389
+ release: async () => {
390
+ if (!acquired) return false;
391
+ return this.driver.release(fullKey, value);
392
+ },
393
+ extend: async (newTTL?: number) => {
394
+ if (!acquired) return false;
395
+ return this.driver.extend(fullKey, value, newTTL ?? ttl);
396
+ },
397
+ isValid: async () => {
398
+ if (!acquired) return false;
399
+ return this.driver.isValid(fullKey, value);
400
+ },
401
+ getRemainingTTL: async () => {
402
+ if (!acquired) return -1;
403
+ return this.driver.getTTL(fullKey, value);
404
+ },
405
+ };
406
+ }
407
+
408
+ /**
409
+ * Acquire a lock and execute a function
410
+ * Automatically releases the lock when done
411
+ */
412
+ async withLock<T>(
413
+ key: string,
414
+ fn: (lock: LockHandle) => Promise<T>,
415
+ options: LockOptions = {},
416
+ ): Promise<T> {
417
+ const lock = await this.acquire(key, options);
418
+
419
+ if (!lock.acquired) {
420
+ throw new LockAcquireError(`Failed to acquire lock: ${key}`);
421
+ }
422
+
423
+ try {
424
+ return await fn(lock);
425
+ } finally {
426
+ await lock.release();
427
+ }
428
+ }
429
+
430
+ /**
431
+ * Acquire a lock with automatic extension for long-running operations
432
+ */
433
+ async withAutoExtend<T>(
434
+ key: string,
435
+ fn: (lock: LockHandle) => Promise<T>,
436
+ options: LockOptions = {},
437
+ ): Promise<T> {
438
+ const lock = await this.acquire(key, options);
439
+
440
+ if (!lock.acquired) {
441
+ throw new LockAcquireError(`Failed to acquire lock: ${key}`);
442
+ }
443
+
444
+ const ttl = options.ttl ?? this.defaultTTL;
445
+ const extendInterval = ttl * 0.7; // Extend at 70% of TTL
446
+
447
+ let extendTimer: ReturnType<typeof setInterval> | null = null;
448
+ let completed = false;
449
+
450
+ // Setup auto-extend timer
451
+ extendTimer = setInterval(async () => {
452
+ if (!completed) {
453
+ const remaining = await lock.getRemainingTTL();
454
+ if (remaining > 0 && remaining < extendInterval) {
455
+ await lock.extend(ttl);
456
+ }
457
+ }
458
+ }, extendInterval);
459
+
460
+ try {
461
+ const result = await fn(lock);
462
+ completed = true;
463
+ return result;
464
+ } finally {
465
+ if (extendTimer) {
466
+ clearInterval(extendTimer);
467
+ }
468
+ await lock.release();
469
+ }
470
+ }
471
+
472
+ /**
473
+ * Try to acquire a lock without waiting
474
+ * Returns immediately whether the lock was acquired
475
+ */
476
+ async tryLock(key: string, options: LockOptions = {}): Promise<LockHandle> {
477
+ return this.acquire(key, { ...options, retryCount: 0 });
478
+ }
479
+
480
+ /**
481
+ * Check if a lock exists (anyone holds it)
482
+ */
483
+ async isLocked(key: string): Promise<boolean> {
484
+ const fullKey = this.keyPrefix + key;
485
+ const value = this.generateLockValue();
486
+
487
+ // Try to acquire - if successful, it wasn't locked
488
+ const acquired = await this.driver.acquire(fullKey, value, 1);
489
+
490
+ if (acquired) {
491
+ // Release immediately since we just wanted to check
492
+ await this.driver.release(fullKey, value);
493
+ return false;
494
+ }
495
+
496
+ return true;
497
+ }
498
+
499
+ /**
500
+ * Force release a lock (dangerous - use with caution)
501
+ * This will release the lock regardless of ownership
502
+ */
503
+ async forceRelease(key: string): Promise<void> {
504
+ const fullKey = this.keyPrefix + key;
505
+ const value = this.generateLockValue();
506
+ await this.driver.release(fullKey, value);
507
+ }
508
+
509
+ private sleep(ms: number): Promise<void> {
510
+ return new Promise((resolve) => setTimeout(resolve, ms));
511
+ }
512
+ }
513
+
514
+ // ============= Error Classes =============
515
+
516
+ export class LockAcquireError extends Error {
517
+ constructor(message: string) {
518
+ super(message);
519
+ this.name = "LockAcquireError";
520
+ }
521
+ }
522
+
523
+ export class LockTimeoutError extends Error {
524
+ constructor(message: string) {
525
+ super(message);
526
+ this.name = "LockTimeoutError";
527
+ }
528
+ }
529
+
530
+ // ============= Factory Functions =============
531
+
532
+ /**
533
+ * Create a distributed lock instance
534
+ */
535
+ export function createDistributedLock(config?: LockConfig): DistributedLock {
536
+ return new DistributedLock(config);
537
+ }
538
+
539
+ /**
540
+ * Create a Redis-based distributed lock
541
+ */
542
+ export function createRedisLock(
543
+ url: string,
544
+ options?: Omit<LockConfig, "driver" | "url">,
545
+ ): DistributedLock {
546
+ return new DistributedLock({ driver: "redis", url, ...options });
547
+ }
548
+
549
+ /**
550
+ * Create an in-memory lock (for development/testing)
551
+ */
552
+ export function createMemoryLock(): DistributedLock {
553
+ return new DistributedLock({ driver: "memory" });
554
+ }
555
+
556
+ // ============= Convenience Exports =============
557
+
558
+ // Default lock instance (in-memory for convenience)
559
+ let defaultLock: DistributedLock | null = null;
560
+
561
+ /**
562
+ * Get the default lock instance
563
+ */
564
+ export function getDefaultLock(): DistributedLock {
565
+ if (!defaultLock) {
566
+ defaultLock = new DistributedLock({ driver: "memory" });
567
+ }
568
+ return defaultLock;
569
+ }
570
+
571
+ /**
572
+ * Set the default lock instance
573
+ */
574
+ export function setDefaultLock(lock: DistributedLock): void {
575
+ defaultLock = lock;
576
+ }
577
+
578
+ /**
579
+ * Acquire a lock using the default instance
580
+ */
581
+ export async function lock<T>(
582
+ key: string,
583
+ fn: () => Promise<T>,
584
+ options?: LockOptions,
585
+ ): Promise<T> {
586
+ return getDefaultLock().withLock(key, async () => fn(), options);
587
+ }