@rocicorp/zero 0.13.2025020500 → 0.13.2025020700

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 (82) hide show
  1. package/out/{chunk-H3C7QNPL.js → chunk-FVVIM6TT.js} +1042 -865
  2. package/out/chunk-FVVIM6TT.js.map +7 -0
  3. package/out/replicache/src/persist/collect-idb-databases.d.ts +2 -2
  4. package/out/replicache/src/persist/collect-idb-databases.d.ts.map +1 -1
  5. package/out/replicache/src/replicache-impl.d.ts +2 -1
  6. package/out/replicache/src/replicache-impl.d.ts.map +1 -1
  7. package/out/replicache/src/sync/patch.d.ts +5 -1
  8. package/out/replicache/src/sync/patch.d.ts.map +1 -1
  9. package/out/replicache/src/sync/pull.d.ts +2 -0
  10. package/out/replicache/src/sync/pull.d.ts.map +1 -1
  11. package/out/solid.js +1 -1
  12. package/out/zero-cache/src/config/zero-config.d.ts +20 -0
  13. package/out/zero-cache/src/config/zero-config.d.ts.map +1 -1
  14. package/out/zero-cache/src/config/zero-config.js +14 -0
  15. package/out/zero-cache/src/config/zero-config.js.map +1 -1
  16. package/out/zero-cache/src/db/migration.d.ts.map +1 -1
  17. package/out/zero-cache/src/db/migration.js +1 -2
  18. package/out/zero-cache/src/db/migration.js.map +1 -1
  19. package/out/zero-cache/src/server/change-streamer.d.ts.map +1 -1
  20. package/out/zero-cache/src/server/change-streamer.js +1 -1
  21. package/out/zero-cache/src/server/change-streamer.js.map +1 -1
  22. package/out/zero-cache/src/server/main.js +1 -1
  23. package/out/zero-cache/src/server/main.js.map +1 -1
  24. package/out/zero-cache/src/server/multi/config.d.ts +10 -0
  25. package/out/zero-cache/src/server/multi/config.d.ts.map +1 -1
  26. package/out/zero-cache/src/services/change-source/pg/schema/published.d.ts.map +1 -1
  27. package/out/zero-cache/src/services/change-source/pg/schema/published.js +7 -1
  28. package/out/zero-cache/src/services/change-source/pg/schema/published.js.map +1 -1
  29. package/out/zero-cache/src/services/change-source/pg/schema/shard.d.ts.map +1 -1
  30. package/out/zero-cache/src/services/change-source/pg/schema/shard.js +3 -1
  31. package/out/zero-cache/src/services/change-source/pg/schema/shard.js.map +1 -1
  32. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts +1 -1
  33. package/out/zero-cache/src/services/change-streamer/change-streamer-service.d.ts.map +1 -1
  34. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js +9 -7
  35. package/out/zero-cache/src/services/change-streamer/change-streamer-service.js.map +1 -1
  36. package/out/zero-cache/src/services/change-streamer/schema/init.d.ts +2 -2
  37. package/out/zero-cache/src/services/change-streamer/schema/init.d.ts.map +1 -1
  38. package/out/zero-cache/src/services/change-streamer/schema/init.js +40 -27
  39. package/out/zero-cache/src/services/change-streamer/schema/init.js.map +1 -1
  40. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts +5 -4
  41. package/out/zero-cache/src/services/change-streamer/schema/tables.d.ts.map +1 -1
  42. package/out/zero-cache/src/services/change-streamer/schema/tables.js +44 -19
  43. package/out/zero-cache/src/services/change-streamer/schema/tables.js.map +1 -1
  44. package/out/zero-cache/src/services/change-streamer/storer.d.ts +1 -1
  45. package/out/zero-cache/src/services/change-streamer/storer.d.ts.map +1 -1
  46. package/out/zero-cache/src/services/change-streamer/storer.js +21 -9
  47. package/out/zero-cache/src/services/change-streamer/storer.js.map +1 -1
  48. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts +3 -3
  49. package/out/zero-cache/src/services/view-syncer/cvr-store.d.ts.map +1 -1
  50. package/out/zero-cache/src/services/view-syncer/cvr-store.js +64 -37
  51. package/out/zero-cache/src/services/view-syncer/cvr-store.js.map +1 -1
  52. package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts +3 -3
  53. package/out/zero-cache/src/services/view-syncer/schema/cvr.d.ts.map +1 -1
  54. package/out/zero-cache/src/services/view-syncer/schema/cvr.js +61 -33
  55. package/out/zero-cache/src/services/view-syncer/schema/cvr.js.map +1 -1
  56. package/out/zero-cache/src/services/view-syncer/schema/init.d.ts +1 -1
  57. package/out/zero-cache/src/services/view-syncer/schema/init.d.ts.map +1 -1
  58. package/out/zero-cache/src/services/view-syncer/schema/init.js +56 -43
  59. package/out/zero-cache/src/services/view-syncer/schema/init.js.map +1 -1
  60. package/out/zero-cache/src/services/view-syncer/view-syncer.d.ts.map +1 -1
  61. package/out/zero-cache/src/services/view-syncer/view-syncer.js +9 -10
  62. package/out/zero-cache/src/services/view-syncer/view-syncer.js.map +1 -1
  63. package/out/zero-client/src/client/context.d.ts +1 -1
  64. package/out/zero-client/src/client/context.d.ts.map +1 -1
  65. package/out/zero-client/src/client/crud.d.ts +5 -4
  66. package/out/zero-client/src/client/crud.d.ts.map +1 -1
  67. package/out/zero-client/src/client/custom.d.ts.map +1 -1
  68. package/out/zero-client/src/client/ivm-source-repo.d.ts +12 -3
  69. package/out/zero-client/src/client/ivm-source-repo.d.ts.map +1 -1
  70. package/out/zero-client/src/client/keys.d.ts +1 -0
  71. package/out/zero-client/src/client/keys.d.ts.map +1 -1
  72. package/out/zero-client/src/client/zero.d.ts +1 -1
  73. package/out/zero-client/src/client/zero.d.ts.map +1 -1
  74. package/out/zero-protocol/src/push.d.ts.map +1 -1
  75. package/out/zero-protocol/src/push.js +8 -20
  76. package/out/zero-protocol/src/push.js.map +1 -1
  77. package/out/zero-schema/src/name-mapper.d.ts +2 -2
  78. package/out/zero-schema/src/name-mapper.d.ts.map +1 -1
  79. package/out/zero-schema/src/name-mapper.js.map +1 -1
  80. package/out/zero.js +1 -1
  81. package/package.json +1 -2
  82. package/out/chunk-H3C7QNPL.js.map +0 -7
@@ -3789,6 +3789,10 @@ async function readFromHead(name, dagRead, formatVersion) {
3789
3789
  const commit = await commitFromHead(name, dagRead);
3790
3790
  return readFromCommit(commit, dagRead, formatVersion);
3791
3791
  }
3792
+ async function readFromHash(hash2, dagRead, formatVersion) {
3793
+ const commit = await commitFromHash(hash2, dagRead);
3794
+ return readFromCommit(commit, dagRead, formatVersion);
3795
+ }
3792
3796
  function readFromCommit(commit, dagRead, formatVersion) {
3793
3797
  const indexes = readIndexesForRead(commit, dagRead, formatVersion);
3794
3798
  const map = new BTreeRead(dagRead, formatVersion, commit.valueHash);
@@ -4995,10 +4999,30 @@ var CookieMismatch = 2;
4995
4999
 
4996
5000
  // ../replicache/src/sync/patch.ts
4997
5001
  async function apply(lc, dbWrite, patch) {
5002
+ const ret = [];
5003
+ function pushChangeOrAdd(key, oldValue, newValue) {
5004
+ if (oldValue === void 0) {
5005
+ ret.push({
5006
+ op: "add",
5007
+ key,
5008
+ newValue
5009
+ });
5010
+ } else {
5011
+ ret.push({
5012
+ op: "change",
5013
+ key,
5014
+ oldValue,
5015
+ newValue
5016
+ });
5017
+ }
5018
+ }
4998
5019
  for (const p of patch) {
4999
5020
  switch (p.op) {
5000
5021
  case "put": {
5001
- await dbWrite.put(lc, p.key, deepFreeze(p.value));
5022
+ const existing = await dbWrite.get(p.key);
5023
+ const frozen = deepFreeze(p.value);
5024
+ pushChangeOrAdd(p.key, existing, frozen);
5025
+ await dbWrite.put(lc, p.key, frozen);
5002
5026
  break;
5003
5027
  }
5004
5028
  case "update": {
@@ -5018,17 +5042,33 @@ async function apply(lc, dbWrite, patch) {
5018
5042
  if (p.merge) {
5019
5043
  addToEntries(p.merge);
5020
5044
  }
5021
- await dbWrite.put(lc, p.key, deepFreeze(Object.fromEntries(entries)));
5045
+ const frozen = deepFreeze(Object.fromEntries(entries));
5046
+ pushChangeOrAdd(p.key, existing, frozen);
5047
+ await dbWrite.put(lc, p.key, frozen);
5022
5048
  break;
5023
5049
  }
5024
- case "del":
5050
+ case "del": {
5051
+ const existing = await dbWrite.get(p.key);
5052
+ if (existing === void 0) {
5053
+ continue;
5054
+ }
5025
5055
  await dbWrite.del(lc, p.key);
5056
+ ret.push({
5057
+ op: "del",
5058
+ key: p.key,
5059
+ oldValue: existing
5060
+ });
5026
5061
  break;
5062
+ }
5027
5063
  case "clear":
5028
5064
  await dbWrite.clear();
5065
+ ret.push({
5066
+ op: "clear"
5067
+ });
5029
5068
  break;
5030
5069
  }
5031
5070
  }
5071
+ return ret;
5032
5072
  }
5033
5073
 
5034
5074
  // ../replicache/src/sync/pull-error.ts
@@ -5195,10 +5235,11 @@ function handlePullResponseV1(lc, store, expectedBaseCookie, response, clientID,
5195
5235
  clientID,
5196
5236
  formatVersion
5197
5237
  );
5198
- await apply(lc, dbWrite, response.patch);
5238
+ const diffs = await apply(lc, dbWrite, response.patch);
5199
5239
  return {
5200
5240
  type: Applied,
5201
- syncHead: await dbWrite.commit(SYNC_HEAD_NAME)
5241
+ syncHead: await dbWrite.commit(SYNC_HEAD_NAME),
5242
+ diffs
5202
5243
  };
5203
5244
  });
5204
5245
  }
@@ -7285,6 +7326,7 @@ var ReplicacheImpl = class {
7285
7326
  void this.#open(
7286
7327
  indexes,
7287
7328
  enableClientGroupForking,
7329
+ enableMutationRecovery,
7288
7330
  clientMaxAgeMs,
7289
7331
  profileIDResolver.resolve,
7290
7332
  clientGroupIDResolver.resolve,
@@ -7292,7 +7334,7 @@ var ReplicacheImpl = class {
7292
7334
  onClientsDeleted
7293
7335
  );
7294
7336
  }
7295
- async #open(indexes, enableClientGroupForking, clientMaxAgeMs, profileIDResolver, resolveClientGroupID, resolveReady, onClientsDeleted) {
7337
+ async #open(indexes, enableClientGroupForking, enableMutationRecovery, clientMaxAgeMs, profileIDResolver, resolveClientGroupID, resolveReady, onClientsDeleted) {
7296
7338
  const { clientID } = this;
7297
7339
  await closingInstances.get(this.name);
7298
7340
  await this.#idbDatabases.getProfileID().then(profileIDResolver);
