@absolutejs/sync 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -614,12 +614,98 @@ var createPollingChangeSource = (options) => {
614
614
  }
615
615
  };
616
616
  };
617
+ // src/engine/cluster.ts
618
+ var createInMemoryClusterBus = () => {
619
+ const listeners = new Set;
620
+ return {
621
+ publish: (message) => {
622
+ for (const listener of listeners) {
623
+ listener(message);
624
+ }
625
+ },
626
+ subscribe: (onMessage) => {
627
+ listeners.add(onMessage);
628
+ return () => {
629
+ listeners.delete(onMessage);
630
+ };
631
+ }
632
+ };
633
+ };
634
+ // src/engine/presence.ts
635
+ var createPresenceHub = () => {
636
+ const rooms = new Map;
637
+ const roomMembers = (room) => {
638
+ const members = rooms.get(room);
639
+ if (members === undefined) {
640
+ return [];
641
+ }
642
+ return [...members].map(([id, member]) => ({
643
+ id,
644
+ state: member.state
645
+ }));
646
+ };
647
+ const notify = (room, diff, exceptId) => {
648
+ const members = rooms.get(room);
649
+ if (members === undefined) {
650
+ return;
651
+ }
652
+ for (const [id, member] of members) {
653
+ if (id !== exceptId) {
654
+ member.onDiff(diff);
655
+ }
656
+ }
657
+ };
658
+ return {
659
+ join: (room, memberId, state, onDiff) => {
660
+ let members = rooms.get(room);
661
+ if (members === undefined) {
662
+ members = new Map;
663
+ rooms.set(room, members);
664
+ }
665
+ members.set(memberId, {
666
+ state,
667
+ onDiff
668
+ });
669
+ notify(room, { joined: [{ id: memberId, state }], updated: [], left: [] }, memberId);
670
+ const snapshot = roomMembers(room);
671
+ return {
672
+ members: snapshot,
673
+ set: (next) => {
674
+ const current = rooms.get(room)?.get(memberId);
675
+ if (current === undefined) {
676
+ return;
677
+ }
678
+ current.state = next;
679
+ notify(room, {
680
+ joined: [],
681
+ updated: [{ id: memberId, state: next }],
682
+ left: []
683
+ }, memberId);
684
+ },
685
+ leave: () => {
686
+ const roomNow = rooms.get(room);
687
+ if (roomNow?.delete(memberId) !== true) {
688
+ return;
689
+ }
690
+ notify(room, { joined: [], updated: [], left: [memberId] }, memberId);
691
+ if (roomNow.size === 0) {
692
+ rooms.delete(room);
693
+ }
694
+ }
695
+ };
696
+ },
697
+ members: (room) => roomMembers(room),
698
+ count: (room) => rooms.get(room)?.size ?? 0
699
+ };
700
+ };
617
701
  // src/engine/collection.ts
618
702
  var defineCollection = (definition) => definition;
619
703
  var defineJoinCollection = (definition) => ({
620
704
  ...definition,
621
705
  kind: "join"
622
706
  });
707
+ // src/engine/reactive.ts
708
+ var defineReactiveQuery = (definition) => ({ ...definition, kind: "reactive" });
623
709
  // src/engine/graph.ts
624
710
  var PLANS = new WeakMap;
625
711
  var planTables = (plan) => {
@@ -807,14 +893,36 @@ class UnauthorizedError extends Error {
807
893
  }
808
894
  }
809
895
  var defaultKey = (row) => row.id;
