@atlaskit/node-data-provider 7.3.0 → 7.5.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,25 @@
1
1
  # @atlaskit/node-data-provider
2
2
 
3
+ ## 7.5.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`e3f45edd75547`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/e3f45edd75547) -
8
+ [https://product-fabric.atlassian.net/browse/ED-29647](ED-29647) - OTP will not use network data
9
+ if preloaded data is unavailable during SSR rendering
10
+
11
+ ## 7.4.0
12
+
13
+ ### Minor Changes
14
+
15
+ - [`fdba2e94783b7`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/fdba2e94783b7) -
16
+ [https://product-fabric.atlassian.net/browse/ED-29638](ED-29638) - fix editor NodeDataProvider
17
+ network requests deduplication
18
+
19
+ ### Patch Changes
20
+
21
+ - Updated dependencies
22
+
3
23
  ## 7.3.0
4
24
 
5
25
  ### 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,112 @@ 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];
156
- if (!(dataFromCache !== undefined)) {
157
- _context.next = 20;
158
- break;
159
- }
160
- if (!isPromise(dataFromCache.data)) {
161
- _context.next = 17;
162
- break;
151
+ case 4:
152
+ dataKey = this.nodeDataKey(jsonNode);
153
+ dataFromCache = this.cache[dataKey];
154
+ if (dataFromCache !== undefined) {
155
+ // If we have the data in the SSR data, we can use it directly
156
+ callback({
157
+ data: dataFromCache.data
158
+ });
163
159
  }
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:
176
- callback({
177
- data: dataFromCache.data
178
- });
179
- case 18:
180
160
  if (!(0, _coreUtils.isSSR)()) {
181
- _context.next = 20;
161
+ _context.next = 9;
182
162
  break;
183
163
  }
184
164
  return _context.abrupt("return");
185
- case 20:
165
+ case 9:
186
166
  if (!((dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.source) !== 'network')) {
187
- _context.next = 29;
167
+ _context.next = 41;
188
168
  break;
189
169
  }
190
170
  // Store the current cache version before making the request,
191
171
  // so we can check if the cache has changed while we are waiting for the network response.
192
- cacheVersionBeforeRequest = this.cacheVersion;
172
+ cacheVersionBeforeRequest = this.cacheVersion; // Create a unique key for the in-flight network request
173
+ // based on the cache version and the data key.
174
+ networkRequestInFlightKey = "".concat(cacheVersionBeforeRequest, "-").concat(dataKey); // Check if there is already a network request in flight for this data
175
+ // to avoid duplicate requests.
176
+ networkRequestInFlight = this.networkRequestsInFlight[networkRequestInFlightKey];
177
+ if (!networkRequestInFlight) {
178
+ _context.next = 25;
179
+ break;
180
+ }
181
+ _context.prev = 14;
182
+ _context.next = 17;
183
+ return networkRequestInFlight;
184
+ case 17:
185
+ data = _context.sent;
186
+ callback({
187
+ data: data
188
+ });
189
+ _context.next = 24;
190
+ break;
191
+ case 21:
192
+ _context.prev = 21;
193
+ _context.t0 = _context["catch"](14);
194
+ callback({
195
+ error: _context.t0 instanceof Error ? _context.t0 : new Error(String(_context.t0))
196
+ });
197
+ case 24:
198
+ return _context.abrupt("return");
199
+ case 25:
200
+ _context.prev = 25;
193
201
  dataPromise = this.fetchNodesData([jsonNode]).then(function (_ref3) {
194
202
  var _ref4 = (0, _slicedToArray2.default)(_ref3, 1),
195
203
  value = _ref4[0];
196
204
  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;
205
+ }); // Store the promise in the in-flight requests map
206
+ this.networkRequestsInFlight[networkRequestInFlightKey] = dataPromise;
207
+ _context.next = 30;
203
208
  return dataPromise;
204
- case 26:
205
- data = _context.sent;
209
+ case 30:
210
+ _data = _context.sent;
206
211
  // We need to call the callback with the data with result even if the cache version has changed,
207
212
  // so all promises that are waiting for the data can resolve.
208
213
  callback({
209
- data: data
214
+ data: _data
210
215
  });
211
216
 
212
217
  // If the cache version has changed, we don't want to use the data from the network
213
218
  // because it could be stale data.
214
219
  if (cacheVersionBeforeRequest === this.cacheVersion) {
215
220
  // Replace promise with the resolved data in the cache
216
- this.cache[_dataKey] = {
221
+ this.cache[dataKey] = {
217
222
  source: 'network',
218
- data: data
223
+ data: _data
219
224
  };
220
225
  }
221
- case 29:
222
- _context.next = 34;
226
+ _context.next = 38;
223
227
  break;
224
- case 31:
225
- _context.prev = 31;
226
- _context.t3 = _context["catch"](0);
228
+ case 35:
229
+ _context.prev = 35;
230
+ _context.t1 = _context["catch"](25);
227
231
  // If an error occurs, we call the callback with the error
228
232
  callback({
229
- error: _context.t3 instanceof Error ? _context.t3 : new Error(String(_context.t3))
233
+ error: _context.t1 instanceof Error ? _context.t1 : new Error(String(_context.t1))
230
234
  });
231
- case 34:
235
+ case 38:
236
+ _context.prev = 38;
237
+ // Ensure we clean up the in-flight request entry
238
+ delete this.networkRequestsInFlight[networkRequestInFlightKey];
239
+ return _context.finish(38);
240
+ case 41:
232
241
  case "end":
233
242
  return _context.stop();
234
243
  }
235
- }, _callee, this, [[0, 31]]);
244
+ }, _callee, this, [[14, 21], [25, 35, 38, 41]]);
236
245
  }));
