@recordtimelabel/core 0.2.0 → 0.2.1

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 (3) hide show
  1. package/README.md +2 -2
  2. package/package.json +1 -1
  3. package/src/index.js +40 -11
package/README.md CHANGED
@@ -20,7 +20,7 @@ During local development an app can consume a sibling checkout with:
20
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 v2 gateway contract release is:
21
21
 
22
22
  ```json
23
- "@recordtimelabel/core": "0.2.0"
23
+ "@recordtimelabel/core": "0.2.1"
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.
@@ -50,7 +50,7 @@ If this checkout's `package.json` is ahead of the published version, publish the
50
50
  - `extendFirestoreV2OperationReadPlanWithRecords(readPlan, records)`
51
51
  - `planFirestoreV2OperationChanges({ documents, operations, localState, now })`
52
52
  - `estimateFirestoreV2WriteUnits(changes, overhead)`
53
- - `buildOperationsFromSnapshotDiff({ previousState, nextState, now, operationIdPrefix, batchSize })`
53
+ - `buildOperationsFromSnapshotDiff({ previousState, nextState, now, operationIdPrefix, batchSize })`(一般操作在 `batches`,`folder.delete` 在 `bulkOperations`)
54
54
  - `RTL_SYNC_PROTOCOL_VERSION`
55
55
  - `hasMeaningfulRecordTimeLabelCloudState(data, options)`
56
56
  - `buildRecordTimeLabelContentFingerprint(data)`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recordtimelabel/core",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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/index.js CHANGED
@@ -5,7 +5,7 @@ const REQUIRED_FOLDERS = [
5
5
  { id: DEFAULT_FOLDER_ID, name: 'Uncategorized' }
6
6
  ];
7
7
 
8
- export const RECORD_TIMELABEL_CORE_VERSION = '0.2.0';
8
+ export const RECORD_TIMELABEL_CORE_VERSION = '0.2.1';
9
9
  export const RTL_SYNC_PROTOCOL_VERSION = 1;
10
10
  export const RTL_MAX_OPERATIONS_PER_REQUEST = 20;
11
11
  export const RTL_MAX_REQUEST_BYTES = 256 * 1024;
@@ -46,9 +46,20 @@ export const RECORD_TIMELABEL_CLOUD_SCHEMAS = Object.freeze({
46
46
  export const FIRESTORE_V2_SETTINGS_DOC_ID = 'main';
47
47
 
48
48
  const toArray = (value) => (Array.isArray(value) ? value : []);
49
- const clone = (value) => {
50
- if (value === undefined || value === null) return value;
51
- return JSON.parse(JSON.stringify(value));
49
+ const clone = (value, seen = new WeakMap()) => {
50
+ if (value === undefined || value === null || typeof value !== 'object') return value;
51
+ if (value instanceof Date) return new Date(value.getTime());
52
+ const prototype = Object.getPrototypeOf(value);
53
+ if (prototype !== Object.prototype && prototype !== null && !Array.isArray(value)) {
54
+ return value;
55
+ }
56
+ if (seen.has(value)) return seen.get(value);
57
+ const copy = Array.isArray(value) ? [] : {};
58
+ seen.set(value, copy);
59
+ Reflect.ownKeys(value).forEach((key) => {
60
+ copy[key] = clone(value[key], seen);
61
+ });
62
+ return copy;
52
63
  };
53
64
 
54
65
  const toFiniteTimestamp = (value) => {
@@ -2526,21 +2537,23 @@ export const planFirestoreV2OperationChanges = ({
2526
2537
  });
2527
2538
  const recordDocument = applied.documents.records[recordId];
2528
2539
  if (!recordDocument) { reason = 'blocked_by_tombstone'; break; }
2529
- rtlSetRoot(nextDocuments.root, applied.documents.root);
2530
- nextDocuments.records[recordId] = recordDocument;
2540
+ const candidateRoot = clone(applied.documents.root);
2541
+ const candidateFolders = clone(nextDocuments.folders);
2531
2542
  const targetFolder = rtlEnsureTargetFolder({
2532
- root: nextDocuments.root,
2533
- folders: nextDocuments.folders,
2543
+ root: candidateRoot,
2544
+ folders: candidateFolders,
2534
2545
  localState: normalizedLocalState,
2535
2546
  folderId: targetFolderId,
2536
2547
  now: operationNow
2537
2548
  });
2538
2549
  if (!targetFolder) {
2539
2550
  reason = 'folder_not_found';
2540
- delete nextDocuments.records[recordId];
2541
2551
  break;
2542
2552
  }
2543
2553
  targetFolder.recordOrder = rtlMergeOrder([recordId], targetFolder.recordOrder);
2554
+ rtlSetRoot(nextDocuments.root, candidateRoot);
2555
+ nextDocuments.folders = candidateFolders;
2556
+ nextDocuments.records[recordId] = recordDocument;
2544
2557
  changed = true;
2545
2558
  break;
2546
2559
  }
@@ -2751,6 +2764,7 @@ export const buildOperationsFromSnapshotDiff = ({
2751
2764
  const previousFolders = new Map(previous.folders.map((folder) => [folder.id, folder]));
2752
2765
  const nextFolders = new Map(next.folders.map((folder) => [folder.id, folder]));
2753
2766
  const drafts = [];
2767
+ const bulkDrafts = [];
2754
2768
 
2755
2769
  nextFolders.forEach((folder, folderId) => {
2756
2770
  if (RTL_PROTECTED_FOLDER_IDS.has(folderId)) return;
@@ -2787,7 +2801,10 @@ export const buildOperationsFromSnapshotDiff = ({
2787
2801
 
2788
2802
  previousFolders.forEach((folder, folderId) => {
2789
2803
  if (!RTL_PROTECTED_FOLDER_IDS.has(folderId) && !nextFolders.has(folderId)) {
2790
- drafts.push({type: OPERATION_TYPES.FOLDER_DELETE, payload: {folderId}});
2804
+ bulkDrafts.push({
2805
+ type: OPERATION_TYPES.FOLDER_DELETE,
2806
+ payload: {folderId, deletedAt: now}
2807
+ });
2791
2808
  }
2792
2809
  });
2793
2810
 
@@ -2812,6 +2829,11 @@ export const buildOperationsFromSnapshotDiff = ({
2812
2829
  createdAt: now + index,
2813
2830
  ...draft
2814
2831
  }));
2832
+ const bulkOperations = bulkDrafts.map((draft, index) => ({
2833
+ id: `${operationIdPrefix}:bulk:${String(index).padStart(6, '0')}`,
2834
+ createdAt: now + operations.length + index,
2835
+ ...draft
2836
+ }));
2815
2837
  const boundedBatchSize = Math.max(1, Math.min(
2816
2838
  RTL_MAX_OPERATIONS_PER_REQUEST,
2817
2839
  Number.isSafeInteger(batchSize) ? batchSize : RTL_MAX_OPERATIONS_PER_REQUEST
@@ -2826,7 +2848,14 @@ export const buildOperationsFromSnapshotDiff = ({
2826
2848
  const changes = buildFirestoreV2DocumentChangeSet(previousDocuments, nextDocuments, {
2827
2849
  allowDeletes: true
2828
2850
  });
2829
- return {operations, batches, changes, previousDocuments, nextDocuments};
2851
+ return {
2852
+ operations,
2853
+ batches,
2854
+ bulkOperations,
2855
+ changes,
2856
+ previousDocuments,
2857
+ nextDocuments
2858
+ };
2830
2859
  };
2831
2860
 
2832
2861
  export default {