@recordtimelabel/core 0.6.8 → 0.6.9

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/README.md CHANGED
@@ -17,10 +17,10 @@ During local development an app can consume a sibling checkout with:
17
17
  "@recordtimelabel/core": "file:../recordtimelabel-core"
18
18
  ```
19
19
 
20
- For release builds, consume a fixed npm package, git tag, or private registry version so builds do not depend on a sibling folder path. The release target is `0.6.8`; verify that its registry tarball and lockfile integrity are available before updating consumers:
20
+ For release builds, consume a fixed npm package, git tag, or private registry version so builds do not depend on a sibling folder path. The release target is `0.6.9`; verify that its registry tarball and lockfile integrity are available before updating consumers:
21
21
 
22
22
  ```json
23
- "@recordtimelabel/core": "0.6.8"
23
+ "@recordtimelabel/core": "0.6.9"
24
24
  ```
25
25
 
26
26
  If this checkout's `package.json` is ahead of the published version, publish the new package before updating consumers to that version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recordtimelabel/core",
3
- "version": "0.6.8",
3
+ "version": "0.6.9",
4
4
  "type": "module",
5
5
  "description": "Shared RecordTimeLabel data model, merge logic, operations, and sync engine.",
6
6
  "main": "./src/index.js",
package/src/changefeed.js CHANGED
@@ -145,6 +145,33 @@ export const applyFirestoreV2ResolvedChangeBatch = ({
145
145
  };
146
146
  let rootChanged = false;
147
147
 
148
+ // Resolved documents represent the final target revision, not every
149
+ // intermediate receipt. If an entity is changed and then deleted within
150
+ // this contiguous batch, its final document correctly does not exist.
151
+ // Record the final mutation so that specific case can be reduced without
152
+ // weakening the fail-closed rule for genuinely missing changed documents.
153
+ const finalMutations = {
154
+ records: new Map(),
155
+ folders: new Map(),
156
+ trash: new Map()
157
+ };
158
+ batch.forEach((change, index) => {
159
+ [
160
+ ['records', change?.changedRecordIds, 'changed'],
161
+ ['records', change?.deletedRecordIds, 'deleted'],
162
+ ['folders', change?.changedFolderIds, 'changed'],
163
+ ['folders', change?.deletedFolderIds, 'deleted'],
164
+ ['trash', change?.changedTrashEntryIds, 'changed'],
165
+ ['trash', change?.deletedTrashEntryIds, 'deleted']
166
+ ].forEach(([kind, ids, mutation]) => {
167
+ toIdList(ids).forEach((id) => finalMutations[kind].set(id, {index, mutation}));
168
+ });
169
+ });
170
+ const isShadowedByLaterDelete = (kind, id, index) => {
171
+ const finalMutation = finalMutations[kind].get(id);
172
+ return finalMutation?.mutation === 'deleted' && finalMutation.index > index;
173
+ };
174
+
148
175
  for (let index = 0; index < batch.length; index += 1) {
149
176
  const change = batch[index] || {};
150
177
  const revision = Number(change.revision || 0);
@@ -193,7 +220,7 @@ export const applyFirestoreV2ResolvedChangeBatch = ({
193
220
  ...changedTrash.map((id) => ['trash', id])
194
221
  ].some(([kind, id]) => {
195
222
  const document = resolvedDocument(resolvedDocuments, kind, id);
196
- return document == null;
223
+ return document == null && !isShadowedByLaterDelete(kind, id, index);
197
224
  });
198
225
  if (missingChanged) {
199
226
  return fail(FIRESTORE_V2_BOOTSTRAP_REASONS.CHANGED_DOCUMENT_MISSING, inputCache);
@@ -211,14 +238,17 @@ export const applyFirestoreV2ResolvedChangeBatch = ({
211
238
 
212
239
  changedRecords.forEach((id) => {
213
240
  const document = resolvedDocument(resolvedDocuments, 'records', id);
241
+ if (document == null && isShadowedByLaterDelete('records', id, index)) return;
214
242
  candidate.documents.records[id] = {id, ...document};
215
243
  });
216
244
  changedFolders.forEach((id) => {
217
245
  const document = resolvedDocument(resolvedDocuments, 'folders', id);
246
+ if (document == null && isShadowedByLaterDelete('folders', id, index)) return;
218
247
  candidate.documents.folders[id] = {id, ...document};
219
248
  });
220
249
  changedTrash.forEach((id) => {
221
250
  const document = resolvedDocument(resolvedDocuments, 'trash', id);
251
+ if (document == null && isShadowedByLaterDelete('trash', id, index)) return;
222
252
  candidate.documents.trash[id] = {id, ...document};
223
253
  });
224
254
  deletedRecords.forEach((id) => delete candidate.documents.records[id]);
package/src/index.js CHANGED
@@ -145,7 +145,7 @@ export {
145
145
  selectBestMatchingTwitchVod
146
146
  };
147
147
 
148
- export const RECORD_TIMELABEL_CORE_VERSION = '0.6.8';
148
+ export const RECORD_TIMELABEL_CORE_VERSION = '0.6.9';
149
149
  export const RTL_SYNC_PROTOCOL_VERSION = 2;
150
150
  export const RECORD_TIMELABEL_DURABLE_ENGINE_CAPABILITIES = Object.freeze([
151
151
  'fifo-retry-fence',