xflight 1.0.0 → 2.0.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 (3) hide show
  1. package/README.md +166 -2
  2. package/package.json +20 -5
  3. package/lib/index.js +0 -113
package/README.md CHANGED
@@ -1,9 +1,173 @@
1
1
  # xflight
2
2
 
3
+ **Avoid redundant async calls by sharing inflight promises for the same key.**
4
+
5
+ ## Description
6
+
7
+ `xflight` is a lightweight Node.js utility that manages inflight promises by key. It ensures that only one asynchronous operation per key is running at a time, returning the same promise for concurrent requests. This is useful for avoiding duplicate network or resource-intensive calls.
8
+
9
+ ## Features
10
+ - Prevents duplicate async operations for the same key
11
+ - Tracks start and check times for inflight items
12
+ - Cleans up after promise resolution or rejection
13
+ - 100% test coverage
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install xflight
19
+ ```
20
+
21
+ ## Usage
22
+
3
23
  ```js
4
24
  const Xflight = require("xflight");
5
25
  const xfl = new Xflight();
6
- xfl.promise(url, () => {
7
- return fetch(url);
26
+
27
+ function fetchData(url) {
28
+ return xfl.promise(url, () => fetch(url));
29
+ }
30
+
31
+ // Multiple calls with the same URL will share the same promise if still pending
32
+ fetchData("https://api.example.com/data").then(console.log);
33
+ fetchData("https://api.example.com/data").then(console.log);
34
+ ```
35
+
36
+ ## Usage in ESM and CommonJS
37
+
38
+ ### ESM (ECMAScript Modules)
39
+ ```ts
40
+ import Inflight from "xflight";
41
+
42
+ const inflight = new Inflight();
43
+ const key = "resource-1";
44
+
45
+ // Deduplicate async calls
46
+ const resultPromise = inflight.promise(key, () => fetch("https://api.example.com/data"));
47
+ ```
48
+
49
+ ### CommonJS
50
+ ```js
51
+ const Inflight = require("xflight").default;
52
+
53
+ const inflight = new Inflight();
54
+ // ... use as shown in examples above
55
+ ```
56
+
57
+ ## API
58
+
59
+ ### `new Xflight([PromiseImpl])`
60
+ - `PromiseImpl` (optional): Custom Promise implementation (e.g., Bluebird, Aveazul, or native Promise).
61
+
62
+ **Note:** By default, the constructor will try to use Bluebird or Aveazul as the Promise implementation if they are available. If you want to always use the native Promise and skip these checks, pass the global Promise as the argument:
63
+
64
+ ```ts
65
+ const inflight = new Inflight(Promise);
66
+ ```
67
+
68
+ ### Methods
69
+
70
+ #### `promise(key, promiseFactory)`
71
+ - `key`: Unique identifier for the inflight operation.
72
+ - `promiseFactory`: Function that returns a promise.
73
+ - **Returns:** The promise from `promiseFactory`, or the existing inflight promise for the key.
74
+
75
+ #### `add(key, value, [now])`
76
+ - Manually add an inflight item. `value` should be a promise.
77
+
78
+ #### `get(key)`
79
+ - Get the current inflight promise for a key, or `undefined`.
80
+
81
+ #### `remove(key)`
82
+ - Remove an inflight item by key.
83
+
84
+ #### `isEmpty`
85
+ - Boolean: true if no inflight items.
86
+
87
+ #### `count`
88
+ - Number of inflight items.
89
+
90
+ #### Timing Methods
91
+ - `getStartTime(key)`: Get start time (ms since epoch) for a key.
92
+ - `time(key, [now])` / `elapseTime(key, [now])`: Elapsed time since start.
93
+ - `getCheckTime(key)`: Get last check time for a key.
94
+ - `lastCheckTime(key, [now])` / `elapseCheckTime(key, [now])`: Elapsed time since last check.
95
+ - `resetCheckTime(key, [now])`: Reset last check time to now.
96
+
97
+ ## Testing
98
+
99
+ To run tests:
100
+
101
+ ```bash
102
+ npm test
103
+ ```
104
+
105
+ Test coverage is enforced at 100% using `nyc`.
106
+
107
+
108
+ ## Examples
109
+
110
+ ### Basic Usage with `promise`
111
+ ```ts
112
+ import Inflight from "xflight";
113
+
114
+ const inflight = new Inflight();
115
+ const key = "resource-1";
116
+
117
+ // Deduplicate async calls
118
+ const resultPromise = inflight.promise(key, () => fetch("https://api.example.com/data"));
119
+ // or, for clarity:
120
+ const resultPromise2 = inflight.promise(key, function promiseFactory() {
121
+ return fetch("https://api.example.com/data");
8
122
  });
9
123
  ```
124
+
125
+ ### Manually Add and Get an Inflight Promise
126
+ ```ts
127
+ const promise = new Promise((resolve) => setTimeout(() => resolve("done"), 100));
128
+ inflight.add("manual-key", promise);
129
+ const inflightPromise = inflight.get("manual-key"); // Promise<string> | undefined
130
+ ```
131
+
132
+ ### Remove an Inflight Item
133
+ ```ts
134
+ inflight.remove("manual-key");
135
+ ```
136
+
137
+ ### Check if Inflight is Empty and Get Count
138
+ ```ts
139
+ console.log(inflight.isEmpty); // true or false
140
+ console.log(inflight.count); // number of inflight items
141
+ ```
142
+
143
+ ### Timing Methods
144
+ ```ts
145
+ inflight.add("timed-key", new Promise(() => {}));
146
+ const start = inflight.getStartTime("timed-key");
147
+ const elapsed = inflight.time("timed-key");
148
+ const elapsedAlias = inflight.elapseTime("timed-key");
149
+ ```
150
+
151
+ ### Check Time Methods
152
+ ```ts
153
+ const check = inflight.getCheckTime("timed-key");
154
+ const sinceLastCheck = inflight.lastCheckTime("timed-key");
155
+ const sinceLastCheckAlias = inflight.elapseCheckTime("timed-key");
156
+ ```
157
+
158
+ ### Reset Check Time
159
+ ```ts
160
+ // Reset for a specific key
161
+ inflight.resetCheckTime("timed-key");
162
+ // Reset for all inflight items
163
+ inflight.resetCheckTime();
164
+ ```
165
+
166
+
167
+ ## License
168
+
169
+ Apache-2.0
170
+
171
+ ---
172
+
173
+ © Joel Chen
package/package.json CHANGED
@@ -1,10 +1,19 @@
1
1
  {
2
2
  "name": "xflight",
3
- "version": "1.0.0",
3
+ "version": "2.0.0",
4
4
  "description": "Handle inflight promise to avoid async duplication",
5
- "main": "lib/index.js",
5
+ "type": "module",
6
+ "exports": {
7
+ ".": {
8
+ "import": "./dist-esm/esm/index.js",
9
+ "require": "./dist-cjs/cjs/index.cjs"
10
+ }
11
+ },
6
12
  "scripts": {
7
- "test": "clap check"
13
+ "test": "vitest run",
14
+ "test:ui": "vitest --ui",
15
+ "coverage": "vitest run --coverage",
16
+ "build": "rm -rf dist-* && tsc --build tsconfig.esm.json && tsc --build tsconfig.cjs.json && ts2mjs --cjs --remove-source --skip-ts dist-cjs"
8
17
  },
9
18
  "repository": {
10
19
  "type": "git",
@@ -20,10 +29,16 @@
20
29
  "author": "Joel Chen",
21
30
  "license": "Apache-2.0",
22
31
  "devDependencies": {
23
- "electrode-archetype-njs-module-dev": "^3.0.0"
32
+ "@types/node": "^22.15.29",
33
+ "@vitest/coverage-v8": "^3.2.0",
34
+ "@xarc/run": "^1.1.2",
35
+ "tsx": "^4.19.4",
36
+ "typescript": "^5.8.3",
37
+ "vitest": "^3.2.0",
38
+ "ts2mjs": "github:jchip/ts2mjs#main"
24
39
  },
25
40
  "dependencies": {
26
- "optional-require": "^1.0.0"
41
+ "optional-require": "^2.0.1"
27
42
  },
28
43
  "nyc": {
29
44
  "all": true,
package/lib/index.js DELETED
@@ -1,113 +0,0 @@
1
- "use strict";
2
-
3
- const assert = require("assert");
4
- const Promise = require("optional-require")(require)("bluebird", { default: global.Promise });
5
-
6
- class Inflight {
7
- constructor(xPromise) {
8
- this._count = 0;
9
- this._inflights = {};
10
- this.Promise = xPromise || Promise;
11
- }
12
-
13
- promise(key, func) {
14
- const f = this._inflights[key];
15
- if (f) {
16
- return f.value;
17
- }
18
-
19
- const remove = () => this.remove(key);
20
-
21
- try {
22
- const p = func();
23
- assert(p && p.then, `xflight: func for key ${key} didn't return a promise`);
24
- this.add(key, p).then(remove, remove);
25
- return p;
26
- } catch (err) {
27
- return this.Promise.reject(err);
28
- }
29
- }
30
-
31
- add(key, value, now) {
32
- assert(this._inflights[key] === undefined, `xflight: item ${key} already exist`);
33
- this._count++;
34
- now = now || Date.now();
35
- this._inflights[key] = { start: now, lastXTime: now, value };
36
-
37
- return value;
38
- }
39
-
40
- get(key) {
41
- const x = this._inflights[key];
42
- return x && x.value;
43
- }
44
-
45
- remove(key) {
46
- assert(this._inflights[key] !== undefined, `xflight: removing non-existing item ${key}`);
47
- assert(
48
- this._count > 0,
49
- `xflight: removing item ${key} but list is empty - count ${this._count}`
50
- );
51
-
52
- this._count--;
53
-
54
- if (this._count === 0) {
55
- this._inflights = {};
56
- } else {
57
- this._inflights[key] = undefined;
58
- }
59
- }
60
-
61
- get isEmpty() {
62
- return this._count === 0;
63
- }
64
-
65
- get count() {
66
- return this._count;
67
- }
68
-
69
- getStartTime(key) {
70
- const x = this._inflights[key];
71
- return x && x.start;
72
- }
73
-
74
- time(key, now) {
75
- const x = this._inflights[key];
76
- if (x) {
77
- return (now || Date.now()) - x.start;
78
- }
79
- return -1;
80
- }
81
-
82
- elapseTime(key, now) {
83
- return this.time(key, now);
84
- }
85
-
86
- getCheckTime(key) {
87
- const x = this._inflights[key];
88
- return x && x.lastXTime;
89
- }
90
-
91
- lastCheckTime(key, now) {
92
- const x = this._inflights[key];
93
- if (x) {
94
- const t = (now || Date.now()) - x.lastXTime;
95
- return t;
96
- }
97
- return -1;
98
- }
99
-
100
- elapseCheckTime(key, now) {
101
- return this.lastCheckTime(key, now);
102
- }
103
-
104
- resetCheckTime(key, now) {
105
- const x = this._inflights[key];
106
- if (x) {
107
- x.lastXTime = now || Date.now();
108
- }
109
- return this;
110
- }
111
- }
112
-
113
- module.exports = Inflight;