@atlaskit/editor-synced-block-provider 8.1.10 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atlaskit/editor-synced-block-provider
2
2
 
3
+ ## 8.2.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`f13db8731461d`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/f13db8731461d) -
8
+ [EDITOR-7860] Gate synced block reference fetch and subscribe on data-provider readiness so
9
+ reference blocks no longer attempt to fetch before the (asynchronously wired) provider is ready.
10
+ This removes spurious "Data provider not set" fetch errors on Jira and keeps reference blocks
11
+ loading correctly once the provider resolves.
12
+
13
+ This also hardens the not-ready / torn-down window: a teardown-aware `hasDataProvider()` (also
14
+ checks `isDestroyed`), a tagged `ProviderNotReadyError` thrown at the fetch source, and catch-site
15
+ suppression in the hook, store manager and batch fetcher so an in-flight or queued fetch on an
16
+ orphaned manager re-queues for retry instead of emitting a false fetch error. All behaviour is
17
+ behind the `platform_editor_blocks_patch_3` feature gate.
18
+
3
19
  ## 8.1.10
4
20
 
5
21
  ### Patch Changes
@@ -1,9 +1,20 @@
1
1
  "use strict";
2
2
 
3
+ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
3
4
  Object.defineProperty(exports, "__esModule", {
4
5
  value: true
5
6
  });
6
- exports.SyncBlockError = void 0;
7
+ exports.isProviderNotReadyError = exports.SyncBlockError = exports.ProviderNotReadyError = exports.PROVIDER_NOT_READY_MESSAGE = void 0;
8
+ var _typeof2 = _interopRequireDefault(require("@babel/runtime/helpers/typeof"));
9
+ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
10
+ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
11
+ var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
12
+ var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
13
+ var _inherits2 = _interopRequireDefault(require("@babel/runtime/helpers/inherits"));
14
+ var _wrapNativeSuper2 = _interopRequireDefault(require("@babel/runtime/helpers/wrapNativeSuper"));
15
+ var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
16
+ function _callSuper(t, o, e) { return o = (0, _getPrototypeOf2.default)(o), (0, _possibleConstructorReturn2.default)(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], (0, _getPrototypeOf2.default)(t).constructor) : o.apply(t, e)); }
17
+ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
7
18
  var SyncBlockError = exports.SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
8
19
  SyncBlockError["Errored"] = "errored";
9
20
  SyncBlockError["NotFound"] = "not_found";
@@ -22,4 +33,54 @@ var SyncBlockError = exports.SyncBlockError = /*#__PURE__*/function (SyncBlockEr
22
33
  // block does not exist on this site (e.g. cross-site reference or hard deleted)
23
34
  SyncBlockError["EntityNotFound"] = "entity_not_found";
24
35
  return SyncBlockError;
25
- }({});
36
+ }({});
37
+ /**
38
+ * Helpers to distinguish "data provider not ready / torn down" from a genuine
39
+ * fetch/subscribe failure (EDITOR-7860). On Jira the provider is wired
40
+ * asynchronously and `destroy()` nulls it on orphaned managers, so queued/
41
+ * in-flight ops throw `Data provider not set` — previously mis-logged as a real
42
+ * error. These let throw and catch sites agree on one non-string-matched signal
43
+ * so the residual false errors are suppressed. Gated by
44
+ * `platform_editor_blocks_patch_3`.
45
+ *
46
+ * NB: these intentionally live here rather than in a dedicated module to avoid
47
+ * adding a downstream file to consuming Jira packages' Thunderstone complexity.
48
+ */
49
+
50
+ /** Legacy message — kept identical for gate-off and historical events. */
51
+ var PROVIDER_NOT_READY_MESSAGE = exports.PROVIDER_NOT_READY_MESSAGE = 'Data provider not set';
52
+
53
+ /**
54
+ * Thrown when a fetch/subscribe runs against a manager whose provider is not
55
+ * (yet) available or torn down. Keeps the legacy message + name so string
56
+ * matchers still work, while new code uses {@link isProviderNotReadyError}.
57
+ */
58
+ var ProviderNotReadyError = exports.ProviderNotReadyError = /*#__PURE__*/function (_Error) {
59
+ function ProviderNotReadyError() {
60
+ var _this;
61
+ var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : PROVIDER_NOT_READY_MESSAGE;
62
+ (0, _classCallCheck2.default)(this, ProviderNotReadyError);
63
+ _this = _callSuper(this, ProviderNotReadyError, [message]);
64
+ (0, _defineProperty2.default)(_this, "isProviderNotReadyError", true);
65
+ _this.name = 'ProviderNotReadyError';
66
+ // Restore prototype chain so instanceof works after transpilation.
67
+ Object.setPrototypeOf(_this, ProviderNotReadyError.prototype);
68
+ return _this;
69
+ }
70
+ (0, _inherits2.default)(ProviderNotReadyError, _Error);
71
+ return (0, _createClass2.default)(ProviderNotReadyError);
72
+ }( /*#__PURE__*/(0, _wrapNativeSuper2.default)(Error));
73
+ /**
74
+ * True when the value is a "provider not ready / torn down" condition, not a
75
+ * genuine failure. Recognises the tagged {@link ProviderNotReadyError} and the
76
+ * legacy message (for errors thrown before rollout).
77
+ */
78
+ var isProviderNotReadyError = exports.isProviderNotReadyError = function isProviderNotReadyError(error) {
79
+ if (error instanceof ProviderNotReadyError) {
80
+ return true;
81
+ }
82
+ if ((0, _typeof2.default)(error) === 'object' && error !== null && error.isProviderNotReadyError === true) {
83
+ return true;
84
+ }
85
+ return error instanceof Error && error.message === PROVIDER_NOT_READY_MESSAGE;
86
+ };
@@ -12,12 +12,14 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
12
12
  var _react = require("react");
13
13
  var _coreUtils = require("@atlaskit/editor-common/core-utils");
14
14
  var _monitoring = require("@atlaskit/editor-common/monitoring");
15
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
15
16
  var _types = require("../common/types");
16
17
  var _errorHandling = require("../utils/errorHandling");
17
18
  var _utils = require("../utils/utils");
18
19
  function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
