@1upvision/sdk 0.1.2 → 0.1.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.
@@ -6,9 +6,11 @@ export interface UseQueryResult<T = unknown> {
6
6
  }
7
7
  /**
8
8
  * Call a server query function by name and reactively return the result.
9
+ * Automatically opens a WebSocket subscription for live updates when the
10
+ * host supports it.
9
11
  *
10
12
  * ```tsx
11
- * const { data: players, isLoading, refetch } = useQuery("getPlayers");
13
+ * const { data: players, isLoading } = useQuery("getPlayers");
12
14
  * const { data: player } = useQuery("getPlayer", { userId: "alice" });
13
15
  * ```
14
16
  */
@@ -1 +1 @@
1
- {"version":3,"file":"useQuery.d.ts","sourceRoot":"","sources":["../../src/hooks/useQuery.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IACzC,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAClC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,cAAc,CAAC,CAAC,CAAC,CAiEnB"}
1
+ {"version":3,"file":"useQuery.d.ts","sourceRoot":"","sources":["../../src/hooks/useQuery.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,cAAc,CAAC,CAAC,GAAG,OAAO;IACzC,IAAI,EAAE,CAAC,GAAG,SAAS,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB;AA0CD;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,CAAC,GAAG,OAAO,EAClC,IAAI,EAAE,MAAM,EACZ,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC7B,cAAc,CAAC,CAAC,CAAC,CAwFnB"}
@@ -1,9 +1,27 @@
1
1
  import { useState, useEffect, useCallback, useRef } from "react";
2
+ let subscriptionCounter = 0;
3
+ const subscriptionListeners = new Map();
4
+ let bridgeListenersInstalled = false;
5
+ function ensureBridgeListeners(bridge) {
6
+ if (bridgeListenersInstalled)
7
+ return;
8
+ bridgeListenersInstalled = true;
9
+ bridge.onNotification("server.subscription.update", (params) => {
10
+ const { subscriptionId, data } = params;
11
+ subscriptionListeners.get(subscriptionId)?.onUpdate(data);
12
+ });
13
+ bridge.onNotification("server.subscription.error", (params) => {
14
+ const { subscriptionId, error, message } = params;
15
+ subscriptionListeners.get(subscriptionId)?.onError(error, message);
16
+ });
17
+ }
2
18
  /**
3
19
  * Call a server query function by name and reactively return the result.
20
+ * Automatically opens a WebSocket subscription for live updates when the
21
+ * host supports it.
4
22
  *
5
23
  * ```tsx
6
- * const { data: players, isLoading, refetch } = useQuery("getPlayers");
24
+ * const { data: players, isLoading } = useQuery("getPlayers");
7
25
  * const { data: player } = useQuery("getPlayer", { userId: "alice" });
8
26
  * ```
9
27
  */
@@ -35,6 +53,7 @@ export function useQuery(name, args) {
35
53
  }
36
54
  });
37
55
  }, [bridge, name, argsJson]);
56
+ // Initial fetch
38
57
  useEffect(() => {
39
58
  mountedRef.current = true;
40
59
  fetchData();
@@ -42,21 +61,43 @@ export function useQuery(name, args) {
42
61
  mountedRef.current = false;
43
62
  };
44
63
  }, [fetchData]);
45
- // Listen for server-side invalidation pushed by the host after mutations
64
+ // Open a live WebSocket subscription for real-time updates
46
65
  useEffect(() => {
47
- const handler = (detail) => {
48
- const payload = detail;
49
- if (!payload?.queryNames || payload.queryNames.includes(name)) {
50
- fetchData();
51
- }
52
- };
53
- globalThis.__visionQueryInvalidate = handler;
66
+ if (!bridge)
67
+ return;
68
+ ensureBridgeListeners(bridge);
69
+ const subId = `sub_${++subscriptionCounter}`;
70
+ const parsedArgs = argsJson ? JSON.parse(argsJson) : undefined;
71
+ // Register listener for this subscription's updates
72
+ subscriptionListeners.set(subId, {
73
+ onUpdate: (newData) => {
74
+ if (mountedRef.current) {
75
+ setData(newData);
76
+ setIsLoading(false);
77
+ }
78
+ },
79
+ onError: (_error, message) => {
80
+ if (mountedRef.current) {
81
+ setError(new Error(message));
82
+ }
83
+ },
84
+ });
85
+ // Request the subscription (the host will push updates via notifications)
86
+ bridge
87
+ .request("server.subscribe", {
88
+ subscriptionId: subId,
89
+ name,
90
+ args: parsedArgs,
91
+ })
92
+ .catch(() => {
93
+ // Subscription not supported or failed — rely on initial fetch only
94
+ });
54
95
  return () => {
55
- if (globalThis.__visionQueryInvalidate ===
56
- handler) {
57
- delete globalThis.__visionQueryInvalidate;
58
- }
96
+ subscriptionListeners.delete(subId);
97
+ bridge
98
+ .request("server.unsubscribe", { subscriptionId: subId })
99
+ .catch(() => { });
59
100
  };
60
- }, [fetchData, name]);
101
+ }, [bridge, name, argsJson]);
61
102
  return { data, isLoading, error, refetch: fetchData };
