@atlaskit/node-data-provider 7.3.0 → 7.4.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/CHANGELOG.md CHANGED
@@ -1,5 +1,17 @@
1
1
  # @atlaskit/node-data-provider
2
2
 
3
+ ## 7.4.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`fdba2e94783b7`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/fdba2e94783b7) -
8
+ [https://product-fabric.atlassian.net/browse/ED-29638](ED-29638) - fix editor NodeDataProvider
9
+ network requests deduplication
10
+
11
+ ### Patch Changes
12
+
13
+ - Updated dependencies
14
+
3
15
  ## 7.3.0
4
16
 
5
17
  ### Minor Changes
package/dist/cjs/index.js CHANGED
@@ -15,12 +15,6 @@ Object.defineProperty(exports, "findNodesToPrefetch", {
15
15
  return _findNodesToPrefetch.findNodesToPrefetch;
16
16
  }
17
17
  });
18
- Object.defineProperty(exports, "isPromise", {
19
- enumerable: true,
20
- get: function get() {
21
- return _nodeDataProvider.isPromise;
22
- }
23
- });
24
18
  Object.defineProperty(exports, "prefetchNodeDataProvidersData", {
25
19
  enumerable: true,
26
20
  get: function get() {
@@ -5,9 +5,7 @@ Object.defineProperty(exports, "__esModule", {
5
5
  value: true
6
6
  });
7
7
  exports.NodeDataProvider = void 0;
8
- exports.isPromise = isPromise;
9
8
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
10
- var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
11
9
  var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
12
10
  var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
13
11
  var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
@@ -32,7 +30,7 @@ var _coreUtils = require("@atlaskit/editor-common/core-utils");
32
30
  * @example
33
31
  * {
34
32
  * 'node-id-1': { source: 'ssr', data: { value: 'some data' } },
35
- * 'node-id-2': { source: 'network', data: Promise.resolve({ value: 'other data' }) }
33
+ * 'node-id-2': { source: 'network', data: { value: 'other data' } }
36
34
  * }
37
35
  */
38
36
  /**
@@ -51,6 +49,7 @@ var NodeDataProvider = exports.NodeDataProvider = /*#__PURE__*/function () {
51
49
  (0, _classCallCheck2.default)(this, NodeDataProvider);
52
50
  this.cacheVersion = 0;
53
51
  this.cache = {};
52
+ this.networkRequestsInFlight = {};
54
53
  }
55
54
 
56
55
  /**
@@ -137,102 +136,114 @@ var NodeDataProvider = exports.NodeDataProvider = /*#__PURE__*/function () {
137
136
  key: "getDataAsync",
138
137
  value: function () {
139
138
  var _getDataAsync = (0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee(node, callback) {
140
- var jsonNode, _dataKey, dataFromCache, cacheVersionBeforeRequest, dataPromise, data;
139
+ var jsonNode, dataKey, dataFromCache, cacheVersionBeforeRequest, networkRequestInFlightKey, networkRequestInFlight, data, dataPromise, _data;
141
140
  return _regenerator.default.wrap(function _callee$(_context) {
142
141
  while (1) switch (_context.prev = _context.next) {
143
142
  case 0:
144
- _context.prev = 0;
145
143
  jsonNode = 'toJSON' in node ? node.toJSON() : node;
146
144
  if (this.isNodeSupported(jsonNode)) {
147
- _context.next = 5;
145
+ _context.next = 4;
148
146
  break;
149
147
  }
150
148
  // eslint-disable-next-line no-console
151
149
  console.error("The ".concat(this.constructor.name, " doesn't support Node ").concat(jsonNode.type, "."));
152
150
  return _context.abrupt("return");
153
- case 5:
154
- _dataKey = this.nodeDataKey(jsonNode);
155
- dataFromCache = this.cache[_dataKey];
151
+ case 4:
152
+ dataKey = this.nodeDataKey(jsonNode);
153
+ dataFromCache = this.cache[dataKey];
156
154
  if (!(dataFromCache !== undefined)) {
157
- _context.next = 20;
155
+ _context.next = 10;
158
156
  break;
159
157
  }
160
- if (!isPromise(dataFromCache.data)) {
161
- _context.next = 17;
162
- break;
163
- }
164
- _context.t0 = callback;
165
- _context.next = 12;
166
- return dataFromCache.data;
167
- case 12:
168
- _context.t1 = _context.sent;
169
- _context.t2 = {
170
- data: _context.t1
171
- };
172
- (0, _context.t0)(_context.t2);
173
- _context.next = 18;
174
- break;
175
- case 17:
158
+ // If we have the data in the SSR data, we can use it directly
176
159
  callback({
177
160
  data: dataFromCache.data
178
161
  });
179
- case 18:
180
162
  if (!(0, _coreUtils.isSSR)()) {
181
- _context.next = 20;
163
+ _context.next = 10;
182
164
  break;
183
165
  }
184
166
  return _context.abrupt("return");
185
- case 20:
167
+ case 10:
186
168
  if (!((dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.source) !== 'network')) {
187
- _context.next = 29;
169
+ _context.next = 42;
188
170
  break;
189
171
  }
190
172
  // Store the current cache version before making the request,
191
173
  // so we can check if the cache has changed while we are waiting for the network response.
192
- cacheVersionBeforeRequest = this.cacheVersion;
174
+ cacheVersionBeforeRequest = this.cacheVersion; // Create a unique key for the in-flight network request
175
+ // based on the cache version and the data key.
176
+ networkRequestInFlightKey = "".concat(cacheVersionBeforeRequest, "-").concat(dataKey); // Check if there is already a network request in flight for this data
177
+ // to avoid duplicate requests.
178
+ networkRequestInFlight = this.networkRequestsInFlight[networkRequestInFlightKey];
179
+ if (!networkRequestInFlight) {
180
+ _context.next = 26;
181
+ break;
182
+ }
183
+ _context.prev = 15;
184
+ _context.next = 18;
185
+ return networkRequestInFlight;
186
+ case 18:
187
+ data = _context.sent;
188
+ callback({
189
+ data: data
190
+ });
191
+ _context.next = 25;
192
+ break;
193
+ case 22:
194
+ _context.prev = 22;
195
+ _context.t0 = _context["catch"](15);
196
+ callback({
197
+ error: _context.t0 instanceof Error ? _context.t0 : new Error(String(_context.t0))
198
+ });
199
+ case 25:
200
+ return _context.abrupt("return");
201
+ case 26:
202
+ _context.prev = 26;
193
203
  dataPromise = this.fetchNodesData([jsonNode]).then(function (_ref3) {
194
204
  var _ref4 = (0, _slicedToArray2.default)(_ref3, 1),
195
205
  value = _ref4[0];
196
206
  return value;
197
- }); // Store the promise in the cache to avoid multiple requests for the same data
198
- this.cache[_dataKey] = {
199
- source: 'network',
200
- data: dataPromise
201
- };
202
- _context.next = 26;
207
+ }); // Store the promise in the in-flight requests map
208
+ this.networkRequestsInFlight[networkRequestInFlightKey] = dataPromise;
209
+ _context.next = 31;
203
210
  return dataPromise;
204
- case 26:
205
- data = _context.sent;
211
+ case 31:
212
+ _data = _context.sent;
206
213
  // We need to call the callback with the data with result even if the cache version has changed,
207
214
  // so all promises that are waiting for the data can resolve.
208
215
  callback({
209
- data: data
216
+ data: _data
210
217
  });
211
218
 
212
219
  // If the cache version has changed, we don't want to use the data from the network
213
220
  // because it could be stale data.
214
221
  if (cacheVersionBeforeRequest === this.cacheVersion) {
215
222
  // Replace promise with the resolved data in the cache
216
- this.cache[_dataKey] = {
223
+ this.cache[dataKey] = {
217
224
  source: 'network',
218
- data: data
225
+ data: _data
219
226
  };
220
227
  }
221
- case 29:
222
- _context.next = 34;
228
+ _context.next = 39;
223
229
  break;
224
- case 31:
225
- _context.prev = 31;
226
- _context.t3 = _context["catch"](0);
230
+ case 36:
231
+ _context.prev = 36;
232
+ _context.t1 = _context["catch"](26);
227
233
  // If an error occurs, we call the callback with the error
228
234
  callback({
229
- error: _context.t3 instanceof Error ? _context.t3 : new Error(String(_context.t3))
235
+ error: _context.t1 instanceof Error ? _context.t1 : new Error(String(_context.t1))
230
236
  });
231
- case 34:
237
+ case 39:
238
+ _context.prev = 39;
239
+ // Ensure we clean up the in-flight request entry
240
+ delete this.networkRequestsInFlight[networkRequestInFlightKey];
241
+ return _context.finish(39);
242
+ case 42:
232
243
  case "end":
233
244
  return _context.stop();
234
245
  }
235
- }, _callee, this, [[0, 31]]);
246
+ }, _callee, this, [[15, 22], [26, 36, 39, 42]]);
236
247
  }));
237
248
  function getDataAsync(_x, _x2) {
238
249
  return _getDataAsync.apply(this, arguments);
@@ -288,28 +299,27 @@ var NodeDataProvider = exports.NodeDataProvider = /*#__PURE__*/function () {
288
299
  }, {
289
300
  key: "getCacheStatusForNode",
290
301
  value: function getCacheStatusForNode(node) {
291
- var jsonNode = 'toJSON' in node ? node.toJSON() : node;
292
- if (!this.isNodeSupported(jsonNode)) {
293
- // eslint-disable-next-line no-console
294
- console.error("The ".concat(this.constructor.name, " doesn't support Node ").concat(jsonNode.type, "."));
295
- return false;
296
- }
297
- var dataKey = this.nodeDataKey(jsonNode);
298
- var dataFromCache = this.cache[dataKey];
302
+ var dataFromCache = this.getNodeDataFromCache(node);
299
303
  return dataFromCache ? dataFromCache.source : false;
300
304
  }
305
+
306
+ /**
307
+ * Retrieves the cached data for a given node, if available.
308
+ *
309
+ * @param node The node (or its ProseMirror representation) for which to retrieve cached data.
310
+ * @returns The cached data object containing `data` and `source`, or `undefined` if no cache entry exists.
311
+ */
301
312
  }, {
302
313
  key: "getNodeDataFromCache",
303
314
  value: function getNodeDataFromCache(node) {
304
315
  var jsonNode = 'toJSON' in node ? node.toJSON() : node;
316
+ if (!this.isNodeSupported(jsonNode)) {
317
+ // eslint-disable-next-line no-console
318
+ console.error("The ".concat(this.constructor.name, " doesn't support Node ").concat(jsonNode.type, "."));
319
+ return undefined;
320
+ }
305
321
  var dataKey = this.nodeDataKey(jsonNode);
306
322
  return this.cache[dataKey];
307
323
  }
308
324
  }]);
