@arcote.tech/arc 0.3.1 → 0.3.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 CHANGED
@@ -48,14 +48,20 @@ class AuthAdapter {
48
48
  }
49
49
  // src/adapters/wire.ts
50
50
  class Wire {
51
- baseUrl;
52
51
  token = null;
52
+ baseUrl;
53
53
  constructor(baseUrl) {
54
54
  this.baseUrl = baseUrl;
55
55
  }
56
56
  setAuthToken(token) {
57
57
  this.token = token;
58
58
  }
59
+ getAuthToken() {
60
+ return this.token;
61
+ }
62
+ getBaseUrl() {
63
+ return this.baseUrl;
64
+ }
59
65
  async fetch(endpoint, options = {}) {
60
66
  const headers = new Headers(options.headers);
61
67
  if (this.token) {
@@ -241,6 +247,62 @@ class EventWire {
241
247
  }, 3000);
242
248
  }
243
249
  }
250
+ // src/adapters/query-wire.ts
251
+ class QueryWire extends Wire {
252
+ constructor(baseUrl) {
253
+ super(baseUrl);
254
+ }
255
+ async query(viewName, options = {}) {
256
+ const response = await this.fetch(`/query/${viewName}`, {
257
+ method: "POST",
258
+ body: JSON.stringify(options)
259
+ });
260
+ if (!response.ok) {
261
+ const errorText = await response.text();
262
+ throw new Error(`Query ${viewName} failed: ${response.status} ${errorText}`);
263
+ }
264
+ return await response.json();
265
+ }
266
+ stream(viewName, options, callback, token) {
267
+ const params = new URLSearchParams;
268
+ if (options?.where) {
269
+ params.set("where", JSON.stringify(options.where));
270
+ }
271
+ if (options?.orderBy) {
272
+ params.set("orderBy", JSON.stringify(options.orderBy));
273
+ }
274
+ if (options?.limit) {
275
+ params.set("limit", String(options.limit));
276
+ }
277
+ const authToken = token ?? this.getAuthToken();
278
+ if (authToken) {
279
+ params.set("token", authToken);
280
+ }
281
+ const queryString = params.toString();
282
+ const url = `${this.getBaseUrl()}/stream/${viewName}${queryString ? `?${queryString}` : ""}`;
283
+ const eventSource = new EventSource(url);
284
+ eventSource.onmessage = (event) => {
285
+ try {
286
+ const message = JSON.parse(event.data);
287
+ if (message.type === "data") {
288
+ callback(message.data);
289
+ }
290
+ } catch (err) {
291
+ console.error("QueryWire: Failed to parse SSE message", err);
292
+ }
293
+ };
294
+ eventSource.onerror = (err) => {
295
+ console.error("QueryWire: SSE error", err);
296
+ };
297
+ const unsubscribe = () => {
298
+ eventSource.close();
299
+ };
300
+ return {
301
+ eventSource,
302
+ unsubscribe
303
+ };
304
+ }
305
+ }
244
306
  // src/adapters/event-publisher.ts
