@sylphx/lens-solid 2.3.3 → 2.3.5

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 (2) hide show
  1. package/dist/index.js +1241 -321
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -464,6 +464,229 @@ class EntityBuilder_ {
464
464
  return createEntityDef(this._name, fields);
465
465
  }
466
466
  }
467
+ var valueStrategy = {
468
+ name: "value",
469
+ encode(_prev, next) {
470
+ return { strategy: "value", data: next };
471
+ },
472
+ decode(_current, update) {
473
+ return update.data;
474
+ },
475
+ estimateSize(update) {
476
+ return JSON.stringify(update.data).length;
477
+ }
478
+ };
479
+ var deltaStrategy = {
480
+ name: "delta",
481
+ encode(prev, next) {
482
+ const operations2 = computeStringDiff(prev, next);
483
+ const diffSize = JSON.stringify(operations2).length;
484
+ const valueSize = next.length + 20;
485
+ if (diffSize >= valueSize) {
486
+ return { strategy: "value", data: next };
487
+ }
488
+ return { strategy: "delta", data: operations2 };
489
+ },
490
+ decode(current, update) {
491
+ if (update.strategy === "value") {
492
+ return update.data;
493
+ }
494
+ const operations2 = update.data;
495
+ return applyStringDiff(current, operations2);
496
+ },
497
+ estimateSize(update) {
498
+ return JSON.stringify(update.data).length;
499
+ }
500
+ };
501
+ function computeStringDiff(prev, next) {
502
+ const operations2 = [];
503
+ let prefixLen = 0;
504
+ const minLen = Math.min(prev.length, next.length);
505
+ while (prefixLen < minLen && prev[prefixLen] === next[prefixLen]) {
506
+ prefixLen++;
507
+ }
508
+ let suffixLen = 0;
509
+ const remainingPrev = prev.length - prefixLen;
510
+ const remainingNext = next.length - prefixLen;
511
+ const maxSuffix = Math.min(remainingPrev, remainingNext);
512
+ while (suffixLen < maxSuffix && prev[prev.length - 1 - suffixLen] === next[next.length - 1 - suffixLen]) {
513
+ suffixLen++;
514
+ }
515
+ const deleteCount = prev.length - prefixLen - suffixLen;
516
+ const insertText = next.slice(prefixLen, next.length - suffixLen || undefined);
517
+ if (deleteCount > 0 || insertText.length > 0) {
518
+ operations2.push({
519
+ position: prefixLen,
520
+ ...deleteCount > 0 ? { delete: deleteCount } : {},
521
+ ...insertText.length > 0 ? { insert: insertText } : {}
522
+ });
523
+ }
524
+ return operations2;
525
+ }
526
+ function applyStringDiff(current, operations2) {
527
+ let result = current;
528
+ const sortedOps = [...operations2].sort((a, b) => b.position - a.position);
529
+ for (const op2 of sortedOps) {
530
+ const before = result.slice(0, op2.position);
531
+ const after = result.slice(op2.position + (op2.delete ?? 0));
532
+ result = before + (op2.insert ?? "") + after;
533
+ }
534
+ return result;
535
+ }
536
+ var patchStrategy = {
537
+ name: "patch",
538
+ encode(prev, next) {
539
+ const operations2 = computeJsonPatch(prev, next);
540
+ const patchSize = JSON.stringify(operations2).length;
541
+ const valueSize = JSON.stringify(next).length + 20;
542
+ if (patchSize >= valueSize) {
543
+ return { strategy: "value", data: next };
544
+ }
545
+ return { strategy: "patch", data: operations2 };
546
+ },
547
+ decode(current, update) {
548
+ if (update.strategy === "value") {
549
+ return update.data;
550
+ }
551
+ const operations2 = update.data;
552
+ return applyJsonPatch(current, operations2);
553
+ },
554
+ estimateSize(update) {
555
+ return JSON.stringify(update.data).length;
556
+ }
557
+ };
558
+ function computeJsonPatch(prev, next, basePath = "") {
559
+ const operations2 = [];
560
+ const prevObj = prev;
561
+ const nextObj = next;
562
+ for (const key of Object.keys(prevObj)) {
563
+ if (!(key in nextObj)) {
564
+ operations2.push({ op: "remove", path: `${basePath}/${escapeJsonPointer(key)}` });
565
+ }
566
+ }
567
+ for (const [key, nextValue] of Object.entries(nextObj)) {
568
+ const path = `${basePath}/${escapeJsonPointer(key)}`;
569
+ const prevValue = prevObj[key];
570
+ if (!(key in prevObj)) {
571
+ operations2.push({ op: "add", path, value: nextValue });
572
+ } else if (!deepEqual2(prevValue, nextValue)) {
573
+ if (isPlainObject(prevValue) && isPlainObject(nextValue) && !Array.isArray(prevValue) && !Array.isArray(nextValue)) {
574
+ operations2.push(...computeJsonPatch(prevValue, nextValue, path));
575
+ } else {
576
+ operations2.push({ op: "replace", path, value: nextValue });
577
+ }
578
+ }
579
+ }
580
+ return operations2;
581
+ }
582
+ function applyJsonPatch(current, operations2) {
583
+ const result = structuredClone(current);
584
+ for (const op2 of operations2) {
585
+ const pathParts = parseJsonPointer(op2.path);
586
+ switch (op2.op) {
587
+ case "add":
588
+ case "replace":
589
+ setValueAtPath(result, pathParts, op2.value);
590
+ break;
591
+ case "remove":
592
+ removeValueAtPath(result, pathParts);
593
+ break;
594
+ case "move":
595
+ if (op2.from) {
596
+ const fromParts = parseJsonPointer(op2.from);
597
+ const value = getValueAtPath(result, fromParts);
598
+ removeValueAtPath(result, fromParts);
599
+ setValueAtPath(result, pathParts, value);
600
+ }
601
+ break;
602
+ case "copy":
603
+ if (op2.from) {
604
+ const fromParts = parseJsonPointer(op2.from);
605
+ const value = structuredClone(getValueAtPath(result, fromParts));
606
+ setValueAtPath(result, pathParts, value);
607
+ }
608
+ break;
609
+ case "test":
610
+ break;
611
+ }
612
+ }
613
+ return result;
614
+ }
615
+ function applyUpdate(current, update) {
616
+ switch (update.strategy) {
617
+ case "value":
618
+ return valueStrategy.decode(current, update);
619
+ case "delta":
620
+ return deltaStrategy.decode(current, update);
621
+ case "patch":
622
+ return patchStrategy.decode(current, update);
623
+ default:
624
+ return update.data;
625
+ }
626
+ }
627
+ function isPlainObject(value) {
628
+ return typeof value === "object" && value !== null && !Array.isArray(value);
629
+ }
630
+ function deepEqual2(a, b) {
631
+ if (a === b)
632
+ return true;
633
+ if (typeof a !== typeof b)
634
+ return false;
635
+ if (typeof a !== "object" || a === null || b === null)
636
+ return false;
637
+ const aKeys = Object.keys(a);
638
+ const bKeys = Object.keys(b);
639
+ if (aKeys.length !== bKeys.length)
640
+ return false;
641
+ for (const key of aKeys) {
642
+ if (!deepEqual2(a[key], b[key])) {
643
+ return false;
644
+ }
645
+ }
646
+ return true;
647
+ }
648
+ function escapeJsonPointer(str) {
649
+ return str.replace(/~/g, "~0").replace(/\//g, "~1");
650
+ }
651
+ function parseJsonPointer(path) {
652
+ if (!path || path === "/")
653
+ return [];
654
+ return path.slice(1).split("/").map((p) => p.replace(/~1/g, "/").replace(/~0/g, "~"));
655
+ }
656
+ function getValueAtPath(obj, path) {
657
+ let current = obj;
658
+ for (const key of path) {
659
+ if (current === null || typeof current !== "object")
660
+ return;
661
+ current = current[key];
662
+ }
663
+ return current;
664
+ }
665
+ function setValueAtPath(obj, path, value) {
666
+ if (path.length === 0)
667
+ return;
668
+ let current = obj;
669
+ for (let i = 0;i < path.length - 1; i++) {
670
+ const key = path[i];
671
+ if (!(key in current) || typeof current[key] !== "object") {
672
+ current[key] = {};
673
+ }
674
+ current = current[key];
675
+ }
676
+ current[path[path.length - 1]] = value;
677
+ }
678
+ function removeValueAtPath(obj, path) {
679
+ if (path.length === 0)
680
+ return;
681
+ let current = obj;
682
+ for (let i = 0;i < path.length - 1; i++) {
683
+ const key = path[i];
684
+ if (!(key in current) || typeof current[key] !== "object")
685
+ return;
686
+ current = current[key];
687
+ }
688
+ delete current[path[path.length - 1]];
689
+ }
467
690
  var OPTIMISTIC_PLUGIN_SYMBOL = Symbol.for("lens:optimistic-plugin");
468
691
  var DEFAULT_OPERATION_LOG_CONFIG = {
469
692
  maxEntries: 1e4,
@@ -836,18 +1059,744 @@ class ReconnectionMetricsTracker {
836
1059
  for (const result of results) {
837
1060
  counts[result.status] = (counts[result.status] ?? 0) + 1;
838
1061
  }
839
- return counts;
840
- }
841
- percentile(p) {
842
- if (this.latencies.length === 0)
843
- return 0;
844
- const sorted = [...this.latencies].sort((a, b) => a - b);
845
- const index = Math.ceil(p / 100 * sorted.length) - 1;
846
- return sorted[Math.max(0, index)];
1062
+ return counts;
1063
+ }
1064
+ percentile(p) {
1065
+ if (this.latencies.length === 0)
1066
+ return 0;
1067
+ const sorted = [...this.latencies].sort((a, b) => a - b);
1068
+ const index = Math.ceil(p / 100 * sorted.length) - 1;
1069
+ return sorted[Math.max(0, index)];
1070
+ }
1071
+ }
1072
+ class SubscriptionRegistry {
1073
+ subscriptions = new Map;
1074
+ entityIndex = new Map;
1075
+ add(sub) {
1076
+ const tracked = {
1077
+ ...sub,
1078
+ state: "pending",
1079
+ lastDataHash: sub.lastData ? hashEntityState(sub.lastData) : null,
1080
+ createdAt: Date.now(),
1081
+ lastUpdateAt: null
1082
+ };
1083
+ this.subscriptions.set(sub.id, tracked);
1084
+ const entityKey = `${sub.entity}:${sub.entityId}`;
1085
+ let ids = this.entityIndex.get(entityKey);
1086
+ if (!ids) {
1087
+ ids = new Set;
1088
+ this.entityIndex.set(entityKey, ids);
1089
+ }
1090
+ ids.add(sub.id);
1091
+ }
1092
+ get(id2) {
1093
+ return this.subscriptions.get(id2);
1094
+ }
1095
+ has(id2) {
1096
+ return this.subscriptions.has(id2);
1097
+ }
1098
+ remove(id2) {
1099
+ const sub = this.subscriptions.get(id2);
1100
+ if (!sub)
1101
+ return;
1102
+ this.subscriptions.delete(id2);
1103
+ const entityKey = `${sub.entity}:${sub.entityId}`;
1104
+ const ids = this.entityIndex.get(entityKey);
1105
+ if (ids) {
1106
+ ids.delete(id2);
1107
+ if (ids.size === 0) {
1108
+ this.entityIndex.delete(entityKey);
1109
+ }
1110
+ }
1111
+ }
1112
+ getByEntity(entity22, entityId) {
1113
+ const entityKey = `${entity22}:${entityId}`;
1114
+ const ids = this.entityIndex.get(entityKey);
1115
+ if (!ids)
1116
+ return [];
1117
+ const result = [];
1118
+ for (const id2 of ids) {
1119
+ const sub = this.subscriptions.get(id2);
1120
+ if (sub) {
1121
+ result.push(sub);
1122
+ }
1123
+ }
1124
+ return result;
1125
+ }
1126
+ updateVersion(id2, version, data) {
1127
+ const sub = this.subscriptions.get(id2);
1128
+ if (!sub)
1129
+ return;
1130
+ sub.version = version;
1131
+ sub.lastUpdateAt = Date.now();
1132
+ if (data !== undefined) {
1133
+ sub.lastData = data;
1134
+ sub.lastDataHash = hashEntityState(data);
1135
+ }
1136
+ if (sub.state === "pending" || sub.state === "reconnecting") {
1137
+ sub.state = "active";
1138
+ }
1139
+ }
1140
+ updateData(id2, data) {
1141
+ const sub = this.subscriptions.get(id2);
1142
+ if (!sub)
1143
+ return;
1144
+ sub.lastData = data;
1145
+ sub.lastDataHash = hashEntityState(data);
1146
+ }
1147
+ getLastData(id2) {
1148
+ return this.subscriptions.get(id2)?.lastData ?? null;
1149
+ }
1150
+ getVersion(id2) {
1151
+ return this.subscriptions.get(id2)?.version ?? null;
1152
+ }
1153
+ markActive(id2) {
1154
+ const sub = this.subscriptions.get(id2);
1155
+ if (sub) {
1156
+ sub.state = "active";
1157
+ }
1158
+ }
1159
+ markError(id2) {
1160
+ const sub = this.subscriptions.get(id2);
1161
+ if (sub) {
1162
+ sub.state = "error";
1163
+ }
1164
+ }
1165
+ markAllReconnecting() {
1166
+ for (const sub of this.subscriptions.values()) {
1167
+ if (sub.state === "active") {
1168
+ sub.state = "reconnecting";
1169
+ }
1170
+ }
1171
+ }
1172
+ getByState(state) {
1173
+ const result = [];
1174
+ for (const sub of this.subscriptions.values()) {
1175
+ if (sub.state === state) {
1176
+ result.push(sub);
1177
+ }
1178
+ }
1179
+ return result;
1180
+ }
1181
+ getAllForReconnect() {
1182
+ const result = [];
1183
+ for (const sub of this.subscriptions.values()) {
1184
+ if (sub.state === "reconnecting" || sub.state === "active") {
1185
+ const reconnectSub = {
1186
+ id: sub.id,
1187
+ entity: sub.entity,
1188
+ entityId: sub.entityId,
1189
+ fields: sub.fields,
1190
+ version: sub.version,
1191
+ input: sub.input
1192
+ };
1193
+ if (sub.lastDataHash) {
1194
+ reconnectSub.dataHash = sub.lastDataHash;
1195
+ }
1196
+ result.push(reconnectSub);
1197
+ }
1198
+ }
1199
+ return result;
1200
+ }
1201
+ processReconnectResult(id2, version, data) {
1202
+ const sub = this.subscriptions.get(id2);
1203
+ if (!sub)
1204
+ return;
1205
+ sub.version = version;
1206
+ sub.state = "active";
1207
+ sub.lastUpdateAt = Date.now();
1208
+ if (data !== undefined) {
1209
+ sub.lastData = data;
1210
+ sub.lastDataHash = hashEntityState(data);
1211
+ }
1212
+ }
1213
+ getObserver(id2) {
1214
+ return this.subscriptions.get(id2)?.observer;
1215
+ }
1216
+ updateObserver(id2, observer) {
1217
+ const sub = this.subscriptions.get(id2);
1218
+ if (sub) {
1219
+ sub.observer = observer;
1220
+ }
1221
+ }
1222
+ notifyNext(id2, data) {
1223
+ const sub = this.subscriptions.get(id2);
1224
+ sub?.observer.next?.({ data, version: sub.version });
1225
+ }
1226
+ notifyError(id2, error) {
1227
+ this.subscriptions.get(id2)?.observer.error?.(error);
1228
+ }
1229
+ notifyAllReconnectingError(error) {
1230
+ for (const sub of this.subscriptions.values()) {
1231
+ if (sub.state === "reconnecting") {
1232
+ sub.observer.error?.(error);
1233
+ }
1234
+ }
1235
+ }
1236
+ get size() {
1237
+ return this.subscriptions.size;
1238
+ }
1239
+ getIds() {
1240
+ return Array.from(this.subscriptions.keys());
1241
+ }
1242
+ values() {
1243
+ return this.subscriptions.values();
1244
+ }
1245
+ getStats() {
1246
+ const byState = {
1247
+ pending: 0,
1248
+ active: 0,
1249
+ reconnecting: 0,
1250
+ error: 0
1251
+ };
1252
+ const byEntity = {};
1253
+ for (const sub of this.subscriptions.values()) {
1254
+ byState[sub.state]++;
1255
+ const entityKey = `${sub.entity}:${sub.entityId}`;
1256
+ byEntity[entityKey] = (byEntity[entityKey] ?? 0) + 1;
1257
+ }
1258
+ return {
1259
+ total: this.subscriptions.size,
1260
+ byState,
1261
+ byEntity
1262
+ };
1263
+ }
1264
+ clear() {
1265
+ for (const sub of this.subscriptions.values()) {
1266
+ sub.observer.complete?.();
1267
+ }
1268
+ this.subscriptions.clear();
1269
+ this.entityIndex.clear();
1270
+ }
1271
+ clearErrors() {
1272
+ const toRemove = [];
1273
+ for (const [id2, sub] of this.subscriptions) {
1274
+ if (sub.state === "error") {
1275
+ toRemove.push(id2);
1276
+ }
1277
+ }
1278
+ for (const id2 of toRemove) {
1279
+ this.remove(id2);
1280
+ }
1281
+ }
1282
+ }
1283
+ function applyOps(state, ops) {
1284
+ let result = state;
1285
+ for (const op2 of ops) {
1286
+ result = applyOp(result, op2);
1287
+ }
1288
+ return result;
1289
+ }
1290
+ function applyOp(state, op2) {
1291
+ switch (op2.o) {
1292
+ case "set":
1293
+ return setAtPath(state, op2.p, op2.v);
1294
+ case "del":
1295
+ return deleteAtPath(state, op2.p);
1296
+ case "merge":
1297
+ return mergeAtPath(state, op2.p, op2.v);
1298
+ case "delta": {
1299
+ const current = getAtPath(state, op2.p);
1300
+ const updated = applyUpdate(current, { strategy: "delta", data: op2.d });
1301
+ return setAtPath(state, op2.p, updated);
1302
+ }
1303
+ case "patch": {
1304
+ const current = getAtPath(state, op2.p);
1305
+ const updated = applyUpdate(current, { strategy: "patch", data: op2.d });
1306
+ return setAtPath(state, op2.p, updated);
1307
+ }
1308
+ case "push":
1309
+ return arrayPush(state, op2.p, op2.v);
1310
+ case "unshift":
1311
+ return arrayUnshift(state, op2.p, op2.v);
1312
+ case "splice":
1313
+ return arraySplice(state, op2.p, op2.i, op2.dc, op2.v);
1314
+ case "arrSet":
1315
+ return arraySetAt(state, op2.p, op2.i, op2.v);
1316
+ case "arrDel":
1317
+ return arrayDeleteAt(state, op2.p, op2.i);
1318
+ case "arrSetId":
1319
+ return arraySetById(state, op2.p, op2.id, op2.v);
1320
+ case "arrDelId":
1321
+ return arrayDeleteById(state, op2.p, op2.id);
1322
+ case "arrMerge":
1323
+ return arrayMergeAt(state, op2.p, op2.i, op2.v);
1324
+ case "arrMergeId":
1325
+ return arrayMergeById(state, op2.p, op2.id, op2.v);
1326
+ default:
1327
+ return state;
1328
+ }
1329
+ }
1330
+ function parsePath(path) {
1331
+ if (!path)
1332
+ return [];
1333
+ return path.split(".");
1334
+ }
1335
+ function getAtPath(state, path) {
1336
+ const segments = parsePath(path);
1337
+ let current = state;
1338
+ for (const segment of segments) {
1339
+ if (current === null || current === undefined)
1340
+ return;
1341
+ if (typeof current !== "object")
1342
+ return;
1343
+ current = current[segment];
1344
+ }
1345
+ return current;
1346
+ }
1347
+ function setAtPath(state, path, value) {
1348
+ const segments = parsePath(path);
1349
+ if (segments.length === 0)
1350
+ return value;
1351
+ return updateAtPath(state, segments, () => value);
1352
+ }
1353
+ function deleteAtPath(state, path) {
1354
+ const segments = parsePath(path);
1355
+ if (segments.length === 0)
1356
+ return;
1357
+ const parentPath = segments.slice(0, -1);
1358
+ const key = segments[segments.length - 1];
1359
+ return updateAtPath(state, parentPath, (parent) => {
1360
+ if (parent === null || parent === undefined)
1361
+ return parent;
1362
+ if (Array.isArray(parent)) {
1363
+ const idx = parseInt(key, 10);
1364
+ const result = [...parent];
1365
+ result.splice(idx, 1);
1366
+ return result;
1367
+ }
1368
+ if (typeof parent === "object") {
1369
+ const { [key]: _, ...rest } = parent;
1370
+ return rest;
1371
+ }
1372
+ return parent;
1373
+ });
1374
+ }
1375
+ function mergeAtPath(state, path, value) {
1376
+ const segments = parsePath(path);
1377
+ return updateAtPath(state, segments, (current) => {
1378
+ if (current === null || current === undefined)
1379
+ return value;
1380
+ if (typeof current !== "object" || Array.isArray(current))
1381
+ return value;
1382
+ return { ...current, ...value };
1383
+ });
1384
+ }
1385
+ function updateAtPath(state, segments, transform) {
1386
+ if (segments.length === 0) {
1387
+ return transform(state);
1388
+ }
1389
+ const [head, ...tail] = segments;
1390
+ if (state === null || state === undefined) {
1391
+ const isArrayIndex = /^\d+$/.test(head);
1392
+ const newState = isArrayIndex ? [] : {};
1393
+ return updateAtPath(newState, segments, transform);
1394
+ }
1395
+ if (Array.isArray(state)) {
1396
+ const result = [...state];
1397
+ const idx = parseInt(head, 10);
1398
+ result[idx] = updateAtPath(result[idx], tail, transform);
1399
+ return result;
1400
+ }
1401
+ if (typeof state === "object") {
1402
+ const obj = state;
1403
+ return {
1404
+ ...obj,
1405
+ [head]: updateAtPath(obj[head], tail, transform)
1406
+ };
1407
+ }
1408
+ return { [head]: updateAtPath(undefined, tail, transform) };
1409
+ }
1410
+ function arrayPush(state, path, items) {
1411
+ return updateAtPath(state, parsePath(path), (arr) => {
1412
+ if (!Array.isArray(arr))
1413
+ return items;
1414
+ return [...arr, ...items];
1415
+ });
1416
+ }
1417
+ function arrayUnshift(state, path, items) {
1418
+ return updateAtPath(state, parsePath(path), (arr) => {
1419
+ if (!Array.isArray(arr))
1420
+ return items;
1421
+ return [...items, ...arr];
1422
+ });
1423
+ }
1424
+ function arraySplice(state, path, index, deleteCount, items) {
1425
+ return updateAtPath(state, parsePath(path), (arr) => {
1426
+ if (!Array.isArray(arr))
1427
+ return items ?? [];
1428
+ const result = [...arr];
1429
+ if (items) {
1430
+ result.splice(index, deleteCount, ...items);
1431
+ } else {
1432
+ result.splice(index, deleteCount);
1433
+ }
1434
+ return result;
1435
+ });
1436
+ }
1437
+ function arraySetAt(state, path, index, value) {
1438
+ return updateAtPath(state, parsePath(path), (arr) => {
1439
+ if (!Array.isArray(arr)) {
1440
+ const result2 = [];
1441
+ result2[index] = value;
1442
+ return result2;
1443
+ }
1444
+ const result = [...arr];
1445
+ result[index] = value;
1446
+ return result;
1447
+ });
1448
+ }
1449
+ function arrayDeleteAt(state, path, index) {
1450
+ return updateAtPath(state, parsePath(path), (arr) => {
1451
+ if (!Array.isArray(arr))
1452
+ return [];
1453
+ const result = [...arr];
1454
+ result.splice(index, 1);
1455
+ return result;
1456
+ });
1457
+ }
1458
+ function arraySetById(state, path, id2, value) {
1459
+ return updateAtPath(state, parsePath(path), (arr) => {
1460
+ if (!Array.isArray(arr))
1461
+ return [value];
1462
+ const index = arr.findIndex((item) => item && typeof item === "object" && item.id === id2);
1463
+ if (index === -1) {
1464
+ return [...arr, value];
1465
+ }
1466
+ const result = [...arr];
1467
+ result[index] = value;
1468
+ return result;
1469
+ });
1470
+ }
1471
+ function arrayDeleteById(state, path, id2) {
1472
+ return updateAtPath(state, parsePath(path), (arr) => {
1473
+ if (!Array.isArray(arr))
1474
+ return [];
1475
+ return arr.filter((item) => !(item && typeof item === "object" && item.id === id2));
1476
+ });
1477
+ }
1478
+ function arrayMergeAt(state, path, index, value) {
1479
+ return updateAtPath(state, parsePath(path), (arr) => {
1480
+ if (!Array.isArray(arr)) {
1481
+ const result2 = [];
1482
+ result2[index] = value;
1483
+ return result2;
1484
+ }
1485
+ const result = [...arr];
1486
+ const current = result[index];
1487
+ if (current && typeof current === "object" && !Array.isArray(current)) {
1488
+ result[index] = { ...current, ...value };
1489
+ } else {
1490
+ result[index] = value;
1491
+ }
1492
+ return result;
1493
+ });
1494
+ }
1495
+ function arrayMergeById(state, path, id2, value) {
1496
+ return updateAtPath(state, parsePath(path), (arr) => {
1497
+ if (!Array.isArray(arr))
1498
+ return [{ id: id2, ...value }];
1499
+ const index = arr.findIndex((item) => item && typeof item === "object" && item.id === id2);
1500
+ if (index === -1) {
1501
+ return [...arr, { id: id2, ...value }];
1502
+ }
1503
+ const result = [...arr];
1504
+ const current = result[index];
1505
+ if (current && typeof current === "object" && !Array.isArray(current)) {
1506
+ result[index] = { ...current, ...value };
1507
+ } else {
1508
+ result[index] = { id: id2, ...value };
1509
+ }
1510
+ return result;
1511
+ });
1512
+ }
1513
+ function isSnapshot(msg) {
1514
+ return msg.$ === "snapshot";
1515
+ }
1516
+ function isOps(msg) {
1517
+ return msg.$ === "ops";
1518
+ }
1519
+ function isError(msg) {
1520
+ return msg.$ === "error";
1521
+ }
1522
+
1523
+ // ../client/dist/index.js
1524
+ class SelectionRegistry {
1525
+ endpoints = new Map;
1526
+ addSubscriber(params) {
1527
+ const { endpointKey, subscriberId, selection, onData, onError } = params;
1528
+ let endpoint = this.endpoints.get(endpointKey);
1529
+ const previousSelection = endpoint ? { ...endpoint.mergedSelection } : {};
1530
+ if (!endpoint) {
1531
+ endpoint = {
1532
+ key: endpointKey,
1533
+ subscribers: new Map,
1534
+ mergedSelection: {},
1535
+ lastData: null,
1536
+ isSubscribed: false,
1537
+ createdAt: Date.now(),
1538
+ lastSelectionChangeAt: null
1539
+ };
1540
+ this.endpoints.set(endpointKey, endpoint);
1541
+ }
1542
+ const subscriberMeta = {
1543
+ id: subscriberId,
1544
+ selection,
1545
+ onData,
1546
+ createdAt: Date.now()
1547
+ };
1548
+ if (onError) {
1549
+ subscriberMeta.onError = onError;
1550
+ }
1551
+ endpoint.subscribers.set(subscriberId, subscriberMeta);
1552
+ const newSelection = this.computeMergedSelection(endpoint);
1553
+ const analysis = this.analyzeSelectionChange(previousSelection, newSelection);
1554
+ if (analysis.hasChanged) {
1555
+ endpoint.mergedSelection = newSelection;
1556
+ endpoint.lastSelectionChangeAt = Date.now();
1557
+ }
1558
+ return analysis;
1559
+ }
1560
+ removeSubscriber(endpointKey, subscriberId) {
1561
+ const endpoint = this.endpoints.get(endpointKey);
1562
+ if (!endpoint) {
1563
+ return this.noChangeAnalysis();
1564
+ }
1565
+ const previousSelection = { ...endpoint.mergedSelection };
1566
+ endpoint.subscribers.delete(subscriberId);
1567
+ if (endpoint.subscribers.size === 0) {
1568
+ this.endpoints.delete(endpointKey);
1569
+ return this.analyzeSelectionChange(previousSelection, {});
1570
+ }
1571
+ const newSelection = this.computeMergedSelection(endpoint);
1572
+ const analysis = this.analyzeSelectionChange(previousSelection, newSelection);
1573
+ if (analysis.hasChanged) {
1574
+ endpoint.mergedSelection = newSelection;
1575
+ endpoint.lastSelectionChangeAt = Date.now();
1576
+ }
1577
+ return analysis;
1578
+ }
1579
+ getMergedSelection(endpointKey) {
1580
+ return this.endpoints.get(endpointKey)?.mergedSelection ?? null;
1581
+ }
1582
+ getSubscriberIds(endpointKey) {
1583
+ const endpoint = this.endpoints.get(endpointKey);
1584
+ return endpoint ? Array.from(endpoint.subscribers.keys()) : [];
1585
+ }
1586
+ getSubscriberCount(endpointKey) {
1587
+ return this.endpoints.get(endpointKey)?.subscribers.size ?? 0;
1588
+ }
1589
+ hasSubscribers(endpointKey) {
1590
+ return (this.endpoints.get(endpointKey)?.subscribers.size ?? 0) > 0;
1591
+ }
1592
+ distributeData(endpointKey, data) {
1593
+ const endpoint = this.endpoints.get(endpointKey);
1594
+ if (!endpoint)
1595
+ return;
1596
+ endpoint.lastData = data;
1597
+ for (const subscriber of endpoint.subscribers.values()) {
1598
+ try {
1599
+ const filteredData = filterToSelection(data, subscriber.selection);
1600
+ subscriber.onData(filteredData);
1601
+ } catch (error) {
1602
+ if (subscriber.onError) {
1603
+ subscriber.onError(error instanceof Error ? error : new Error(String(error)));
1604
+ }
1605
+ }
1606
+ }
1607
+ }
1608
+ distributeError(endpointKey, error) {
1609
+ const endpoint = this.endpoints.get(endpointKey);
1610
+ if (!endpoint)
1611
+ return;
1612
+ for (const subscriber of endpoint.subscribers.values()) {
1613
+ subscriber.onError?.(error);
1614
+ }
1615
+ }
1616
+ getLastData(endpointKey) {
1617
+ return this.endpoints.get(endpointKey)?.lastData ?? null;
1618
+ }
1619
+ markSubscribed(endpointKey) {
1620
+ const endpoint = this.endpoints.get(endpointKey);
1621
+ if (endpoint) {
1622
+ endpoint.isSubscribed = true;
1623
+ }
1624
+ }
1625
+ markUnsubscribed(endpointKey) {
1626
+ const endpoint = this.endpoints.get(endpointKey);
1627
+ if (endpoint) {
1628
+ endpoint.isSubscribed = false;
1629
+ }
1630
+ }
1631
+ isSubscribed(endpointKey) {
1632
+ return this.endpoints.get(endpointKey)?.isSubscribed ?? false;
1633
+ }
1634
+ getEndpointKeys() {
1635
+ return Array.from(this.endpoints.keys());
1636
+ }
1637
+ clear() {
1638
+ this.endpoints.clear();
1639
+ }
1640
+ getStats() {
1641
+ let totalSubscribers = 0;
1642
+ for (const endpoint of this.endpoints.values()) {
1643
+ totalSubscribers += endpoint.subscribers.size;
1644
+ }
1645
+ return {
1646
+ endpointCount: this.endpoints.size,
1647
+ totalSubscribers,
1648
+ avgSubscribersPerEndpoint: this.endpoints.size > 0 ? totalSubscribers / this.endpoints.size : 0
1649
+ };
1650
+ }
1651
+ computeMergedSelection(endpoint) {
1652
+ const selections = Array.from(endpoint.subscribers.values()).map((s) => s.selection);
1653
+ return mergeSelections(selections);
1654
+ }
1655
+ analyzeSelectionChange(previous, next) {
1656
+ const previousFields = this.flattenSelectionKeys(previous);
1657
+ const nextFields = this.flattenSelectionKeys(next);
1658
+ const addedFields = new Set;
1659
+ const removedFields = new Set;
1660
+ for (const field of nextFields) {
1661
+ if (!previousFields.has(field)) {
1662
+ addedFields.add(field);
1663
+ }
1664
+ }
1665
+ for (const field of previousFields) {
1666
+ if (!nextFields.has(field)) {
1667
+ removedFields.add(field);
1668
+ }
1669
+ }
1670
+ const hasChanged = addedFields.size > 0 || removedFields.size > 0;
1671
+ return {
1672
+ hasChanged,
1673
+ previousSelection: previous,
1674
+ newSelection: next,
1675
+ addedFields,
1676
+ removedFields,
1677
+ isExpanded: addedFields.size > 0,
1678
+ isShrunk: removedFields.size > 0
1679
+ };
1680
+ }
1681
+ flattenSelectionKeys(selection, prefix = "") {
1682
+ const keys = new Set;
1683
+ for (const [key, value] of Object.entries(selection)) {
1684
+ const path = prefix ? `${prefix}.${key}` : key;
1685
+ keys.add(path);
1686
+ if (typeof value === "boolean") {
1687
+ continue;
1688
+ }
1689
+ if (typeof value === "object" && value !== null) {
1690
+ let nestedSelection;
1691
+ if ("select" in value && typeof value.select === "object") {
1692
+ nestedSelection = value.select;
1693
+ } else if (!("input" in value)) {
1694
+ nestedSelection = value;
1695
+ }
1696
+ if (nestedSelection) {
1697
+ const nestedKeys = this.flattenSelectionKeys(nestedSelection, path);
1698
+ for (const nestedKey of nestedKeys) {
1699
+ keys.add(nestedKey);
1700
+ }
1701
+ }
1702
+ }
1703
+ }
1704
+ return keys;
1705
+ }
1706
+ noChangeAnalysis() {
1707
+ return {
1708
+ hasChanged: false,
1709
+ previousSelection: {},
1710
+ newSelection: {},
1711
+ addedFields: new Set,
1712
+ removedFields: new Set,
1713
+ isExpanded: false,
1714
+ isShrunk: false
1715
+ };
1716
+ }
1717
+ }
1718
+ function mergeSelections(selections) {
1719
+ if (selections.length === 0)
1720
+ return {};
1721
+ if (selections.length === 1)
1722
+ return selections[0];
1723
+ const merged = {};
1724
+ const allKeys = new Set;
1725
+ for (const selection of selections) {
1726
+ for (const key of Object.keys(selection)) {
1727
+ allKeys.add(key);
1728
+ }
1729
+ }
1730
+ for (const key of allKeys) {
1731
+ const values = selections.map((s) => s[key]).filter((v) => v !== undefined && v !== null);
1732
+ if (values.length === 0)
1733
+ continue;
1734
+ if (values.some((v) => v === true)) {
1735
+ merged[key] = true;
1736
+ continue;
1737
+ }
1738
+ const nestedSelections = [];
1739
+ let lastInput;
1740
+ for (const value of values) {
1741
+ if (typeof value === "object" && value !== null) {
1742
+ if ("select" in value && typeof value.select === "object") {
1743
+ nestedSelections.push(value.select);
1744
+ if ("input" in value) {
1745
+ lastInput = value.input;
1746
+ }
1747
+ } else if ("input" in value) {
1748
+ lastInput = value.input;
1749
+ merged[key] = { input: lastInput };
1750
+ } else {
1751
+ nestedSelections.push(value);
1752
+ }
1753
+ }
1754
+ }
1755
+ if (nestedSelections.length > 0) {
1756
+ const mergedNested = mergeSelections(nestedSelections);
1757
+ if (lastInput !== undefined) {
1758
+ merged[key] = { input: lastInput, select: mergedNested };
1759
+ } else {
1760
+ merged[key] = mergedNested;
1761
+ }
1762
+ }
1763
+ }
1764
+ return merged;
1765
+ }
1766
+ function filterToSelection(data, selection) {
1767
+ if (data == null)
1768
+ return data;
1769
+ if (Array.isArray(data)) {
1770
+ return data.map((item) => filterToSelection(item, selection));
1771
+ }
1772
+ if (typeof data !== "object")
1773
+ return data;
1774
+ const obj = data;
1775
+ const result = {};
1776
+ if ("id" in obj) {
1777
+ result.id = obj.id;
1778
+ }
1779
+ for (const [key, value] of Object.entries(selection)) {
1780
+ if (!(key in obj))
1781
+ continue;
1782
+ if (value === true) {
1783
+ result[key] = obj[key];
1784
+ } else if (typeof value === "object" && value !== null) {
1785
+ let nestedSelection = null;
1786
+ if ("select" in value && typeof value.select === "object") {
1787
+ nestedSelection = value.select;
1788
+ } else if (!("input" in value)) {
1789
+ nestedSelection = value;
1790
+ }
1791
+ if (nestedSelection) {
1792
+ result[key] = filterToSelection(obj[key], nestedSelection);
1793
+ } else {
1794
+ result[key] = obj[key];
1795
+ }
1796
+ }
847
1797
  }
1798
+ return result;
848
1799
  }
849
-
850
- // ../client/dist/index.js
851
1800
  function hasAnySubscription(entities, entityName, select, visited = new Set) {
852
1801
  if (!entities)
853
1802
  return false;
@@ -879,13 +1828,19 @@ function hasAnySubscription(entities, entityName, select, visited = new Set) {
879
1828
  }
880
1829
  return false;
881
1830
  }
1831
+ var subscriberIdCounter = 0;
1832
+ function generateSubscriberId() {
1833
+ return `sub_${Date.now()}_${++subscriberIdCounter}`;
1834
+ }
882
1835
 
883
1836
  class ClientImpl {
884
1837
  transport;
885
1838
  plugins;
886
1839
  metadata = null;
887
1840
  connectPromise = null;
888
- subscriptions = new Map;
1841
+ endpoints = new Map;
1842
+ pendingBatches = new Map;
1843
+ batchScheduled = false;
889
1844
  queryResultCache = new Map;
890
1845
  observerEntries = new WeakMap;
891
1846
  constructor(config) {
@@ -926,16 +1881,16 @@ class ClientImpl {
926
1881
  result = await plugin.afterResponse(result, processedOp);
927
1882
  }
928
1883
  }
929
- if (result.error) {
930
- const error = result.error;
1884
+ if (isError(result)) {
1885
+ const error = new Error(result.error);
931
1886
  for (const plugin of this.plugins) {
932
1887
  if (plugin.onError) {
933
1888
  try {
934
1889
  result = await plugin.onError(error, processedOp, () => this.execute(processedOp));
935
- if (!result.error)
1890
+ if (!isError(result))
936
1891
  break;
937
1892
  } catch (e) {
938
- result = { error: e };
1893
+ result = { $: "error", error: e instanceof Error ? e.message : String(e) };
939
1894
  }
940
1895
  }
941
1896
  }
@@ -1001,7 +1956,7 @@ class ClientImpl {
1001
1956
  return `${type}-${path}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
1002
1957
  }
1003
1958
  inputHashCache = new WeakMap;
1004
- makeQueryKey(path, input) {
1959
+ makeEndpointKey(path, input) {
1005
1960
  if (input === undefined || input === null) {
1006
1961
  return `${path}:null`;
1007
1962
  }
@@ -1016,69 +1971,231 @@ class ClientImpl {
1016
1971
  }
1017
1972
  return `${path}:${hash}`;
1018
1973
  }
1019
- executeQuery(path, input, select) {
1020
- const key = this.makeQueryKey(path, input);
1021
- const cached = this.queryResultCache.get(key);
1022
- if (cached && !select) {
1023
- return cached;
1024
- }
1025
- if (!this.subscriptions.has(key)) {
1026
- this.subscriptions.set(key, {
1974
+ makeQueryResultKey(endpointKey, select) {
1975
+ if (!select)
1976
+ return endpointKey;
1977
+ return `${endpointKey}:${JSON.stringify(select)}`;
1978
+ }
1979
+ getOrCreateEndpoint(key) {
1980
+ let endpoint = this.endpoints.get(key);
1981
+ if (!endpoint) {
1982
+ endpoint = {
1027
1983
  data: null,
1028
1984
  error: null,
1029
1985
  completed: false,
1030
- observers: new Set,
1031
- ...select && { select }
1032
- });
1986
+ observers: new Map,
1987
+ mergedSelection: undefined,
1988
+ isSubscribed: false
1989
+ };
1990
+ this.endpoints.set(key, endpoint);
1991
+ }
1992
+ return endpoint;
1993
+ }
1994
+ addObserver(key, observer) {
1995
+ const endpoint = this.getOrCreateEndpoint(key);
1996
+ const previousSelection = endpoint.mergedSelection;
1997
+ endpoint.observers.set(observer.id, observer);
1998
+ const selections = Array.from(endpoint.observers.values()).map((o) => o.selection).filter((s) => s !== undefined);
1999
+ endpoint.mergedSelection = selections.length > 0 ? mergeSelections(selections) : undefined;
2000
+ const selectionChanged = JSON.stringify(previousSelection) !== JSON.stringify(endpoint.mergedSelection);
2001
+ const isExpanded = selectionChanged && this.isSelectionExpanded(previousSelection, endpoint.mergedSelection);
2002
+ return { endpoint, selectionChanged, isExpanded };
2003
+ }
2004
+ removeObserver(key, observerId) {
2005
+ const endpoint = this.endpoints.get(key);
2006
+ if (!endpoint)
2007
+ return { endpoint: undefined, shouldUnsubscribe: false };
2008
+ endpoint.observers.delete(observerId);
2009
+ if (endpoint.observers.size === 0) {
2010
+ return { endpoint, shouldUnsubscribe: true };
2011
+ }
2012
+ const selections = Array.from(endpoint.observers.values()).map((o) => o.selection).filter((s) => s !== undefined);
2013
+ endpoint.mergedSelection = selections.length > 0 ? mergeSelections(selections) : undefined;
2014
+ return { endpoint, shouldUnsubscribe: false };
2015
+ }
2016
+ isSelectionExpanded(previous, current) {
2017
+ if (!previous)
2018
+ return current !== undefined;
2019
+ if (!current)
2020
+ return false;
2021
+ const previousKeys = this.flattenSelectionKeys(previous);
2022
+ const currentKeys = this.flattenSelectionKeys(current);
2023
+ for (const key of currentKeys) {
2024
+ if (!previousKeys.has(key))
2025
+ return true;
2026
+ }
2027
+ return false;
2028
+ }
2029
+ flattenSelectionKeys(selection, prefix = "") {
2030
+ const keys = new Set;
2031
+ for (const [key, value] of Object.entries(selection)) {
2032
+ const path = prefix ? `${prefix}.${key}` : key;
2033
+ keys.add(path);
2034
+ if (typeof value === "boolean") {
2035
+ continue;
2036
+ }
2037
+ if (typeof value === "object" && value !== null) {
2038
+ const nested = "select" in value ? value.select : value;
2039
+ if (nested && typeof nested === "object") {
2040
+ for (const nestedKey of this.flattenSelectionKeys(nested, path)) {
2041
+ keys.add(nestedKey);
2042
+ }
2043
+ }
2044
+ }
2045
+ }
2046
+ return keys;
2047
+ }
2048
+ distributeData(endpoint, data) {
2049
+ endpoint.data = data;
2050
+ endpoint.error = null;
2051
+ for (const observer of endpoint.observers.values()) {
2052
+ if (observer.next) {
2053
+ const filteredData = observer.selection ? filterToSelection(data, observer.selection) : data;
2054
+ observer.next(filteredData);
2055
+ }
2056
+ }
2057
+ }
2058
+ distributeError(endpoint, error) {
2059
+ endpoint.error = error;
2060
+ for (const observer of endpoint.observers.values()) {
2061
+ observer.error?.(error);
1033
2062
  }
1034
- const sub = this.subscriptions.get(key);
2063
+ }
2064
+ scheduleBatchedQuery(key, path, input, selection) {
2065
+ return new Promise((resolve, reject) => {
2066
+ const observerId = generateSubscriberId();
2067
+ let batch = this.pendingBatches.get(key);
2068
+ if (!batch) {
2069
+ batch = {
2070
+ path,
2071
+ input,
2072
+ observers: [],
2073
+ mergedSelection: undefined
2074
+ };
2075
+ this.pendingBatches.set(key, batch);
2076
+ }
2077
+ batch.observers.push({ id: observerId, selection, resolve, reject });
2078
+ const selections = batch.observers.map((o) => o.selection).filter((s) => s !== undefined);
2079
+ batch.mergedSelection = selections.length > 0 ? mergeSelections(selections) : undefined;
2080
+ if (!this.batchScheduled) {
2081
+ this.batchScheduled = true;
2082
+ queueMicrotask(() => this.flushBatches());
2083
+ }
2084
+ });
2085
+ }
2086
+ async flushBatches() {
2087
+ this.batchScheduled = false;
2088
+ const batches = Array.from(this.pendingBatches.entries());
2089
+ this.pendingBatches.clear();
2090
+ await Promise.all(batches.map(async ([key, batch]) => {
2091
+ try {
2092
+ const op2 = {
2093
+ id: this.generateId("query", batch.path),
2094
+ path: batch.path,
2095
+ type: "query",
2096
+ input: batch.input,
2097
+ meta: batch.mergedSelection ? { select: batch.mergedSelection } : {}
2098
+ };
2099
+ const response = await this.execute(op2);
2100
+ if (isError(response)) {
2101
+ const error = new Error(response.error);
2102
+ for (const observer of batch.observers) {
2103
+ observer.reject(error);
2104
+ }
2105
+ return;
2106
+ }
2107
+ if (isSnapshot(response)) {
2108
+ const endpoint = this.getOrCreateEndpoint(key);
2109
+ endpoint.data = response.data;
2110
+ for (const observer of batch.observers) {
2111
+ const filteredData = observer.selection ? filterToSelection(response.data, observer.selection) : response.data;
2112
+ observer.resolve(filteredData);
2113
+ }
2114
+ }
2115
+ } catch (error) {
2116
+ const err = error instanceof Error ? error : new Error(String(error));
2117
+ for (const observer of batch.observers) {
2118
+ observer.reject(err);
2119
+ }
2120
+ }
2121
+ }));
2122
+ }
2123
+ executeQuery(path, input, select) {
2124
+ const key = this.makeEndpointKey(path, input);
2125
+ const cacheKey = this.makeQueryResultKey(key, select);
2126
+ const cached = this.queryResultCache.get(cacheKey);
2127
+ if (cached) {
2128
+ return cached;
2129
+ }
2130
+ const endpoint = this.getOrCreateEndpoint(key);
1035
2131
  const result = {
1036
2132
  get value() {
1037
- return sub.data;
2133
+ if (endpoint.data === null)
2134
+ return null;
2135
+ return select ? filterToSelection(endpoint.data, select) : endpoint.data;
1038
2136
  },
1039
2137
  subscribe: (observerOrCallback) => {
2138
+ const observerId = generateSubscriberId();
1040
2139
  let entry;
1041
2140
  if (typeof observerOrCallback === "function") {
1042
2141
  const callback = observerOrCallback;
1043
- entry = { next: (data) => callback(data) };
2142
+ entry = {
2143
+ id: observerId,
2144
+ selection: select,
2145
+ next: (data) => callback(data)
2146
+ };
1044
2147
  } else if (observerOrCallback && typeof observerOrCallback === "object") {
1045
2148
  const observer = observerOrCallback;
1046
2149
  entry = {
2150
+ id: observerId,
2151
+ selection: select,
1047
2152
  next: observer.next ? (data) => observer.next(data) : undefined,
1048
2153
  error: observer.error,
1049
2154
  complete: observer.complete
1050
2155
  };
1051
2156
  } else {
1052
- entry = {};
2157
+ entry = { id: observerId, selection: select };
1053
2158
  }
1054
2159
  if (observerOrCallback) {
1055
2160
  this.observerEntries.set(observerOrCallback, entry);
1056
2161
  }
1057
- sub.observers.add(entry);
1058
- if (sub.error && entry.error) {
1059
- entry.error(sub.error);
1060
- } else if (sub.data !== null && entry.next) {
1061
- entry.next(sub.data);
1062
- }
1063
- if (sub.completed && entry.complete) {
1064
- entry.complete();
1065
- }
1066
- if (!sub.unsubscribe) {
2162
+ const { endpoint: ep, isExpanded } = this.addObserver(key, entry);
2163
+ if (!ep.isSubscribed) {
1067
2164
  this.startSubscription(path, input, key);
2165
+ } else if (isExpanded) {
2166
+ if (ep.unsubscribe) {
2167
+ ep.unsubscribe();
2168
+ }
2169
+ ep.isSubscribed = false;
2170
+ this.startSubscription(path, input, key);
2171
+ } else {
2172
+ if (ep.error && entry.error) {
2173
+ entry.error(ep.error);
2174
+ } else if (ep.data !== null && entry.next) {
2175
+ const filteredData = select ? filterToSelection(ep.data, select) : ep.data;
2176
+ entry.next(filteredData);
2177
+ }
2178
+ if (ep.completed && entry.complete) {
2179
+ entry.complete();
2180
+ }
1068
2181
  }
1069
2182
  return () => {
1070
2183
  if (observerOrCallback) {
1071
2184
  const storedEntry = this.observerEntries.get(observerOrCallback);
1072
2185
  if (storedEntry) {
1073
- sub.observers.delete(storedEntry);
2186
+ const { shouldUnsubscribe } = this.removeObserver(key, storedEntry.id);
2187
+ if (shouldUnsubscribe) {
2188
+ ep.unsubscribe?.();
2189
+ ep.isSubscribed = false;
2190
+ this.endpoints.delete(key);
2191
+ for (const [k] of this.queryResultCache) {
2192
+ if (k.startsWith(key)) {
2193
+ this.queryResultCache.delete(k);
2194
+ }
2195
+ }
2196
+ }
1074
2197
  }
1075
2198
  }
1076
- if (sub.observers.size === 0 && sub.unsubscribe) {
1077
- sub.unsubscribe();
1078
- sub.unsubscribe = undefined;
1079
- this.subscriptions.delete(key);
1080
- this.queryResultCache.delete(key);
1081
- }
1082
2199
  };
1083
2200
  },
1084
2201
  select: (selection) => {
@@ -1086,27 +2203,17 @@ class ClientImpl {
1086
2203
  },
1087
2204
  then: async (onfulfilled, onrejected) => {
1088
2205
  try {
1089
- const op2 = {
1090
- id: this.generateId("query", path),
1091
- path,
1092
- type: "query",
1093
- input,
1094
- meta: select ? { select } : {}
1095
- };
1096
- const response = await this.execute(op2);
1097
- if (response.error) {
1098
- throw response.error;
2206
+ const data = await this.scheduleBatchedQuery(key, path, input, select);
2207
+ const ep = this.getOrCreateEndpoint(key);
2208
+ if (ep.data === null) {
2209
+ ep.data = data;
1099
2210
  }
1100
- sub.data = response.data;
1101
- for (const observer of sub.observers) {
1102
- observer.next?.(response.data);
1103
- }
1104
- return onfulfilled ? onfulfilled(response.data) : response.data;
2211
+ return onfulfilled ? onfulfilled(data) : data;
1105
2212
  } catch (error) {
1106
2213
  const err = error instanceof Error ? error : new Error(String(error));
1107
- sub.error = err;
1108
- for (const observer of sub.observers) {
1109
- observer.error?.(err);
2214
+ const ep = this.endpoints.get(key);
2215
+ if (ep) {
2216
+ ep.error = err;
1110
2217
  }
1111
2218
  if (onrejected) {
1112
2219
  return onrejected(error);
@@ -1115,65 +2222,70 @@ class ClientImpl {
1115
2222
  }
1116
2223
  }
1117
2224
  };
1118
- if (!select) {
1119
- this.queryResultCache.set(key, result);
1120
- }
2225
+ this.queryResultCache.set(cacheKey, result);
1121
2226
  return result;
1122
2227
  }
1123
2228
  async startSubscription(path, input, key) {
1124
- const sub = this.subscriptions.get(key);
1125
- if (!sub)
2229
+ const endpoint = this.endpoints.get(key);
2230
+ if (!endpoint)
1126
2231
  return;
1127
2232
  await this.ensureConnected();
1128
- const isSubscription = this.requiresSubscription(path, sub.select);
2233
+ const meta = this.getOperationMeta(path);
2234
+ if (meta?.type === "mutation") {
2235
+ return;
2236
+ }
2237
+ const isSubscription = this.requiresSubscription(path, endpoint.mergedSelection);
2238
+ endpoint.isSubscribed = true;
1129
2239
  if (isSubscription) {
1130
2240
  const op2 = {
1131
2241
  id: this.generateId("subscription", path),
1132
2242
  path,
1133
2243
  type: "subscription",
1134
- input
2244
+ input,
2245
+ meta: endpoint.mergedSelection ? { select: endpoint.mergedSelection } : {}
1135
2246
  };
1136
2247
  const resultOrObservable = this.transport.execute(op2);
1137
2248
  if (this.isObservable(resultOrObservable)) {
1138
2249
  const subscription = resultOrObservable.subscribe({
1139
- next: (result) => {
1140
- if (result.data !== undefined) {
1141
- sub.data = result.data;
1142
- sub.error = null;
1143
- for (const observer of sub.observers) {
1144
- observer.next?.(result.data);
1145
- }
1146
- }
1147
- if (result.error) {
1148
- const err = result.error instanceof Error ? result.error : new Error(String(result.error));
1149
- sub.error = err;
1150
- for (const observer of sub.observers) {
1151
- observer.error?.(err);
2250
+ next: (message) => {
2251
+ if (isSnapshot(message)) {
2252
+ this.distributeData(endpoint, message.data);
2253
+ } else if (isOps(message)) {
2254
+ try {
2255
+ const newData = applyOps(endpoint.data, message.ops);
2256
+ this.distributeData(endpoint, newData);
2257
+ } catch (updateErr) {
2258
+ const err = updateErr instanceof Error ? updateErr : new Error(String(updateErr));
2259
+ this.distributeError(endpoint, err);
1152
2260
  }
2261
+ } else if (isError(message)) {
2262
+ this.distributeError(endpoint, new Error(message.error));
1153
2263
  }
1154
2264
  },
1155
2265
  error: (err) => {
1156
- sub.error = err;
1157
- for (const observer of sub.observers) {
1158
- observer.error?.(err);
1159
- }
2266
+ this.distributeError(endpoint, err);
1160
2267
  },
1161
2268
  complete: () => {
1162
- sub.completed = true;
1163
- for (const observer of sub.observers) {
2269
+ endpoint.completed = true;
2270
+ for (const observer of endpoint.observers.values()) {
1164
2271
  observer.complete?.();
1165
2272
  }
1166
2273
  }
1167
2274
  });
1168
- sub.unsubscribe = () => subscription.unsubscribe();
2275
+ endpoint.unsubscribe = () => subscription.unsubscribe();
1169
2276
  }
1170
2277
  } else {
1171
- this.executeQuery(path, input).then(() => {
1172
- sub.completed = true;
1173
- for (const observer of sub.observers) {
2278
+ try {
2279
+ const data = await this.scheduleBatchedQuery(key, path, input, endpoint.mergedSelection);
2280
+ this.distributeData(endpoint, data);
2281
+ endpoint.completed = true;
2282
+ for (const observer of endpoint.observers.values()) {
1174
2283
  observer.complete?.();
1175
2284
  }
1176
- });
2285
+ } catch (error) {
2286
+ const err = error instanceof Error ? error : new Error(String(error));
2287
+ this.distributeError(endpoint, err);
2288
+ }
1177
2289
  }
1178
2290
  }
1179
2291
  async executeMutation(path, input, select) {
@@ -1186,10 +2298,13 @@ class ClientImpl {
1186
2298
  meta: select ? { select } : {}
1187
2299
  };
1188
2300
  const response = await this.execute(op2);
1189
- if (response.error) {
1190
- throw response.error;
2301
+ if (isError(response)) {
2302
+ throw new Error(response.error);
1191
2303
  }
1192
- return { data: response.data };
2304
+ if (isSnapshot(response)) {
2305
+ return { data: response.data };
2306
+ }
2307
+ return { data: null };
1193
2308
  }
1194
2309
  createAccessor(path) {
1195
2310
  return (descriptor) => {
@@ -1218,6 +2333,17 @@ class ClientImpl {
1218
2333
  return queryResult;
1219
2334
  };
1220
2335
  }
2336
+ getStats() {
2337
+ let totalObservers = 0;
2338
+ for (const endpoint of this.endpoints.values()) {
2339
+ totalObservers += endpoint.observers.size;
2340
+ }
2341
+ return {
2342
+ endpointCount: this.endpoints.size,
2343
+ totalObservers,
2344
+ pendingBatches: this.pendingBatches.size
2345
+ };
2346
+ }
1221
2347
  }
1222
2348
  function createClient(config) {
1223
2349
  const impl = new ClientImpl(config);
@@ -1309,16 +2435,17 @@ async function executeRequest(baseUrl, op2, options) {
1309
2435
  }
1310
2436
  if (!response.ok) {
1311
2437
  return {
1312
- error: new Error(`HTTP ${response.status}: ${response.statusText}`)
2438
+ $: "error",
2439
+ error: `HTTP ${response.status}: ${response.statusText}`
1313
2440
  };
1314
2441
  }
1315
2442
  const result = await response.json();
1316
2443
  return result;
1317
2444
  } catch (error) {
1318
2445
  if (error instanceof Error && error.name === "AbortError") {
1319
- return { error: new Error("Request timeout") };
2446
+ return { $: "error", error: "Request timeout" };
1320
2447
  }
1321
- return { error };
2448
+ return { $: "error", error: error instanceof Error ? error.message : String(error) };
1322
2449
  }
1323
2450
  }
1324
2451
  function createPollingObservable(baseUrl, op2, options) {
@@ -1331,24 +2458,28 @@ function createPollingObservable(baseUrl, op2, options) {
1331
2458
  if (!active)
1332
2459
  return;
1333
2460
  try {
1334
- const result = await executeRequest(baseUrl, op2, {
2461
+ const message = await executeRequest(baseUrl, op2, {
1335
2462
  headers: options.headers,
1336
2463
  fetch: options.fetch
1337
2464
  });
1338
2465
  if (!active)
1339
2466
  return;
1340
- if (result.error) {
2467
+ if (isError(message)) {
1341
2468
  retries++;
1342
2469
  if (retries > options.maxRetries) {
1343
- observer.error?.(result.error);
2470
+ observer.error?.(new Error(message.error));
1344
2471
  return;
1345
2472
  }
1346
2473
  } else {
1347
2474
  retries = 0;
1348
- const newValue = JSON.stringify(result.data);
1349
- if (newValue !== JSON.stringify(lastValue)) {
1350
- lastValue = result.data;
1351
- observer.next?.(result);
2475
+ if (isSnapshot(message)) {
2476
+ const hasDataChange = JSON.stringify(message.data) !== JSON.stringify(lastValue);
2477
+ if (hasDataChange) {
2478
+ lastValue = message.data;
2479
+ observer.next?.(message);
2480
+ }
2481
+ } else if (isOps(message)) {
2482
+ observer.next?.(message);
1352
2483
  }
1353
2484
  }
1354
2485
  if (active) {
@@ -1397,217 +2528,6 @@ http.server = function httpServer(options) {
1397
2528
  }
1398
2529
  };
1399
2530
  };
1400
- class SubscriptionRegistry {
1401
- subscriptions = new Map;
1402
- entityIndex = new Map;
1403
- add(sub) {
1404
- const tracked = {
1405
- ...sub,
1406
- state: "pending",
1407
- lastDataHash: sub.lastData ? hashEntityState(sub.lastData) : null,
1408
- createdAt: Date.now(),
1409
- lastUpdateAt: null
1410
- };
1411
- this.subscriptions.set(sub.id, tracked);
1412
- const entityKey = `${sub.entity}:${sub.entityId}`;
1413
- let ids = this.entityIndex.get(entityKey);
1414
- if (!ids) {
1415
- ids = new Set;
1416
- this.entityIndex.set(entityKey, ids);
1417
- }
1418
- ids.add(sub.id);
1419
- }
1420
- get(id) {
1421
- return this.subscriptions.get(id);
1422
- }
1423
- has(id) {
1424
- return this.subscriptions.has(id);
1425
- }
1426
- remove(id) {
1427
- const sub = this.subscriptions.get(id);
1428
- if (!sub)
1429
- return;
1430
- this.subscriptions.delete(id);
1431
- const entityKey = `${sub.entity}:${sub.entityId}`;
1432
- const ids = this.entityIndex.get(entityKey);
1433
- if (ids) {
1434
- ids.delete(id);
1435
- if (ids.size === 0) {
1436
- this.entityIndex.delete(entityKey);
1437
- }
1438
- }
1439
- }
1440
- getByEntity(entity3, entityId) {
1441
- const entityKey = `${entity3}:${entityId}`;
1442
- const ids = this.entityIndex.get(entityKey);
1443
- if (!ids)
1444
- return [];
1445
- const result = [];
1446
- for (const id of ids) {
1447
- const sub = this.subscriptions.get(id);
1448
- if (sub) {
1449
- result.push(sub);
1450
- }
1451
- }
1452
- return result;
1453
- }
1454
- updateVersion(id, version, data) {
1455
- const sub = this.subscriptions.get(id);
1456
- if (!sub)
1457
- return;
1458
- sub.version = version;
1459
- sub.lastUpdateAt = Date.now();
1460
- if (data !== undefined) {
1461
- sub.lastData = data;
1462
- sub.lastDataHash = hashEntityState(data);
1463
- }
1464
- if (sub.state === "pending" || sub.state === "reconnecting") {
1465
- sub.state = "active";
1466
- }
1467
- }
1468
- updateData(id, data) {
1469
- const sub = this.subscriptions.get(id);
1470
- if (!sub)
1471
- return;
1472
- sub.lastData = data;
1473
- sub.lastDataHash = hashEntityState(data);
1474
- }
1475
- getLastData(id) {
1476
- return this.subscriptions.get(id)?.lastData ?? null;
1477
- }
1478
- getVersion(id) {
1479
- return this.subscriptions.get(id)?.version ?? null;
1480
- }
1481
- markActive(id) {
1482
- const sub = this.subscriptions.get(id);
1483
- if (sub) {
1484
- sub.state = "active";
1485
- }
1486
- }
1487
- markError(id) {
1488
- const sub = this.subscriptions.get(id);
1489
- if (sub) {
1490
- sub.state = "error";
1491
- }
1492
- }
1493
- markAllReconnecting() {
1494
- for (const sub of this.subscriptions.values()) {
1495
- if (sub.state === "active") {
1496
- sub.state = "reconnecting";
1497
- }
1498
- }
1499
- }
1500
- getByState(state) {
1501
- const result = [];
1502
- for (const sub of this.subscriptions.values()) {
1503
- if (sub.state === state) {
1504
- result.push(sub);
1505
- }
1506
- }
1507
- return result;
1508
- }
1509
- getAllForReconnect() {
1510
- const result = [];
1511
- for (const sub of this.subscriptions.values()) {
1512
- if (sub.state === "reconnecting" || sub.state === "active") {
1513
- const reconnectSub = {
1514
- id: sub.id,
1515
- entity: sub.entity,
1516
- entityId: sub.entityId,
1517
- fields: sub.fields,
1518
- version: sub.version,
1519
- input: sub.input
1520
- };
1521
- if (sub.lastDataHash) {
1522
- reconnectSub.dataHash = sub.lastDataHash;
1523
- }
1524
- result.push(reconnectSub);
1525
- }
1526
- }
1527
- return result;
1528
- }
1529
- processReconnectResult(id, version, data) {
1530
- const sub = this.subscriptions.get(id);
1531
- if (!sub)
1532
- return;
1533
- sub.version = version;
1534
- sub.state = "active";
1535
- sub.lastUpdateAt = Date.now();
1536
- if (data !== undefined) {
1537
- sub.lastData = data;
1538
- sub.lastDataHash = hashEntityState(data);
1539
- }
1540
- }
1541
- getObserver(id) {
1542
- return this.subscriptions.get(id)?.observer;
1543
- }
1544
- updateObserver(id, observer) {
1545
- const sub = this.subscriptions.get(id);
1546
- if (sub) {
1547
- sub.observer = observer;
1548
- }
1549
- }
1550
- notifyNext(id, data) {
1551
- const sub = this.subscriptions.get(id);
1552
- sub?.observer.next?.({ data, version: sub.version });
1553
- }
1554
- notifyError(id, error) {
1555
- this.subscriptions.get(id)?.observer.error?.(error);
1556
- }
1557
- notifyAllReconnectingError(error) {
1558
- for (const sub of this.subscriptions.values()) {
1559
- if (sub.state === "reconnecting") {
1560
- sub.observer.error?.(error);
1561
- }
1562
- }
1563
- }
1564
- get size() {
1565
- return this.subscriptions.size;
1566
- }
1567
- getIds() {
1568
- return Array.from(this.subscriptions.keys());
1569
- }
1570
- values() {
1571
- return this.subscriptions.values();
1572
- }
1573
- getStats() {
1574
- const byState = {
1575
- pending: 0,
1576
- active: 0,
1577
- reconnecting: 0,
1578
- error: 0
1579
- };
1580
- const byEntity = {};
1581
- for (const sub of this.subscriptions.values()) {
1582
- byState[sub.state]++;
1583
- const entityKey = `${sub.entity}:${sub.entityId}`;
1584
- byEntity[entityKey] = (byEntity[entityKey] ?? 0) + 1;
1585
- }
1586
- return {
1587
- total: this.subscriptions.size,
1588
- byState,
1589
- byEntity
1590
- };
1591
- }
1592
- clear() {
1593
- for (const sub of this.subscriptions.values()) {
1594
- sub.observer.complete?.();
1595
- }
1596
- this.subscriptions.clear();
1597
- this.entityIndex.clear();
1598
- }
1599
- clearErrors() {
1600
- const toRemove = [];
1601
- for (const [id, sub] of this.subscriptions) {
1602
- if (sub.state === "error") {
1603
- toRemove.push(id);
1604
- }
1605
- }
1606
- for (const id of toRemove) {
1607
- this.remove(id);
1608
- }
1609
- }
1610
- }
1611
2531
 
1612
2532
  // src/create.ts
1613
2533
  import { createEffect, createSignal, on, onCleanup } from "solid-js";