309
- }();
310
- /**
311
- * Checks if value is a promise using hacky heuristics.
312
- */
313
- function isPromise(value) {
314
- return (0, _typeof2.default)(value) === 'object' && value !== null && 'then' in value && typeof value.then === 'function';
315
- }
325
+ }();
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @atlaskit/editor/no-re-export */
2
2
 
3
- export { NodeDataProvider, isPromise } from './node-data-provider';
3
+ export { NodeDataProvider } from './node-data-provider';
4
4
  export { findNodesToPrefetch } from './utils/find-nodes-to-prefetch';
5
5
  export { prefetchNodeDataProvidersData } from './utils/prefetch-node-data-providers-data';
@@ -20,7 +20,7 @@ import { isSSR } from '@atlaskit/editor-common/core-utils';
20
20
  * @example
21
21
  * {
22
22
  * 'node-id-1': { source: 'ssr', data: { value: 'some data' } },
23
- * 'node-id-2': { source: 'network', data: Promise.resolve({ value: 'other data' }) }
23
+ * 'node-id-2': { source: 'network', data: { value: 'other data' } }
24
24
  * }
25
25
  */
26
26
 
@@ -66,6 +66,7 @@ export class NodeDataProvider {
66
66
  constructor() {
67
67
  this.cacheVersion = 0;
68
68
  this.cache = {};
69
+ this.networkRequestsInFlight = {};
69
70
  }
70
71
 
71
72
  /**
@@ -137,45 +138,59 @@ export class NodeDataProvider {
137
138
  void this.getDataAsync(node, callback);
138
139
  }
139
140
  async getDataAsync(node, callback) {
140
- try {
141
- const jsonNode = 'toJSON' in node ? node.toJSON() : node;
142
- if (!this.isNodeSupported(jsonNode)) {
143
- // eslint-disable-next-line no-console
144
- console.error(`The ${this.constructor.name} doesn't support Node ${jsonNode.type}.`);
141
+ const jsonNode = 'toJSON' in node ? node.toJSON() : node;
142
+ if (!this.isNodeSupported(jsonNode)) {
143
+ // eslint-disable-next-line no-console
144
+ console.error(`The ${this.constructor.name} doesn't support Node ${jsonNode.type}.`);
145
+ return;
146
+ }
147
+ const dataKey = this.nodeDataKey(jsonNode);
148
+ const dataFromCache = this.cache[dataKey];
149
+ if (dataFromCache !== undefined) {
150
+ // If we have the data in the SSR data, we can use it directly
151
+ callback({
152
+ data: dataFromCache.data
153
+ });
154
+ if (isSSR()) {
155
+ // If we are in SSR, we don't want to fetch the data again, as it is already available in the SSR data
145
156
  return;
146
157
  }
147
- const dataKey = this.nodeDataKey(jsonNode);
148
- const dataFromCache = this.cache[dataKey];
149
- if (dataFromCache !== undefined) {
150
- // If we have the data in the SSR data, we can use it directly
151
- if (isPromise(dataFromCache.data)) {
158
+ }
159
+
160
+ // If no data is available in the cache, or the data is from the network,
161
+ // we need to fetch it from the network.
162
+ if ((dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.source) !== 'network') {
163
+ // Store the current cache version before making the request,
164
+ // so we can check if the cache has changed while we are waiting for the network response.
165
+ const cacheVersionBeforeRequest = this.cacheVersion;
166
+
167
+ // Create a unique key for the in-flight network request
168
+ // based on the cache version and the data key.
169
+ const networkRequestInFlightKey = `${cacheVersionBeforeRequest}-${dataKey}`;
170
+
171
+ // Check if there is already a network request in flight for this data
172
+ // to avoid duplicate requests.
173
+ const networkRequestInFlight = this.networkRequestsInFlight[networkRequestInFlightKey];
174
+ if (networkRequestInFlight) {
175
+ try {
176
+ const data = await networkRequestInFlight;
152
177
  callback({
153
- data: await dataFromCache.data
178
+ data
154
179
  });
155
- } else {
180
+ } catch (error) {
156
181
  callback({
157
- data: dataFromCache.data
182
+ error: error instanceof Error ? error : new Error(String(error))
158
183
  });
159
184
  }
160
- if (isSSR()) {
161
- // If we are in SSR, we don't want to fetch the data again, as it is already available in the SSR data
162
- return;
163
- }
185
+ return;
164
186
  }
165
-
166
- // If no data is available in the cache or the data is from the network,
167
- // we need to fetch it from the network.
168
- if ((dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.source) !== 'network') {
169
- // Store the current cache version before making the request,
170
- // so we can check if the cache has changed while we are waiting for the network response.
171
- const cacheVersionBeforeRequest = this.cacheVersion;
187
+ try {
172
188
  const dataPromise = this.fetchNodesData([jsonNode]).then(([value]) => value);
173
- // Store the promise in the cache to avoid multiple requests for the same data
174
- this.cache[dataKey] = {
175
- source: 'network',
176
- data: dataPromise
177
- };
189
+
190
+ // Store the promise in the in-flight requests map
191
+ this.networkRequestsInFlight[networkRequestInFlightKey] = dataPromise;
178
192
  const data = await dataPromise;
193
+
179
194
  // We need to call the callback with the data with result even if the cache version has changed,
180
195
  // so all promises that are waiting for the data can resolve.
181
196
  callback({
@@ -191,12 +206,15 @@ export class NodeDataProvider {
191
206
  data
192
207
  };
193
208
  }
209
+ } catch (error) {
210
+ // If an error occurs, we call the callback with the error
211
+ callback({
212
+ error: error instanceof Error ? error : new Error(String(error))
213
+ });
214
+ } finally {
215
+ // Ensure we clean up the in-flight request entry
216
+ delete this.networkRequestsInFlight[networkRequestInFlightKey];
194
217
  }
195
- } catch (error) {
196
- // If an error occurs, we call the callback with the error
197
- callback({
198
- error: error instanceof Error ? error : new Error(String(error))
199
- });
200
218
  }
201
219
  }
202
220
 
@@ -244,26 +262,24 @@ export class NodeDataProvider {
244
262
  * @returns The cache status: `false`, `'ssr'`, or `'network'`.
245
263
  */
246
264
  getCacheStatusForNode(node) {
265
+ const dataFromCache = this.getNodeDataFromCache(node);
266
+ return dataFromCache ? dataFromCache.source : false;
267
+ }
268
+
269
+ /**
270
+ * Retrieves the cached data for a given node, if available.
271
+ *
272
+ * @param node The node (or its ProseMirror representation) for which to retrieve cached data.
273
+ * @returns The cached data object containing `data` and `source`, or `undefined` if no cache entry exists.
274
+ */
275
+ getNodeDataFromCache(node) {
247
276
  const jsonNode = 'toJSON' in node ? node.toJSON() : node;
248
277
  if (!this.isNodeSupported(jsonNode)) {
249
278
  // eslint-disable-next-line no-console
250
279
  console.error(`The ${this.constructor.name} doesn't support Node ${jsonNode.type}.`);
251
- return false;
280
+ return undefined;
252
281
  }
253
282
  const dataKey = this.nodeDataKey(jsonNode);
254
- const dataFromCache = this.cache[dataKey];
255
- return dataFromCache ? dataFromCache.source : false;
256
- }
257
- getNodeDataFromCache(node) {
258
- const jsonNode = 'toJSON' in node ? node.toJSON() : node;
259
- const dataKey = this.nodeDataKey(jsonNode);
260
283
  return this.cache[dataKey];
261
284
  }
262
- }
263
-
264
- /**
265
- * Checks if value is a promise using hacky heuristics.
266
- */
267
- export function isPromise(value) {
268
- return typeof value === 'object' && value !== null && 'then' in value && typeof value.then === 'function';
269
285
  }
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable @atlaskit/editor/no-re-export */
2
2
 
