@objectstack/client 4.0.2 → 4.0.4

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.mjs CHANGED
@@ -2,6 +2,135 @@
2
2
  import { isFilterAST } from "@objectstack/spec/data";
3
3
  import { createLogger } from "@objectstack/core";
4
4
 
5
+ // src/realtime-api.ts
6
+ var RealtimeAPI = class {
7
+ constructor(baseUrl, token) {
8
+ this.subscriptions = /* @__PURE__ */ new Map();
9
+ this.eventBuffer = [];
10
+ this._baseUrl = baseUrl;
11
+ this._token = token;
12
+ }
13
+ /**
14
+ * Subscribe to metadata events
15
+ * Returns an unsubscribe function
16
+ */
17
+ subscribeMetadata(type, callback, options) {
18
+ const subscriptionId = `metadata-${type}-${Date.now()}`;
19
+ this.subscriptions.set(subscriptionId, {
20
+ filter: {
21
+ type,
22
+ packageId: options?.packageId,
23
+ eventTypes: [
24
+ `metadata.${type}.created`,
25
+ `metadata.${type}.updated`,
26
+ `metadata.${type}.deleted`
27
+ ]
28
+ },
29
+ handler: (event) => {
30
+ if (event.type.startsWith("metadata.")) {
31
+ callback(event);
32
+ }
33
+ }
34
+ });
35
+ this.startPolling();
36
+ return () => {
37
+ this.subscriptions.delete(subscriptionId);
38
+ if (this.subscriptions.size === 0) {
39
+ this.stopPolling();
40
+ }
41
+ };
42
+ }
43
+ /**
44
+ * Subscribe to data record events
45
+ * Returns an unsubscribe function
46
+ */
47
+ subscribeData(object, callback, options) {
48
+ const subscriptionId = `data-${object}-${Date.now()}`;
49
+ this.subscriptions.set(subscriptionId, {
50
+ filter: {
51
+ type: object,
52
+ recordId: options?.recordId,
53
+ eventTypes: [
54
+ "data.record.created",
55
+ "data.record.updated",
56
+ "data.record.deleted"
57
+ ]
58
+ },
59
+ handler: (event) => {
60
+ if (event.type.startsWith("data.") && event.object === object) {
61
+ if (!options?.recordId || event.payload?.recordId === options.recordId) {
62
+ callback(event);
63
+ }
64
+ }
65
+ }
66
+ });
67
+ this.startPolling();
68
+ return () => {
69
+ this.subscriptions.delete(subscriptionId);
70
+ if (this.subscriptions.size === 0) {
71
+ this.stopPolling();
72
+ }
73
+ };
74
+ }
75
+ /**
76
+ * Emit an event to all matching subscriptions (client-side only)
77
+ * This is used for in-process event delivery
78
+ */
79
+ emitEvent(event) {
80
+ for (const sub of this.subscriptions.values()) {
81
+ const matchesType = !sub.filter.type || event.type.includes(sub.filter.type) || event.object === sub.filter.type;
82
+ const matchesEventType = !sub.filter.eventTypes?.length || sub.filter.eventTypes.includes(event.type);
83
+ const matchesPackage = !sub.filter.packageId || event.payload?.packageId === sub.filter.packageId;
84
+ if (matchesType && matchesEventType && matchesPackage) {
85
+ try {
86
+ sub.handler(event);
87
+ } catch (error) {
88
+ console.error("Error in realtime event handler:", error);
89
+ }
90
+ }
91
+ }
92
+ }
93
+ /**
94
+ * Start polling for events (fallback mechanism)
95
+ * In production, this would be replaced with WebSocket/SSE
96
+ */
97
+ startPolling() {
98
+ if (this.pollInterval) return;
99
+ this.pollInterval = setInterval(() => {
100
+ while (this.eventBuffer.length > 0) {
101
+ const event = this.eventBuffer.shift();
102
+ if (event) {
103
+ this.emitEvent(event);
104
+ }
105
+ }
106
+ }, 2e3);
107
+ }
108
+ /**
109
+ * Stop polling for events
110
+ */
111
+ stopPolling() {
112
+ if (this.pollInterval) {
113
+ clearInterval(this.pollInterval);
114
+ this.pollInterval = void 0;
115
+ }
116
+ }
117
+ /**
118
+ * Internal method to buffer events from server
119
+ * This would be called by WebSocket/SSE handlers in production
120
+ */
121
+ _bufferEvent(event) {
122
+ this.eventBuffer.push(event);
123
+ }
124
+ /**
125
+ * Disconnect and clean up all subscriptions
126
+ */
127
+ disconnect() {
128
+ this.stopPolling();
129
+ this.subscriptions.clear();
130
+ this.eventBuffer = [];
131
+ }
132
+ };
133
+
5
134
  // src/query-builder.ts
