@objectstack/client 4.0.1 → 4.0.3

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() {
@@ -310,10 +439,15 @@ var ObjectStackClient = class {
310
439
  * Get a specific metadata item by type and name
311
440
  * @param type - Metadata type (e.g., 'object', 'plugin')
312
441
  * @param name - Item name (snake_case identifier)
442
+ * @param options - Optional filters (e.g., packageId to scope by package)
313
443
  */
314
- getItem: async (type, name) => {
444
+ getItem: async (type, name, options) => {
315
445
  const route = this.getRoute("metadata");
316
- const res = await this.fetch(`${this.baseUrl}${route}/${type}/${name}`);
446
+ const params = new URLSearchParams();
447
+ if (options?.packageId) params.set("package", options.packageId);
448
+ const qs = params.toString();
449
+ const url = `${this.baseUrl}${route}/${type}/${name}${qs ? `?${qs}` : ""}`;
450
+ const res = await this.fetch(url);
317
451
  return this.unwrapResponse(res);
318
452
  },
319
453
  /**
@@ -330,6 +464,18 @@ var ObjectStackClient = class {
330
464
  });
331
465
  return this.unwrapResponse(res);
332
466
  },
467
+ /**
468
+ * Delete a metadata item
469
+ * @param type - Metadata type (e.g., 'object', 'plugin')
470
+ * @param name - Item name (snake_case identifier)
471
+ */
472
+ deleteItem: async (type, name) => {
473
+ const route = this.getRoute("metadata");
474
+ const res = await this.fetch(`${this.baseUrl}${route}/${encodeURIComponent(type)}/${encodeURIComponent(name)}`, {
475
+ method: "DELETE"
476
+ });
477
+ return this.unwrapResponse(res);
478
+ },
333
479
  /**
334
480
  * Get object metadata with cache support
335
481
  * Supports ETag-based conditional requests for efficient caching
@@ -1073,17 +1219,7 @@ var ObjectStackClient = class {
1073
1219
  });
1074
1220
  return this.unwrapResponse(res);
1075
1221
  },
1076
- /**
1077
- * Multi-turn AI chat
1078
- */
1079
- chat: async (request) => {
1080
- const route = this.getRoute("ai");
1081
- const res = await this.fetch(`${this.baseUrl}${route}/chat`, {
1082
- method: "POST",
1083
- body: JSON.stringify(request)
1084
- });
1085
- return this.unwrapResponse(res);
1086
- },
1222
+ // AI chat method removed — use Vercel AI SDK `useChat()` / `@ai-sdk/react` directly.
1087
1223
  /**
1088
1224
  * AI-powered field value suggestions
1089
1225
  */
@@ -1465,6 +1601,7 @@ var ObjectStackClient = class {
1465
1601
  level: config.debug ? "debug" : "info",
1466
1602
  format: "pretty"
1467
1603
  });
1604
+ this.realtimeAPI = new RealtimeAPI(this.baseUrl, this.token);
1468
1605
  this.logger.debug("ObjectStack client created", { baseUrl: this.baseUrl });
1469
1606
  }
1470
1607
  /**
@@ -1535,6 +1672,13 @@ var ObjectStackClient = class {
1535
1672
  }
1536
1673
  return result;
1537
1674
  }
1675
+ /**
1676
+ * Event Subscription API
1677
+ * Provides real-time event subscriptions for metadata and data changes
1678
+ */
1679
+ get events() {
1680
+ return this.realtimeAPI;
1681
+ }
1538
1682
  /**
1539
1683
  * Private Helpers
1540
1684
  */
@@ -1638,6 +1782,7 @@ export {
1638
1782
  FilterBuilder,
1639
1783
  ObjectStackClient,
1640
1784
  QueryBuilder,
1785
+ RealtimeAPI,
1641
1786
  createFilter,
1642
1787
  createQuery
1643
1788
  };