hyperstack-typescript 0.3.2 → 0.3.4

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
@@ -23,6 +23,12 @@ function isGzipData(data) {
23
23
  function isSnapshotFrame(frame) {
24
24
  return frame.op === 'snapshot';
25
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
+ }
26
32
  function parseFrame(data) {
27
33
  if (typeof data === 'string') {
28
34
  return JSON.parse(data);
@@ -131,7 +137,6 @@ class ConnectionManager {
131
137
  this.notifyFrameHandlers(frame);
132
138
  }
133
139
  catch (error) {
134
- console.error('[hyperstack] Error parsing frame:', error);
135
140
  this.updateState('error', 'Failed to parse frame from server');
136
141
  }
137
142
  };
@@ -170,21 +175,15 @@ class ConnectionManager {
170
175
  const subKey = this.makeSubKey(subscription);
171
176
  if (this.currentState === 'connected' && this.ws?.readyState === WebSocket.OPEN) {
172
177
  if (this.activeSubscriptions.has(subKey)) {
173
- console.log('[hyperstack] Skipping already active subscription:', subKey);
174
178
  return;
175
179
  }
176
180
  const subMsg = { type: 'subscribe', ...subscription };
177
- console.log('[hyperstack] Sending subscribe:', subKey);
178
181
  this.ws.send(JSON.stringify(subMsg));
179
182
  this.activeSubscriptions.add(subKey);
180
183
  }
181
184
  else {
182
185
  const alreadyQueued = this.subscriptionQueue.some((s) => this.makeSubKey(s) === subKey);
183
- if (alreadyQueued) {
184
- console.log('[hyperstack] Skipping duplicate queue entry:', subKey);
185
- }
186
- else {
187
- console.log('[hyperstack] Queuing subscription:', subKey, '| Queue:', this.subscriptionQueue.map(s => this.makeSubKey(s)));
186
+ if (!alreadyQueued) {
188
187
  this.subscriptionQueue.push(subscription);
189
188
  }
190
189
  }
@@ -207,7 +206,6 @@ class ConnectionManager {
207
206
  return `${subscription.view}:${subscription.key ?? '*'}:${subscription.partition ?? ''}`;
208
207
  }
209
208
  flushSubscriptionQueue() {
210
- console.log('[hyperstack] Flushing subscription queue:', this.subscriptionQueue.map(s => this.makeSubKey(s)));
211
209
  while (this.subscriptionQueue.length > 0) {
212
210
  const sub = this.subscriptionQueue.shift();
213
211
  if (sub) {
@@ -216,7 +214,6 @@ class ConnectionManager {
216
214
  }
217
215
  }
218
216
  resubscribeActive() {
219
- console.log('[hyperstack] Resubscribing active:', Array.from(this.activeSubscriptions));
220
217
  for (const subKey of this.activeSubscriptions) {
221
218
  const [view, key, partition] = subKey.split(':');
222
219
  const subscription = {
@@ -226,7 +223,6 @@ class ConnectionManager {
226
223
  };
227
224
  if (this.ws?.readyState === WebSocket.OPEN) {
228
225
  const subMsg = { type: 'subscribe', ...subscription };
229
- console.log('[hyperstack] Resubscribe sending:', subKey);
230
226
  this.ws.send(JSON.stringify(subMsg));
231
227
  }
232
228
  }
@@ -279,11 +275,11 @@ class ConnectionManager {
279
275
  }
280
276
  }
281
277
 
282
- function isObject(item) {
278
+ function isObject$1(item) {
283
279
  return item !== null && typeof item === 'object' && !Array.isArray(item);
284
280
  }
285
- function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
286
- if (!isObject(target) || !isObject(source)) {
281
+ function deepMergeWithAppend$1(target, source, appendPaths, currentPath = '') {
282
+ if (!isObject$1(target) || !isObject$1(source)) {
287
283
  return source;
288
284
  }
289
285
  const result = { ...target };
@@ -299,8 +295,8 @@ function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
299
295
  result[key] = sourceValue;
300
296
  }
301
297
  }
302
- else if (isObject(sourceValue) && isObject(targetValue)) {
303
- 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);
304
300
  }
305
301
  else {
306
302
  result[key] = sourceValue;
@@ -310,20 +306,107 @@ function deepMergeWithAppend(target, source, appendPaths, currentPath = '') {
310
306
  }
311
307
  class FrameProcessor {
312
308
  constructor(storage, config = {}) {
309
+ this.pendingUpdates = [];
310
+ this.flushTimer = null;
311
+ this.isProcessing = false;
313
312
  this.storage = storage;
314
313
  this.maxEntriesPerView = config.maxEntriesPerView === undefined
315
314
  ? DEFAULT_MAX_ENTRIES_PER_VIEW
316
315
  : config.maxEntriesPerView;
316
+ this.flushIntervalMs = config.flushIntervalMs ?? 0;
317
317
  }
318
318
  handleFrame(frame) {
319
- 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)) {
320
380
  this.handleSnapshotFrame(frame);
321
381
  }
322
382
  else {
323
383
  this.handleEntityFrame(frame);
324
384
  }
325
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
+ }
326
405
  handleSnapshotFrame(frame) {
406
+ this.handleSnapshotFrameWithoutEnforce(frame);
407
+ this.enforceMaxEntries(frame.entity);
408
+ }
409
+ handleSnapshotFrameWithoutEnforce(frame) {
327
410
  const viewPath = frame.entity;
328
411
  for (const entity of frame.data) {
329
412
  const previousValue = this.storage.get(viewPath, entity.key);
@@ -335,16 +418,18 @@ class FrameProcessor {
335
418
  });
336
419
  this.emitRichUpdate(viewPath, entity.key, previousValue, entity.data, 'upsert');
337
420
  }
338
- this.enforceMaxEntries(viewPath);
339
421
  }
340
422
  handleEntityFrame(frame) {
423
+ this.handleEntityFrameWithoutEnforce(frame);
424
+ this.enforceMaxEntries(frame.entity);
425
+ }
426
+ handleEntityFrameWithoutEnforce(frame) {
341
427
  const viewPath = frame.entity;
342
428
  const previousValue = this.storage.get(viewPath, frame.key);
343
429
  switch (frame.op) {
344
430
  case 'create':
345
431
  case 'upsert':
346
432
  this.storage.set(viewPath, frame.key, frame.data);
347
- this.enforceMaxEntries(viewPath);
348
433
  this.storage.notifyUpdate(viewPath, frame.key, {
349
434
  type: 'upsert',
350
435
  key: frame.key,
@@ -356,10 +441,9 @@ class FrameProcessor {
356
441
  const existing = this.storage.get(viewPath, frame.key);
357
442
  const appendPaths = frame.append ?? [];
358
443
  const merged = existing
359
- ? deepMergeWithAppend(existing, frame.data, appendPaths)
444
+ ? deepMergeWithAppend$1(existing, frame.data, appendPaths)
360
445
  : frame.data;
361
446
  this.storage.set(viewPath, frame.key, merged);
362
- this.enforceMaxEntries(viewPath);
363
447
  this.storage.notifyUpdate(viewPath, frame.key, {
364
448
  type: 'patch',
365
449
  key: frame.key,
@@ -398,7 +482,7 @@ class FrameProcessor {
398
482
  }
399
483
  }
400
484
 
401
- class ViewData {
485
+ let ViewData$1 = class ViewData {
402
486
  constructor() {
403
487
  this.entities = new Map();
404
488
  this.accessOrder = [];
@@ -452,7 +536,7 @@ class ViewData {
452
536
  this.entities.clear();
453
537
  this.accessOrder = [];
454
538
  }
455
- }
539
+ };
456
540
  class MemoryAdapter {
457
541
  constructor(_config = {}) {
458
542
  this.views = new Map();
@@ -500,7 +584,7 @@ class MemoryAdapter {
500
584
  set(viewPath, key, data) {
501
585
  let view = this.views.get(viewPath);
502
586
  if (!view) {
503
- view = new ViewData();
587
+ view = new ViewData$1();
504
588
  this.views.set(viewPath, view);
505
589
  }
506
590
  view.set(key, data);
@@ -841,5 +925,425 @@ class HyperStack {
841
925
  }
842
926
  }
843
927
 
844
- 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 };
845
1349
  //# sourceMappingURL=index.esm.js.map