19
20
  function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2.default)(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
20
21
  var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyncBlockData(manager, resourceId, localId, fireAnalyticsEvent) {
22
+ var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
21
23
  // Initialize both states from a single cache lookup to avoid race conditions.
22
24
  // When a block is moved/remounted, the old component's cleanup may clear the cache
23
25
  // before or after the new component mounts. By doing a single lookup, we ensure
@@ -41,6 +43,14 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
41
43
  syncBlockInstance = _useState2$.syncBlockInstance,
42
44
  isLoading = _useState2$.isLoading,
43
45
  setFetchState = _useState2[1];
46
+
47
+ // On Jira the data provider is wired asynchronously, so the manager can be
48
+ // constructed with `dataProvider === undefined`. Fetching/subscribing in that
49
+ // window throws `Data provider not set`, logged as a false fetch error
50
+ // (EDITOR-7860). Gate on readiness; once the provider resolves a new manager
51
+ // instance is created so `referenceManager` changes identity and the effect
52
+ // below re-runs, subscribing exactly once.
53
+ var isDataProviderReady = (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3') ? (_manager$referenceMan2 = (_manager$referenceMan3 = (_manager$referenceMan4 = manager.referenceManager).hasDataProvider) === null || _manager$referenceMan3 === void 0 ? void 0 : _manager$referenceMan3.call(_manager$referenceMan4)) !== null && _manager$referenceMan2 !== void 0 ? _manager$referenceMan2 : false : true;
44
54
  var reloadData = (0, _react.useCallback)( /*#__PURE__*/(0, _asyncToGenerator2.default)( /*#__PURE__*/_regenerator.default.mark(function _callee() {
45
55
  var syncBlockNode, _t;
46
56
  return _regenerator.default.wrap(function (_context) {
@@ -52,14 +62,20 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
52
62
  }
53
63
  return _context.abrupt("return");
54
64
  case 1:
55
- _context.prev = 1;
65
+ if (isDataProviderReady) {
66
+ _context.next = 2;
67
+ break;
68
+ }
69
+ return _context.abrupt("return");
70
+ case 2:
71
+ _context.prev = 2;
56
72
  syncBlockNode = resourceId && localId ? (0, _utils.createSyncBlockNode)(localId, resourceId) : null;
57
73
  if (syncBlockNode) {
58
- _context.next = 2;
74
+ _context.next = 3;
59
75
  break;
60
76
  }
61
77
  throw new Error('Failed to create sync block node from resourceid and localid');
62
- case 2:
78
+ case 3:
63
79
  setFetchState(function (prev) {
64
80
  return _objectSpread(_objectSpread({}, prev), {}, {
65
81
  isLoading: true
@@ -67,14 +83,25 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
67
83
  });
68
84
 
69
85
  // Fetch sync block data, the `subscribeToSyncBlock` will update the state once data is fetched
70
- _context.next = 3;
86
+ _context.next = 4;
71
87
  return manager.referenceManager.fetchSyncBlocksData([syncBlockNode]);
72
- case 3:
73
- _context.next = 5;
74
- break;
75
88
  case 4:
76
- _context.prev = 4;
77
- _t = _context["catch"](1);
89
+ _context.next = 7;
90
+ break;
91
+ case 5:
92
+ _context.prev = 5;
93
+ _t = _context["catch"](2);
94
+ if (!((0, _types.isProviderNotReadyError)(_t) && (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3'))) {
95
+ _context.next = 6;
96
+ break;
97
+ }
98
+ setFetchState(function (prev) {
99
+ return _objectSpread(_objectSpread({}, prev), {}, {
100
+ isLoading: true
101
+ });
102
+ });
103
+ return _context.abrupt("return");
104
+ case 6:
78
105
  (0, _monitoring.logException)(_t, {
79
106
  location: 'editor-synced-block-provider/useFetchSyncBlockData'
80
107
  });
@@ -91,24 +118,31 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
91
118
  isLoading: false
92
119
  });
93
120
  return _context.abrupt("return");
94
- case 5:
121
+ case 7:
95
122
  setFetchState(function (prev) {
96
123
  return _objectSpread(_objectSpread({}, prev), {}, {
97
124
  isLoading: false
98
125
  });
99
126
  });
100
- case 6:
127
+ case 8:
101
128
  case "end":
102
129
  return _context.stop();
103
130
  }
104
- }, _callee, null, [[1, 4]]);
105
- })), [isLoading, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
131
+ }, _callee, null, [[2, 5]]);
132
+ })), [isLoading, isDataProviderReady, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
106
133
  (0, _react.useEffect)(function () {
107
134
  if ((0, _coreUtils.isSSR)()) {
108
135
  // in SSR, we don't need to subscribe to updates,
109
136
  // instead we rely on pre-fetched data ONLY, see initialization of syncBlockInstance above
110
137
  return;
111
138
  }
139
+
140
+ // Not ready: skip subscribe (it would trigger a batched fetch and a false
141
+ // error) and keep `isLoading: true`. `isDataProviderReady` is in the deps,
142
+ // so the effect re-runs and subscribes once the provider resolves.
143
+ if (!isDataProviderReady) {
144
+ return;
145
+ }
112
146
  var unsubscribe = manager.referenceManager.subscribeToSyncBlock(resourceId || '', localId || '', function (data) {
113
147
  setFetchState({
114
148
  syncBlockInstance: data,
@@ -118,7 +152,7 @@ var useFetchSyncBlockData = exports.useFetchSyncBlockData = function useFetchSyn
118
152
  return function () {
119
153
  unsubscribe();
120
154
  };
121
- }, [localId, manager.referenceManager, resourceId]);
155
+ }, [isDataProviderReady, localId, manager.referenceManager, resourceId]);
122
156
  var ssrProviders = (0, _react.useMemo)(function () {
123
157
  return resourceId ? manager.referenceManager.getSSRProviders(resourceId) : null;
124
158
  }, [resourceId, manager.referenceManager]);
@@ -128,6 +128,10 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
128
128
  },
129
129
  getFireAnalyticsEvent: function getFireAnalyticsEvent() {
130
130
  return _this.fireAnalyticsEvent;
131
+ },
132
+ // EDITOR-7860: skip + re-queue fetches while not ready / torn down.
133
+ isProviderReady: function isProviderReady() {
134
+ return _this.hasDataProvider();
131
135
  }
132
136
  });
133
137
 
@@ -140,6 +144,19 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
140
144
  return node.type.name === 'syncBlock';
141
145
  }
142
146
 