3
- export { NodeDataProvider, isPromise } from './node-data-provider';
3
+ export { NodeDataProvider } from './node-data-provider';
4
4
  export { findNodesToPrefetch } from './utils/find-nodes-to-prefetch';
5
5
  export { prefetchNodeDataProvidersData } from './utils/prefetch-node-data-providers-data';
@@ -1,4 +1,3 @@
1
- import _typeof from "@babel/runtime/helpers/typeof";
2
1
  import _asyncToGenerator from "@babel/runtime/helpers/asyncToGenerator";
3
2
  import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
4
3
  import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
@@ -26,7 +25,7 @@ import { isSSR } from '@atlaskit/editor-common/core-utils';
26
25
  * @example
27
26
  * {
28
27
  * 'node-id-1': { source: 'ssr', data: { value: 'some data' } },
29
- * 'node-id-2': { source: 'network', data: Promise.resolve({ value: 'other data' }) }
28
+ * 'node-id-2': { source: 'network', data: { value: 'other data' } }
30
29
  * }
31
30
  */
32
31
 
@@ -47,6 +46,7 @@ export var NodeDataProvider = /*#__PURE__*/function () {
47
46
  _classCallCheck(this, NodeDataProvider);
48
47
  this.cacheVersion = 0;
49
48
  this.cache = {};
49
+ this.networkRequestsInFlight = {};
50
50
  }
