@firtoz/collection-sync 5.0.0 → 6.0.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/README.md +46 -0
- package/dist/cache-manager.d.ts +52 -0
- package/dist/cache-manager.js +5 -0
- package/dist/cache-manager.js.map +1 -0
- package/dist/chunk-3EHHMLSV.js +57 -0
- package/dist/chunk-3EHHMLSV.js.map +1 -0
- package/dist/chunk-43KYAIKY.js +46 -0
- package/dist/chunk-43KYAIKY.js.map +1 -0
- package/dist/chunk-4BEXLBCH.js +64 -0
- package/dist/chunk-4BEXLBCH.js.map +1 -0
- package/dist/chunk-5V6BSQAB.js +148 -0
- package/dist/chunk-5V6BSQAB.js.map +1 -0
- package/dist/chunk-5VMFQT5Z.js +112 -0
- package/dist/chunk-5VMFQT5Z.js.map +1 -0
- package/dist/chunk-6EHROJFY.js +111 -0
- package/dist/chunk-6EHROJFY.js.map +1 -0
- package/dist/chunk-6X3434GJ.js +21 -0
- package/dist/chunk-6X3434GJ.js.map +1 -0
- package/dist/chunk-BGJH6PH2.js +175 -0
- package/dist/chunk-BGJH6PH2.js.map +1 -0
- package/dist/chunk-BJJEAKXL.js +252 -0
- package/dist/chunk-BJJEAKXL.js.map +1 -0
- package/dist/chunk-GWIOC5CP.js +51 -0
- package/dist/chunk-GWIOC5CP.js.map +1 -0
- package/dist/chunk-HMLY7DHA.js +12 -0
- package/dist/chunk-HMLY7DHA.js.map +1 -0
- package/dist/chunk-I6RJWBGF.js +112 -0
- package/dist/chunk-I6RJWBGF.js.map +1 -0
- package/dist/chunk-M5MJHS6A.js +10 -0
- package/dist/chunk-M5MJHS6A.js.map +1 -0
- package/dist/chunk-O3KBDCEI.js +615 -0
- package/dist/chunk-O3KBDCEI.js.map +1 -0
- package/dist/chunk-OP53UBPN.js +19 -0
- package/dist/chunk-OP53UBPN.js.map +1 -0
- package/dist/chunk-P3JOTUAB.js +802 -0
- package/dist/chunk-P3JOTUAB.js.map +1 -0
- package/dist/chunk-QJP4GSJH.js +373 -0
- package/dist/chunk-QJP4GSJH.js.map +1 -0
- package/dist/chunk-RDDS7JQW.js +623 -0
- package/dist/chunk-RDDS7JQW.js.map +1 -0
- package/dist/chunk-TEH7V76G.js +209 -0
- package/dist/chunk-TEH7V76G.js.map +1 -0
- package/dist/chunk-UJ24XW52.js +20 -0
- package/dist/chunk-UJ24XW52.js.map +1 -0
- package/dist/chunk-UVZJL6QV.js +18 -0
- package/dist/chunk-UVZJL6QV.js.map +1 -0
- package/dist/chunk-XC4QNFSQ.js +238 -0
- package/dist/chunk-XC4QNFSQ.js.map +1 -0
- package/dist/chunk-YD5LVGWX.js +125 -0
- package/dist/chunk-YD5LVGWX.js.map +1 -0
- package/dist/chunk-YYGPIHHJ.js +166 -0
- package/dist/chunk-YYGPIHHJ.js.map +1 -0
- package/dist/connect-partial-sync.d.ts +41 -0
- package/dist/connect-partial-sync.js +6 -0
- package/dist/connect-partial-sync.js.map +1 -0
- package/dist/connect-sync.d.ts +26 -0
- package/dist/connect-sync.js +5 -0
- package/dist/connect-sync.js.map +1 -0
- package/dist/create-partial-synced-collection.d.ts +24 -0
- package/dist/create-partial-synced-collection.js +8 -0
- package/dist/create-partial-synced-collection.js.map +1 -0
- package/dist/create-synced-collection.d.ts +26 -0
- package/dist/create-synced-collection.js +8 -0
- package/dist/create-synced-collection.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.js +18 -0
- package/dist/index.js.map +1 -0
- package/dist/partial-sync-client-bridge.d.ts +157 -0
- package/dist/partial-sync-client-bridge.js +6 -0
- package/dist/partial-sync-client-bridge.js.map +1 -0
- package/dist/partial-sync-interest.d.ts +48 -0
- package/dist/partial-sync-interest.js +6 -0
- package/dist/partial-sync-interest.js.map +1 -0
- package/dist/partial-sync-mutation-handler.d.ts +31 -0
- package/dist/partial-sync-mutation-handler.js +6 -0
- package/dist/partial-sync-mutation-handler.js.map +1 -0
- package/dist/partial-sync-predicate-match.d.ts +8 -0
- package/dist/partial-sync-predicate-match.js +4 -0
- package/dist/partial-sync-predicate-match.js.map +1 -0
- package/dist/partial-sync-row-key.d.ts +41 -0
- package/dist/partial-sync-row-key.js +4 -0
- package/dist/partial-sync-row-key.js.map +1 -0
- package/dist/partial-sync-server-bridge.d.ts +102 -0
- package/dist/partial-sync-server-bridge.js +8 -0
- package/dist/partial-sync-server-bridge.js.map +1 -0
- package/dist/react/constants.d.ts +12 -0
- package/dist/react/constants.js +4 -0
- package/dist/react/constants.js.map +1 -0
- package/dist/react/index.d.ts +19 -0
- package/dist/react/index.js +17 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/partial-sync-adapter.d.ts +40 -0
- package/dist/react/partial-sync-adapter.js +4 -0
- package/dist/react/partial-sync-adapter.js.map +1 -0
- package/dist/react/partial-sync-utils.d.ts +42 -0
- package/dist/react/partial-sync-utils.js +5 -0
- package/dist/react/partial-sync-utils.js.map +1 -0
- package/dist/react/range-conditions-expression.d.ts +49 -0
- package/dist/react/range-conditions-expression.js +4 -0
- package/dist/react/range-conditions-expression.js.map +1 -0
- package/dist/react/types.d.ts +196 -0
- package/dist/react/types.js +3 -0
- package/dist/react/types.js.map +1 -0
- package/dist/react/usePartialSyncCollection.d.ts +20 -0
- package/dist/react/usePartialSyncCollection.js +10 -0
- package/dist/react/usePartialSyncCollection.js.map +1 -0
- package/dist/react/usePartialSyncViewport.d.ts +20 -0
- package/dist/react/usePartialSyncViewport.js +7 -0
- package/dist/react/usePartialSyncViewport.js.map +1 -0
- package/dist/react/usePartialSyncWindow.d.ts +17 -0
- package/dist/react/usePartialSyncWindow.js +12 -0
- package/dist/react/usePartialSyncWindow.js.map +1 -0
- package/dist/react/usePredicateFilteredRows.d.ts +20 -0
- package/dist/react/usePredicateFilteredRows.js +6 -0
- package/dist/react/usePredicateFilteredRows.js.map +1 -0
- package/dist/sync-client-bridge.d.ts +48 -0
- package/dist/sync-client-bridge.js +6 -0
- package/dist/sync-client-bridge.js.map +1 -0
- package/dist/sync-protocol.d.ts +378 -0
- package/dist/sync-protocol.js +4 -0
- package/dist/sync-protocol.js.map +1 -0
- package/dist/sync-server-bridge.d.ts +35 -0
- package/dist/sync-server-bridge.js +6 -0
- package/dist/sync-server-bridge.js.map +1 -0
- package/dist/with-sync.d.ts +107 -0
- package/dist/with-sync.js +7 -0
- package/dist/with-sync.js.map +1 -0
- package/package.json +23 -17
package/README.md
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# @firtoz/collection-sync
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@firtoz/collection-sync)
|
|
4
|
+
[](https://www.npmjs.com/package/@firtoz/collection-sync)
|
|
5
|
+
[](https://github.com/firtoz/fullstack-toolkit/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://tanstack.com/db)
|
|
9
|
+
[](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket)
|
|
10
|
+
|
|
11
|
+
**Real-time sync for [TanStack DB](https://tanstack.com/db) collections over WebSockets**—shared Zod message schemas, client/server bridges, optional partial sync and viewport-aware caching for large datasets.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @firtoz/collection-sync @tanstack/db @standard-schema/spec @firtoz/websocket-do react @tanstack/react-db
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Also: `pnpm add` · `bun add` · `yarn add`
|
|
20
|
+
|
|
21
|
+
Peers include `@firtoz/websocket-do`, `@tanstack/db`, `@tanstack/react-db`, `react`, and `@standard-schema/spec`. Wire your Durable Object or server to the protocol types exported from the package root.
|
|
22
|
+
|
|
23
|
+
## React entry
|
|
24
|
+
|
|
25
|
+
Import hooks and adapters from the dedicated subpath:
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
import { /* … */ } from "@firtoz/collection-sync/react";
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Core exports
|
|
32
|
+
|
|
33
|
+
The package root exposes protocol types (`SyncClientMessage`, `SyncServerMessage`, …), bridges (`SyncClientBridge`, `SyncServerBridge`, `PartialSyncClientBridge`, …), `withSync` for wrapping collections, and related utilities. See `src/index.ts` for the full public API.
|
|
34
|
+
|
|
35
|
+
## Related
|
|
36
|
+
|
|
37
|
+
- [`@firtoz/idb-collections`](https://www.npmjs.com/package/@firtoz/idb-collections) — IndexedDB-backed collections
|
|
38
|
+
- [`@firtoz/db-helpers`](https://www.npmjs.com/package/@firtoz/db-helpers) — shared TanStack DB helpers
|
|
39
|
+
|
|
40
|
+
## Links
|
|
41
|
+
|
|
42
|
+
- [GitHub](https://github.com/firtoz/fullstack-toolkit/tree/main/packages/collection-sync) · [Issues](https://github.com/firtoz/fullstack-toolkit/issues)
|
|
43
|
+
|
|
44
|
+
## License
|
|
45
|
+
|
|
46
|
+
MIT © [Firtina Ozbalikchi](https://github.com/firtoz)
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { PartialSyncRowRef } from './partial-sync-row-key.js';
|
|
2
|
+
|
|
3
|
+
interface CacheStorageEstimate {
|
|
4
|
+
usageBytes: number;
|
|
5
|
+
quotaBytes: number;
|
|
6
|
+
utilizationRatio: number;
|
|
7
|
+
}
|
|
8
|
+
type CacheEntry = {
|
|
9
|
+
key: string | number;
|
|
10
|
+
lastAccessedAt: number;
|
|
11
|
+
fetchedAt: number;
|
|
12
|
+
sortPositions: Map<string, unknown>;
|
|
13
|
+
estimatedSizeBytes: number;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
interface CacheManagerOptions<TItem extends PartialSyncRowRef> {
|
|
17
|
+
evictionThreshold?: number;
|
|
18
|
+
evictionTarget?: number;
|
|
19
|
+
estimateRowSize?: (row: TItem) => number;
|
|
20
|
+
getStorageEstimate: () => Promise<CacheStorageEstimate>;
|
|
21
|
+
deleteRows: (keys: Array<string | number>) => Promise<void>;
|
|
22
|
+
}
|
|
23
|
+
type CacheViewport = {
|
|
24
|
+
sortColumn: string;
|
|
25
|
+
sortDirection: "asc" | "desc";
|
|
26
|
+
fromValue: unknown;
|
|
27
|
+
toValue: unknown;
|
|
28
|
+
};
|
|
29
|
+
declare class CacheManager<TItem extends PartialSyncRowRef> {
|
|
30
|
+
#private;
|
|
31
|
+
private readonly options;
|
|
32
|
+
readonly evictionThreshold: number;
|
|
33
|
+
readonly evictionTarget: number;
|
|
34
|
+
constructor(options: CacheManagerOptions<TItem>);
|
|
35
|
+
get entryCount(): number;
|
|
36
|
+
/**
|
|
37
|
+
* Recompute `sortPositions` for rows already tracked (e.g. after a local edit changes sort keys).
|
|
38
|
+
* Does not add new entries; skips keys missing from `getRow`.
|
|
39
|
+
*/
|
|
40
|
+
resyncSortPositionsForTrackedRows(getRow: (key: string | number) => TItem | undefined, getSortPositions: (row: TItem) => Record<string, unknown>): void;
|
|
41
|
+
recordFetchedRows(rows: TItem[], getSortPositions: (row: TItem) => Record<string, unknown>): void;
|
|
42
|
+
markAccessed(keys: Array<string | number>): void;
|
|
43
|
+
removeEntries(keys: Array<string | number>): void;
|
|
44
|
+
clear(): void;
|
|
45
|
+
estimateStoragePressure(): Promise<CacheStorageEstimate>;
|
|
46
|
+
evictIfNeeded(viewport: CacheViewport): Promise<{
|
|
47
|
+
evictedKeys: Array<string | number>;
|
|
48
|
+
estimate: CacheStorageEstimate;
|
|
49
|
+
}>;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export { type CacheEntry, CacheManager, type CacheManagerOptions, type CacheStorageEstimate, type CacheViewport };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"cache-manager.js"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { exhaustiveGuard } from '@firtoz/maybe-error';
|
|
2
|
+
|
|
3
|
+
// src/partial-sync-predicate-match.ts
|
|
4
|
+
function compareUnknown(a, b) {
|
|
5
|
+
const na = a instanceof Date ? a.getTime() : a;
|
|
6
|
+
const nb = b instanceof Date ? b.getTime() : b;
|
|
7
|
+
if (typeof na === "number" && typeof nb === "number") {
|
|
8
|
+
if (na === nb) return 0;
|
|
9
|
+
return na < nb ? -1 : 1;
|
|
10
|
+
}
|
|
11
|
+
return String(na).localeCompare(String(nb));
|
|
12
|
+
}
|
|
13
|
+
function defaultPredicateColumnValue(row, column) {
|
|
14
|
+
if (row !== null && typeof row === "object" && column in row) {
|
|
15
|
+
return row[column];
|
|
16
|
+
}
|
|
17
|
+
return void 0;
|
|
18
|
+
}
|
|
19
|
+
function matchesPredicate(row, conditions, getColumnValue) {
|
|
20
|
+
for (const c of conditions) {
|
|
21
|
+
const v = getColumnValue(row, c.column);
|
|
22
|
+
switch (c.op) {
|
|
23
|
+
case "eq":
|
|
24
|
+
if (compareUnknown(v, c.value) !== 0) return false;
|
|
25
|
+
break;
|
|
26
|
+
case "neq":
|
|
27
|
+
if (compareUnknown(v, c.value) === 0) return false;
|
|
28
|
+
break;
|
|
29
|
+
case "gt":
|
|
30
|
+
if (compareUnknown(v, c.value) <= 0) return false;
|
|
31
|
+
break;
|
|
32
|
+
case "gte":
|
|
33
|
+
if (compareUnknown(v, c.value) < 0) return false;
|
|
34
|
+
break;
|
|
35
|
+
case "lt":
|
|
36
|
+
if (compareUnknown(v, c.value) >= 0) return false;
|
|
37
|
+
break;
|
|
38
|
+
case "lte":
|
|
39
|
+
if (compareUnknown(v, c.value) > 0) return false;
|
|
40
|
+
break;
|
|
41
|
+
case "between": {
|
|
42
|
+
if (c.valueTo === void 0) return false;
|
|
43
|
+
if (compareUnknown(v, c.value) < 0 || compareUnknown(v, c.valueTo) > 0) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
default:
|
|
49
|
+
exhaustiveGuard(c.op);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export { defaultPredicateColumnValue, matchesPredicate };
|
|
56
|
+
//# sourceMappingURL=chunk-3EHHMLSV.js.map
|
|
57
|
+
//# sourceMappingURL=chunk-3EHHMLSV.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/partial-sync-predicate-match.ts"],"names":[],"mappings":";;;AAGA,SAAS,cAAA,CAAe,GAAY,CAAA,EAAoB;AACvD,EAAA,MAAM,EAAA,GAAK,CAAA,YAAa,IAAA,GAAO,CAAA,CAAE,SAAQ,GAAI,CAAA;AAC7C,EAAA,MAAM,EAAA,GAAK,CAAA,YAAa,IAAA,GAAO,CAAA,CAAE,SAAQ,GAAI,CAAA;AAC7C,EAAA,IAAI,OAAO,EAAA,KAAO,QAAA,IAAY,OAAO,OAAO,QAAA,EAAU;AACrD,IAAA,IAAI,EAAA,KAAO,IAAI,OAAO,CAAA;AACtB,IAAA,OAAO,EAAA,GAAK,KAAK,EAAA,GAAK,CAAA;AAAA,EACvB;AACA,EAAA,OAAO,OAAO,EAAE,CAAA,CAAE,aAAA,CAAc,MAAA,CAAO,EAAE,CAAC,CAAA;AAC3C;AAEO,SAAS,2BAAA,CACf,KACA,MAAA,EACU;AACV,EAAA,IAAI,QAAQ,IAAA,IAAQ,OAAO,GAAA,KAAQ,QAAA,IAAY,UAAU,GAAA,EAAK;AAC7D,IAAA,OAAQ,IAAgC,MAAM,CAAA;AAAA,EAC/C;AACA,EAAA,OAAO,MAAA;AACR;AAEO,SAAS,gBAAA,CACf,GAAA,EACA,UAAA,EACA,cAAA,EACU;AACV,EAAA,KAAA,MAAW,KAAK,UAAA,EAAY;AAC3B,IAAA,MAAM,CAAA,GAAI,cAAA,CAAe,GAAA,EAAK,CAAA,CAAE,MAAM,CAAA;AACtC,IAAA,QAAQ,EAAE,EAAA;AAAI,MACb,KAAK,IAAA;AACJ,QAAA,IAAI,eAAe,CAAA,EAAG,CAAA,CAAE,KAAK,CAAA,KAAM,GAAG,OAAO,KAAA;AAC7C,QAAA;AAAA,MACD,KAAK,KAAA;AACJ,QAAA,IAAI,eAAe,CAAA,EAAG,CAAA,CAAE,KAAK,CAAA,KAAM,GAAG,OAAO,KAAA;AAC7C,QAAA;AAAA,MACD,KAAK,IAAA;AACJ,QAAA,IAAI,eAAe,CAAA,EAAG,CAAA,CAAE,KAAK,CAAA,IAAK,GAAG,OAAO,KAAA;AAC5C,QAAA;AAAA,MACD,KAAK,KAAA;AACJ,QAAA,IAAI,eAAe,CAAA,EAAG,CAAA,CAAE,KAAK,CAAA,GAAI,GAAG,OAAO,KAAA;AAC3C,QAAA;AAAA,MACD,KAAK,IAAA;AACJ,QAAA,IAAI,eAAe,CAAA,EAAG,CAAA,CAAE,KAAK,CAAA,IAAK,GAAG,OAAO,KAAA;AAC5C,QAAA;AAAA,MACD,KAAK,KAAA;AACJ,QAAA,IAAI,eAAe,CAAA,EAAG,CAAA,CAAE,KAAK,CAAA,GAAI,GAAG,OAAO,KAAA;AAC3C,QAAA;AAAA,MACD,KAAK,SAAA,EAAW;AACf,QAAA,IAAI,CAAA,CAAE,OAAA,KAAY,MAAA,EAAW,OAAO,KAAA;AACpC,QAAA,IACC,cAAA,CAAe,CAAA,EAAG,CAAA,CAAE,KAAK,CAAA,GAAI,CAAA,IAC7B,cAAA,CAAe,CAAA,EAAG,CAAA,CAAE,OAAO,CAAA,GAAI,CAAA,EAC9B;AACD,UAAA,OAAO,KAAA;AAAA,QACR;AACA,QAAA;AAAA,MACD;AAAA,MACA;AACC,QAAA,eAAA,CAAgB,EAAE,EAAE,CAAA;AAAA;AACtB,EACD;AACA,EAAA,OAAO,IAAA;AACR","file":"chunk-3EHHMLSV.js","sourcesContent":["import { exhaustiveGuard } from \"@firtoz/maybe-error\";\nimport type { RangeCondition } from \"./sync-protocol\";\n\nfunction compareUnknown(a: unknown, b: unknown): number {\n\tconst na = a instanceof Date ? a.getTime() : a;\n\tconst nb = b instanceof Date ? b.getTime() : b;\n\tif (typeof na === \"number\" && typeof nb === \"number\") {\n\t\tif (na === nb) return 0;\n\t\treturn na < nb ? -1 : 1;\n\t}\n\treturn String(na).localeCompare(String(nb));\n}\n\nexport function defaultPredicateColumnValue<TItem>(\n\trow: TItem,\n\tcolumn: string,\n): unknown {\n\tif (row !== null && typeof row === \"object\" && column in row) {\n\t\treturn (row as Record<string, unknown>)[column];\n\t}\n\treturn undefined;\n}\n\nexport function matchesPredicate<TItem>(\n\trow: TItem,\n\tconditions: RangeCondition[],\n\tgetColumnValue: (row: TItem, column: string) => unknown,\n): boolean {\n\tfor (const c of conditions) {\n\t\tconst v = getColumnValue(row, c.column);\n\t\tswitch (c.op) {\n\t\t\tcase \"eq\":\n\t\t\t\tif (compareUnknown(v, c.value) !== 0) return false;\n\t\t\t\tbreak;\n\t\t\tcase \"neq\":\n\t\t\t\tif (compareUnknown(v, c.value) === 0) return false;\n\t\t\t\tbreak;\n\t\t\tcase \"gt\":\n\t\t\t\tif (compareUnknown(v, c.value) <= 0) return false;\n\t\t\t\tbreak;\n\t\t\tcase \"gte\":\n\t\t\t\tif (compareUnknown(v, c.value) < 0) return false;\n\t\t\t\tbreak;\n\t\t\tcase \"lt\":\n\t\t\t\tif (compareUnknown(v, c.value) >= 0) return false;\n\t\t\t\tbreak;\n\t\t\tcase \"lte\":\n\t\t\t\tif (compareUnknown(v, c.value) > 0) return false;\n\t\t\t\tbreak;\n\t\t\tcase \"between\": {\n\t\t\t\tif (c.valueTo === undefined) return false;\n\t\t\t\tif (\n\t\t\t\t\tcompareUnknown(v, c.value) < 0 ||\n\t\t\t\t\tcompareUnknown(v, c.valueTo) > 0\n\t\t\t\t) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tdefault:\n\t\t\t\texhaustiveGuard(c.op);\n\t\t}\n\t}\n\treturn true;\n}\n"]}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { and, gte, lte, lt, gt, not, eq } from '@tanstack/db';
|
|
2
|
+
import { exhaustiveGuard } from '@firtoz/maybe-error';
|
|
3
|
+
|
|
4
|
+
// src/react/range-conditions-expression.ts
|
|
5
|
+
function buildRangeConditionsAndExpression(row, conditions) {
|
|
6
|
+
if (conditions.length === 0) {
|
|
7
|
+
throw new Error(
|
|
8
|
+
"buildRangeConditionsAndExpression: pass a non-empty conditions list"
|
|
9
|
+
);
|
|
10
|
+
}
|
|
11
|
+
const parts = conditions.map((c) => rangeConditionExpression(row, c));
|
|
12
|
+
if (parts.length === 1) {
|
|
13
|
+
return parts[0];
|
|
14
|
+
}
|
|
15
|
+
const [a, b, ...rest] = parts;
|
|
16
|
+
return rest.length === 0 ? and(a, b) : and(a, b, ...rest);
|
|
17
|
+
}
|
|
18
|
+
function rangeConditionExpression(row, c) {
|
|
19
|
+
const col = row[c.column];
|
|
20
|
+
switch (c.op) {
|
|
21
|
+
case "eq":
|
|
22
|
+
return eq(col, c.value);
|
|
23
|
+
case "neq":
|
|
24
|
+
return not(eq(col, c.value));
|
|
25
|
+
case "gt":
|
|
26
|
+
return gt(col, c.value);
|
|
27
|
+
case "gte":
|
|
28
|
+
return gte(col, c.value);
|
|
29
|
+
case "lt":
|
|
30
|
+
return lt(col, c.value);
|
|
31
|
+
case "lte":
|
|
32
|
+
return lte(col, c.value);
|
|
33
|
+
case "between": {
|
|
34
|
+
if (c.valueTo === void 0) {
|
|
35
|
+
throw new Error(`between requires valueTo for column ${c.column}`);
|
|
36
|
+
}
|
|
37
|
+
return and(gte(col, c.value), lte(col, c.valueTo));
|
|
38
|
+
}
|
|
39
|
+
default:
|
|
40
|
+
exhaustiveGuard(c.op);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export { buildRangeConditionsAndExpression };
|
|
45
|
+
//# sourceMappingURL=chunk-43KYAIKY.js.map
|
|
46
|
+
//# sourceMappingURL=chunk-43KYAIKY.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/range-conditions-expression.ts"],"names":[],"mappings":";;;;AAyBO,SAAS,iCAAA,CACf,KACA,UAAA,EACC;AACD,EAAA,IAAI,UAAA,CAAW,WAAW,CAAA,EAAG;AAC5B,IAAA,MAAM,IAAI,KAAA;AAAA,MACT;AAAA,KACD;AAAA,EACD;AACA,EAAA,MAAM,KAAA,GAAQ,WAAW,GAAA,CAAI,CAAC,MAAM,wBAAA,CAAyB,GAAA,EAAK,CAAC,CAAC,CAAA;AACpE,EAAA,IAAI,KAAA,CAAM,WAAW,CAAA,EAAG;AACvB,IAAA,OAAO,MAAM,CAAC,CAAA;AAAA,EACf;AACA,EAAA,MAAM,CAAC,CAAA,EAAG,CAAA,EAAG,GAAG,IAAI,CAAA,GAAI,KAAA;AACxB,EAAA,OAAO,IAAA,CAAK,MAAA,KAAW,CAAA,GAAI,GAAA,CAAI,CAAA,EAAG,CAAC,CAAA,GAAI,GAAA,CAAI,CAAA,EAAG,CAAA,EAAG,GAAG,IAAI,CAAA;AACzD;AAEA,SAAS,wBAAA,CACR,KACA,CAAA,EACC;AACD,EAAA,MAAM,GAAA,GAAM,GAAA,CAAI,CAAA,CAAE,MAAM,CAAA;AACxB,EAAA,QAAQ,EAAE,EAAA;AAAI,IACb,KAAK,IAAA;AACJ,MAAA,OAAO,EAAA,CAAG,GAAA,EAAK,CAAA,CAAE,KAAK,CAAA;AAAA,IACvB,KAAK,KAAA;AACJ,MAAA,OAAO,GAAA,CAAI,EAAA,CAAG,GAAA,EAAK,CAAA,CAAE,KAAK,CAAC,CAAA;AAAA,IAC5B,KAAK,IAAA;AACJ,MAAA,OAAO,EAAA,CAAG,GAAA,EAAK,CAAA,CAAE,KAAK,CAAA;AAAA,IACvB,KAAK,KAAA;AACJ,MAAA,OAAO,GAAA,CAAI,GAAA,EAAK,CAAA,CAAE,KAAK,CAAA;AAAA,IACxB,KAAK,IAAA;AACJ,MAAA,OAAO,EAAA,CAAG,GAAA,EAAK,CAAA,CAAE,KAAK,CAAA;AAAA,IACvB,KAAK,KAAA;AACJ,MAAA,OAAO,GAAA,CAAI,GAAA,EAAK,CAAA,CAAE,KAAK,CAAA;AAAA,IACxB,KAAK,SAAA,EAAW;AACf,MAAA,IAAI,CAAA,CAAE,YAAY,MAAA,EAAW;AAC5B,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,oCAAA,EAAuC,CAAA,CAAE,MAAM,CAAA,CAAE,CAAA;AAAA,MAClE;AACA,MAAA,OAAO,GAAA,CAAI,GAAA,CAAI,GAAA,EAAK,CAAA,CAAE,KAAK,GAAG,GAAA,CAAI,GAAA,EAAK,CAAA,CAAE,OAAO,CAAC,CAAA;AAAA,IAClD;AAAA,IACA;AACC,MAAA,eAAA,CAAgB,EAAE,EAAE,CAAA;AAAA;AAEvB","file":"chunk-43KYAIKY.js","sourcesContent":["import { and, eq, gt, gte, lt, lte, not, type Ref } from \"@tanstack/db\";\nimport { exhaustiveGuard } from \"@firtoz/maybe-error\";\nimport type { PartialSyncRowShape } from \"../partial-sync-row-key\";\nimport type { RangeCondition } from \"../sync-protocol\";\n\n/**\n * Row ref from `q.from({ items: collection }).where(({ items }) => …)`.\n * Column access must match {@link RangeCondition.column} names on the stored row shape.\n *\n * Uses TanStack {@link Ref} so column reads are `ExpressionLike` (e.g. for `inArray`), not `unknown`.\n */\nexport type PredicateRowRef = Ref<\n\tPartialSyncRowShape & Record<string, unknown>\n>;\n\n/**\n * Row accepted by {@link buildRangeConditionsAndExpression}: live query refs or plain objects (e.g. tests).\n * Dynamic `column` access yields `unknown`, which TanStack comparison helpers still accept.\n */\nexport type PredicateRangeBuildRow = PredicateRowRef | Record<string, unknown>;\n\n/**\n * Builds a TanStack DB `where` expression AND-ing all conditions (same semantics as\n * {@link matchesPredicate} for plain property rows).\n */\nexport function buildRangeConditionsAndExpression(\n\trow: PredicateRangeBuildRow,\n\tconditions: RangeCondition[],\n) {\n\tif (conditions.length === 0) {\n\t\tthrow new Error(\n\t\t\t\"buildRangeConditionsAndExpression: pass a non-empty conditions list\",\n\t\t);\n\t}\n\tconst parts = conditions.map((c) => rangeConditionExpression(row, c));\n\tif (parts.length === 1) {\n\t\treturn parts[0];\n\t}\n\tconst [a, b, ...rest] = parts;\n\treturn rest.length === 0 ? and(a, b) : and(a, b, ...rest);\n}\n\nfunction rangeConditionExpression(\n\trow: PredicateRangeBuildRow,\n\tc: RangeCondition,\n) {\n\tconst col = row[c.column];\n\tswitch (c.op) {\n\t\tcase \"eq\":\n\t\t\treturn eq(col, c.value);\n\t\tcase \"neq\":\n\t\t\treturn not(eq(col, c.value));\n\t\tcase \"gt\":\n\t\t\treturn gt(col, c.value);\n\t\tcase \"gte\":\n\t\t\treturn gte(col, c.value);\n\t\tcase \"lt\":\n\t\t\treturn lt(col, c.value);\n\t\tcase \"lte\":\n\t\t\treturn lte(col, c.value);\n\t\tcase \"between\": {\n\t\t\tif (c.valueTo === undefined) {\n\t\t\t\tthrow new Error(`between requires valueTo for column ${c.column}`);\n\t\t\t}\n\t\t\treturn and(gte(col, c.value), lte(col, c.valueTo));\n\t\t}\n\t\tdefault:\n\t\t\texhaustiveGuard(c.op);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// src/react/partial-sync-utils.ts
|
|
2
|
+
function primePartialSyncBridgeCachedIdsFromCollection(bridge, collection) {
|
|
3
|
+
bridge.seedHydratedLocalRows(Array.from(collection.entries(), ([, v]) => v));
|
|
4
|
+
}
|
|
5
|
+
function assertSyncUtils(utils) {
|
|
6
|
+
const receiveSync = utils.receiveSync;
|
|
7
|
+
const truncate = utils.truncate;
|
|
8
|
+
if (typeof receiveSync === "function" && typeof truncate === "function") {
|
|
9
|
+
return {
|
|
10
|
+
receiveSync,
|
|
11
|
+
truncate
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
throw new Error(
|
|
15
|
+
"Partial sync requires collection.utils.receiveSync and collection.utils.truncate"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
function defaultPartialSyncVersionMs(row) {
|
|
19
|
+
const v = row.updatedAt;
|
|
20
|
+
if (v === null) return 0;
|
|
21
|
+
if (v instanceof Date) return v.getTime();
|
|
22
|
+
if (typeof v === "number") return v;
|
|
23
|
+
return 0;
|
|
24
|
+
}
|
|
25
|
+
function getPartialSyncRowByMapId(collection, id) {
|
|
26
|
+
const direct = collection.get(id);
|
|
27
|
+
if (direct !== void 0) return direct;
|
|
28
|
+
const sid = String(id);
|
|
29
|
+
for (const [k, row] of collection.entries()) {
|
|
30
|
+
if (String(k) === sid) return row;
|
|
31
|
+
}
|
|
32
|
+
return void 0;
|
|
33
|
+
}
|
|
34
|
+
function tryIdsForIndexWindow(map, offset, want, totalCount) {
|
|
35
|
+
if (totalCount === 0) return null;
|
|
36
|
+
const n = Math.min(want, Math.max(0, totalCount - offset));
|
|
37
|
+
if (n === 0) return [];
|
|
38
|
+
const out = [];
|
|
39
|
+
for (let i = 0; i < n; i += 1) {
|
|
40
|
+
const id = map.get(offset + i);
|
|
41
|
+
if (id === void 0) return null;
|
|
42
|
+
out.push(id);
|
|
43
|
+
}
|
|
44
|
+
return out;
|
|
45
|
+
}
|
|
46
|
+
function computeFingerprintForIndexWindow(collection, map, offset, want, getVersionMs = defaultPartialSyncVersionMs) {
|
|
47
|
+
if (want <= 0) return void 0;
|
|
48
|
+
let maxV = 0;
|
|
49
|
+
let count = 0;
|
|
50
|
+
for (let i = 0; i < want; i += 1) {
|
|
51
|
+
const id = map.get(offset + i);
|
|
52
|
+
if (id === void 0) return void 0;
|
|
53
|
+
const row = getPartialSyncRowByMapId(collection, id);
|
|
54
|
+
if (row === void 0) return void 0;
|
|
55
|
+
count += 1;
|
|
56
|
+
const ms = getVersionMs(row);
|
|
57
|
+
if (ms > maxV) maxV = ms;
|
|
58
|
+
}
|
|
59
|
+
return { version: maxV, count };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { assertSyncUtils, computeFingerprintForIndexWindow, defaultPartialSyncVersionMs, getPartialSyncRowByMapId, primePartialSyncBridgeCachedIdsFromCollection, tryIdsForIndexWindow };
|
|
63
|
+
//# sourceMappingURL=chunk-4BEXLBCH.js.map
|
|
64
|
+
//# sourceMappingURL=chunk-4BEXLBCH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react/partial-sync-utils.ts"],"names":[],"mappings":"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","file":"chunk-4BEXLBCH.js","sourcesContent":["import type { CollectionUtils } from \"@firtoz/db-helpers\";\nimport type { UtilsRecord } from \"@tanstack/db\";\nimport type { RangeFingerprint } from \"../sync-protocol\";\nimport type { PartialSyncCollection, PartialSyncItem } from \"./types\";\n\n/** Merge every row currently in the collection into the bridge cache (id set only). Idempotent. */\nexport function primePartialSyncBridgeCachedIdsFromCollection<\n\tTItem extends PartialSyncItem,\n>(\n\tbridge: { seedHydratedLocalRows: (rows: readonly TItem[]) => void },\n\tcollection: Pick<PartialSyncCollection<TItem>, \"entries\">,\n): void {\n\tbridge.seedHydratedLocalRows(Array.from(collection.entries(), ([, v]) => v));\n}\n\n/**\n * TanStack `Collection` types `utils` as {@link UtilsRecord}. This narrows to sync helpers when\n * present (e.g. after `memoryCollectionOptions` / Drizzle sync config).\n */\nexport function assertSyncUtils<TItem>(\n\tutils: UtilsRecord,\n): CollectionUtils<TItem> {\n\tconst receiveSync = utils.receiveSync;\n\tconst truncate = utils.truncate;\n\tif (typeof receiveSync === \"function\" && typeof truncate === \"function\") {\n\t\treturn {\n\t\t\treceiveSync: receiveSync as CollectionUtils<TItem>[\"receiveSync\"],\n\t\t\ttruncate: truncate as CollectionUtils<TItem>[\"truncate\"],\n\t\t};\n\t}\n\tthrow new Error(\n\t\t\"Partial sync requires collection.utils.receiveSync and collection.utils.truncate\",\n\t);\n}\n\n/** Default fingerprint version: max `updatedAt` as epoch ms. */\nexport function defaultPartialSyncVersionMs<TItem extends PartialSyncItem>(\n\trow: TItem,\n): number {\n\tconst v = row.updatedAt;\n\tif (v === null) return 0;\n\tif (v instanceof Date) return v.getTime();\n\tif (typeof v === \"number\") return v;\n\treturn 0;\n}\n\n/**\n * Resolve a row for an id stored in the partial-sync index map. Uses {@link PartialSyncCollection.get}\n * first; if that misses (e.g. key type / boxed string mismatch vs TanStack’s internal map), scans\n * {@link PartialSyncCollection.entries} by `String(key)`.\n */\nexport function getPartialSyncRowByMapId<TItem extends PartialSyncItem>(\n\tcollection: PartialSyncCollection<TItem>,\n\tid: string | number,\n): TItem | undefined {\n\tconst direct = collection.get(id);\n\tif (direct !== undefined) return direct;\n\tconst sid = String(id);\n\tfor (const [k, row] of collection.entries()) {\n\t\tif (String(k) === sid) return row;\n\t}\n\treturn undefined;\n}\n\n/**\n * Returns consecutive row ids for `[offset, offset + want)` if all present in the map; else null.\n */\nexport function tryIdsForIndexWindow<TKey extends string | number>(\n\tmap: Map<number, TKey>,\n\toffset: number,\n\twant: number,\n\ttotalCount: number,\n): TKey[] | null {\n\tif (totalCount === 0) return null;\n\tconst n = Math.min(want, Math.max(0, totalCount - offset));\n\tif (n === 0) return [];\n\tconst out: TKey[] = [];\n\tfor (let i = 0; i < n; i += 1) {\n\t\tconst id = map.get(offset + i);\n\t\tif (id === undefined) return null;\n\t\tout.push(id);\n\t}\n\treturn out;\n}\n\n/**\n * Fingerprint for reconciliation when every index in `[offset, offset + want)` is mapped and rows\n * exist in the collection.\n */\nexport function computeFingerprintForIndexWindow<TItem extends PartialSyncItem>(\n\tcollection: PartialSyncCollection<TItem>,\n\tmap: Map<number, string | number>,\n\toffset: number,\n\twant: number,\n\tgetVersionMs: (row: TItem) => number = defaultPartialSyncVersionMs,\n): RangeFingerprint | undefined {\n\tif (want <= 0) return undefined;\n\tlet maxV = 0;\n\tlet count = 0;\n\tfor (let i = 0; i < want; i += 1) {\n\t\tconst id = map.get(offset + i);\n\t\tif (id === undefined) return undefined;\n\t\tconst row = getPartialSyncRowByMapId(collection, id);\n\t\tif (row === undefined) return undefined;\n\t\tcount += 1;\n\t\tconst ms = getVersionMs(row);\n\t\tif (ms > maxV) maxV = ms;\n\t}\n\treturn { version: maxV, count };\n}\n\nexport {\n\tdefaultPredicateColumnValue,\n\tmatchesPredicate,\n} from \"../partial-sync-predicate-match\";\n"]}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { partialSyncRowKey } from './chunk-UJ24XW52.js';
|
|
2
|
+
import { __privateAdd, __privateGet, __privateMethod } from './chunk-HMLY7DHA.js';
|
|
3
|
+
|
|
4
|
+
// src/cache-manager.ts
|
|
5
|
+
var _entries, _CacheManager_instances, scoreEntry_fn, distanceFromViewport_fn, isEntryProtectedByViewport_fn, compareValues_fn;
|
|
6
|
+
var CacheManager = class {
|
|
7
|
+
constructor(options) {
|
|
8
|
+
this.options = options;
|
|
9
|
+
__privateAdd(this, _CacheManager_instances);
|
|
10
|
+
__privateAdd(this, _entries, /* @__PURE__ */ new Map());
|
|
11
|
+
this.evictionThreshold = options.evictionThreshold ?? 0.85;
|
|
12
|
+
this.evictionTarget = options.evictionTarget ?? 0.7;
|
|
13
|
+
}
|
|
14
|
+
get entryCount() {
|
|
15
|
+
return __privateGet(this, _entries).size;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Recompute `sortPositions` for rows already tracked (e.g. after a local edit changes sort keys).
|
|
19
|
+
* Does not add new entries; skips keys missing from `getRow`.
|
|
20
|
+
*/
|
|
21
|
+
resyncSortPositionsForTrackedRows(getRow, getSortPositions) {
|
|
22
|
+
for (const key of __privateGet(this, _entries).keys()) {
|
|
23
|
+
const row = getRow(key);
|
|
24
|
+
if (row === void 0) continue;
|
|
25
|
+
const entry = __privateGet(this, _entries).get(key);
|
|
26
|
+
if (entry === void 0) continue;
|
|
27
|
+
const sortPositionsObject = getSortPositions(row);
|
|
28
|
+
entry.sortPositions = new Map(
|
|
29
|
+
Object.entries(sortPositionsObject)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
recordFetchedRows(rows, getSortPositions) {
|
|
34
|
+
const now = Date.now();
|
|
35
|
+
for (const row of rows) {
|
|
36
|
+
const rowKey = partialSyncRowKey(row.id);
|
|
37
|
+
const existing = __privateGet(this, _entries).get(rowKey);
|
|
38
|
+
const sortPositionsObject = getSortPositions(row);
|
|
39
|
+
const sortPositions = new Map(
|
|
40
|
+
Object.entries(sortPositionsObject)
|
|
41
|
+
);
|
|
42
|
+
const estimatedSizeBytes = this.options.estimateRowSize?.(row) ?? 256;
|
|
43
|
+
__privateGet(this, _entries).set(rowKey, {
|
|
44
|
+
key: rowKey,
|
|
45
|
+
lastAccessedAt: existing?.lastAccessedAt ?? now,
|
|
46
|
+
fetchedAt: now,
|
|
47
|
+
sortPositions,
|
|
48
|
+
estimatedSizeBytes
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
markAccessed(keys) {
|
|
53
|
+
const now = Date.now();
|
|
54
|
+
for (const key of keys) {
|
|
55
|
+
const entry = __privateGet(this, _entries).get(key);
|
|
56
|
+
if (!entry) continue;
|
|
57
|
+
entry.lastAccessedAt = now;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
removeEntries(keys) {
|
|
61
|
+
for (const key of keys) __privateGet(this, _entries).delete(key);
|
|
62
|
+
}
|
|
63
|
+
clear() {
|
|
64
|
+
__privateGet(this, _entries).clear();
|
|
65
|
+
}
|
|
66
|
+
async estimateStoragePressure() {
|
|
67
|
+
const estimate = await this.options.getStorageEstimate();
|
|
68
|
+
return {
|
|
69
|
+
usageBytes: Math.max(0, estimate.usageBytes),
|
|
70
|
+
quotaBytes: Math.max(1, estimate.quotaBytes),
|
|
71
|
+
utilizationRatio: estimate.usageBytes / Math.max(1, estimate.quotaBytes)
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
async evictIfNeeded(viewport) {
|
|
75
|
+
const estimate = await this.estimateStoragePressure();
|
|
76
|
+
if (estimate.utilizationRatio < this.evictionThreshold) {
|
|
77
|
+
return { evictedKeys: [], estimate };
|
|
78
|
+
}
|
|
79
|
+
const candidates = Array.from(__privateGet(this, _entries).values()).filter((entry) => !__privateMethod(this, _CacheManager_instances, isEntryProtectedByViewport_fn).call(this, entry, viewport)).sort(
|
|
80
|
+
(a, b) => __privateMethod(this, _CacheManager_instances, scoreEntry_fn).call(this, b, viewport) - __privateMethod(this, _CacheManager_instances, scoreEntry_fn).call(this, a, viewport)
|
|
81
|
+
);
|
|
82
|
+
const evictedKeys = [];
|
|
83
|
+
let currentEstimate = estimate;
|
|
84
|
+
for (const candidate of candidates) {
|
|
85
|
+
if (currentEstimate.utilizationRatio <= this.evictionTarget) break;
|
|
86
|
+
evictedKeys.push(candidate.key);
|
|
87
|
+
__privateGet(this, _entries).delete(candidate.key);
|
|
88
|
+
currentEstimate = {
|
|
89
|
+
...currentEstimate,
|
|
90
|
+
usageBytes: Math.max(
|
|
91
|
+
0,
|
|
92
|
+
currentEstimate.usageBytes - candidate.estimatedSizeBytes
|
|
93
|
+
),
|
|
94
|
+
utilizationRatio: Math.max(
|
|
95
|
+
0,
|
|
96
|
+
currentEstimate.usageBytes - candidate.estimatedSizeBytes
|
|
97
|
+
) / currentEstimate.quotaBytes
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
if (evictedKeys.length > 0) {
|
|
101
|
+
await this.options.deleteRows(evictedKeys);
|
|
102
|
+
}
|
|
103
|
+
return { evictedKeys, estimate: currentEstimate };
|
|
104
|
+
}
|
|
105
|
+
};
|
|
106
|
+
_entries = new WeakMap();
|
|
107
|
+
_CacheManager_instances = new WeakSet();
|
|
108
|
+
scoreEntry_fn = function(entry, viewport) {
|
|
109
|
+
const now = Date.now();
|
|
110
|
+
const timeSinceAccessMs = Math.max(0, now - entry.lastAccessedAt);
|
|
111
|
+
const distance = __privateMethod(this, _CacheManager_instances, distanceFromViewport_fn).call(this, entry, viewport);
|
|
112
|
+
return timeSinceAccessMs * 1e-3 + distance * 1e3;
|
|
113
|
+
};
|
|
114
|
+
distanceFromViewport_fn = function(entry, viewport) {
|
|
115
|
+
const value = entry.sortPositions.get(viewport.sortColumn);
|
|
116
|
+
if (value === void 0 || value === null) return 1;
|
|
117
|
+
let low = viewport.fromValue;
|
|
118
|
+
let high = viewport.toValue;
|
|
119
|
+
if (__privateMethod(this, _CacheManager_instances, compareValues_fn).call(this, low, high) > 0) {
|
|
120
|
+
const t = low;
|
|
121
|
+
low = high;
|
|
122
|
+
high = t;
|
|
123
|
+
}
|
|
124
|
+
const lowerCompare = __privateMethod(this, _CacheManager_instances, compareValues_fn).call(this, value, low);
|
|
125
|
+
const upperCompare = __privateMethod(this, _CacheManager_instances, compareValues_fn).call(this, value, high);
|
|
126
|
+
if (lowerCompare >= 0 && upperCompare <= 0) return 0;
|
|
127
|
+
const lowerDistance = Math.abs(lowerCompare);
|
|
128
|
+
const upperDistance = Math.abs(upperCompare);
|
|
129
|
+
return Math.min(lowerDistance, upperDistance);
|
|
130
|
+
};
|
|
131
|
+
isEntryProtectedByViewport_fn = function(entry, viewport) {
|
|
132
|
+
return __privateMethod(this, _CacheManager_instances, distanceFromViewport_fn).call(this, entry, viewport) === 0;
|
|
133
|
+
};
|
|
134
|
+
compareValues_fn = function(left, right) {
|
|
135
|
+
const normalizedLeft = left instanceof Date ? left.getTime() : left;
|
|
136
|
+
const normalizedRight = right instanceof Date ? right.getTime() : right;
|
|
137
|
+
if (typeof normalizedLeft === "number" && typeof normalizedRight === "number") {
|
|
138
|
+
if (normalizedLeft === normalizedRight) return 0;
|
|
139
|
+
return normalizedLeft < normalizedRight ? -1 : 1;
|
|
140
|
+
}
|
|
141
|
+
const leftStr = String(normalizedLeft);
|
|
142
|
+
const rightStr = String(normalizedRight);
|
|
143
|
+
return leftStr.localeCompare(rightStr);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
export { CacheManager };
|
|
147
|
+
//# sourceMappingURL=chunk-5V6BSQAB.js.map
|
|
148
|
+
//# sourceMappingURL=chunk-5V6BSQAB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cache-manager.ts"],"names":[],"mappings":"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","file":"chunk-5V6BSQAB.js","sourcesContent":["export interface CacheStorageEstimate {\n\tusageBytes: number;\n\tquotaBytes: number;\n\tutilizationRatio: number;\n}\n\nexport type CacheEntry = {\n\tkey: string | number;\n\tlastAccessedAt: number;\n\tfetchedAt: number;\n\tsortPositions: Map<string, unknown>;\n\testimatedSizeBytes: number;\n};\n\nimport type { PartialSyncRowRef } from \"./partial-sync-row-key\";\nimport { partialSyncRowKey } from \"./partial-sync-row-key\";\n\nexport interface CacheManagerOptions<TItem extends PartialSyncRowRef> {\n\tevictionThreshold?: number;\n\tevictionTarget?: number;\n\testimateRowSize?: (row: TItem) => number;\n\tgetStorageEstimate: () => Promise<CacheStorageEstimate>;\n\tdeleteRows: (keys: Array<string | number>) => Promise<void>;\n}\n\nexport type CacheViewport = {\n\tsortColumn: string;\n\tsortDirection: \"asc\" | \"desc\";\n\tfromValue: unknown;\n\ttoValue: unknown;\n};\n\nexport class CacheManager<TItem extends PartialSyncRowRef> {\n\t#entries = new Map<string | number, CacheEntry>();\n\treadonly evictionThreshold: number;\n\treadonly evictionTarget: number;\n\n\tconstructor(private readonly options: CacheManagerOptions<TItem>) {\n\t\tthis.evictionThreshold = options.evictionThreshold ?? 0.85;\n\t\tthis.evictionTarget = options.evictionTarget ?? 0.7;\n\t}\n\n\tget entryCount(): number {\n\t\treturn this.#entries.size;\n\t}\n\n\t/**\n\t * Recompute `sortPositions` for rows already tracked (e.g. after a local edit changes sort keys).\n\t * Does not add new entries; skips keys missing from `getRow`.\n\t */\n\tresyncSortPositionsForTrackedRows(\n\t\tgetRow: (key: string | number) => TItem | undefined,\n\t\tgetSortPositions: (row: TItem) => Record<string, unknown>,\n\t): void {\n\t\tfor (const key of this.#entries.keys()) {\n\t\t\tconst row = getRow(key);\n\t\t\tif (row === undefined) continue;\n\t\t\tconst entry = this.#entries.get(key);\n\t\t\tif (entry === undefined) continue;\n\t\t\tconst sortPositionsObject = getSortPositions(row);\n\t\t\tentry.sortPositions = new Map<string, unknown>(\n\t\t\t\tObject.entries(sortPositionsObject),\n\t\t\t);\n\t\t}\n\t}\n\n\trecordFetchedRows(\n\t\trows: TItem[],\n\t\tgetSortPositions: (row: TItem) => Record<string, unknown>,\n\t): void {\n\t\tconst now = Date.now();\n\t\tfor (const row of rows) {\n\t\t\tconst rowKey = partialSyncRowKey(row.id);\n\t\t\tconst existing = this.#entries.get(rowKey);\n\t\t\tconst sortPositionsObject = getSortPositions(row);\n\t\t\tconst sortPositions = new Map<string, unknown>(\n\t\t\t\tObject.entries(sortPositionsObject),\n\t\t\t);\n\t\t\tconst estimatedSizeBytes = this.options.estimateRowSize?.(row) ?? 256;\n\t\t\tthis.#entries.set(rowKey, {\n\t\t\t\tkey: rowKey,\n\t\t\t\tlastAccessedAt: existing?.lastAccessedAt ?? now,\n\t\t\t\tfetchedAt: now,\n\t\t\t\tsortPositions,\n\t\t\t\testimatedSizeBytes,\n\t\t\t});\n\t\t}\n\t}\n\n\tmarkAccessed(keys: Array<string | number>): void {\n\t\tconst now = Date.now();\n\t\tfor (const key of keys) {\n\t\t\tconst entry = this.#entries.get(key);\n\t\t\tif (!entry) continue;\n\t\t\tentry.lastAccessedAt = now;\n\t\t}\n\t}\n\n\tremoveEntries(keys: Array<string | number>): void {\n\t\tfor (const key of keys) this.#entries.delete(key);\n\t}\n\n\tclear(): void {\n\t\tthis.#entries.clear();\n\t}\n\n\tasync estimateStoragePressure(): Promise<CacheStorageEstimate> {\n\t\tconst estimate = await this.options.getStorageEstimate();\n\t\treturn {\n\t\t\tusageBytes: Math.max(0, estimate.usageBytes),\n\t\t\tquotaBytes: Math.max(1, estimate.quotaBytes),\n\t\t\tutilizationRatio: estimate.usageBytes / Math.max(1, estimate.quotaBytes),\n\t\t};\n\t}\n\n\tasync evictIfNeeded(viewport: CacheViewport): Promise<{\n\t\tevictedKeys: Array<string | number>;\n\t\testimate: CacheStorageEstimate;\n\t}> {\n\t\tconst estimate = await this.estimateStoragePressure();\n\t\tif (estimate.utilizationRatio < this.evictionThreshold) {\n\t\t\treturn { evictedKeys: [], estimate };\n\t\t}\n\n\t\tconst candidates = Array.from(this.#entries.values())\n\t\t\t.filter((entry) => !this.#isEntryProtectedByViewport(entry, viewport))\n\t\t\t.sort(\n\t\t\t\t(a, b) => this.#scoreEntry(b, viewport) - this.#scoreEntry(a, viewport),\n\t\t\t);\n\n\t\tconst evictedKeys: Array<string | number> = [];\n\t\tlet currentEstimate = estimate;\n\t\tfor (const candidate of candidates) {\n\t\t\tif (currentEstimate.utilizationRatio <= this.evictionTarget) break;\n\t\t\tevictedKeys.push(candidate.key);\n\t\t\tthis.#entries.delete(candidate.key);\n\t\t\tcurrentEstimate = {\n\t\t\t\t...currentEstimate,\n\t\t\t\tusageBytes: Math.max(\n\t\t\t\t\t0,\n\t\t\t\t\tcurrentEstimate.usageBytes - candidate.estimatedSizeBytes,\n\t\t\t\t),\n\t\t\t\tutilizationRatio:\n\t\t\t\t\tMath.max(\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tcurrentEstimate.usageBytes - candidate.estimatedSizeBytes,\n\t\t\t\t\t) / currentEstimate.quotaBytes,\n\t\t\t};\n\t\t}\n\n\t\tif (evictedKeys.length > 0) {\n\t\t\tawait this.options.deleteRows(evictedKeys);\n\t\t}\n\t\treturn { evictedKeys, estimate: currentEstimate };\n\t}\n\n\t#scoreEntry(entry: CacheEntry, viewport: CacheViewport): number {\n\t\tconst now = Date.now();\n\t\tconst timeSinceAccessMs = Math.max(0, now - entry.lastAccessedAt);\n\t\tconst distance = this.#distanceFromViewport(entry, viewport);\n\t\t// Weighted score: heavily prefer evicting far-away rows that have not been used recently.\n\t\treturn timeSinceAccessMs * 0.001 + distance * 1000;\n\t}\n\n\t#distanceFromViewport(entry: CacheEntry, viewport: CacheViewport): number {\n\t\tconst value = entry.sortPositions.get(viewport.sortColumn);\n\t\tif (value === undefined || value === null) return 1;\n\t\t// `fromValue` / `toValue` come from first/last *visible* rows. After a local sort-key edit,\n\t\t// the first row can temporarily sort *after* the last row, so from > to. Treat the band as\n\t\t// [min, max] so rows between them stay protected; otherwise every entry looks \"outside\" and\n\t\t// eviction can wipe the visible window (empty list, no throw).\n\t\tlet low = viewport.fromValue;\n\t\tlet high = viewport.toValue;\n\t\tif (this.#compareValues(low, high) > 0) {\n\t\t\tconst t = low;\n\t\t\tlow = high;\n\t\t\thigh = t;\n\t\t}\n\t\tconst lowerCompare = this.#compareValues(value, low);\n\t\tconst upperCompare = this.#compareValues(value, high);\n\t\tif (lowerCompare >= 0 && upperCompare <= 0) return 0;\n\t\tconst lowerDistance = Math.abs(lowerCompare);\n\t\tconst upperDistance = Math.abs(upperCompare);\n\t\treturn Math.min(lowerDistance, upperDistance);\n\t}\n\n\t#isEntryProtectedByViewport(\n\t\tentry: CacheEntry,\n\t\tviewport: CacheViewport,\n\t): boolean {\n\t\treturn this.#distanceFromViewport(entry, viewport) === 0;\n\t}\n\n\t#compareValues(left: unknown, right: unknown): number {\n\t\tconst normalizedLeft = left instanceof Date ? left.getTime() : left;\n\t\tconst normalizedRight = right instanceof Date ? right.getTime() : right;\n\t\tif (\n\t\t\ttypeof normalizedLeft === \"number\" &&\n\t\t\ttypeof normalizedRight === \"number\"\n\t\t) {\n\t\t\tif (normalizedLeft === normalizedRight) return 0;\n\t\t\treturn normalizedLeft < normalizedRight ? -1 : 1;\n\t\t}\n\t\tconst leftStr = String(normalizedLeft);\n\t\tconst rightStr = String(normalizedRight);\n\t\treturn leftStr.localeCompare(rightStr);\n\t}\n}\n"]}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { DEFAULT_SYNC_COLLECTION_ID, toSyncMessage } from './chunk-BJJEAKXL.js';
|
|
2
|
+
import { partialSyncRowKey } from './chunk-UJ24XW52.js';
|
|
3
|
+
import { __privateAdd, __privateSet, __privateGet, __privateMethod } from './chunk-HMLY7DHA.js';
|
|
4
|
+
import { exhaustiveGuard } from '@firtoz/maybe-error';
|
|
5
|
+
|
|
6
|
+
function toMillis(value) {
|
|
7
|
+
if (typeof value === "number") return value;
|
|
8
|
+
if (value instanceof Date) return value.getTime();
|
|
9
|
+
return 0;
|
|
10
|
+
}
|
|
11
|
+
var _cid, _PartialSyncMutationHandler_instances, emit_fn, intentToMessageLww_fn, handleMutateBatch_fn;
|
|
12
|
+
var PartialSyncMutationHandler = class {
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
__privateAdd(this, _PartialSyncMutationHandler_instances);
|
|
16
|
+
__privateAdd(this, _cid);
|
|
17
|
+
__privateSet(this, _cid, options.collectionId ?? DEFAULT_SYNC_COLLECTION_ID);
|
|
18
|
+
}
|
|
19
|
+
get collectionId() {
|
|
20
|
+
return __privateGet(this, _cid);
|
|
21
|
+
}
|
|
22
|
+
async handleClientMessage(message) {
|
|
23
|
+
const mid = message.collectionId ?? DEFAULT_SYNC_COLLECTION_ID;
|
|
24
|
+
if (mid !== __privateGet(this, _cid)) return;
|
|
25
|
+
switch (message.type) {
|
|
26
|
+
case "ping":
|
|
27
|
+
__privateMethod(this, _PartialSyncMutationHandler_instances, emit_fn).call(this, message.clientId, {
|
|
28
|
+
type: "pong",
|
|
29
|
+
timestamp: message.timestamp
|
|
30
|
+
});
|
|
31
|
+
return;
|
|
32
|
+
case "mutateBatch":
|
|
33
|
+
await __privateMethod(this, _PartialSyncMutationHandler_instances, handleMutateBatch_fn).call(this, message);
|
|
34
|
+
return;
|
|
35
|
+
case "syncHello":
|
|
36
|
+
case "queryRange":
|
|
37
|
+
case "queryByOffset":
|
|
38
|
+
case "rangeQuery":
|
|
39
|
+
case "rangeReconcile":
|
|
40
|
+
return;
|
|
41
|
+
default:
|
|
42
|
+
exhaustiveGuard(message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
_cid = new WeakMap();
|
|
47
|
+
_PartialSyncMutationHandler_instances = new WeakSet();
|
|
48
|
+
emit_fn = function(clientId, body) {
|
|
49
|
+
this.options.sendToClient(clientId, {
|
|
50
|
+
...body,
|
|
51
|
+
collectionId: __privateGet(this, _cid)
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
intentToMessageLww_fn = async function(intent) {
|
|
55
|
+
const base = toSyncMessage(intent);
|
|
56
|
+
if (base.type !== "update") {
|
|
57
|
+
return base;
|
|
58
|
+
}
|
|
59
|
+
const key = partialSyncRowKey(intent.key ?? base.value.id);
|
|
60
|
+
const existing = await this.options.store.getRow(key);
|
|
61
|
+
if (!existing) {
|
|
62
|
+
return base;
|
|
63
|
+
}
|
|
64
|
+
const incomingUpdatedAt = toMillis(base.value.updatedAt);
|
|
65
|
+
const existingUpdatedAt = toMillis(existing.updatedAt);
|
|
66
|
+
if (incomingUpdatedAt <= existingUpdatedAt) {
|
|
67
|
+
return {
|
|
68
|
+
type: "update",
|
|
69
|
+
value: existing,
|
|
70
|
+
previousValue: base.previousValue
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
return base;
|
|
74
|
+
};
|
|
75
|
+
handleMutateBatch_fn = async function(message) {
|
|
76
|
+
const resolvedChanges = [];
|
|
77
|
+
const acceptedMutationIds = [];
|
|
78
|
+
for (const mutation of message.mutations) {
|
|
79
|
+
try {
|
|
80
|
+
const change = await __privateMethod(this, _PartialSyncMutationHandler_instances, intentToMessageLww_fn).call(this, mutation);
|
|
81
|
+
if (!change) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
resolvedChanges.push(change);
|
|
85
|
+
acceptedMutationIds.push(mutation.clientMutationId);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
__privateMethod(this, _PartialSyncMutationHandler_instances, emit_fn).call(this, message.clientId, {
|
|
88
|
+
type: "reject",
|
|
89
|
+
clientId: message.clientId,
|
|
90
|
+
clientMutationId: mutation.clientMutationId,
|
|
91
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
92
|
+
correctiveChanges: []
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
if (resolvedChanges.length === 0) return;
|
|
97
|
+
await this.options.store.applySyncMessages(resolvedChanges);
|
|
98
|
+
__privateMethod(this, _PartialSyncMutationHandler_instances, emit_fn).call(this, message.clientId, {
|
|
99
|
+
type: "ack",
|
|
100
|
+
clientId: message.clientId,
|
|
101
|
+
clientMutationIds: acceptedMutationIds,
|
|
102
|
+
serverVersion: 0,
|
|
103
|
+
changes: resolvedChanges
|
|
104
|
+
});
|
|
105
|
+
await this.options.partialBridge.pushServerChanges(resolvedChanges, {
|
|
106
|
+
excludeClientId: message.clientId
|
|
107
|
+
});
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
export { PartialSyncMutationHandler };
|
|
111
|
+
//# sourceMappingURL=chunk-5VMFQT5Z.js.map
|
|
112
|
+
//# sourceMappingURL=chunk-5VMFQT5Z.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/partial-sync-mutation-handler.ts"],"names":[],"mappings":"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","file":"chunk-5VMFQT5Z.js","sourcesContent":["import type { SyncMessage } from \"@firtoz/db-helpers\";\nimport { exhaustiveGuard } from \"@firtoz/maybe-error\";\nimport type { PartialSyncServerBridge } from \"./partial-sync-server-bridge\";\nimport {\n\tpartialSyncRowKey,\n\ttype PartialSyncRowShape,\n} from \"./partial-sync-row-key\";\nimport type {\n\tMutationIntent,\n\tSyncClientMessage,\n\tSyncServerMessage,\n\tSyncServerMessageBody,\n} from \"./sync-protocol\";\nimport { DEFAULT_SYNC_COLLECTION_ID, toSyncMessage } from \"./sync-protocol\";\n\nexport interface PartialSyncMutationHandlerStore<\n\tTItem extends PartialSyncRowShape,\n> {\n\tapplySyncMessages: (messages: SyncMessage<TItem>[]) => Promise<void>;\n\tgetRow: (key: string | number) => Promise<TItem | undefined>;\n}\n\nexport interface PartialSyncMutationHandlerOptions<\n\tTItem extends PartialSyncRowShape,\n> {\n\tstore: PartialSyncMutationHandlerStore<TItem>;\n\tpartialBridge: Pick<PartialSyncServerBridge<TItem>, \"pushServerChanges\">;\n\tsendToClient: (clientId: string, message: SyncServerMessage<TItem>) => void;\n\tcollectionId?: string;\n}\n\nfunction toMillis(value: number | Date | null | undefined): number {\n\tif (typeof value === \"number\") return value;\n\tif (value instanceof Date) return value.getTime();\n\treturn 0;\n}\n\n/**\n * Partial-sync durable object mutation path: `mutateBatch` → `ack` / `reject` + interest-scoped\n * `rangePatch` via {@link PartialSyncServerBridge.pushServerChanges}. No `serverVersion`, changelog,\n * or `syncBatch` broadcast.\n */\nexport class PartialSyncMutationHandler<TItem extends PartialSyncRowShape> {\n\treadonly #cid: string;\n\n\tconstructor(\n\t\tprivate readonly options: PartialSyncMutationHandlerOptions<TItem>,\n\t) {\n\t\tthis.#cid = options.collectionId ?? DEFAULT_SYNC_COLLECTION_ID;\n\t}\n\n\tget collectionId(): string {\n\t\treturn this.#cid;\n\t}\n\n\t#emit(clientId: string, body: SyncServerMessageBody<TItem>): void {\n\t\tthis.options.sendToClient(clientId, {\n\t\t\t...body,\n\t\t\tcollectionId: this.#cid,\n\t\t} as SyncServerMessage<TItem>);\n\t}\n\n\tasync handleClientMessage(message: SyncClientMessage): Promise<void> {\n\t\tconst mid = message.collectionId ?? DEFAULT_SYNC_COLLECTION_ID;\n\t\tif (mid !== this.#cid) return;\n\t\tswitch (message.type) {\n\t\t\tcase \"ping\":\n\t\t\t\tthis.#emit(message.clientId, {\n\t\t\t\t\ttype: \"pong\",\n\t\t\t\t\ttimestamp: message.timestamp,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\tcase \"mutateBatch\":\n\t\t\t\tawait this.#handleMutateBatch(message);\n\t\t\t\treturn;\n\t\t\tcase \"syncHello\":\n\t\t\tcase \"queryRange\":\n\t\t\tcase \"queryByOffset\":\n\t\t\tcase \"rangeQuery\":\n\t\t\tcase \"rangeReconcile\":\n\t\t\t\treturn;\n\t\t\tdefault:\n\t\t\t\texhaustiveGuard(message);\n\t\t}\n\t}\n\n\tasync #intentToMessageLww(\n\t\tintent: MutationIntent,\n\t): Promise<SyncMessage<TItem> | null> {\n\t\tconst base = toSyncMessage(intent) as SyncMessage<TItem>;\n\t\tif (base.type !== \"update\") {\n\t\t\treturn base;\n\t\t}\n\t\tconst key = partialSyncRowKey(intent.key ?? base.value.id);\n\t\tconst existing = await this.options.store.getRow(key);\n\t\tif (!existing) {\n\t\t\treturn base;\n\t\t}\n\t\tconst incomingUpdatedAt = toMillis(base.value.updatedAt);\n\t\tconst existingUpdatedAt = toMillis(existing.updatedAt);\n\t\tif (incomingUpdatedAt <= existingUpdatedAt) {\n\t\t\treturn {\n\t\t\t\ttype: \"update\",\n\t\t\t\tvalue: existing,\n\t\t\t\tpreviousValue: base.previousValue,\n\t\t\t};\n\t\t}\n\t\treturn base;\n\t}\n\n\tasync #handleMutateBatch(\n\t\tmessage: Extract<SyncClientMessage, { type: \"mutateBatch\" }>,\n\t): Promise<void> {\n\t\tconst resolvedChanges: SyncMessage<TItem>[] = [];\n\t\tconst acceptedMutationIds: string[] = [];\n\n\t\tfor (const mutation of message.mutations) {\n\t\t\ttry {\n\t\t\t\tconst change = await this.#intentToMessageLww(mutation);\n\t\t\t\tif (!change) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tresolvedChanges.push(change);\n\t\t\t\tacceptedMutationIds.push(mutation.clientMutationId);\n\t\t\t} catch (error) {\n\t\t\t\tthis.#emit(message.clientId, {\n\t\t\t\t\ttype: \"reject\",\n\t\t\t\t\tclientId: message.clientId,\n\t\t\t\t\tclientMutationId: mutation.clientMutationId,\n\t\t\t\t\treason: error instanceof Error ? error.message : String(error),\n\t\t\t\t\tcorrectiveChanges: [],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tif (resolvedChanges.length === 0) return;\n\n\t\tawait this.options.store.applySyncMessages(resolvedChanges);\n\n\t\tthis.#emit(message.clientId, {\n\t\t\ttype: \"ack\",\n\t\t\tclientId: message.clientId,\n\t\t\tclientMutationIds: acceptedMutationIds,\n\t\t\tserverVersion: 0,\n\t\t\tchanges: resolvedChanges,\n\t\t});\n\n\t\tawait this.options.partialBridge.pushServerChanges(resolvedChanges, {\n\t\t\texcludeClientId: message.clientId,\n\t\t});\n\t}\n}\n"]}
|