6
135
  var FilterBuilder = class {
7
136
  constructor() {
@@ -506,6 +635,15 @@ var ObjectStackClient = class {
506
635
  * Authentication Services
507
636
  */
508
637
  this.auth = {
638
+ /**
639
+ * Get authentication configuration
640
+ * Returns available auth providers and features
641
+ */
642
+ getConfig: async () => {
643
+ const route = this.getRoute("auth");
644
+ const res = await this.fetch(`${this.baseUrl}${route}/config`);
645
+ return this.unwrapResponse(res);
646
+ },
509
647
  /**
510
648
  * Login with email and password
511
649
  * Uses better-auth endpoint: POST /sign-in/email
@@ -1472,6 +1610,7 @@ var ObjectStackClient = class {
1472
1610
  level: config.debug ? "debug" : "info",
1473
1611
  format: "pretty"
1474
1612
  });
1613
+ this.realtimeAPI = new RealtimeAPI(this.baseUrl, this.token);
1475
1614
  this.logger.debug("ObjectStack client created", { baseUrl: this.baseUrl });
1476
1615
  }
1477
1616
  /**
@@ -1482,6 +1621,18 @@ var ObjectStackClient = class {
1482
1621
  try {
1483
1622
  let data;
1484
1623
  try {
1624
+ const discoveryUrl = `${this.baseUrl}/api/v1/discovery`;
1625
+ this.logger.debug("Probing protocol-standard discovery endpoint", { url: discoveryUrl });
1626
+ const res = await this.fetchImpl(discoveryUrl);
1627
+ if (res.ok) {
1628
+ const body = await res.json();
1629
+ data = body.data || body;
1630
+ this.logger.debug("Discovered via /api/v1/discovery");
1631
+ }
1632
+ } catch (e) {
1633
+ this.logger.debug("Protocol-standard discovery probe failed", { error: e.message });
1634
+ }
1635
+ if (!data) {
1485
1636
  let wellKnownUrl;
1486
1637
  try {
1487
1638
  const url = new URL(this.baseUrl);
@@ -1489,22 +1640,10 @@ var ObjectStackClient = class {
1489
1640
  } catch {
1490
1641
  wellKnownUrl = "/.well-known/objectstack";
1491
1642
  }
1492
- this.logger.debug("Probing .well-known discovery", { url: wellKnownUrl });
1643
+ this.logger.debug("Falling back to .well-known discovery", { url: wellKnownUrl });
1493
1644
  const res = await this.fetchImpl(wellKnownUrl);
1494
- if (res.ok) {
1495
- const body = await res.json();
1496
- data = body.data || body;
1497
- this.logger.debug("Discovered via .well-known");
1498
- }
1499
- } catch (e) {
1500
- this.logger.debug("Standard discovery probe failed", { error: e.message });
1501
- }
1502
- if (!data) {
1503
- const fallbackUrl = `${this.baseUrl}/api/v1/discovery`;
1504
- this.logger.debug("Falling back to standard discovery endpoint", { url: fallbackUrl });
1505
- const res = await this.fetchImpl(fallbackUrl);
1506
1645
  if (!res.ok) {
1507
- throw new Error(`Failed to connect to ${fallbackUrl}: ${res.statusText}`);
1646
+ throw new Error(`Failed to connect to ${wellKnownUrl}: ${res.statusText}`);
1508
1647
  }
1509
1648
  const body = await res.json();
1510
1649
  data = body.data || body;
@@ -1542,6 +1681,13 @@ var ObjectStackClient = class {
1542
1681
  }
1543
1682
  return result;
1544
1683
  }
1684
+ /**
1685
+ * Event Subscription API
1686
+ * Provides real-time event subscriptions for metadata and data changes
1687
+ */
1688
+ get events() {
1689
+ return this.realtimeAPI;
1690
+ }
1545
1691
  /**
1546
1692
  * Private Helpers
1547
1693
  */
@@ -1645,6 +1791,7 @@ export {
1645
1791
  FilterBuilder,
1646
1792
  ObjectStackClient,
1647
1793
  QueryBuilder,
1794
+ RealtimeAPI,
1648
1795
  createFilter,
1649
1796
  createQuery
1650
1797
  };