51
51
 
52
52
  /**
@@ -133,102 +133,114 @@ export var NodeDataProvider = /*#__PURE__*/function () {
133
133
  key: "getDataAsync",
134
134
  value: function () {
135
135
  var _getDataAsync = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee(node, callback) {
136
- var jsonNode, _dataKey, dataFromCache, cacheVersionBeforeRequest, dataPromise, data;
136
+ var jsonNode, dataKey, dataFromCache, cacheVersionBeforeRequest, networkRequestInFlightKey, networkRequestInFlight, data, dataPromise, _data;
137
137
  return _regeneratorRuntime.wrap(function _callee$(_context) {
138
138
  while (1) switch (_context.prev = _context.next) {
139
139
  case 0:
140
- _context.prev = 0;
141
140
  jsonNode = 'toJSON' in node ? node.toJSON() : node;
142
141
  if (this.isNodeSupported(jsonNode)) {
143
- _context.next = 5;
142
+ _context.next = 4;
144
143
  break;
145
144
  }
146
145
  // eslint-disable-next-line no-console
147
146
  console.error("The ".concat(this.constructor.name, " doesn't support Node ").concat(jsonNode.type, "."));
148
147
  return _context.abrupt("return");
149
- case 5:
150
- _dataKey = this.nodeDataKey(jsonNode);
151
- dataFromCache = this.cache[_dataKey];
148
+ case 4:
149
+ dataKey = this.nodeDataKey(jsonNode);
150
+ dataFromCache = this.cache[dataKey];
152
151
  if (!(dataFromCache !== undefined)) {
153
- _context.next = 20;
152
+ _context.next = 10;
154
153
  break;
155
154
  }
156
- if (!isPromise(dataFromCache.data)) {
157
- _context.next = 17;
158
- break;
159
- }
160
- _context.t0 = callback;
161
- _context.next = 12;
162
- return dataFromCache.data;
163
- case 12:
164
- _context.t1 = _context.sent;
165
- _context.t2 = {
166
- data: _context.t1
167
- };
168
- (0, _context.t0)(_context.t2);
169
- _context.next = 18;
170
- break;
171
- case 17:
155
+ // If we have the data in the SSR data, we can use it directly
172
156
  callback({
173
157
  data: dataFromCache.data
174
158
  });
175
- case 18:
176
159
  if (!isSSR()) {
177
- _context.next = 20;
160
+ _context.next = 10;
178
161
  break;
179
162
  }
180
163
  return _context.abrupt("return");
181
- case 20:
164
+ case 10:
182
165
  if (!((dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.source) !== 'network')) {
183
- _context.next = 29;
166
+ _context.next = 42;
184
167
  break;
185
168
  }
186
169
  // Store the current cache version before making the request,
187
170
  // so we can check if the cache has changed while we are waiting for the network response.
188
- cacheVersionBeforeRequest = this.cacheVersion;
171
+ cacheVersionBeforeRequest = this.cacheVersion; // Create a unique key for the in-flight network request
172
+ // based on the cache version and the data key.
173
+ networkRequestInFlightKey = "".concat(cacheVersionBeforeRequest, "-").concat(dataKey); // Check if there is already a network request in flight for this data
174
+ // to avoid duplicate requests.
175
+ networkRequestInFlight = this.networkRequestsInFlight[networkRequestInFlightKey];
176
+ if (!networkRequestInFlight) {
177
+ _context.next = 26;
178
+ break;
179
+ }
180
+ _context.prev = 15;
181
+ _context.next = 18;
182
+ return networkRequestInFlight;
183
+ case 18:
184
+ data = _context.sent;
185
+ callback({
186
+ data: data
187
+ });
188
+ _context.next = 25;
189
+ break;
190
+ case 22:
191
+ _context.prev = 22;
192
+ _context.t0 = _context["catch"](15);
193
+ callback({
194
+ error: _context.t0 instanceof Error ? _context.t0 : new Error(String(_context.t0))
195
+ });
196
+ case 25:
197
+ return _context.abrupt("return");
198
+ case 26:
199
+ _context.prev = 26;
189
200
  dataPromise = this.fetchNodesData([jsonNode]).then(function (_ref3) {
190
201
  var _ref4 = _slicedToArray(_ref3, 1),
191
202
  value = _ref4[0];
192
203
  return value;
193
- }); // Store the promise in the cache to avoid multiple requests for the same data
194
- this.cache[_dataKey] = {
195
- source: 'network',
196
- data: dataPromise
197
- };
198
- _context.next = 26;
204
+ }); // Store the promise in the in-flight requests map
205
+ this.networkRequestsInFlight[networkRequestInFlightKey] = dataPromise;
206
+ _context.next = 31;
199
207
  return dataPromise;
200
- case 26:
201
- data = _context.sent;
208
+ case 31:
209
+ _data = _context.sent;
202
210
  // We need to call the callback with the data with result even if the cache version has changed,
203
211
  // so all promises that are waiting for the data can resolve.
204
212
  callback({
205
- data: data
213
+ data: _data
206
214
  });
207
215
 
208
216
  // If the cache version has changed, we don't want to use the data from the network
209
217
  // because it could be stale data.
210
218
  if (cacheVersionBeforeRequest === this.cacheVersion) {
211
219
  // Replace promise with the resolved data in the cache
212
- this.cache[_dataKey] = {
220
+ this.cache[dataKey] = {
213
221
  source: 'network',
214
- data: data
222
+ data: _data
215
223
  };
216
224
  }
217
- case 29:
218
- _context.next = 34;
225
+ _context.next = 39;
219
226
  break;
220
- case 31:
221
- _context.prev = 31;
222
- _context.t3 = _context["catch"](0);
227
+ case 36:
228
+ _context.prev = 36;
229
+ _context.t1 = _context["catch"](26);
223
230
  // If an error occurs, we call the callback with the error
224
231
  callback({
225
- error: _context.t3 instanceof Error ? _context.t3 : new Error(String(_context.t3))
232
+ error: _context.t1 instanceof Error ? _context.t1 : new Error(String(_context.t1))
226
233
  });
227
- case 34:
234
+ case 39:
235
+ _context.prev = 39;
236
+ // Ensure we clean up the in-flight request entry
237
+ delete this.networkRequestsInFlight[networkRequestInFlightKey];
238
+ return _context.finish(39);
239
+ case 42:
228
240
  case "end":
229
241
  return _context.stop();
230
242
  }
231
- }, _callee, this, [[0, 31]]);
243
+ }, _callee, this, [[15, 22], [26, 36, 39, 42]]);
232
244
  }));