62
103
  }
@@ -9233,6 +9233,21 @@
9233
9233
 
9234
9234
  // src/hooks/useQuery.ts
9235
9235
  var import_react14 = __toESM(require_react(), 1);
9236
+ var subscriptionCounter = 0;
9237
+ var subscriptionListeners = /* @__PURE__ */ new Map();
9238
+ var bridgeListenersInstalled = false;
9239
+ function ensureBridgeListeners(bridge2) {
9240
+ if (bridgeListenersInstalled) return;
9241
+ bridgeListenersInstalled = true;
9242
+ bridge2.onNotification("server.subscription.update", (params) => {
9243
+ const { subscriptionId, data } = params;
9244
+ subscriptionListeners.get(subscriptionId)?.onUpdate(data);
9245
+ });
9246
+ bridge2.onNotification("server.subscription.error", (params) => {
9247
+ const { subscriptionId, error, message } = params;
9248
+ subscriptionListeners.get(subscriptionId)?.onError(error, message);
9249
+ });
9250
+ }
9236
9251
  function useQuery(name, args) {
9237
9252
  const [data, setData] = (0, import_react14.useState)(void 0);
9238
9253
  const [isLoading, setIsLoading] = (0, import_react14.useState)(true);
@@ -9265,19 +9280,35 @@
9265
9280
  };
9266
9281
  }, [fetchData]);
9267
9282
  (0, import_react14.useEffect)(() => {
9268
- const handler = (detail) => {
9269
- const payload = detail;
9270
- if (!payload?.queryNames || payload.queryNames.includes(name)) {
9271
- fetchData();
9283
+ if (!bridge2) return;
9284
+ ensureBridgeListeners(bridge2);
9285
+ const subId = `sub_${++subscriptionCounter}`;
9286
+ const parsedArgs = argsJson ? JSON.parse(argsJson) : void 0;
9287
+ subscriptionListeners.set(subId, {
9288
+ onUpdate: (newData) => {
9289
+ if (mountedRef.current) {
9290
+ setData(newData);
9291
+ setIsLoading(false);
9292
+ }
9293
+ },
9294
+ onError: (_error, message) => {
9295
+ if (mountedRef.current) {
9296
+ setError(new Error(message));
9297
+ }
9272
9298
  }
9273
- };
9274
- globalThis.__visionQueryInvalidate = handler;
9299
+ });
9300
+ bridge2.request("server.subscribe", {
9301
+ subscriptionId: subId,
9302
+ name,
9303
+ args: parsedArgs
9304
+ }).catch(() => {
9305
+ });
9275
9306
  return () => {
9276
- if (globalThis.__visionQueryInvalidate === handler) {
9277
- delete globalThis.__visionQueryInvalidate;
9278
- }
9307
+ subscriptionListeners.delete(subId);
9308
+ bridge2.request("server.unsubscribe", { subscriptionId: subId }).catch(() => {
9309
+ });
9279
9310
  };
9280
- }, [fetchData, name]);
9311
+ }, [bridge2, name, argsJson]);
9281
9312
  return { data, isLoading, error, refetch: fetchData };
9282
9313
  }
9283
9314
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1upvision/sdk",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Vision SDK for building extensions on the 1up.vision platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -23,7 +23,7 @@
23
23
  ],
24
24
  "dependencies": {
25
25
  "react-reconciler": "^0.32.0",
26
- "@1upvision/protocol": "0.1.1"
26
+ "@1upvision/protocol": "0.1.2"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "react": "^18.0.0 || ^19.0.0"