147
+ /**
148
+ * Whether the async data provider is wired and the manager is not torn down.
149
+ * Consumers gate fetch/subscribe on this to avoid a false `Data provider not
150
+ * set` fetch error (EDITOR-7860). The `isDestroyed` check covers managers
151
+ * orphaned mid-flight during an async provider swap, where `dataProvider`
152
+ * alone is insufficient.
153
+ */
154
+ }, {
155
+ key: "hasDataProvider",
156
+ value: function hasDataProvider() {
157
+ return !this.isDestroyed && !!this.dataProvider;
158
+ }
159
+
143
160
  /**
144
161
  * Enables or disables real-time GraphQL subscriptions for block updates.
145
162
  * When enabled, the store manager will subscribe to real-time updates
@@ -531,22 +548,28 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
531
548
  return _context2.abrupt("return");
532
549
  case 2:
533
550
  if (this.dataProvider) {
551
+ _context2.next = 4;
552
+ break;
553
+ }
554
+ if (!(0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
534
555
  _context2.next = 3;
535
556
  break;
536
557
  }
537
- throw new Error('Data provider not set');
558
+ throw new _types.ProviderNotReadyError();
538
559
  case 3:
560
+ throw new Error('Data provider not set');
561
+ case 4:
539
562
  nodesToFetch.forEach(function (node) {
540
563
  _this4.syncBlockFetchDataRequests.set(node.attrs.resourceId, true);
541
564
  });
542
565
  (_this$fetchExperience6 = this.fetchExperience) === null || _this$fetchExperience6 === void 0 || _this$fetchExperience6.start({});
543
- _context2.next = 4;
566
+ _context2.next = 5;
544
567
  return this.dataProvider.fetchNodesData(nodesToFetch).finally(function () {
545
568
  nodesToFetch.forEach(function (node) {
546
569
  _this4.syncBlockFetchDataRequests.delete(node.attrs.resourceId);
547
570
  });
548
571
  });
549
- case 4:
572
+ case 5:
550
573
  data = _context2.sent;
551
574
  _this$processFetchedD2 = this.processFetchedData(data), hasUnexpectedError = _this$processFetchedD2.hasUnexpectedError, hasExpectedError = _this$processFetchedD2.hasExpectedError;
552
575
  if (hasUnexpectedError) {
@@ -560,7 +583,7 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
560
583
  } else {
561
584
  (_this$fetchExperience9 = this.fetchExperience) === null || _this$fetchExperience9 === void 0 || _this$fetchExperience9.success();
562
585
  }
563
- case 5:
586
+ case 6:
564
587
  case "end":
565
588
  return _context2.stop();
566
589
  }
@@ -938,6 +961,12 @@ var ReferenceSyncBlockStoreManager = exports.ReferenceSyncBlockStoreManager = /*
938
961
  return this._subscriptionManager.subscribeToSyncBlock(resourceId, localId, callback);
939
962
  } catch (error) {
940
963
  var _this$fireAnalyticsEv7;
964
+ // EDITOR-7860: benign not-ready/torn-down case — suppress both the
965
+ // exception-tracker log and the analytics event (checked first so the
966
+ // benign case stays fully silent). Gate-off behaviour is unchanged.
967
+ if ((0, _types.isProviderNotReadyError)(error) && (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
968
+ return function () {};
969
+ }
941
970
  (0, _monitoring.logException)(error, {
942
971
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
943
972
  });
@@ -10,6 +10,8 @@ var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/creat
10
10
  var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
11
11
  var _rafSchd = _interopRequireDefault(require("raf-schd"));
12
12
  var _monitoring = require("@atlaskit/editor-common/monitoring");
13
+ var _platformFeatureFlags = require("@atlaskit/platform-feature-flags");
14
+ var _types = require("../common/types");
13
15
  var _errorHandling = require("../utils/errorHandling");
14
16
  var _utils = require("../utils/utils");
15
17
  /**
@@ -32,6 +34,17 @@ var SyncBlockBatchFetcher = exports.SyncBlockBatchFetcher = /*#__PURE__*/functio
32
34
  if (_this.pendingFetchRequests.size === 0) {
33
35
  return;
34
36
  }
37
+
38
+ // EDITOR-7860: not ready — skip and leave resourceIds queued for the
39
+ // next batch once the provider resolves. Gate-off is unchanged (the
40
+ // readiness check is nested under the gate so it is not consulted when
41
+ // the gate is off, and `fg()` stays a standalone condition so gate
42
+ // exposure is still tracked — satisfies @atlaskit/platform/no-preconditioning).
43
+ if ((0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
44
+ if (_this.deps.isProviderReady && !_this.deps.isProviderReady()) {
45
+ return;
46
+ }
47
+ }
35
48
  var resourceIds = Array.from(_this.pendingFetchRequests);
36
49
  var syncBlockNodes = resourceIds.map(function (resId) {
37
50
  var subscriptions = _this.deps.getSubscriptions().get(resId) || {};
@@ -45,6 +58,20 @@ var SyncBlockBatchFetcher = exports.SyncBlockBatchFetcher = /*#__PURE__*/functio
45
58
  return _this.inFlightFetches.add(resId);
46
59
  });
47
60
  _this.deps.fetchSyncBlocksData(syncBlockNodes).catch(function (error) {
61
+ // EDITOR-7860: benign not-ready throw — re-queue for retry and emit
62
+ // nothing (no analytics, no exception-tracker noise). Checked before
63
+ // `logException` so the benign case stays silent. Re-schedule so the
64
+ // re-queued IDs are retried on the next frame even if no further
65
+ // `queueFetch()` arrives. Gate-off behaviour is unchanged.
66
+ if ((0, _types.isProviderNotReadyError)(error) && (0, _platformFeatureFlags.fg)('platform_editor_blocks_patch_3')) {
67
+ resourceIds.forEach(function (resId) {
68
+ return _this.pendingFetchRequests.add(resId);
69
+ });
70
+ if (!_this.isDestroyed) {
71
+ _this.scheduledBatchFetch();
72
+ }
73
+ return;
74
+ }
48
75
  (0, _monitoring.logException)(error, {
49
76
  location: 'editor-synced-block-provider/syncBlockBatchFetcher/batchedFetchSyncBlocks'
50
77
  });
@@ -1,3 +1,4 @@
1
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
1
2
  export let SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
2
3
  SyncBlockError["Errored"] = "errored";
3
4
  SyncBlockError["NotFound"] = "not_found";
@@ -16,4 +17,49 @@ export let SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
16
17
  // block does not exist on this site (e.g. cross-site reference or hard deleted)
17
18
  SyncBlockError["EntityNotFound"] = "entity_not_found";
18
19
  return SyncBlockError;
19
- }({});
20
+ }({});
21
+ /**
22
+ * Helpers to distinguish "data provider not ready / torn down" from a genuine
23
+ * fetch/subscribe failure (EDITOR-7860). On Jira the provider is wired
24
+ * asynchronously and `destroy()` nulls it on orphaned managers, so queued/
25
+ * in-flight ops throw `Data provider not set` — previously mis-logged as a real
26
+ * error. These let throw and catch sites agree on one non-string-matched signal
27
+ * so the residual false errors are suppressed. Gated by
28
+ * `platform_editor_blocks_patch_3`.
29
+ *
30
+ * NB: these intentionally live here rather than in a dedicated module to avoid
31
+ * adding a downstream file to consuming Jira packages' Thunderstone complexity.
32
+ */
33
+
34
+ /** Legacy message — kept identical for gate-off and historical events. */
35
+ export const PROVIDER_NOT_READY_MESSAGE = 'Data provider not set';
36
+
37
+ /**
38
+ * Thrown when a fetch/subscribe runs against a manager whose provider is not
39
+ * (yet) available or torn down. Keeps the legacy message + name so string
40
+ * matchers still work, while new code uses {@link isProviderNotReadyError}.
41
+ */
42
+ export class ProviderNotReadyError extends Error {
43
+ constructor(message = PROVIDER_NOT_READY_MESSAGE) {
44
+ super(message);
45
+ _defineProperty(this, "isProviderNotReadyError", true);
46
+ this.name = 'ProviderNotReadyError';
47
+ // Restore prototype chain so instanceof works after transpilation.
48
+ Object.setPrototypeOf(this, ProviderNotReadyError.prototype);
49
+ }
50
+ }
51
+
52
+ /**
53
+ * True when the value is a "provider not ready / torn down" condition, not a
54
+ * genuine failure. Recognises the tagged {@link ProviderNotReadyError} and the
55
+ * legacy message (for errors thrown before rollout).
56
+ */
57
+ export const isProviderNotReadyError = error => {
58
+ if (error instanceof ProviderNotReadyError) {
59
+ return true;
60
+ }
61
+ if (typeof error === 'object' && error !== null && error.isProviderNotReadyError === true) {
62
+ return true;
63
+ }
64
+ return error instanceof Error && error.message === PROVIDER_NOT_READY_MESSAGE;
65
+ };
@@ -1,10 +1,12 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from 'react';
2
2
  import { isSSR } from '@atlaskit/editor-common/core-utils';
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
- import { SyncBlockError } from '../common/types';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { isProviderNotReadyError, SyncBlockError } from '../common/types';
5
6
  import { fetchErrorPayload } from '../utils/errorHandling';
6
7
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
7
8
  export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalyticsEvent) => {
9
+ var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
8
10
  // Initialize both states from a single cache lookup to avoid race conditions.
9
11
  // When a block is moved/remounted, the old component's cleanup may clear the cache
10
12
  // before or after the new component mounts. By doing a single lookup, we ensure
@@ -26,10 +28,23 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
26
28
  isLoading: true
27
29
  };
28
30
  });
