@1upvision/sdk 0.1.0 → 0.1.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.
@@ -8683,6 +8683,8 @@
8683
8683
  renderExtension: () => renderExtension,
8684
8684
  useExtensionContext: () => useExtensionContext,
8685
8685
  useExtensionStorage: () => useExtensionStorage,
8686
+ useMutation: () => useMutation,
8687
+ useQuery: () => useQuery,
8686
8688
  useVisionState: () => useVisionState
8687
8689
  });
8688
8690
 
@@ -8706,6 +8708,9 @@
8706
8708
  }
8707
8709
  return handler(...args);
8708
8710
  }
8711
+ update(handlerId, fn) {
8712
+ this.handlers.set(handlerId, fn);
8713
+ }
8709
8714
  remove(handlerId) {
8710
8715
  this.handlers.delete(handlerId);
8711
8716
  }
@@ -8721,29 +8726,67 @@
8721
8726
  function nextId() {
8722
8727
  return `node_${++nodeCounter}`;
8723
8728
  }
8724
- function sanitizeProps(props) {
8729
+ function cleanupHandlers(instance) {
8730
+ for (const [key, value] of Object.entries(instance.props)) {
8731
+ if (key.startsWith("__handler_")) {
8732
+ callbackRegistry.remove(value);
8733
+ }
8734
+ }
8735
+ for (const child of instance.children) {
8736
+ cleanupHandlers(child);
8737
+ }
8738
+ }
8739
+ function sanitizeProps(props, existingProps) {
8725
8740
  const sanitized = {};
8726
8741
  for (const [key, value] of Object.entries(props)) {
8727
8742
  if (key === "children") continue;
8728
8743
  if (typeof value === "function") {
8729
- const handlerId = callbackRegistry.register(value);
8730
- sanitized[`__handler_${key}`] = handlerId;
8744
+ const handlerKey = `__handler_${key}`;
8745
+ const existingHandlerId = existingProps?.[handlerKey];
8746
+ if (existingHandlerId) {
8747
+ callbackRegistry.update(existingHandlerId, value);
8748
+ sanitized[handlerKey] = existingHandlerId;
8749
+ } else {
8750
+ const handlerId = callbackRegistry.register(value);
8751
+ sanitized[handlerKey] = handlerId;
8752
+ }
8731
8753
  } else {
8732
8754
  sanitized[key] = value;
8733
8755
  }
8734
8756
  }
8735
8757
  return sanitized;
8736
8758
  }
8737
- function deepSanitize(value) {
8759
+ function isPlainSerializable(value) {
8760
+ if (value === null || value === void 0) return true;
8761
+ const t = typeof value;
8762
+ if (t === "string" || t === "number" || t === "boolean") return true;
8763
+ if (t === "function" || t === "symbol") return false;
8764
+ if (t !== "object") return false;
8765
+ if (Array.isArray(value)) return true;
8766
+ if ("$$typeof" in value) return false;
8767
+ if (typeof value.nodeType === "number")
8768
+ return false;
8769
+ const proto = Object.getPrototypeOf(value);
8770
+ return proto === Object.prototype || proto === null;
8771
+ }
8772
+ var MAX_SANITIZE_DEPTH = 10;
8773
+ function deepSanitize(value, seen = /* @__PURE__ */ new WeakSet(), depth = 0) {
8774
+ if (depth > MAX_SANITIZE_DEPTH) return void 0;
8738
8775
  if (value === null || value === void 0) return value;
8739
8776
  const t = typeof value;
8740
8777
  if (t === "string" || t === "number" || t === "boolean") return value;
8741
8778
  if (t === "function" || t === "symbol") return void 0;
8742
- if (Array.isArray(value)) return value.map(deepSanitize);
8743
8779
  if (t === "object") {
8780
+ const obj = value;
8781
+ if (seen.has(obj)) return void 0;
8782
+ seen.add(obj);
8783
+ if (!isPlainSerializable(value)) return void 0;
8784
+ if (Array.isArray(value)) {
8785
+ return value.map((v) => deepSanitize(v, seen, depth + 1));
8786
+ }
8744
8787
  const out = {};
8745
8788
  for (const [k, v] of Object.entries(value)) {
8746
- const sanitized = deepSanitize(v);
8789
+ const sanitized = deepSanitize(v, seen, depth + 1);
8747
8790
  if (sanitized !== void 0) {
8748
8791
  out[k] = sanitized;
8749
8792
  }
@@ -8763,6 +8806,13 @@
8763
8806
  var DefaultEventPriority = 32;
8764
8807
  function createHostConfig(onCommit) {
8765
8808
  let currentUpdatePriority = 0;
8809
+ let prevTreeCompareJson = null;
8810
+ function treeToCompareJson(tree) {
8811
+ return JSON.stringify(tree, (key, value) => {
8812
+ if (key.startsWith("__handler_")) return "__handler__";
8813
+ return value;
8814
+ });
8815
+ }
8766
8816
  return {
8767
8817
  // -----------------------------------------------------------------------
8768
8818
  // Feature flags
@@ -8864,6 +8914,7 @@
8864
8914
  parent.children.push(child);
8865
8915
  },
8866
8916
  removeChild(parent, child) {
8917
+ cleanupHandlers(child);
8867
8918
  child.parent = null;
8868
8919
  const idx = parent.children.indexOf(child);
8869
8920
  if (idx !== -1) parent.children.splice(idx, 1);
@@ -8882,6 +8933,7 @@
8882
8933
  container.children.push(child);
8883
8934
  },
8884
8935
  removeChildFromContainer(container, child) {
8936
+ cleanupHandlers(child);
8885
8937
  child.parent = null;
8886
8938
  const idx = container.children.indexOf(child);
8887
8939
  if (idx !== -1) container.children.splice(idx, 1);
@@ -8897,17 +8949,20 @@
8897
8949
  },
8898
8950
  // -----------------------------------------------------------------------
8899
8951
  // Updates
8952
+ //
8953
+ // NOTE: react-reconciler@0.32.0 (React 19) removed prepareUpdate and
8954
+ // the updatePayload parameter from commitUpdate. The reconciler now
8955
+ // calls commitUpdate directly with (instance, type, oldProps, newProps,
8956
+ // internalHandle) — no updatePayload.
8900
8957
  // -----------------------------------------------------------------------
8901
- prepareUpdate(_instance, _type, _oldProps, newProps) {
8902
- return newProps;
8903
- },
8904
- commitUpdate(instance, _updatePayload, _type, _oldProps, newProps) {
8905
- for (const [key, value] of Object.entries(instance.props)) {
8906
- if (key.startsWith("__handler_")) {
8958
+ commitUpdate(instance, _type, _oldProps, newProps) {
8959
+ const oldInstanceProps = instance.props;
8960
+ instance.props = sanitizeProps(newProps, oldInstanceProps);
8961
+ for (const [key, value] of Object.entries(oldInstanceProps)) {
8962
+ if (key.startsWith("__handler_") && !(key in instance.props)) {
8907
8963
  callbackRegistry.remove(value);
8908
8964
  }
8909
8965
  }
8910
- instance.props = sanitizeProps(newProps);
8911
8966
  },
8912
8967
  commitTextUpdate(textInstance, _oldText, newText) {
8913
8968
  textInstance.props = { content: newText };
@@ -8925,6 +8980,9 @@
8925
8980
  },
8926
8981
  resetAfterCommit(container) {
8927
8982
  const tree = serializeTree(container.children);
8983
+ const compareJson = treeToCompareJson(tree);
8984
+ if (compareJson === prevTreeCompareJson) return;
8985
+ prevTreeCompareJson = compareJson;
8928
8986
  onCommit(tree);
8929
8987
  },
8930
8988
  // -----------------------------------------------------------------------
@@ -8946,6 +9004,9 @@
8946
9004
  // Container operations
8947
9005
  // -----------------------------------------------------------------------
8948
9006
  clearContainer(container) {
9007
+ for (const child of container.children) {
9008
+ cleanupHandlers(child);
9009
+ }
8949
9010
  container.children = [];
8950
9011
  },
8951
9012
  preparePortalMount() {
@@ -8995,26 +9056,28 @@
8995
9056
 
8996
9057
  // src/renderer/render.ts
8997
9058
  var bridgeInstance = null;
9059
+ var reconcilerInstance = null;
9060
+ var rootInstance = null;
8998
9061
  function setBridge(bridge2) {
8999
9062
  bridgeInstance = bridge2;
9000
9063
  }
9001
- function renderExtension(element) {
9002
- if (!bridgeInstance) {
9003
- throw new Error(
9004
- "Vision SDK: Bridge not initialized. Ensure worker-entry has run before calling render."
9005
- );
9064
+ function ensureRoot() {
9065
+ if (reconcilerInstance && rootInstance) {
9066
+ return {
9067
+ reconciler: reconcilerInstance,
9068
+ root: rootInstance
9069
+ };
9006
9070
  }
9007
- const bridge2 = bridgeInstance;
9008
9071
  const onCommit = (tree) => {
9009
- bridge2.notify("ui.render", { tree });
9072
+ bridgeInstance?.notify("ui.render", { tree });
9010
9073
  };
9011
9074
  const hostConfig = createHostConfig(onCommit);
9012
9075
  const reconciler = (0, import_react_reconciler.default)(hostConfig);
9013
9076
  const container = { children: [] };
9014
9077
  const root = reconciler.createContainer(
9015
9078
  container,
9016
- 0,
9017
- // ConcurrentRoot tag
9079
+ 1,
9080
+ // ConcurrentRoot (0 = LegacyRoot/sync, 1 = ConcurrentRoot/async)
9018
9081
  null,
9019
9082
  // hydrationCallbacks
9020
9083
  false,
@@ -9028,8 +9091,19 @@
9028
9091
  null
9029
9092
  // transitionCallbacks
9030
9093
  );
9094
+ reconcilerInstance = reconciler;
9095
+ rootInstance = root;
9096
+ return { reconciler, root };
9097
+ }
9098
+ function renderExtension(element) {
9099
+ if (!bridgeInstance) {
9100
+ throw new Error(
9101
+ "Vision SDK: Bridge not initialized. Ensure worker-entry has run before calling render."
9102
+ );
9103
+ }
9104
+ const { reconciler, root } = ensureRoot();
9031
9105
  reconciler.updateContainer(element, root, null, () => {
9032
- bridge2.notify("ui.ready");
9106
+ bridgeInstance?.notify("ui.ready");
9033
9107
  });
9034
9108
  const clearInit = globalThis.__visionClearInitTimeout;
9035
9109
  if (typeof clearInit === "function") {
@@ -9157,27 +9231,139 @@
9157
9231
  );
9158
9232
  }
9159
9233
 
9234
+ // src/hooks/useQuery.ts
9235
+ var import_react14 = __toESM(require_react(), 1);
9236
+ function useQuery(name, args) {
9237
+ const [data, setData] = (0, import_react14.useState)(void 0);
9238
+ const [isLoading, setIsLoading] = (0, import_react14.useState)(true);
9239
+ const [error, setError] = (0, import_react14.useState)(null);
9240
+ const argsJson = JSON.stringify(args);
9241
+ const mountedRef = (0, import_react14.useRef)(true);
9242
+ const bridge2 = globalThis.__visionBridge;
9243
+ const fetchData = (0, import_react14.useCallback)(() => {
9244
+ if (!bridge2) return;
9245
+ setIsLoading(true);
9246
+ setError(null);
9247
+ const parsedArgs = argsJson ? JSON.parse(argsJson) : void 0;
9248
+ bridge2.request("server.query", { name, args: parsedArgs }).then((result) => {
9249
+ if (mountedRef.current) {
9250
+ setData(result);
9251
+ setIsLoading(false);
9252
+ }
9253
+ }).catch((err) => {
9254
+ if (mountedRef.current) {
9255
+ setError(err instanceof Error ? err : new Error(String(err)));
9256
+ setIsLoading(false);
9257
+ }
9258
+ });
9259
+ }, [bridge2, name, argsJson]);
9260
+ (0, import_react14.useEffect)(() => {
9261
+ mountedRef.current = true;
9262
+ fetchData();
9263
+ return () => {
9264
+ mountedRef.current = false;
9265
+ };
9266
+ }, [fetchData]);
9267
+ (0, import_react14.useEffect)(() => {
9268
+ const handler = (detail) => {
9269
+ const payload = detail;
9270
+ if (!payload?.queryNames || payload.queryNames.includes(name)) {
9271
+ fetchData();
9272
+ }
9273
+ };
9274
+ globalThis.__visionQueryInvalidate = handler;
9275
+ return () => {
9276
+ if (globalThis.__visionQueryInvalidate === handler) {
9277
+ delete globalThis.__visionQueryInvalidate;
9278
+ }
9279
+ };
9280
+ }, [fetchData, name]);
9281
+ return { data, isLoading, error, refetch: fetchData };
9282
+ }
9283
+
9284
+ // src/hooks/useMutation.ts
9285
+ var import_react15 = __toESM(require_react(), 1);
9286
+ function useMutation(name) {
9287
+ const [data, setData] = (0, import_react15.useState)(void 0);
9288
+ const [isLoading, setIsLoading] = (0, import_react15.useState)(false);
9289
+ const [error, setError] = (0, import_react15.useState)(null);
9290
+ const bridge2 = globalThis.__visionBridge;
9291
+ const mutateAsync = (0, import_react15.useCallback)(
9292
+ async (args) => {
9293
+ if (!bridge2) {
9294
+ throw new Error("Vision bridge not available");
9295
+ }
9296
+ setIsLoading(true);
9297
+ setError(null);
9298
+ try {
9299
+ const result = await bridge2.request("server.mutation", {
9300
+ name,
9301
+ args
9302
+ });
9303
+ setData(result);
9304
+ setIsLoading(false);
9305
+ const invalidate = globalThis.__visionQueryInvalidate;
9306
+ if (invalidate) {
9307
+ invalidate({});
9308
+ }
9309
+ return result;
9310
+ } catch (err) {
9311
+ const error2 = err instanceof Error ? err : new Error(String(err));
9312
+ setError(error2);
9313
+ setIsLoading(false);
9314
+ throw error2;
9315
+ }
9316
+ },
9317
+ [bridge2, name]
9318
+ );
9319
+ const mutate = (0, import_react15.useCallback)(
9320
+ (args) => {
9321
+ mutateAsync(args).catch(() => {
9322
+ });
9323
+ },
9324
+ [mutateAsync]
9325
+ );
9326
+ return { mutate, mutateAsync, data, isLoading, error };
9327
+ }
9328
+
9160
9329
  // src/bridge/worker-bridge.ts
9330
+ var isIframe = typeof window !== "undefined" && window.parent && window.parent !== window;
9161
9331
  var WorkerBridge = class {
9162
9332
  requestHandlers = /* @__PURE__ */ new Map();
9163
9333
  notificationHandlers = /* @__PURE__ */ new Map();
9164
9334
  pendingRequests = /* @__PURE__ */ new Map();
9165
9335
  requestCounter = 0;
9336
+ messageListener = null;
9166
9337
  constructor() {
9167
- self.addEventListener("message", (event) => {
9168
- this.handleMessage(event.data);
9169
- });
9338
+ this.messageListener = (event) => {
9339
+ if (isIframe && event.source !== window.parent) return;
9340
+ const data = event.data;
9341
+ if (!data || typeof data !== "object" || data.jsonrpc !== "2.0") return;
9342
+ this.handleMessage(data);
9343
+ };
9344
+ if (isIframe) {
9345
+ window.addEventListener("message", this.messageListener);
9346
+ } else {
9347
+ self.addEventListener("message", this.messageListener);
9348
+ }
9349
+ }
9350
+ send(message) {
9351
+ if (isIframe) {
9352
+ window.parent.postMessage(message, "*");
9353
+ } else {
9354
+ self.postMessage(message);
9355
+ }
9170
9356
  }
9171
9357
  notify(method, params) {
9172
9358
  const message = { jsonrpc: "2.0", method, params };
9173
- self.postMessage(message);
9359
+ this.send(message);
9174
9360
  }
9175
9361
  request(method, params) {
9176
9362
  const id = `req_${++this.requestCounter}`;
9177
9363
  const message = { jsonrpc: "2.0", id, method, params };
9178
9364
  return new Promise((resolve, reject) => {
9179
9365
  this.pendingRequests.set(id, { resolve, reject });
9180
- self.postMessage(message);
9366
+ this.send(message);
9181
9367
  });
9182
9368
  }
9183
9369
  onRequest(method, handler) {
@@ -9213,14 +9399,14 @@
9213
9399
  id: message.id,
9214
9400
  result
9215
9401
  };
9216
- self.postMessage(response);
9402
+ this.send(response);
9217
9403
  }).catch((error) => {
9218
9404
  const response = {
9219
9405
  jsonrpc: "2.0",
9220
9406
  id: message.id,
9221
9407
  error: { code: -32e3, message: String(error) }
9222
9408
  };
9223
- self.postMessage(response);
9409
+ this.send(response);
9224
9410
  });
9225
9411
  }
9226
9412
  } else {
@@ -9232,6 +9418,14 @@
9232
9418
  }
9233
9419
  }
9234
9420
  destroy() {
9421
+ if (this.messageListener) {
9422
+ if (isIframe) {
9423
+ window.removeEventListener("message", this.messageListener);
9424
+ } else {
9425
+ self.removeEventListener("message", this.messageListener);
9426
+ }
9427
+ this.messageListener = null;
9428
+ }
9235
9429
  this.pendingRequests.clear();
9236
9430
  this.requestHandlers.clear();
9237
9431
  this.notificationHandlers.clear();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@1upvision/sdk",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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.0"
26
+ "@1upvision/protocol": "0.1.1"
27
27
  },
28
28
  "peerDependencies": {
29
29
  "react": "^18.0.0 || ^19.0.0"