@@ -7342,6 +7384,7 @@ var ReplicacheImpl = class {
7342
7384
  COLLECT_IDB_INTERVAL,
7343
7385
  INITIAL_COLLECT_IDB_DELAY,
7344
7386
  2 * clientMaxAgeMs,
7387
+ enableMutationRecovery,
7345
7388
  onClientsDeleted,
7346
7389
  this.#lc,
7347
7390
  signal
@@ -7719,7 +7762,7 @@ var ReplicacheImpl = class {
7719
7762
  *
7720
7763
  * @experimental This method is under development and its semantics will change.
7721
7764
  */
7722
- async poke(poke) {
7765
+ async poke(poke, pullApplied) {
7723
7766
  await this.#ready;
7724
7767
  const { clientID } = this;
7725
7768
  const requestID = newRequestID(clientID);
@@ -7743,6 +7786,7 @@ var ReplicacheImpl = class {
7743
7786
  );
7744
7787
  switch (result.type) {
7745
7788
  case Applied:
7789
+ await pullApplied(this.memdag, result.syncHead, result.diffs);
7746
7790
  await this.maybeEndPull(result.syncHead, requestID);
7747
7791
  break;
7748
7792
  case CookieMismatch:
@@ -8164,7 +8208,7 @@ function getKVStoreProvider(lc, kvStore) {
8164
8208
  // ../replicache/src/persist/collect-idb-databases.ts
8165
8209
  var COLLECT_IDB_INTERVAL = 12 * 60 * 60 * 1e3;
8166
8210
  var INITIAL_COLLECT_IDB_DELAY = 5 * 60 * 1e3;
8167
- function initCollectIDBDatabases(idbDatabasesStore, kvDropStore, collectInterval, initialCollectDelay, maxAge, onClientsDeleted, lc, signal) {
8211
+ function initCollectIDBDatabases(idbDatabasesStore, kvDropStore, collectInterval, initialCollectDelay, maxAge, enableMutationRecovery, onClientsDeleted, lc, signal) {
8168
8212
  let initial = true;
8169
8213
  initBgIntervalProcess(
8170
8214
  "CollectIDBDatabases",
@@ -8174,6 +8218,7 @@ function initCollectIDBDatabases(idbDatabasesStore, kvDropStore, collectInterval
8174
8218
  Date.now(),
8175
8219
  maxAge,
8176
8220
  kvDropStore,
8221
+ enableMutationRecovery,
8177
8222
  onClientsDeleted
8178
8223
  );
8179
8224
  },
@@ -8188,14 +8233,20 @@ function initCollectIDBDatabases(idbDatabasesStore, kvDropStore, collectInterval
8188
8233
  signal
8189
8234
  );
8190
8235
  }
8191
- async function collectIDBDatabases(idbDatabasesStore, now, maxAge, kvDropStore, onClientsDeleted, newDagStore = defaultNewDagStore) {
8236
+ async function collectIDBDatabases(idbDatabasesStore, now, maxAge, kvDropStore, enableMutationRecovery, onClientsDeleted, newDagStore = defaultNewDagStore) {
8192
8237
  const databases = await idbDatabasesStore.getDatabases();
8193
8238
  const dbs = Object.values(databases);
8194
8239
  const collectResults = await Promise.all(
8195
8240
  dbs.map(
8196
8241
  async (db) => [
8197
8242
  db.name,
8198
- await gatherDatabaseInfoForCollect(db, now, maxAge, newDagStore)
8243
+ await gatherDatabaseInfoForCollect(
8244
+ db,
8245
+ now,
8246
+ maxAge,
8247
+ enableMutationRecovery,
8248
+ newDagStore
8249
+ )
8199
8250
  ]
8200
8251
  )
8201
8252
  );
@@ -8245,7 +8296,7 @@ function defaultNewDagStore(name) {
8245
8296
  const perKvStore = new IDBStore(name);
8246
8297
  return new StoreImpl(perKvStore, newRandomHash, assertHash);
8247
8298
  }
8248
- function gatherDatabaseInfoForCollect(db, now, maxAge, newDagStore) {
8299
+ function gatherDatabaseInfoForCollect(db, now, maxAge, enableMutationRecovery, newDagStore) {
8249
8300
  if (db.replicacheFormatVersion > Latest) {
8250
8301
  return [false];
8251
8302
  }
@@ -8256,7 +8307,10 @@ function gatherDatabaseInfoForCollect(db, now, maxAge, newDagStore) {
8256
8307
  assert(
8257
8308
  db.replicacheFormatVersion === DD31 || db.replicacheFormatVersion === V6 || db.replicacheFormatVersion === V7
8258
8309
  );
8259
- return gatherPendingMutationsInClientGroups(newDagStore(db.name));
8310
+ return canDatabaseBeCollectedAndGetDeletedClientIDs(
8311
+ enableMutationRecovery,
8312
+ newDagStore(db.name)
8313
+ );
8260
8314
  }
8261
8315
  async function dropDatabase(dbName, opts) {
8262
8316
  const logContext = createLogContext(opts?.logLevel, opts?.logSinks, {
@@ -8280,12 +8334,14 @@ async function dropAllDatabases(opts) {
8280
8334
  const result = await dropDatabases(store, dbNames, kvStoreProvider.drop);
8281
8335
  return result;
8282
8336
  }
8283
- function gatherPendingMutationsInClientGroups(perdag) {
8337
+ function canDatabaseBeCollectedAndGetDeletedClientIDs(enableMutationRecovery, perdag) {
8284
8338
  return withRead(perdag, async (read) => {
8285
- const clientGroups = await getClientGroups(read);
8286
- for (const clientGroup of clientGroups.values()) {
8287
- if (clientGroupHasPendingMutations(clientGroup)) {
8288
- return [false];
8339
+ if (enableMutationRecovery) {
8340
+ const clientGroups = await getClientGroups(read);
8341
+ for (const clientGroup of clientGroups.values()) {
8342
+ if (clientGroupHasPendingMutations(clientGroup)) {
8343
+ return [false];
8344
+ }
8289
8345
  }
8290
8346
  }
8291
8347
  const clients = await getClients(read);
@@ -11976,25 +12032,16 @@ var pushBodySchema = valita_exports.object({
11976
12032
  var pushMessageSchema = valita_exports.tuple([valita_exports.literal("push"), pushBodySchema]);
11977
12033
  function mapCRUD(arg, map) {
11978
12034
  return {
11979
- ops: arg.ops.map(({ op, tableName, primaryKey, value }) => {
11980
- switch (op) {
11981
- case "delete": {
11982
- return {
11983
- op,
11984
- tableName: map.tableName(tableName),
11985
- primaryKey: map.columns(tableName, primaryKey),
11986
- value: map.row(tableName, value)
11987
- };
11988
- }
11989
- default:
11990
- return {
11991
- op,
11992
- tableName: map.tableName(tableName),
11993
- primaryKey: map.columns(tableName, primaryKey),
11994
- value: map.row(tableName, value)
11995
- };
11996
- }
11997
- })
12035
+ ops: arg.ops.map(
12036
+ ({ op, tableName, primaryKey, value }) => ({
12037
+ op,
12038
+ tableName: map.tableName(tableName),
12039
+ primaryKey: map.columns(tableName, primaryKey),
12040
+ value: map.row(tableName, value)
12041
+ // The cast is necessary because ts objects to the `value` field
12042
+ // for "delete" ops being different.
12043
+ })
12044
+ )
11998
12045
  };
11999
12046
  }
12000
12047
 
@@ -12550,931 +12597,1061 @@ var MemoryStorage = class {
12550
12597
  }
12551
12598
  };
12552
12599
 
12553
- // ../zero-client/src/client/keys.ts
12554
- var CLIENTS_KEY_PREFIX = "c/";
12555
- var DESIRED_QUERIES_KEY_PREFIX = "d/";
12556
- var GOT_QUERIES_KEY_PREFIX = "g/";
12557
- var ENTITIES_KEY_PREFIX = "e/";
12558
- function toClientsKey(clientID) {
12559
- return CLIENTS_KEY_PREFIX + clientID;
12560
- }
12561
- function toDesiredQueriesKey(clientID, hash2) {
12562
- return DESIRED_QUERIES_KEY_PREFIX + clientID + "/" + hash2;
12563
- }
12564
- function desiredQueriesPrefixForClient(clientID) {
12565
- return DESIRED_QUERIES_KEY_PREFIX + clientID + "/";
12566
- }
12567
- function toGotQueriesKey(hash2) {
12568
- return GOT_QUERIES_KEY_PREFIX + hash2;
12600
+ // ../zql/src/ivm/constraint.ts
12601
+ function constraintMatchesRow(constraint, row) {
12602
+ for (const key in constraint) {
12603
+ if (!valuesEqual(row[key], constraint[key])) {
12604
+ return false;
12605
+ }
12606
+ }
12607
+ return true;
12569
12608
  }
12570
- function toPrimaryKeyString(tableName, primaryKey, value) {
12571
- if (primaryKey.length === 1) {
12572
- return ENTITIES_KEY_PREFIX + tableName + "/" + parse(value[primaryKey[0]], primaryKeyValueSchema);
12609
+ function constraintMatchesPrimaryKey(constraint, primary) {
12610
+ const constraintKeys = Object.keys(constraint);
12611
+ if (constraintKeys.length !== primary.length) {
12612
+ return false;
12573
12613
  }
12574
- const values = primaryKey.map((k) => parse(value[k], primaryKeyValueSchema));
12575
- const str = JSON.stringify(values);
12576
- const idSegment = h128(str);
12577
- return ENTITIES_KEY_PREFIX + tableName + "/" + idSegment;
12614
+ constraintKeys.sort(stringCompare);
12615
+ for (let i = 0; i < constraintKeys.length; i++) {
12616
+ if (constraintKeys[i][0] !== primary[i]) {
12617
+ return false;
12618
+ }
12619
+ }
12620
+ return true;
12578
12621
  }
12579
12622
 
12580
- // ../zero-client/src/client/context.ts
12581
- var ZeroContext = class {
12582
- // It is a bummer to have to maintain separate MemorySources here and copy the
12583
- // data in from the Replicache db. But we want the data to be accessible via
12584
- // pipelines *synchronously* and the core Replicache infra is all async. So
12585
- // that needs to be fixed.
12586
- #mainSources;
12587
- #addQuery;
12588
- #batchViewUpdates;
12589
- #commitListeners = /* @__PURE__ */ new Set();
12590
- staticQueryParameters = void 0;
12591
- constructor(mainSources, addQuery, batchViewUpdates) {
12592
- this.#mainSources = mainSources;
12593
- this.#addQuery = addQuery;
12594
- this.#batchViewUpdates = batchViewUpdates;
12595
- }
12596
- getSource(name) {
12597
- return this.#mainSources.getSource(name);
12623
+ // ../zql/src/ivm/memory-source.ts
12624
+ var MemorySource = class _MemorySource {
12625
+ #tableName;
12626
+ #columns;
12627
+ #primaryKey;
12628
+ #primaryIndexSort;
12629
+ #indexes = /* @__PURE__ */ new Map();
12630
+ #connections = [];
12631
+ #overlay;
12632
+ constructor(tableName, columns, primaryKey, primaryIndexData) {
12633
+ this.#tableName = tableName;
12634
+ this.#columns = columns;
12635
+ this.#primaryKey = primaryKey;
12636
+ this.#primaryIndexSort = primaryKey.map((k) => [k, "asc"]);
12637
+ const comparator2 = makeBoundComparator(this.#primaryIndexSort);
12638
+ this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {
12639
+ comparator: comparator2,
12640
+ data: primaryIndexData ?? new BTreeSet(comparator2),
12641
+ usedBy: /* @__PURE__ */ new Set()
12642
+ });
12643
+ assertOrderingIncludesPK(this.#primaryIndexSort, this.#primaryKey);
12598
12644
  }
12599
- addServerQuery(ast, gotCallback) {
12600
- return this.#addQuery(ast, gotCallback);
12645
+ // Mainly for tests.
12646
+ getSchemaInfo() {
12647
+ return {
12648
+ tableName: this.#tableName,
12649
+ columns: this.#columns,
12650
+ primaryKey: this.#primaryKey
12651
+ };
12601
12652
  }
12602
- createStorage() {
12603
- return new MemoryStorage();
12653
+ fork() {
12654
+ const primaryIndex = this.#getPrimaryIndex();
12655
+ return new _MemorySource(
12656
+ this.#tableName,
12657
+ this.#columns,
12658
+ this.#primaryKey,
12659
+ primaryIndex.data.clone()
12660
+ );
12604
12661
  }
12605
- onTransactionCommit(cb) {
12606
- this.#commitListeners.add(cb);
12607
- return () => {
12608
- this.#commitListeners.delete(cb);
12662
+ #getSchema(connection) {
12663
+ return {
12664
+ tableName: this.#tableName,
12665
+ columns: this.#columns,
12666
+ primaryKey: this.#primaryKey,
12667
+ sort: connection.sort,
12668
+ system: "client",
12669
+ relationships: {},
12670
+ isHidden: false,
12671
+ compareRows: connection.compareRows
12609
12672
  };
12610
12673
  }
12611
- batchViewUpdates(applyViewUpdates) {
12612
- let result;
12613
- let viewChangesPerformed = false;
12614
- this.#batchViewUpdates(() => {
12615
- result = applyViewUpdates();
12616
- viewChangesPerformed = true;
12617
- });
12618
- assert(
12619
- viewChangesPerformed,
12620
- "batchViewUpdates must call applyViewUpdates synchronously."
12621
- );
12622
- return result;
12674
+ connect(sort, filters) {
12675
+ const transformedFilters = transformFilters(filters);
12676
+ const input = {
12677
+ getSchema: () => schema,
12678
+ fetch: (req) => this.#fetch(req, connection),
12679
+ cleanup: (req) => this.#cleanup(req, connection),
12680
+ setOutput: (output) => {
12681
+ connection.output = output;
12682
+ },
12683
+ destroy: () => {
12684
+ this.#disconnect(input);
12685
+ },
12686
+ fullyAppliedFilters: !transformedFilters.conditionsRemoved
12687
+ };
12688
+ const connection = {
12689
+ input,
12690
+ output: void 0,
12691
+ sort,
12692
+ compareRows: makeComparator(sort),
12693
+ filters: transformedFilters.filters ? {
12694
+ condition: transformedFilters.filters,
12695
+ predicate: createPredicate(transformedFilters.filters)
12696
+ } : void 0
12697
+ };
12698
+ const schema = this.#getSchema(connection);
12699
+ assertOrderingIncludesPK(sort, this.#primaryKey);
12700
+ this.#connections.push(connection);
12701
+ return input;
12623
12702
  }
12624
- processChanges(changes) {
12625
- this.batchViewUpdates(() => {
12626
- try {
12627
- for (const diff2 of changes) {
12628
- const { key } = diff2;
12629
- assert(key.startsWith(ENTITIES_KEY_PREFIX));
12630
- const slash = key.indexOf("/", ENTITIES_KEY_PREFIX.length);
12631
- const name = key.slice(ENTITIES_KEY_PREFIX.length, slash);
12632
- const source = this.getSource(name);
12633
- if (!source) {
12634
- continue;
12635
- }
12636
- switch (diff2.op) {
12637
- case "del":
12638
- assert(typeof diff2.oldValue === "object");
12639
- source.push({
12640
- type: "remove",
12641
- row: diff2.oldValue
12642
- });
12643
- break;
12644
- case "add":
12645
- assert(typeof diff2.newValue === "object");
12646
- source.push({
12647
- type: "add",
12648
- row: diff2.newValue
12649
- });
12650
- break;
12651
- case "change":
12652
- assert(typeof diff2.newValue === "object");
12653
- assert(typeof diff2.oldValue === "object");
12654
- source.push({
12655
- type: "edit",
12656
- row: diff2.newValue,
12657
- oldRow: diff2.oldValue
12658
- });
12659
- break;
12660
- default:
12661
- unreachable(diff2);
12662
- }
12663
- }
12664
- } finally {
12665
- this.#endTransaction();
12703
+ #disconnect(input) {
12704
+ const idx = this.#connections.findIndex((c) => c.input === input);
12705
+ assert(idx !== -1, "Connection not found");
12706
+ const connection = this.#connections[idx];
12707
+ this.#connections.splice(idx, 1);
12708
+ const primaryIndexKey = JSON.stringify(this.#primaryIndexSort);
12709
+ for (const [key, index] of this.#indexes) {
12710
+ if (key === primaryIndexKey) {
12711
+ continue;
12712
+ }
12713
+ index.usedBy.delete(connection);
12714
+ if (index.usedBy.size === 0) {
12715
+ this.#indexes.delete(key);
12666
12716
  }
12667
- });
12668
- }
12669
- #endTransaction() {
12670
- for (const listener of this.#commitListeners) {
12671
- listener();
12672
12717
  }
12673
12718
  }
12674
- };
12675
-
12676
- // ../zero-client/src/client/crud.ts
12677
- function makeCRUDMutate(schema, repMutate) {
12678
- const { [CRUD_MUTATION_NAME]: zeroCRUD } = repMutate;
12679
- let inBatch = false;
12680
- const mutateBatch = async (body) => {
12681
- if (inBatch) {
12682
- throw new Error("Cannot call mutate inside a batch");
12683
- }
12684
- inBatch = true;
12685
- try {
12686
- const ops = [];
12687
- const m = {};
12688
- for (const name of Object.keys(schema.tables)) {
12689
- m[name] = makeBatchCRUDMutate(name, schema, ops);
12690
- }
12691
- const rv = await body(m);
12692
- await zeroCRUD({ ops });
12693
- return rv;
12694
- } finally {
12695
- inBatch = false;
12719
+ #getPrimaryIndex() {
12720
+ const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));
12721
+ assert(index, "Primary index not found");
12722
+ return index;
12723
+ }
12724
+ #getOrCreateIndex(sort, usedBy) {
12725
+ const key = JSON.stringify(sort);
12726
+ const index = this.#indexes.get(key);
12727
+ if (index) {
12728
+ index.usedBy.add(usedBy);
12729
+ return index;
12696
12730
  }
12697
- };
12698
- const assertNotInBatch = (tableName, op) => {
12699
- if (inBatch) {
12700
- throw new Error(`Cannot call mutate.${tableName}.${op} inside a batch`);
12731
+ const comparator2 = makeBoundComparator(sort);
12732
+ const data = new BTreeSet(comparator2);
12733
+ for (const row of this.#getPrimaryIndex().data) {
12734
+ data.add(row);
12701
12735
  }
12702
- };
12703
- const mutate = {};
12704
- for (const [name, tableSchema] of Object.entries(schema.tables)) {
12705
- mutate[name] = makeEntityCRUDMutate(
12706
- name,
12707
- tableSchema.primaryKey,
12708
- zeroCRUD,
12709
- assertNotInBatch
12710
- );
12736
+ const newIndex = { comparator: comparator2, data, usedBy: /* @__PURE__ */ new Set([usedBy]) };
12737
+ this.#indexes.set(key, newIndex);
12738
+ return newIndex;
12711
12739
  }
12712
- return {
12713
- mutate,
12714
- mutateBatch
12715
- };
12716
- }
12717
- function makeEntityCRUDMutate(tableName, primaryKey, zeroCRUD, assertNotInBatch) {
12718
- return {
12719
- insert: (value) => {
12720
- assertNotInBatch(tableName, "insert");
12721
- const op = {
12722
- op: "insert",
12723
- tableName,
12724
- primaryKey,
12725
- value
12726
- };
12727
- return zeroCRUD({ ops: [op] });
12728
- },
12729
- upsert: (value) => {
12730
- assertNotInBatch(tableName, "upsert");
12731
- const op = {
12732
- op: "upsert",
12733
- tableName,
12734
- primaryKey,
12735
- value
12736
- };
12737
- return zeroCRUD({ ops: [op] });
12738
- },
12739
- update: (value) => {
12740
- assertNotInBatch(tableName, "update");
12741
- const op = {
12742
- op: "update",
12743
- tableName,
12744
- primaryKey,
12745
- value
12746
- };
12747
- return zeroCRUD({ ops: [op] });
12748
- },
12749
- delete: (id) => {
12750
- assertNotInBatch(tableName, "delete");
12751
- const op = {
12752
- op: "delete",
12753
- tableName,
12754
- primaryKey,
12755
- value: id
12756
- };
12757
- return zeroCRUD({ ops: [op] });
12740
+ // For unit testing that we correctly clean up indexes.
12741
+ getIndexKeys() {
12742
+ return [...this.#indexes.keys()];
12743
+ }
12744
+ *#fetch(req, from) {
12745
+ let overlay;
12746
+ const callingConnectionNum = this.#connections.indexOf(from);
12747
+ assert(callingConnectionNum !== -1, "Output not found");
12748
+ const conn = this.#connections[callingConnectionNum];
12749
+ const { sort: requestedSort } = conn;
12750
+ const indexSort = [];
12751
+ if (req.constraint) {
12752
+ for (const key of Object.keys(req.constraint)) {
12753
+ indexSort.push([key, "asc"]);
12754
+ }
12758
12755
  }
12759
- };
12760
- }
12761
- function makeBatchCRUDMutate(tableName, schema, ops) {
12762
- const { primaryKey } = schema.tables[tableName];
12763
- return {
12764
- insert: (value) => {
12765
- const op = {
12766
- op: "insert",
12767
- tableName,
12768
- primaryKey,
12769
- value
12770
- };
12771
- ops.push(op);
12772
- return promiseVoid;
12773
- },
12774
- upsert: (value) => {
12775
- const op = {
12776
- op: "upsert",
12777
- tableName,
12778
- primaryKey,
12779
- value
12780
- };
12781
- ops.push(op);
12782
- return promiseVoid;
12783
- },
12784
- update: (value) => {
12785
- const op = {
12786
- op: "update",
12787
- tableName,
12788
- primaryKey,
12789
- value
12790
- };
12791
- ops.push(op);
12792
- return promiseVoid;
12793
- },
12794
- delete: (id) => {
12795
- const op = {
12796
- op: "delete",
12797
- tableName,
12798
- primaryKey,
12799
- value: id
12800
- };
12801
- ops.push(op);
12802
- return promiseVoid;
12756
+ if (this.#primaryKey.length > 1 || !req.constraint || !constraintMatchesPrimaryKey(req.constraint, this.#primaryKey)) {
12757
+ indexSort.push(...requestedSort);
12803
12758
  }
12804
- };
12805
- }
12806
- function makeCRUDMutator(schema) {
12807
- return async function zeroCRUDMutator(tx, crudArg) {
12808
- for (const op of crudArg.ops) {
12809
- switch (op.op) {
12810
- case "insert":
12811
- await insertImpl(tx, op, schema);
12812
- break;
12813
- case "upsert":
12814
- await upsertImpl(tx, op, schema);
12815
- break;
12816
- case "update":
12817
- await updateImpl(tx, op, schema);
12818
- break;
12819
- case "delete":
12820
- await deleteImpl(tx, op, schema);
12821
- break;
12759
+ const index = this.#getOrCreateIndex(indexSort, from);
12760
+ const { data, comparator: compare } = index;
12761
+ const comparator2 = (r1, r2) => compare(r1, r2) * (req.reverse ? -1 : 1);
12762
+ if (this.#overlay) {
12763
+ if (callingConnectionNum <= this.#overlay.outputIndex) {
12764
+ overlay = this.#overlay;
12822
12765
  }
12823
12766
  }
12824
- };
12825
- }
12826
- function defaultOptionalFieldsToNull(schema, value) {
12827
- let rv = value;
12828
- for (const name in schema.columns) {
12829
- if (rv[name] === void 0) {
12830
- rv = { ...rv, [name]: null };
12767
+ const startAt = req.start?.row;
12768
+ let scanStart;
12769
+ if (req.constraint) {
12770
+ scanStart = {};
12771
+ for (const [key, dir] of indexSort) {
12772
+ if (hasOwn(req.constraint, key)) {
12773
+ scanStart[key] = req.constraint[key];
12774
+ } else {
12775
+ if (req.reverse) {
12776
+ scanStart[key] = dir === "asc" ? maxValue : minValue;
12777
+ } else {
12778
+ scanStart[key] = dir === "asc" ? minValue : maxValue;
12779
+ }
12780
+ }
12781
+ }
12782
+ } else {
12783
+ scanStart = startAt;
12831
12784
  }
12832
- }
12833
- return rv;
12834
- }
12835
- async function insertImpl(tx, arg, schema) {
12836
- const key = toPrimaryKeyString(
12837
- arg.tableName,
12838
- schema.tables[arg.tableName].primaryKey,
12839
- arg.value
12840
- );
12841
- if (!await tx.has(key)) {
12842
- const val = defaultOptionalFieldsToNull(
12843
- schema.tables[arg.tableName],
12844
- arg.value
12785
+ const withOverlay = generateWithOverlay(
12786
+ startAt,
12787
+ generateRows(data, scanStart, req.reverse),
12788
+ req.constraint,
12789
+ overlay,
12790
+ comparator2,
12791
+ conn.filters?.predicate
12845
12792
  );
12846
- await tx.set(key, val);
12793
+ const withConstraint = generateWithConstraint(
12794
+ generateWithStart(withOverlay, req.start, comparator2),
12795
+ req.constraint
12796
+ );
12797
+ yield* conn.filters ? generateWithFilter(withConstraint, conn.filters.predicate) : withConstraint;
12847
12798
  }
12848
- }
12849
- async function upsertImpl(tx, arg, schema) {
12850
- const key = toPrimaryKeyString(
12851
- arg.tableName,
12852
- schema.tables[arg.tableName].primaryKey,
12853
- arg.value
12854
- );
12855
- const val = defaultOptionalFieldsToNull(
12856
- schema.tables[arg.tableName],
12857
- arg.value
12858
- );
12859
- await tx.set(key, val);
12860
- }
12861
- async function updateImpl(tx, arg, schema) {
12862
- const key = toPrimaryKeyString(
12863
- arg.tableName,
12864
- schema.tables[arg.tableName].primaryKey,
12865
- arg.value
12866
- );
12867
- const prev = await tx.get(key);
12868
- if (prev === void 0) {
12869
- return;
12799
+ #cleanup(req, connection) {
12800
+ return this.#fetch(req, connection);
12870
12801
  }
12871
- const update = arg.value;
12872
- const next = { ...prev };
12873
- for (const k in update) {
12874
- if (update[k] !== void 0) {
12875
- next[k] = update[k];
12802
+ push(change) {
12803
+ for (const _ of this.genPush(change)) {
12876
12804
  }
12877
12805
  }
12878
- await tx.set(key, next);
12879
- }
12880
- async function deleteImpl(tx, arg, schema) {
12881
- const key = toPrimaryKeyString(
12882
- arg.tableName,
12883
- schema.tables[arg.tableName].primaryKey,
12884
- arg.value
12885
- );
12886
- await tx.del(key);
12887
- }
12888
-
12889
- // ../zero-client/src/client/custom.ts
12890
- var TransactionImpl = class {
12891
- constructor(repTx, schema, ivmSourceRepo) {
12892
- must(repTx.reason === "initial" || repTx.reason === "rebase");
12893
- this.clientID = repTx.clientID;
12894
- this.mutationID = repTx.mutationID;
12895
- this.reason = repTx.reason === "initial" ? "optimistic" : "rebase";
12896
- this.mutate = makeSchemaCRUD(schema, repTx);
12897
- this.query = makeSchemaQuery(
12898
- schema,
12899
- this.reason === "optimistic" ? ivmSourceRepo.main : (
12900
- // TODO: we don't initialize the sync branch yet.
12901
- // to fix in the next PR.
12902
- ivmSourceRepo.main
12903
- )
12904
- );
12806
+ *genPush(change) {
12807
+ const primaryIndex = this.#getPrimaryIndex();
12808
+ const { data } = primaryIndex;
12809
+ switch (change.type) {
12810
+ case "add":
12811
+ assert(
12812
+ !data.has(change.row),
12813
+ () => `Row already exists ${JSON.stringify(change)}`
12814
+ );
12815
+ break;
12816
+ case "remove":
12817
+ assert(
12818
+ data.has(change.row),
12819
+ () => `Row not found ${JSON.stringify(change)}`
12820
+ );
12821
+ break;
12822
+ case "edit":
12823
+ assert(
12824
+ data.has(change.oldRow),
12825
+ () => `Row not found ${JSON.stringify(change)}`
12826
+ );
12827
+ break;
12828
+ default:
12829
+ unreachable(change);
12830
+ }
12831
+ const outputChange = change.type === "edit" ? {
12832
+ type: change.type,
12833
+ oldNode: {
12834
+ row: change.oldRow,
12835
+ relationships: {}
12836
+ },
12837
+ node: {
12838
+ row: change.row,
12839
+ relationships: {}
12840
+ }
12841
+ } : {
12842
+ type: change.type,
12843
+ node: {
12844
+ row: change.row,
12845
+ relationships: {}
12846
+ }
12847
+ };
12848
+ for (const [
12849
+ outputIndex,
12850
+ { output, filters }
12851
+ ] of this.#connections.entries()) {
12852
+ if (output) {
12853
+ this.#overlay = { outputIndex, change };
12854
+ filterPush(outputChange, output, filters?.predicate);
12855
+ yield;
12856
+ }
12857
+ }
12858
+ this.#overlay = void 0;
12859
+ for (const { data: data2 } of this.#indexes.values()) {
12860
+ switch (change.type) {
12861
+ case "add": {
12862
+ const added = data2.add(change.row);
12863
+ assert(added);
12864
+ break;
12865
+ }
12866
+ case "remove": {
12867
+ const removed = data2.delete(change.row);
12868
+ assert(removed);
12869
+ break;
12870
+ }
12871
+ case "edit": {
12872
+ const removed = data2.delete(change.oldRow);
12873
+ assert(removed);
12874
+ data2.add(change.row);
12875
+ break;
12876
+ }
12877
+ default:
12878
+ unreachable(change);
12879
+ }
12880
+ }
12905
12881
  }
12906
- clientID;
12907
- mutationID;
12908
- reason;
12909
- mutate;
12910
- query;
12911
12882
  };
12912
- function makeReplicacheMutator(mutator, schema, ivmSourceRepo) {
12913
- return (repTx, args) => {
12914
- const tx = new TransactionImpl(repTx, schema, ivmSourceRepo);
12915
- return mutator(tx, args);
12916
- };
12883
+ function* generateWithConstraint(it, constraint) {
12884
+ for (const node of it) {
12885
+ if (constraint && !constraintMatchesRow(constraint, node.row)) {
12886
+ break;
12887
+ }
12888
+ yield node;
12889
+ }
12917
12890
  }
12918
- function makeSchemaQuery(schema, ivmBranch) {
12919
- const rv = {};
12920
- const context = new ZeroContext(
12921
- ivmBranch,
12922
- () => () => {
12923
- },
12924
- (applyViewUpdates) => applyViewUpdates()
12925
- );
12926
- for (const name of Object.keys(schema.tables)) {
12927
- rv[name] = newQuery(context, schema, name);
12891
+ function* generateWithFilter(it, filter) {
12892
+ for (const node of it) {
12893
+ if (filter(node.row)) {
12894
+ yield node;
12895
+ }
12928
12896
  }
12929
- return rv;
12930
12897
  }
12931
- function makeSchemaCRUD(schema, tx) {
12932
- const mutate = {};
12933
- for (const [name] of Object.entries(schema.tables)) {
12934
- mutate[name] = makeTableCRUD(schema, name, tx);
12898
+ function* generateWithStart(nodes, start, compare) {
12899
+ if (!start) {
12900
+ yield* nodes;
12901
+ return;
12902
+ }
12903
+ let started = false;
12904
+ for (const node of nodes) {
12905
+ if (!started) {
12906
+ if (start.basis === "at") {
12907
+ if (compare(node.row, start.row) >= 0) {
12908
+ started = true;
12909
+ }
12910
+ } else if (start.basis === "after") {
12911
+ if (compare(node.row, start.row) > 0) {
12912
+ started = true;
12913
+ }
12914
+ }
12915
+ }
12916
+ if (started) {
12917
+ yield node;
12918
+ }
12935
12919
  }
12936
- return mutate;
12937
12920
  }
12938
- function makeTableCRUD(schema, tableName, tx) {
12939
- const table2 = must(schema.tables[tableName]);
12940
- const { primaryKey } = table2;
12941
- return {
12942
- insert: (value) => insertImpl(tx, { op: "insert", tableName, primaryKey, value }, schema),
12943
- upsert: (value) => upsertImpl(tx, { op: "upsert", tableName, primaryKey, value }, schema),
12944
- update: (value) => updateImpl(tx, { op: "update", tableName, primaryKey, value }, schema),
12945
- delete: (id) => deleteImpl(tx, { op: "delete", tableName, primaryKey, value: id }, schema)
12946
- };
12921
+ function* generateWithOverlay(startAt, rows, constraint, overlay, compare, filterPredicate) {
12922
+ const overlays = computeOverlays(
12923
+ startAt,
12924
+ constraint,
12925
+ overlay,
12926
+ compare,
12927
+ filterPredicate
12928
+ );
12929
+ yield* generateWithOverlayInner(rows, overlays, compare);
12947
12930
  }
12948
-
12949
- // ../zero-client/src/client/enable-analytics.ts
12950
- var IPV4_ADDRESS_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
12951
- var IPV6_ADDRESS_HOSTNAME_REGEX = /^\[[a-fA-F0-9:]*:[a-fA-F0-9:]*\]$/;
12952
- var IP_ADDRESS_HOSTNAME_REGEX = new RegExp(
12953
- `(${IPV4_ADDRESS_REGEX.source}|${IPV6_ADDRESS_HOSTNAME_REGEX.source})`
12954
- );
12955
- function shouldEnableAnalytics(server, enableAnalytics = true) {
12956
- if (!enableAnalytics) {
12957
- return false;
12931
+ function computeOverlays(startAt, constraint, overlay, compare, filterPredicate) {
12932
+ let overlays = {
12933
+ add: void 0,
12934
+ remove: void 0
12935
+ };
12936
+ switch (overlay?.change.type) {
12937
+ case "add":
12938
+ overlays = {
12939
+ add: overlay.change.row,
12940
+ remove: void 0
12941
+ };
12942
+ break;
12943
+ case "remove":
12944
+ overlays = {
12945
+ add: void 0,
12946
+ remove: overlay.change.row
12947
+ };
12948
+ break;
12949
+ case "edit":
12950
+ overlays = {
12951
+ add: overlay.change.row,
12952
+ remove: overlay.change.oldRow
12953
+ };
12954
+ break;
12958
12955
  }
12959
- const serverURL = server === null ? null : new URL(server);
12960
- const socketHostname = serverURL?.hostname;
12961
- return server !== null && socketHostname !== void 0 && socketHostname !== "localhost" && !IP_ADDRESS_HOSTNAME_REGEX.test(socketHostname);
12956
+ if (startAt) {
12957
+ overlays = overlaysForStartAt(overlays, startAt, compare);
12958
+ }
12959
+ if (constraint) {
12960
+ overlays = overlaysForConstraint(overlays, constraint);
12961
+ }
12962
+ if (filterPredicate) {
12963
+ overlays = overlaysForFilterPredicate(overlays, filterPredicate);
12964
+ }
12965
+ return overlays;
12962
12966
  }
12963
-
12964
- // ../zero-client/src/client/http-string.ts
12965
- function toWSString(url) {
12966
- return "ws" + url.slice(4);
12967
+ function overlaysForStartAt({ add, remove }, startAt, compare) {
12968
+ const undefinedIfBeforeStartAt = (row) => row === void 0 || compare(row, startAt) < 0 ? void 0 : row;
12969
+ return {
12970
+ add: undefinedIfBeforeStartAt(add),
12971
+ remove: undefinedIfBeforeStartAt(remove)
12972
+ };
12967
12973
  }
12968
- function appendPath(url, toAppend) {
12969
- return url + (url.endsWith("/") ? toAppend.substring(1) : toAppend);
12974
+ function overlaysForConstraint({ add, remove }, constraint) {
12975
+ const undefinedIfDoesntMatchConstraint = (row) => row === void 0 || !constraintMatchesRow(constraint, row) ? void 0 : row;
12976
+ return {
12977
+ add: undefinedIfDoesntMatchConstraint(add),
12978
+ remove: undefinedIfDoesntMatchConstraint(remove)
12979
+ };
12970
12980
  }
12971
-
12972
- // ../zql/src/ivm/constraint.ts
12973
- function constraintMatchesRow(constraint, row) {
12974
- for (const key in constraint) {
12975
- if (!valuesEqual(row[key], constraint[key])) {
12976
- return false;
12981
+ function overlaysForFilterPredicate({ add, remove }, filterPredicate) {
12982
+ const undefinedIfDoesntMatchFilter = (row) => row === void 0 || !filterPredicate(row) ? void 0 : row;
12983
+ return {
12984
+ add: undefinedIfDoesntMatchFilter(add),
12985
+ remove: undefinedIfDoesntMatchFilter(remove)
12986
+ };
12987
+ }
12988
+ function* generateWithOverlayInner(rowIterator, overlays, compare) {
12989
+ let addOverlayYielded = false;
12990
+ let removeOverlaySkipped = false;
12991
+ for (const row of rowIterator) {
12992
+ if (!addOverlayYielded && overlays.add) {
12993
+ const cmp2 = compare(overlays.add, row);
12994
+ if (cmp2 < 0) {
12995
+ addOverlayYielded = true;
12996
+ yield { row: overlays.add, relationships: {} };
12997
+ }
12998
+ }
12999
+ if (!removeOverlaySkipped && overlays.remove) {
13000
+ const cmp2 = compare(overlays.remove, row);
13001
+ if (cmp2 === 0) {
13002
+ removeOverlaySkipped = true;
13003
+ continue;
13004
+ }
12977
13005
  }
13006
+ yield { row, relationships: {} };
12978
13007
  }
12979
- return true;
12980
- }
12981
- function constraintMatchesPrimaryKey(constraint, primary) {
12982
- const constraintKeys = Object.keys(constraint);
12983
- if (constraintKeys.length !== primary.length) {
12984
- return false;
13008
+ if (!addOverlayYielded && overlays.add) {
13009
+ yield { row: overlays.add, relationships: {} };
12985
13010
  }
12986
- constraintKeys.sort(stringCompare);
12987
- for (let i = 0; i < constraintKeys.length; i++) {
12988
- if (constraintKeys[i][0] !== primary[i]) {
12989
- return false;
13011
+ }
13012
+ var minValue = Symbol("min-value");
13013
+ var maxValue = Symbol("max-value");
13014
+ function makeBoundComparator(sort) {
13015
+ return (a, b) => {
13016
+ for (const entry of sort) {
13017
+ const key = entry[0];
13018
+ const cmp2 = compareBounds(a[key], b[key]);
13019
+ if (cmp2 !== 0) {
13020
+ return entry[1] === "asc" ? cmp2 : -cmp2;
13021
+ }
12990
13022
  }
12991
- }
12992
- return true;
13023
+ return 0;
13024
+ };
12993
13025
  }
12994
-
12995
- // ../zql/src/ivm/memory-source.ts
12996
- var MemorySource = class _MemorySource {
12997
- #tableName;
12998
- #columns;
12999
- #primaryKey;
13000
- #primaryIndexSort;
13001
- #indexes = /* @__PURE__ */ new Map();
13002
- #connections = [];
13003
- #overlay;
13004
- constructor(tableName, columns, primaryKey, primaryIndexData) {
13005
- this.#tableName = tableName;
13006
- this.#columns = columns;
13007
- this.#primaryKey = primaryKey;
13008
- this.#primaryIndexSort = primaryKey.map((k) => [k, "asc"]);
13009
- const comparator2 = makeBoundComparator(this.#primaryIndexSort);
13010
- this.#indexes.set(JSON.stringify(this.#primaryIndexSort), {
13011
- comparator: comparator2,
13012
- data: primaryIndexData ?? new BTreeSet(comparator2),
13013
- usedBy: /* @__PURE__ */ new Set()
13014
- });
13015
- assertOrderingIncludesPK(this.#primaryIndexSort, this.#primaryKey);
13016
- }
13017
- // Mainly for tests.
13018
- getSchemaInfo() {
13019
- return {
13020
- tableName: this.#tableName,
13021
- columns: this.#columns,
13022
- primaryKey: this.#primaryKey
13023
- };
13026
+ function compareBounds(a, b) {
13027
+ if (a === b) {
13028
+ return 0;
13024
13029
  }
13025
- fork() {
13026
- const primaryIndex = this.#getPrimaryIndex();
13027
- return new _MemorySource(
13028
- this.#tableName,
13029
- this.#columns,
13030
- this.#primaryKey,
13031
- primaryIndex.data.clone()
13032
- );
13030
+ if (a === minValue) {
13031
+ return -1;
13033
13032
  }
13034
- #getSchema(connection) {
13035
- return {
13036
- tableName: this.#tableName,
13037
- columns: this.#columns,
13038
- primaryKey: this.#primaryKey,
13039
- sort: connection.sort,
13040
- system: "client",
13041
- relationships: {},
13042
- isHidden: false,
13043
- compareRows: connection.compareRows
13044
- };
13033
+ if (b === minValue) {
13034
+ return 1;
13045
13035
  }
13046
- connect(sort, filters) {
13047
- const transformedFilters = transformFilters(filters);
13048
- const input = {
13049
- getSchema: () => schema,
13050
- fetch: (req) => this.#fetch(req, connection),
13051
- cleanup: (req) => this.#cleanup(req, connection),
13052
- setOutput: (output) => {
13053
- connection.output = output;
13054
- },
13055
- destroy: () => {
13056
- this.#disconnect(input);
13057
- },
13058
- fullyAppliedFilters: !transformedFilters.conditionsRemoved
13059
- };
13060
- const connection = {
13061
- input,
13062
- output: void 0,
13063
- sort,
13064
- compareRows: makeComparator(sort),
13065
- filters: transformedFilters.filters ? {
13066
- condition: transformedFilters.filters,
13067
- predicate: createPredicate(transformedFilters.filters)
13068
- } : void 0
13069
- };
13070
- const schema = this.#getSchema(connection);
13071
- assertOrderingIncludesPK(sort, this.#primaryKey);
13072
- this.#connections.push(connection);
13073
- return input;
13036
+ if (a === maxValue) {
13037
+ return 1;
13074
13038
  }
13075
- #disconnect(input) {
13076
- const idx = this.#connections.findIndex((c) => c.input === input);
13077
- assert(idx !== -1, "Connection not found");
13078
- const connection = this.#connections[idx];
13079
- this.#connections.splice(idx, 1);
13080
- const primaryIndexKey = JSON.stringify(this.#primaryIndexSort);
13081
- for (const [key, index] of this.#indexes) {
13082
- if (key === primaryIndexKey) {
13083
- continue;
13084
- }
13085
- index.usedBy.delete(connection);
13086
- if (index.usedBy.size === 0) {
13087
- this.#indexes.delete(key);
13088
- }
13089
- }
13039
+ if (b === maxValue) {
13040
+ return -1;
13090
13041
  }
13091
- #getPrimaryIndex() {
13092
- const index = this.#indexes.get(JSON.stringify(this.#primaryIndexSort));
13093
- assert(index, "Primary index not found");
13094
- return index;
13042
+ return compareValues(a, b);
13043
+ }
13044
+ function* generateRows(data, scanStart, reverse) {
13045
+ yield* data[reverse ? "valuesFromReversed" : "valuesFrom"](
13046
+ scanStart
13047
+ );
13048
+ }
13049
+
13050
+ // ../zero-client/src/client/keys.ts
13051
+ var CLIENTS_KEY_PREFIX = "c/";
13052
+ var DESIRED_QUERIES_KEY_PREFIX = "d/";
13053
+ var GOT_QUERIES_KEY_PREFIX = "g/";
13054
+ var ENTITIES_KEY_PREFIX = "e/";
13055
+ function toClientsKey(clientID) {
13056
+ return CLIENTS_KEY_PREFIX + clientID;
13057
+ }
13058
+ function toDesiredQueriesKey(clientID, hash2) {
13059
+ return DESIRED_QUERIES_KEY_PREFIX + clientID + "/" + hash2;
13060
+ }
13061
+ function desiredQueriesPrefixForClient(clientID) {
13062
+ return DESIRED_QUERIES_KEY_PREFIX + clientID + "/";
13063
+ }
13064
+ function toGotQueriesKey(hash2) {
13065
+ return GOT_QUERIES_KEY_PREFIX + hash2;
13066
+ }
13067
+ function toPrimaryKeyString(tableName, primaryKey, value) {
13068
+ if (primaryKey.length === 1) {
13069
+ return ENTITIES_KEY_PREFIX + tableName + "/" + parse(value[primaryKey[0]], primaryKeyValueSchema);
13095
13070
  }
13096
- #getOrCreateIndex(sort, usedBy) {
13097
- const key = JSON.stringify(sort);
13098
- const index = this.#indexes.get(key);
13099
- if (index) {
13100
- index.usedBy.add(usedBy);
13101
- return index;
13102
- }
13103
- const comparator2 = makeBoundComparator(sort);
13104
- const data = new BTreeSet(comparator2);
13105
- for (const row of this.#getPrimaryIndex().data) {
13106
- data.add(row);
13107
- }
13108
- const newIndex = { comparator: comparator2, data, usedBy: /* @__PURE__ */ new Set([usedBy]) };
13109
- this.#indexes.set(key, newIndex);
13110
- return newIndex;
13071
+ const values = primaryKey.map((k) => parse(value[k], primaryKeyValueSchema));
13072
+ const str = JSON.stringify(values);
13073
+ const idSegment = h128(str);
13074
+ return ENTITIES_KEY_PREFIX + tableName + "/" + idSegment;
13075
+ }
13076
+ function sourceNameFromKey(key) {
13077
+ const slash = key.indexOf("/", ENTITIES_KEY_PREFIX.length);
13078
+ return key.slice(ENTITIES_KEY_PREFIX.length, slash);
13079
+ }
13080
+
13081
+ // ../zero-client/src/client/ivm-source-repo.ts
13082
+ var IVMSourceRepo = class {
13083
+ #main;
13084
+ #tables;
13085
+ /**
13086
+ * Sync is lazily created when the first response from the server is received.
13087
+ */
13088
+ #sync;
13089
+ /**
13090
+ * Rebase is created when the sync head is advanced and points to a fork
13091
+ * of the sync head. This is used to rebase optimistic mutations.
13092
+ */
13093
+ #rebase;
13094
+ constructor(tables) {
13095
+ this.#main = new IVMSourceBranch(tables);
13096
+ this.#tables = tables;
13111
13097
  }
13112
- // For unit testing that we correctly clean up indexes.
13113
- getIndexKeys() {
13114
- return [...this.#indexes.keys()];
13098
+ get main() {
13099
+ return this.#main;
13115
13100
  }
13116
- *#fetch(req, from) {
13117
- let overlay;
13118
- const callingConnectionNum = this.#connections.indexOf(from);
13119
- assert(callingConnectionNum !== -1, "Output not found");
13120
- const conn = this.#connections[callingConnectionNum];
13121
- const { sort: requestedSort } = conn;
13122
- const indexSort = [];
13123
- if (req.constraint) {
13124
- for (const key of Object.keys(req.constraint)) {
13125
- indexSort.push([key, "asc"]);
13126
- }
13127
- }
13128
- if (this.#primaryKey.length > 1 || !req.constraint || !constraintMatchesPrimaryKey(req.constraint, this.#primaryKey)) {
13129
- indexSort.push(...requestedSort);
13130
- }
13131
- const index = this.#getOrCreateIndex(indexSort, from);
13132
- const { data, comparator: compare } = index;
13133
- const comparator2 = (r1, r2) => compare(r1, r2) * (req.reverse ? -1 : 1);
13134
- if (this.#overlay) {
13135
- if (callingConnectionNum <= this.#overlay.outputIndex) {
13136
- overlay = this.#overlay;
13137
- }
13138
- }
13139
- const startAt = req.start?.row;
13140
- let scanStart;
13141
- if (req.constraint) {
13142
- scanStart = {};
13143
- for (const [key, dir] of indexSort) {
13144
- if (hasOwn(req.constraint, key)) {
13145
- scanStart[key] = req.constraint[key];
13146
- } else {
13147
- if (req.reverse) {
13148
- scanStart[key] = dir === "asc" ? maxValue : minValue;
13149
- } else {
13150
- scanStart[key] = dir === "asc" ? minValue : maxValue;
13101
+ /**
13102
+ * Used for reads in `zero.TransactionImpl`.
13103
+ * Writes in `zero.TransactionImpl` also get applied to the rebase branch.
13104
+ *
13105
+ * The rebase branch is always forked off of the sync branch when a rebase begins.
13106
+ */
13107
+ get rebase() {
13108
+ return must(this.#rebase, "rebase branch does not exist!");
13109
+ }
13110
+ advanceSyncHead = async (store, syncHeadHash, patches) => {
13111
+ if (this.#sync === void 0) {
13112
+ await withRead(store, async (dagRead) => {
13113
+ const syncSources = new IVMSourceBranch(this.#tables);
13114
+ const read = await readFromHash(
13115
+ syncHeadHash,
13116
+ dagRead,
13117
+ Latest
13118
+ );
13119
+ for await (const entry of read.map.scan(ENTITIES_KEY_PREFIX)) {
13120
+ if (!entry[0].startsWith(ENTITIES_KEY_PREFIX)) {
13121
+ break;
13151
13122
  }
13123
+ const name = sourceNameFromKey(entry[0]);
13124
+ const source = must(syncSources.getSource(name));
13125
+ source.push({
13126
+ type: "add",
13127
+ row: entry[1]
13128
+ });
13152
13129
  }
13153
- }
13130
+ this.#sync = syncSources;
13131
+ });
13154
13132
  } else {
13155
- scanStart = startAt;
13133
+ for (const patch of patches) {
13134
+ if (patch.op === "clear") {
13135
+ this.#sync.clear();
13136
+ continue;
13137
+ }
13138
+ const { key } = patch;
13139
+ if (!key.startsWith(ENTITIES_KEY_PREFIX)) {
13140
+ continue;
13141
+ }
13142
+ const name = sourceNameFromKey(key);
13143
+ const source = must(this.#sync.getSource(name));
13144
+ switch (patch.op) {
13145
+ case "del":
13146
+ source.push({
13147
+ type: "remove",
13148
+ row: patch.oldValue
13149
+ });
13150
+ break;
13151
+ case "add":
13152
+ source.push({
13153
+ type: "add",
13154
+ row: patch.newValue
13155
+ });
13156
+ break;
13157
+ case "change":
13158
+ source.push({
13159
+ type: "edit",
13160
+ row: patch.newValue,
13161
+ oldRow: patch.oldValue
13162
+ });
13163
+ break;
13164
+ }
13165
+ }
13156
13166
  }
13157
- const withOverlay = generateWithOverlay(
13158
- startAt,
13159
- generateRows(data, scanStart, req.reverse),
13160
- req.constraint,
13161
- overlay,
13162
- comparator2,
13163
- conn.filters?.predicate
13164
- );
13165
- const withConstraint = generateWithConstraint(
13166
- generateWithStart(withOverlay, req.start, comparator2),
13167
- req.constraint
13167
+ this.#rebase = must(this.#sync).fork();
13168
+ };
13169
+ };
13170
+ var IVMSourceBranch = class _IVMSourceBranch {
13171
+ #sources;
13172
+ #tables;
13173
+ constructor(tables, sources = /* @__PURE__ */ new Map()) {
13174
+ this.#tables = tables;
13175
+ this.#sources = sources;
13176
+ }
13177
+ getSource(name) {
13178
+ if (this.#sources.has(name)) {
13179
+ return this.#sources.get(name);
13180
+ }
13181
+ const schema = this.#tables[name];
13182
+ const source = schema ? new MemorySource(name, schema.columns, schema.primaryKey) : void 0;
13183
+ this.#sources.set(name, source);
13184
+ return source;
13185
+ }
13186
+ clear() {
13187
+ this.#sources.clear();
13188
+ }
13189
+ /**
13190
+ * Creates a new IVMSourceBranch that is a copy of the current one.
13191
+ * This is a cheap operation since the b-trees are shared until a write is performed
13192
+ * and then only the modified nodes are copied.
13193
+ *
13194
+ * This is used when:
13195
+ * 1. We need to rebase a change. We fork the `sync` branch and run the mutations against the fork.
13196
+ * 2. We need to create `main` at startup.
13197
+ * 3. We need to create a new `sync` head because we got a new server snapshot.
13198
+ * The old `sync` head is forked and the new server snapshot is applied to the fork.
13199
+ */
13200
+ fork() {
13201
+ return new _IVMSourceBranch(
13202
+ this.#tables,
13203
+ new Map(
13204
+ wrapIterable(this.#sources.entries()).map(([name, source]) => [
13205
+ name,
13206
+ source?.fork()
13207
+ ])
13208
+ )
13168
13209
  );
13169
- yield* conn.filters ? generateWithFilter(withConstraint, conn.filters.predicate) : withConstraint;
13170
13210
  }
13171
- #cleanup(req, connection) {
13172
- return this.#fetch(req, connection);
13211
+ };
13212
+
13213
+ // ../zero-client/src/client/context.ts
13214
+ var ZeroContext = class {
13215
+ // It is a bummer to have to maintain separate MemorySources here and copy the
13216
+ // data in from the Replicache db. But we want the data to be accessible via
13217
+ // pipelines *synchronously* and the core Replicache infra is all async. So
13218
+ // that needs to be fixed.
13219
+ #mainSources;
13220
+ #addQuery;
13221
+ #batchViewUpdates;
13222
+ #commitListeners = /* @__PURE__ */ new Set();
13223
+ staticQueryParameters = void 0;
13224
+ constructor(mainSources, addQuery, batchViewUpdates) {
13225
+ this.#mainSources = mainSources;
13226
+ this.#addQuery = addQuery;
13227
+ this.#batchViewUpdates = batchViewUpdates;
13173
13228
  }
13174
- push(change) {
13175
- for (const _ of this.genPush(change)) {
13176
- }
13229
+ getSource(name) {
13230
+ return this.#mainSources.getSource(name);
13177
13231
  }
13178
- *genPush(change) {
13179
- const primaryIndex = this.#getPrimaryIndex();
13180
- const { data } = primaryIndex;
13181
- switch (change.type) {
13182
- case "add":
13183
- assert(
13184
- !data.has(change.row),
13185
- () => `Row already exists ${JSON.stringify(change)}`
13186
- );
13187
- break;
13188
- case "remove":
13189
- assert(
13190
- data.has(change.row),
13191
- () => `Row not found ${JSON.stringify(change)}`
13192
- );
13193
- break;
13194
- case "edit":
13195
- assert(
13196
- data.has(change.oldRow),
13197
- () => `Row not found ${JSON.stringify(change)}`
13198
- );
13199
- break;
13200
- default:
13201
- unreachable(change);
13202
- }
13203
- const outputChange = change.type === "edit" ? {
13204
- type: change.type,
13205
- oldNode: {
13206
- row: change.oldRow,
13207
- relationships: {}
13208
- },
13209
- node: {
13210
- row: change.row,
13211
- relationships: {}
13212
- }
13213
- } : {
13214
- type: change.type,
13215
- node: {
13216
- row: change.row,
13217
- relationships: {}
13218
- }
13219
- };
13220
- for (const [
13221
- outputIndex,
13222
- { output, filters }
13223
- ] of this.#connections.entries()) {
13224
- if (output) {
13225
- this.#overlay = { outputIndex, change };
13226
- filterPush(outputChange, output, filters?.predicate);
13227
- yield;
13228
- }
13229
- }
13230
- this.#overlay = void 0;
13231
- for (const { data: data2 } of this.#indexes.values()) {
13232
- switch (change.type) {
13233
- case "add": {
13234
- const added = data2.add(change.row);
13235
- assert(added);
13236
- break;
13237
- }
13238
- case "remove": {
13239
- const removed = data2.delete(change.row);
13240
- assert(removed);
13241
- break;
13242
- }
13243
- case "edit": {
13244
- const removed = data2.delete(change.oldRow);
13245
- assert(removed);
13246
- data2.add(change.row);
13247
- break;
13232
+ addServerQuery(ast, gotCallback) {
13233
+ return this.#addQuery(ast, gotCallback);
13234
+ }
13235
+ createStorage() {
13236
+ return new MemoryStorage();
13237
+ }
13238
+ onTransactionCommit(cb) {
13239
+ this.#commitListeners.add(cb);
13240
+ return () => {
13241
+ this.#commitListeners.delete(cb);
13242
+ };
13243
+ }
13244
+ batchViewUpdates(applyViewUpdates) {
13245
+ let result;
13246
+ let viewChangesPerformed = false;
13247
+ this.#batchViewUpdates(() => {
13248
+ result = applyViewUpdates();
13249
+ viewChangesPerformed = true;
13250
+ });
13251
+ assert(
13252
+ viewChangesPerformed,
13253
+ "batchViewUpdates must call applyViewUpdates synchronously."
13254
+ );
13255
+ return result;
13256
+ }
13257
+ processChanges(changes) {
13258
+ this.batchViewUpdates(() => {
13259
+ try {
13260
+ for (const diff2 of changes) {
13261
+ const { key } = diff2;
13262
+ assert(key.startsWith(ENTITIES_KEY_PREFIX));
13263
+ const name = sourceNameFromKey(key);
13264
+ const source = this.getSource(name);
13265
+ if (!source) {
13266
+ continue;
13267
+ }
13268
+ switch (diff2.op) {
13269
+ case "del":
13270
+ assert(typeof diff2.oldValue === "object");
13271
+ source.push({
13272
+ type: "remove",
13273
+ row: diff2.oldValue
13274
+ });
13275
+ break;
13276
+ case "add":
13277
+ assert(typeof diff2.newValue === "object");
13278
+ source.push({
13279
+ type: "add",
13280
+ row: diff2.newValue
13281
+ });
13282
+ break;
13283
+ case "change":
13284
+ assert(typeof diff2.newValue === "object");
13285
+ assert(typeof diff2.oldValue === "object");
13286
+ source.push({
13287
+ type: "edit",
13288
+ row: diff2.newValue,
13289
+ oldRow: diff2.oldValue
13290
+ });
13291
+ break;
13292
+ default:
13293
+ unreachable(diff2);
13294
+ }
13248
13295
  }
13249
- default:
13250
- unreachable(change);
13296
+ } finally {
13297
+ this.#endTransaction();
13251
13298
  }
13252
- }
13299
+ });
13253
13300
  }
13254
- };
13255
- function* generateWithConstraint(it, constraint) {
13256
- for (const node of it) {
13257
- if (constraint && !constraintMatchesRow(constraint, node.row)) {
13258
- break;
13301
+ #endTransaction() {
13302
+ for (const listener of this.#commitListeners) {
13303
+ listener();
13259
13304
  }
13260
- yield node;
13261
13305
  }
13262
- }
13263
- function* generateWithFilter(it, filter) {
13264
- for (const node of it) {
13265
- if (filter(node.row)) {
13266
- yield node;
13306
+ };
13307
+
13308
+ // ../zero-client/src/client/crud.ts
13309
+ function makeCRUDMutate(schema, repMutate) {
13310
+ const { [CRUD_MUTATION_NAME]: zeroCRUD } = repMutate;
13311
+ let inBatch = false;
13312
+ const mutateBatch = async (body) => {
13313
+ if (inBatch) {
13314
+ throw new Error("Cannot call mutate inside a batch");
13267
13315
  }
13268
- }
13269
- }
13270
- function* generateWithStart(nodes, start, compare) {
13271
- if (!start) {
13272
- yield* nodes;
13273
- return;
13274
- }
13275
- let started = false;
13276
- for (const node of nodes) {
13277
- if (!started) {
13278
- if (start.basis === "at") {
13279
- if (compare(node.row, start.row) >= 0) {
13280
- started = true;
13281
- }
13282
- } else if (start.basis === "after") {
13283
- if (compare(node.row, start.row) > 0) {
13284
- started = true;
13285
- }
13316
+ inBatch = true;
13317
+ try {
13318
+ const ops = [];
13319
+ const m = {};
13320
+ for (const name of Object.keys(schema.tables)) {
13321
+ m[name] = makeBatchCRUDMutate(name, schema, ops);
13286
13322
  }
13323
+ const rv = await body(m);
13324
+ await zeroCRUD({ ops });
13325
+ return rv;
13326
+ } finally {
13327
+ inBatch = false;
13287
13328
  }
13288
- if (started) {
13289
- yield node;
13329
+ };
13330
+ const assertNotInBatch = (tableName, op) => {
13331
+ if (inBatch) {
13332
+ throw new Error(`Cannot call mutate.${tableName}.${op} inside a batch`);
13290
13333
  }
13291
- }
13292
- }
13293
- function* generateWithOverlay(startAt, rows, constraint, overlay, compare, filterPredicate) {
13294
- const overlays = computeOverlays(
13295
- startAt,
13296
- constraint,
13297
- overlay,
13298
- compare,
13299
- filterPredicate
13300
- );
13301
- yield* generateWithOverlayInner(rows, overlays, compare);
13302
- }
13303
- function computeOverlays(startAt, constraint, overlay, compare, filterPredicate) {
13304
- let overlays = {
13305
- add: void 0,
13306
- remove: void 0
13307
13334
  };
13308
- switch (overlay?.change.type) {
13309
- case "add":
13310
- overlays = {
13311
- add: overlay.change.row,
13312
- remove: void 0
13313
- };
13314
- break;
13315
- case "remove":
13316
- overlays = {
13317
- add: void 0,
13318
- remove: overlay.change.row
13319
- };
13320
- break;
13321
- case "edit":
13322
- overlays = {
13323
- add: overlay.change.row,
13324
- remove: overlay.change.oldRow
13325
- };
13326
- break;
13327
- }
13328
- if (startAt) {
13329
- overlays = overlaysForStartAt(overlays, startAt, compare);
13330
- }
13331
- if (constraint) {
13332
- overlays = overlaysForConstraint(overlays, constraint);
13333
- }
13334
- if (filterPredicate) {
13335
- overlays = overlaysForFilterPredicate(overlays, filterPredicate);
13335
+ const mutate = {};
13336
+ for (const [name, tableSchema] of Object.entries(schema.tables)) {
13337
+ mutate[name] = makeEntityCRUDMutate(
13338
+ name,
13339
+ tableSchema.primaryKey,
13340
+ zeroCRUD,
13341
+ assertNotInBatch
13342
+ );
13336
13343
  }
13337
- return overlays;
13338
- }
13339
- function overlaysForStartAt({ add, remove }, startAt, compare) {
13340
- const undefinedIfBeforeStartAt = (row) => row === void 0 || compare(row, startAt) < 0 ? void 0 : row;
13341
13344
  return {
13342
- add: undefinedIfBeforeStartAt(add),
13343
- remove: undefinedIfBeforeStartAt(remove)
13345
+ mutate,
13346
+ mutateBatch
13344
13347
  };
13345
13348
  }
13346
- function overlaysForConstraint({ add, remove }, constraint) {
13347
- const undefinedIfDoesntMatchConstraint = (row) => row === void 0 || !constraintMatchesRow(constraint, row) ? void 0 : row;
13349
+ function makeEntityCRUDMutate(tableName, primaryKey, zeroCRUD, assertNotInBatch) {
13348
13350
  return {
13349
- add: undefinedIfDoesntMatchConstraint(add),
13350
- remove: undefinedIfDoesntMatchConstraint(remove)
13351
+ insert: (value) => {
13352
+ assertNotInBatch(tableName, "insert");
13353
+ const op = {
13354
+ op: "insert",
13355
+ tableName,
13356
+ primaryKey,
13357
+ value
13358
+ };
13359
+ return zeroCRUD({ ops: [op] });
13360
+ },
13361
+ upsert: (value) => {
13362
+ assertNotInBatch(tableName, "upsert");
13363
+ const op = {
13364
+ op: "upsert",
13365
+ tableName,
13366
+ primaryKey,
13367
+ value
13368
+ };
13369
+ return zeroCRUD({ ops: [op] });
13370
+ },
13371
+ update: (value) => {
13372
+ assertNotInBatch(tableName, "update");
13373
+ const op = {
13374
+ op: "update",
13375
+ tableName,
13376
+ primaryKey,
13377
+ value
13378
+ };
13379
+ return zeroCRUD({ ops: [op] });
13380
+ },
13381
+ delete: (id) => {
13382
+ assertNotInBatch(tableName, "delete");
13383
+ const op = {
13384
+ op: "delete",
13385
+ tableName,
13386
+ primaryKey,
13387
+ value: id
13388
+ };
13389
+ return zeroCRUD({ ops: [op] });
13390
+ }
13351
13391
  };
13352
13392
  }
13353
- function overlaysForFilterPredicate({ add, remove }, filterPredicate) {
13354
- const undefinedIfDoesntMatchFilter = (row) => row === void 0 || !filterPredicate(row) ? void 0 : row;
13393
+ function makeBatchCRUDMutate(tableName, schema, ops) {
13394
+ const { primaryKey } = schema.tables[tableName];
13355
13395
  return {
13356
- add: undefinedIfDoesntMatchFilter(add),
13357
- remove: undefinedIfDoesntMatchFilter(remove)
13396
+ insert: (value) => {
13397
+ const op = {
13398
+ op: "insert",
13399
+ tableName,
13400
+ primaryKey,
13401
+ value
13402
+ };
13403
+ ops.push(op);
13404
+ return promiseVoid;
13405
+ },
13406
+ upsert: (value) => {
13407
+ const op = {
13408
+ op: "upsert",
13409
+ tableName,
13410
+ primaryKey,
13411
+ value
13412
+ };
13413
+ ops.push(op);
13414
+ return promiseVoid;
13415
+ },
13416
+ update: (value) => {
13417
+ const op = {
13418
+ op: "update",
13419
+ tableName,
13420
+ primaryKey,
13421
+ value
13422
+ };
13423
+ ops.push(op);
13424
+ return promiseVoid;
13425
+ },
13426
+ delete: (id) => {
13427
+ const op = {
13428
+ op: "delete",
13429
+ tableName,
13430
+ primaryKey,
13431
+ value: id
13432
+ };
13433
+ ops.push(op);
13434
+ return promiseVoid;
13435
+ }
13358
13436
  };
13359
13437
  }
13360
- function* generateWithOverlayInner(rowIterator, overlays, compare) {
13361
- let addOverlayYielded = false;
13362
- let removeOverlaySkipped = false;
13363
- for (const row of rowIterator) {
13364
- if (!addOverlayYielded && overlays.add) {
13365
- const cmp2 = compare(overlays.add, row);
13366
- if (cmp2 < 0) {
13367
- addOverlayYielded = true;
13368
- yield { row: overlays.add, relationships: {} };
13438
+ function makeCRUDMutator(schema) {
13439
+ return async function zeroCRUDMutator(tx, crudArg) {
13440
+ for (const op of crudArg.ops) {
13441
+ switch (op.op) {
13442
+ case "insert":
13443
+ await insertImpl(tx, op, schema, void 0);
13444
+ break;
13445
+ case "upsert":
13446
+ await upsertImpl(tx, op, schema, void 0);
13447
+ break;
13448
+ case "update":
13449
+ await updateImpl(tx, op, schema, void 0);
13450
+ break;
13451
+ case "delete":
13452
+ await deleteImpl(tx, op, schema, void 0);
13453
+ break;
13369
13454
  }
13370
13455
  }
13371
- if (!removeOverlaySkipped && overlays.remove) {
13372
- const cmp2 = compare(overlays.remove, row);
13373
- if (cmp2 === 0) {
13374
- removeOverlaySkipped = true;
13375
- continue;
13376
- }
13456
+ };
13457
+ }
13458
+ function defaultOptionalFieldsToNull(schema, value) {
13459
+ let rv = value;
13460
+ for (const name in schema.columns) {
13461
+ if (rv[name] === void 0) {
13462
+ rv = { ...rv, [name]: null };
13377
13463
  }
13378
- yield { row, relationships: {} };
13379
- }
13380
- if (!addOverlayYielded && overlays.add) {
13381
- yield { row: overlays.add, relationships: {} };
13382
13464
  }
13465
+ return rv;
13383
13466
  }
13384
- var minValue = Symbol("min-value");
13385
- var maxValue = Symbol("max-value");
13386
- function makeBoundComparator(sort) {
13387
- return (a, b) => {
13388
- for (const entry of sort) {
13389
- const key = entry[0];
13390
- const cmp2 = compareBounds(a[key], b[key]);
13391
- if (cmp2 !== 0) {
13392
- return entry[1] === "asc" ? cmp2 : -cmp2;
13393
- }
13467
+ async function insertImpl(tx, arg, schema, ivmBranch) {
13468
+ const key = toPrimaryKeyString(
13469
+ arg.tableName,
13470
+ schema.tables[arg.tableName].primaryKey,
13471
+ arg.value
13472
+ );
13473
+ if (!await tx.has(key)) {
13474
+ const val = defaultOptionalFieldsToNull(
13475
+ schema.tables[arg.tableName],
13476
+ arg.value
13477
+ );
13478
+ await tx.set(key, val);
13479
+ if (ivmBranch) {
13480
+ must(ivmBranch.getSource(arg.tableName)).push({
13481
+ type: "add",
13482
+ row: arg.value
13483
+ });
13394
13484
  }
13395
- return 0;
13396
- };
13397
- }
13398
- function compareBounds(a, b) {
13399
- if (a === b) {
13400
- return 0;
13401
13485
  }
13402
- if (a === minValue) {
13403
- return -1;
13486
+ }
13487
+ async function upsertImpl(tx, arg, schema, ivmBranch) {
13488
+ const key = toPrimaryKeyString(
13489
+ arg.tableName,
13490
+ schema.tables[arg.tableName].primaryKey,
13491
+ arg.value
13492
+ );
13493
+ const val = defaultOptionalFieldsToNull(
13494
+ schema.tables[arg.tableName],
13495
+ arg.value
13496
+ );
13497
+ await tx.set(key, val);
13498
+ if (ivmBranch) {
13499
+ must(ivmBranch.getSource(arg.tableName)).push({
13500
+ type: "add",
13501
+ row: arg.value
13502
+ });
13404
13503
  }
13405
- if (b === minValue) {
13406
- return 1;
13504
+ }
13505
+ async function updateImpl(tx, arg, schema, ivmBranch) {
13506
+ const key = toPrimaryKeyString(
13507
+ arg.tableName,
13508
+ schema.tables[arg.tableName].primaryKey,
13509
+ arg.value
13510
+ );
13511
+ const prev = await tx.get(key);
13512
+ if (prev === void 0) {
13513
+ return;
13407
13514
  }
13408
- if (a === maxValue) {
13409
- return 1;
13515
+ const update = arg.value;
13516
+ const next = { ...prev };
13517
+ for (const k in update) {
13518
+ if (update[k] !== void 0) {
13519
+ next[k] = update[k];
13520
+ }
13410
13521
  }
13411
- if (b === maxValue) {
13412
- return -1;
13522
+ await tx.set(key, next);
13523
+ if (ivmBranch) {
13524
+ must(ivmBranch.getSource(arg.tableName)).push({
13525
+ type: "edit",
13526
+ oldRow: prev,
13527
+ row: next
13528
+ });
13413
13529
  }
13414
- return compareValues(a, b);
13415
13530
  }
13416
- function* generateRows(data, scanStart, reverse) {
13417
- yield* data[reverse ? "valuesFromReversed" : "valuesFrom"](
13418
- scanStart
13531
+ async function deleteImpl(tx, arg, schema, ivmBranch) {
13532
+ const key = toPrimaryKeyString(
13533
+ arg.tableName,
13534
+ schema.tables[arg.tableName].primaryKey,
13535
+ arg.value
13419
13536
  );
13420
- }
13421
-
13422
- // ../zero-client/src/client/ivm-source-repo.ts
13423
- var IVMSourceRepo = class {
13424
- #main;
13425
- #tables;
13426
- sync;
13427
- constructor(tables) {
13428
- this.#main = new IVMSourceBranch(tables);
13429
- this.#tables = tables;
13537
+ const prev = await tx.get(key);
13538
+ if (prev === void 0) {
13539
+ return;
13430
13540
  }
13431
- get main() {
13432
- return this.#main;
13541
+ await tx.del(key);
13542
+ if (ivmBranch) {
13543
+ must(ivmBranch.getSource(arg.tableName)).push({
13544
+ type: "remove",
13545
+ row: prev
13546
+ });
13433
13547
  }
13434
- /**
13435
- * Creates a new empty branch.
13436
- */
13437
- newBranch() {
13438
- return new IVMSourceBranch(this.#tables);
13548
+ }
13549
+
13550
+ // ../zero-client/src/client/custom.ts
13551
+ var TransactionImpl = class {
13552
+ constructor(repTx, schema, ivmSourceRepo) {
13553
+ must(repTx.reason === "initial" || repTx.reason === "rebase");
13554
+ this.clientID = repTx.clientID;
13555
+ this.mutationID = repTx.mutationID;
13556
+ this.reason = repTx.reason === "initial" ? "optimistic" : "rebase";
13557
+ this.mutate = makeSchemaCRUD(
13558
+ schema,
13559
+ repTx,
13560
+ // Mutators do not write to the main IVM sources during optimistic mutations
13561
+ // so we pass undefined here.
13562
+ // ExperimentalWatch handles updating main.
13563
+ this.reason === "optimistic" ? void 0 : ivmSourceRepo.rebase
13564
+ );
13565
+ this.query = makeSchemaQuery(
13566
+ schema,
13567
+ this.reason === "optimistic" ? ivmSourceRepo.main : ivmSourceRepo.rebase
13568
+ );
13439
13569
  }
13570
+ clientID;
13571
+ mutationID;
13572
+ reason;
13573
+ mutate;
13574
+ query;
13440
13575
  };
13441
- var IVMSourceBranch = class _IVMSourceBranch {
13442
- #sources;
13443
- #tables;
13444
- constructor(tables, sources = /* @__PURE__ */ new Map()) {
13445
- this.#tables = tables;
13446
- this.#sources = sources;
13576
+ function makeReplicacheMutator(mutator, schema, ivmSourceRepo) {
13577
+ return (repTx, args) => {
13578
+ const tx = new TransactionImpl(repTx, schema, ivmSourceRepo);
13579
+ return mutator(tx, args);
13580
+ };
13581
+ }
13582
+ function makeSchemaQuery(schema, ivmBranch) {
13583
+ const rv = {};
13584
+ const context = new ZeroContext(
13585
+ ivmBranch,
13586
+ () => () => {
13587
+ },
13588
+ (applyViewUpdates) => applyViewUpdates()
13589
+ );
13590
+ for (const name of Object.keys(schema.tables)) {
13591
+ rv[name] = newQuery(context, schema, name);
13447
13592
  }
13448
- getSource(name) {
13449
- if (this.#sources.has(name)) {
13450
- return this.#sources.get(name);
13451
- }
13452
- const schema = this.#tables[name];
13453
- const source = schema ? new MemorySource(name, schema.columns, schema.primaryKey) : void 0;
13454
- this.#sources.set(name, source);
13455
- return source;
13593
+ return rv;
13594
+ }
13595
+ function makeSchemaCRUD(schema, tx, ivmBranch) {
13596
+ const mutate = {};
13597
+ for (const [name] of Object.entries(schema.tables)) {
13598
+ mutate[name] = makeTableCRUD(schema, name, tx, ivmBranch);
13456
13599
  }
13457
- /**
13458
- * Creates a new IVMSourceBranch that is a copy of the current one.
13459
- *
13460
- * This is used when:
13461
- * 1. We need to rebase a change. We fork the `sync` branch and run the mutations against the fork.
13462
- * 2. We need to create `main` at startup.
13463
- * 3. We need to create a new `sync` head because we got a new server snapshot.
13464
- * The old `sync` head is forked and the new server snapshot is applied to the fork.
13465
- */
13466
- fork() {
13467
- return new _IVMSourceBranch(
13468
- this.#tables,
13469
- new Map(
13470
- wrapIterable(this.#sources.entries()).map(([name, source]) => [
13471
- name,
13472
- source?.fork()
13473
- ])
13474
- )
13475
- );
13600
+ return mutate;
13601
+ }
13602
+ function makeTableCRUD(schema, tableName, tx, ivmBranch) {
13603
+ const table2 = must(schema.tables[tableName]);
13604
+ const { primaryKey } = table2;
13605
+ return {
13606
+ insert: (value) => insertImpl(
13607
+ tx,
13608
+ { op: "insert", tableName, primaryKey, value },
13609
+ schema,
13610
+ ivmBranch
13611
+ ),
13612
+ upsert: (value) => upsertImpl(
13613
+ tx,
13614
+ { op: "upsert", tableName, primaryKey, value },
13615
+ schema,
13616
+ ivmBranch
13617
+ ),
13618
+ update: (value) => updateImpl(
13619
+ tx,
13620
+ { op: "update", tableName, primaryKey, value },
13621
+ schema,
13622
+ ivmBranch
13623
+ ),
13624
+ delete: (id) => deleteImpl(
13625
+ tx,
13626
+ { op: "delete", tableName, primaryKey, value: id },
13627
+ schema,
13628
+ ivmBranch
13629
+ )
13630
+ };
13631
+ }
13632
+
13633
+ // ../zero-client/src/client/enable-analytics.ts
13634
+ var IPV4_ADDRESS_REGEX = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
13635
+ var IPV6_ADDRESS_HOSTNAME_REGEX = /^\[[a-fA-F0-9:]*:[a-fA-F0-9:]*\]$/;
13636
+ var IP_ADDRESS_HOSTNAME_REGEX = new RegExp(
13637
+ `(${IPV4_ADDRESS_REGEX.source}|${IPV6_ADDRESS_HOSTNAME_REGEX.source})`
13638
+ );
13639
+ function shouldEnableAnalytics(server, enableAnalytics = true) {
13640
+ if (!enableAnalytics) {
13641
+ return false;
13476
13642
  }
13477
- };
13643
+ const serverURL = server === null ? null : new URL(server);
13644
+ const socketHostname = serverURL?.hostname;
13645
+ return server !== null && socketHostname !== void 0 && socketHostname !== "localhost" && !IP_ADDRESS_HOSTNAME_REGEX.test(socketHostname);
13646
+ }
13647
+
13648
+ // ../zero-client/src/client/http-string.ts
13649
+ function toWSString(url) {
13650
+ return "ws" + url.slice(4);
13651
+ }
13652
+ function appendPath(url, toAppend) {
13653
+ return url + (url.endsWith("/") ? toAppend.substring(1) : toAppend);
13654
+ }
13478
13655
 
13479
13656
  // ../zero-client/src/client/log-options.ts
13480
13657
  import {
@@ -13701,7 +13878,7 @@ function makeMessage(message, context, logLevel) {
13701
13878
  }
13702
13879
 
13703
13880
  // ../zero-client/src/client/version.ts
13704
- var version2 = "0.13.2025020500";
13881
+ var version2 = "0.13.2025020700";
13705
13882
 
13706
13883
  // ../zero-client/src/client/log-options.ts
13707
13884
  var LevelFilterLogSink = class {
@@ -14900,7 +15077,7 @@ var Zero = class {
14900
15077
  });
14901
15078
  this.#metrics.tags.push(`version:${this.version}`);
14902
15079
  this.#pokeHandler = new PokeHandler(
14903
- (poke) => this.#rep.poke(poke),
15080
+ (poke) => this.#rep.poke(poke, this.#ivmSources.advanceSyncHead),
14904
15081
  () => this.#onPokeError(),
14905
15082
  rep.clientID,
14906
15083
  schema,
@@ -15813,4 +15990,4 @@ export {
15813
15990
  escapeLike,
15814
15991
  Zero
15815
15992
  };
15816
- //# sourceMappingURL=chunk-H3C7QNPL.js.map
15993
+ //# sourceMappingURL=chunk-FVVIM6TT.js.map