31
+
32
+ // On Jira the data provider is wired asynchronously, so the manager can be
33
+ // constructed with `dataProvider === undefined`. Fetching/subscribing in that
34
+ // window throws `Data provider not set`, logged as a false fetch error
35
+ // (EDITOR-7860). Gate on readiness; once the provider resolves a new manager
36
+ // instance is created so `referenceManager` changes identity and the effect
37
+ // below re-runs, subscribing exactly once.
38
+ const isDataProviderReady = fg('platform_editor_blocks_patch_3') ? (_manager$referenceMan2 = (_manager$referenceMan3 = (_manager$referenceMan4 = manager.referenceManager).hasDataProvider) === null || _manager$referenceMan3 === void 0 ? void 0 : _manager$referenceMan3.call(_manager$referenceMan4)) !== null && _manager$referenceMan2 !== void 0 ? _manager$referenceMan2 : false : true;
29
39
  const reloadData = useCallback(async () => {
30
40
  if (isLoading) {
31
41
  return;
32
42
  }
43
+
44
+ // Not ready: skip fetch, emit no error (see readiness note above).
45
+ if (!isDataProviderReady) {
46
+ return;
47
+ }
33
48
  try {
34
49
  const syncBlockNode = resourceId && localId ? createSyncBlockNode(localId, resourceId) : null;
35
50
  if (!syncBlockNode) {
@@ -43,6 +58,17 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
43
58
  // Fetch sync block data, the `subscribeToSyncBlock` will update the state once data is fetched
44
59
  await manager.referenceManager.fetchSyncBlocksData([syncBlockNode]);
45
60
  } catch (error) {
61
+ // EDITOR-7860: benign not-ready throw — emit no error and stay loading
62
+ // so it resolves on retry once the provider is wired. Checked before
63
+ // `logException` so the benign case produces no exception-tracker noise.
64
+ // Gate-off behaviour is unchanged.
65
+ if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
66
+ setFetchState(prev => ({
67
+ ...prev,
68
+ isLoading: true
69
+ }));
70
+ return;
71
+ }
46
72
  logException(error, {
47
73
  location: 'editor-synced-block-provider/useFetchSyncBlockData'
48
74
  });
@@ -64,13 +90,20 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
64
90
  ...prev,
65
91
  isLoading: false
66
92
  }));
