aveazul 1.0.0 → 1.0.1

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 CHANGED
@@ -1,4 +1,4 @@
1
- # AveAzul
1
+ # AveAzul.js
2
2
 
3
3
  AveAzul ("Blue Bird" in Spanish) serves as a near drop-in replacement for Bluebird. It's built on native Promise, by extending it with the familiar utility methods from Bluebird.
4
4
 
@@ -14,6 +14,10 @@ Further, if you like Bluebird's API but want to use native Promises, AveAzul giv
14
14
  - Implements most commonly used Bluebird methods
15
15
  - Comprehensive test suite ensuring compatibility
16
16
 
17
+ ## Requirements
18
+
19
+ - node.js version >= 12
20
+
17
21
  ## Installation
18
22
 
19
23
  ```bash
@@ -143,6 +147,7 @@ Key differences to be aware of:
143
147
  - `all()` - Like Promise.all(), resolves when all promises resolve, rejects if any reject
144
148
  - `call(propertyName, ...args)` - Call a method on the resolved value with the provided arguments
145
149
  - `asCallback(callback, options?)` - Register a Node-style callback that handles the resolution or rejection
150
+ - `error(handler)` - Like catch(), but only catches operational errors, letting programmer errors bubble up
146
151
 
147
152
  ### Static Methods
148
153
 
@@ -164,6 +169,10 @@ Key differences to be aware of:
164
169
  - `using(resources, fn)` - Manage resources with automatic cleanup
165
170
  - `join(...values, handler?)` - Wait for multiple promises and pass their resolved values as separate arguments to the handler function. If no handler is provided, behaves like Promise.all
166
171
 
172
+ ### Error Types
173
+
174
+ - `AveAzul.OperationalError` - Error type for representing expected operational errors (network failures, validation errors, etc.)
175
+
167
176
  ### PromisifyAll Options
168
177
 
169
178
  - `suffix` (default: 'Async') - Suffix to append to promisified method names
package/lib/any.js ADDED
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ const { toArray, isPromise } = require("./util");
4
+ const { AggregateError } = require("@jchip/error");
5
+
6
+ function addStaticAny(AveAzul, force = false) {
7
+ if (force || !AveAzul.any) {
8
+ AveAzul.any = function (args) {
9
+ try {
10
+ args = toArray(args);
11
+ } catch (error) {
12
+ return AveAzul.reject(error);
13
+ }
14
+
15
+ if (args.length === 0) {
16
+ return AveAzul.reject(
17
+ new RangeError(
18
+ "Input array must contain at least 1 items but contains only 0 items"
19
+ )
20
+ );
21
+ }
22
+
23
+ return new AveAzul((resolve, reject) => {
24
+ const len = args.length;
25
+ let settled = false;
26
+ const errors = [];
27
+
28
+ const doFinish = (value) => {
29
+ if (settled) return;
30
+ settled = true;
31
+ resolve(value);
32
+ };
33
+
34
+ const addError = (err) => {
35
+ errors.push(err);
36
+ if (!settled && errors.length >= len) {
37
+ settled = true;
38
+ reject(new AggregateError(errors));
39
+ }
40
+ };
41
+
42
+ for (let i = 0; i < len; i++) {
43
+ const arg = args[i];
44
+ if (isPromise(arg)) {
45
+ arg.then(doFinish, addError);
46
+ } else {
47
+ doFinish(arg);
48
+ }
49
+ }
50
+ });
51
+ };
52
+ }
53
+ }
54
+
55
+ module.exports.addStaticAny = addStaticAny;
package/lib/aveazul.js CHANGED
@@ -5,8 +5,9 @@ const { promisify } = require("./promisify");
5
5
  const { promisifyAll } = require("./promisify-all");
6
6
  const { Disposer } = require("./disposer");
7
7
  const { using } = require("./using");
8
- const { isPromise, triggerUncaughtException } = require("./util");
9
-
8
+ const { isPromise, triggerUncaughtException, toArray } = require("./util");
9
+ const { AggregateError } = require("@jchip/error");
10
+ const { OperationalError, isOperationalError } = require("./operational-error");
10
11
  /**
11
12
  * @fileoverview
12
13
  * AveAzul ("Blue Bird" in Spanish) - Extended Promise class that provides Bluebird like utility methods
@@ -86,20 +87,7 @@ class AveAzul extends Promise {
86
87
  */
87
88
  any() {
88
89
  return this.then((args) => {
89
- if (!Array.isArray(args)) {
90
- // Check if args is iterable
91
- if (args != null && typeof args[Symbol.iterator] === "function") {
92
- // Convert iterable to array, must do this to get the length, in order
93
- // to detect if too many errors occurred and completion is impossible.
94
- args = Array.from(args);
95
- } else {
96
- throw new TypeError(
97
- "expecting an array or an iterable object but got " + args
98
- );
99
- }
100
- }
101
-
102
- return AveAzul.any(args);
90
+ return AveAzul.any(toArray(args));
103
91
  });
104
92
  }
105
93
 
@@ -140,7 +128,12 @@ class AveAzul extends Promise {
140
128
  * @returns {Promise} Promise that rejects if timeout occurs
141
129
  */
142
130
  timeout(ms, message = "operation timed out") {
143
- return AveAzul.resolve(xaa.timeout(ms, message).run(this));
131
+ return xaa
132
+ .timeout(ms, message, {
133
+ Promise: AveAzul,
134
+ TimeoutError: OperationalError,
135
+ })
136
+ .run(this);
144
137
  }
145
138
 
146
139
  /**
@@ -291,18 +284,7 @@ class AveAzul extends Promise {
291
284
 
292
285
  some(count) {
293
286
  return this.then((args) => {
294
- if (!Array.isArray(args)) {
295
- // Check if args is iterable
296
- if (args != null && typeof args[Symbol.iterator] === "function") {
297
- // Convert iterable to array, must do this to get the length, in order
298
- // to detect if too many errors occurred and completion is impossible.
299
- args = Array.from(args);
300
- } else {
301
- throw new TypeError(
302
- "expecting an array or an iterable object but got " + args
303
- );
304
- }
305
- }
287
+ args = toArray(args);
306
288
 
307
289
  return new AveAzul((resolve, reject) => {
308
290
  // If too many promises are rejected so that the promise can never become fulfilled,
@@ -314,17 +296,23 @@ class AveAzul extends Promise {
314
296
  const results = [];
315
297
  const len = args.length;
316
298
 
299
+ let settled = false;
300
+
317
301
  const addDone = (result) => {
302
+ if (settled) return;
318
303
  results.push(result);
319
304
  if (results.length >= count) {
305
+ settled = true;
320
306
  // Resolve with exactly count results to match Bluebird's behavior
321
307
  resolve(results.slice(0, count));
322
308
  }
323
309
  };
324
310
 
325
311
  const addError = (err) => {
312
+ if (settled) return;
326
313
  errors.push(err);
327
314
  if (len - errors.length < count) {
315
+ settled = true;
328
316
  reject(new AggregateError(errors, `aggregate error`));
329
317
  }
330
318
  };
@@ -348,19 +336,7 @@ class AveAzul extends Promise {
348
336
  */
349
337
  all() {
350
338
  return this.then((value) => {
351
- if (!Array.isArray(value)) {
352
- // Check if value is iterable
353
- if (value != null && typeof value[Symbol.iterator] === "function") {
354
- // Convert iterable to array
355
- value = Array.from(value);
356
- } else {
357
- throw new TypeError(
358
- "expecting an array or an iterable object but got " + value
359
- );
360
- }
361
- }
362
-
363
- return AveAzul.all(value);
339
+ return AveAzul.all(toArray(value));
364
340
  });
365
341
  }
366
342
 
@@ -420,6 +396,21 @@ class AveAzul extends Promise {
420
396
  return obj[methodName].call(obj, ...args);
421
397
  });
422
398
  }
399
+
400
+ /**
401
+ * Catches only operational errors and passes them to the handler.
402
+ * Programmer errors (non-operational) are rethrown.
403
+ * @param {Function} handler - Function to handle operational errors
404
+ * @returns {Promise} - Promise with the error handled or rethrown
405
+ */
406
+ error(handler) {
407
+ return this.catch((err) => {
408
+ if (isOperationalError(err)) {
409
+ return handler(err);
410
+ }
411
+ throw err;
412
+ });
413
+ }
423
414
  }
424
415
 
425
416
  /**
@@ -584,7 +575,7 @@ AveAzul.using = (resources, ...args) => {
584
575
  * @returns {Promise} Promise that resolves with the handler's return value
585
576
  */
586
577
  AveAzul.join = function (...args) {
587
- if (args.length > 1 && typeof args.at(-1) === "function") {
578
+ if (args.length > 1 && typeof args[args.length - 1] === "function") {
588
579
  const handler = args.pop();
589
580
  return AveAzul.all(args).then((results) => handler(...results));
590
581
  } else {
@@ -641,8 +632,14 @@ AveAzul.some = function (promises, count) {
641
632
  return AveAzul.resolve(promises).some(count);
642
633
  };
643
634
 
635
+ const { addStaticAny } = require("./any");
636
+ addStaticAny(AveAzul);
637
+
644
638
  // Setup the not implemented methods
645
639
  const { setupNotImplemented } = require("./not-implemented");
646
640
  setupNotImplemented(AveAzul);
647
641
 
642
+ // Add these static properties after the class definition
643
+ AveAzul.OperationalError = OperationalError;
644
+
648
645
  module.exports = AveAzul;
@@ -13,7 +13,6 @@ function createInstanceNotImplemented(AveAzul) {
13
13
  "then",
14
14
  "spread",
15
15
  "catch",
16
- "error",
17
16
  "finally",
18
17
  "bind",
19
18
  "isFulfilled",
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * OperationalError class for representing errors that are expected during normal operation
5
+ * Similar to Bluebird's OperationalError
6
+ */
7
+ class OperationalError extends Error {
8
+ constructor(message) {
9
+ super(message);
10
+ this.name = "OperationalError";
11
+ this.isOperational = true;
12
+
13
+ // Capture stack trace
14
+ if (Error.captureStackTrace) {
15
+ Error.captureStackTrace(this, this.constructor);
16
+ }
17
+ }
18
+ }
19
+
20
+ /**
21
+ * Check if an error is an operational error
22
+ * @param {*} error - Error to check
23
+ * @returns {boolean} True if the error is operational
24
+ */
25
+ function isOperationalError(error) {
26
+ if (!error || typeof error !== "object") return false;
27
+ return error instanceof OperationalError || error.isOperational === true;
28
+ }
29
+
30
+ /**
31
+ * Check if an error is a programmer error (unexpected, likely a bug)
32
+ * @param {*} error - Error to check
33
+ * @returns {boolean} True if the error is a programmer error
34
+ */
35
+ function isProgrammerError(error) {
36
+ if (!error || typeof error !== "object") return false;
37
+ return !isOperationalError(error);
38
+ }
39
+
40
+ // Only export the OperationalError class
41
+ module.exports = {
42
+ OperationalError,
43
+ // Internal utilities used by AveAzul.prototype.error
44
+ isOperationalError,
45
+ isProgrammerError,
46
+ };
@@ -76,7 +76,7 @@ function promisifyAll2(obj, options) {
76
76
  }
77
77
 
78
78
  obj[promisifiedKey] = options.promisifier(value, defaultPromisifier, {
79
- context: obj,
79
+ // context: obj, // promisified function should get the binded object using this
80
80
  copyProps: false,
81
81
  multiArgs: options.multiArgs,
82
82
  Promise: options.Promise,
package/lib/promisify.js CHANGED
@@ -22,7 +22,7 @@ module.exports.promisify = function promisify(fn, _options) {
22
22
  const Promise = options.Promise;
23
23
  const multiArgs = !!options.multiArgs;
24
24
 
25
- const promisifiedFn = (...args) => {
25
+ const promisifiedFn = function (...args) {
26
26
  return new Promise((resolve, reject) => {
27
27
  // add a callback to the end of the arguments to transfer the result to the promise
28
28
  args.push((err, ...values) => {
@@ -37,7 +37,7 @@ module.exports.promisify = function promisify(fn, _options) {
37
37
  });
38
38
 
39
39
  // call the original function with the updated args
40
- fn.call(options.context, ...args);
40
+ fn.call(options.context || this, ...args);
41
41
  });
42
42
  };
43
43
 
package/lib/using.js CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { Disposer } = require("./disposer");
4
4
  const { isPromise } = require("./util");
5
+ const { AggregateError } = require("@jchip/error");
5
6
 
6
7
  const SYM_FN_DISPOSE = Symbol("fnDispose");
7
8
  /**
@@ -28,6 +29,25 @@ function using(resources, handler, Promise, asArray) {
28
29
  // Expect Promise to be AveAzul or Bluebird that has map method
29
30
  const acquisitionErrors = [];
30
31
 
32
+ // Helper function to process a disposer
33
+ const processDisposer = async (resource, disposer) => {
34
+ try {
35
+ const res = await disposer._promise;
36
+ resource._result = res;
37
+ resource[SYM_FN_DISPOSE] = disposer._data;
38
+ } catch (error) {
39
+ acquisitionErrors.push(error);
40
+ resource._error = error;
41
+ }
42
+ return resource;
43
+ };
44
+
45
+ // Helper to check if something is a disposer
46
+ const isDisposer = (obj) =>
47
+ obj &&
48
+ (obj instanceof Disposer ||
49
+ (obj._promise && typeof obj._data === "function"));
50
+
31
51
  const acquireResources = () => {
32
52
  const promiseRes = resources.map((resource) => {
33
53
  // if it's a promise-like, wait for its resolved value
@@ -38,27 +58,21 @@ function using(resources, handler, Promise, asArray) {
38
58
  });
39
59
 
40
60
  return Promise.map(promiseRes, async (resource) => {
41
- if (
42
- resource &&
43
- (resource instanceof Disposer ||
44
- (resource._promise && typeof resource._data === "function"))
45
- ) {
46
- try {
47
- const res = await resource._promise;
48
- resource._result = res;
49
- resource[SYM_FN_DISPOSE] = resource._data;
50
- } catch (error) {
51
- acquisitionErrors.push(error);
52
- resource._error = error;
53
- }
54
- return resource;
61
+ // If it's directly a disposer
62
+ if (isDisposer(resource)) {
63
+ return processDisposer(resource, resource);
55
64
  }
56
65
 
57
66
  // if it's a promise like, wait for its resolved value
58
67
  if (resource && resource.___promise) {
59
68
  try {
60
69
  const res = await resource.___promise;
61
- resource._result = res;
70
+ // Check if the resolved value is a disposer
71
+ if (isDisposer(res)) {
72
+ return processDisposer(resource, res);
73
+ } else {
74
+ resource._result = res;
75
+ }
62
76
  } catch (error) {
63
77
  acquisitionErrors.push(error);
64
78
  resource._error = error;
package/lib/util.js CHANGED
@@ -183,6 +183,23 @@ function triggerUncaughtException(error) {
183
183
  }, 0);
184
184
  }
185
185
 
186
+ function toArray(args) {
187
+ if (!Array.isArray(args)) {
188
+ // Check if args is iterable
189
+ if (args != null && typeof args[Symbol.iterator] === "function") {
190
+ // Convert iterable to array, must do this to get the length, in order
191
+ // to detect if too many errors occurred and completion is impossible.
192
+ args = Array.from(args);
193
+ } else {
194
+ throw new TypeError(
195
+ "expecting an array or an iterable object but got " + args
196
+ );
197
+ }
198
+ }
199
+
200
+ return args;
201
+ }
202
+
186
203
  module.exports.copyOwnProperties = copyOwnProperties;
187
204
  module.exports.isClass = isClass;
188
205
  module.exports.isIdentifier = isIdentifier;
@@ -192,3 +209,4 @@ module.exports.isPromise = isPromise;
192
209
  module.exports.triggerUncaughtException = triggerUncaughtException;
193
210
  module.exports.getObjectKeys = getObjectKeys;
194
211
  module.exports.isExcludedPrototype = isExcludedPrototype;
212
+ module.exports.toArray = toArray;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "aveazul",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "Bluebird drop-in replacement built on native Promise",
5
5
  "main": "lib/aveazul.js",
6
6
  "homepage": "https://github.com/jchip/aveazul",
@@ -53,18 +53,24 @@
53
53
  "any",
54
54
  "props",
55
55
  "filter",
56
- "reduce"
56
+ "reduce",
57
+ "aveazul",
58
+ "aveazul.js"
57
59
  ],
58
60
  "repository": {
59
61
  "type": "git",
60
62
  "url": "git+https://github.com/jchip/aveazul.git"
61
63
  },
62
64
  "dependencies": {
63
- "xaa": "^1.7.3"
65
+ "@jchip/error": "^1.0.3",
66
+ "xaa": "^1.8.0"
64
67
  },
65
68
  "devDependencies": {
66
69
  "bluebird": "^3.7.2",
67
- "jest": "^29.7.0",
70
+ "jest": "^28.0.0",
68
71
  "rimraf": "^3.0.1"
72
+ },
73
+ "engines": {
74
+ "node": ">=12.0.0"
69
75
  }
70
76
  }