@rocicorp/zero 0.13.2025020402 → 0.13.2025020501

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