237
246
  function getDataAsync(_x, _x2) {
238
247
  return _getDataAsync.apply(this, arguments);
@@ -288,28 +297,27 @@ var NodeDataProvider = exports.NodeDataProvider = /*#__PURE__*/function () {
288
297
  }, {
289
298
  key: "getCacheStatusForNode",
290
299
  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];
300
+ var dataFromCache = this.getNodeDataFromCache(node);
299
301
  return dataFromCache ? dataFromCache.source : false;
300
302
  }
303
+
304
+ /**
305
+ * Retrieves the cached data for a given node, if available.
306
+ *
307
+ * @param node The node (or its ProseMirror representation) for which to retrieve cached data.
308
+ * @returns The cached data object containing `data` and `source`, or `undefined` if no cache entry exists.
309
+ */
301
310
  }, {
302
311
  key: "getNodeDataFromCache",
303
312
  value: function getNodeDataFromCache(node) {
304
313
  var jsonNode = 'toJSON' in node ? node.toJSON() : node;
314
+ if (!this.isNodeSupported(jsonNode)) {
315
+ // eslint-disable-next-line no-console
316
+ console.error("The ".concat(this.constructor.name, " doesn't support Node ").concat(jsonNode.type, "."));
317
+ return undefined;
318
+ }
305
319
  var dataKey = this.nodeDataKey(jsonNode);
306
320
  return this.cache[dataKey];
307
321
  }
308
322
  }]);
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
- }
323
+ }();
@@ -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,60 @@ 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}.`);
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
- if (isPromise(dataFromCache.data)) {
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
+ }
155
+ if (isSSR()) {
156
+ // During SSR, we only use the cache and never fetch from the network.
157
+ // Use loading state instead.
158
+ return;
159
+ }
160
+
161
+ // If no data is available in the cache, or the data is from the network,
162
+ // we need to fetch it from the network.
163
+ if ((dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.source) !== 'network') {
164
+ // Store the current cache version before making the request,
165
+ // so we can check if the cache has changed while we are waiting for the network response.
166
+ const cacheVersionBeforeRequest = this.cacheVersion;
167
+
168
+ // Create a unique key for the in-flight network request
169
+ // based on the cache version and the data key.
170
+ const networkRequestInFlightKey = `${cacheVersionBeforeRequest}-${dataKey}`;
171
+
172
+ // Check if there is already a network request in flight for this data
173
+ // to avoid duplicate requests.
174
+ const networkRequestInFlight = this.networkRequestsInFlight[networkRequestInFlightKey];
175
+ if (networkRequestInFlight) {
176
+ try {
177
+ const data = await networkRequestInFlight;
152
178
  callback({
153
- data: await dataFromCache.data
179
+ data
154
180
  });
155
- } else {
181
+ } catch (error) {
156
182
  callback({
157
- data: dataFromCache.data
183
+ error: error instanceof Error ? error : new Error(String(error))
158
184
  });
159
185
  }
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
- }
186
+ return;
164
187
  }
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;
188
+ try {
172
189
  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
- };
190
+
191
+ // Store the promise in the in-flight requests map
192
+ this.networkRequestsInFlight[networkRequestInFlightKey] = dataPromise;
178
193
  const data = await dataPromise;
194
+
179
195
  // We need to call the callback with the data with result even if the cache version has changed,
180
196
  // so all promises that are waiting for the data can resolve.
181
197
  callback({
@@ -191,12 +207,15 @@ export class NodeDataProvider {
191
207
  data
192
208
  };
193
209
  }
210
+ } catch (error) {
211
+ // If an error occurs, we call the callback with the error
212
+ callback({
213
+ error: error instanceof Error ? error : new Error(String(error))
214
+ });
215
+ } finally {
216
+ // Ensure we clean up the in-flight request entry
217
+ delete this.networkRequestsInFlight[networkRequestInFlightKey];
194
218
  }
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
219
  }
201
220
  }
202
221
 
@@ -244,26 +263,24 @@ export class NodeDataProvider {
244
263
  * @returns The cache status: `false`, `'ssr'`, or `'network'`.
245
264
  */
246
265
  getCacheStatusForNode(node) {
266
+ const dataFromCache = this.getNodeDataFromCache(node);
267
+ return dataFromCache ? dataFromCache.source : false;
268
+ }
269
+
270
+ /**
271
+ * Retrieves the cached data for a given node, if available.
272
+ *
273
+ * @param node The node (or its ProseMirror representation) for which to retrieve cached data.
274
+ * @returns The cached data object containing `data` and `source`, or `undefined` if no cache entry exists.
275
+ */
276
+ getNodeDataFromCache(node) {
247
277
  const jsonNode = 'toJSON' in node ? node.toJSON() : node;
248
278
  if (!this.isNodeSupported(jsonNode)) {
249
279
  // eslint-disable-next-line no-console
250
280
  console.error(`The ${this.constructor.name} doesn't support Node ${jsonNode.type}.`);
