cry-synced-db-client 0.1.189 → 0.1.190

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,50 @@
1
1
  # Versions
2
2
 
3
+ ## 0.1.190 (2026-05-15)
4
+
5
+ ### `addCollectionsToSync(specs[])` — batch register with single-RTT download
6
+
7
+ Plural counterpart to `addCollectionToSync`. Per-spec semantics are
8
+ identical (permanent existing = skip; temporary or absent = install),
9
+ but the install work is decomposed into three phases optimized for
10
+ batching:
11
+
12
+ 1. **Filter + register (sync)** — every survivor's
13
+ `this.collections.set()` and `syncOnlyTheseCollections` extension
14
+ happen up front, so a sync triggered mid-install already sees the
15
+ new collections.
16
+ 2. **Hydrate (parallel)** — Dexie cursor reads + in-mem hydrations
17
+ fan out via `Promise.allSettled`. Per-collection failure is logged
18
+ and isolated; the collection stays installed and hydrates on the
19
+ next sync tick.
20
+ 3. **Download (one RTT)** — every newly-installed non-`writeOnly`
21
+ spec is bundled into a **single `findNewerMany` request**, then
22
+ each result is fed through `processCollectionServerData` for
23
+ conflict resolution. **N collections = 1 RTT** of wall-clock time.
24
+
25
+ ```ts
26
+ // Login flow: install a workspace's collections in one shot.
27
+ await syncedDb.addCollectionsToSync([
28
+ { name: "obravnave", syncConfig: { query: () => ({ lokacija_id }) } },
29
+ { name: "zivali", syncConfig: { query: () => ({ tenant }) } },
30
+ { name: "racuni", syncConfig: { query: () => ({ leto }) } },
31
+ { name: "audit_log", writeOnly: true }, // installed but excluded from RTT
32
+ ]);
33
+ // → 1 findNewerMany covering [obravnave, zivali, racuni].
34
+ ```
35
+
36
+ Skipped specs (existing permanent) consume zero install work. No-op
37
+ when `specs` is empty or every spec is skipped. Network failure on the
38
+ batched download leaves collections registered; next auto-sync tick
39
+ retries via the normal sync flow.
40
+
41
+ Compared to looping `addCollectionToSync(spec)` per item:
42
+ - N collections × per-collection RTT (sequential or `Promise.all`)
43
+ → 1 RTT for the whole batch.
44
+ - Hydration also parallelized.
45
+ - Compatible with `syncOnlyTheseCollections` filter (each addition
46
+ extends the active set).
47
+
3
48
  ## 0.1.189 (2026-05-15)
4
49
 
5
50
  ### Periodic full-resync maintenance heartbeat
package/dist/index.js CHANGED
@@ -4770,6 +4770,99 @@ var _SyncedDb = class _SyncedDb {
4770
4770
  }
4771
4771
  await this._installCollectionConfig(spec);
4772
4772
  }
4773
+ /**
4774
+ * Batch register. See `I_SyncedDb.addCollectionsToSync`.
4775
+ *
4776
+ * The work decomposes into three phases:
4777
+ *
4778
+ * 1. Filter + synchronously seed `this.collections` and the
4779
+ * `syncOnlyCollections` filter so any concurrent sync triggered
4780
+ * mid-install already sees the new collections.
4781
+ * 2. Parallel Dexie cursor load + in-mem hydration.
4782
+ * 3. Parallel per-collection one-shot download.
4783
+ *
4784
+ * Per-spec errors in phase 2/3 are isolated via `Promise.allSettled`:
4785
+ * one collection's hydration / download failure does not block the
4786
+ * others. Failures are logged and the spec is left installed (its
4787
+ * cache will hydrate on the next sync tick).
4788
+ */
4789
+ async addCollectionsToSync(specs) {
4790
+ var _a;
4791
+ if (specs.length === 0) return;
4792
+ const toInstall = [];
4793
+ for (const spec of specs) {
4794
+ const existing = this.collections.get(spec.name);
4795
+ if (existing && !existing.temporaryConfig) continue;
4796
+ this.collections.set(spec.name, spec);
4797
+ if (this.syncOnlyCollections) {
4798
+ this.syncOnlyCollections.add(spec.name);
4799
+ }
4800
+ toInstall.push(spec);
4801
+ }
4802
+ if (toInstall.length === 0) return;
4803
+ const hydrationResults = await Promise.allSettled(
4804
+ toInstall.map(async (spec) => {
4805
+ const meta = await this.dexieDb.getSyncMeta(spec.name);
4806
+ if (meta) this.syncMetaCache.set(spec.name, meta);
4807
+ if (!spec.writeOnly) {
4808
+ await this.loadCollectionToInMem(spec.name);
4809
+ }
4810
+ })
4811
+ );
4812
+ for (let i = 0; i < hydrationResults.length; i++) {
4813
+ const r = hydrationResults[i];
4814
+ if (r.status === "rejected") {
4815
+ console.error(
4816
+ `[SyncedDb] addCollectionsToSync: hydration failed for "${toInstall[i].name}":`,
4817
+ r.reason
4818
+ );
4819
+ }
4820
+ }
4821
+ if (!this.connectionManager.canSync()) return;
4822
+ const downloadable = toInstall.filter((s) => !s.writeOnly);
4823
+ if (downloadable.length === 0) return;
4824
+ const plans = [];
4825
+ const fetchSpecs = [];
4826
+ for (const spec of downloadable) {
4827
+ const rawQuery = (_a = spec.syncConfig) == null ? void 0 : _a.query;
4828
+ const query = typeof rawQuery === "function" ? rawQuery() : rawQuery;
4829
+ const meta = this.syncMetaCache.get(spec.name);
4830
+ const timestamp = (meta == null ? void 0 : meta.lastSyncTs) || 0;
4831
+ plans.push({ spec, wasFirstTime: !timestamp });
4832
+ fetchSpecs.push({
4833
+ collection: spec.name,
4834
+ timestamp,
4835
+ query,
4836
+ opts: { returnDeleted: true }
4837
+ });
4838
+ }
4839
+ let results;
4840
+ try {
4841
+ results = await this.connectionManager.withRestTimeout(
4842
+ this.restInterface.findNewerMany(fetchSpecs),
4843
+ "addCollectionsToSync"
4844
+ );
4845
+ } catch (err) {
4846
+ console.error(
4847
+ `[SyncedDb] addCollectionsToSync: batched findNewerMany failed:`,
4848
+ err
4849
+ );
4850
+ return;
4851
+ }
4852
+ for (const plan of plans) {
4853
+ const data = results[plan.spec.name];
4854
+ if (!data || data.length === 0) continue;
4855
+ const source = plan.wasFirstTime ? "initial" : "incremental";
4856
+ try {
4857
+ await this.syncEngine.processCollectionServerData(plan.spec.name, data, { source });
4858
+ } catch (err) {
4859
+ console.error(
4860
+ `[SyncedDb] addCollectionsToSync: processCollectionServerData failed for "${plan.spec.name}":`,
4861
+ err
4862
+ );
4863
+ }
4864
+ }
4865
+ }
4773
4866
  /**
4774
4867
  * Replace the collection config and re-sync. See `I_SyncedDb.replaceSyncCollection`.
4775
4868
  */
