better-lock 3.0.0 → 3.2.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 (40) hide show
  1. package/README.md +12 -3
  2. package/dist/better_lock.d.ts +13 -7
  3. package/dist/better_lock.js +80 -23
  4. package/dist/better_lock.js.map +1 -1
  5. package/dist/errors.d.ts +1 -0
  6. package/dist/errors.js +7 -1
  7. package/dist/errors.js.map +1 -1
  8. package/dist/options.d.ts +12 -0
  9. package/dist/types.d.ts +4 -0
  10. package/docs/assets/search.js +1 -1
  11. package/docs/classes/BetterLockError.html +3 -3
  12. package/docs/classes/BetterLockExecutionTimeoutError.html +6 -6
  13. package/docs/classes/BetterLockInternalError.html +3 -3
  14. package/docs/classes/BetterLockInvalidArgumentError.html +4 -4
  15. package/docs/classes/BetterLockJobAbortedError.html +5 -5
  16. package/docs/classes/BetterLockQueueOverflowError.html +8 -8
  17. package/docs/classes/BetterLockWaitTimeoutError.html +6 -6
  18. package/docs/classes/_internal_.BetterLock.html +189 -7
  19. package/docs/classes/_internal_.KeyQueue.html +4 -4
  20. package/docs/classes/_internal_.LockJob.html +13 -13
  21. package/docs/functions/_internal_.__home_panta_dev_pantas_better_lock_src_errors_.isAcquisitionFailureError.html +62 -0
  22. package/docs/index.html +13 -2
  23. package/docs/interfaces/BetterLockJobOptions.html +27 -4
  24. package/docs/interfaces/BetterLockOptions.html +26 -8
  25. package/docs/modules/_internal_.__home_panta_dev_pantas_better_lock_src_errors_.html +6 -1
  26. package/docs/types/BetterLockErrorName.html +1 -1
  27. package/docs/types/BetterLockExecutor.html +1 -1
  28. package/docs/types/BetterLockKey.html +1 -1
  29. package/docs/types/BetterLockQueueEjectionStrategy.html +1 -1
  30. package/docs/types/_internal_.ICallback.html +1 -1
  31. package/docs/types/_internal_.ICallbackExecutor.html +1 -1
  32. package/docs/types/_internal_.IErrorExports.html +1 -1
  33. package/docs/types/_internal_.IPromiseExecutor.html +1 -1
  34. package/docs/variables/BetterLock.html +1 -1
  35. package/package.json +1 -1
  36. package/spec/better_lock.spec.ts +160 -0
  37. package/src/better_lock.ts +115 -31
  38. package/src/errors.ts +14 -1
  39. package/src/options.ts +15 -0
  40. package/src/types.ts +5 -0
@@ -5,11 +5,12 @@ import {
5
5
  BetterLockJobAbortedError,
6
6
  BetterLockQueueOverflowError,
7
7
  BetterLockWaitTimeoutError,
8
+ isAcquisitionFailureError,
8
9
  } from './errors';
9
10
  import { KeyQueue, LockJob } from './internals';
10
11
  import { ILockJobOptions, IOptions } from './options';
11
12
  import * as tools from './tools';
12
- import { ICallback, ICallbackExecutor, ILockKey, IPromiseExecutor } from './types';
13
+ import { ICallback, ICallbackExecutor, ILockKey, IPromiseExecutor, IValidResult } from './types';
13
14
 
14
15
  /**
15
16
  * Main better lock class.
@@ -80,30 +81,30 @@ export class BetterLock {
80
81
  );
81
82
  }
82
83
 
83
- public acquire<TResult>(
84
+ public acquire<TResult extends IValidResult>(
84
85
  executor: IPromiseExecutor<TResult>,
85
86
  jobOptions?: ILockJobOptions
86
87
  ): Promise<TResult>;
87
- public acquire<TResult>(
88
+ public acquire<TResult extends IValidResult>(
88
89
  executor: ICallbackExecutor<TResult>,
89
90
  jobOptions?: ILockJobOptions
90
91
  ): Promise<TResult>;
91
- public acquire<TResult>(
92
+ public acquire<TResult extends IValidResult>(
92
93
  executor: ICallbackExecutor<TResult>,
93
94
  callback: ICallback<TResult>,
94
95
  jobOptions?: ILockJobOptions
95
96
  ): void;
96
- public acquire<TResult>(
97
+ public acquire<TResult extends IValidResult>(
97
98
  key: ILockKey | Array<ILockKey>,
98
99
  executor: IPromiseExecutor<TResult>,
99
100
  jobOptions?: ILockJobOptions
100
101
  ): Promise<TResult>;
101
- public acquire<TResult>(
102
+ public acquire<TResult extends IValidResult>(
102
103
  key: ILockKey | Array<ILockKey>,
103
104
  executor: ICallbackExecutor<TResult>,
104
105
  jobOptions?: ILockJobOptions
105
106
  ): Promise<TResult>;
106
- public acquire<TResult>(
107
+ public acquire<TResult extends IValidResult>(
107
108
  key: ILockKey | Array<ILockKey>,
108
109
  executor: ICallbackExecutor<TResult>,
109
110
  callback: ICallback<TResult>,
@@ -125,7 +126,7 @@ export class BetterLock {
125
126
  * @param jobOptions Options to be applied on this job only
126
127
  */
