hyperstack-typescript 0.3.1 → 0.3.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.esm.js CHANGED
@@ -15,40 +15,32 @@ class HyperStackError extends Error {
15
15
  }
16
16
  }
17
17
 
18
- function isCompressedFrame(obj) {
19
- return (typeof obj === 'object' &&
20
- obj !== null &&
21
- obj.compressed === 'gzip' &&
22
- typeof obj.data === 'string');
23
- }
24
- function decompressGzip(base64Data) {
25
- const binaryString = atob(base64Data);
26
- const bytes = new Uint8Array(binaryString.length);
27
- for (let i = 0; i < binaryString.length; i++) {
28
- bytes[i] = binaryString.charCodeAt(i);
29
- }
30
- const decompressed = inflate(bytes);
31
- return new TextDecoder().decode(decompressed);
32
- }
33
- function parseAndDecompress(jsonString) {
34
- const parsed = JSON.parse(jsonString);
35
- if (isCompressedFrame(parsed)) {
36
- const decompressedJson = decompressGzip(parsed.data);
37
- const frame = JSON.parse(decompressedJson);
38
- return frame;
39
- }
40
- return parsed;
18
+ const GZIP_MAGIC_0 = 0x1f;
19
+ const GZIP_MAGIC_1 = 0x8b;
20
+ function isGzipData(data) {
21
+ return data.length >= 2 && data[0] === GZIP_MAGIC_0 && data[1] === GZIP_MAGIC_1;
41
22
  }
42
23
  function isSnapshotFrame(frame) {
43
24
  return frame.op === 'snapshot';
44
25
  }
26
+ function isSubscribedFrame(frame) {
27
+ return frame.op === 'subscribed';
28
+ }
29
+ function isEntityFrame(frame) {
30
+ return ['create', 'upsert', 'patch', 'delete'].includes(frame.op);
31
+ }
45
32
  function parseFrame(data) {
46
33
  if (typeof data === 'string') {
47
- return parseAndDecompress(data);
34
+ return JSON.parse(data);
35
+ }
36
+ const bytes = new Uint8Array(data);
37
+ if (isGzipData(bytes)) {
38
+ const decompressed = inflate(bytes);
39
+ const jsonString = new TextDecoder().decode(decompressed);
40
+ return JSON.parse(jsonString);
48
41
  }
49
- const decoder = new TextDecoder('utf-8');
50
- const jsonString = decoder.decode(data);
51
- return parseAndDecompress(jsonString);
42
+ const jsonString = new TextDecoder('utf-8').decode(data);
43
+ return JSON.parse(jsonString);
52
44
  }
53
45
  async function parseFrameFromBlob(blob) {
54
46
  const arrayBuffer = await blob.arrayBuffer();
@@ -123,8 +115,8 @@ class ConnectionManager {
123
115
  this.reconnectAttempts = 0;
124
116
  this.updateState('connected');
125
117
  this.startPingInterval();
126
- this.flushSubscriptionQueue();
127
118
  this.resubscribeActive();
119
+ this.flushSubscriptionQueue();
128
120
  resolve();
129
121
  };
130
122
  this.ws.onmessage = async (event) => {
@@ -145,7 +137,6 @@ class ConnectionManager {
145
137
  this.notifyFrameHandlers(frame);
146
138
  }
147
139
  catch (error) {
148
- console.error('[hyperstack] Error parsing frame:', error);
149
140
  this.updateState('error', 'Failed to parse frame from server');
150
141
  }
151
142
  };
@@ -183,12 +174,18 @@ class ConnectionManager {
183
174
  subscribe(subscription) {
184
175
  const subKey = this.makeSubKey(subscription);
185
176
  if (this.currentState === 'connected' && this.ws?.readyState === WebSocket.OPEN) {
177
+ if (this.activeSubscriptions.has(subKey)) {
178
+ return;
179
+ }
186
180
  const subMsg = { type: 'subscribe', ...subscription };
187
181
  this.ws.send(JSON.stringify(subMsg));
188
182
  this.activeSubscriptions.add(subKey);
189
183
  }
190
184
  else {
191
- this.subscriptionQueue.push(subscription);
185
+ const alreadyQueued = this.subscriptionQueue.some((s) => this.makeSubKey(s) === subKey);
186
+ if (!alreadyQueued) {
187
+ this.subscriptionQueue.push(subscription);
188
+ }
192
189
  }
193
190
  }
194
191
  unsubscribe(view, key) {
@@ -278,11 +275,11 @@ class ConnectionManager {
278
275
  }
279
276
  }
280
277
 
281
- function isObject(item) {
278
+ function isObject$1(item) {
282
279
  return item !== null && typeof item === 'object' && !Array.isArray(item);
283
280
  }
284
- function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
285
- if (!isObject(target) || !isObject(source)) {
281
+ function deepMergeWithAppend$1(target, source, appendPaths, currentPath = '') {
282
+ if (!isObject$1(target) || !isObject$1(source)) {
286
283
  return source;
287
284
  }
288
285
  const result = { ...target };
@@ -298,8 +295,8 @@ function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
298
295
  result[key] = sourceValue;
299
296
  }
300
297
  }
301
- else if (isObject(sourceValue) && isObject(targetValue)) {
302
- result[key] = deepMergeWithAppend(targetValue, sourceValue, appendPaths, fieldPath);
298
+ else if (isObject$1(sourceValue) && isObject$1(targetValue)) {
299
+ result[key] = deepMergeWithAppend$1(targetValue, sourceValue, appendPaths, fieldPath);
303
300
  }
304
301
  else {
305
302
  result[key] = sourceValue;
@@ -309,20 +306,107 @@ function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
309
306
  }
310
307
  class FrameProcessor {
311
308
  constructor(storage, config = {}) {
309
+ this.pendingUpdates = [];
310
+ this.flushTimer = null;
311
+ this.isProcessing = false;
312
312
  this.storage = storage;
313
313
  this.maxEntriesPerView = config.maxEntriesPerView === undefined
314
314
  ? DEFAULT_MAX_ENTRIES_PER_VIEW
315
315
  : config.maxEntriesPerView;
316
+ this.flushIntervalMs = config.flushIntervalMs ?? 0;
316
317
  }
317
318
  handleFrame(frame) {
318
- if (isSnapshotFrame(frame)) {
319
+ if (this.flushIntervalMs === 0) {
320
+ this.processFrame(frame);
321
+ return;
322
+ }
323
+ this.pendingUpdates.push({ frame });
324
+ this.scheduleFlush();
325
+ }
326
+ /**
327
+ * Immediately flush all pending updates.
328
+ * Useful for ensuring all updates are processed before reading state.
329
+ */
330
+ flush() {
331
+ if (this.flushTimer !== null) {
332
+ clearTimeout(this.flushTimer);
333
+ this.flushTimer = null;
334
+ }
335
+ this.flushPendingUpdates();
336
+ }
337
+ /**
338
+ * Clean up any pending timers. Call when disposing the processor.
339
+ */
340
+ dispose() {
341
+ if (this.flushTimer !== null) {
342
+ clearTimeout(this.flushTimer);
343
+ this.flushTimer = null;
344
+ }
345
+ this.pendingUpdates = [];
346
+ }
347
+ scheduleFlush() {
348
+ if (this.flushTimer !== null) {
349
+ return;
350
+ }
351
+ this.flushTimer = setTimeout(() => {
352
+ this.flushTimer = null;
353
+ this.flushPendingUpdates();
354
+ }, this.flushIntervalMs);
355
+ }
356
+ flushPendingUpdates() {
357
+ if (this.isProcessing || this.pendingUpdates.length === 0) {
358
+ return;
359
+ }
360
+ this.isProcessing = true;
361
+ const batch = this.pendingUpdates;
362
+ this.pendingUpdates = [];
363
+ const viewsToEnforce = new Set();
364
+ for (const { frame } of batch) {
365
+ const viewPath = this.processFrameWithoutEnforce(frame);
366
+ if (viewPath) {
367
+ viewsToEnforce.add(viewPath);
368
+ }
369
+ }
370
+ viewsToEnforce.forEach((viewPath) => {
371
+ this.enforceMaxEntries(viewPath);
372
+ });
373
+ this.isProcessing = false;
374
+ }
375
+ processFrame(frame) {
376
+ if (isSubscribedFrame(frame)) {
377
+ this.handleSubscribedFrame(frame);
378
+ }
379
+ else if (isSnapshotFrame(frame)) {
319
380
  this.handleSnapshotFrame(frame);
320
381
  }
321
382
  else {
322
383
  this.handleEntityFrame(frame);
323
384
  }
324
385
  }
386
+ processFrameWithoutEnforce(frame) {
387
+ if (isSubscribedFrame(frame)) {
388
+ this.handleSubscribedFrame(frame);
389
+ return null;
390
+ }
391
+ else if (isSnapshotFrame(frame)) {
392
+ this.handleSnapshotFrameWithoutEnforce(frame);
393
+ return frame.entity;
394
+ }
395
+ else {
396
+ this.handleEntityFrameWithoutEnforce(frame);
397
+ return frame.entity;
398
+ }
399
+ }
400
+ handleSubscribedFrame(frame) {
401
+ if (this.storage.setViewConfig && frame.sort) {
402
+ this.storage.setViewConfig(frame.view, { sort: frame.sort });
403
+ }
404
+ }
325
405
  handleSnapshotFrame(frame) {
406
+ this.handleSnapshotFrameWithoutEnforce(frame);
407
+ this.enforceMaxEntries(frame.entity);
408
+ }
409
+ handleSnapshotFrameWithoutEnforce(frame) {
326
410
  const viewPath = frame.entity;
327
411
  for (const entity of frame.data) {
328
412
  const previousValue = this.storage.get(viewPath, entity.key);
@@ -334,16 +418,18 @@ class FrameProcessor {
334
418
  });
335
419
  this.emitRichUpdate(viewPath, entity.key, previousValue, entity.data, 'upsert');
336
420
  }
337
- this.enforceMaxEntries(viewPath);
338
421
  }
339
422
  handleEntityFrame(frame) {
423
+ this.handleEntityFrameWithoutEnforce(frame);
424
+ this.enforceMaxEntries(frame.entity);
425
+ }
426
+ handleEntityFrameWithoutEnforce(frame) {
340
427
  const viewPath = frame.entity;
341
428
  const previousValue = this.storage.get(viewPath, frame.key);
342
429
  switch (frame.op) {
343
430
  case 'create':
344
431
  case 'upsert':
345
432
  this.storage.set(viewPath, frame.key, frame.data);
346
- this.enforceMaxEntries(viewPath);
347
433
  this.storage.notifyUpdate(viewPath, frame.key, {
348
434
  type: 'upsert',
349
435
  key: frame.key,
@@ -355,10 +441,9 @@ class FrameProcessor {
355
441
  const existing = this.storage.get(viewPath, frame.key);
356
442
  const appendPaths = frame.append ?? [];
357
443
  const merged = existing
358
- ? deepMergeWithAppend(existing, frame.data, appendPaths)
444
+ ? deepMergeWithAppend$1(existing, frame.data, appendPaths)
359
445
  : frame.data;
360
446
  this.storage.set(viewPath, frame.key, merged);
361
- this.enforceMaxEntries(viewPath);
362
447
  this.storage.notifyUpdate(viewPath, frame.key, {
363
448
  type: 'patch',
364
449
  key: frame.key,
@@ -397,7 +482,7 @@ class FrameProcessor {
397
482
  }
398
483
  }
399
484
 
400
- class ViewData {
485
+ let ViewData$1 = class ViewData {
401
486
  constructor() {
402
487
  this.entities = new Map();
403
488
  this.accessOrder = [];
@@ -451,7 +536,7 @@ class ViewData {
451
536
  this.entities.clear();
452
537
  this.accessOrder = [];
453
538
  }
454
- }
539
+ };
455
540
  class MemoryAdapter {
456
541
  constructor(_config = {}) {
457
542
  this.views = new Map();
@@ -499,7 +584,7 @@ class MemoryAdapter {
499
584
  set(viewPath, key, data) {
500
585
  let view = this.views.get(viewPath);
501
586
  if (!view) {
502
- view = new ViewData();
587
+ view = new ViewData$1();
503
588
  this.views.set(viewPath, view);
504
589
  }
505
590
  view.set(key, data);
@@ -840,5 +925,425 @@ class HyperStack {
840
925
  }
841
926
  }
842
927
 
843
- export { ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, FrameProcessor, HyperStack, HyperStackError, MemoryAdapter, SubscriptionRegistry, createRichUpdateStream, createTypedListView, createTypedStateView, createTypedViews, createUpdateStream, isSnapshotFrame, isValidFrame, parseFrame, parseFrameFromBlob };
928
+ function getNestedValue(obj, path) {
929
+ let current = obj;
930
+ for (const segment of path) {
931
+ if (current === null || current === undefined)
932
+ return undefined;
933
+ if (typeof current !== 'object')
934
+ return undefined;
935
+ current = current[segment];
936
+ }
937
+ return current;
938
+ }
939
+ function compareSortValues(a, b) {
940
+ if (a === b)
941
+ return 0;
942
+ if (a === undefined || a === null)
943
+ return -1;
944
+ if (b === undefined || b === null)
945
+ return 1;
946
+ if (typeof a === 'number' && typeof b === 'number') {
947
+ return a - b;
948
+ }
949
+ if (typeof a === 'string' && typeof b === 'string') {
950
+ return a.localeCompare(b);
951
+ }
952
+ if (typeof a === 'boolean' && typeof b === 'boolean') {
953
+ return (a ? 1 : 0) - (b ? 1 : 0);
954
+ }
955
+ return String(a).localeCompare(String(b));
956
+ }
957
+ class ViewData {
958
+ constructor(sortConfig) {
959
+ this.entities = new Map();
960
+ this.accessOrder = [];
961
+ this.sortedKeys = [];
962
+ this.sortConfig = sortConfig;
963
+ }
964
+ get(key) {
965
+ return this.entities.get(key);
966
+ }
967
+ set(key, value) {
968
+ const isNew = !this.entities.has(key);
969
+ this.entities.set(key, value);
970
+ if (this.sortConfig) {
971
+ this.updateSortedPosition(key, value, isNew);
972
+ }
973
+ else {
974
+ if (isNew) {
975
+ this.accessOrder.push(key);
976
+ }
977
+ else {
978
+ this.touch(key);
979
+ }
980
+ }
981
+ }
982
+ updateSortedPosition(key, value, isNew) {
983
+ if (!isNew) {
984
+ const existingIdx = this.sortedKeys.indexOf(key);
985
+ if (existingIdx !== -1) {
986
+ this.sortedKeys.splice(existingIdx, 1);
987
+ }
988
+ }
989
+ const sortValue = getNestedValue(value, this.sortConfig.field);
990
+ const isDesc = this.sortConfig.order === 'desc';
991
+ let insertIdx = this.binarySearchInsertPosition(sortValue, key, isDesc);
992
+ this.sortedKeys.splice(insertIdx, 0, key);
993
+ }
994
+ binarySearchInsertPosition(sortValue, key, isDesc) {
995
+ let low = 0;
996
+ let high = this.sortedKeys.length;
997
+ while (low < high) {
998
+ const mid = Math.floor((low + high) / 2);
999
+ const midKey = this.sortedKeys[mid];
1000
+ const midEntity = this.entities.get(midKey);
1001
+ const midValue = getNestedValue(midEntity, this.sortConfig.field);
1002
+ let cmp = compareSortValues(sortValue, midValue);
1003
+ if (isDesc)
1004
+ cmp = -cmp;
1005
+ if (cmp === 0) {
1006
+ cmp = key.localeCompare(midKey);
1007
+ }
1008
+ if (cmp < 0) {
1009
+ high = mid;
1010
+ }
1011
+ else {
1012
+ low = mid + 1;
1013
+ }
1014
+ }
1015
+ return low;
1016
+ }
1017
+ delete(key) {
1018
+ if (this.sortConfig) {
1019
+ const idx = this.sortedKeys.indexOf(key);
1020
+ if (idx !== -1) {
1021
+ this.sortedKeys.splice(idx, 1);
1022
+ }
1023
+ }
1024
+ else {
1025
+ const idx = this.accessOrder.indexOf(key);
1026
+ if (idx !== -1) {
1027
+ this.accessOrder.splice(idx, 1);
1028
+ }
1029
+ }
1030
+ return this.entities.delete(key);
1031
+ }
1032
+ has(key) {
1033
+ return this.entities.has(key);
1034
+ }
1035
+ values() {
1036
+ if (this.sortConfig) {
1037
+ return this.sortedKeys.map(k => this.entities.get(k));
1038
+ }
1039
+ return Array.from(this.entities.values());
1040
+ }
1041
+ keys() {
1042
+ if (this.sortConfig) {
1043
+ return [...this.sortedKeys];
1044
+ }
1045
+ return Array.from(this.entities.keys());
1046
+ }
1047
+ get size() {
1048
+ return this.entities.size;
1049
+ }
1050
+ touch(key) {
1051
+ if (this.sortConfig)
1052
+ return;
1053
+ const idx = this.accessOrder.indexOf(key);
1054
+ if (idx !== -1) {
1055
+ this.accessOrder.splice(idx, 1);
1056
+ this.accessOrder.push(key);
1057
+ }
1058
+ }
1059
+ evictOldest() {
1060
+ if (this.sortConfig) {
1061
+ const oldest = this.sortedKeys.pop();
1062
+ if (oldest !== undefined) {
1063
+ this.entities.delete(oldest);
1064
+ }
1065
+ return oldest;
1066
+ }
1067
+ const oldest = this.accessOrder.shift();
1068
+ if (oldest !== undefined) {
1069
+ this.entities.delete(oldest);
1070
+ }
1071
+ return oldest;
1072
+ }
1073
+ setSortConfig(config) {
1074
+ if (this.sortConfig)
1075
+ return;
1076
+ this.sortConfig = config;
1077
+ this.rebuildSortedKeys();
1078
+ }
1079
+ rebuildSortedKeys() {
1080
+ if (!this.sortConfig)
1081
+ return;
1082
+ const entries = Array.from(this.entities.entries());
1083
+ const isDesc = this.sortConfig.order === 'desc';
1084
+ entries.sort((a, b) => {
1085
+ const aValue = getNestedValue(a[1], this.sortConfig.field);
1086
+ const bValue = getNestedValue(b[1], this.sortConfig.field);
1087
+ let cmp = compareSortValues(aValue, bValue);
1088
+ if (isDesc)
1089
+ cmp = -cmp;
1090
+ if (cmp === 0) {
1091
+ cmp = a[0].localeCompare(b[0]);
1092
+ }
1093
+ return cmp;
1094
+ });
1095
+ this.sortedKeys = entries.map(([k]) => k);
1096
+ this.accessOrder = [];
1097
+ }
1098
+ getSortConfig() {
1099
+ return this.sortConfig;
1100
+ }
1101
+ }
1102
+ function isObject(item) {
1103
+ return item !== null && typeof item === 'object' && !Array.isArray(item);
1104
+ }
1105
+ function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
1106
+ if (!isObject(target) || !isObject(source)) {
1107
+ return source;
1108
+ }
1109
+ const result = { ...target };
1110
+ for (const key in source) {
1111
+ const sourceValue = source[key];
1112
+ const targetValue = result[key];
1113
+ const fieldPath = currentPath ? `${currentPath}.${key}` : key;
1114
+ if (Array.isArray(sourceValue) && Array.isArray(targetValue)) {
1115
+ if (appendPaths.includes(fieldPath)) {
1116
+ result[key] = [...targetValue, ...sourceValue];
1117
+ }
1118
+ else {
1119
+ result[key] = sourceValue;
1120
+ }
1121
+ }
1122
+ else if (isObject(sourceValue) && isObject(targetValue)) {
1123
+ result[key] = deepMergeWithAppend(targetValue, sourceValue, appendPaths, fieldPath);
1124
+ }
1125
+ else {
1126
+ result[key] = sourceValue;
1127
+ }
1128
+ }
1129
+ return result;
1130
+ }
1131
+ class EntityStore {
1132
+ constructor(config = {}) {
1133
+ this.views = new Map();
1134
+ this.viewConfigs = new Map();
1135
+ this.updateCallbacks = new Set();
1136
+ this.richUpdateCallbacks = new Set();
1137
+ this.maxEntriesPerView = config.maxEntriesPerView === undefined
1138
+ ? DEFAULT_MAX_ENTRIES_PER_VIEW
1139
+ : config.maxEntriesPerView;
1140
+ }
1141
+ enforceMaxEntries(viewData) {
1142
+ if (this.maxEntriesPerView === null)
1143
+ return;
1144
+ while (viewData.size > this.maxEntriesPerView) {
1145
+ viewData.evictOldest();
1146
+ }
1147
+ }
1148
+ handleFrame(frame) {
1149
+ if (isSubscribedFrame(frame)) {
1150
+ this.handleSubscribedFrame(frame);
1151
+ return;
1152
+ }
1153
+ if (isSnapshotFrame(frame)) {
1154
+ this.handleSnapshotFrame(frame);
1155
+ return;
1156
+ }
1157
+ this.handleEntityFrame(frame);
1158
+ }
1159
+ handleSubscribedFrame(frame) {
1160
+ const viewPath = frame.view;
1161
+ const config = {};
1162
+ if (frame.sort) {
1163
+ config.sort = frame.sort;
1164
+ }
1165
+ this.viewConfigs.set(viewPath, config);
1166
+ const existingView = this.views.get(viewPath);
1167
+ if (existingView && frame.sort) {
1168
+ existingView.setSortConfig(frame.sort);
1169
+ }
1170
+ }
1171
+ handleSnapshotFrame(frame) {
1172
+ const viewPath = frame.entity;
1173
+ let viewData = this.views.get(viewPath);
1174
+ const viewConfig = this.viewConfigs.get(viewPath);
1175
+ if (!viewData) {
1176
+ viewData = new ViewData(viewConfig?.sort);
1177
+ this.views.set(viewPath, viewData);
1178
+ }
1179
+ for (const entity of frame.data) {
1180
+ const previousValue = viewData.get(entity.key);
1181
+ viewData.set(entity.key, entity.data);
1182
+ this.notifyUpdate(viewPath, entity.key, {
1183
+ type: 'upsert',
1184
+ key: entity.key,
1185
+ data: entity.data,
1186
+ });
1187
+ this.notifyRichUpdate(viewPath, entity.key, previousValue, entity.data, 'upsert');
1188
+ }
1189
+ this.enforceMaxEntries(viewData);
1190
+ }
1191
+ handleEntityFrame(frame) {
1192
+ const viewPath = frame.entity;
1193
+ let viewData = this.views.get(viewPath);
1194
+ const viewConfig = this.viewConfigs.get(viewPath);
1195
+ if (!viewData) {
1196
+ viewData = new ViewData(viewConfig?.sort);
1197
+ this.views.set(viewPath, viewData);
1198
+ }
1199
+ const previousValue = viewData.get(frame.key);
1200
+ switch (frame.op) {
1201
+ case 'create':
1202
+ case 'upsert':
1203
+ viewData.set(frame.key, frame.data);
1204
+ this.enforceMaxEntries(viewData);
1205
+ this.notifyUpdate(viewPath, frame.key, {
1206
+ type: 'upsert',
1207
+ key: frame.key,
1208
+ data: frame.data,
1209
+ });
1210
+ this.notifyRichUpdate(viewPath, frame.key, previousValue, frame.data, frame.op);
1211
+ break;
1212
+ case 'patch': {
1213
+ const existing = viewData.get(frame.key);
1214
+ const appendPaths = frame.append ?? [];
1215
+ const merged = existing
1216
+ ? deepMergeWithAppend(existing, frame.data, appendPaths)
1217
+ : frame.data;
1218
+ viewData.set(frame.key, merged);
1219
+ this.enforceMaxEntries(viewData);
1220
+ this.notifyUpdate(viewPath, frame.key, {
1221
+ type: 'patch',
1222
+ key: frame.key,
1223
+ data: frame.data,
1224
+ });
1225
+ this.notifyRichUpdate(viewPath, frame.key, previousValue, merged, 'patch', frame.data);
1226
+ break;
1227
+ }
1228
+ case 'delete':
1229
+ viewData.delete(frame.key);
1230
+ this.notifyUpdate(viewPath, frame.key, {
1231
+ type: 'delete',
1232
+ key: frame.key,
1233
+ });
1234
+ if (previousValue !== undefined) {
1235
+ this.notifyRichDelete(viewPath, frame.key, previousValue);
1236
+ }
1237
+ break;
1238
+ }
1239
+ }
1240
+ getAll(viewPath) {
1241
+ const viewData = this.views.get(viewPath);
1242
+ if (!viewData)
1243
+ return [];
1244
+ return viewData.values();
1245
+ }
1246
+ get(viewPath, key) {
1247
+ const viewData = this.views.get(viewPath);
1248
+ if (!viewData)
1249
+ return null;
1250
+ const value = viewData.get(key);
1251
+ return value !== undefined ? value : null;
1252
+ }
1253
+ getAllSync(viewPath) {
1254
+ const viewData = this.views.get(viewPath);
1255
+ if (!viewData)
1256
+ return undefined;
1257
+ return viewData.values();
1258
+ }
1259
+ getSync(viewPath, key) {
1260
+ const viewData = this.views.get(viewPath);
1261
+ if (!viewData)
1262
+ return undefined;
1263
+ const value = viewData.get(key);
1264
+ return value !== undefined ? value : null;
1265
+ }
1266
+ keys(viewPath) {
1267
+ const viewData = this.views.get(viewPath);
1268
+ if (!viewData)
1269
+ return [];
1270
+ return viewData.keys();
1271
+ }
1272
+ size(viewPath) {
1273
+ const viewData = this.views.get(viewPath);
1274
+ return viewData?.size ?? 0;
1275
+ }
1276
+ clear() {
1277
+ this.views.clear();
1278
+ }
1279
+ clearView(viewPath) {
1280
+ this.views.delete(viewPath);
1281
+ this.viewConfigs.delete(viewPath);
1282
+ }
1283
+ getViewConfig(viewPath) {
1284
+ return this.viewConfigs.get(viewPath);
1285
+ }
1286
+ setViewConfig(viewPath, config) {
1287
+ this.viewConfigs.set(viewPath, config);
1288
+ const existingView = this.views.get(viewPath);
1289
+ if (existingView && config.sort) {
1290
+ existingView.setSortConfig(config.sort);
1291
+ }
1292
+ }
1293
+ onUpdate(callback) {
1294
+ this.updateCallbacks.add(callback);
1295
+ return () => {
1296
+ this.updateCallbacks.delete(callback);
1297
+ };
1298
+ }
1299
+ onRichUpdate(callback) {
1300
+ this.richUpdateCallbacks.add(callback);
1301
+ return () => {
1302
+ this.richUpdateCallbacks.delete(callback);
1303
+ };
1304
+ }
1305
+ subscribe(viewPath, callback) {
1306
+ const handler = (path, _key, update) => {
1307
+ if (path === viewPath) {
1308
+ callback(update);
1309
+ }
1310
+ };
1311
+ this.updateCallbacks.add(handler);
1312
+ return () => {
1313
+ this.updateCallbacks.delete(handler);
1314
+ };
1315
+ }
1316
+ subscribeToKey(viewPath, key, callback) {
1317
+ const handler = (path, updateKey, update) => {
1318
+ if (path === viewPath && updateKey === key) {
1319
+ callback(update);
1320
+ }
1321
+ };
1322
+ this.updateCallbacks.add(handler);
1323
+ return () => {
1324
+ this.updateCallbacks.delete(handler);
1325
+ };
1326
+ }
1327
+ notifyUpdate(viewPath, key, update) {
1328
+ for (const callback of this.updateCallbacks) {
1329
+ callback(viewPath, key, update);
1330
+ }
1331
+ }
1332
+ notifyRichUpdate(viewPath, key, before, after, _op, patch) {
1333
+ const richUpdate = before === undefined
1334
+ ? { type: 'created', key, data: after }
1335
+ : { type: 'updated', key, before, after, patch };
1336
+ for (const callback of this.richUpdateCallbacks) {
1337
+ callback(viewPath, key, richUpdate);
1338
+ }
1339
+ }
1340
+ notifyRichDelete(viewPath, key, lastKnown) {
1341
+ const richUpdate = { type: 'deleted', key, lastKnown };
1342
+ for (const callback of this.richUpdateCallbacks) {
1343
+ callback(viewPath, key, richUpdate);
1344
+ }
1345
+ }
1346
+ }
1347
+
1348
+ export { ConnectionManager, DEFAULT_CONFIG, DEFAULT_MAX_ENTRIES_PER_VIEW, EntityStore, FrameProcessor, HyperStack, HyperStackError, MemoryAdapter, SubscriptionRegistry, createRichUpdateStream, createTypedListView, createTypedStateView, createTypedViews, createUpdateStream, isEntityFrame, isSnapshotFrame, isSubscribedFrame, isValidFrame, parseFrame, parseFrameFromBlob };
844
1349
  //# sourceMappingURL=index.esm.js.map