67
- }, [isLoading, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
93
+ }, [isLoading, isDataProviderReady, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
68
94
  useEffect(() => {
69
95
  if (isSSR()) {
70
96
  // in SSR, we don't need to subscribe to updates,
71
97
  // instead we rely on pre-fetched data ONLY, see initialization of syncBlockInstance above
72
98
  return;
73
99
  }
100
+
101
+ // Not ready: skip subscribe (it would trigger a batched fetch and a false
102
+ // error) and keep `isLoading: true`. `isDataProviderReady` is in the deps,
103
+ // so the effect re-runs and subscribes once the provider resolves.
104
+ if (!isDataProviderReady) {
105
+ return;
106
+ }
74
107
  const unsubscribe = manager.referenceManager.subscribeToSyncBlock(resourceId || '', localId || '', data => {
75
108
  setFetchState({
76
109
  syncBlockInstance: data,
@@ -80,7 +113,7 @@ export const useFetchSyncBlockData = (manager, resourceId, localId, fireAnalytic
80
113
  return () => {
81
114
  unsubscribe();
82
115
  };
83
- }, [localId, manager.referenceManager, resourceId]);
116
+ }, [isDataProviderReady, localId, manager.referenceManager, resourceId]);
84
117
  const ssrProviders = useMemo(() => {
85
118
  return resourceId ? manager.referenceManager.getSSRProviders(resourceId) : null;
86
119
  }, [resourceId, manager.referenceManager]);
@@ -2,7 +2,7 @@ import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import isEqual from 'lodash/isEqual';
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
4
  import { fg } from '@atlaskit/platform-feature-flags';
5
- import { SyncBlockError } from '../common/types';
5
+ import { isProviderNotReadyError, ProviderNotReadyError, SyncBlockError } from '../common/types';
6
6
  import { buildErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
7
7
  import { getFetchExperience, getFetchSourceInfoExperience, getSaveReferenceExperience } from '../utils/experienceTracking';
8
8
  import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
@@ -79,7 +79,9 @@ export class ReferenceSyncBlockStoreManager {
79
79
  this._batchFetcher = new SyncBlockBatchFetcher({
80
80
  getSubscriptions: () => this._subscriptionManager.getSubscriptions(),
81
81
  fetchSyncBlocksData: nodes => this.fetchSyncBlocksData(nodes),
82
- getFireAnalyticsEvent: () => this.fireAnalyticsEvent
82
+ getFireAnalyticsEvent: () => this.fireAnalyticsEvent,
83
+ // EDITOR-7860: skip + re-queue fetches while not ready / torn down.
84
+ isProviderReady: () => this.hasDataProvider()
83
85
  });
84
86
 
85
87
  // The provider might have SSR data cache already set, so we need to update the cache in session memory storage
@@ -89,6 +91,17 @@ export class ReferenceSyncBlockStoreManager {
89
91
  return node.type.name === 'syncBlock';
90
92
  }
91
93
 
94
+ /**
95
+ * Whether the async data provider is wired and the manager is not torn down.
96
+ * Consumers gate fetch/subscribe on this to avoid a false `Data provider not
97
+ * set` fetch error (EDITOR-7860). The `isDestroyed` check covers managers
98
+ * orphaned mid-flight during an async provider swap, where `dataProvider`
99
+ * alone is insufficient.
100
+ */
101
+ hasDataProvider() {
102
+ return !this.isDestroyed && !!this.dataProvider;
103
+ }
104
+
92
105
  /**
93
106
  * Enables or disables real-time GraphQL subscriptions for block updates.
94
107
  * When enabled, the store manager will subscribe to real-time updates
@@ -412,6 +425,11 @@ export class ReferenceSyncBlockStoreManager {
412
425
  return;
413
426
  }
414
427
  if (!this.dataProvider) {
428
+ // EDITOR-7860: tag the throw so catch sites can suppress the benign
429
+ // not-ready/torn-down case. Gate-off keeps the legacy throw + message.
430
+ if (fg('platform_editor_blocks_patch_3')) {
431
+ throw new ProviderNotReadyError();
432
+ }
415
433
  throw new Error('Data provider not set');
416
434
  }
417
435
  nodesToFetch.forEach(node => {
@@ -779,6 +797,12 @@ export class ReferenceSyncBlockStoreManager {
779
797
  return this._subscriptionManager.subscribeToSyncBlock(resourceId, localId, callback);
780
798
  } catch (error) {
781
799
  var _this$fireAnalyticsEv11;
800
+ // EDITOR-7860: benign not-ready/torn-down case — suppress both the
801
+ // exception-tracker log and the analytics event (checked first so the
802
+ // benign case stays fully silent). Gate-off behaviour is unchanged.
803
+ if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
804
+ return () => {};
805
+ }
782
806
  logException(error, {
783
807
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
784
808
  });
@@ -1,6 +1,8 @@
1
1
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
2
2
  import rafSchedule from 'raf-schd';
3
3
  import { logException } from '@atlaskit/editor-common/monitoring';
4
+ import { fg } from '@atlaskit/platform-feature-flags';
5
+ import { isProviderNotReadyError } from '../common/types';
4
6
  import { fetchErrorPayload } from '../utils/errorHandling';
5
7
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
6
8
  /**
@@ -21,6 +23,17 @@ export class SyncBlockBatchFetcher {
21
23
  if (this.pendingFetchRequests.size === 0) {
22
24
  return;
23
25
  }
26
+
27
+ // EDITOR-7860: not ready — skip and leave resourceIds queued for the
28
+ // next batch once the provider resolves. Gate-off is unchanged (the
29
+ // readiness check is nested under the gate so it is not consulted when
30
+ // the gate is off, and `fg()` stays a standalone condition so gate
31
+ // exposure is still tracked — satisfies @atlaskit/platform/no-preconditioning).
32
+ if (fg('platform_editor_blocks_patch_3')) {
33
+ if (this.deps.isProviderReady && !this.deps.isProviderReady()) {
34
+ return;
35
+ }
36
+ }
24
37
  const resourceIds = Array.from(this.pendingFetchRequests);
25
38
  const syncBlockNodes = resourceIds.map(resId => {
26
39
  const subscriptions = this.deps.getSubscriptions().get(resId) || {};
@@ -32,6 +45,18 @@ export class SyncBlockBatchFetcher {
32
45
  // Track in-flight before the fetch so guards remain positive.
33
46
  resourceIds.forEach(resId => this.inFlightFetches.add(resId));
34
47
  this.deps.fetchSyncBlocksData(syncBlockNodes).catch(error => {
48
+ // EDITOR-7860: benign not-ready throw — re-queue for retry and emit
49
+ // nothing (no analytics, no exception-tracker noise). Checked before
50
+ // `logException` so the benign case stays silent. Re-schedule so the
51
+ // re-queued IDs are retried on the next frame even if no further
52
+ // `queueFetch()` arrives. Gate-off behaviour is unchanged.
53
+ if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
54
+ resourceIds.forEach(resId => this.pendingFetchRequests.add(resId));
55
+ if (!this.isDestroyed) {
56
+ this.scheduledBatchFetch();
57
+ }
58
+ return;
59
+ }
35
60
  logException(error, {
36
61
  location: 'editor-synced-block-provider/syncBlockBatchFetcher/batchedFetchSyncBlocks'
37
62
  });
@@ -1,3 +1,13 @@
1
+ import _typeof from "@babel/runtime/helpers/typeof";
2
+ import _createClass from "@babel/runtime/helpers/createClass";
3
+ import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
4
+ import _possibleConstructorReturn from "@babel/runtime/helpers/possibleConstructorReturn";
5
+ import _getPrototypeOf from "@babel/runtime/helpers/getPrototypeOf";
6
+ import _inherits from "@babel/runtime/helpers/inherits";
7
+ import _wrapNativeSuper from "@babel/runtime/helpers/wrapNativeSuper";
8
+ import _defineProperty from "@babel/runtime/helpers/defineProperty";
9
+ function _callSuper(t, o, e) { return o = _getPrototypeOf(o), _possibleConstructorReturn(t, _isNativeReflectConstruct() ? Reflect.construct(o, e || [], _getPrototypeOf(t).constructor) : o.apply(t, e)); }
10
+ function _isNativeReflectConstruct() { try { var t = !Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); } catch (t) {} return (_isNativeReflectConstruct = function _isNativeReflectConstruct() { return !!t; })(); }
1
11
  export var SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
2
12
  SyncBlockError["Errored"] = "errored";
3
13
  SyncBlockError["NotFound"] = "not_found";
@@ -16,4 +26,55 @@ export var SyncBlockError = /*#__PURE__*/function (SyncBlockError) {
16
26
  // block does not exist on this site (e.g. cross-site reference or hard deleted)
17
27
  SyncBlockError["EntityNotFound"] = "entity_not_found";
18
28
  return SyncBlockError;
19
- }({});
29
+ }({});
30
+ /**
31
+ * Helpers to distinguish "data provider not ready / torn down" from a genuine
32
+ * fetch/subscribe failure (EDITOR-7860). On Jira the provider is wired
33
+ * asynchronously and `destroy()` nulls it on orphaned managers, so queued/
34
+ * in-flight ops throw `Data provider not set` — previously mis-logged as a real
35
+ * error. These let throw and catch sites agree on one non-string-matched signal
36
+ * so the residual false errors are suppressed. Gated by
37
+ * `platform_editor_blocks_patch_3`.
38
+ *
39
+ * NB: these intentionally live here rather than in a dedicated module to avoid
40
+ * adding a downstream file to consuming Jira packages' Thunderstone complexity.
41
+ */
42
+
43
+ /** Legacy message — kept identical for gate-off and historical events. */
44
+ export var PROVIDER_NOT_READY_MESSAGE = 'Data provider not set';
45
+
46
+ /**
47
+ * Thrown when a fetch/subscribe runs against a manager whose provider is not
48
+ * (yet) available or torn down. Keeps the legacy message + name so string
49
+ * matchers still work, while new code uses {@link isProviderNotReadyError}.
50
+ */
51
+ export var ProviderNotReadyError = /*#__PURE__*/function (_Error) {
52
+ function ProviderNotReadyError() {
53
+ var _this;
54
+ var message = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : PROVIDER_NOT_READY_MESSAGE;
55
+ _classCallCheck(this, ProviderNotReadyError);
56
+ _this = _callSuper(this, ProviderNotReadyError, [message]);
57
+ _defineProperty(_this, "isProviderNotReadyError", true);
58
+ _this.name = 'ProviderNotReadyError';
59
+ // Restore prototype chain so instanceof works after transpilation.
60
+ Object.setPrototypeOf(_this, ProviderNotReadyError.prototype);
61
+ return _this;
62
+ }
63
+ _inherits(ProviderNotReadyError, _Error);
64
+ return _createClass(ProviderNotReadyError);
65
+ }( /*#__PURE__*/_wrapNativeSuper(Error));
66
+
67
+ /**
68
+ * True when the value is a "provider not ready / torn down" condition, not a
69
+ * genuine failure. Recognises the tagged {@link ProviderNotReadyError} and the
70
+ * legacy message (for errors thrown before rollout).
71
+ */
72
+ export var isProviderNotReadyError = function isProviderNotReadyError(error) {
73
+ if (error instanceof ProviderNotReadyError) {
74
+ return true;
75
+ }
76
+ if (_typeof(error) === 'object' && error !== null && error.isProviderNotReadyError === true) {
77
+ return true;
78
+ }
79
+ return error instanceof Error && error.message === PROVIDER_NOT_READY_MESSAGE;
80
+ };
@@ -7,10 +7,12 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
7
7
  import { useCallback, useEffect, useMemo, useState } from 'react';
8
8
  import { isSSR } from '@atlaskit/editor-common/core-utils';
9
9
  import { logException } from '@atlaskit/editor-common/monitoring';
10
- import { SyncBlockError } from '../common/types';
10
+ import { fg } from '@atlaskit/platform-feature-flags';
11
+ import { isProviderNotReadyError, SyncBlockError } from '../common/types';
11
12
  import { fetchErrorPayload } from '../utils/errorHandling';
12
13
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
13
14
  export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resourceId, localId, fireAnalyticsEvent) {
15
+ var _manager$referenceMan2, _manager$referenceMan3, _manager$referenceMan4;
14
16
  // Initialize both states from a single cache lookup to avoid race conditions.
15
17
  // When a block is moved/remounted, the old component's cleanup may clear the cache
16
18
  // before or after the new component mounts. By doing a single lookup, we ensure
@@ -34,6 +36,14 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
34
36
  syncBlockInstance = _useState2$.syncBlockInstance,
35
37
  isLoading = _useState2$.isLoading,
36
38
  setFetchState = _useState2[1];
39
+
40
+ // On Jira the data provider is wired asynchronously, so the manager can be
41
+ // constructed with `dataProvider === undefined`. Fetching/subscribing in that
42
+ // window throws `Data provider not set`, logged as a false fetch error
43
+ // (EDITOR-7860). Gate on readiness; once the provider resolves a new manager
44
+ // instance is created so `referenceManager` changes identity and the effect
45
+ // below re-runs, subscribing exactly once.
46
+ var isDataProviderReady = fg('platform_editor_blocks_patch_3') ? (_manager$referenceMan2 = (_manager$referenceMan3 = (_manager$referenceMan4 = manager.referenceManager).hasDataProvider) === null || _manager$referenceMan3 === void 0 ? void 0 : _manager$referenceMan3.call(_manager$referenceMan4)) !== null && _manager$referenceMan2 !== void 0 ? _manager$referenceMan2 : false : true;
37
47
  var reloadData = useCallback( /*#__PURE__*/_asyncToGenerator( /*#__PURE__*/_regeneratorRuntime.mark(function _callee() {
38
48
  var syncBlockNode, _t;
39
49
  return _regeneratorRuntime.wrap(function (_context) {
@@ -45,14 +55,20 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
45
55
  }
46
56
  return _context.abrupt("return");
47
57
  case 1:
48
- _context.prev = 1;
58
+ if (isDataProviderReady) {
59
+ _context.next = 2;
60
+ break;
61
+ }
62
+ return _context.abrupt("return");
63
+ case 2:
64
+ _context.prev = 2;
49
65
  syncBlockNode = resourceId && localId ? createSyncBlockNode(localId, resourceId) : null;
50
66
  if (syncBlockNode) {
51
- _context.next = 2;
67
+ _context.next = 3;
52
68
  break;
53
69
  }
54
70
  throw new Error('Failed to create sync block node from resourceid and localid');
55
- case 2:
71
+ case 3:
56
72
  setFetchState(function (prev) {
57
73
  return _objectSpread(_objectSpread({}, prev), {}, {
58
74
  isLoading: true
@@ -60,14 +76,25 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
60
76
  });
61
77
 
62
78
  // Fetch sync block data, the `subscribeToSyncBlock` will update the state once data is fetched
63
- _context.next = 3;
79
+ _context.next = 4;
64
80
  return manager.referenceManager.fetchSyncBlocksData([syncBlockNode]);
65
- case 3:
66
- _context.next = 5;
67
- break;
68
81
  case 4:
69
- _context.prev = 4;
70
- _t = _context["catch"](1);
82
+ _context.next = 7;
83
+ break;
84
+ case 5:
85
+ _context.prev = 5;
86
+ _t = _context["catch"](2);
87
+ if (!(isProviderNotReadyError(_t) && fg('platform_editor_blocks_patch_3'))) {
88
+ _context.next = 6;
89
+ break;
90
+ }
91
+ setFetchState(function (prev) {
92
+ return _objectSpread(_objectSpread({}, prev), {}, {
93
+ isLoading: true
94
+ });
95
+ });
96
+ return _context.abrupt("return");
97
+ case 6:
71
98
  logException(_t, {
72
99
  location: 'editor-synced-block-provider/useFetchSyncBlockData'
73
100
  });
@@ -84,24 +111,31 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
84
111
  isLoading: false
85
112
  });
86
113
  return _context.abrupt("return");
87
- case 5:
114
+ case 7:
88
115
  setFetchState(function (prev) {
89
116
  return _objectSpread(_objectSpread({}, prev), {}, {
90
117
  isLoading: false
91
118
  });
92
119
  });
93
- case 6:
120
+ case 8:
94
121
  case "end":
95
122
  return _context.stop();
96
123
  }
97
- }, _callee, null, [[1, 4]]);
98
- })), [isLoading, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
124
+ }, _callee, null, [[2, 5]]);
125
+ })), [isLoading, isDataProviderReady, localId, manager.referenceManager, resourceId, fireAnalyticsEvent]);
99
126
  useEffect(function () {
100
127
  if (isSSR()) {
101
128
  // in SSR, we don't need to subscribe to updates,
102
129
  // instead we rely on pre-fetched data ONLY, see initialization of syncBlockInstance above
103
130
  return;
104
131
  }
132
+
133
+ // Not ready: skip subscribe (it would trigger a batched fetch and a false
134
+ // error) and keep `isLoading: true`. `isDataProviderReady` is in the deps,
135
+ // so the effect re-runs and subscribes once the provider resolves.
136
+ if (!isDataProviderReady) {
137
+ return;
138
+ }
105
139
  var unsubscribe = manager.referenceManager.subscribeToSyncBlock(resourceId || '', localId || '', function (data) {
106
140
  setFetchState({
107
141
  syncBlockInstance: data,
@@ -111,7 +145,7 @@ export var useFetchSyncBlockData = function useFetchSyncBlockData(manager, resou
111
145
  return function () {
112
146
  unsubscribe();
113
147
  };
114
- }, [localId, manager.referenceManager, resourceId]);
148
+ }, [isDataProviderReady, localId, manager.referenceManager, resourceId]);
115
149
  var ssrProviders = useMemo(function () {
116
150
  return resourceId ? manager.referenceManager.getSSRProviders(resourceId) : null;
117
151
  }, [resourceId, manager.referenceManager]);
@@ -12,7 +12,7 @@ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t =
12
12
  import isEqual from 'lodash/isEqual';
13
13
  import { logException } from '@atlaskit/editor-common/monitoring';
14
14
  import { fg } from '@atlaskit/platform-feature-flags';
15
- import { SyncBlockError } from '../common/types';
15
+ import { isProviderNotReadyError, ProviderNotReadyError, SyncBlockError } from '../common/types';
16
16
  import { buildErrorAttribution, cacheDeletionForcedPayload, fetchErrorPayload, fetchSuccessPayload, getSourceInfoErrorPayload, sourceInfoOrphanedPayload, updateReferenceErrorPayload } from '../utils/errorHandling';
17
17
  import { getFetchExperience, getFetchSourceInfoExperience, getSaveReferenceExperience } from '../utils/experienceTracking';
18
18
  import { resolveSyncBlockInstance } from '../utils/resolveSyncBlockInstance';
@@ -121,6 +121,10 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
121
121
  },
122
122
  getFireAnalyticsEvent: function getFireAnalyticsEvent() {
123
123
  return _this.fireAnalyticsEvent;
124
+ },
125
+ // EDITOR-7860: skip + re-queue fetches while not ready / torn down.
126
+ isProviderReady: function isProviderReady() {
127
+ return _this.hasDataProvider();
124
128
  }
125
129
  });
