@happyvertical/smrt-web 0.43.2 → 0.43.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/AGENTS.md +30 -1
- package/README.md +84 -0
- package/dist/chunks/{src-CDdW9uYx.js → src-n14q6RHC.js} +754 -43
- package/dist/chunks/src-n14q6RHC.js.map +1 -0
- package/dist/index.d.ts +143 -3
- package/dist/index.js +2 -2
- package/dist/webmcp.d.ts +59 -2
- package/dist/webmcp.js +1 -1
- package/package.json +1 -1
- package/dist/chunks/src-CDdW9uYx.js.map +0 -1
|
@@ -1343,6 +1343,425 @@ function persistCollection(config) {
|
|
|
1343
1343
|
};
|
|
1344
1344
|
}
|
|
1345
1345
|
//#endregion
|
|
1346
|
+
//#region src/remote-query.ts
|
|
1347
|
+
function canonicalize(value) {
|
|
1348
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
1349
|
+
if (value && typeof value === "object") {
|
|
1350
|
+
const record = value;
|
|
1351
|
+
return Object.fromEntries(Object.keys(record).sort().map((key) => [key, canonicalize(record[key])]));
|
|
1352
|
+
}
|
|
1353
|
+
return value;
|
|
1354
|
+
}
|
|
1355
|
+
function keyFor(request) {
|
|
1356
|
+
const { requestId: _requestId, ...semantic } = request;
|
|
1357
|
+
return JSON.stringify(canonicalize(semantic));
|
|
1358
|
+
}
|
|
1359
|
+
function rebindRequestId(result, request) {
|
|
1360
|
+
return result.requestId === request.requestId ? result : {
|
|
1361
|
+
...result,
|
|
1362
|
+
requestId: request.requestId
|
|
1363
|
+
};
|
|
1364
|
+
}
|
|
1365
|
+
function abortError() {
|
|
1366
|
+
if (typeof DOMException !== "undefined") return new DOMException("The operation was aborted", "AbortError");
|
|
1367
|
+
const error = /* @__PURE__ */ new Error("The operation was aborted");
|
|
1368
|
+
error.name = "AbortError";
|
|
1369
|
+
return error;
|
|
1370
|
+
}
|
|
1371
|
+
function isAbort(error) {
|
|
1372
|
+
return !!error && typeof error === "object" && error.name === "AbortError";
|
|
1373
|
+
}
|
|
1374
|
+
function pageOf(result) {
|
|
1375
|
+
if (!result.page) return void 0;
|
|
1376
|
+
return result.page.kind === "offset" ? { ...result.page } : { ...result.page };
|
|
1377
|
+
}
|
|
1378
|
+
function createSmrtWebQuery(_collection, transport, options = {}) {
|
|
1379
|
+
const staleTimeMs = options.staleTimeMs ?? 3e4;
|
|
1380
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1381
|
+
const inFlight = /* @__PURE__ */ new Map();
|
|
1382
|
+
const latestSuccessfulFlight = /* @__PURE__ */ new Map();
|
|
1383
|
+
let flightSequence = 0;
|
|
1384
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
1385
|
+
let request;
|
|
1386
|
+
let visibleController;
|
|
1387
|
+
const runControllers = /* @__PURE__ */ new Set();
|
|
1388
|
+
let generation = 0;
|
|
1389
|
+
let live;
|
|
1390
|
+
let liveIntent;
|
|
1391
|
+
let liveGeneration = 0;
|
|
1392
|
+
let disposed = false;
|
|
1393
|
+
let state = {
|
|
1394
|
+
rows: [],
|
|
1395
|
+
page: void 0,
|
|
1396
|
+
total: void 0,
|
|
1397
|
+
loading: false,
|
|
1398
|
+
refreshing: false,
|
|
1399
|
+
stale: false,
|
|
1400
|
+
error: null,
|
|
1401
|
+
lastUpdated: void 0,
|
|
1402
|
+
result: void 0
|
|
1403
|
+
};
|
|
1404
|
+
const publish = (next) => {
|
|
1405
|
+
state = next;
|
|
1406
|
+
for (const listener of listeners) listener(state);
|
|
1407
|
+
};
|
|
1408
|
+
const cached = (key) => cache.get(key);
|
|
1409
|
+
const cacheSuccess = (key, result, sequence, updatedAt = Date.now()) => {
|
|
1410
|
+
if (disposed) return false;
|
|
1411
|
+
const latest = latestSuccessfulFlight.get(key);
|
|
1412
|
+
if (latest === void 0 || sequence > latest) {
|
|
1413
|
+
latestSuccessfulFlight.set(key, sequence);
|
|
1414
|
+
cache.set(key, {
|
|
1415
|
+
result,
|
|
1416
|
+
updatedAt
|
|
1417
|
+
});
|
|
1418
|
+
return true;
|
|
1419
|
+
}
|
|
1420
|
+
return false;
|
|
1421
|
+
};
|
|
1422
|
+
const apply = (result, candidate, updatedAt = Date.now(), successSequence) => {
|
|
1423
|
+
if (successSequence !== void 0 && !cacheSuccess(keyFor(candidate), result, successSequence, updatedAt)) return;
|
|
1424
|
+
publish({
|
|
1425
|
+
rows: result.rows,
|
|
1426
|
+
page: pageOf(result),
|
|
1427
|
+
total: result.total,
|
|
1428
|
+
loading: false,
|
|
1429
|
+
refreshing: false,
|
|
1430
|
+
stale: result.freshness.state === "stale",
|
|
1431
|
+
error: null,
|
|
1432
|
+
lastUpdated: updatedAt,
|
|
1433
|
+
result
|
|
1434
|
+
});
|
|
1435
|
+
};
|
|
1436
|
+
const runTransport = async (candidate, controller) => {
|
|
1437
|
+
runControllers.add(controller);
|
|
1438
|
+
try {
|
|
1439
|
+
const result = await executeSmrtWebDataQuery(transport, candidate, { signal: controller.signal });
|
|
1440
|
+
if (controller.signal.aborted) throw abortError();
|
|
1441
|
+
return result;
|
|
1442
|
+
} catch (error) {
|
|
1443
|
+
if (controller.signal.aborted) throw abortError();
|
|
1444
|
+
throw error;
|
|
1445
|
+
} finally {
|
|
1446
|
+
runControllers.delete(controller);
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
const execute = async (candidate, runOptions = {}) => {
|
|
1450
|
+
if (disposed) throw abortError();
|
|
1451
|
+
const mode = runOptions.mode ?? "visible";
|
|
1452
|
+
const key = keyFor(candidate);
|
|
1453
|
+
if (runOptions.signal?.aborted || (runOptions.deadlineMs ?? 1) <= 0) throw abortError();
|
|
1454
|
+
const rebindIntent = mode === "visible" && liveIntent?.active && (!live || request !== void 0 && keyFor(request) !== key) ? liveIntent : void 0;
|
|
1455
|
+
const entry = cached(key);
|
|
1456
|
+
const fresh = entry !== void 0 && Date.now() - entry.updatedAt < staleTimeMs;
|
|
1457
|
+
if (mode !== "visible" && !runOptions.force && fresh) return rebindRequestId(entry.result, candidate);
|
|
1458
|
+
if (mode === "visible" && !runOptions.force && fresh) {
|
|
1459
|
+
generation += 1;
|
|
1460
|
+
visibleController?.abort(abortError());
|
|
1461
|
+
if (disposed) throw abortError();
|
|
1462
|
+
visibleController = void 0;
|
|
1463
|
+
request = candidate;
|
|
1464
|
+
if (rebindIntent) live?.disconnect();
|
|
1465
|
+
const result = rebindRequestId(entry.result, candidate);
|
|
1466
|
+
apply(result, candidate, entry.updatedAt);
|
|
1467
|
+
if (disposed) throw abortError();
|
|
1468
|
+
if (rebindIntent?.active && !live) resumeLive(rebindIntent);
|
|
1469
|
+
return result;
|
|
1470
|
+
}
|
|
1471
|
+
if (mode === "visible") {
|
|
1472
|
+
generation += 1;
|
|
1473
|
+
visibleController?.abort(abortError());
|
|
1474
|
+
if (disposed) throw abortError();
|
|
1475
|
+
const invocationController = new AbortController();
|
|
1476
|
+
visibleController = invocationController;
|
|
1477
|
+
request = candidate;
|
|
1478
|
+
if (rebindIntent) live?.disconnect();
|
|
1479
|
+
const current = generation;
|
|
1480
|
+
const signal = runOptions.signal;
|
|
1481
|
+
let removeCallerAbort;
|
|
1482
|
+
if (signal?.aborted) invocationController.abort(signal.reason);
|
|
1483
|
+
else if (signal) {
|
|
1484
|
+
const abortCaller = () => invocationController.abort(signal.reason);
|
|
1485
|
+
signal.addEventListener("abort", abortCaller, { once: true });
|
|
1486
|
+
removeCallerAbort = () => signal.removeEventListener("abort", abortCaller);
|
|
1487
|
+
}
|
|
1488
|
+
if (entry) {
|
|
1489
|
+
const cachedResult = rebindRequestId(entry.result, candidate);
|
|
1490
|
+
publish({
|
|
1491
|
+
rows: cachedResult.rows,
|
|
1492
|
+
page: pageOf(cachedResult),
|
|
1493
|
+
total: cachedResult.total,
|
|
1494
|
+
loading: false,
|
|
1495
|
+
refreshing: true,
|
|
1496
|
+
stale: true,
|
|
1497
|
+
error: null,
|
|
1498
|
+
lastUpdated: entry.updatedAt,
|
|
1499
|
+
result: cachedResult
|
|
1500
|
+
});
|
|
1501
|
+
} else publish({
|
|
1502
|
+
...state,
|
|
1503
|
+
loading: true,
|
|
1504
|
+
refreshing: false,
|
|
1505
|
+
stale: false,
|
|
1506
|
+
error: null
|
|
1507
|
+
});
|
|
1508
|
+
runOptions = {
|
|
1509
|
+
...runOptions,
|
|
1510
|
+
signal: invocationController.signal,
|
|
1511
|
+
force: true
|
|
1512
|
+
};
|
|
1513
|
+
try {
|
|
1514
|
+
if (disposed) throw abortError();
|
|
1515
|
+
const result = await runShared(candidate, key, runOptions);
|
|
1516
|
+
if (current === generation) {
|
|
1517
|
+
if (cached(key)?.result === result) apply(result, candidate);
|
|
1518
|
+
else publish({
|
|
1519
|
+
...state,
|
|
1520
|
+
loading: false,
|
|
1521
|
+
refreshing: false
|
|
1522
|
+
});
|
|
1523
|
+
if (rebindIntent?.active && !live) resumeLive(rebindIntent);
|
|
1524
|
+
}
|
|
1525
|
+
return result;
|
|
1526
|
+
} catch (error) {
|
|
1527
|
+
if (current === generation) if (isAbort(error)) publish({
|
|
1528
|
+
...state,
|
|
1529
|
+
loading: false,
|
|
1530
|
+
refreshing: false
|
|
1531
|
+
});
|
|
1532
|
+
else publish({
|
|
1533
|
+
...state,
|
|
1534
|
+
loading: false,
|
|
1535
|
+
refreshing: false,
|
|
1536
|
+
stale: entry !== void 0,
|
|
1537
|
+
error
|
|
1538
|
+
});
|
|
1539
|
+
throw error;
|
|
1540
|
+
} finally {
|
|
1541
|
+
removeCallerAbort?.();
|
|
1542
|
+
if (visibleController === invocationController) visibleController = void 0;
|
|
1543
|
+
}
|
|
1544
|
+
}
|
|
1545
|
+
return runShared(candidate, key, runOptions);
|
|
1546
|
+
};
|
|
1547
|
+
const waitForFlight = (flight, key, candidate, runOptions) => {
|
|
1548
|
+
if (runOptions.signal?.aborted || (runOptions.deadlineMs ?? 1) <= 0) return Promise.reject(abortError());
|
|
1549
|
+
return new Promise((resolve, reject) => {
|
|
1550
|
+
let settled = false;
|
|
1551
|
+
const settle = () => {
|
|
1552
|
+
if (settled) return;
|
|
1553
|
+
settled = true;
|
|
1554
|
+
flight.waiters -= 1;
|
|
1555
|
+
if (timer) clearTimeout(timer);
|
|
1556
|
+
runOptions.signal?.removeEventListener("abort", cancel);
|
|
1557
|
+
};
|
|
1558
|
+
const cancel = () => {
|
|
1559
|
+
if (settled) return;
|
|
1560
|
+
settle();
|
|
1561
|
+
if (flight.waiters === 0 && !flight.settled) {
|
|
1562
|
+
flight.controller.abort(abortError());
|
|
1563
|
+
if (inFlight.get(key) === flight) inFlight.delete(key);
|
|
1564
|
+
}
|
|
1565
|
+
reject(abortError());
|
|
1566
|
+
};
|
|
1567
|
+
const timer = runOptions.deadlineMs === void 0 ? void 0 : setTimeout(cancel, runOptions.deadlineMs);
|
|
1568
|
+
flight.waiters += 1;
|
|
1569
|
+
runOptions.signal?.addEventListener("abort", cancel, { once: true });
|
|
1570
|
+
flight.promise.then((result) => {
|
|
1571
|
+
if (settled) return;
|
|
1572
|
+
settle();
|
|
1573
|
+
resolve(rebindRequestId(result, candidate));
|
|
1574
|
+
}, (error) => {
|
|
1575
|
+
if (settled) return;
|
|
1576
|
+
settle();
|
|
1577
|
+
reject(error);
|
|
1578
|
+
});
|
|
1579
|
+
});
|
|
1580
|
+
};
|
|
1581
|
+
const runShared = async (candidate, key, runOptions) => {
|
|
1582
|
+
if (runOptions.signal?.aborted || (runOptions.deadlineMs ?? 1) <= 0) throw abortError();
|
|
1583
|
+
if (!runOptions.force) {
|
|
1584
|
+
const existing = inFlight.get(key);
|
|
1585
|
+
if (existing) return waitForFlight(existing, key, candidate, runOptions);
|
|
1586
|
+
}
|
|
1587
|
+
const flight = ++flightSequence;
|
|
1588
|
+
const controller = new AbortController();
|
|
1589
|
+
const running = runTransport(candidate, controller).then((result) => {
|
|
1590
|
+
if (controller.signal.aborted) throw abortError();
|
|
1591
|
+
cacheSuccess(key, result, flight);
|
|
1592
|
+
return result;
|
|
1593
|
+
});
|
|
1594
|
+
const shared = {
|
|
1595
|
+
controller,
|
|
1596
|
+
promise: running,
|
|
1597
|
+
waiters: 0,
|
|
1598
|
+
settled: false
|
|
1599
|
+
};
|
|
1600
|
+
inFlight.set(key, shared);
|
|
1601
|
+
running.then(() => {
|
|
1602
|
+
shared.settled = true;
|
|
1603
|
+
if (inFlight.get(key) === shared) inFlight.delete(key);
|
|
1604
|
+
}, () => {
|
|
1605
|
+
shared.settled = true;
|
|
1606
|
+
if (inFlight.get(key) === shared) inFlight.delete(key);
|
|
1607
|
+
});
|
|
1608
|
+
return waitForFlight(shared, key, candidate, runOptions);
|
|
1609
|
+
};
|
|
1610
|
+
const refresh = (refreshOptions = {}) => {
|
|
1611
|
+
if (!request) return Promise.resolve(void 0);
|
|
1612
|
+
return execute(request, {
|
|
1613
|
+
...refreshOptions,
|
|
1614
|
+
mode: "visible",
|
|
1615
|
+
force: true
|
|
1616
|
+
});
|
|
1617
|
+
};
|
|
1618
|
+
const startLive = (intent) => {
|
|
1619
|
+
if (disposed || !intent.active || !request || !transport.subscribe) return void 0;
|
|
1620
|
+
const candidate = request;
|
|
1621
|
+
const currentGeneration = ++liveGeneration;
|
|
1622
|
+
const controller = new AbortController();
|
|
1623
|
+
let connected = true;
|
|
1624
|
+
let subscription;
|
|
1625
|
+
let handle;
|
|
1626
|
+
const disconnect = () => {
|
|
1627
|
+
if (!connected) return;
|
|
1628
|
+
connected = false;
|
|
1629
|
+
controller.abort();
|
|
1630
|
+
subscription.unsubscribe();
|
|
1631
|
+
if (live === handle) live = void 0;
|
|
1632
|
+
};
|
|
1633
|
+
const unsubscribe = () => {
|
|
1634
|
+
if (!intent.active) return;
|
|
1635
|
+
intent.active = false;
|
|
1636
|
+
disconnect();
|
|
1637
|
+
if (liveIntent === intent) liveIntent = void 0;
|
|
1638
|
+
if (live?.intent === intent) live.disconnect();
|
|
1639
|
+
};
|
|
1640
|
+
const reconnect = () => {
|
|
1641
|
+
if (!intent.active || disposed) return;
|
|
1642
|
+
if (!connected) {
|
|
1643
|
+
if (live?.intent === intent) live.reconnect();
|
|
1644
|
+
return;
|
|
1645
|
+
}
|
|
1646
|
+
if (currentGeneration !== liveGeneration) return;
|
|
1647
|
+
disconnect();
|
|
1648
|
+
const reconnectRequest = request;
|
|
1649
|
+
if (!reconnectRequest || keyFor(reconnectRequest) !== keyFor(candidate)) return;
|
|
1650
|
+
execute(reconnectRequest, {
|
|
1651
|
+
mode: "visible",
|
|
1652
|
+
force: true
|
|
1653
|
+
}).catch(() => void 0).finally(() => {
|
|
1654
|
+
if (intent.active && !disposed && liveIntent === intent && !live && request && keyFor(request) === keyFor(candidate)) resumeLive(intent);
|
|
1655
|
+
});
|
|
1656
|
+
};
|
|
1657
|
+
handle = {
|
|
1658
|
+
intent,
|
|
1659
|
+
disconnect,
|
|
1660
|
+
unsubscribe,
|
|
1661
|
+
reconnect
|
|
1662
|
+
};
|
|
1663
|
+
subscription = transport.subscribe(candidate, (raw) => {
|
|
1664
|
+
if (!connected || !intent.active || disposed || currentGeneration !== liveGeneration || controller.signal.aborted) return;
|
|
1665
|
+
const liveFlight = ++flightSequence;
|
|
1666
|
+
(async () => {
|
|
1667
|
+
const result = await executeSmrtWebDataQuery({ query: async () => raw }, candidate);
|
|
1668
|
+
if (connected && intent.active && !disposed && currentGeneration === liveGeneration && request && keyFor(request) === keyFor(candidate)) apply(rebindRequestId(result, request), request, Date.now(), liveFlight);
|
|
1669
|
+
})().catch((error) => {
|
|
1670
|
+
const latestSuccessful = latestSuccessfulFlight.get(keyFor(candidate));
|
|
1671
|
+
if (!isAbort(error) && connected && intent.active && !disposed && currentGeneration === liveGeneration && request && keyFor(request) === keyFor(candidate) && (latestSuccessful === void 0 || liveFlight > latestSuccessful)) publish({
|
|
1672
|
+
...state,
|
|
1673
|
+
error
|
|
1674
|
+
});
|
|
1675
|
+
});
|
|
1676
|
+
}, { signal: controller.signal });
|
|
1677
|
+
if (disposed || !intent.active) {
|
|
1678
|
+
disconnect();
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
live = handle;
|
|
1682
|
+
return handle;
|
|
1683
|
+
};
|
|
1684
|
+
const clearLiveIntent = (intent) => {
|
|
1685
|
+
intent.active = false;
|
|
1686
|
+
if (liveIntent === intent) liveIntent = void 0;
|
|
1687
|
+
};
|
|
1688
|
+
const resumeLive = (intent) => {
|
|
1689
|
+
try {
|
|
1690
|
+
startLive(intent);
|
|
1691
|
+
} catch (error) {
|
|
1692
|
+
clearLiveIntent(intent);
|
|
1693
|
+
if (!disposed) publish({
|
|
1694
|
+
...state,
|
|
1695
|
+
error
|
|
1696
|
+
});
|
|
1697
|
+
}
|
|
1698
|
+
};
|
|
1699
|
+
const subscribeLive = () => {
|
|
1700
|
+
if (disposed || !request || !transport.subscribe) return void 0;
|
|
1701
|
+
if (liveIntent) {
|
|
1702
|
+
liveIntent.active = false;
|
|
1703
|
+
live?.disconnect();
|
|
1704
|
+
}
|
|
1705
|
+
const intent = { active: true };
|
|
1706
|
+
liveIntent = intent;
|
|
1707
|
+
try {
|
|
1708
|
+
return startLive(intent);
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
clearLiveIntent(intent);
|
|
1711
|
+
throw error;
|
|
1712
|
+
}
|
|
1713
|
+
};
|
|
1714
|
+
return {
|
|
1715
|
+
get state() {
|
|
1716
|
+
return state;
|
|
1717
|
+
},
|
|
1718
|
+
get request() {
|
|
1719
|
+
return request;
|
|
1720
|
+
},
|
|
1721
|
+
execute,
|
|
1722
|
+
refresh,
|
|
1723
|
+
retry: () => refresh(),
|
|
1724
|
+
subscribe(listener) {
|
|
1725
|
+
listeners.add(listener);
|
|
1726
|
+
listener(state);
|
|
1727
|
+
return () => listeners.delete(listener);
|
|
1728
|
+
},
|
|
1729
|
+
subscribeLive,
|
|
1730
|
+
invalidate() {
|
|
1731
|
+
if (!request) return;
|
|
1732
|
+
const key = keyFor(request);
|
|
1733
|
+
const entry = cache.get(key);
|
|
1734
|
+
latestSuccessfulFlight.set(key, ++flightSequence);
|
|
1735
|
+
if (entry) cache.set(key, {
|
|
1736
|
+
...entry,
|
|
1737
|
+
updatedAt: 0
|
|
1738
|
+
});
|
|
1739
|
+
publish({
|
|
1740
|
+
...state,
|
|
1741
|
+
loading: false,
|
|
1742
|
+
refreshing: false,
|
|
1743
|
+
stale: true
|
|
1744
|
+
});
|
|
1745
|
+
},
|
|
1746
|
+
dispose() {
|
|
1747
|
+
disposed = true;
|
|
1748
|
+
generation += 1;
|
|
1749
|
+
liveGeneration += 1;
|
|
1750
|
+
visibleController?.abort(abortError());
|
|
1751
|
+
for (const controller of runControllers) controller.abort(abortError());
|
|
1752
|
+
if (liveIntent) liveIntent.active = false;
|
|
1753
|
+
live?.disconnect();
|
|
1754
|
+
live = void 0;
|
|
1755
|
+
liveIntent = void 0;
|
|
1756
|
+
request = void 0;
|
|
1757
|
+
listeners.clear();
|
|
1758
|
+
cache.clear();
|
|
1759
|
+
latestSuccessfulFlight.clear();
|
|
1760
|
+
inFlight.clear();
|
|
1761
|
+
}
|
|
1762
|
+
};
|
|
1763
|
+
}
|
|
1764
|
+
//#endregion
|
|
1346
1765
|
//#region src/sse-client.ts
|
|
1347
1766
|
var EVENT_SOURCE_CLOSED = 2;
|
|
1348
1767
|
function defaultEventSourceFactory(url, init) {
|
|
@@ -1697,38 +2116,266 @@ function getModelContext() {
|
|
|
1697
2116
|
if (mc && typeof mc.registerTool === "function") return mc;
|
|
1698
2117
|
}
|
|
1699
2118
|
function registerWebMcpTools(definitions, options = {}) {
|
|
2119
|
+
const exposure = validateExposurePolicy(options);
|
|
1700
2120
|
const ctx = getModelContext();
|
|
1701
|
-
if (!ctx) return () => {};
|
|
2121
|
+
if (!ctx) return registrationDisposer(() => {}, Promise.resolve());
|
|
1702
2122
|
const basePath = options.basePath ?? "/api/v1";
|
|
2123
|
+
const client = options.client;
|
|
2124
|
+
const { tools, allowedEffects } = selectProspectiveTools(definitions, options, exposure);
|
|
2125
|
+
if (client && tools.some((tool) => tool.kind === "canonical" && tool.effect !== "read")) validateSmrtWebClient(client);
|
|
1703
2126
|
const controller = new AbortController();
|
|
1704
2127
|
const collections = /* @__PURE__ */ new Map();
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
fetchers,
|
|
1711
|
-
basePath,
|
|
1712
|
-
fetchFn: options.fetchFn,
|
|
1713
|
-
...options.client ? { client: options.client } : {},
|
|
1714
|
-
...options.scope ? { scope: options.scope } : {}
|
|
1715
|
-
});
|
|
1716
|
-
collections.set(definition, collection);
|
|
1717
|
-
for (const descriptor of descriptors) {
|
|
1718
|
-
if (options.filter && !options.filter(definition, descriptor)) continue;
|
|
1719
|
-
ctx.registerTool({
|
|
1720
|
-
name: descriptor.name,
|
|
1721
|
-
description: descriptor.description,
|
|
1722
|
-
inputSchema: descriptor.inputSchema,
|
|
1723
|
-
annotations: { readOnlyHint: descriptor.readOnly },
|
|
1724
|
-
execute: (args) => dispatch(fetchers, collection, definition, descriptor.action, descriptor.route, args ?? {})
|
|
1725
|
-
}, { signal: controller.signal });
|
|
1726
|
-
}
|
|
1727
|
-
}
|
|
1728
|
-
return () => {
|
|
2128
|
+
const collectionFetchers = /* @__PURE__ */ new Map();
|
|
2129
|
+
let disposed = false;
|
|
2130
|
+
const dispose = () => {
|
|
2131
|
+
if (disposed) return;
|
|
2132
|
+
disposed = true;
|
|
1729
2133
|
controller.abort();
|
|
1730
2134
|
for (const collection of collections.values()) collection.cleanup().catch(() => void 0);
|
|
1731
2135
|
};
|
|
2136
|
+
const registrations = [];
|
|
2137
|
+
try {
|
|
2138
|
+
for (const tool of tools) {
|
|
2139
|
+
if (tool.kind === "legacy") {
|
|
2140
|
+
const { definition: definition2, descriptor } = tool;
|
|
2141
|
+
let fetchers2 = collectionFetchers.get(definition2);
|
|
2142
|
+
let collection = collections.get(definition2);
|
|
2143
|
+
if (!fetchers2 || !collection) {
|
|
2144
|
+
fetchers2 = options.resolveFetchers ? options.resolveFetchers(snapshotLegacyDefinition(definition2)) : createDefinitionFetchers(definition2, basePath, options.fetchFn);
|
|
2145
|
+
collection = createSmrtCollection(definition2, {
|
|
2146
|
+
fetchers: fetchers2,
|
|
2147
|
+
basePath,
|
|
2148
|
+
fetchFn: options.fetchFn,
|
|
2149
|
+
...client ? { client } : {},
|
|
2150
|
+
...options.scope ? { scope: options.scope } : {}
|
|
2151
|
+
});
|
|
2152
|
+
collectionFetchers.set(definition2, fetchers2);
|
|
2153
|
+
collections.set(definition2, collection);
|
|
2154
|
+
}
|
|
2155
|
+
registrations.push(Promise.resolve(ctx.registerTool({
|
|
2156
|
+
name: tool.name,
|
|
2157
|
+
description: descriptor.description,
|
|
2158
|
+
inputSchema: descriptor.inputSchema,
|
|
2159
|
+
annotations: annotationsFor(tool),
|
|
2160
|
+
execute: guardedExecute(tool, allowedEffects, () => disposed, (args) => dispatchCollection(fetchers2, collection, definition2, descriptor.action, descriptor.route, args))
|
|
2161
|
+
}, { signal: controller.signal })));
|
|
2162
|
+
continue;
|
|
2163
|
+
}
|
|
2164
|
+
const { definition } = tool;
|
|
2165
|
+
const fetchers = options.resolveToolFetchers ? options.resolveToolFetchers(snapshotCanonicalDefinition(definition, {
|
|
2166
|
+
effect: tool.effect,
|
|
2167
|
+
destructive: tool.destructive,
|
|
2168
|
+
idempotent: tool.idempotent,
|
|
2169
|
+
openWorld: tool.openWorld
|
|
2170
|
+
})) : createDefinitionFetchers({
|
|
2171
|
+
name: definition.collection,
|
|
2172
|
+
endpoint: definition.endpoint
|
|
2173
|
+
}, basePath, options.fetchFn);
|
|
2174
|
+
registrations.push(Promise.resolve(ctx.registerTool({
|
|
2175
|
+
name: tool.name,
|
|
2176
|
+
description: definition.description,
|
|
2177
|
+
inputSchema: definition.inputSchema,
|
|
2178
|
+
annotations: annotationsFor(tool),
|
|
2179
|
+
execute: guardedExecute(tool, allowedEffects, () => disposed, (args) => dispatchDirect(fetchers, definition, args, client))
|
|
2180
|
+
}, { signal: controller.signal })));
|
|
2181
|
+
}
|
|
2182
|
+
} catch (error) {
|
|
2183
|
+
Promise.all(registrations).catch(() => void 0);
|
|
2184
|
+
dispose();
|
|
2185
|
+
throw error;
|
|
2186
|
+
}
|
|
2187
|
+
const ready = Promise.all(registrations).then(() => void 0).catch((error) => {
|
|
2188
|
+
dispose();
|
|
2189
|
+
throw error;
|
|
2190
|
+
});
|
|
2191
|
+
ready.catch(() => void 0);
|
|
2192
|
+
return registrationDisposer(dispose, ready);
|
|
2193
|
+
}
|
|
2194
|
+
function registrationDisposer(dispose, ready) {
|
|
2195
|
+
return Object.assign(dispose, { ready });
|
|
2196
|
+
}
|
|
2197
|
+
var VALID_EFFECTS = [
|
|
2198
|
+
"read",
|
|
2199
|
+
"write",
|
|
2200
|
+
"destructive"
|
|
2201
|
+
];
|
|
2202
|
+
var NAMESPACE_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
2203
|
+
function validateExposurePolicy(options) {
|
|
2204
|
+
const effects = options.effects ?? ["read"];
|
|
2205
|
+
for (const effect of effects) if (!VALID_EFFECTS.includes(effect)) throw new Error(`Invalid WebMCP effect: ${String(effect)}`);
|
|
2206
|
+
const maxTools = options.maxTools;
|
|
2207
|
+
if (maxTools !== void 0 && (!Number.isSafeInteger(maxTools) || maxTools < 0)) throw new Error("WebMCP maxTools must be a non-negative safe integer");
|
|
2208
|
+
const namespace = options.namespace;
|
|
2209
|
+
if (namespace !== void 0 && !NAMESPACE_PATTERN.test(namespace)) throw new Error("WebMCP namespace must start with an alphanumeric character and contain only letters, numbers, underscores, or hyphens");
|
|
2210
|
+
return {
|
|
2211
|
+
allowedEffects: new Set(effects),
|
|
2212
|
+
...maxTools !== void 0 ? { maxTools } : {},
|
|
2213
|
+
...namespace !== void 0 ? { namespace } : {}
|
|
2214
|
+
};
|
|
2215
|
+
}
|
|
2216
|
+
function selectProspectiveTools(definitions, options, exposure) {
|
|
2217
|
+
const { allowedEffects, maxTools, namespace } = exposure;
|
|
2218
|
+
const stableDefinitions = definitions.map((definition) => snapshotValue(definition));
|
|
2219
|
+
const tools = [];
|
|
2220
|
+
for (const definition of stableDefinitions) {
|
|
2221
|
+
if (isCanonicalToolDefinition(definition)) {
|
|
2222
|
+
const semantics = actionSemantics(definition.action, definition);
|
|
2223
|
+
if (!allowedEffects.has(semantics.effect)) continue;
|
|
2224
|
+
if (options.filter && !options.filterTool) throw new Error("[smrt-web] canonical WebMCP definitions require filterTool when filter is configured");
|
|
2225
|
+
const stableDefinition2 = snapshotCanonicalDefinition(definition, semantics);
|
|
2226
|
+
if (options.filterTool && !options.filterTool(snapshotCanonicalDefinition(stableDefinition2, semantics))) continue;
|
|
2227
|
+
tools.push({
|
|
2228
|
+
kind: "canonical",
|
|
2229
|
+
definition: stableDefinition2,
|
|
2230
|
+
descriptor: stableDefinition2,
|
|
2231
|
+
name: qualifiedToolName(stableDefinition2.name, namespace),
|
|
2232
|
+
identity: `${stableDefinition2.collection}#${stableDefinition2.action}`,
|
|
2233
|
+
...semantics
|
|
2234
|
+
});
|
|
2235
|
+
continue;
|
|
2236
|
+
}
|
|
2237
|
+
const stableDefinition = snapshotLegacyDefinition(definition);
|
|
2238
|
+
for (const descriptor of stableDefinition.toolDescriptors ?? []) {
|
|
2239
|
+
if (!stableDefinition.actions.includes(descriptor.action)) throw new Error(`WebMCP tool ${descriptor.name} exposes action ${descriptor.action} outside ${stableDefinition.name}'s allowed actions`);
|
|
2240
|
+
const semantics = actionSemantics(descriptor.action, descriptor);
|
|
2241
|
+
if (!allowedEffects.has(semantics.effect)) continue;
|
|
2242
|
+
if (options.filterTool && !options.filter) throw new Error("[smrt-web] legacy WebMCP definitions require filter when filterTool is configured");
|
|
2243
|
+
const stableDescriptor = snapshotLegacyDescriptor(descriptor, semantics);
|
|
2244
|
+
if (options.filter && !options.filter(snapshotLegacyDefinition(stableDefinition), snapshotLegacyDescriptor(stableDescriptor, semantics))) continue;
|
|
2245
|
+
tools.push({
|
|
2246
|
+
kind: "legacy",
|
|
2247
|
+
definition: stableDefinition,
|
|
2248
|
+
descriptor: stableDescriptor,
|
|
2249
|
+
name: qualifiedToolName(stableDescriptor.name, namespace),
|
|
2250
|
+
identity: `${stableDefinition.name}#${stableDescriptor.action}`,
|
|
2251
|
+
...semantics
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
validateProspectiveTools(tools, maxTools);
|
|
2256
|
+
return {
|
|
2257
|
+
tools,
|
|
2258
|
+
allowedEffects
|
|
2259
|
+
};
|
|
2260
|
+
}
|
|
2261
|
+
function qualifiedToolName(name, namespace) {
|
|
2262
|
+
return namespace ? `${namespace}_${name}` : name;
|
|
2263
|
+
}
|
|
2264
|
+
function actionSemantics(action, declared) {
|
|
2265
|
+
switch (action) {
|
|
2266
|
+
case "list":
|
|
2267
|
+
case "get": return {
|
|
2268
|
+
effect: "read",
|
|
2269
|
+
destructive: false,
|
|
2270
|
+
idempotent: true,
|
|
2271
|
+
openWorld: false
|
|
2272
|
+
};
|
|
2273
|
+
case "create": return {
|
|
2274
|
+
effect: "write",
|
|
2275
|
+
destructive: true,
|
|
2276
|
+
idempotent: false,
|
|
2277
|
+
openWorld: false
|
|
2278
|
+
};
|
|
2279
|
+
case "update": return {
|
|
2280
|
+
effect: "write",
|
|
2281
|
+
destructive: true,
|
|
2282
|
+
idempotent: true,
|
|
2283
|
+
openWorld: false
|
|
2284
|
+
};
|
|
2285
|
+
case "delete": return {
|
|
2286
|
+
effect: "destructive",
|
|
2287
|
+
destructive: true,
|
|
2288
|
+
idempotent: true,
|
|
2289
|
+
openWorld: false
|
|
2290
|
+
};
|
|
2291
|
+
default: {
|
|
2292
|
+
const effect = VALID_EFFECTS.includes(declared.effect) ? declared.effect : "destructive";
|
|
2293
|
+
return {
|
|
2294
|
+
effect,
|
|
2295
|
+
destructive: effect !== "read",
|
|
2296
|
+
idempotent: declared.idempotent ?? false,
|
|
2297
|
+
openWorld: declared.openWorld ?? true
|
|
2298
|
+
};
|
|
2299
|
+
}
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
function snapshotRoute(route) {
|
|
2303
|
+
return route ? snapshotValue(route) : void 0;
|
|
2304
|
+
}
|
|
2305
|
+
function snapshotValue(value, active = /* @__PURE__ */ new WeakSet(), path = "$") {
|
|
2306
|
+
if (value && typeof value === "object") {
|
|
2307
|
+
if (active.has(value)) throw new Error(`[smrt-web] WebMCP definitions must be acyclic (cycle at ${path})`);
|
|
2308
|
+
active.add(value);
|
|
2309
|
+
}
|
|
2310
|
+
try {
|
|
2311
|
+
if (Array.isArray(value)) return value.map((entry, index) => snapshotValue(entry, active, `${path}[${index}]`));
|
|
2312
|
+
if (value && typeof value === "object") {
|
|
2313
|
+
const snapshot = {};
|
|
2314
|
+
for (const [key, entry] of Object.entries(value)) snapshot[key] = snapshotValue(entry, active, `${path}.${key}`);
|
|
2315
|
+
return snapshot;
|
|
2316
|
+
}
|
|
2317
|
+
return value;
|
|
2318
|
+
} finally {
|
|
2319
|
+
if (value && typeof value === "object") active.delete(value);
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
function snapshotLegacyDescriptor(descriptor, semantics) {
|
|
2323
|
+
return snapshotValue({
|
|
2324
|
+
...descriptor,
|
|
2325
|
+
effect: semantics.effect,
|
|
2326
|
+
idempotent: semantics.idempotent,
|
|
2327
|
+
openWorld: semantics.openWorld,
|
|
2328
|
+
readOnly: semantics.effect === "read",
|
|
2329
|
+
route: snapshotRoute(descriptor.route)
|
|
2330
|
+
});
|
|
2331
|
+
}
|
|
2332
|
+
function snapshotLegacyDefinition(definition) {
|
|
2333
|
+
const snapshot = snapshotValue({
|
|
2334
|
+
...definition,
|
|
2335
|
+
actions: [...definition.actions]
|
|
2336
|
+
});
|
|
2337
|
+
snapshot.toolDescriptors = snapshot.toolDescriptors?.map((descriptor) => snapshotLegacyDescriptor(descriptor, actionSemantics(descriptor.action, descriptor)));
|
|
2338
|
+
return snapshot;
|
|
2339
|
+
}
|
|
2340
|
+
function snapshotCanonicalDefinition(definition, semantics) {
|
|
2341
|
+
return snapshotValue({
|
|
2342
|
+
...definition,
|
|
2343
|
+
effect: semantics.effect,
|
|
2344
|
+
idempotent: semantics.idempotent,
|
|
2345
|
+
openWorld: semantics.openWorld,
|
|
2346
|
+
readOnly: semantics.effect === "read",
|
|
2347
|
+
route: snapshotRoute(definition.route)
|
|
2348
|
+
});
|
|
2349
|
+
}
|
|
2350
|
+
function validateProspectiveTools(tools, maxTools) {
|
|
2351
|
+
if (maxTools !== void 0 && tools.length > maxTools) throw new Error(`WebMCP tool budget exceeded: ${tools.length} tools selected, maximum is ${maxTools}`);
|
|
2352
|
+
const names = /* @__PURE__ */ new Set();
|
|
2353
|
+
const identities = /* @__PURE__ */ new Set();
|
|
2354
|
+
for (const tool of tools) {
|
|
2355
|
+
if (names.has(tool.name)) throw new Error(`Duplicate WebMCP tool name: ${tool.name}`);
|
|
2356
|
+
names.add(tool.name);
|
|
2357
|
+
if (identities.has(tool.identity)) throw new Error(`Duplicate WebMCP tool identity: ${tool.identity}`);
|
|
2358
|
+
identities.add(tool.identity);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
function annotationsFor(tool) {
|
|
2362
|
+
return {
|
|
2363
|
+
readOnlyHint: tool.effect === "read",
|
|
2364
|
+
destructiveHint: tool.destructive,
|
|
2365
|
+
idempotentHint: tool.idempotent,
|
|
2366
|
+
openWorldHint: tool.openWorld,
|
|
2367
|
+
untrustedContentHint: true
|
|
2368
|
+
};
|
|
2369
|
+
}
|
|
2370
|
+
function guardedExecute(tool, allowedEffects, isDisposed, execute) {
|
|
2371
|
+
return (args) => {
|
|
2372
|
+
if (isDisposed()) throw new Error(`WebMCP tool ${tool.name} is no longer registered`);
|
|
2373
|
+
if (!allowedEffects.has(tool.effect)) throw new Error(`WebMCP policy no longer allows ${tool.effect} tool ${tool.name}`);
|
|
2374
|
+
return execute(args ?? {});
|
|
2375
|
+
};
|
|
2376
|
+
}
|
|
2377
|
+
function isCanonicalToolDefinition(definition) {
|
|
2378
|
+
return "collection" in definition && "readOnly" in definition;
|
|
1732
2379
|
}
|
|
1733
2380
|
function requireId(args, action) {
|
|
1734
2381
|
const id = args.id;
|
|
@@ -1748,7 +2395,7 @@ function listParams(args) {
|
|
|
1748
2395
|
if (args.where !== void 0) params.where = args.where;
|
|
1749
2396
|
return params;
|
|
1750
2397
|
}
|
|
1751
|
-
async function
|
|
2398
|
+
async function dispatchCollection(fetchers, collection, definition, action, route, args) {
|
|
1752
2399
|
switch (action) {
|
|
1753
2400
|
case "list": {
|
|
1754
2401
|
const rows = unwrapListResult(await fetchers.list(listParams(args)), definition.name);
|
|
@@ -1789,6 +2436,45 @@ async function dispatch(fetchers, collection, definition, action, route, args) {
|
|
|
1789
2436
|
return JSON.stringify(await collection.action(action, args, route));
|
|
1790
2437
|
}
|
|
1791
2438
|
}
|
|
2439
|
+
async function dispatchDirect(fetchers, definition, args, client) {
|
|
2440
|
+
let result;
|
|
2441
|
+
switch (definition.action) {
|
|
2442
|
+
case "list":
|
|
2443
|
+
if (!fetchers.list) throw new Error(`${definition.collection} has no list action`);
|
|
2444
|
+
result = unwrapListResult(await fetchers.list(listParams(args)), definition.collection);
|
|
2445
|
+
break;
|
|
2446
|
+
case "get":
|
|
2447
|
+
if (!fetchers.get) throw new Error(`${definition.collection} has no get action`);
|
|
2448
|
+
result = unwrapItemResult(await fetchers.get(requireIdentifier(args)), `get(${definition.collection})`);
|
|
2449
|
+
break;
|
|
2450
|
+
case "create":
|
|
2451
|
+
if (!fetchers.create) throw new Error(`${definition.collection} has no create action`);
|
|
2452
|
+
result = unwrapItemResult(await fetchers.create(args), `create(${definition.collection})`);
|
|
2453
|
+
break;
|
|
2454
|
+
case "update": {
|
|
2455
|
+
if (!fetchers.update) throw new Error(`${definition.collection} has no update action`);
|
|
2456
|
+
const id = requireId(args, "update");
|
|
2457
|
+
const { id: _id, ...body } = args;
|
|
2458
|
+
result = unwrapItemResult(await fetchers.update(id, body), `update(${definition.collection})`);
|
|
2459
|
+
break;
|
|
2460
|
+
}
|
|
2461
|
+
case "delete": {
|
|
2462
|
+
if (!fetchers.delete) throw new Error(`${definition.collection} has no delete action`);
|
|
2463
|
+
const id = requireId(args, "delete");
|
|
2464
|
+
throwIfSmrtWebError(await fetchers.delete(id), `delete(${definition.collection})`);
|
|
2465
|
+
result = {
|
|
2466
|
+
success: true,
|
|
2467
|
+
id
|
|
2468
|
+
};
|
|
2469
|
+
break;
|
|
2470
|
+
}
|
|
2471
|
+
default:
|
|
2472
|
+
if (!fetchers.custom) throw new Error(`${definition.collection} has no custom action fetcher`);
|
|
2473
|
+
result = throwIfSmrtWebError(await fetchers.custom(definition.action, args, definition.route), `${definition.action}(${definition.collection})`);
|
|
2474
|
+
}
|
|
2475
|
+
if (!definition.readOnly && client) invalidateSmrtWebCollections(client, [definition.collection, ...definition.relationships.map((relationship) => relationship.relatedCollection)]);
|
|
2476
|
+
return JSON.stringify(result);
|
|
2477
|
+
}
|
|
1792
2478
|
async function settleTransaction(transaction, collection, key, fallback) {
|
|
1793
2479
|
await transaction.isPersisted.promise;
|
|
1794
2480
|
const persisted = persistedMutationResults.get(collection)?.get(key);
|
|
@@ -1837,23 +2523,34 @@ function createHttpRequestError(collectionName, status, payload) {
|
|
|
1837
2523
|
return new SmrtWebRequestError(`[smrt-web] ${collectionName} request failed: ${detail.message ?? `HTTP ${status}`}`, payload, status, detail.code);
|
|
1838
2524
|
}
|
|
1839
2525
|
function unwrapListResult(result, collectionName) {
|
|
2526
|
+
throwIfSmrtWebError(result, `list(${collectionName})`);
|
|
1840
2527
|
if (Array.isArray(result)) return result;
|
|
1841
2528
|
if (result && typeof result === "object") {
|
|
1842
2529
|
const record = result;
|
|
1843
|
-
if (typeof record.error === "string") throw new SmrtWebRequestError(`[smrt-web] list(${collectionName}) failed: ${record.error}`, result);
|
|
1844
2530
|
if (Array.isArray(record.data)) return record.data;
|
|
1845
2531
|
}
|
|
1846
2532
|
throw new SmrtWebRequestError(`[smrt-web] list(${collectionName}) returned an unexpected payload shape`, result);
|
|
1847
2533
|
}
|
|
1848
2534
|
function unwrapItemResult(result, context) {
|
|
2535
|
+
throwIfSmrtWebError(result, context);
|
|
1849
2536
|
if (result && typeof result === "object" && !Array.isArray(result)) {
|
|
1850
2537
|
const record = result;
|
|
1851
|
-
if (typeof record.error === "string") throw new SmrtWebRequestError(`[smrt-web] ${context} failed: ${record.error}`, result);
|
|
1852
2538
|
if (record.data && typeof record.data === "object" && !Array.isArray(record.data)) return record.data;
|
|
1853
2539
|
return record;
|
|
1854
2540
|
}
|
|
1855
2541
|
throw new SmrtWebRequestError(`[smrt-web] ${context} returned an unexpected payload shape`, result);
|
|
1856
2542
|
}
|
|
2543
|
+
function throwIfSmrtWebError(result, context) {
|
|
2544
|
+
if (!result || typeof result !== "object" || Array.isArray(result)) return result;
|
|
2545
|
+
const error = result.error;
|
|
2546
|
+
if (typeof error === "string") throw new SmrtWebRequestError(`[smrt-web] ${context} failed: ${error}`, result);
|
|
2547
|
+
if (!error || typeof error !== "object" || Array.isArray(error)) return result;
|
|
2548
|
+
const failure = error;
|
|
2549
|
+
if (failure.ok !== false || typeof failure.code !== "string" || typeof failure.message !== "string") return result;
|
|
2550
|
+
const status = typeof failure.status === "number" && Number.isInteger(failure.status) && failure.status >= 400 && failure.status <= 599 ? failure.status : void 0;
|
|
2551
|
+
if (status !== void 0 && status >= 500) throw new SmrtWebRequestError(`[smrt-web] ${context} failed: server error`, void 0, status);
|
|
2552
|
+
throw new SmrtWebRequestError(`[smrt-web] ${context} failed: ${failure.message}`, result, status, failure.code);
|
|
2553
|
+
}
|
|
1857
2554
|
var SMRT_TO_REST_OPERATOR = {
|
|
1858
2555
|
">": "gt",
|
|
1859
2556
|
">=": "gte",
|
|
@@ -1955,12 +2652,12 @@ function createDefinitionFetchers(definition, basePath = "/api/v1", fetchFn = (.
|
|
|
1955
2652
|
if (customRoute.method !== "GET") init.body = JSON.stringify(body);
|
|
1956
2653
|
else {
|
|
1957
2654
|
const queryParams = new URLSearchParams();
|
|
1958
|
-
if (optionsBag) if (optionsValue === void 0) queryParams.set(CUSTOM_OPTIONS_QUERY_MARKER, "undefined");
|
|
1959
|
-
else if (optionsValue === null) queryParams.set(CUSTOM_OPTIONS_QUERY_MARKER, "null");
|
|
2655
|
+
if (optionsBag) if (optionsValue === void 0 && pathArgs.size === 0) queryParams.set(CUSTOM_OPTIONS_QUERY_MARKER, "undefined");
|
|
2656
|
+
else if (optionsValue === null && pathArgs.size === 0) queryParams.set(CUSTOM_OPTIONS_QUERY_MARKER, "null");
|
|
1960
2657
|
else {
|
|
1961
|
-
const entries = Object.entries(typeof optionsValue === "object" && !Array.isArray(optionsValue) ? optionsValue : {}).filter(([, value]) => value !== void 0 && value !== null);
|
|
2658
|
+
const entries = Object.entries(optionsValue !== null && typeof optionsValue === "object" && !Array.isArray(optionsValue) ? optionsValue : {}).filter(([, value]) => value !== void 0 && value !== null);
|
|
1962
2659
|
for (const [key, value] of entries) queryParams.set(key, typeof value === "object" ? JSON.stringify(value) : String(value));
|
|
1963
|
-
if (entries.length === 0) queryParams.set(CUSTOM_OPTIONS_QUERY_MARKER, "object");
|
|
2660
|
+
if (entries.length === 0 && pathArgs.size === 0) queryParams.set(CUSTOM_OPTIONS_QUERY_MARKER, "object");
|
|
1964
2661
|
}
|
|
1965
2662
|
else {
|
|
1966
2663
|
const queryBody = body !== null && typeof body === "object" && !Array.isArray(body) ? body : {};
|
|
@@ -1981,18 +2678,36 @@ function newLocalId() {
|
|
|
1981
2678
|
if (cryptoRef?.randomUUID) return cryptoRef.randomUUID();
|
|
1982
2679
|
return `local-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
1983
2680
|
}
|
|
2681
|
+
var smrtWebClientHandles = /* @__PURE__ */ new WeakSet();
|
|
1984
2682
|
function createSmrtWebClient() {
|
|
1985
|
-
|
|
2683
|
+
const engine = {
|
|
1986
2684
|
__smrtWebClient: "SmrtWebClient",
|
|
1987
2685
|
queryClient: new QueryClient()
|
|
1988
2686
|
};
|
|
2687
|
+
smrtWebClientHandles.add(engine);
|
|
2688
|
+
return engine;
|
|
1989
2689
|
}
|
|
1990
2690
|
function resolveQueryClient(client) {
|
|
1991
2691
|
if (!client) return new QueryClient();
|
|
1992
2692
|
const engine = client;
|
|
1993
|
-
if (engine.__smrtWebClient !== "SmrtWebClient" || !engine.queryClient) throw new SmrtWebRequestError("[smrt-web] options.client must be a handle from createSmrtWebClient()");
|
|
2693
|
+
if (!smrtWebClientHandles.has(engine) || engine.__smrtWebClient !== "SmrtWebClient" || !engine.queryClient) throw new SmrtWebRequestError("[smrt-web] options.client must be a handle from createSmrtWebClient()");
|
|
1994
2694
|
return engine.queryClient;
|
|
1995
2695
|
}
|
|
2696
|
+
function validateSmrtWebClient(client) {
|
|
2697
|
+
resolveQueryClient(client);
|
|
2698
|
+
}
|
|
2699
|
+
function invalidateCollectionQueries(queryClient, collectionNames) {
|
|
2700
|
+
if (collectionNames.size === 0) return;
|
|
2701
|
+
queryClient.invalidateQueries({ predicate: (query) => {
|
|
2702
|
+
const key = query.queryKey;
|
|
2703
|
+
if (!Array.isArray(key) || key.length === 0) return false;
|
|
2704
|
+
const collectionSegment = key[key.length - 1];
|
|
2705
|
+
return typeof collectionSegment === "string" && collectionNames.has(collectionSegment);
|
|
2706
|
+
} });
|
|
2707
|
+
}
|
|
2708
|
+
function invalidateSmrtWebCollections(client, collectionNames) {
|
|
2709
|
+
invalidateCollectionQueries(resolveQueryClient(client), new Set(collectionNames));
|
|
2710
|
+
}
|
|
1996
2711
|
function toPlainRow(row) {
|
|
1997
2712
|
const plain = {};
|
|
1998
2713
|
for (const [key, value] of Object.entries(row)) if (key.charCodeAt(0) !== 36) plain[key] = value;
|
|
@@ -2041,12 +2756,7 @@ function createSmrtCollection(definition, options) {
|
|
|
2041
2756
|
const invalidationTargets = /* @__PURE__ */ new Set([definition.name]);
|
|
2042
2757
|
for (const relationship of definition.relationships ?? []) invalidationTargets.add(relationship.relatedCollection);
|
|
2043
2758
|
const invalidateRelated = () => {
|
|
2044
|
-
queryClient
|
|
2045
|
-
const key = query.queryKey;
|
|
2046
|
-
if (!Array.isArray(key) || key.length === 0) return false;
|
|
2047
|
-
const collectionSegment = key[key.length - 1];
|
|
2048
|
-
return typeof collectionSegment === "string" && invalidationTargets.has(collectionSegment);
|
|
2049
|
-
} });
|
|
2759
|
+
invalidateCollectionQueries(queryClient, invalidationTargets);
|
|
2050
2760
|
};
|
|
2051
2761
|
const ctx = {
|
|
2052
2762
|
definition,
|
|
@@ -2210,7 +2920,7 @@ function createSmrtCollection(definition, options) {
|
|
|
2210
2920
|
data: {},
|
|
2211
2921
|
baseUpdatedAt: getBaseUpdatedAt(mutation.original)
|
|
2212
2922
|
};
|
|
2213
|
-
const outcome = await persistMutation(envelope, async () => fetchers.delete(key));
|
|
2923
|
+
const outcome = await persistMutation(envelope, async () => throwIfSmrtWebError(await fetchers.delete(key), `delete(${definition.name})`));
|
|
2214
2924
|
mutationResults.set(envelope.key, outcome.result);
|
|
2215
2925
|
anyHandled = anyHandled || outcome.handled;
|
|
2216
2926
|
}
|
|
@@ -2269,6 +2979,7 @@ function createSmrtCollection(definition, options) {
|
|
|
2269
2979
|
async action(action, args, route) {
|
|
2270
2980
|
if (!fetchers.custom) throw new Error(`${definition.name} has no custom action fetcher`);
|
|
2271
2981
|
const result = route === void 0 ? await fetchers.custom(action, args) : await fetchers.custom(action, args, route);
|
|
2982
|
+
throwIfSmrtWebError(result, `${action}(${definition.name})`);
|
|
2272
2983
|
invalidateRelated();
|
|
2273
2984
|
return result;
|
|
2274
2985
|
}
|
|
@@ -2287,6 +2998,6 @@ function createSmrtCollection(definition, options) {
|
|
|
2287
2998
|
return handle;
|
|
2288
2999
|
}
|
|
2289
3000
|
//#endregion
|
|
2290
|
-
export {
|
|
3001
|
+
export { MAX_SMRT_WEB_DATA_QUERY_RESULT_BYTES as A, registerDurableResource as C, MAX_SMRT_WEB_DATA_QUERY_FACET_VALUES as D, MAX_SMRT_WEB_DATA_QUERY_FACETS as E, normalizeSmrtWebDataQueryResult as F, runWrapMutation as I, MAX_SMRT_WEB_DATA_QUERY_STRING_LENGTH as M, MAX_SMRT_WEB_DATA_QUERY_WARNINGS as N, MAX_SMRT_WEB_DATA_QUERY_OFFSET as O, executeSmrtWebDataQuery as P, durableStoreNamespace as S, MAX_SMRT_WEB_DATA_QUERY_CONTAINER_ITEMS as T, createSmrtWebQuery as _, createSmrtWebClient as a, getOutboxHandle as b, newLocalId as c, unwrapListResult as d, validateSmrtWebClient as f, liveInvalidation as g, createSmrtWebEventSubscriber as h, createSmrtCollection as i, MAX_SMRT_WEB_DATA_QUERY_ROWS as j, MAX_SMRT_WEB_DATA_QUERY_PAGE_LIMIT as k, throwIfSmrtWebError as l, createUpdateState as m, buildListQuery as n, getEngineCollection as o, registerWebMcpTools as p, createDefinitionFetchers as r, invalidateSmrtWebCollections as s, SmrtWebRequestError as t, unwrapItemResult as u, DEFAULT_PERSIST_DEBOUNCE_MS as v, wipeDurableStore as w, offlineOutbox as x, persistCollection as y };
|
|
2291
3002
|
|
|
2292
|
-
//# sourceMappingURL=src-
|
|
3003
|
+
//# sourceMappingURL=src-n14q6RHC.js.map
|