@@ -100,6 +100,23 @@ export declare class SyncedDb implements I_SyncedDb {
100
100
  * Register a collection for sync at runtime. See `I_SyncedDb.addCollectionToSync`.
101
101
  */
102
102
  addCollectionToSync(spec: CollectionConfig): Promise<void>;
103
+ /**
104
+ * Batch register. See `I_SyncedDb.addCollectionsToSync`.
105
+ *
106
+ * The work decomposes into three phases:
107
+ *
108
+ * 1. Filter + synchronously seed `this.collections` and the
109
+ * `syncOnlyCollections` filter so any concurrent sync triggered
110
+ * mid-install already sees the new collections.
111
+ * 2. Parallel Dexie cursor load + in-mem hydration.
112
+ * 3. Parallel per-collection one-shot download.
113
+ *
114
+ * Per-spec errors in phase 2/3 are isolated via `Promise.allSettled`:
115
+ * one collection's hydration / download failure does not block the
116
+ * others. Failures are logged and the spec is left installed (its
117
+ * cache will hydrate on the next sync tick).
118
+ */
119
+ addCollectionsToSync(specs: CollectionConfig[]): Promise<void>;
103
120
  /**
104
121
  * Replace the collection config and re-sync. See `I_SyncedDb.replaceSyncCollection`.
105
122
  */
@@ -943,6 +943,34 @@ export interface I_SyncedDb {
943
943
  * @param spec Collection config to register
944
944
  */
945
945
  addCollectionToSync(spec: CollectionConfig): Promise<void>;
946
+ /**
947
+ * Batch variant of `addCollectionToSync`. Semantics are identical
948
+ * per-spec (permanent existing: no-op; temporary or absent: install).
949
+ *
950
+ * Three-phase install:
951
+ *
952
+ * 1. **Filter + register (sync)** — `this.collections.set()` and
953
+ * `syncOnlyTheseCollections` extensions happen up front so any
954
+ * concurrent sync triggered mid-install already sees the new
955
+ * collections.
956
+ * 2. **Hydrate (parallel)** — Dexie cursor reads + in-mem
957
+ * hydrations run via `Promise.allSettled`. Per-collection
958
+ * failures are logged and isolated; the collection stays
959
+ * installed and hydrates on the next sync tick.
960
+ * 3. **Download (one RTT)** — every newly-installed non-writeOnly
961
+ * collection is bundled into a SINGLE `findNewerMany` request,
962
+ * then each result is fed through `processCollectionServerData`
963
+ * for conflict resolution. N collections cost 1 RTT total
964
+ * instead of N parallel RTTs.
965
+ *
966
+ * Skipped specs (existing permanent) consume no install work. No-op
967
+ * when `specs` is empty or every spec is a no-op. Network failure on
968
+ * the batched download leaves the collections installed; the next
969
+ * auto-sync tick retries via the normal sync flow.
970
+ *
971
+ * @param specs Array of collection configs to register
972
+ */
973
+ addCollectionsToSync(specs: CollectionConfig[]): Promise<void>;
946
974
  /**
947
975
  * Replace the collection config and re-sync.
948
976
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cry-synced-db-client",
3
- "version": "0.1.189",
3
+ "version": "0.1.190",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.js",