233
245
  function getDataAsync(_x, _x2) {
234
246
  return _getDataAsync.apply(this, arguments);
@@ -284,29 +296,27 @@ export var NodeDataProvider = /*#__PURE__*/function () {
284
296
  }, {
285
297
  key: "getCacheStatusForNode",
286
298
  value: function getCacheStatusForNode(node) {
287
- var jsonNode = 'toJSON' in node ? node.toJSON() : node;
288
- if (!this.isNodeSupported(jsonNode)) {
289
- // eslint-disable-next-line no-console
290
- console.error("The ".concat(this.constructor.name, " doesn't support Node ").concat(jsonNode.type, "."));
291
- return false;
292
- }
293
- var dataKey = this.nodeDataKey(jsonNode);
294
- var dataFromCache = this.cache[dataKey];
299
+ var dataFromCache = this.getNodeDataFromCache(node);
295
300
  return dataFromCache ? dataFromCache.source : false;
296
301
  }
302
+
303
+ /**
304
+ * Retrieves the cached data for a given node, if available.
305
+ *
306
+ * @param node The node (or its ProseMirror representation) for which to retrieve cached data.
307
+ * @returns The cached data object containing `data` and `source`, or `undefined` if no cache entry exists.
308
+ */
297
309
  }, {
298
310
  key: "getNodeDataFromCache",
299
311
  value: function getNodeDataFromCache(node) {
300
312
  var jsonNode = 'toJSON' in node ? node.toJSON() : node;
313
+ if (!this.isNodeSupported(jsonNode)) {
314
+ // eslint-disable-next-line no-console
315
+ console.error("The ".concat(this.constructor.name, " doesn't support Node ").concat(jsonNode.type, "."));
316
+ return undefined;
317
+ }
301
318
  var dataKey = this.nodeDataKey(jsonNode);
302
319
  return this.cache[dataKey];
303
320
  }
304
321
  }]);
