@airtable/blocks 1.8.0 → 1.9.0-experimental

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/CHANGELOG.md +16 -2
  2. package/dist/cjs/error_utils.js +17 -0
  3. package/dist/cjs/models/base.js +16 -5
  4. package/dist/cjs/models/cursor.js +2 -0
  5. package/dist/cjs/models/field.js +146 -27
  6. package/dist/cjs/models/grouped_record_query_result.js +5 -14
  7. package/dist/cjs/models/linked_records_query_result.js +75 -27
  8. package/dist/cjs/models/mutation_constants.js +3 -1
  9. package/dist/cjs/models/mutations.js +67 -176
  10. package/dist/cjs/models/query_manager.js +327 -0
  11. package/dist/cjs/models/record.js +308 -55
  12. package/dist/cjs/models/record_query_result.js +4 -1
  13. package/dist/cjs/models/record_store.js +554 -765
  14. package/dist/cjs/models/table.js +22 -13
  15. package/dist/cjs/models/table_or_view_query_result.js +480 -414
  16. package/dist/cjs/models/view_data_store.js +243 -295
  17. package/dist/cjs/private_utils.js +50 -0
  18. package/dist/cjs/sdk.js +12 -2
  19. package/dist/cjs/testing/{mock_airtable_interface.js → abstract_mock_airtable_interface.js} +71 -22
  20. package/dist/cjs/types/block_query_spec.js +85 -0
  21. package/dist/cjs/types/mutations.js +1 -0
  22. package/dist/cjs/ui/icon_config.js +4 -2
  23. package/dist/cjs/ui/use_global_config.js +1 -1
  24. package/dist/cjs/ui/use_records.js +5 -1
  25. package/dist/cjs/unstable_testing_utils.js +2 -2
  26. package/dist/cjs/watchable.js +123 -71
  27. package/dist/types/src/models/base.d.ts +10 -3
  28. package/dist/types/src/models/base.d.ts.map +1 -1
  29. package/dist/types/src/models/cursor.d.ts +2 -0
  30. package/dist/types/src/models/cursor.d.ts.map +1 -1
  31. package/dist/types/src/models/field.d.ts +65 -1
  32. package/dist/types/src/models/field.d.ts.map +1 -1
  33. package/dist/types/src/models/grouped_record_query_result.d.ts +3 -3
  34. package/dist/types/src/models/grouped_record_query_result.d.ts.map +1 -1
  35. package/dist/types/src/models/linked_records_query_result.d.ts.map +1 -1
  36. package/dist/types/src/models/mutation_constants.d.ts +1 -0
  37. package/dist/types/src/models/mutation_constants.d.ts.map +1 -1
  38. package/dist/types/src/models/mutations.d.ts.map +1 -1
  39. package/dist/types/src/models/query_manager.d.ts +2 -0
  40. package/dist/types/src/models/query_manager.d.ts.map +1 -0
  41. package/dist/types/src/models/record.d.ts +12 -3
  42. package/dist/types/src/models/record.d.ts.map +1 -1
  43. package/dist/types/src/models/record_query_result.d.ts +3 -2
  44. package/dist/types/src/models/record_query_result.d.ts.map +1 -1
  45. package/dist/types/src/models/record_store.d.ts.map +1 -1
  46. package/dist/types/src/models/table.d.ts +8 -4
  47. package/dist/types/src/models/table.d.ts.map +1 -1
  48. package/dist/types/src/models/table_or_view_query_result.d.ts +3 -5
  49. package/dist/types/src/models/table_or_view_query_result.d.ts.map +1 -1
  50. package/dist/types/src/models/view_data_store.d.ts +0 -1
  51. package/dist/types/src/models/view_data_store.d.ts.map +1 -1
  52. package/dist/types/src/models/view_metadata_query_result.d.ts +1 -1
  53. package/dist/types/src/models/view_metadata_query_result.d.ts.map +1 -1
  54. package/dist/types/src/private_utils.d.ts +30 -1
  55. package/dist/types/src/private_utils.d.ts.map +1 -1
  56. package/dist/types/src/sdk.d.ts.map +1 -1
  57. package/dist/types/src/testing/{mock_airtable_interface.d.ts → abstract_mock_airtable_interface.d.ts} +20 -15
  58. package/dist/types/src/testing/abstract_mock_airtable_interface.d.ts.map +1 -0
  59. package/dist/types/src/types/airtable_interface.d.ts +43 -19
  60. package/dist/types/src/types/airtable_interface.d.ts.map +1 -1
  61. package/dist/types/src/types/block_query_spec.d.ts +139 -0
  62. package/dist/types/src/types/block_query_spec.d.ts.map +1 -0
  63. package/dist/types/src/types/field.d.ts +80 -44
  64. package/dist/types/src/types/field.d.ts.map +1 -1
  65. package/dist/types/src/types/mutations.d.ts +31 -3
  66. package/dist/types/src/types/mutations.d.ts.map +1 -1
  67. package/dist/types/src/types/table.d.ts +0 -2
  68. package/dist/types/src/types/table.d.ts.map +1 -1
  69. package/dist/types/src/types/view.d.ts +3 -8
  70. package/dist/types/src/types/view.d.ts.map +1 -1
  71. package/dist/types/src/ui/icon_config.d.ts +5 -3
  72. package/dist/types/src/ui/icon_config.d.ts.map +1 -1
  73. package/dist/types/src/ui/link.d.ts +1 -1
  74. package/dist/types/src/ui/link.d.ts.map +1 -1
  75. package/dist/types/src/ui/use_global_config.d.ts +1 -1
  76. package/dist/types/src/unstable_testing_utils.d.ts +1 -1
  77. package/dist/types/src/unstable_testing_utils.d.ts.map +1 -1
  78. package/dist/types/src/watchable.d.ts.map +1 -1
  79. package/dist/types/test/airtable_interface_mocks/fixture_data.d.ts +121 -0
  80. package/dist/types/test/airtable_interface_mocks/fixture_data.d.ts.map +1 -0
  81. package/dist/types/test/airtable_interface_mocks/linked_records.d.ts +2 -2
  82. package/dist/types/test/airtable_interface_mocks/linked_records.d.ts.map +1 -1
  83. package/dist/types/test/airtable_interface_mocks/{mock_airtable_interface_internal.d.ts → mock_airtable_interface.d.ts} +26 -18
  84. package/dist/types/test/airtable_interface_mocks/mock_airtable_interface.d.ts.map +1 -0
  85. package/dist/types/test/airtable_interface_mocks/mock_base_data_stores.d.ts +51 -0
  86. package/dist/types/test/airtable_interface_mocks/mock_base_data_stores.d.ts.map +1 -0
  87. package/dist/types/test/airtable_interface_mocks/project_tracker.d.ts +2 -2
  88. package/dist/types/test/airtable_interface_mocks/project_tracker.d.ts.map +1 -1
  89. package/dist/types/test/test_helpers.d.ts +2 -0
  90. package/dist/types/test/test_helpers.d.ts.map +1 -1
  91. package/package.json +3 -1
  92. package/dist/types/src/testing/mock_airtable_interface.d.ts.map +0 -1
  93. package/dist/types/test/airtable_interface_mocks/mock_airtable_interface_internal.d.ts.map +0 -1
