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