305
- }();
306
-
307
- /**
308
- * Checks if value is a promise using hacky heuristics.
309
- */
310
- export function isPromise(value) {
311
- return _typeof(value) === 'object' && value !== null && 'then' in value && typeof value.then === 'function';
312
- }
322
+ }();
@@ -1,3 +1,3 @@
1
- export { NodeDataProvider, isPromise } from './node-data-provider';
1
+ export { NodeDataProvider } from './node-data-provider';
2
2
  export { findNodesToPrefetch } from './utils/find-nodes-to-prefetch';
3
3
  export { prefetchNodeDataProvidersData } from './utils/prefetch-node-data-providers-data';
@@ -13,6 +13,22 @@ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
13
13
  type SSRData<Data> = {
14
14
  [dataKey: string]: Data;
15
15
  };
16
+ /**
17
+ * Represents the cached data for a Node Data Provider.
18
+ * Each key is a unique node data key, and the value is an object containing:
19
+ * - `source`: Indicates whether the data was fetched from SSR or the network.
20
+ * - `data`: The actual data, which can be either a resolved value or a Promise.
21
+ *
22
+ * @example
23
+ * {
24
+ * 'node-id-1': { source: 'ssr', data: { value: 'some data' } },
25
+ * 'node-id-2': { source: 'network', data: { value: 'other data' } }
26
+ * }
27
+ */
28
+ type CacheData<Data> = Record<string, {
29
+ data: Data;
30
+ source: 'ssr' | 'network';
31
+ }>;
16
32
  /**
17
33
  * Represents the payload passed to the callback function when data is fetched.
18
34
  * It can either contain an error or the fetched data.
@@ -36,6 +52,7 @@ type CallbackPayload<Data> = {
36
52
  export declare abstract class NodeDataProvider<Node extends JSONNode, Data> {
37
53
  private cacheVersion;
38
54
  private cache;
55
+ private readonly networkRequestsInFlight;
39
56
  /**
40
57
  * A unique name for the provider. Used for identification in SSR.
41
58
  */