@@ -2,13 +2,7 @@
2
2
 
3
3
  var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
4
4
 
5
- require("core-js/modules/es.symbol");
6
-
7
- require("core-js/modules/es.symbol.description");
8
-
9
- require("core-js/modules/es.array.filter");
10
-
11
- require("core-js/modules/es.array.iterator");
5
+ require("core-js/modules/es.array.find");
12
6
 
13
7
  require("core-js/modules/es.array.map");
14
8
 
@@ -16,17 +10,17 @@ require("core-js/modules/es.array.slice");
16
10
 
17
11
  require("core-js/modules/es.object.to-string");
18
12
 
13
+ require("core-js/modules/es.object.values");
14
+
19
15
  require("core-js/modules/es.promise");
20
16
 
21
- require("core-js/modules/web.dom-collections.iterator");
17
+ require("core-js/modules/web.dom-collections.for-each");
22
18
 
23
19
  Object.defineProperty(exports, "__esModule", {
24
20
  value: true
25
21
  });
26
22
  exports.default = exports.WatchableViewDataStoreKeys = void 0;
27
23
 
28
- var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
29
-
30
24
  var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
31
25
 
32
26
  require("regenerator-runtime/runtime");
@@ -35,11 +29,9 @@ var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/cl
35
29
 
36
30
  var _possibleConstructorReturn2 = _interopRequireDefault(require("@babel/runtime/helpers/possibleConstructorReturn"));
37
31
 
38
- var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
39
-
40
32
  var _getPrototypeOf2 = _interopRequireDefault(require("@babel/runtime/helpers/getPrototypeOf"));
41
33
 
42
- var _get2 = _interopRequireDefault(require("@babel/runtime/helpers/get"));
34
+ var _assertThisInitialized2 = _interopRequireDefault(require("@babel/runtime/helpers/assertThisInitialized"));
43
35
 
44
36
  var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
45
37
 
@@ -51,11 +43,12 @@ var _private_utils = require("../private_utils");
51
43
 
52
44
  var _error_utils = require("../error_utils");
53
45
 
46
+ var _block_query_spec = require("../types/block_query_spec");
47
+
54
48
  var _abstract_model_with_async_data = _interopRequireDefault(require("./abstract_model_with_async_data"));
55
49
 