251
- return false;
281
+ return undefined;
252
282
  }
253
283
  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
284
  return this.cache[dataKey];
261
285
  }
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
286
  }
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,112 @@ 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];
152
- if (!(dataFromCache !== undefined)) {
153
- _context.next = 20;
154
- break;
155
- }
156
- if (!isPromise(dataFromCache.data)) {
157
- _context.next = 17;
158
- break;
148
+ case 4:
149
+ dataKey = this.nodeDataKey(jsonNode);
150
+ dataFromCache = this.cache[dataKey];
151
+ if (dataFromCache !== undefined) {
152
+ // If we have the data in the SSR data, we can use it directly
153
+ callback({
154
+ data: dataFromCache.data
155
+ });
159
156
  }
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:
172
- callback({
173
- data: dataFromCache.data
174
- });
175
- case 18:
176
157
  if (!isSSR()) {
177
- _context.next = 20;
158
+ _context.next = 9;
178
159
  break;
179
160
  }
180
161
  return _context.abrupt("return");
181
- case 20:
162
+ case 9:
182
163
  if (!((dataFromCache === null || dataFromCache === void 0 ? void 0 : dataFromCache.source) !== 'network')) {
183
- _context.next = 29;
164
+ _context.next = 41;
184
165
  break;
185
166
  }
186
167
  // Store the current cache version before making the request,
187
168
  // so we can check if the cache has changed while we are waiting for the network response.
188
- cacheVersionBeforeRequest = this.cacheVersion;
169
+ cacheVersionBeforeRequest = this.cacheVersion; // Create a unique key for the in-flight network request
170
+ // based on the cache version and the data key.
171
+ networkRequestInFlightKey = "".concat(cacheVersionBeforeRequest, "-").concat(dataKey); // Check if there is already a network request in flight for this data
172
+ // to avoid duplicate requests.
173
+ networkRequestInFlight = this.networkRequestsInFlight[networkRequestInFlightKey];
174
+ if (!networkRequestInFlight) {
175
+ _context.next = 25;
176
+ break;
177
+ }
178
+ _context.prev = 14;
179
+ _context.next = 17;
180
+ return networkRequestInFlight;
181
+ case 17:
182
+ data = _context.sent;
183
+ callback({
184
+ data: data
185
+ });
186
+ _context.next = 24;
187
+ break;
188
+ case 21:
189
+ _context.prev = 21;
190
+ _context.t0 = _context["catch"](14);
191
+ callback({
192
+ error: _context.t0 instanceof Error ? _context.t0 : new Error(String(_context.t0))
193
+ });
194
+ case 24:
195
+ return _context.abrupt("return");
196
+ case 25:
197
+ _context.prev = 25;
189
198
  dataPromise = this.fetchNodesData([jsonNode]).then(function (_ref3) {
190
199
  var _ref4 = _slicedToArray(_ref3, 1),
191
200
  value = _ref4[0];
192
201
  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;
202
+ }); // Store the promise in the in-flight requests map
203
+ this.networkRequestsInFlight[networkRequestInFlightKey] = dataPromise;
204
+ _context.next = 30;
199
205
  return dataPromise;
200
- case 26:
201
- data = _context.sent;
206
+ case 30:
207
+ _data = _context.sent;
202
208
  // We need to call the callback with the data with result even if the cache version has changed,
203
209
  // so all promises that are waiting for the data can resolve.
204
210
  callback({
205
- data: data
211
+ data: _data
206
212
  });
207
213
 
208
214
  // If the cache version has changed, we don't want to use the data from the network
209
215
  // because it could be stale data.
210
216
  if (cacheVersionBeforeRequest === this.cacheVersion) {
211
217
  // Replace promise with the resolved data in the cache
212
- this.cache[_dataKey] = {
218
+ this.cache[dataKey] = {
213
219
  source: 'network',
214
- data: data
220
+ data: _data
215
221
  };
216
222
  }
217
- case 29:
218
- _context.next = 34;
223
+ _context.next = 38;
219
224
  break;
220
- case 31:
221
- _context.prev = 31;
222
- _context.t3 = _context["catch"](0);
225
+ case 35:
226
+ _context.prev = 35;
227
+ _context.t1 = _context["catch"](25);
223
228
  // If an error occurs, we call the callback with the error
224
229
  callback({
225
- error: _context.t3 instanceof Error ? _context.t3 : new Error(String(_context.t3))
230
+ error: _context.t1 instanceof Error ? _context.t1 : new Error(String(_context.t1))
226
231
  });
227
- case 34:
232
+ case 38:
233
+ _context.prev = 38;
234
+ // Ensure we clean up the in-flight request entry
235
+ delete this.networkRequestsInFlight[networkRequestInFlightKey];
236
+ return _context.finish(38);
237
+ case 41:
228
238
  case "end":
229
239
  return _context.stop();
230
240
  }
231
- }, _callee, this, [[0, 31]]);
241
+ }, _callee, this, [[14, 21], [25, 35, 38, 41]]);
232
242
  }));