896
+ var shallowEqual4 = (a, b) => {
897
+ if (a === b) {
898
+ return true;
899
+ }
900
+ if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) {
901
+ return false;
902
+ }
903
+ const aKeys = Object.keys(a);
904
+ const bKeys = Object.keys(b);
905
+ return aKeys.length === bKeys.length && aKeys.every((k) => a[k] === b[k]);
906
+ };
810
907
  var createSyncEngine = (options = {}) => {
811
908
  const registry = new Map;
812
909
  const mutations = new Map;
910
+ const writers = new Map;
911
+ const readers = new Map;
912
+ const reactiveSubs = new Set;
813
913
  const active = new Map;
814
914
  const tableIndex = new Map;
815
915
  const changeLogSize = options.changeLogSize ?? 1024;
816
916
  const changeLog = [];
817
917
  let version = 0;
918
+ const runInTransaction = options.transaction;
919
+ const instanceId = globalThis.crypto?.randomUUID?.() ?? `i${Math.random()}`;
920
+ let clusterBus;
921
+ const broadcast = (changes) => {
922
+ if (clusterBus !== undefined && changes.length > 0) {
923
+ clusterBus.publish({ changes, origin: instanceId });
924
+ }
925
+ };
818
926
  const subsFor = (collection) => {
819
927
  let set = active.get(collection);
820
928
  if (set === undefined) {
@@ -832,52 +940,243 @@ var createSyncEngine = (options = {}) => {
832
940
  set.add(name);
833
941
  };
834
942
  const sideChange = (change, match) => change.op !== "delete" && match !== undefined && !match(change.row) ? { op: "delete", row: change.row } : change;
835
- const applyToSubscription = async (subscription, table, change, changeVersion) => {
836
- let diff;
943
+ const EMPTY_DIFF = {
944
+ added: [],
945
+ removed: [],
946
+ changed: []
947
+ };
948
+ const subscriptionDiff = async (subscription, table, change) => {
837
949
  if (subscription.kind === "graph") {
838
- diff = subscription.instance.applyChange(table, change);
839
- } else if (subscription.kind === "join") {
950
+ return subscription.instance.applyChange(table, change);
951
+ }
952
+ if (subscription.kind === "join") {
840
953
  const js = subscription.join;
841
954
  if (table === js.leftTable) {
842
- diff = js.op.applyLeft(sideChange(change, js.leftMatch));
843
- } else if (table === js.rightTable) {
844
- diff = js.op.applyRight(sideChange(change, js.rightMatch));
845
- } else {
846
- return;
955
+ return js.op.applyLeft(sideChange(change, js.leftMatch));
847
956
  }
848
- } else if (subscription.incremental) {
957
+ if (table === js.rightTable) {
958
+ return js.op.applyRight(sideChange(change, js.rightMatch));
959
+ }
960
+ return EMPTY_DIFF;
961
+ }
962
+ if (subscription.kind === "reactive") {
963
+ return EMPTY_DIFF;
964
+ }
965
+ if (subscription.incremental) {
849
966
  try {
850
- diff = subscription.view.apply(change);
967
+ return subscription.view.apply(change);
851
968
  } catch {
852
- diff = subscription.view.reset(await subscription.rehydrate());
969
+ return subscription.view.reset(await subscription.rehydrate());
853
970
  }
854
- } else {
855
- diff = subscription.view.reset(await subscription.rehydrate());
856
971
  }
857
- if (!isEmptyViewDiff(diff)) {
858
- subscription.onDiff(diff, changeVersion);
972
+ return subscription.view.reset(await subscription.rehydrate());
973
+ };
974
+ const subscriptionsForTable = function* (table) {
975
+ const names = tableIndex.get(table);
976
+ if (names === undefined) {
977
+ return;
978
+ }
979
+ for (const name of names) {
980
+ const set = active.get(name);
981
+ if (set === undefined) {
982
+ continue;
983
+ }
984
+ yield* set;
859
985
  }
860
986
  };
861
- const applyChange = async (table, change) => {
862
- version += 1;
863
- changeLog.push({ version, table, change });
987
+ const mergeViewDiffs = (diffs, key) => {
988
+ const net = new Map;
989
+ for (const diff of diffs) {
990
+ for (const row of diff.removed) {
991
+ const previous = net.get(key(row));
992
+ if (previous?.state === "added") {
993
+ net.delete(key(row));
994
+ } else {
995
+ net.set(key(row), { state: "removed", row });
996
+ }
997
+ }
998
+ for (const row of diff.added) {
999
+ const previous = net.get(key(row));
1000
+ net.set(key(row), {
1001
+ state: previous?.state === "removed" ? "changed" : "added",
1002
+ row
1003
+ });
1004
+ }
1005
+ for (const row of diff.changed) {
1006
+ const previous = net.get(key(row));
1007
+ net.set(key(row), {
1008
+ state: previous?.state === "added" ? "added" : "changed",
1009
+ row
1010
+ });
1011
+ }
1012
+ }
1013
+ const added = [];
1014
+ const changed = [];
1015
+ const removed = [];
1016
+ for (const { state, row } of net.values()) {
1017
+ if (state === "added") {
1018
+ added.push(row);
1019
+ } else if (state === "changed") {
1020
+ changed.push(row);
1021
+ } else {
1022
+ removed.push(row);
1023
+ }
1024
+ }
1025
+ return { added, changed, removed };
1026
+ };
1027
+ const depKey = (table, key) => `${table} ${key}`;
1028
+ const changedKeyFor = (table, change) => readers.get(table)?.key?.(change.row);
1029
+ const makeReadHandle = (ctx, readTables, readKeys, rangeDeps) => {
1030
+ const readerFor = (table) => {
1031
+ const reader = readers.get(table);
1032
+ if (reader === undefined) {
1033
+ throw new Error(`No reader registered for table "${table}" \u2014 register one with engine.registerReader`);
1034
+ }
1035
+ return reader;
1036
+ };
1037
+ return {
1038
+ all: async (table) => {
1039
+ readTables.add(table);
1040
+ return [...await readerFor(table).all(ctx)];
1041
+ },
1042
+ get: async (table, key) => {
1043
+ const reader = readerFor(table);
1044
+ if (reader.get === undefined) {
1045
+ throw new Error(`Reader for table "${table}" has no get(); use db.all() or add get`);
1046
+ }
1047
+ if (reader.key !== undefined) {
1048
+ readKeys.add(depKey(table, key));
1049
+ } else {
1050
+ readTables.add(table);
1051
+ }
1052
+ return await reader.get(key, ctx);
1053
+ },
1054
+ where: async (table, predicate) => {
1055
+ const reader = readerFor(table);
1056
+ const matched = [...await reader.all(ctx)].filter(predicate);
1057
+ if (reader.key !== undefined) {
1058
+ const key = reader.key;
1059
+ rangeDeps.push({
1060
+ table,
1061
+ predicate,
1062
+ keys: new Set(matched.map(key))
1063
+ });
1064
+ } else {
1065
+ readTables.add(table);
1066
+ }
1067
+ return matched;
1068
+ }
1069
+ };
1070
+ };
1071
+ const diffRerun = (sub, rows) => {
1072
+ const next = new Map;
1073
+ for (const row of rows) {
1074
+ next.set(sub.key(row), row);
1075
+ }
1076
+ const added = [];
1077
+ const removed = [];
1078
+ const changed = [];
1079
+ for (const [rowKey, row] of next) {
1080
+ const previous = sub.current.get(rowKey);
1081
+ if (previous === undefined) {
1082
+ added.push(row);
1083
+ } else if (!shallowEqual4(previous, row)) {
1084
+ changed.push(row);
1085
+ }
1086
+ }
1087
+ for (const [rowKey, row] of sub.current) {
1088
+ if (!next.has(rowKey)) {
1089
+ removed.push(row);
1090
+ }
1091
+ }
1092
+ sub.current = next;
1093
+ return { added, removed, changed };
1094
+ };
1095
+ const inRange = (dep, change) => dep.table === change.table && (change.key !== undefined && dep.keys.has(change.key) || dep.predicate(change.row));
1096
+ const isReactiveAffected = (sub, changes) => changes.some((change) => sub.readTables.has(change.table) || change.key !== undefined && sub.readKeys.has(depKey(change.table, change.key)) || sub.rangeDeps.some((dep) => inRange(dep, change)));
1097
+ const reactivePairs = async (changes) => {
1098
+ const pairs = [];
1099
+ for (const sub of reactiveSubs) {
1100
+ if (!isReactiveAffected(sub, changes)) {
1101
+ continue;
1102
+ }
1103
+ const { rows, readTables, readKeys, rangeDeps } = await sub.rerun();
1104
+ sub.readTables = readTables;
1105
+ sub.readKeys = readKeys;
1106
+ sub.rangeDeps = rangeDeps;
1107
+ const diff = diffRerun(sub, rows);
1108
+ if (!isEmptyViewDiff(diff)) {
1109
+ pairs.push([sub, diff]);
1110
+ }
1111
+ }
1112
+ return pairs;
1113
+ };
1114
+ const logChange = (changeVersion, entry) => {
1115
+ changeLog.push(entry);
864
1116
  if (changeLog.length > changeLogSize) {
865
1117
  changeLog.shift();
866
1118
  }
1119
+ };
1120
+ const applyChange = async (table, change, shouldBroadcast = true) => {
1121
+ version += 1;
867
1122
  const changeVersion = version;
868
- const collectionNames = tableIndex.get(table);
869
- if (collectionNames === undefined) {
1123
+ logChange(changeVersion, { version: changeVersion, table, change });
1124
+ const emissions = [];
1125
+ for (const subscription of subscriptionsForTable(table)) {
1126
+ const diff = await subscriptionDiff(subscription, table, change);
1127
+ if (!isEmptyViewDiff(diff)) {
1128
+ emissions.push([subscription, diff]);
1129
+ }
1130
+ }
1131
+ emissions.push(...await reactivePairs([
1132
+ { table, key: changedKeyFor(table, change), row: change.row }
1133
+ ]));
1134
+ for (const [subscription, diff] of emissions) {
1135
+ subscription.onDiff(diff, changeVersion);
1136
+ }
1137
+ if (shouldBroadcast) {
1138
+ broadcast([{ table, change }]);
1139
+ }
1140
+ };
1141
+ const applyChangeBatch = async (changes, shouldBroadcast = true) => {
1142
+ if (changes.length === 0) {
870
1143
  return;
871
1144
  }
872
- for (const name of collectionNames) {
873
- const set = active.get(name);
874
- if (set === undefined || set.size === 0) {
875
- continue;
1145
+ version += 1;
1146
+ const batchVersion = version;
1147
+ const perSubscription = new Map;
1148
+ const reactiveChanges = [];
1149
+ for (const { table, change } of changes) {
1150
+ logChange(batchVersion, { version: batchVersion, table, change });
1151
+ reactiveChanges.push({
1152
+ table,
1153
+ key: changedKeyFor(table, change),
1154
+ row: change.row
1155
+ });
1156
+ for (const subscription of subscriptionsForTable(table)) {
1157
+ const diff = await subscriptionDiff(subscription, table, change);
1158
+ const list = perSubscription.get(subscription);
1159
+ if (list === undefined) {
1160
+ perSubscription.set(subscription, [diff]);
1161
+ } else {
1162
+ list.push(diff);
1163
+ }
876
1164
  }
877
- for (const subscription of set) {
878
- await applyToSubscription(subscription, table, change, changeVersion);
1165
+ }
1166
+ const emissions = [];
1167
+ for (const [subscription, diffs] of perSubscription) {
1168
+ const merged = diffs.length === 1 ? diffs[0] : mergeViewDiffs(diffs, subscription.key);
1169
+ if (!isEmptyViewDiff(merged)) {
1170
+ emissions.push([subscription, merged]);
879
1171
  }
880
1172
  }
1173
+ emissions.push(...await reactivePairs(reactiveChanges));
1174
+ for (const [subscription, diff] of emissions) {
1175
+ subscription.onDiff(diff, batchVersion);
1176
+ }
1177
+ if (shouldBroadcast) {
1178
+ broadcast(changes);
1179
+ }
881
1180
  };
882
1181
  const canResume = (since, incremental) => {
883
1182
  if (!incremental) {
@@ -933,6 +1232,7 @@ var createSyncEngine = (options = {}) => {
933
1232
  leftMatch: left.match ? (row) => left.match(row, params, ctx) : undefined,
934
1233
  rightMatch: right.match ? (row) => right.match(row, params, ctx) : undefined
935
1234
  },
1235
+ key: definition.key,
936
1236
  onDiff
937
1237
  };
938
1238
  set.add(subscription);
@@ -958,6 +1258,7 @@ var createSyncEngine = (options = {}) => {
958
1258
  kind: "graph",
959
1259
  collection,
960
1260
  instance,
1261
+ key: definition.key,
961
1262
  onDiff
962
1263
  };
963
1264
  set.add(subscription);
@@ -969,6 +1270,49 @@ var createSyncEngine = (options = {}) => {
969
1270
  }
970
1271
  };
971
1272
  };
1273
+ const subscribeReactive = async (collection, definition, params, ctx, onDiff, set) => {
1274
+ if (definition.authorize !== undefined) {
1275
+ const allowed = await definition.authorize(params, ctx);
1276
+ if (!allowed) {
1277
+ throw new UnauthorizedError(`subscribe to collection "${collection}"`);
1278
+ }
1279
+ }
1280
+ const rerun = async () => {
1281
+ const readTables = new Set;
1282
+ const readKeys = new Set;
1283
+ const rangeDeps = [];
1284
+ const db = makeReadHandle(ctx, readTables, readKeys, rangeDeps);
1285
+ const rows = [...await definition.run({ ctx, db, params })];
1286
+ return { rangeDeps, readKeys, readTables, rows };
1287
+ };
1288
+ const first = await rerun();
1289
+ const current = new Map;
1290
+ for (const row of first.rows) {
1291
+ current.set(definition.key(row), row);
1292
+ }
1293
+ const atVersion = version;
1294
+ const subscription = {
1295
+ kind: "reactive",
1296
+ collection,
1297
+ key: definition.key,
1298
+ rerun,
1299
+ current,
1300
+ readTables: first.readTables,
1301
+ readKeys: first.readKeys,
1302
+ rangeDeps: first.rangeDeps,
1303
+ onDiff
1304
+ };
1305
+ set.add(subscription);
1306
+ reactiveSubs.add(subscription);
1307
+ return {
1308
+ initial: first.rows,
1309
+ version: atVersion,
1310
+ unsubscribe: () => {
1311
+ set.delete(subscription);
1312
+ reactiveSubs.delete(subscription);
1313
+ }
1314
+ };
1315
+ };
972
1316
  return {
973
1317
  register: (collection) => {
974
1318
  registry.set(collection.name, collection);
@@ -1003,6 +1347,10 @@ var createSyncEngine = (options = {}) => {
1003
1347
  const graphed = await subscribeGraph(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1004
1348
  return graphed;
1005
1349
  }
1350
+ if (registeredKind === "reactive") {
1351
+ const reactived = await subscribeReactive(collection, registered, params, ctx, typedOnDiff, subscribeSet);
1352
+ return reactived;
1353
+ }
1006
1354
  const definition = registered;
1007
1355
  if (definition.authorize !== undefined) {
1008
1356
  const allowed = await definition.authorize(params, ctx);
@@ -1029,6 +1377,7 @@ var createSyncEngine = (options = {}) => {
1029
1377
  view,
1030
1378
  incremental,
1031
1379
  rehydrate,
1380
+ key,
1032
1381
  onDiff: typedOnDiff
1033
1382
  };
1034
1383
  subscribeSet.add(subscription);
@@ -1069,6 +1418,19 @@ var createSyncEngine = (options = {}) => {
1069
1418
  await source.stop();
1070
1419
  };
1071
1420
  },
1421
+ connectCluster: async (bus) => {
1422
+ const unsubscribe = await bus.subscribe((message) => {
1423
+ if (message.origin === instanceId) {
1424
+ return;
1425
+ }
1426
+ applyChangeBatch(message.changes, false);
1427
+ });
1428
+ clusterBus = bus;
1429
+ return async () => {
1430
+ clusterBus = undefined;
1431
+ await unsubscribe();
1432
+ };
1433
+ },
1072
1434
  subscriptionCount: (collection) => {
1073
1435
  if (collection !== undefined) {
1074
1436
  return active.get(collection)?.size ?? 0;
@@ -1082,6 +1444,15 @@ var createSyncEngine = (options = {}) => {
1082
1444
  registerMutation: (mutation) => {
1083
1445
  mutations.set(mutation.name, mutation);
1084
1446
  },
1447
+ registerWriter: (table, writer) => {
1448
+ writers.set(table, writer);
1449
+ },
1450
+ registerReactive: (query2) => {
1451
+ registry.set(query2.name, query2);
1452
+ },
1453
+ registerReader: (table, reader) => {
1454
+ readers.set(table, reader);
1455
+ },
1085
1456
  runMutation: async (name, args, ctx) => {
1086
1457
  const mutation = mutations.get(name);
1087
1458
  if (mutation === undefined) {
@@ -1093,9 +1464,44 @@ var createSyncEngine = (options = {}) => {
1093
1464
  throw new UnauthorizedError(`run mutation "${name}"`);
1094
1465
  }
1095
1466
  }
1096
- return mutation.handler(args, ctx, {
1097
- change: (collection, change) => applyChange(collection, change)
1098
- });
1467
+ const writerFor = (table) => {
1468
+ const writer = writers.get(table);
1469
+ if (writer === undefined) {
1470
+ throw new Error(`No writer registered for table "${table}" \u2014 register one with engine.registerWriter, or use actions.change`);
1471
+ }
1472
+ return writer;
1473
+ };
1474
+ const runHandler = async (tx) => {
1475
+ const buffered2 = [];
1476
+ const actions = {
1477
+ change: (collection, change) => {
1478
+ buffered2.push({
1479
+ table: collection,
1480
+ change
1481
+ });
1482
+ return Promise.resolve();
1483
+ },
1484
+ insert: async (table, data) => {
1485
+ const row = await writerFor(table).insert(data, ctx, tx);
1486
+ buffered2.push({ table, change: { op: "insert", row } });
1487
+ return row;
1488
+ },
1489
+ update: async (table, data) => {
1490
+ const row = await writerFor(table).update(data, ctx, tx);
1491
+ buffered2.push({ table, change: { op: "update", row } });
1492
+ return row;
1493
+ },
1494
+ delete: async (table, row) => {
1495
+ await writerFor(table).delete(row, ctx, tx);
1496
+ buffered2.push({ table, change: { op: "delete", row } });
1497
+ }
1498
+ };
1499
+ const handlerResult = await mutation.handler(args, ctx, actions);
1500
+ return { buffered: buffered2, result: handlerResult };
1501
+ };
1502
+ const { buffered, result } = runInTransaction !== undefined ? await runInTransaction((tx) => runHandler(tx)) : await runHandler(undefined);
1503
+ await applyChangeBatch(buffered);
1504
+ return result;
1099
1505
  }
1100
1506
  };
1101
1507
  };
@@ -1147,14 +1553,73 @@ var parseFrame = (raw) => {
1147
1553
  args: frame.args
1148
1554
  } : undefined;
1149
1555
  }
1556
+ if (frame.type === "presence-join") {
1557
+ return typeof frame.room === "string" && typeof frame.memberId === "string" ? {
1558
+ type: "presence-join",
1559
+ room: frame.room,
1560
+ memberId: frame.memberId,
1561
+ state: frame.state
1562
+ } : undefined;
1563
+ }
1564
+ if (frame.type === "presence-set") {
1565
+ return typeof frame.room === "string" ? { type: "presence-set", room: frame.room, state: frame.state } : undefined;
1566
+ }
1567
+ if (frame.type === "presence-leave") {
1568
+ return typeof frame.room === "string" ? { type: "presence-leave", room: frame.room } : undefined;
1569
+ }
1150
1570
  return;
1151
1571
  };
1152
1572
  var createSyncConnection = ({
1153
1573
  engine,
1154
1574
  ctx,
1155
- send
1575
+ send,
1576
+ presence
1156
1577
  }) => {
1157
1578
  const subscriptions = new Map;
1579
+ const presenceRooms = new Map;
1580
+ let pending = [];
1581
+ let pendingVersion;
1582
+ let flushScheduled = false;
1583
+ const flush = () => {
1584
+ if (pending.length === 0) {
1585
+ return;
1586
+ }
1587
+ const diffs = pending;
1588
+ const version = pendingVersion;
1589
+ pending = [];
1590
+ pendingVersion = undefined;
1591
+ if (diffs.length === 1) {
1592
+ const only = diffs[0];
1593
+ send({
1594
+ type: "diff",
1595
+ id: only.id,
1596
+ added: only.added,
1597
+ removed: only.removed,
1598
+ changed: only.changed,
1599
+ version
1600
+ });
1601
+ } else {
1602
+ send({ type: "frame", diffs, version });
1603
+ }
1604
+ };
1605
+ const scheduleFlush = () => {
1606
+ if (flushScheduled) {
1607
+ return;
1608
+ }
1609
+ flushScheduled = true;
1610
+ queueMicrotask(() => {
1611
+ flushScheduled = false;
1612
+ flush();
1613
+ });
1614
+ };
1615
+ const bufferDiff = (diff, diffVersion) => {
1616
+ if (pending.length > 0 && pendingVersion !== diffVersion) {
1617
+ flush();
1618
+ }
1619
+ pending.push(diff);
1620
+ pendingVersion = diffVersion;
1621
+ scheduleFlush();
1622
+ };
1158
1623
  const handle = async (raw) => {
1159
1624
  const frame = parseFrame(raw);
1160
1625
  if (frame === undefined) {
@@ -1164,6 +1629,7 @@ var createSyncConnection = ({
1164
1629
  if (frame.type === "mutate") {
1165
1630
  try {
1166
1631
  const result = await engine.runMutation(frame.name, frame.args, ctx);
1632
+ flush();
1167
1633
  send({ type: "ack", mutationId: frame.mutationId, result });
1168
1634
  } catch (error) {
1169
1635
  send({
@@ -1179,6 +1645,40 @@ var createSyncConnection = ({
1179
1645
  subscriptions.delete(frame.id);
1180
1646
  return;
1181
1647
  }
1648
+ if (frame.type === "presence-join") {
1649
+ if (presence === undefined) {
1650
+ send({ type: "error", message: "Presence is not enabled" });
1651
+ return;
1652
+ }
1653
+ presenceRooms.get(frame.room)?.leave();
1654
+ const handle2 = presence.join(frame.room, frame.memberId, frame.state, (diff) => {
1655
+ send({
1656
+ type: "presence",
1657
+ room: frame.room,
1658
+ joined: diff.joined,
1659
+ updated: diff.updated,
1660
+ left: diff.left
1661
+ });
1662
+ });
1663
+ presenceRooms.set(frame.room, handle2);
1664
+ send({
1665
+ type: "presence",
1666
+ room: frame.room,
1667
+ joined: handle2.members,
1668
+ updated: [],
1669
+ left: []
1670
+ });
1671
+ return;
1672
+ }
1673
+ if (frame.type === "presence-set") {
1674
+ presenceRooms.get(frame.room)?.set(frame.state);
1675
+ return;
1676
+ }
1677
+ if (frame.type === "presence-leave") {
1678
+ presenceRooms.get(frame.room)?.leave();
1679
+ presenceRooms.delete(frame.room);
1680
+ return;
1681
+ }
1182
1682
  if (subscriptions.has(frame.id)) {
1183
1683
  send({
1184
1684
  type: "error",
@@ -1194,14 +1694,12 @@ var createSyncConnection = ({
1194
1694
  ctx,
1195
1695
  since: frame.since,
1196
1696
  onDiff: (diff, diffVersion) => {
1197
- send({
1198
- type: "diff",
1697
+ bufferDiff({
1199
1698
  id: frame.id,
1200
1699
  added: diff.added,
1201
1700
  removed: diff.removed,
1202
- changed: diff.changed,
1203
- version: diffVersion
1204
- });
1701
+ changed: diff.changed
1702
+ }, diffVersion);
1205
1703
  }
1206
1704
  });
1207
1705
  subscriptions.set(frame.id, subscription);
@@ -1235,6 +1733,10 @@ var createSyncConnection = ({
1235
1733
  subscription.unsubscribe();
1236
1734
  }
1237
1735
  subscriptions.clear();
1736
+ for (const handle2 of presenceRooms.values()) {
1737
+ handle2.leave();
1738
+ }
1739
+ presenceRooms.clear();
1238
1740
  };
1239
1741
  return { handle, close };
1240
1742
  };
@@ -1250,14 +1752,17 @@ export {
1250
1752
  hydrateRoute,
1251
1753
  fromRowChange,
1252
1754
  filterOp,
1755
+ defineReactiveQuery,
1253
1756
  defineMutation,
1254
1757
  defineJoinCollection,
1255
1758
  defineGraphCollection,
1256
1759
  defineCollection,
1257
1760
  createSyncEngine,
1258
1761
  createSyncConnection,
1762
+ createPresenceHub,
1259
1763
  createPollingChangeSource,
1260
1764
  createMaterializedView,
1765
+ createInMemoryClusterBus,
1261
1766
  createEquiJoin,
1262
1767
  createAggregate,
1263
1768
  chain,
@@ -1265,5 +1770,5 @@ export {
1265
1770
  UnauthorizedError
1266
1771
  };
1267
1772
 
1268
- //# debugId=CDF0007CE81F93F364756E2164756E21
1773
+ //# debugId=4ADB152722C87EA464756E2164756E21
1269
1774
  //# sourceMappingURL=index.js.map