56
50
  var WatchableViewDataStoreKeys = Object.freeze({
57
51
  visibleRecords: 'visibleRecords',
58
- visibleRecordIds: 'visibleRecordIds',
59
52
  groups: 'groups',
60
53
  groupLevels: 'groupLevels',
61
54
  recordColors: 'recordColors',
@@ -66,9 +59,11 @@ var WatchableViewDataStoreKeys = Object.freeze({
66
59
 
67
60
  exports.WatchableViewDataStoreKeys = WatchableViewDataStoreKeys;
68
61
 
69
- // ViewDataStore contains loadable data for a specific view. That means the set of visible records,
62
+ // ViewDataStore manages loadable data for a specific view. That means the set of visible records,
70
63
  // and field order/visibility information. View itself only contains core schema information. The
71
- // data here doesn't belong in View as it's record data or conditionally loaded.
64
+ // data here doesn't belong in View as it's record data or conditionally loaded and therefore
65
+ // loaded via queries (and therefore managed by publicAirtableInterface)
66
+ // TODO: (#proj-blocks-sdk-record-limits) Remove AbstractModelWithAsyncData
72
67
 
73
68
  /** @internal */
74
69
  var ViewDataStore =
@@ -80,11 +75,6 @@ function (_AbstractModelWithAsy) {
80
75
  value: function _isWatchableKey(key) {
81
76
  return (0, _private_utils.isEnumValue)(WatchableViewDataStoreKeys, key);
82
77
  }
83
- }, {
84
- key: "_shouldLoadDataForKey",
85
- value: function _shouldLoadDataForKey(key) {
86
- return true;
87
- }
88
78
  }]);
89
79
 
90
80
  function ViewDataStore(sdk, parentRecordStore, viewId) {
@@ -94,16 +84,38 @@ function (_AbstractModelWithAsy) {
94
84
  _this = (0, _possibleConstructorReturn2.default)(this, (0, _getPrototypeOf2.default)(ViewDataStore).call(this, sdk, "".concat(viewId, "-ViewDataStore")));
95
85
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "viewId", void 0);
96
86
  (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "parentRecordStore", void 0);
97
- (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "_mostRecentTableLoadPromise", void 0);
98
- (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "_airtableInterface", void 0);
87
+ (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "_publicViewStore", void 0);
88
+ (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "_queryManager", void 0);
89
+ (0, _defineProperty2.default)((0, _assertThisInitialized2.default)(_this), "_queryIdFromReferenceCountedLoadData", null);
99
90
  _this.parentRecordStore = parentRecordStore;
100
- _this._airtableInterface = sdk.__airtableInterface;
101
- _this._mostRecentTableLoadPromise = null;
91
+ _this._queryManager = sdk._queryManager;
102
92
  _this.viewId = viewId;
93
+
94
+ var publicViewStore = _this.parentRecordStore._airtableInterface.getViewMetadataStoreIfExists(_this.parentRecordStore.tableId, viewId);
95
+
96
+ (0, _error_utils.invariant)(publicViewStore, 'PublicViewStore must exist for all valid viewIds');
97
+ _this._publicViewStore = publicViewStore;
103
98
  return _this;
104
99
  }
105
100
 
106
101
  (0, _createClass2.default)(ViewDataStore, [{
102
+ key: "_assertExistsAndLoaded",
103
+
104
+ /**
105
+ * @internal
106
+ */
107
+ value: function _assertExistsAndLoaded() {
108
+ // Access data in order to throw if the data has been deleted / isn't loaded
109
+ var data = this._dataOrNullIfDeleted; // Currently it is not possible for the ViewDataStore to spawn an error on deletion
110
+ // because the parent TableOrViewQueryResult or ViewMetadataQueryResult throw their own
111
+ // errors when the view is deleted.
112
+ // istanbul ignore if
113
+
114
+ if (data === null) {
115
+ throw this._spawnErrorForDeletion();
116
+ }
117
+ }
118
+ }, {
107
119
  key: "_onChangeIsDataLoaded",
108
120
  value: function _onChangeIsDataLoaded() {// noop
109
121
  }
@@ -111,212 +123,58 @@ function (_AbstractModelWithAsy) {
111
123
  key: "__onDataDeletion",
112
124
  value: function __onDataDeletion() {
113
125
  this._forceUnload();
114
- }
115
- }, {
116
- key: "loadDataAsync",
117
- value: function loadDataAsync() {
118
- var tableLoadPromise;
119
- return _regenerator.default.async(function loadDataAsync$(_context) {
120
- while (1) {
121
- switch (_context.prev = _context.next) {
122
- case 0:
123
- // Override this method to also load table data.
124
- // NOTE: it's important that we call loadDataAsync on the table here and not in
125
- // _loadDataAsync since we want the retain counts for the view and table to increase/decrease
126
- // in lock-step. If we load table data in _loadDataAsync, the table's retain
127
- // count only increments some of the time, which leads to unexpected behavior.
128
- tableLoadPromise = this.parentRecordStore.loadRecordMetadataAsync();
129
- this._mostRecentTableLoadPromise = tableLoadPromise;
130
- _context.next = 4;
131
- return _regenerator.default.awrap((0, _get2.default)((0, _getPrototypeOf2.default)(ViewDataStore.prototype), "loadDataAsync", this).call(this));
132
-
133
- case 4:
134
- case "end":
135
- return _context.stop();
136
- }
137
- }
138
- }, null, this);
126
+
127
+ this._queryManager.forceUnloadWatchesOnWatchableModel(this);
139
128
  }
140
129
  }, {
141
130
  key: "_loadDataAsync",
142
131
  value: function _loadDataAsync() {
143
- var tableLoadPromise, _ref, _ref2, viewData, _iteratorNormalCompletion, _didIteratorError, _iteratorError, _iterator, _step, record;
144
-
145
- return _regenerator.default.async(function _loadDataAsync$(_context2) {
132
+ var changedKeysOnLoad;
133
+ return _regenerator.default.async(function _loadDataAsync$(_context) {
146
134
  while (1) {
147
- switch (_context2.prev = _context2.next) {
135
+ switch (_context.prev = _context.next) {
148
136
  case 0:
149
- // We need to be sure that the table data is loaded *before* we return
150
- // from this method.
151
- (0, _error_utils.invariant)(this._mostRecentTableLoadPromise, 'No table load promise');
152
- tableLoadPromise = this._mostRecentTableLoadPromise;
153
- _context2.next = 4;
154
- return _regenerator.default.awrap(Promise.all([this._airtableInterface.fetchAndSubscribeToViewDataAsync(this.parentRecordStore.tableId, this.viewId), tableLoadPromise]));
155
-
156
- case 4:
157
- _ref = _context2.sent;
158
- _ref2 = (0, _slicedToArray2.default)(_ref, 1);
159
- viewData = _ref2[0];
160
- this._data.visibleRecordIds = viewData.visibleRecordIds;
161
- this._data.fieldOrder = viewData.fieldOrder;
162
- this._data.groups = viewData.groups;
163
- this._data.groupLevels = viewData.groupLevels;
164
- this._data.colorsByRecordId = viewData.colorsByRecordId;
165
-
166
- if (!this._data.colorsByRecordId) {
167
- _context2.next = 32;
168
- break;
169
- }
170
-
171
- _iteratorNormalCompletion = true;
172
- _didIteratorError = false;
173
- _iteratorError = undefined;
174
- _context2.prev = 16;
175
-
176
- for (_iterator = this.visibleRecords[Symbol.iterator](); !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
177
- record = _step.value;
178
-
179
- if ((0, _private_utils.has)(this._data.colorsByRecordId, record.id)) {
180
- record.__triggerOnChangeForRecordColorInViewId(this.viewId);
137
+ // TODO: (#proj-blocks-sdk-record-limits) Do we want to remove this reference counting semantic?
138
+ // We can assume that each query is identical, but it might be helpful to have all queries be
139
+ // registered across the bridge so that we can track utilization.
140
+ (0, _error_utils.invariant)(this._queryIdFromReferenceCountedLoadData === null, 'Can not reissue an identical view data store query');
141
+ _context.next = 3;
142
+ return _regenerator.default.awrap(this._queryManager.fetchAndSubscribeToQueryAsync({
143
+ sourceType: _block_query_spec.BlockQuerySourceType.VIEW,
144
+ sourceTableId: this.parentRecordStore.tableId,
145
+ sourceViewId: this.viewId,
146
+ viewMetadataSelection: {
147
+ shouldIncludeVisibleRecordIds: true,
148
+ shouldIncludeFieldOrder: true,
149
+ shouldIncludeGroups: true,
150
+ shouldIncludeGroupingLevels: true,
151
+ shouldIncludeColorsForRecordId: true
181
152
  }
182
- }
153
+ }));
183
154
 
184
- _context2.next = 24;
185
- break;
155
+ case 3:
156
+ this._queryIdFromReferenceCountedLoadData = _context.sent;
157
+ changedKeysOnLoad = Object.values(WatchableViewDataStoreKeys);
186
158
 
187
- case 20:
188
- _context2.prev = 20;
189
- _context2.t0 = _context2["catch"](16);
190
- _didIteratorError = true;
191
- _iteratorError = _context2.t0;
159
+ this._queryManager.onLoadKeys(this, changedKeysOnLoad);
192
160
 
193
- case 24:
194
- _context2.prev = 24;
195
- _context2.prev = 25;
161
+ return _context.abrupt("return", []);
196
162
 
197
- if (!_iteratorNormalCompletion && _iterator.return != null) {
198
- _iterator.return();
199
- }
200
-
201
- case 27:
202
- _context2.prev = 27;
203
-
204
- if (!_didIteratorError) {
205
- _context2.next = 30;
206
- break;
207
- }
208
-
209
- throw _iteratorError;
210
-
211
- case 30:
212
- return _context2.finish(27);
213
-
214
- case 31:
215
- return _context2.finish(24);
216
-
217
- case 32:
218
- return _context2.abrupt("return", [WatchableViewDataStoreKeys.visibleRecords, WatchableViewDataStoreKeys.visibleRecordIds, WatchableViewDataStoreKeys.allFieldIds, WatchableViewDataStoreKeys.groups, WatchableViewDataStoreKeys.groupLevels, WatchableViewDataStoreKeys.visibleFieldIds, WatchableViewDataStoreKeys.recordColors]);
219
-
220
- case 33:
163
+ case 7:
221
164
  case "end":
222
- return _context2.stop();
165
+ return _context.stop();
223
166
  }
224
167
  }
225
- }, null, this, [[16, 20, 24, 32], [25,, 27, 31]]);
226
- }
227
- }, {
228
- key: "unloadData",
229
- value: function unloadData() {
230
- // Override this method to also unload the table's data.
231
- // NOTE: it's important that we do this here, since we want the view and table's
232
- // retain counts to increment/decrement in lock-step. If we unload the table's
233
- // data in _unloadData, it leads to unexpected behavior.
234
- (0, _get2.default)((0, _getPrototypeOf2.default)(ViewDataStore.prototype), "unloadData", this).call(this);
235
- this.parentRecordStore.unloadRecordMetadata();
168
+ }, null, this);
236
169
  }
237
170
  }, {
238
171
  key: "_unloadData",
239
172
  value: function _unloadData() {
240
- this._mostRecentTableLoadPromise = null;
241
-
242
- this._airtableInterface.unsubscribeFromViewData(this.parentRecordStore.tableId, this.viewId);
243
-
244
- if (!this.isDeleted) {
245
- this._data.visibleRecordIds = undefined;
246
- this._data.groups = undefined;
247
- this._data.colorsByRecordId = undefined;
248
- }
249
- }
250
- }, {
251
- key: "__generateChangesForParentTableAddMultipleRecords",
252
- value: function __generateChangesForParentTableAddMultipleRecords(recordIds) {
253
- var newVisibleRecordIds = [...this.visibleRecordIds, ...recordIds];
254
- return [{
255
- path: ['tablesById', this.parentRecordStore.tableId, 'viewsById', this.viewId, 'visibleRecordIds'],
256
- value: newVisibleRecordIds
257
- }];
258
- }
259
- }, {
260
- key: "__generateChangesForParentTableDeleteMultipleRecords",
261
- value: function __generateChangesForParentTableDeleteMultipleRecords(recordIds) {
262
- var recordIdsToDeleteSet = {};
263
- var _iteratorNormalCompletion2 = true;
264
- var _didIteratorError2 = false;
265
- var _iteratorError2 = undefined;
266
-
267
- try {
268
- for (var _iterator2 = recordIds[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
269
- var recordId = _step2.value;
270
- recordIdsToDeleteSet[recordId] = true;
271
- }
272
- } catch (err) {
273
- _didIteratorError2 = true;
274
- _iteratorError2 = err;
275
- } finally {
276
- try {
277
- if (!_iteratorNormalCompletion2 && _iterator2.return != null) {
278
- _iterator2.return();
279
- }
280
- } finally {
281
- if (_didIteratorError2) {
282
- throw _iteratorError2;
283
- }
284
- }
285
- }
286
-
287
- var newVisibleRecordIds = this.visibleRecordIds.filter(recordId => !recordIdsToDeleteSet[recordId]);
288
- var changePayload = [{
289
- path: ['tablesById', this.parentRecordStore.tableId, 'viewsById', this.viewId, 'visibleRecordIds'],
290
- value: newVisibleRecordIds
291
- }];
292
-
293
- if (this._data.groups) {
294
- var newGroups = this.__recursivelyRemoveRecordsFromGroupsInPlace((0, _private_utils.cloneDeep)(this._data.groups), recordIdsToDeleteSet);
295
-
296
- changePayload.push({
297
- path: ['tablesById', this.parentRecordStore.tableId, 'viewsById', this.viewId, 'groups'],
298
- value: newGroups
299
- });
300
- }
301
-
302
- return changePayload;
303
- }
304
- }, {
305
- key: "__recursivelyRemoveRecordsFromGroupsInPlace",
306
- value: function __recursivelyRemoveRecordsFromGroupsInPlace(groups, recordIdsToDeleteSet) {
307
- if (!groups || groups.length === 0) {
308
- return groups;
309
- }
173
+ (0, _error_utils.invariant)(this._queryIdFromReferenceCountedLoadData, 'Can not unsubscribe from a view store that is not subscribed');
310
174
 
311
- return groups.map(group => {
312
- if (group.visibleRecordIds) {
313
- group.visibleRecordIds = group.visibleRecordIds.filter(id => !recordIdsToDeleteSet[id]);
314
- }
315
-
316
- this.__recursivelyRemoveRecordsFromGroupsInPlace(group.groups, recordIdsToDeleteSet);
175
+ this._queryManager.unsubscribeFromQueryId(this._queryIdFromReferenceCountedLoadData);
317
176
 
318
- return group;
319
- });
177
+ this._queryIdFromReferenceCountedLoadData = null;
320
178
  }
321
179
  /**
322
180
  * The record IDs that are not filtered out of this view.
@@ -334,107 +192,187 @@ function (_AbstractModelWithAsy) {
334
192
  * @param recordOrRecordId the record/record id to get the color for
335
193
  */
336
194
  value: function getRecordColor(record) {
337
- var _ref3, _this$_data$colorsByR;
338
-
339
195
  (0, _error_utils.invariant)(this.isDataLoaded, 'View data is not loaded');
340
- return (_ref3 = (_this$_data$colorsByR = this._data.colorsByRecordId) === null || _this$_data$colorsByR === void 0 ? void 0 : _this$_data$colorsByR[record.id]) !== null && _ref3 !== void 0 ? _ref3 : null;
196
+
197
+ this._assertExistsAndLoaded();
198
+
199
+ return this._publicViewStore.getColorForRecordId(record.id);
341
200
  }
342
201
  }, {
343
- key: "triggerOnChangeForDirtyPaths",
344
- value: function triggerOnChangeForDirtyPaths(dirtyPaths) {
345
- if (dirtyPaths.visibleRecordIds) {
346
- this._onChange(WatchableViewDataStoreKeys.visibleRecords);
202
+ key: "_findBlockSubscriptionChangedMatchingSubscriptionChangeTypeForViewIfExists",
203
+ value: function _findBlockSubscriptionChangedMatchingSubscriptionChangeTypeForViewIfExists(changes, changeTypeToMatch) {
204
+ var _changes$find;
347
205
 
348
- this._onChange(WatchableViewDataStoreKeys.visibleRecordIds);
349
- }
206
+ return (_changes$find = changes.find(change => change.type === changeTypeToMatch && change.tableId === this.parentRecordStore.tableId && change.viewId === this.viewId)) !== null && _changes$find !== void 0 ? _changes$find : null;
207
+ }
208
+ }, {
209
+ key: "_constructBlockQuerySpecForWatchableKey",
210
+ value: function _constructBlockQuerySpecForWatchableKey(key, callbackForRegistration, context) {
211
+ var querySpecOptions = {};
212
+
213
+ var wrappedCallbackForQueryChange = () => {
214
+ callbackForRegistration.call(context, this, key);
215
+ };
216
+
217
+ switch (key) {
218
+ case WatchableViewDataStoreKeys.visibleRecords:
219
+ querySpecOptions.shouldIncludeVisibleRecordIds = true;
220
+ break;
221
+
222
+ case WatchableViewDataStoreKeys.groups:
223
+ {
224
+ querySpecOptions.shouldIncludeGroups = true;
225
+ break;
226
+ }
350
227
 
351
- if (dirtyPaths.fieldOrder) {
352
- this._onChange(WatchableViewDataStoreKeys.allFieldIds); // TODO(kasra): only trigger visibleFields if the *visible* field ids changed.
228
+ case WatchableViewDataStoreKeys.groupLevels:
229
+ {
230
+ querySpecOptions.shouldIncludeGroupingLevels = true;
231
+ break;
232
+ }
353
233
 
234
+ case WatchableViewDataStoreKeys.recordColors:
235
+ {
236
+ querySpecOptions.shouldIncludeColorsForRecordId = true;
354
237
 
355
- this._onChange(WatchableViewDataStoreKeys.visibleFieldIds);
356
- } // Technically it's possible for groupLevels changing to cause a groups
357
- // change since we derive group information from the groupLevels (fieldId)
238
+ wrappedCallbackForQueryChange = changes => {
239
+ var recordColorsChange = this._findBlockSubscriptionChangedMatchingSubscriptionChangeTypeForViewIfExists(changes, _block_query_spec.BlockQuerySubscriptionChangeType.CHANGED_COLORS_BY_RECORD_ID_FOR_VIEW);
358
240
 
241
+ var recordIdsOrNull = null;
359
242
 
360
- if (dirtyPaths.groups || dirtyPaths.groupLevels) {
361
- this._onChange(WatchableViewDataStoreKeys.groups);
362
- }
243
+ if (recordColorsChange) {
244
+ (0, _error_utils.invariant)(recordColorsChange.type === _block_query_spec.BlockQuerySubscriptionChangeType.CHANGED_COLORS_BY_RECORD_ID_FOR_VIEW, 'Invalid record colors change event');
245
+ recordIdsOrNull = recordColorsChange.recordIds;
246
+ } // We also always call this, even if no recordColors changed to closely mirror
247
+ // the old version of the SDK (https://github.com/Hyperbase/blocks-sdk/blob/8e3ac1a37be9c9850e64085975b102a483116080/packages/sdk/src/models/view_data_store.ts#L319-L361)
248
+ // In the future, this should not be the case (don't call callback if nothing changed)
363
249
 
364
- if (dirtyPaths.groupLevels) {
365
- this._onChange(WatchableViewDataStoreKeys.groupLevels);
366
- }
367
250
 
368
- if (dirtyPaths.colorsByRecordId) {
369
- var changedRecordIds = dirtyPaths.colorsByRecordId._isDirty ? null : Object.keys(dirtyPaths.colorsByRecordId);
370
-
371
- if (changedRecordIds) {
372
- // Checking isRecordMetadataLoaded fixes a timing issue:
373
- // When a new table loads in liveapp, we'll receive the record
374
- // colors before getting the response to our loadData call.
375
- // This is a temporary fix: we need a more general solution to
376
- // avoid processing events associated with subscriptions whose
377
- // data we haven't received yet.
378
- if (this.parentRecordStore.isRecordMetadataLoaded) {
379
- var _iteratorNormalCompletion3 = true;
380
- var _didIteratorError3 = false;
381
- var _iteratorError3 = undefined;
382
-
383
- try {
384
- for (var _iterator3 = changedRecordIds[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
385
- var recordId = _step3.value;
386
- var record = this.parentRecordStore.getRecordByIdIfExists(recordId); // Similar to above, we could be receiving the change notification
387
- // for a record color before receiving the new record itself.
388
-
389
- if (record) {
390
- record.__triggerOnChangeForRecordColorInViewId(this.viewId);
391
- }
392
- }
393
- } catch (err) {
394
- _didIteratorError3 = true;
395
- _iteratorError3 = err;
396
- } finally {
397
- try {
398
- if (!_iteratorNormalCompletion3 && _iterator3.return != null) {
399
- _iterator3.return();
400
- }
401
- } finally {
402
- if (_didIteratorError3) {
403
- throw _iteratorError3;
404
- }
405
- }
406
- }
251
+ callbackForRegistration.call(context, this, key, recordIdsOrNull);
252
+ };
253
+
254
+ break;
407
255
  }
408
- }
409
256
 
410
- this._onChange(WatchableViewDataStoreKeys.recordColors, changedRecordIds);
257
+ case WatchableViewDataStoreKeys.allFieldIds:
258
+ {
259
+ querySpecOptions.shouldIncludeFieldOrder = true;
260
+ break;
261
+ }
262
+
263
+ case WatchableViewDataStoreKeys.visibleFieldIds:
264
+ {
265
+ // TODO: This will trigger even if fieldIds which aren't visible in the view change.
266
+ // This is NOT a regression, a fix would be to add an intermediate callback
267
+ // which swallows changes if the visible fieldOrder does not change.
268
+ querySpecOptions.shouldIncludeFieldOrder = true;
269
+ break;
270
+ }
271
+ // TypeScript ensures that key is of type never so we don't need to test this via istanbul
272
+ // istanbul ignore next
273
+
274
+ default:
275
+ throw (0, _error_utils.spawnExhaustiveSwitchError)(key);
411
276
  }
277
+
278
+ return {
279
+ querySpecToSubscribeWith: {
280
+ sourceType: _block_query_spec.BlockQuerySourceType.VIEW,
281
+ sourceTableId: this.parentRecordStore.tableId,
282
+ sourceViewId: this.viewId,
283
+ viewMetadataSelection: querySpecOptions
284
+ },
285
+ wrappedCallbackForQueryChange
286
+ };
287
+ }
288
+ /**
289
+ * Watching a key that needs to load data asynchronously will automatically
290
+ * cause the data to be fetched. Once the data is available, the callback
291
+ * will be called.
292
+ *
293
+ * @inheritdoc
294
+ */
295
+
296
+ }, {
297
+ key: "watch",
298
+ value: function watch(keys, callbackForWatchKey, context) {
299
+ this._assertNotForceUnloaded();
300
+
301
+ var validKeys = this._getWatchableValidKeysOrThrow(keys, 'view watch');
302
+
303
+ validKeys.forEach(validKey => {
304
+ var _this$_constructBlock = this._constructBlockQuerySpecForWatchableKey(validKey, callbackForWatchKey, context),
305
+ querySpecToSubscribeWith = _this$_constructBlock.querySpecToSubscribeWith,
306
+ wrappedCallbackForQueryChange = _this$_constructBlock.wrappedCallbackForQueryChange;
307
+
308
+ this._queryManager.watchWithQuerySpec(querySpecToSubscribeWith, this, validKey, wrappedCallbackForQueryChange, callbackForWatchKey, context);
309
+ });
310
+ return validKeys;
311
+ }
312
+ /**
313
+ * Unwatching a key that needs to load data asynchronously will automatically
314
+ * cause the data to be released. Once the data is available, the callback
315
+ * will be called.
316
+ *
317
+ * @inheritdoc
318
+ */
319
+
320
+ }, {
321
+ key: "unwatch",
322
+ value: function unwatch(keys, callbackForRegistration, context) {
323
+ // We have already unsubscribed from all watches (because query-based watches force data to be loaded)
324
+ // so no watches are subscribed
325
+ if (this._isForceUnloaded) {
326
+ return [];
327
+ } // We warn instead of throw here because we used to warn instead of throw and don't
328
+ // want to make a breaking change.
329
+
330
+
331
+ var validKeys = this._getWatchableValidKeysOrThrow(keys, 'view unwatch', true);
332
+
333
+ validKeys.forEach(key => this._queryManager.unwatchFromQueryKey(key, callbackForRegistration, context));
334
+ return validKeys;
335
+ }
336
+ }, {
337
+ key: "triggerOnChangeForDirtyPaths",
338
+ value: function triggerOnChangeForDirtyPaths(dirtyPaths) {// This is only triggered for changes to values in the type ViewData, which is
339
+ // populated and kept up to date by watching base schema (aka the view's id, name, type).
340
+ // None of these are watchable, so we don't need to call this._onChange() for them.
341
+ // In the future we may want to make the view's name watchable, which is the only property
342
+ // here that we'd expect to change. In this case, it would be handled here.
412
343
  }
413
344
  }, {
414
345
  key: "_dataOrNullIfDeleted",
415
346
  get: function get() {
416
- var _ref4;
347
+ var _ref;
417
348
 
418
349
  var tableData = this._baseData.tablesById[this.parentRecordStore.tableId];
419
- return (_ref4 = tableData === null || tableData === void 0 ? void 0 : tableData.viewsById[this.viewId]) !== null && _ref4 !== void 0 ? _ref4 : null;
350
+ return (_ref = tableData === null || tableData === void 0 ? void 0 : tableData.viewsById[this.viewId]) !== null && _ref !== void 0 ? _ref : null;
420
351
  }
421
352
  }, {
422
353
  key: "isDataLoaded",
423
354
  get: function get() {
424
- return this._isDataLoaded && this.parentRecordStore.isRecordMetadataLoaded;
355
+ return this._isDataLoaded;
356
+ }
357
+ }, {
358
+ key: "isRecordDataLoadedForView",
359
+ get: function get() {
360
+ // We _currently_ only issue queries that also load cell data in the view which
361
+ // is synchronously returned with all other view data, so we can collapse this
362
+ // to if the viewData has loaded.
363
+ // TODO: (#proj-blocks-sdk-record-limits) Actually track if all cell data is loaded
364
+ // based on issued queries from ViewDataStore
365
+ return this.isDataLoaded;
425
366
  }
426
367
  }, {
427
368
  key: "visibleRecordIds",
428
369
  get: function get() {
429
- var visibleRecordIds = this._data.visibleRecordIds;
430
- (0, _error_utils.invariant)(visibleRecordIds, 'View data is not loaded'); // Freeze visibleRecordIds so users can't mutate it.
431
- // If it changes from liveapp, we get an entire new array which will
432
- // replace this one, so it's okay to freeze it.
370
+ this._assertExistsAndLoaded(); // Freezing the visibleRecordIds happens on the other side of the bridge
433
371
 
434
- if (!Object.isFrozen(visibleRecordIds)) {
435
- Object.freeze(visibleRecordIds);
436
- }
437
372
 
373
+ var visibleRecordIds = this._publicViewStore.getVisibleRecordIds();
374
+
375
+ (0, _error_utils.invariant)(visibleRecordIds, 'View data is not loaded');
438
376
  return visibleRecordIds;
439
377
  }
440
378
  /**
@@ -446,8 +384,12 @@ function (_AbstractModelWithAsy) {
446
384
  }, {
447
385
  key: "visibleRecords",
448
386
  get: function get() {
449
- (0, _error_utils.invariant)(this.parentRecordStore.isRecordMetadataLoaded, 'Table data is not loaded');
450
- var visibleRecordIds = this._data.visibleRecordIds;
387
+ (0, _error_utils.invariant)(this.isRecordDataLoadedForView, 'Table data is not loaded for this view');
388
+
389
+ this._assertExistsAndLoaded();
390
+
391
+ var visibleRecordIds = this._publicViewStore.getVisibleRecordIds();
392
+
451
393
  (0, _error_utils.invariant)(visibleRecordIds, 'View data is not loaded');
452
394
  return visibleRecordIds.map(recordId => {
453
395
  var record = this.parentRecordStore.getRecordByIdIfExists(recordId);
@@ -466,9 +408,9 @@ function (_AbstractModelWithAsy) {
466
408
  }, {
467
409
  key: "groups",
468
410
  get: function get() {
469
- (0, _error_utils.invariant)(this.parentRecordStore.isRecordMetadataLoaded, 'Table data is not loaded');
470
- var groups = this._data.groups;
471
- return groups !== null && groups !== void 0 ? groups : null;
411
+ this._assertExistsAndLoaded();
412
+
413
+ return this._publicViewStore.getGroups();
472
414
  }
473
415
  /**
474
416
  * Gets the group config for this view, can be watched to know when groupLevels
@@ -478,23 +420,29 @@ function (_AbstractModelWithAsy) {
478
420
  }, {
479
421
  key: "groupLevels",
480
422
  get: function get() {
481
- (0, _error_utils.invariant)(this.parentRecordStore.isRecordMetadataLoaded, 'Table data is not loaded');
482
- var groupLevels = this._data.groupLevels;
483
- return groupLevels !== null && groupLevels !== void 0 ? groupLevels : null;
423
+ this._assertExistsAndLoaded();
424
+
425
+ return this._publicViewStore.getGroupLevels();
484
426
  }
485
427
  }, {
486
428
  key: "allFieldIds",
487
429
  get: function get() {
488
- var fieldOrder = this._data.fieldOrder;
489
- (0, _error_utils.invariant)(fieldOrder, 'View data is not loaded');
490
- return fieldOrder.fieldIds;
430
+ this._assertExistsAndLoaded(); // TODO: (#proj-blocks-sdk-record-limits) Future sdk version shouldn't deep
431
+ // copy this, and it should be legal to return the frozen object directly
432
+
433
+
434
+ return (0, _private_utils.cloneDeep)(this._publicViewStore.getFieldOrder().fieldIds);
491
435
  }
492
436
  }, {
493
437
  key: "visibleFieldIds",
494
438
  get: function get() {
495
- var fieldOrder = this._data.fieldOrder;
439
+ this._assertExistsAndLoaded();
440
+
441
+ var fieldOrder = this._publicViewStore.getFieldOrder();
442
+
496
443
  (0, _error_utils.invariant)(fieldOrder, 'View data is not loaded');
497
- var fieldIds = fieldOrder.fieldIds;
444
+ var fieldIds = fieldOrder.fieldIds; // Slice returns a mutable copy even though visibleFieldCount is readonly
445
+
498
446
  return fieldIds.slice(0, fieldOrder.visibleFieldCount);
499
447
  }
500
448
  }]);