@arcote.tech/arc-adapter-db-sqlite 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.
Files changed (2) hide show
  1. package/dist/index.js +694 -20
  2. package/package.json +2 -2
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-rewrite/dist/index.js
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 (!value)
1568
- return null;
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
- setViewData(viewName, items) {
3687
+ hasData(viewName) {
3091
3688
  const store = this.stores.get(viewName);
3092
- if (store) {
3093
- store.setAll(items);
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
- this.data.set(id3, { ...existing, ...updates });
3151
- this.notifyListeners();
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 {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-adapter-db-sqlite",
3
- "version": "0.3.3",
3
+ "version": "0.4.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -20,7 +20,7 @@
20
20
  "test": "bun test"
21
21
  },
22
22
  "peerDependencies": {
23
- "@arcote.tech/arc": "^0.3.0"
23
+ "@arcote.tech/arc": "^0.4.2"
24
24
  },
25
25
  "devDependencies": {
26
26
  "typescript": "^5.0.0"