245
307
  var EVENT_TABLES = {
246
308
  events: "events",
@@ -252,6 +314,7 @@ class LocalEventPublisher {
252
314
  dataStorage;
253
315
  views = [];
254
316
  syncCallback;
317
+ subscribers = new Map;
255
318
  constructor(dataStorage) {
256
319
  this.dataStorage = dataStorage;
257
320
  }
@@ -261,6 +324,21 @@ class LocalEventPublisher {
261
324
  registerViews(views) {
262
325
  this.views = views;
263
326
  }
327
+ subscribe(eventType, callback) {
328
+ if (!this.subscribers.has(eventType)) {
329
+ this.subscribers.set(eventType, new Set);
330
+ }
331
+ this.subscribers.get(eventType).add(callback);
332
+ return () => {
333
+ const subs = this.subscribers.get(eventType);
334
+ if (subs) {
335
+ subs.delete(callback);
336
+ if (subs.size === 0) {
337
+ this.subscribers.delete(eventType);
338
+ }
339
+ }
340
+ };
341
+ }
264
342
  async publish(event) {
265
343
  const allChanges = [];
266
344
  const storedEvent = {
@@ -309,10 +387,30 @@ class LocalEventPublisher {
309
387
  const viewChanges = await this.collectViewChanges(event);
310
388
  allChanges.push(...viewChanges);
311
389
  await this.dataStorage.commitChanges(allChanges);
390
+ await this.notifySubscribers(event);
312
391
  if (this.syncCallback) {
313
392
  this.syncCallback(event);
314
393
  }
315
394
  }
395
+ async notifySubscribers(event) {
396
+ const subs = this.subscribers.get(event.type);
397
+ if (!subs || subs.size === 0)
398
+ return;
399
+ const promises = [];
400
+ for (const callback of subs) {
401
+ try {
402
+ const result = callback(event);
403
+ if (result instanceof Promise) {
404
+ promises.push(result);
405
+ }
406
+ } catch (error) {
407
+ console.error(`Listener error for event "${event.type}":`, error);
408
+ }
409
+ }
410
+ if (promises.length > 0) {
411
+ await Promise.all(promises.map((p) => p.catch((e) => console.error(e))));
412
+ }
413
+ }
316
414
  async markSynced(eventIds) {
317
415
  const syncStatusStore = this.dataStorage.getStore(EVENT_TABLES.syncStatus);
318
416
  const changes = eventIds.map((eventId) => ({
@@ -423,13 +521,6 @@ function contextMerge(...contexts) {
423
521
  const allElements = contexts.flatMap((ctx) => ctx.elements);
424
522
  return new ArcContext(allElements);
425
523
  }
426
- // src/context-element/context-element.ts
427
- class ArcContextElement {
428
- name;
429
- constructor(name) {
430
- this.name = name;
431
- }
432
- }
433
524
  // src/elements/optional.ts
434
525
  class ArcOptional {
435
526
  parent;
@@ -958,6 +1049,14 @@ function object(element) {
958
1049
  return new ArcObject(element);
959
1050
  }
960
1051
 
1052
+ // src/context-element/context-element.ts
1053
+ class ArcContextElement {
1054
+ name;
1055
+ constructor(name) {
1056
+ this.name = name;
1057
+ }
1058
+ }
1059
+
961
1060
  // src/context-element/command/command.ts
962
1061
  class ArcCommand extends ArcContextElement {
963
1062
  data;
@@ -1502,17 +1601,13 @@ class ArcEvent extends ArcContextElement {
1502
1601
  });
1503
1602
  const tagsSchema = new ArcObject({
1504
1603
  _id: new ArcString().primaryKey(),
1505
- eventId: new ArcString().foreignKey("events", "_id", {
1506
- onDelete: "CASCADE"
1507
- }),
1604
+ eventId: new ArcString().index(),
1508
1605
  tagKey: new ArcString().index(),
1509
1606
  tagValue: new ArcString().index()
1510
1607
  });
1511
1608
  const syncStatusSchema = new ArcObject({
1512
1609
  _id: new ArcString().primaryKey(),
1513
- eventId: new ArcString().foreignKey("events", "_id", {
1514
- onDelete: "CASCADE"
1515
- }),
1610
+ eventId: new ArcString().index(),
1516
1611
  synced: new ArcBoolean().index(),
1517
1612
  timestamp: new ArcNumber,
1518
1613
  retryCount: new ArcNumber
@@ -1537,6 +1632,305 @@ function event(name, payload) {
1537
1632
  protections: []
1538
1633
  });
1539
1634
  }
1635
+ // src/context-element/listener/listener.ts
1636
+ class ArcListener extends ArcContextElement {
1637
+ data;
1638
+ unsubscribers = [];
1639
+ constructor(data) {
1640
+ super(data.name);
1641
+ this.data = data;
1642
+ }
1643
+ description(description) {
1644
+ return new ArcListener({
1645
+ ...this.data,
1646
+ description
1647
+ });
1648
+ }
1649
+ listenTo(events) {
1650
+ return new ArcListener({
1651
+ ...this.data,
1652
+ eventElements: events
1653
+ });
1654
+ }
1655
+ query(elements) {
1656
+ return new ArcListener({
1657
+ ...this.data,
1658
+ queryElements: elements
1659
+ });
1660
+ }
1661
+ mutate(elements) {
1662
+ return new ArcListener({
1663
+ ...this.data,
1664
+ mutationElements: elements
1665
+ });
1666
+ }
1667
+ async() {
1668
+ return new ArcListener({
1669
+ ...this.data,
1670
+ isAsync: true
1671
+ });
1672
+ }
1673
+ handle(handler) {
1674
+ return new ArcListener({
1675
+ ...this.data,
1676
+ handler
1677
+ });
1678
+ }
1679
+ get eventElements() {
1680
+ return this.data.eventElements || [];
1681
+ }
1682
+ get isAsync() {
1683
+ return this.data.isAsync;
1684
+ }
1685
+ async init(environment, adapters) {
1686
+ if (environment !== "server") {
1687
+ return;
1688
+ }
1689
+ if (!this.data.handler) {
1690
+ console.warn(`Listener "${this.data.name}" has no handler`);
1691
+ return;
1692
+ }
1693
+ if (!adapters.eventPublisher) {
1694
+ console.warn(`Listener "${this.data.name}" cannot subscribe: no eventPublisher adapter`);
1695
+ return;
1696
+ }
1697
+ for (const eventElement of this.data.eventElements) {
1698
+ const unsubscribe = adapters.eventPublisher.subscribe(eventElement.name, async (event2) => {
1699
+ await this.handleEvent(event2, adapters);
1700
+ });
1701
+ this.unsubscribers.push(unsubscribe);
1702
+ }
1703
+ }
1704
+ async handleEvent(event2, adapters) {
1705
+ if (!this.data.handler)
1706
+ return;
1707
+ const context2 = this.buildListenerContext(adapters);
1708
+ if (this.data.isAsync) {
1709
+ Promise.resolve(this.data.handler(context2, event2)).catch((error) => {
1710
+ console.error(`Async listener "${this.data.name}" error:`, error);
1711
+ });
1712
+ } else {
1713
+ await this.data.handler(context2, event2);
1714
+ }
1715
+ }
1716
+ buildListenerContext(adapters) {
1717
+ const context2 = {};
1718
+ const elementMap = new Map;
1719
+ for (const element of this.data.queryElements) {
1720
+ if (element.queryContext) {
1721
+ const elementContext = element.queryContext(adapters);
1722
+ context2[element.name] = elementContext;
1723
+ elementMap.set(element, elementContext);
1724
+ }
1725
+ }
1726
+ for (const element of this.data.mutationElements) {
1727
+ if (element.mutateContext) {
1728
+ const elementContext = element.mutateContext(adapters);
1729
+ context2[element.name] = elementContext;
1730
+ elementMap.set(element, elementContext);
1731
+ }
1732
+ }
1733
+ context2.get = (element) => {
1734
+ const cached = elementMap.get(element);
1735
+ if (cached)
1736
+ return cached;
1737
+ if (element.queryContext) {
1738
+ const ctx = element.queryContext(adapters);
1739
+ elementMap.set(element, ctx);
1740
+ return ctx;
1741
+ }
1742
+ if (element.mutateContext) {
1743
+ const ctx = element.mutateContext(adapters);
1744
+ elementMap.set(element, ctx);
1745
+ return ctx;
1746
+ }
1747
+ throw new Error(`Element "${element.name}" has no context available`);
1748
+ };
1749
+ if (adapters.authAdapter) {
1750
+ const decoded = adapters.authAdapter.getDecoded();
1751
+ if (decoded) {
1752
+ context2.$auth = {
1753
+ params: decoded.params,
1754
+ tokenName: decoded.tokenName
1755
+ };
1756
+ }
1757
+ }
1758
+ return context2;
1759
+ }
1760
+ destroy() {
1761
+ for (const unsubscribe of this.unsubscribers) {
1762
+ unsubscribe();
1763
+ }
1764
+ this.unsubscribers = [];
1765
+ }
1766
+ }
1767
+ function listener(name) {
1768
+ return new ArcListener({
1769
+ name,
1770
+ eventElements: [],
1771
+ queryElements: [],
1772
+ mutationElements: [],
1773
+ isAsync: false
1774
+ });
1775
+ }
1776
+ // src/context-element/route/route.ts
1777
+ class ArcRoute extends ArcContextElement {
1778
+ data;
1779
+ constructor(data) {
1780
+ super(data.name);
1781
+ this.data = data;
1782
+ }
1783
+ description(description) {
1784
+ return new ArcRoute({
1785
+ ...this.data,
1786
+ description
1787
+ });
1788
+ }
1789
+ path(path) {
1790
+ return new ArcRoute({
1791
+ ...this.data,
1792
+ path
1793
+ });
1794
+ }
1795
+ public() {
1796
+ return new ArcRoute({
1797
+ ...this.data,
1798
+ isPublic: true
1799
+ });
1800
+ }
1801
+ query(elements) {
1802
+ return new ArcRoute({
1803
+ ...this.data,
1804
+ queryElements: elements
1805
+ });
1806
+ }
1807
+ mutate(elements) {
1808
+ return new ArcRoute({
1809
+ ...this.data,
1810
+ mutationElements: elements
1811
+ });
1812
+ }
1813
+ protectBy(token, check) {
1814
+ const existingProtections = this.data.protections || [];
1815
+ return new ArcRoute({
1816
+ ...this.data,
1817
+ protections: [...existingProtections, { token, check }]
1818
+ });
1819
+ }
1820
+ handle(handlers) {
1821
+ return new ArcRoute({
1822
+ ...this.data,
1823
+ handlers
1824
+ });
1825
+ }
1826
+ get routePath() {
1827
+ return this.data.path || `/${this.data.name}`;
1828
+ }
1829
+ get fullPath() {
1830
+ return `/route${this.routePath}`;
1831
+ }
1832
+ get isPublic() {
1833
+ return this.data.isPublic;
1834
+ }
1835
+ get hasProtections() {
1836
+ return (this.data.protections?.length ?? 0) > 0;
1837
+ }
1838
+ get protections() {
1839
+ return this.data.protections || [];
1840
+ }
1841
+ getHandler(method) {
1842
+ return this.data.handlers?.[method];
1843
+ }
1844
+ matchesPath(pathname) {
1845
+ const routePath = this.fullPath;
1846
+ const routeParts = routePath.split("/").filter(Boolean);
1847
+ const pathParts = pathname.split("/").filter(Boolean);
1848
+ if (routeParts.length !== pathParts.length) {
1849
+ return { matches: false, params: {} };
1850
+ }
1851
+ const params = {};
1852
+ for (let i = 0;i < routeParts.length; i++) {
1853
+ const routePart = routeParts[i];
1854
+ const pathPart = pathParts[i];
1855
+ if (routePart.startsWith(":")) {
1856
+ const paramName = routePart.slice(1);
1857
+ params[paramName] = pathPart;
1858
+ } else if (routePart !== pathPart) {
1859
+ return { matches: false, params: {} };
1860
+ }
1861
+ }
1862
+ return { matches: true, params };
1863
+ }
1864
+ async verifyProtections(tokens) {
1865
+ if (this.data.isPublic) {
1866
+ return true;
1867
+ }
1868
+ if (!this.data.protections || this.data.protections.length === 0) {
1869
+ return true;
1870
+ }
1871
+ for (const protection of this.data.protections) {
1872
+ const tokenInstance = tokens.find((t) => t.getTokenDefinition() === protection.token);
1873
+ if (!tokenInstance) {
1874
+ return false;
1875
+ }
1876
+ const allowed = await protection.check(tokenInstance);
1877
+ if (!allowed) {
1878
+ return false;
1879
+ }
1880
+ }
1881
+ return true;
1882
+ }
1883
+ buildContext(adapters, authParams) {
1884
+ const context2 = {};
1885
+ const elementMap = new Map;
1886
+ for (const element of this.data.queryElements) {
1887
+ if (element.queryContext) {
1888
+ const elementContext = element.queryContext(adapters);
1889
+ context2[element.name] = elementContext;
1890
+ elementMap.set(element, elementContext);
1891
+ }
1892
+ }
1893
+ for (const element of this.data.mutationElements) {
1894
+ if (element.mutateContext) {
1895
+ const elementContext = element.mutateContext(adapters);
1896
+ context2[element.name] = elementContext;
1897
+ elementMap.set(element, elementContext);
1898
+ }
1899
+ }
1900
+ context2.get = (element) => {
1901
+ const cached = elementMap.get(element);
1902
+ if (cached)
1903
+ return cached;
1904
+ if (element.queryContext) {
1905
+ const ctx = element.queryContext(adapters);
1906
+ elementMap.set(element, ctx);
1907
+ return ctx;
1908
+ }
1909
+ if (element.mutateContext) {
1910
+ const ctx = element.mutateContext(adapters);
1911
+ elementMap.set(element, ctx);
1912
+ return ctx;
1913
+ }
1914
+ throw new Error(`Element "${element.name}" has no context available`);
1915
+ };
1916
+ if (authParams) {
1917
+ context2.$auth = authParams;
1918
+ }
1919
+ return context2;
1920
+ }
1921
+ }
1922
+ function route(name) {
1923
+ return new ArcRoute({
1924
+ name,
1925
+ path: undefined,
1926
+ description: undefined,
1927
+ queryElements: [],
1928
+ mutationElements: [],
1929
+ handlers: {},
1930
+ protections: [],
1931
+ isPublic: false
1932
+ });
1933
+ }
1540
1934
  // src/context-element/view/view.ts
1541
1935
  class ArcView extends ArcContextElement {
1542
1936
  data;
@@ -1596,20 +1990,22 @@ class ArcView extends ArcContextElement {
1596
1990
  const getReadRestrictions = () => {
1597
1991
  if (protections.length === 0)
1598
1992
  return null;
1599
- if (!adapters.authAdapter)
1600
- return null;
1993
+ if (!adapters.authAdapter) {
1994
+ return false;
1995
+ }
1601
1996
  const decoded = adapters.authAdapter.getDecoded();
1602
- if (!decoded)
1603
- return null;
1997
+ if (!decoded) {
1998
+ return false;
1999
+ }
1604
2000
  for (const protection of protections) {
1605
2001
  if (protection.token.name === decoded.tokenName) {
1606
- const rules = protection.protectionFn(decoded.params);
1607
- if (rules.read === false)
2002
+ const restrictions = protection.protectionFn(decoded.params);
2003
+ if (restrictions === false)
1608
2004
  return false;
1609
- return rules.read ?? null;
2005
+ return restrictions ?? {};
1610
2006
  }
1611
2007
  }
1612
- return null;
2008
+ return false;
1613
2009
  };
1614
2010
  const applyRestrictions = (options) => {
1615
2011
  const restrictions = getReadRestrictions();
@@ -1623,30 +2019,39 @@ class ArcView extends ArcContextElement {
1623
2019
  };
1624
2020
  return {
1625
2021
  find: async (options) => {
1626
- if (!adapters.dataStorage) {
1627
- console.warn(`View "${viewName}" query: no dataStorage available`);
1628
- return [];
1629
- }
1630
2022
  const restrictedOptions = applyRestrictions(options);
1631
2023
  if (restrictedOptions === false) {
1632
2024
  return [];
1633
2025
  }
1634
- const store = adapters.dataStorage.getStore(viewName);
1635
- return store.find(restrictedOptions);
2026
+ if (adapters.dataStorage) {
2027
+ const store = adapters.dataStorage.getStore(viewName);
2028
+ return store.find(restrictedOptions);
2029
+ }
2030
+ if (adapters.queryWire) {
2031
+ return adapters.queryWire.query(viewName, restrictedOptions);
2032
+ }
2033
+ console.warn(`View "${viewName}" query: no dataStorage or queryWire available`);
2034
+ return [];
1636
2035
  },
1637
2036
  findOne: async (where) => {
1638
- if (!adapters.dataStorage) {
1639
- console.warn(`View "${viewName}" query: no dataStorage available`);
1640
- return;
1641
- }
1642
2037
  const restrictions = getReadRestrictions();
1643
2038
  if (restrictions === false) {
1644
2039
  return;
1645
2040
  }
1646
- const store = adapters.dataStorage.getStore(viewName);
1647
2041
  const mergedWhere = restrictions && Object.keys(restrictions).length > 0 ? { ...where, ...restrictions } : where;
1648
- const results = await store.find({ where: mergedWhere });
1649
- return results[0];
2042
+ if (adapters.dataStorage) {
2043
+ const store = adapters.dataStorage.getStore(viewName);
2044
+ const results = await store.find({ where: mergedWhere });
2045
+ return results[0];
2046
+ }
2047
+ if (adapters.queryWire) {
2048
+ const results = await adapters.queryWire.query(viewName, {
2049
+ where: mergedWhere
2050
+ });
2051
+ return results[0];
2052
+ }
2053
+ console.warn(`View "${viewName}" query: no dataStorage or queryWire available`);
2054
+ return;
1650
2055
  }
1651
2056
  };
1652
2057
  }
@@ -1851,12 +2256,12 @@ class StoreState {
1851
2256
  applySerializedChanges(changes) {
1852
2257
  return Promise.all(changes.map((change) => this.applyChange(change)));
1853
2258
  }
1854
- unsubscribe(listener) {
1855
- this.listeners.delete(listener);
2259
+ unsubscribe(listener4) {
2260
+ this.listeners.delete(listener4);
1856
2261
  }
1857
2262
  notifyListeners(events) {
1858
- for (const listener of this.listeners.values()) {
1859
- listener.callback(events);
2263
+ for (const listener4 of this.listeners.values()) {
2264
+ listener4.callback(events);
1860
2265
  }
1861
2266
  }
1862
2267
  }
@@ -1869,7 +2274,7 @@ function deepMerge(target, source) {
1869
2274
  output[key] = undefined;
1870
2275
  continue;
1871
2276
  }
1872
- if (isObject(source[key]) && isObject(target[key])) {
2277
+ if (isPlainObject(source[key]) && isPlainObject(target[key])) {
1873
2278
  output[key] = deepMerge(target[key], source[key]);
1874
2279
  } else {
1875
2280
  output[key] = source[key];
@@ -1877,8 +2282,8 @@ function deepMerge(target, source) {
1877
2282
  }
1878
2283
  return output;
1879
2284
  }
1880
- function isObject(item) {
1881
- return item && typeof item === "object" && !Array.isArray(item);
2285
+ function isPlainObject(item) {
2286
+ return item && typeof item === "object" && !Array.isArray(item) && !(item instanceof Date) && Object.prototype.toString.call(item) === "[object Object]";
1882
2287
  }
1883
2288
  // src/utils/murmur-hash.ts
1884
2289
  function murmurHash(key, seed = 0) {
@@ -2011,9 +2416,9 @@ class ForkedStoreState extends StoreState {
2011
2416
  this.notifyListeners(events);
2012
2417
  }
2013
2418
  }
2014
- async find(options, listener) {
2015
- if (listener) {
2016
- this.listeners.set(listener, { callback: listener });
2419
+ async find(options, listener4) {
2420
+ if (listener4) {
2421
+ this.listeners.set(listener4, { callback: listener4 });
2017
2422
  }
2018
2423
  const parentResults = await this.master.find(options);
2019
2424
  const results = new Map;
@@ -2172,9 +2577,9 @@ class MasterStoreState extends StoreState {
2172
2577
  this.notifyListeners(events);
2173
2578
  }
2174
2579
  }
2175
- async find(options, listener) {
2176
- if (listener) {
2177
- this.listeners.set(listener, { callback: listener });
2580
+ async find(options, listener4) {
2581
+ if (listener4) {
2582
+ this.listeners.set(listener4, { callback: listener4 });
2178
2583
  }
2179
2584
  const transaction = await this.dataStorage.getReadTransaction();
2180
2585
  const results = await transaction.find(this.storeName, options);
@@ -2364,13 +2769,13 @@ class ObservableDataStorage {
2364
2769
  commitChanges(changes) {
2365
2770
  return this.source.commitChanges(changes);
2366
2771
  }
2367
- trackQuery(storeName, options, result, listener) {
2772
+ trackQuery(storeName, options, result, listener4) {
2368
2773
  const key = this.getQueryKey(storeName, options);
2369
2774
  this.trackedQueries.set(key, {
2370
2775
  storeName,
2371
2776
  options,
2372
2777
  result,
2373
- listener
2778
+ listener: listener4
2374
2779
  });
2375
2780
  }
2376
2781
  handleStoreChange(storeName, events) {
@@ -2427,15 +2832,15 @@ class ObservableStoreState {
2427
2832
  if (cached !== undefined) {
2428
2833
  return cached;
2429
2834
  }
2430
- const listener = (events) => {
2835
+ const listener4 = (events) => {
2431
2836
  this.observable.handleStoreChange(this.storeName, events);
2432
2837
  };
2433
- const result = await this.source.find(options, listener);
2434
- this.observable.trackQuery(this.storeName, options, result, listener);
2838
+ const result = await this.source.find(options, listener4);
2839
+ this.observable.trackQuery(this.storeName, options, result, listener4);
2435
2840
  return result;
2436
2841
  }
2437
- unsubscribe(listener) {
2438
- this.source.unsubscribe(listener);
2842
+ unsubscribe(listener4) {
2843
+ this.source.unsubscribe(listener4);
2439
2844
  }
2440
2845
  async set(item) {
2441
2846
  return this.source.set(item);
@@ -3127,33 +3532,276 @@ function mutationExecutor(model) {
3127
3532
  }
3128
3533
  });
3129
3534
  }
3130
- // src/model/live-query/live-query.ts
3131
- function createQueryContext(model, observableStorage) {
3132
- const adapters = model.getAdapters();
3133
- const observableAdapters = {
3134
- ...adapters,
3135
- dataStorage: observableStorage
3136
- };
3137
- return new Proxy({}, {
3535
+ // src/streaming/streaming-query-cache.ts
3536
+ class StreamingQueryCache {
3537
+ stores = new Map;
3538
+ views = [];
3539
+ registerViews(views) {
3540
+ this.views = views;
3541
+ for (const view3 of views) {
3542
+ if (!this.stores.has(view3.name)) {
3543
+ this.stores.set(view3.name, new StreamingStore);
3544
+ }
3545
+ }
3546
+ }
3547
+ getStore(viewName) {
3548
+ if (!this.stores.has(viewName)) {
3549
+ this.stores.set(viewName, new StreamingStore);
3550
+ }
3551
+ return this.stores.get(viewName);
3552
+ }
3553
+ setViewData(viewName, items) {
3554
+ const store = this.stores.get(viewName);
3555
+ if (store) {
3556
+ store.setAll(items);
3557
+ }
3558
+ }
3559
+ async applyEvent(event3) {
3560
+ for (const view3 of this.views) {
3561
+ const handlers = view3.getHandlers();
3562
+ const handler = handlers[event3.type];
3563
+ if (!handler)
3564
+ continue;
3565
+ const store = this.stores.get(view3.name);
3566
+ if (!store)
3567
+ continue;
3568
+ const ctx = {
3569
+ set: async (id3, data) => {
3570
+ store.set(String(id3), { _id: String(id3), ...data });
3571
+ },
3572
+ modify: async (id3, data) => {
3573
+ store.modify(String(id3), data);
3574
+ },
3575
+ remove: async (id3) => {
3576
+ store.remove(String(id3));
3577
+ },
3578
+ find: async (options) => {
3579
+ return store.find(options);
3580
+ },
3581
+ findOne: async (where) => {
3582
+ return store.findOne(where);
3583
+ },
3584
+ $auth: {}
3585
+ };
3586
+ await handler(ctx, event3);
3587
+ }
3588
+ }
3589
+ clear() {
3590
+ for (const store of this.stores.values()) {
3591
+ store.clear();
3592
+ }
3593
+ }
3594
+ }
3595
+
3596
+ class StreamingStore {
3597
+ data = new Map;
3598
+ listeners = new Set;
3599
+ setAll(items) {
3600
+ this.data.clear();
3601
+ for (const item of items) {
3602
+ this.data.set(item._id, item);
3603
+ }
3604
+ this.notifyListeners();
3605
+ }
3606
+ set(id3, item) {
3607
+ this.data.set(id3, item);
3608
+ this.notifyListeners();
3609
+ }
3610
+ modify(id3, updates) {
3611
+ const existing = this.data.get(id3);
3612
+ if (existing) {
3613
+ this.data.set(id3, { ...existing, ...updates });
3614
+ this.notifyListeners();
3615
+ }
3616
+ }
3617
+ remove(id3) {
3618
+ if (this.data.delete(id3)) {
3619
+ this.notifyListeners();
3620
+ }
3621
+ }
3622
+ clear() {
3623
+ this.data.clear();
3624
+ this.notifyListeners();
3625
+ }
3626
+ find(options = {}) {
3627
+ let results = Array.from(this.data.values());
3628
+ if (options.where) {
3629
+ results = results.filter((item) => checkItemMatchesWhere(item, options.where));
3630
+ }
3631
+ return applyOrderByAndLimit(results, options);
3632
+ }
3633
+ findOne(where) {
3634
+ const results = this.find({ where });
3635
+ return results[0];
3636
+ }
3637
+ subscribe(listener4) {
3638
+ this.listeners.add(listener4);
3639
+ return () => {
3640
+ this.listeners.delete(listener4);
3641
+ };
3642
+ }
3643
+ notifyListeners() {
3644
+ for (const listener4 of this.listeners) {
3645
+ listener4();
3646
+ }
3647
+ }
3648
+ }
3649
+ // src/streaming/streaming-event-publisher.ts
3650
+ class StreamingEventPublisher {
3651
+ cache;
3652
+ eventWire;
3653
+ views = [];
3654
+ subscribers = new Map;
3655
+ constructor(cache, eventWire) {
3656
+ this.cache = cache;
3657
+ this.eventWire = eventWire;
3658
+ }
3659
+ registerViews(views) {
3660
+ this.views = views;
3661
+ this.cache.registerViews(views);
3662
+ }
3663
+ async publish(event3) {
3664
+ await this.cache.applyEvent(event3);
3665
+ await this.notifySubscribers(event3);
3666
+ this.eventWire.syncEvents([
3667
+ {
3668
+ localId: event3.id,
3669
+ type: event3.type,
3670
+ payload: event3.payload,
3671
+ createdAt: event3.createdAt.toISOString()
3672
+ }
3673
+ ]);
3674
+ }
3675
+ subscribe(eventType, callback) {
3676
+ if (!this.subscribers.has(eventType)) {
3677
+ this.subscribers.set(eventType, new Set);
3678
+ }
3679
+ this.subscribers.get(eventType).add(callback);
3680
+ return () => {
3681
+ const subs = this.subscribers.get(eventType);
3682
+ if (subs) {
3683
+ subs.delete(callback);
3684
+ if (subs.size === 0) {
3685
+ this.subscribers.delete(eventType);
3686
+ }
3687
+ }
3688
+ };
3689
+ }
3690
+ async markSynced(_eventIds) {}
3691
+ async getUnsyncedEvents(_eventType) {
3692
+ return [];
3693
+ }
3694
+ async notifySubscribers(event3) {
3695
+ const subs = this.subscribers.get(event3.type);
3696
+ if (!subs || subs.size === 0)
3697
+ return;
3698
+ const promises = [];
3699
+ for (const callback of subs) {
3700
+ try {
3701
+ const result = callback(event3);
3702
+ if (result instanceof Promise) {
3703
+ promises.push(result);
3704
+ }
3705
+ } catch (error) {
3706
+ console.error(`Listener error for event "${event3.type}":`, error);
3707
+ }
3708
+ }
3709
+ if (promises.length > 0) {
3710
+ await Promise.all(promises.map((p) => p.catch((e) => console.error(e))));
3711
+ }
3712
+ }
3713
+ }
3714
+ // src/streaming/streaming-live-query.ts
3715
+ function streamingLiveQuery(model, queryFn, callback, options) {
3716
+ const { queryWire, cache, authToken } = options;
3717
+ let currentResult = undefined;
3718
+ const unsubscribers = [];
3719
+ const streamConnections = [];
3720
+ const queriedViews = new Set;
3721
+ const queryContext = new Proxy({}, {
3138
3722
  get(_target, viewName) {
3139
3723
  const element2 = model.context.get(viewName);
3140
3724
  if (!element2) {
3141
3725
  throw new Error(`View '${viewName}' not found in context`);
3142
3726
  }
3143
- return element2.queryContext(observableAdapters);
3727
+ queriedViews.add(viewName);
3728
+ return {
3729
+ find: async (findOptions = {}) => {
3730
+ const store = cache.getStore(viewName);
3731
+ return store.find(findOptions);
3732
+ },
3733
+ findOne: async (where) => {
3734
+ const store = cache.getStore(viewName);
3735
+ return store.findOne(where);
3736
+ }
3737
+ };
3144
3738
  }
3145
3739
  });
3740
+ const executeQuery = async () => {
3741
+ const result = await queryFn(queryContext);
3742
+ currentResult = result;
3743
+ callback(result);
3744
+ };
3745
+ const setupStreams = async () => {
3746
+ queriedViews.clear();
3747
+ await queryFn(queryContext);
3748
+ for (const viewName of queriedViews) {
3749
+ const store = cache.getStore(viewName);
3750
+ const unsub = store.subscribe(() => {
3751
+ executeQuery();
3752
+ });
3753
+ unsubscribers.push(unsub);
3754
+ const streamConn = queryWire.stream(viewName, {}, (data) => {
3755
+ cache.setViewData(viewName, data);
3756
+ }, authToken);
3757
+ streamConnections.push(streamConn);
3758
+ }
3759
+ };
3760
+ setupStreams();
3761
+ return {
3762
+ get result() {
3763
+ return currentResult;
3764
+ },
3765
+ unsubscribe: () => {
3766
+ for (const unsub of unsubscribers) {
3767
+ unsub();
3768
+ }
3769
+ for (const conn of streamConnections) {
3770
+ conn.unsubscribe();
3771
+ }
3772
+ }
3773
+ };
3146
3774
  }
3775
+ // src/model/live-query/live-query.ts
3147
3776
  function liveQuery(model, queryFn, callback) {
3148
3777
  let currentResult = undefined;
3149
3778
  const adapters = model.getAdapters();
3150
- if (!adapters.dataStorage) {
3151
- throw new Error("liveQuery requires dataStorage adapter");
3779
+ if (!adapters.dataStorage && adapters.streamingCache && adapters.queryWire) {
3780
+ return streamingLiveQuery(model, queryFn, callback, {
3781
+ queryWire: adapters.queryWire,
3782
+ cache: adapters.streamingCache,
3783
+ authToken: adapters.authAdapter?.getToken?.() ?? null
3784
+ });
3785
+ }
3786
+ if (!adapters.dataStorage && !adapters.queryWire) {
3787
+ throw new Error("liveQuery requires dataStorage or queryWire adapter");
3152
3788
  }
3153
- const observableStorage = observeQueries(adapters.dataStorage, () => {
3789
+ const observableStorage = adapters.dataStorage ? observeQueries(adapters.dataStorage, () => {
3154
3790
  executeQuery();
3791
+ }) : null;
3792
+ const observableAdapters = {
3793
+ ...adapters,
3794
+ dataStorage: observableStorage ?? undefined
3795
+ };
3796
+ const queryContext = new Proxy({}, {
3797
+ get(_target, viewName) {
3798
+ const element2 = model.context.get(viewName);
3799
+ if (!element2) {
3800
+ throw new Error(`View '${viewName}' not found in context`);
3801
+ }
3802
+ return element2.queryContext(observableAdapters);
3803
+ }
3155
3804
  });
3156
- const queryContext = createQueryContext(model, observableStorage);
3157
3805
  const executeQuery = async () => {
3158
3806
  const result = await queryFn(queryContext);
3159
3807
  currentResult = result;
@@ -3165,7 +3813,7 @@ function liveQuery(model, queryFn, callback) {
3165
3813
  return currentResult;
3166
3814
  },
3167
3815
  unsubscribe: () => {
3168
- observableStorage.clear();
3816
+ observableStorage?.clear();
3169
3817
  }
3170
3818
  };
3171
3819
  }
@@ -3552,7 +4200,9 @@ export {
3552
4200
  token,
3553
4201
  stringEnum,
3554
4202
  string,
4203
+ streamingLiveQuery,
3555
4204
  secureDataStorage,
4205
+ route,
3556
4206
  resolveQueryChange,
3557
4207
  record,
3558
4208
  or,
@@ -3562,6 +4212,7 @@ export {
3562
4212
  mutationExecutor,
3563
4213
  murmurHash,
3564
4214
  liveQuery,
4215
+ listener,
3565
4216
  id,
3566
4217
  file,
3567
4218
  extractDatabaseAgnosticSchema,
@@ -3581,9 +4232,12 @@ export {
3581
4232
  Wire,
3582
4233
  TokenInstance,
3583
4234
  TokenCache,
4235
+ StreamingQueryCache,
4236
+ StreamingEventPublisher,
3584
4237
  StoreState,
3585
4238
  SecuredStoreState,
3586
4239
  SecuredDataStorage,
4240
+ QueryWire,
3587
4241
  ObservableDataStorage,
3588
4242
  Model,
3589
4243
  MasterStoreState,
@@ -3600,11 +4254,13 @@ export {
3600
4254
  ArcToken,
3601
4255
  ArcStringEnum,
3602
4256
  ArcString,
4257
+ ArcRoute,
3603
4258
  ArcRecord,
3604
4259
  ArcOr,
3605
4260
  ArcOptional,
3606
4261
  ArcObject,
3607
4262
  ArcNumber,
4263
+ ArcListener,
3608
4264
  ArcId,
3609
4265
  ArcFile,
3610
4266
  ArcEvent,