@arcote.tech/arc-adapter-db-sqlite 0.3.3 → 0.4.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +694 -20
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1352,7 +1352,364 @@ function apply(state, patches, applyOptions) {
|
|
|
1352
1352
|
}
|
|
1353
1353
|
var constructorString = Object.prototype.constructor.toString();
|
|
1354
1354
|
|
|
1355
|
-
// ../../../core
|
|
1355
|
+
// ../../../core/dist/index.js
|
|
1356
|
+
var TOKEN_PREFIX = "arc:token:";
|
|
1357
|
+
function hasLocalStorage() {
|
|
1358
|
+
return typeof localStorage !== "undefined";
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
class AuthAdapter {
|
|
1362
|
+
scopes = new Map;
|
|
1363
|
+
setToken(token, scope = "default") {
|
|
1364
|
+
if (!token) {
|
|
1365
|
+
this.scopes.delete(scope);
|
|
1366
|
+
if (hasLocalStorage())
|
|
1367
|
+
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
1368
|
+
return;
|
|
1369
|
+
}
|
|
1370
|
+
try {
|
|
1371
|
+
const parts = token.split(".");
|
|
1372
|
+
if (parts.length !== 3) {
|
|
1373
|
+
this.scopes.delete(scope);
|
|
1374
|
+
if (hasLocalStorage())
|
|
1375
|
+
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
1376
|
+
return;
|
|
1377
|
+
}
|
|
1378
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
1379
|
+
this.scopes.set(scope, {
|
|
1380
|
+
raw: token,
|
|
1381
|
+
decoded: {
|
|
1382
|
+
tokenName: payload.tokenName,
|
|
1383
|
+
params: payload.params || {},
|
|
1384
|
+
iat: payload.iat,
|
|
1385
|
+
exp: payload.exp
|
|
1386
|
+
}
|
|
1387
|
+
});
|
|
1388
|
+
if (hasLocalStorage())
|
|
1389
|
+
localStorage.setItem(TOKEN_PREFIX + scope, token);
|
|
1390
|
+
} catch {
|
|
1391
|
+
this.scopes.delete(scope);
|
|
1392
|
+
if (hasLocalStorage())
|
|
1393
|
+
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
loadPersisted() {
|
|
1397
|
+
if (!hasLocalStorage())
|
|
1398
|
+
return;
|
|
1399
|
+
for (let i = 0;i < localStorage.length; i++) {
|
|
1400
|
+
const key = localStorage.key(i);
|
|
1401
|
+
if (key?.startsWith(TOKEN_PREFIX)) {
|
|
1402
|
+
const scope = key.slice(TOKEN_PREFIX.length);
|
|
1403
|
+
const raw = localStorage.getItem(key);
|
|
1404
|
+
if (raw) {
|
|
1405
|
+
try {
|
|
1406
|
+
const parts = raw.split(".");
|
|
1407
|
+
if (parts.length !== 3)
|
|
1408
|
+
continue;
|
|
1409
|
+
const payload = JSON.parse(atob(parts[1]));
|
|
1410
|
+
this.scopes.set(scope, {
|
|
1411
|
+
raw,
|
|
1412
|
+
decoded: {
|
|
1413
|
+
tokenName: payload.tokenName,
|
|
1414
|
+
params: payload.params || {},
|
|
1415
|
+
iat: payload.iat,
|
|
1416
|
+
exp: payload.exp
|
|
1417
|
+
}
|
|
1418
|
+
});
|
|
1419
|
+
} catch {
|
|
1420
|
+
localStorage.removeItem(key);
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
}
|
|
1426
|
+
getToken(scope = "default") {
|
|
1427
|
+
return this.scopes.get(scope)?.raw ?? null;
|
|
1428
|
+
}
|
|
1429
|
+
getDecoded(scope = "default") {
|
|
1430
|
+
return this.scopes.get(scope)?.decoded ?? null;
|
|
1431
|
+
}
|
|
1432
|
+
getParams(scope = "default") {
|
|
1433
|
+
return this.scopes.get(scope)?.decoded?.params ?? null;
|
|
1434
|
+
}
|
|
1435
|
+
getTokenName(scope = "default") {
|
|
1436
|
+
return this.scopes.get(scope)?.decoded?.tokenName ?? null;
|
|
1437
|
+
}
|
|
1438
|
+
isAuthenticated(scope) {
|
|
1439
|
+
if (scope !== undefined) {
|
|
1440
|
+
return this.scopes.has(scope);
|
|
1441
|
+
}
|
|
1442
|
+
return this.scopes.size > 0;
|
|
1443
|
+
}
|
|
1444
|
+
isExpired(scope = "default") {
|
|
1445
|
+
const entry = this.scopes.get(scope);
|
|
1446
|
+
if (!entry?.decoded?.exp)
|
|
1447
|
+
return false;
|
|
1448
|
+
return Date.now() > entry.decoded.exp;
|
|
1449
|
+
}
|
|
1450
|
+
getAllScopes() {
|
|
1451
|
+
return Array.from(this.scopes.keys());
|
|
1452
|
+
}
|
|
1453
|
+
clear() {
|
|
1454
|
+
if (hasLocalStorage()) {
|
|
1455
|
+
for (const scope of this.scopes.keys()) {
|
|
1456
|
+
localStorage.removeItem(TOKEN_PREFIX + scope);
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
this.scopes.clear();
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
var eventWireInstanceCounter = 0;
|
|
1463
|
+
|
|
1464
|
+
class EventWire {
|
|
1465
|
+
baseUrl;
|
|
1466
|
+
instanceId;
|
|
1467
|
+
ws = null;
|
|
1468
|
+
state = "disconnected";
|
|
1469
|
+
scopeTokens = new Map;
|
|
1470
|
+
lastHostEventId = null;
|
|
1471
|
+
pendingEvents = [];
|
|
1472
|
+
onEventCallback;
|
|
1473
|
+
onSyncedCallback;
|
|
1474
|
+
reconnectTimeout;
|
|
1475
|
+
syncRequested = false;
|
|
1476
|
+
viewSubscriptions = new Map;
|
|
1477
|
+
viewSubCounter = 0;
|
|
1478
|
+
pendingViewSubs = [];
|
|
1479
|
+
constructor(baseUrl) {
|
|
1480
|
+
this.baseUrl = baseUrl;
|
|
1481
|
+
this.instanceId = ++eventWireInstanceCounter;
|
|
1482
|
+
}
|
|
1483
|
+
setScopeToken(scope, token) {
|
|
1484
|
+
if (token === null) {
|
|
1485
|
+
this.scopeTokens.delete(scope);
|
|
1486
|
+
} else {
|
|
1487
|
+
this.scopeTokens.set(scope, token);
|
|
1488
|
+
}
|
|
1489
|
+
if (this.state === "connected" && this.ws) {
|
|
1490
|
+
if (token !== null) {
|
|
1491
|
+
this.ws.send(JSON.stringify({
|
|
1492
|
+
type: "scope:auth",
|
|
1493
|
+
scope,
|
|
1494
|
+
token
|
|
1495
|
+
}));
|
|
1496
|
+
}
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
hasScopeTokens() {
|
|
1500
|
+
return this.scopeTokens.size > 0;
|
|
1501
|
+
}
|
|
1502
|
+
setLastHostEventId(id) {
|
|
1503
|
+
this.lastHostEventId = id;
|
|
1504
|
+
}
|
|
1505
|
+
getLastHostEventId() {
|
|
1506
|
+
return this.lastHostEventId;
|
|
1507
|
+
}
|
|
1508
|
+
connect() {
|
|
1509
|
+
if (this.state !== "disconnected")
|
|
1510
|
+
return;
|
|
1511
|
+
this.state = "connecting";
|
|
1512
|
+
let wsUrl;
|
|
1513
|
+
if (this.baseUrl.startsWith("http://") || this.baseUrl.startsWith("https://")) {
|
|
1514
|
+
wsUrl = this.baseUrl.replace(/^http/, "ws");
|
|
1515
|
+
} else {
|
|
1516
|
+
const protocol = typeof window !== "undefined" && window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
1517
|
+
const host = typeof window !== "undefined" ? window.location.host : "localhost";
|
|
1518
|
+
wsUrl = `${protocol}//${host}${this.baseUrl}`;
|
|
1519
|
+
}
|
|
1520
|
+
const url = `${wsUrl}/ws`;
|
|
1521
|
+
try {
|
|
1522
|
+
this.ws = new WebSocket(url);
|
|
1523
|
+
this.ws.onopen = () => {
|
|
1524
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
1525
|
+
this.state = "connected";
|
|
1526
|
+
this.sendAllScopeTokens();
|
|
1527
|
+
this.requestSync();
|
|
1528
|
+
this.flushPendingEvents();
|
|
1529
|
+
this.flushPendingViewSubs();
|
|
1530
|
+
} else {
|
|
1531
|
+
console.log(`[EventWire] onopen called but ws is not OPEN, readyState:`, this.ws?.readyState);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
this.ws.onmessage = (event) => {
|
|
1535
|
+
try {
|
|
1536
|
+
const message = JSON.parse(event.data);
|
|
1537
|
+
this.handleMessage(message);
|
|
1538
|
+
} catch (err) {
|
|
1539
|
+
console.error("EventWire: Failed to parse message", err);
|
|
1540
|
+
}
|
|
1541
|
+
};
|
|
1542
|
+
this.ws.onclose = (event) => {
|
|
1543
|
+
this.state = "disconnected";
|
|
1544
|
+
this.ws = null;
|
|
1545
|
+
console.log("EventWire disconnected");
|
|
1546
|
+
this.scheduleReconnect();
|
|
1547
|
+
};
|
|
1548
|
+
this.ws.onerror = (err) => {
|
|
1549
|
+
console.error("EventWire error:", err);
|
|
1550
|
+
if (this.state === "connecting") {
|
|
1551
|
+
this.state = "disconnected";
|
|
1552
|
+
this.ws = null;
|
|
1553
|
+
}
|
|
1554
|
+
};
|
|
1555
|
+
} catch (err) {
|
|
1556
|
+
this.state = "disconnected";
|
|
1557
|
+
this.ws = null;
|
|
1558
|
+
console.error("EventWire: Failed to connect", err);
|
|
1559
|
+
this.scheduleReconnect();
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
disconnect() {
|
|
1563
|
+
if (this.reconnectTimeout) {
|
|
1564
|
+
clearTimeout(this.reconnectTimeout);
|
|
1565
|
+
this.reconnectTimeout = undefined;
|
|
1566
|
+
}
|
|
1567
|
+
if (this.ws) {
|
|
1568
|
+
this.ws.close();
|
|
1569
|
+
this.ws = null;
|
|
1570
|
+
}
|
|
1571
|
+
this.state = "disconnected";
|
|
1572
|
+
this.syncRequested = false;
|
|
1573
|
+
}
|
|
1574
|
+
syncEvents(events) {
|
|
1575
|
+
const isActuallyConnected = this.ws && this.ws.readyState === WebSocket.OPEN;
|
|
1576
|
+
if (!isActuallyConnected) {
|
|
1577
|
+
if (this.state === "connected" && !this.ws) {
|
|
1578
|
+
this.state = "disconnected";
|
|
1579
|
+
}
|
|
1580
|
+
this.pendingEvents.push(...events);
|
|
1581
|
+
if (this.state === "disconnected") {
|
|
1582
|
+
this.connect();
|
|
1583
|
+
}
|
|
1584
|
+
return;
|
|
1585
|
+
}
|
|
1586
|
+
if (this.ws) {
|
|
1587
|
+
this.ws.send(JSON.stringify({
|
|
1588
|
+
type: "sync-events",
|
|
1589
|
+
events: events.map((e) => ({
|
|
1590
|
+
localId: e.localId,
|
|
1591
|
+
type: e.type,
|
|
1592
|
+
payload: e.payload,
|
|
1593
|
+
createdAt: e.createdAt
|
|
1594
|
+
}))
|
|
1595
|
+
}));
|
|
1596
|
+
}
|
|
1597
|
+
}
|
|
1598
|
+
onEvent(callback) {
|
|
1599
|
+
this.onEventCallback = callback;
|
|
1600
|
+
}
|
|
1601
|
+
onSynced(callback) {
|
|
1602
|
+
this.onSyncedCallback = callback;
|
|
1603
|
+
}
|
|
1604
|
+
subscribeQuery(descriptor, callback, scope) {
|
|
1605
|
+
const subscriptionId = `qs_${++this.viewSubCounter}_${Date.now()}`;
|
|
1606
|
+
this.viewSubscriptions.set(subscriptionId, callback);
|
|
1607
|
+
if (this.state === "connected" && this.ws) {
|
|
1608
|
+
this.ws.send(JSON.stringify({
|
|
1609
|
+
type: "subscribe-query",
|
|
1610
|
+
subscriptionId,
|
|
1611
|
+
descriptor,
|
|
1612
|
+
scope
|
|
1613
|
+
}));
|
|
1614
|
+
} else {
|
|
1615
|
+
this.pendingViewSubs.push({ subscriptionId, descriptor, scope });
|
|
1616
|
+
}
|
|
1617
|
+
return subscriptionId;
|
|
1618
|
+
}
|
|
1619
|
+
unsubscribeQuery(subscriptionId) {
|
|
1620
|
+
this.viewSubscriptions.delete(subscriptionId);
|
|
1621
|
+
if (this.state === "connected" && this.ws) {
|
|
1622
|
+
this.ws.send(JSON.stringify({
|
|
1623
|
+
type: "unsubscribe-query",
|
|
1624
|
+
subscriptionId
|
|
1625
|
+
}));
|
|
1626
|
+
}
|
|
1627
|
+
this.pendingViewSubs = this.pendingViewSubs.filter((s) => s.subscriptionId !== subscriptionId);
|
|
1628
|
+
}
|
|
1629
|
+
getState() {
|
|
1630
|
+
return this.state;
|
|
1631
|
+
}
|
|
1632
|
+
sendAllScopeTokens() {
|
|
1633
|
+
if (!this.ws || this.state !== "connected")
|
|
1634
|
+
return;
|
|
1635
|
+
for (const [scope, token] of this.scopeTokens) {
|
|
1636
|
+
this.ws.send(JSON.stringify({
|
|
1637
|
+
type: "scope:auth",
|
|
1638
|
+
scope,
|
|
1639
|
+
token
|
|
1640
|
+
}));
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
handleMessage(message) {
|
|
1644
|
+
switch (message.type) {
|
|
1645
|
+
case "events":
|
|
1646
|
+
if (message.events && Array.isArray(message.events)) {
|
|
1647
|
+
for (const event of message.events) {
|
|
1648
|
+
if (this.onEventCallback) {
|
|
1649
|
+
this.onEventCallback(event);
|
|
1650
|
+
}
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
break;
|
|
1654
|
+
case "sync-complete":
|
|
1655
|
+
this.syncRequested = false;
|
|
1656
|
+
if (message.lastHostEventId) {
|
|
1657
|
+
this.lastHostEventId = message.lastHostEventId;
|
|
1658
|
+
}
|
|
1659
|
+
break;
|
|
1660
|
+
case "query-data": {
|
|
1661
|
+
const cb = this.viewSubscriptions.get(message.subscriptionId);
|
|
1662
|
+
if (cb) {
|
|
1663
|
+
cb(message.data);
|
|
1664
|
+
}
|
|
1665
|
+
break;
|
|
1666
|
+
}
|
|
1667
|
+
case "command-result":
|
|
1668
|
+
break;
|
|
1669
|
+
case "error":
|
|
1670
|
+
console.error("EventWire error from host:", message.message);
|
|
1671
|
+
break;
|
|
1672
|
+
}
|
|
1673
|
+
}
|
|
1674
|
+
requestSync() {
|
|
1675
|
+
if (!this.ws || this.state !== "connected")
|
|
1676
|
+
return;
|
|
1677
|
+
if (this.syncRequested)
|
|
1678
|
+
return;
|
|
1679
|
+
this.syncRequested = true;
|
|
1680
|
+
this.ws.send(JSON.stringify({
|
|
1681
|
+
type: "request-sync",
|
|
1682
|
+
lastHostEventId: this.lastHostEventId
|
|
1683
|
+
}));
|
|
1684
|
+
}
|
|
1685
|
+
flushPendingEvents() {
|
|
1686
|
+
if (this.pendingEvents.length > 0) {
|
|
1687
|
+
this.syncEvents(this.pendingEvents);
|
|
1688
|
+
this.pendingEvents = [];
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
flushPendingViewSubs() {
|
|
1692
|
+
if (!this.ws || this.state !== "connected")
|
|
1693
|
+
return;
|
|
1694
|
+
for (const sub of this.pendingViewSubs) {
|
|
1695
|
+
this.ws.send(JSON.stringify({
|
|
1696
|
+
type: "subscribe-query",
|
|
1697
|
+
subscriptionId: sub.subscriptionId,
|
|
1698
|
+
descriptor: sub.descriptor,
|
|
1699
|
+
scope: sub.scope
|
|
1700
|
+
}));
|
|
1701
|
+
}
|
|
1702
|
+
this.pendingViewSubs = [];
|
|
1703
|
+
}
|
|
1704
|
+
scheduleReconnect() {
|
|
1705
|
+
if (this.reconnectTimeout)
|
|
1706
|
+
return;
|
|
1707
|
+
this.reconnectTimeout = setTimeout(() => {
|
|
1708
|
+
this.reconnectTimeout = undefined;
|
|
1709
|
+
this.connect();
|
|
1710
|
+
}, 3000);
|
|
1711
|
+
}
|
|
1712
|
+
}
|
|
1356
1713
|
var EVENT_TABLES = {
|
|
1357
1714
|
events: "events",
|
|
1358
1715
|
tags: "event_tags",
|
|
@@ -1442,6 +1799,18 @@ class LocalEventPublisher {
|
|
|
1442
1799
|
}
|
|
1443
1800
|
}
|
|
1444
1801
|
async notifySubscribers(event) {
|
|
1802
|
+
const wildcardSubs = this.subscribers.get("*");
|
|
1803
|
+
if (wildcardSubs) {
|
|
1804
|
+
for (const callback of wildcardSubs) {
|
|
1805
|
+
try {
|
|
1806
|
+
const result = callback(event);
|
|
1807
|
+
if (result instanceof Promise)
|
|
1808
|
+
await result;
|
|
1809
|
+
} catch (error) {
|
|
1810
|
+
console.error(`[EventPublisher] Wildcard subscriber error:`, error);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1445
1814
|
const subs = this.subscribers.get(event.type);
|
|
1446
1815
|
if (!subs || subs.size === 0)
|
|
1447
1816
|
return;
|
|
@@ -1564,8 +1933,8 @@ class ArcOptional {
|
|
|
1564
1933
|
return null;
|
|
1565
1934
|
}
|
|
1566
1935
|
deserialize(value) {
|
|
1567
|
-
if (
|
|
1568
|
-
return
|
|
1936
|
+
if (value == null)
|
|
1937
|
+
return;
|
|
1569
1938
|
return this.parent.deserialize(value);
|
|
1570
1939
|
}
|
|
1571
1940
|
validate(value) {
|
|
@@ -1601,6 +1970,9 @@ class ArcBranded {
|
|
|
1601
1970
|
this.parent = parent;
|
|
1602
1971
|
this.brand = brand;
|
|
1603
1972
|
}
|
|
1973
|
+
get name() {
|
|
1974
|
+
return this.brand;
|
|
1975
|
+
}
|
|
1604
1976
|
serialize(value) {
|
|
1605
1977
|
return this.parent.serialize(value);
|
|
1606
1978
|
}
|
|
@@ -2059,12 +2431,6 @@ class ArcObject extends ArcAbstract {
|
|
|
2059
2431
|
return new ArcObject(partialShape);
|
|
2060
2432
|
}
|
|
2061
2433
|
}
|
|
2062
|
-
class ArcContextElement {
|
|
2063
|
-
name;
|
|
2064
|
-
constructor(name) {
|
|
2065
|
-
this.name = name;
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
2434
|
class ArcPrimitive extends ArcAbstract {
|
|
2069
2435
|
serialize(value) {
|
|
2070
2436
|
return value;
|
|
@@ -2328,6 +2694,24 @@ class ArcNumber extends ArcPrimitive {
|
|
|
2328
2694
|
};
|
|
2329
2695
|
}
|
|
2330
2696
|
}
|
|
2697
|
+
class ArcFragmentBase {
|
|
2698
|
+
is(type) {
|
|
2699
|
+
return this.types.includes(type);
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
class ArcContextElement extends ArcFragmentBase {
|
|
2704
|
+
name;
|
|
2705
|
+
types = ["context-element"];
|
|
2706
|
+
get id() {
|
|
2707
|
+
return this.name;
|
|
2708
|
+
}
|
|
2709
|
+
constructor(name) {
|
|
2710
|
+
super();
|
|
2711
|
+
this.name = name;
|
|
2712
|
+
}
|
|
2713
|
+
}
|
|
2714
|
+
|
|
2331
2715
|
class ArcEvent extends ArcContextElement {
|
|
2332
2716
|
data;
|
|
2333
2717
|
eventId = id("event");
|
|
@@ -2430,6 +2814,43 @@ class ArcEvent extends ArcContextElement {
|
|
|
2430
2814
|
return ArcEvent.sharedDatabaseStoreSchema();
|
|
2431
2815
|
}
|
|
2432
2816
|
}
|
|
2817
|
+
class AggregateBase {
|
|
2818
|
+
value;
|
|
2819
|
+
_id;
|
|
2820
|
+
#adapters;
|
|
2821
|
+
constructor(row, adapters) {
|
|
2822
|
+
this._id = row._id;
|
|
2823
|
+
const { _id, ...rest } = row;
|
|
2824
|
+
this.value = rest;
|
|
2825
|
+
this.#adapters = adapters;
|
|
2826
|
+
}
|
|
2827
|
+
toJSON() {
|
|
2828
|
+
return { _id: this._id, ...this.value };
|
|
2829
|
+
}
|
|
2830
|
+
get ctx() {
|
|
2831
|
+
const events = this.constructor.__aggregateEvents ?? [];
|
|
2832
|
+
const adapters = this.#adapters;
|
|
2833
|
+
const result = {};
|
|
2834
|
+
for (const entry of events) {
|
|
2835
|
+
const arcEvent = entry.event;
|
|
2836
|
+
result[arcEvent.name] = {
|
|
2837
|
+
emit: async (payload) => {
|
|
2838
|
+
if (!adapters.eventPublisher) {
|
|
2839
|
+
throw new Error(`Cannot emit event "${arcEvent.name}": no eventPublisher adapter available`);
|
|
2840
|
+
}
|
|
2841
|
+
const eventInstance = {
|
|
2842
|
+
type: arcEvent.name,
|
|
2843
|
+
payload,
|
|
2844
|
+
id: `${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
2845
|
+
createdAt: new Date
|
|
2846
|
+
};
|
|
2847
|
+
await adapters.eventPublisher.publish(eventInstance);
|
|
2848
|
+
}
|
|
2849
|
+
};
|
|
2850
|
+
}
|
|
2851
|
+
return result;
|
|
2852
|
+
}
|
|
2853
|
+
}
|
|
2433
2854
|
class DataStorage {
|
|
2434
2855
|
async commitChanges(changes) {
|
|
2435
2856
|
await Promise.all(changes.map(({ store, changes: changes2 }) => this.getStore(store).applyChanges(changes2)));
|
|
@@ -2489,6 +2910,9 @@ class StoreState {
|
|
|
2489
2910
|
applySerializedChanges(changes) {
|
|
2490
2911
|
return Promise.all(changes.map((change) => this.applyChange(change)));
|
|
2491
2912
|
}
|
|
2913
|
+
get listenerCount() {
|
|
2914
|
+
return this.listeners.size;
|
|
2915
|
+
}
|
|
2492
2916
|
unsubscribe(listener4) {
|
|
2493
2917
|
this.listeners.delete(listener4);
|
|
2494
2918
|
}
|
|
@@ -3070,9 +3494,182 @@ function extractDatabaseAgnosticSchema(arcObject, tableName) {
|
|
|
3070
3494
|
};
|
|
3071
3495
|
}
|
|
3072
3496
|
var dateValidator = typeValidatorBuilder("Date", (value) => value instanceof Date);
|
|
3497
|
+
function buildContextAccessor(context2, adapters, contextMethod, onCall) {
|
|
3498
|
+
const result = {};
|
|
3499
|
+
for (const element2 of context2.elements) {
|
|
3500
|
+
const ctxFn = element2[contextMethod];
|
|
3501
|
+
if (typeof ctxFn !== "function")
|
|
3502
|
+
continue;
|
|
3503
|
+
const methods = ctxFn.call(element2, adapters);
|
|
3504
|
+
if (!methods || typeof methods !== "object")
|
|
3505
|
+
continue;
|
|
3506
|
+
const wrapped = {};
|
|
3507
|
+
for (const [methodName, method] of Object.entries(methods)) {
|
|
3508
|
+
if (typeof method !== "function")
|
|
3509
|
+
continue;
|
|
3510
|
+
wrapped[methodName] = (...args) => onCall({ element: element2.name, method: methodName, args }, () => method(...args));
|
|
3511
|
+
}
|
|
3512
|
+
result[element2.name] = wrapped;
|
|
3513
|
+
}
|
|
3514
|
+
return result;
|
|
3515
|
+
}
|
|
3516
|
+
function executeDescriptor(descriptor, context2, adapters, contextMethod) {
|
|
3517
|
+
const element2 = context2.get(descriptor.element);
|
|
3518
|
+
if (!element2) {
|
|
3519
|
+
throw new Error(`Element '${descriptor.element}' not found in context`);
|
|
3520
|
+
}
|
|
3521
|
+
const ctxFn = element2[contextMethod];
|
|
3522
|
+
if (typeof ctxFn !== "function") {
|
|
3523
|
+
throw new Error(`Element '${descriptor.element}' has no ${contextMethod}`);
|
|
3524
|
+
}
|
|
3525
|
+
const methods = ctxFn.call(element2, adapters);
|
|
3526
|
+
const method = methods?.[descriptor.method];
|
|
3527
|
+
if (typeof method !== "function") {
|
|
3528
|
+
throw new Error(`Method '${descriptor.method}' not found on '${descriptor.element}'`);
|
|
3529
|
+
}
|
|
3530
|
+
return method(...descriptor.args);
|
|
3531
|
+
}
|
|
3532
|
+
|
|
3533
|
+
class ScopedModel {
|
|
3534
|
+
context;
|
|
3535
|
+
scopeName;
|
|
3536
|
+
parent;
|
|
3537
|
+
authAdapter;
|
|
3538
|
+
scopedAdapters;
|
|
3539
|
+
tokenListeners = new Set;
|
|
3540
|
+
constructor(parent, scopeName) {
|
|
3541
|
+
this.parent = parent;
|
|
3542
|
+
this.context = parent.context;
|
|
3543
|
+
this.scopeName = scopeName;
|
|
3544
|
+
this.authAdapter = new AuthAdapter;
|
|
3545
|
+
this.scopedAdapters = {
|
|
3546
|
+
...parent.getAdapters(),
|
|
3547
|
+
authAdapter: this.authAdapter,
|
|
3548
|
+
scope: this
|
|
3549
|
+
};
|
|
3550
|
+
}
|
|
3551
|
+
setToken(token) {
|
|
3552
|
+
this.authAdapter.setToken(token);
|
|
3553
|
+
const eventWire = this.parent.getAdapters().eventWire;
|
|
3554
|
+
if (eventWire) {
|
|
3555
|
+
eventWire.setScopeToken(this.scopeName, token);
|
|
3556
|
+
if (token && eventWire.getState() === "disconnected") {
|
|
3557
|
+
eventWire.connect();
|
|
3558
|
+
}
|
|
3559
|
+
}
|
|
3560
|
+
for (const listener4 of this.tokenListeners) {
|
|
3561
|
+
listener4();
|
|
3562
|
+
}
|
|
3563
|
+
}
|
|
3564
|
+
getToken() {
|
|
3565
|
+
return this.authAdapter.getToken();
|
|
3566
|
+
}
|
|
3567
|
+
getDecoded() {
|
|
3568
|
+
return this.authAdapter.getDecoded();
|
|
3569
|
+
}
|
|
3570
|
+
getParams() {
|
|
3571
|
+
return this.authAdapter.getParams();
|
|
3572
|
+
}
|
|
3573
|
+
isAuthenticated() {
|
|
3574
|
+
return this.authAdapter.isAuthenticated();
|
|
3575
|
+
}
|
|
3576
|
+
onTokenChange(listener4) {
|
|
3577
|
+
this.tokenListeners.add(listener4);
|
|
3578
|
+
return () => {
|
|
3579
|
+
this.tokenListeners.delete(listener4);
|
|
3580
|
+
};
|
|
3581
|
+
}
|
|
3582
|
+
getAdapters() {
|
|
3583
|
+
return this.scopedAdapters;
|
|
3584
|
+
}
|
|
3585
|
+
getEnvironment() {
|
|
3586
|
+
return this.parent.getEnvironment();
|
|
3587
|
+
}
|
|
3588
|
+
getAuth() {
|
|
3589
|
+
return { scope: this.scopeName, token: this.getToken() };
|
|
3590
|
+
}
|
|
3591
|
+
executeCommand(name, params) {
|
|
3592
|
+
const wire = this.parent.getAdapters().commandWire;
|
|
3593
|
+
if (!wire) {
|
|
3594
|
+
throw new Error(`Cannot execute command "${name}": no commandWire available.`);
|
|
3595
|
+
}
|
|
3596
|
+
return wire.executeCommand(name, params, this.getAuth());
|
|
3597
|
+
}
|
|
3598
|
+
remoteQuery(viewName, options) {
|
|
3599
|
+
const wire = this.parent.getAdapters().queryWire;
|
|
3600
|
+
if (!wire) {
|
|
3601
|
+
throw new Error(`Cannot query "${viewName}": no queryWire available.`);
|
|
3602
|
+
}
|
|
3603
|
+
return wire.query(viewName, options, this.getAuth());
|
|
3604
|
+
}
|
|
3605
|
+
subscribeQuery(descriptor, callback) {
|
|
3606
|
+
const wire = this.parent.getAdapters().eventWire;
|
|
3607
|
+
if (!wire) {
|
|
3608
|
+
throw new Error(`Cannot subscribe to query: no eventWire available.`);
|
|
3609
|
+
}
|
|
3610
|
+
return wire.subscribeQuery(descriptor, callback, this.scopeName);
|
|
3611
|
+
}
|
|
3612
|
+
get query() {
|
|
3613
|
+
return buildContextAccessor(this.context, this.scopedAdapters, "queryContext", (descriptor) => descriptor);
|
|
3614
|
+
}
|
|
3615
|
+
get command() {
|
|
3616
|
+
return buildContextAccessor(this.context, this.scopedAdapters, "mutateContext", (descriptor) => descriptor);
|
|
3617
|
+
}
|
|
3618
|
+
callQuery(descriptor) {
|
|
3619
|
+
return executeDescriptor(descriptor, this.context, this.scopedAdapters, "queryContext");
|
|
3620
|
+
}
|
|
3621
|
+
callCommand(descriptor) {
|
|
3622
|
+
return executeDescriptor(descriptor, this.context, this.scopedAdapters, "mutateContext");
|
|
3623
|
+
}
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
class Model {
|
|
3627
|
+
context;
|
|
3628
|
+
adapters;
|
|
3629
|
+
environment;
|
|
3630
|
+
initialized = false;
|
|
3631
|
+
scopes = new Map;
|
|
3632
|
+
constructor(context2, options) {
|
|
3633
|
+
this.context = context2;
|
|
3634
|
+
this.adapters = options.adapters;
|
|
3635
|
+
this.environment = options.environment;
|
|
3636
|
+
}
|
|
3637
|
+
async init() {
|
|
3638
|
+
if (this.initialized) {
|
|
3639
|
+
return;
|
|
3640
|
+
}
|
|
3641
|
+
if (this.adapters.eventPublisher) {
|
|
3642
|
+
const views = this.context.elements.filter((element2) => ("getHandlers" in element2) && ("databaseStoreSchema" in element2) && ("schema" in element2));
|
|
3643
|
+
this.adapters.eventPublisher.registerViews(views);
|
|
3644
|
+
}
|
|
3645
|
+
for (const element2 of this.context.elements) {
|
|
3646
|
+
if (element2.init) {
|
|
3647
|
+
await element2.init(this.environment, this.adapters);
|
|
3648
|
+
}
|
|
3649
|
+
}
|
|
3650
|
+
this.initialized = true;
|
|
3651
|
+
}
|
|
3652
|
+
getAdapters() {
|
|
3653
|
+
return this.adapters;
|
|
3654
|
+
}
|
|
3655
|
+
getEnvironment() {
|
|
3656
|
+
return this.environment;
|
|
3657
|
+
}
|
|
3658
|
+
scope(name) {
|
|
3659
|
+
let s = this.scopes.get(name);
|
|
3660
|
+
if (!s) {
|
|
3661
|
+
s = new ScopedModel(this, name);
|
|
3662
|
+
this.scopes.set(name, s);
|
|
3663
|
+
}
|
|
3664
|
+
return s;
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3073
3667
|
class StreamingQueryCache {
|
|
3074
3668
|
stores = new Map;
|
|
3075
3669
|
views = [];
|
|
3670
|
+
activeStreams = new Map;
|
|
3671
|
+
pendingUnsubscribes = new Map;
|
|
3672
|
+
static UNSUBSCRIBE_DELAY_MS = 5000;
|
|
3076
3673
|
registerViews(views) {
|
|
3077
3674
|
this.views = views;
|
|
3078
3675
|
for (const view3 of views) {
|
|
@@ -3087,10 +3684,74 @@ class StreamingQueryCache {
|
|
|
3087
3684
|
}
|
|
3088
3685
|
return this.stores.get(viewName);
|
|
3089
3686
|
}
|
|
3090
|
-
|
|
3687
|
+
hasData(viewName) {
|
|
3091
3688
|
const store = this.stores.get(viewName);
|
|
3092
|
-
|
|
3093
|
-
|
|
3689
|
+
return store ? store.hasData() : false;
|
|
3690
|
+
}
|
|
3691
|
+
hasActiveStream(viewName) {
|
|
3692
|
+
return this.activeStreams.has(viewName);
|
|
3693
|
+
}
|
|
3694
|
+
registerStream(viewName, createStream) {
|
|
3695
|
+
const pending = this.pendingUnsubscribes.get(viewName);
|
|
3696
|
+
if (pending) {
|
|
3697
|
+
clearTimeout(pending);
|
|
3698
|
+
this.pendingUnsubscribes.delete(viewName);
|
|
3699
|
+
}
|
|
3700
|
+
const existing = this.activeStreams.get(viewName);
|
|
3701
|
+
if (existing) {
|
|
3702
|
+
existing.refCount++;
|
|
3703
|
+
return {
|
|
3704
|
+
unsubscribe: () => this.unregisterStream(viewName),
|
|
3705
|
+
wasReused: true
|
|
3706
|
+
};
|
|
3707
|
+
}
|
|
3708
|
+
const streamConn = createStream();
|
|
3709
|
+
this.activeStreams.set(viewName, {
|
|
3710
|
+
unsubscribe: streamConn.unsubscribe,
|
|
3711
|
+
refCount: 1
|
|
3712
|
+
});
|
|
3713
|
+
return {
|
|
3714
|
+
unsubscribe: () => this.unregisterStream(viewName),
|
|
3715
|
+
wasReused: false
|
|
3716
|
+
};
|
|
3717
|
+
}
|
|
3718
|
+
unregisterStream(viewName) {
|
|
3719
|
+
const stream = this.activeStreams.get(viewName);
|
|
3720
|
+
if (!stream)
|
|
3721
|
+
return;
|
|
3722
|
+
stream.refCount--;
|
|
3723
|
+
if (stream.refCount <= 0) {
|
|
3724
|
+
const timeout = setTimeout(() => {
|
|
3725
|
+
this.pendingUnsubscribes.delete(viewName);
|
|
3726
|
+
const current2 = this.activeStreams.get(viewName);
|
|
3727
|
+
if (current2 && current2.refCount <= 0) {
|
|
3728
|
+
current2.unsubscribe();
|
|
3729
|
+
this.activeStreams.delete(viewName);
|
|
3730
|
+
}
|
|
3731
|
+
}, StreamingQueryCache.UNSUBSCRIBE_DELAY_MS);
|
|
3732
|
+
this.pendingUnsubscribes.set(viewName, timeout);
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
subscribeQuery(descriptor, eventWire, scope) {
|
|
3736
|
+
const key = descriptor.element;
|
|
3737
|
+
const { unsubscribe } = this.registerStream(key, () => {
|
|
3738
|
+
const subId = eventWire.subscribeQuery(descriptor, (data) => {
|
|
3739
|
+
this.setViewData(descriptor.element, data);
|
|
3740
|
+
}, scope);
|
|
3741
|
+
return { unsubscribe: () => eventWire.unsubscribeQuery(subId) };
|
|
3742
|
+
});
|
|
3743
|
+
return unsubscribe;
|
|
3744
|
+
}
|
|
3745
|
+
setViewData(viewName, data) {
|
|
3746
|
+
const store = this.stores.get(viewName);
|
|
3747
|
+
if (!store)
|
|
3748
|
+
return;
|
|
3749
|
+
if (Array.isArray(data)) {
|
|
3750
|
+
store.setAll(data);
|
|
3751
|
+
} else if (data && typeof data === "object" && "_id" in data) {
|
|
3752
|
+
store.setAll([data]);
|
|
3753
|
+
} else {
|
|
3754
|
+
store.setAll([]);
|
|
3094
3755
|
}
|
|
3095
3756
|
}
|
|
3096
3757
|
async applyEvent(event3) {
|
|
@@ -3124,6 +3785,10 @@ class StreamingQueryCache {
|
|
|
3124
3785
|
}
|
|
3125
3786
|
}
|
|
3126
3787
|
clear() {
|
|
3788
|
+
for (const stream of this.activeStreams.values()) {
|
|
3789
|
+
stream.unsubscribe();
|
|
3790
|
+
}
|
|
3791
|
+
this.activeStreams.clear();
|
|
3127
3792
|
for (const store of this.stores.values()) {
|
|
3128
3793
|
store.clear();
|
|
3129
3794
|
}
|
|
@@ -3133,32 +3798,39 @@ class StreamingQueryCache {
|
|
|
3133
3798
|
class StreamingStore {
|
|
3134
3799
|
data = new Map;
|
|
3135
3800
|
listeners = new Set;
|
|
3801
|
+
initialized = false;
|
|
3802
|
+
hasData() {
|
|
3803
|
+
return this.initialized;
|
|
3804
|
+
}
|
|
3136
3805
|
setAll(items) {
|
|
3806
|
+
this.initialized = true;
|
|
3137
3807
|
this.data.clear();
|
|
3138
3808
|
for (const item of items) {
|
|
3139
3809
|
this.data.set(item._id, item);
|
|
3140
3810
|
}
|
|
3141
|
-
this.notifyListeners();
|
|
3811
|
+
this.notifyListeners(null);
|
|
3142
3812
|
}
|
|
3143
3813
|
set(id3, item) {
|
|
3144
3814
|
this.data.set(id3, item);
|
|
3145
|
-
this.notifyListeners();
|
|
3815
|
+
this.notifyListeners([{ type: "set", id: id3, item }]);
|
|
3146
3816
|
}
|
|
3147
3817
|
modify(id3, updates) {
|
|
3148
3818
|
const existing = this.data.get(id3);
|
|
3149
3819
|
if (existing) {
|
|
3150
|
-
|
|
3151
|
-
this.
|
|
3820
|
+
const updated = { ...existing, ...updates };
|
|
3821
|
+
this.data.set(id3, updated);
|
|
3822
|
+
this.notifyListeners([{ type: "set", id: id3, item: updated }]);
|
|
3152
3823
|
}
|
|
3153
3824
|
}
|
|
3154
3825
|
remove(id3) {
|
|
3155
3826
|
if (this.data.delete(id3)) {
|
|
3156
|
-
this.notifyListeners();
|
|
3827
|
+
this.notifyListeners([{ type: "delete", id: id3, item: null }]);
|
|
3157
3828
|
}
|
|
3158
3829
|
}
|
|
3159
3830
|
clear() {
|
|
3831
|
+
this.initialized = false;
|
|
3160
3832
|
this.data.clear();
|
|
3161
|
-
this.notifyListeners();
|
|
3833
|
+
this.notifyListeners(null);
|
|
3162
3834
|
}
|
|
3163
3835
|
find(options = {}) {
|
|
3164
3836
|
let results = Array.from(this.data.values());
|
|
@@ -3177,9 +3849,9 @@ class StreamingStore {
|
|
|
3177
3849
|
this.listeners.delete(listener4);
|
|
3178
3850
|
};
|
|
3179
3851
|
}
|
|
3180
|
-
notifyListeners() {
|
|
3852
|
+
notifyListeners(events) {
|
|
3181
3853
|
for (const listener4 of this.listeners) {
|
|
3182
|
-
listener4();
|
|
3854
|
+
listener4(events);
|
|
3183
3855
|
}
|
|
3184
3856
|
}
|
|
3185
3857
|
}
|
|
@@ -3248,6 +3920,7 @@ class StreamingEventPublisher {
|
|
|
3248
3920
|
}
|
|
3249
3921
|
}
|
|
3250
3922
|
}
|
|
3923
|
+
|
|
3251
3924
|
class SecuredStoreState {
|
|
3252
3925
|
store;
|
|
3253
3926
|
tokenInstance;
|
|
@@ -4042,6 +4715,7 @@ class BunSQLiteDatabase {
|
|
|
4042
4715
|
db;
|
|
4043
4716
|
constructor(filename = ":memory:") {
|
|
4044
4717
|
this.db = new Database(filename);
|
|
4718
|
+
this.db.exec("PRAGMA journal_mode=WAL");
|
|
4045
4719
|
}
|
|
4046
4720
|
async exec(sql, params) {
|
|
4047
4721
|
try {
|