233
243
  function getDataAsync(_x, _x2) {
234
244
  return _getDataAsync.apply(this, arguments);
@@ -284,29 +294,27 @@ export var NodeDataProvider = /*#__PURE__*/function () {
284
294
  }, {
285
295
  key: "getCacheStatusForNode",
286
296
  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];
297
+ var dataFromCache = this.getNodeDataFromCache(node);
295
298
  return dataFromCache ? dataFromCache.source : false;
296
299
  }
300
+
301
+ /**
302
+ * Retrieves the cached data for a given node, if available.
303
+ *
304
+ * @param node The node (or its ProseMirror representation) for which to retrieve cached data.
305
+ * @returns The cached data object containing `data` and `source`, or `undefined` if no cache entry exists.
306
+ */
297
307
  }, {
298
308
  key: "getNodeDataFromCache",
299
309
  value: function getNodeDataFromCache(node) {
300
310
  var jsonNode = 'toJSON' in node ? node.toJSON() : node;
311
+ if (!this.isNodeSupported(jsonNode)) {
312
+ // eslint-disable-next-line no-console
313
+ console.error("The ".concat(this.constructor.name, " doesn't support Node ").concat(jsonNode.type, "."));
314
+ return undefined;
315
+ }
301
316
  var dataKey = this.nodeDataKey(jsonNode);
302
317
  return this.cache[dataKey];
303
318
  }
304
319
  }]);
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
- }
320
+ }();
@@ -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.5.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.22.0"
30
30
  },
31
31
  "techstack": {
32
32
  "@atlassian/frontend": {