@arcote.tech/arc-adapter-db-postgres 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.
Files changed (2) hide show
  1. package/dist/index.js +706 -25
  2. package/package.json +1 -1
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-rewrite/dist/index.js
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 (!value)
1565
- return null;
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
- setViewData(viewName, items) {
3684
+ hasData(viewName) {
3088
3685
  const store = this.stores.get(viewName);
3089
- if (store) {
3090
- store.setAll(items);
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
- this.data.set(id3, { ...existing, ...updates });
3148
- this.notifyListeners();
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;
@@ -5612,11 +6285,19 @@ class PostgreSQLReadWriteTransaction extends PostgreSQLReadTransaction {
5612
6285
  if (!table) {
5613
6286
  throw new Error(`Store ${store} not found`);
5614
6287
  }
5615
- const query = `UPDATE "${table.name}" SET "deleted" = $1, "lastUpdate" = $2 WHERE "${table.primaryKey}" = $3`;
5616
- this.queries.push({
5617
- sql: query,
5618
- params: [true, new Date().toISOString(), id2]
5619
- });
6288
+ if (this.adapter.hasSoftDelete(store)) {
6289
+ const query = `UPDATE "${table.name}" SET "deleted" = $1, "lastUpdate" = $2 WHERE "${table.primaryKey}" = $3`;
6290
+ this.queries.push({
6291
+ sql: query,
6292
+ params: [true, new Date().toISOString(), id2]
6293
+ });
6294
+ } else {
6295
+ const query = `DELETE FROM "${table.name}" WHERE "${table.primaryKey}" = $1`;
6296
+ this.queries.push({
6297
+ sql: query,
6298
+ params: [id2]
6299
+ });
6300
+ }
5620
6301
  }
5621
6302
  async set(store, item) {
5622
6303
  const table = this.tables.get(store);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@arcote.tech/arc-adapter-db-postgres",
3
- "version": "0.3.3",
3
+ "version": "0.4.1",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",