127
128
  public acquire(key, executor?, callback?, jobOptions?) {
128
- if (tools.isFunction(key) || tools.isObject(key)) {
129
+ if (tools.isFunction(key)) {
129
130
  // Presume we weren't given a key (first form)
130
131
  jobOptions = callback;
131
132
  callback = executor;
@@ -133,23 +134,6 @@ export class BetterLock {
133
134
  key = null;
134
135
  }
135
136
 
136
- // Repackage "key" into an array of keys
137
- const keys = [];
138
- if (Array.isArray(key)) {
139
- const arrayOfKeys = key;
140
- // Make sure we don't add duplicates
141
- const seenKeys = new Set();
142
- for (let i = 0; i < arrayOfKeys.length; i++) {
143
- const normalizedKey = this.normalizeAndValidateKey(arrayOfKeys[i]);
144
- if (!seenKeys.has(normalizedKey)) {
145
- keys.push(normalizedKey);
146
- seenKeys.add(normalizedKey);
147
- }
148
- }
149
- } else {
150
- keys.push(this.normalizeAndValidateKey(key));
151
- }
152
-
153
137
  // Create callback wrapper for promise interface
154
138
  if (!tools.isFunction(callback)) {
155
139
  if (jobOptions === undefined) {
@@ -159,6 +143,32 @@ export class BetterLock {
159
143
  callback = tools.callbackWithPromise();
160
144
  }
161
145
 
146
+ // Prepare job options
147
+ const effectiveJobOptions = jobOptions
148
+ ? tools.assign({} as IOptions, [this.options, jobOptions])
149
+ : // No need to create new object since these will be read-only and are subset of global options
150
+ this.options;
151
+
152
+ // Repackage "key" into an array of keys, but only if lock_condition allows it.
153
+ // Otherwise, it stays an array and gets executed immediately.
154
+ const keys = [];
155
+ if (!effectiveJobOptions.lock_condition || effectiveJobOptions.lock_condition(key)) {
156
+ if (Array.isArray(key)) {
157
+ const arrayOfKeys = key;
158
+ // Make sure we don't add duplicates
159
+ const seenKeys = new Set();
160
+ for (let i = 0; i < arrayOfKeys.length; i++) {
161
+ const normalizedKey = this.normalizeAndValidateKey(arrayOfKeys[i]);
162
+ if (!seenKeys.has(normalizedKey)) {
163
+ keys.push(normalizedKey);
164
+ seenKeys.add(normalizedKey);
165
+ }
166
+ }
167
+ } else {
168
+ keys.push(this.normalizeAndValidateKey(key));
169
+ }
170
+ }
171
+
162
172
  // Validate other options
163
173
  if (!tools.isFunction(executor)) {
164
174
  throw new BetterLockInvalidArgumentError(
@@ -177,12 +187,6 @@ export class BetterLock {
177
187
  );
178
188
  }
179
189
 
180
- // Prepare job options
181
- const effectiveJobOptions = jobOptions
182
- ? tools.assign({}, [this.options, jobOptions])
183
- : // No need to create new object since these will be read-only and are subset of global options
184
- this.options;
185
-
186
190
  const job = new LockJob(keys, executor, callback, effectiveJobOptions);
187
191
 
188
192
  // Set incoming stack
@@ -221,6 +225,86 @@ export class BetterLock {
221
225
  return (callback as any).promise;
222
226
  }
223
227
 
228
+ public acquireOr<TResult extends IValidResult>(
229
+ failureResult: TResult,
230
+ executor: IPromiseExecutor<TResult>,
231
+ jobOptions?: ILockJobOptions
232
+ ): Promise<TResult>;
233
+ public acquireOr<TResult extends IValidResult>(
234
+ failureResult: TResult,
235
+ executor: ICallbackExecutor<TResult>,
236
+ jobOptions?: ILockJobOptions
237
+ ): Promise<TResult>;
238
+ public acquireOr<TResult extends IValidResult>(
239
+ failureResult: TResult,
240
+ executor: ICallbackExecutor<TResult>,
241
+ callback: ICallback<TResult>,
242
+ jobOptions?: ILockJobOptions
243
+ ): void;
244
+ public acquireOr<TResult extends IValidResult>(
245
+ key: ILockKey | Array<ILockKey>,
246
+ failureResult: TResult,
247
+ executor: IPromiseExecutor<TResult>,
248
+ jobOptions?: ILockJobOptions
249
+ ): Promise<TResult>;
250
+ public acquireOr<TResult extends IValidResult>(
251
+ key: ILockKey | Array<ILockKey>,
252
+ failureResult: TResult,
253
+ executor: ICallbackExecutor<TResult>,
254
+ jobOptions?: ILockJobOptions
255
+ ): Promise<TResult>;
256
+ public acquireOr<TResult extends IValidResult>(
257
+ key: ILockKey | Array<ILockKey>,
258
+ failureResult: TResult,
259
+ executor: ICallbackExecutor<TResult>,
260
+ callback: ICallback<TResult>,
261
+ jobOptions?: ILockJobOptions
262
+ ): void;
263
+ /**
264
+ * The same as acquire, except if the lock can't be acquired (the executor never gets called), instead of throwing
265
+ * an error, the lock will resolve/callback with the provided failureResult value (for example, a `null`).
266
+ * @param key Named key or array of keys for this particular call. Calls with different keys will be run in parallel. Not required.
267
+ * @param failureResult The value to return if the executor never get called. This happens instead of rejecting.
268
+ * @param executor Function that will run inside the lock. Required.
269
+ * @param callback Function to be called after the executor finishes, or there is an execution timeout. Leave out to use promises.
270
+ * @param jobOptions Options to be applied on this job only
271
+ */
272
+ public acquireOr(key, failureResult, executor?, callback?, jobOptions?) {
273
+ if (tools.isFunction(failureResult)) {
274
+ // There is no key given. Reshuffle.
275
+ jobOptions = callback;
276
+ callback = executor;
277
+ executor = failureResult;
278
+ failureResult = key;
279
+ key = null;
280
+ }
281
+
282
+ if (tools.isFunction(callback)) {
283
+ const originalCallback = callback;
284
+ callback = (err, ...results) => {
285
+ if (isAcquisitionFailureError(err)) {
286
+ originalCallback(null, failureResult);
287
+ } else {
288
+ originalCallback(err, ...results);
289
+ }
290
+ };
291
+ }
292
+
293
+ // noinspection JSVoidFunctionReturnValueUsed
294
+ let promiseResult = this.acquire(key, executor, callback, jobOptions) as any;
295
+
296
+ if (typeof promiseResult !== 'undefined') {
297
+ promiseResult = promiseResult.catch(err => {
298
+ if (isAcquisitionFailureError(err)) {
299
+ return failureResult;
300
+ }
301
+ throw err;
302
+ });
303
+ }
304
+
305
+ return promiseResult;
306
+ }
307
+
224
308
  /**
225
309
  * Update state of given list of queues.
226
310
  */
package/src/errors.ts CHANGED
@@ -51,7 +51,12 @@ export class BetterLockInternalError extends BetterLockError {
51
51
  }
52
52
 
53
53
  export class BetterLockInvalidArgumentError extends BetterLockError {
54
- constructor(lockName, public argument, expected, actual) {
54
+ constructor(
55
+ lockName,
56
+ public argument,
57
+ expected,
58
+ actual
59
+ ) {
55
60
  super(lockName, `Argument "${argument}" must be ${expected} (got: "${actual}")`);
56
61
  this.argument = argument;
57
62
  }
@@ -125,3 +130,11 @@ export class BetterLockJobAbortedError extends BetterLockError {
125
130
  this.keys = job.keys;
126
131
  }
127
132
  }
133
+
134
+ export function isAcquisitionFailureError(err) {
135
+ return (
136
+ (err && err instanceof BetterLockQueueOverflowError) ||
137
+ err instanceof BetterLockWaitTimeoutError ||
138
+ err instanceof BetterLockJobAbortedError
139
+ );
140
+ }
package/src/options.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import { ILockKey } from './types';
2
+
1
3
  /**
2
4
  * Options that apply on LockJob
3
5
  */
@@ -16,6 +18,13 @@ export interface ILockJobOptions {
16
18
  * In any error produced by BetterLock, extend the stack trace to include incoming trace. Overrides BetterLockOptions value.
17
19
  */
18
20
  extend_stack_traces?: boolean;
21
+
22
+ /**
23
+ * Before acquiring lock, call this function with the given key. If condition is rejected, the executor is immediately called, without locking.
24
+ * This can be used to more conveniently opt out of locking, without duplicating or extracting the executor.
25
+ * Overrides BetterLockOptions value.
26
+ */
27
+ lock_condition?: (key: ILockKey | Array<ILockKey>) => boolean;
19
28
  }
20
29
 
21
30
  export type IQueueEjectionStrategy = 'oldest' | 'newest';
@@ -45,6 +54,12 @@ export interface IOptions extends ILockJobOptions {
45
54
  */
46
55
  queue_ejection_strategy?: IQueueEjectionStrategy;
47
56
 
57
+ /**
58
+ * Before acquiring lock, call this function with the given key. If condition is rejected, the executor is immediately called, without locking.
59
+ * This can be used to more conveniently opt out of locking, without duplicating or extracting the executor.
60
+ */
61
+ lock_condition?: (key: ILockKey | Array<ILockKey>) => boolean;
62
+
48
63
  /**
49
64
  * Lock name. This will be written in all logs and error messages, to help you distinguish between different locks
50
65
  */
package/src/types.ts CHANGED
@@ -27,3 +27,8 @@ export type IExecutor<TResult> = ICallbackExecutor<TResult> | IPromiseExecutor<T
27
27
  * Key types that can be used for a lock.
28
28
  */
29
29
  export type ILockKey = string | number | null | undefined;
30
+
31
+ /**
32
+ * Valid results are anything except functions
33
+ */
34
+ export type IValidResult = Exclude<any, (...args: any[]) => any>;