@@ -116,7 +133,7 @@ export declare abstract class NodeDataProvider<Node extends JSONNode, Data> {
116
133
  * @param callback The callback function to call with the fetched data or an error.
117
134
  */
118
135
  getData(node: Node | PMNode, callback: (payload: CallbackPayload<Data>) => void): void;
119
- getDataAsync(node: Node | PMNode, callback: (payload: CallbackPayload<Data>) => void): Promise<void>;
136
+ protected getDataAsync(node: Node | PMNode, callback: (payload: CallbackPayload<Data>) => void): Promise<void>;
120
137
  /**
121
138
  * Fetches data for a given node and returns it as a Promise.
122
139
  * This is a convenience wrapper around the `data` method for use with async/await.
@@ -146,13 +163,12 @@ export declare abstract class NodeDataProvider<Node extends JSONNode, Data> {
146
163
  * @returns The cache status: `false`, `'ssr'`, or `'network'`.
147
164
  */
148
165
  getCacheStatusForNode(node: Node | PMNode): false | 'ssr' | 'network';
149
- getNodeDataFromCache(node: JSONNode | PMNode): {
150
- data: Data | Promise<Data>;
151
- source: "ssr" | "network";
152
- };
166
+ /**
167
+ * Retrieves the cached data for a given node, if available.
168
+ *
169
+ * @param node The node (or its ProseMirror representation) for which to retrieve cached data.
170
+ * @returns The cached data object containing `data` and `source`, or `undefined` if no cache entry exists.
171
+ */
172
+ getNodeDataFromCache(node: JSONNode | PMNode): CacheData<Data>[string] | undefined;
153
173
  }