126
130
 
@@ -133,6 +137,19 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
133
137
  return node.type.name === 'syncBlock';
134
138
  }
135
139
 
140
+ /**
141
+ * Whether the async data provider is wired and the manager is not torn down.
142
+ * Consumers gate fetch/subscribe on this to avoid a false `Data provider not
143
+ * set` fetch error (EDITOR-7860). The `isDestroyed` check covers managers
144
+ * orphaned mid-flight during an async provider swap, where `dataProvider`
145
+ * alone is insufficient.
146
+ */
147
+ }, {
148
+ key: "hasDataProvider",
149
+ value: function hasDataProvider() {
150
+ return !this.isDestroyed && !!this.dataProvider;
151
+ }
152
+
136
153
  /**
137
154
  * Enables or disables real-time GraphQL subscriptions for block updates.
138
155
  * When enabled, the store manager will subscribe to real-time updates
@@ -524,22 +541,28 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
524
541
  return _context2.abrupt("return");
525
542
  case 2:
526
543
  if (this.dataProvider) {
544
+ _context2.next = 4;
545
+ break;
546
+ }
547
+ if (!fg('platform_editor_blocks_patch_3')) {
527
548
  _context2.next = 3;
528
549
  break;
529
550
  }
530
- throw new Error('Data provider not set');
551
+ throw new ProviderNotReadyError();
531
552
  case 3:
553
+ throw new Error('Data provider not set');
554
+ case 4:
532
555
  nodesToFetch.forEach(function (node) {
533
556
  _this4.syncBlockFetchDataRequests.set(node.attrs.resourceId, true);
534
557
  });
535
558
  (_this$fetchExperience6 = this.fetchExperience) === null || _this$fetchExperience6 === void 0 || _this$fetchExperience6.start({});
536
- _context2.next = 4;
559
+ _context2.next = 5;
537
560
  return this.dataProvider.fetchNodesData(nodesToFetch).finally(function () {
538
561
  nodesToFetch.forEach(function (node) {
539
562
  _this4.syncBlockFetchDataRequests.delete(node.attrs.resourceId);
540
563
  });
541
564
  });
542
- case 4:
565
+ case 5:
543
566
  data = _context2.sent;
544
567
  _this$processFetchedD2 = this.processFetchedData(data), hasUnexpectedError = _this$processFetchedD2.hasUnexpectedError, hasExpectedError = _this$processFetchedD2.hasExpectedError;
545
568
  if (hasUnexpectedError) {
@@ -553,7 +576,7 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
553
576
  } else {
554
577
  (_this$fetchExperience9 = this.fetchExperience) === null || _this$fetchExperience9 === void 0 || _this$fetchExperience9.success();
555
578
  }
556
- case 5:
579
+ case 6:
557
580
  case "end":
558
581
  return _context2.stop();
559
582
  }
@@ -931,6 +954,12 @@ export var ReferenceSyncBlockStoreManager = /*#__PURE__*/function () {
931
954
  return this._subscriptionManager.subscribeToSyncBlock(resourceId, localId, callback);
932
955
  } catch (error) {
933
956
  var _this$fireAnalyticsEv7;
957
+ // EDITOR-7860: benign not-ready/torn-down case — suppress both the
958
+ // exception-tracker log and the analytics event (checked first so the
959
+ // benign case stays fully silent). Gate-off behaviour is unchanged.
960
+ if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
961
+ return function () {};
962
+ }
934
963
  logException(error, {
935
964
  location: 'editor-synced-block-provider/referenceSyncBlockStoreManager'
936
965
  });
@@ -3,6 +3,8 @@ import _createClass from "@babel/runtime/helpers/createClass";
3
3
  import _defineProperty from "@babel/runtime/helpers/defineProperty";
4
4
  import rafSchedule from 'raf-schd';
5
5
  import { logException } from '@atlaskit/editor-common/monitoring';
6
+ import { fg } from '@atlaskit/platform-feature-flags';
7
+ import { isProviderNotReadyError } from '../common/types';
6
8
  import { fetchErrorPayload } from '../utils/errorHandling';
7
9
  import { createSyncBlockNode, getSourceProductFromResourceIdSafe } from '../utils/utils';
8
10
  /**
@@ -25,6 +27,17 @@ export var SyncBlockBatchFetcher = /*#__PURE__*/function () {
25
27
  if (_this.pendingFetchRequests.size === 0) {
26
28
  return;
27
29
  }
30
+
31
+ // EDITOR-7860: not ready — skip and leave resourceIds queued for the
32
+ // next batch once the provider resolves. Gate-off is unchanged (the
33
+ // readiness check is nested under the gate so it is not consulted when
34
+ // the gate is off, and `fg()` stays a standalone condition so gate
35
+ // exposure is still tracked — satisfies @atlaskit/platform/no-preconditioning).
36
+ if (fg('platform_editor_blocks_patch_3')) {
37
+ if (_this.deps.isProviderReady && !_this.deps.isProviderReady()) {
38
+ return;
39
+ }
40
+ }
28
41
  var resourceIds = Array.from(_this.pendingFetchRequests);
29
42
  var syncBlockNodes = resourceIds.map(function (resId) {
30
43
  var subscriptions = _this.deps.getSubscriptions().get(resId) || {};
@@ -38,6 +51,20 @@ export var SyncBlockBatchFetcher = /*#__PURE__*/function () {
38
51
  return _this.inFlightFetches.add(resId);
39
52
  });
40
53
  _this.deps.fetchSyncBlocksData(syncBlockNodes).catch(function (error) {
54
+ // EDITOR-7860: benign not-ready throw — re-queue for retry and emit
55
+ // nothing (no analytics, no exception-tracker noise). Checked before
56
+ // `logException` so the benign case stays silent. Re-schedule so the
57
+ // re-queued IDs are retried on the next frame even if no further
58
+ // `queueFetch()` arrives. Gate-off behaviour is unchanged.
59
+ if (isProviderNotReadyError(error) && fg('platform_editor_blocks_patch_3')) {
60
+ resourceIds.forEach(function (resId) {
61
+ return _this.pendingFetchRequests.add(resId);
62
+ });
63
+ if (!_this.isDestroyed) {
64
+ _this.scheduledBatchFetch();
65
+ }
66
+ return;
67
+ }
41
68
  logException(error, {
42
69
  location: 'editor-synced-block-provider/syncBlockBatchFetcher/batchedFetchSyncBlocks'
43
70
  });
@@ -80,3 +80,32 @@ export type SyncBlockPrefetchData = {
80
80
  prefetchPromise: Promise<SyncBlockInstance[] | undefined>;
81
81
  resourceIds: string[];
82
82
  };
83
+ /**
84
+ * Helpers to distinguish "data provider not ready / torn down" from a genuine
85
+ * fetch/subscribe failure (EDITOR-7860). On Jira the provider is wired
86
+ * asynchronously and `destroy()` nulls it on orphaned managers, so queued/
87
+ * in-flight ops throw `Data provider not set` — previously mis-logged as a real
88
+ * error. These let throw and catch sites agree on one non-string-matched signal
89
+ * so the residual false errors are suppressed. Gated by
90
+ * `platform_editor_blocks_patch_3`.
91
+ *
92
+ * NB: these intentionally live here rather than in a dedicated module to avoid
93
+ * adding a downstream file to consuming Jira packages' Thunderstone complexity.
94
+ */
95
+ /** Legacy message — kept identical for gate-off and historical events. */
96
+ export declare const PROVIDER_NOT_READY_MESSAGE = "Data provider not set";
97
+ /**
98
+ * Thrown when a fetch/subscribe runs against a manager whose provider is not
99
+ * (yet) available or torn down. Keeps the legacy message + name so string
100
+ * matchers still work, while new code uses {@link isProviderNotReadyError}.
101
+ */
102
+ export declare class ProviderNotReadyError extends Error {
103
+ readonly isProviderNotReadyError = true;
104
+ constructor(message?: string);
105
+ }
106
+ /**
107
+ * True when the value is a "provider not ready / torn down" condition, not a
108
+ * genuine failure. Recognises the tagged {@link ProviderNotReadyError} and the
109
+ * legacy message (for errors thrown before rollout).
110
+ */
111
+ export declare const isProviderNotReadyError: (error: unknown) => boolean;
@@ -31,6 +31,14 @@ export declare class ReferenceSyncBlockStoreManager {
31
31
  private _batchFetcher;
32
32
  constructor(dataProvider?: SyncBlockDataProviderInterface, viewMode?: ViewMode);
33
33
  isReferenceBlock(node: PMNode): boolean;
34
+ /**
35
+ * Whether the async data provider is wired and the manager is not torn down.
36
+ * Consumers gate fetch/subscribe on this to avoid a false `Data provider not
37
+ * set` fetch error (EDITOR-7860). The `isDestroyed` check covers managers
38
+ * orphaned mid-flight during an async provider swap, where `dataProvider`
39
+ * alone is insufficient.
40
+ */
41
+ hasDataProvider(): boolean;
34
42
  /**
35
43
  * Enables or disables real-time GraphQL subscriptions for block updates.
36
44
  * When enabled, the store manager will subscribe to real-time updates
@@ -7,6 +7,13 @@ export interface SyncBlockBatchFetcherDeps {
7
7
  getSubscriptions: () => Map<ResourceId, {
8
8
  [localId: BlockInstanceId]: SubscriptionCallback;
9
9
  }>;
10
+ /**
11
+ * Returns true when the data provider is wired and not torn down. When false,
12
+ * the fetcher skips and re-queues so the IDs are fetched once ready, avoiding
13
+ * the false `Data provider not set` errors (EDITOR-7860). Optional; when
14
+ * omitted, behaviour is unchanged.
15
+ */
16
+ isProviderReady?: () => boolean;
10
17
  }
11
18
  /**
12
19
  * Handles debounced batch-fetching of sync block data via `raf-schd`.
package/package.json CHANGED
@@ -30,7 +30,7 @@
30
30
  "uuid": "^3.1.0"
31
31
  },
32
32
  "peerDependencies": {
33
- "@atlaskit/editor-common": "^116.14.0",
33
+ "@atlaskit/editor-common": "^116.15.0",
34
34
  "react": "^18.2.0"
35
35
  },
36
36
  "devDependencies": {
@@ -74,7 +74,7 @@
74
74
  }
75
75
  },
76
76
  "name": "@atlaskit/editor-synced-block-provider",
77
- "version": "8.1.10",
77
+ "version": "8.2.0",
78
78
  "description": "Synced Block Provider for @atlaskit/editor-plugin-synced-block",
79
79
  "author": "Atlassian Pty Ltd",
80
80
  "license": "Apache-2.0",