154
- /**
155
- * Checks if value is a promise using hacky heuristics.
156
- */
157
- export declare function isPromise<T>(value: T | Promise<T>): value is Promise<T>;
158
174
  export {};
@@ -1,3 +1,3 @@
1
- export { NodeDataProvider, isPromise } from './node-data-provider';
1
+ export { NodeDataProvider } from './node-data-provider';
2
2
  export { findNodesToPrefetch } from './utils/find-nodes-to-prefetch';
3
3
  export { prefetchNodeDataProvidersData } from './utils/prefetch-node-data-providers-data';
@@ -13,6 +13,22 @@ import type { Node as PMNode } from '@atlaskit/editor-prosemirror/model';
13
13
  type SSRData<Data> = {
14
14
  [dataKey: string]: Data;
15
15
  };
16
+ /**
17
+ * Represents the cached data for a Node Data Provider.
18
+ * Each key is a unique node data key, and the value is an object containing:
19
+ * - `source`: Indicates whether the data was fetched from SSR or the network.
20
+ * - `data`: The actual data, which can be either a resolved value or a Promise.
21
+ *
22
+ * @example
23
+ * {
24
+ * 'node-id-1': { source: 'ssr', data: { value: 'some data' } },
25
+ * 'node-id-2': { source: 'network', data: { value: 'other data' } }
26
+ * }
27
+ */
28
+ type CacheData<Data> = Record<string, {
29
+ data: Data;
30
+ source: 'ssr' | 'network';
31
+ }>;
16
32
  /**
17
33
  * Represents the payload passed to the callback function when data is fetched.
18
34
  * It can either contain an error or the fetched data.
@@ -36,6 +52,7 @@ type CallbackPayload<Data> = {
36
52
  export declare abstract class NodeDataProvider<Node extends JSONNode, Data> {
37
53
  private cacheVersion;
38
54
  private cache;
55
+ private readonly networkRequestsInFlight;
39
56
  /**
40
57
  * A unique name for the provider. Used for identification in SSR.
41
58
  */
@@ -116,7 +133,7 @@ export declare abstract class NodeDataProvider<Node extends JSONNode, Data> {
116
133
  * @param callback The callback function to call with the fetched data or an error.
117
134
  */
118
135
  getData(node: Node | PMNode, callback: (payload: CallbackPayload<Data>) => void): void;
119
- getDataAsync(node: Node | PMNode, callback: (payload: CallbackPayload<Data>) => void): Promise<void>;
136
+ protected getDataAsync(node: Node | PMNode, callback: (payload: CallbackPayload<Data>) => void): Promise<void>;
120
137
  /**
121
138
  * Fetches data for a given node and returns it as a Promise.
122
139
  * This is a convenience wrapper around the `data` method for use with async/await.
@@ -146,13 +163,12 @@ export declare abstract class NodeDataProvider<Node extends JSONNode, Data> {
146
163
  * @returns The cache status: `false`, `'ssr'`, or `'network'`.
147
164
  */
148
165
  getCacheStatusForNode(node: Node | PMNode): false | 'ssr' | 'network';
149
- getNodeDataFromCache(node: JSONNode | PMNode): {
150
- data: Data | Promise<Data>;
151
- source: "ssr" | "network";
152
- };
166
+ /**
167
+ * Retrieves the cached data for a given node, if available.
168
+ *
169
+ * @param node The node (or its ProseMirror representation) for which to retrieve cached data.
170
+ * @returns The cached data object containing `data` and `source`, or `undefined` if no cache entry exists.
171
+ */
172
+ getNodeDataFromCache(node: JSONNode | PMNode): CacheData<Data>[string] | undefined;
153
173
  }
154
- /**
155
- * Checks if value is a promise using hacky heuristics.
156
- */
157
- export declare function isPromise<T>(value: T | Promise<T>): value is Promise<T>;
158
174
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@atlaskit/node-data-provider",
3
- "version": "7.3.0",
3
+ "version": "7.4.0",
4
4
  "description": "Node data provider for @atlaskit/editor-core plugins and @atlaskit/renderer",
5
5
  "author": "Atlassian Pty Ltd",
6
6
  "license": "Apache-2.0",
@@ -26,7 +26,7 @@
26
26
  "@babel/runtime": "^7.0.0"
27
27
  },
28
28
  "peerDependencies": {
29
- "@atlaskit/editor-common": "^110.18.0"
29
+ "@atlaskit/editor-common": "^110.21.0"
30
30
  },
31
31
  "techstack": {
32
32
  "@atlassian/frontend": {