From 88c05c2d2413c077913ab5791d2ef1453049711b Mon Sep 17 00:00:00 2001 From: Alex Warren Date: Sun, 19 Apr 2026 17:05:59 +0000 Subject: [PATCH 01/20] scaffold: g-context library + g-context-example Vite app MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Empty React library skeleton (stubs only) and a Vite+React-18 smoke-test app. Implementation lands in follow-ups — see graphistry/ai_code_notes/architecture/external_bridge.md. Co-Authored-By: Claude Opus 4.7 (1M context) --- lerna.json | 2 + projects/g-context-example/.gitignore | 3 ++ projects/g-context-example/index.html | 12 +++++ projects/g-context-example/package.json | 23 ++++++++++ projects/g-context-example/src/App.tsx | 16 +++++++ projects/g-context-example/src/main.tsx | 14 ++++++ projects/g-context-example/tsconfig.json | 21 +++++++++ projects/g-context-example/tsconfig.node.json | 11 +++++ projects/g-context-example/vite.config.ts | 7 +++ projects/g-context/README.md | 3 ++ projects/g-context/package.json | 46 +++++++++++++++++++ projects/g-context/src/context.ts | 28 +++++++++++ projects/g-context/src/externalStore.ts | 30 ++++++++++++ projects/g-context/src/hooks.ts | 22 +++++++++ projects/g-context/src/index.ts | 11 +++++ projects/g-context/tsconfig.json | 22 +++++++++ 16 files changed, 271 insertions(+) create mode 100644 projects/g-context-example/.gitignore create mode 100644 projects/g-context-example/index.html create mode 100644 projects/g-context-example/package.json create mode 100644 projects/g-context-example/src/App.tsx create mode 100644 projects/g-context-example/src/main.tsx create mode 100644 projects/g-context-example/tsconfig.json create mode 100644 projects/g-context-example/tsconfig.node.json create mode 100644 projects/g-context-example/vite.config.ts create mode 100644 projects/g-context/README.md create mode 100644 projects/g-context/package.json create mode 100644 projects/g-context/src/context.ts create mode 100644 projects/g-context/src/externalStore.ts create mode 100644 projects/g-context/src/hooks.ts create mode 100644 projects/g-context/src/index.ts create mode 100644 projects/g-context/tsconfig.json diff --git a/lerna.json b/lerna.json index ee5616d..32398ce 100644 --- a/lerna.json +++ b/lerna.json @@ -6,6 +6,8 @@ "projects/cra-test", "projects/cra-test-18", "projects/cra-template", + "projects/g-context", + "projects/g-context-example", "projects/js-upload-api", "projects/node-api", "projects/node-api-test", diff --git a/projects/g-context-example/.gitignore b/projects/g-context-example/.gitignore new file mode 100644 index 0000000..b431156 --- /dev/null +++ b/projects/g-context-example/.gitignore @@ -0,0 +1,3 @@ +node_modules +dist +.vite diff --git a/projects/g-context-example/index.html b/projects/g-context-example/index.html new file mode 100644 index 0000000..6a134ae --- /dev/null +++ b/projects/g-context-example/index.html @@ -0,0 +1,12 @@ + + + + + + g-context-example + + +
+ + + diff --git a/projects/g-context-example/package.json b/projects/g-context-example/package.json new file mode 100644 index 0000000..e994b4a --- /dev/null +++ b/projects/g-context-example/package.json @@ -0,0 +1,23 @@ +{ + "name": "g-context-example", + "version": "5.1.6", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@graphistry/g-context": "^5.1.6", + "react": "^18.1.0", + "react-dom": "^18.1.0" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "@vitejs/plugin-react": "^4.0.0", + "typescript": "^5.3.0", + "vite": "^5.0.0" + } +} diff --git a/projects/g-context-example/src/App.tsx b/projects/g-context-example/src/App.tsx new file mode 100644 index 0000000..9657b72 --- /dev/null +++ b/projects/g-context-example/src/App.tsx @@ -0,0 +1,16 @@ +// Import from the scaffold to make sure the workspace link resolves. +// The hooks/provider currently throw — we don't call them yet. +import { + GraphistryProvider as _GraphistryProvider, + useGraphistry as _useGraphistry, + useSelection as _useSelection, +} from '@graphistry/g-context'; + +// Silence unused-import warnings while the scaffold is empty. +void _GraphistryProvider; +void _useGraphistry; +void _useSelection; + +export function App() { + return
g-context-example — scaffold
; +} diff --git a/projects/g-context-example/src/main.tsx b/projects/g-context-example/src/main.tsx new file mode 100644 index 0000000..f1999b5 --- /dev/null +++ b/projects/g-context-example/src/main.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { createRoot } from 'react-dom/client'; +import { App } from './App'; + +const container = document.getElementById('root'); +if (!container) { + throw new Error('Root element #root not found'); +} + +createRoot(container).render( + + + , +); diff --git a/projects/g-context-example/tsconfig.json b/projects/g-context-example/tsconfig.json new file mode 100644 index 0000000..3934b8f --- /dev/null +++ b/projects/g-context-example/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/projects/g-context-example/tsconfig.node.json b/projects/g-context-example/tsconfig.node.json new file mode 100644 index 0000000..97ede7e --- /dev/null +++ b/projects/g-context-example/tsconfig.node.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true, + "strict": true + }, + "include": ["vite.config.ts"] +} diff --git a/projects/g-context-example/vite.config.ts b/projects/g-context-example/vite.config.ts new file mode 100644 index 0000000..627a319 --- /dev/null +++ b/projects/g-context-example/vite.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], +}); diff --git a/projects/g-context/README.md b/projects/g-context/README.md new file mode 100644 index 0000000..ad4e939 --- /dev/null +++ b/projects/g-context/README.md @@ -0,0 +1,3 @@ +# @graphistry/g-context + +React provider + hooks over `@graphistry/client-api`. See `ai_code_notes/architecture/external_bridge.md` in the graphistry repo for design context. diff --git a/projects/g-context/package.json b/projects/g-context/package.json new file mode 100644 index 0000000..185dbd0 --- /dev/null +++ b/projects/g-context/package.json @@ -0,0 +1,46 @@ +{ + "name": "@graphistry/g-context", + "version": "5.1.6", + "description": "React provider + hooks over @graphistry/client-api", + "type": "module", + "main": "dist/index.cjs", + "module": "dist/index.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist", + "README.md" + ], + "scripts": { + "build": "tsup src/index.ts --format esm,cjs --dts", + "clean": "rm -rf dist node_modules", + "lint": "echo 'lint not configured yet'" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/graphistry/graphistry-js.git" + }, + "homepage": "https://github.com/graphistry/graphistry-js/projects/g-context#readme", + "author": "Graphistry, Inc ", + "license": "Apache-2.0", + "dependencies": { + "@graphistry/client-api": "^5.1.6", + "rxjs": ">=7.2.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "devDependencies": { + "@types/react": "^18.2.0", + "@types/react-dom": "^18.2.0", + "tsup": "^8.0.0", + "typescript": "^5.3.0" + } +} diff --git a/projects/g-context/src/context.ts b/projects/g-context/src/context.ts new file mode 100644 index 0000000..841aead --- /dev/null +++ b/projects/g-context/src/context.ts @@ -0,0 +1,28 @@ +/** + * Graphistry React context — skeleton stubs only. + * + * Implementation lands in a follow-up. See + * graphistry/ai_code_notes/architecture/external_bridge.md for design. + */ + +import type { ReactNode } from 'react'; + +export interface GraphistryScene { + readonly id: string; +} + +export interface GraphistryProviderProps { + children?: ReactNode; +} + +export function GraphistryProvider(_props: GraphistryProviderProps): never { + throw new Error('not implemented'); +} + +export function useGraphistry(): never { + throw new Error('not implemented'); +} + +export function useGraphistryScene(): GraphistryScene { + throw new Error('not implemented'); +} diff --git a/projects/g-context/src/externalStore.ts b/projects/g-context/src/externalStore.ts new file mode 100644 index 0000000..a79af8e --- /dev/null +++ b/projects/g-context/src/externalStore.ts @@ -0,0 +1,30 @@ +/** + * ExternalStore — skeleton stub. + * + * Implementation lands in a follow-up. See + * graphistry/ai_code_notes/architecture/external_bridge.md for design. + */ + +export type Listener = () => void; +export type Unsubscribe = () => void; + +export class ExternalStore { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + constructor(_initial: T) { + throw new Error('not implemented'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + subscribe(_listener: Listener): Unsubscribe { + throw new Error('not implemented'); + } + + getSnapshot(): T { + throw new Error('not implemented'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + update(_updater: (prev: T) => T): void { + throw new Error('not implemented'); + } +} diff --git a/projects/g-context/src/hooks.ts b/projects/g-context/src/hooks.ts new file mode 100644 index 0000000..8caf66b --- /dev/null +++ b/projects/g-context/src/hooks.ts @@ -0,0 +1,22 @@ +/** + * Graphistry React hooks — skeleton stubs only. + * + * Implementation lands in a follow-up. See + * graphistry/ai_code_notes/architecture/external_bridge.md for design. + */ + +export function useSelection(): never { + throw new Error('not implemented'); +} + +export function useLabels(): never { + throw new Error('not implemented'); +} + +export function useFilter(): never { + throw new Error('not implemented'); +} + +export function useSetting(_key: string): never { + throw new Error('not implemented'); +} diff --git a/projects/g-context/src/index.ts b/projects/g-context/src/index.ts new file mode 100644 index 0000000..20632e2 --- /dev/null +++ b/projects/g-context/src/index.ts @@ -0,0 +1,11 @@ +export { ExternalStore } from './externalStore.js'; +export type { Listener, Unsubscribe } from './externalStore.js'; + +export { + GraphistryProvider, + useGraphistry, + useGraphistryScene, +} from './context.js'; +export type { GraphistryScene, GraphistryProviderProps } from './context.js'; + +export { useSelection, useLabels, useFilter, useSetting } from './hooks.js'; diff --git a/projects/g-context/tsconfig.json b/projects/g-context/tsconfig.json new file mode 100644 index 0000000..34a7422 --- /dev/null +++ b/projects/g-context/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "jsx": "react-jsx", + "strict": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true, + "outDir": "dist", + "isolatedModules": true, + "resolveJsonModule": true, + "allowSyntheticDefaultImports": true + }, + "include": ["src"], + "exclude": ["node_modules", "dist"] +} From 541a91cd6faf6874871454b19e0e973a21265c78 Mon Sep 17 00:00:00 2001 From: Alex Warren Date: Sun, 19 Apr 2026 17:48:42 +0000 Subject: [PATCH 02/20] =?UTF-8?q?rename:=20g-context=20=E2=86=92=20client-?= =?UTF-8?q?api-context=20+=20matching=20example?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Aligns the package name with the existing client-api / client-api-react naming convention. Co-Authored-By: Claude Opus 4.7 (1M context) --- lerna.json | 4 ++-- .../.gitignore | 0 .../index.html | 2 +- .../package.json | 4 ++-- .../src/App.tsx | 4 ++-- .../src/main.tsx | 0 .../tsconfig.json | 0 .../tsconfig.node.json | 0 .../vite.config.ts | 0 projects/client-api-context/README.md | 3 +++ projects/{g-context => client-api-context}/package.json | 4 ++-- projects/{g-context => client-api-context}/src/context.ts | 0 .../{g-context => client-api-context}/src/externalStore.ts | 0 projects/{g-context => client-api-context}/src/hooks.ts | 0 projects/{g-context => client-api-context}/src/index.ts | 0 projects/{g-context => client-api-context}/tsconfig.json | 0 projects/g-context/README.md | 3 --- 17 files changed, 12 insertions(+), 12 deletions(-) rename projects/{g-context-example => client-api-context-example}/.gitignore (100%) rename projects/{g-context-example => client-api-context-example}/index.html (85%) rename projects/{g-context-example => client-api-context-example}/package.json (82%) rename projects/{g-context-example => client-api-context-example}/src/App.tsx (80%) rename projects/{g-context-example => client-api-context-example}/src/main.tsx (100%) rename projects/{g-context-example => client-api-context-example}/tsconfig.json (100%) rename projects/{g-context-example => client-api-context-example}/tsconfig.node.json (100%) rename projects/{g-context-example => client-api-context-example}/vite.config.ts (100%) create mode 100644 projects/client-api-context/README.md rename projects/{g-context => client-api-context}/package.json (93%) rename projects/{g-context => client-api-context}/src/context.ts (100%) rename projects/{g-context => client-api-context}/src/externalStore.ts (100%) rename projects/{g-context => client-api-context}/src/hooks.ts (100%) rename projects/{g-context => client-api-context}/src/index.ts (100%) rename projects/{g-context => client-api-context}/tsconfig.json (100%) delete mode 100644 projects/g-context/README.md diff --git a/lerna.json b/lerna.json index 32398ce..d956581 100644 --- a/lerna.json +++ b/lerna.json @@ -2,12 +2,12 @@ "version": "5.1.6", "packages": [ "projects/client-api", + "projects/client-api-context", + "projects/client-api-context-example", "projects/client-api-react", "projects/cra-test", "projects/cra-test-18", "projects/cra-template", - "projects/g-context", - "projects/g-context-example", "projects/js-upload-api", "projects/node-api", "projects/node-api-test", diff --git a/projects/g-context-example/.gitignore b/projects/client-api-context-example/.gitignore similarity index 100% rename from projects/g-context-example/.gitignore rename to projects/client-api-context-example/.gitignore diff --git a/projects/g-context-example/index.html b/projects/client-api-context-example/index.html similarity index 85% rename from projects/g-context-example/index.html rename to projects/client-api-context-example/index.html index 6a134ae..cc3a336 100644 --- a/projects/g-context-example/index.html +++ b/projects/client-api-context-example/index.html @@ -3,7 +3,7 @@ - g-context-example + client-api-context-example
diff --git a/projects/g-context-example/package.json b/projects/client-api-context-example/package.json similarity index 82% rename from projects/g-context-example/package.json rename to projects/client-api-context-example/package.json index e994b4a..705aa81 100644 --- a/projects/g-context-example/package.json +++ b/projects/client-api-context-example/package.json @@ -1,5 +1,5 @@ { - "name": "g-context-example", + "name": "client-api-context-example", "version": "5.1.6", "private": true, "type": "module", @@ -9,7 +9,7 @@ "preview": "vite preview" }, "dependencies": { - "@graphistry/g-context": "^5.1.6", + "@graphistry/client-api-context": "^5.1.6", "react": "^18.1.0", "react-dom": "^18.1.0" }, diff --git a/projects/g-context-example/src/App.tsx b/projects/client-api-context-example/src/App.tsx similarity index 80% rename from projects/g-context-example/src/App.tsx rename to projects/client-api-context-example/src/App.tsx index 9657b72..7baf553 100644 --- a/projects/g-context-example/src/App.tsx +++ b/projects/client-api-context-example/src/App.tsx @@ -4,7 +4,7 @@ import { GraphistryProvider as _GraphistryProvider, useGraphistry as _useGraphistry, useSelection as _useSelection, -} from '@graphistry/g-context'; +} from '@graphistry/client-api-context'; // Silence unused-import warnings while the scaffold is empty. void _GraphistryProvider; @@ -12,5 +12,5 @@ void _useGraphistry; void _useSelection; export function App() { - return
g-context-example — scaffold
; + return
client-api-context-example — scaffold
; } diff --git a/projects/g-context-example/src/main.tsx b/projects/client-api-context-example/src/main.tsx similarity index 100% rename from projects/g-context-example/src/main.tsx rename to projects/client-api-context-example/src/main.tsx diff --git a/projects/g-context-example/tsconfig.json b/projects/client-api-context-example/tsconfig.json similarity index 100% rename from projects/g-context-example/tsconfig.json rename to projects/client-api-context-example/tsconfig.json diff --git a/projects/g-context-example/tsconfig.node.json b/projects/client-api-context-example/tsconfig.node.json similarity index 100% rename from projects/g-context-example/tsconfig.node.json rename to projects/client-api-context-example/tsconfig.node.json diff --git a/projects/g-context-example/vite.config.ts b/projects/client-api-context-example/vite.config.ts similarity index 100% rename from projects/g-context-example/vite.config.ts rename to projects/client-api-context-example/vite.config.ts diff --git a/projects/client-api-context/README.md b/projects/client-api-context/README.md new file mode 100644 index 0000000..69769b6 --- /dev/null +++ b/projects/client-api-context/README.md @@ -0,0 +1,3 @@ +# @graphistry/client-api-context + +React provider + hooks over `@graphistry/client-api`. See `ai_code_notes/architecture/external_bridge.md` and `client_context.md` in the graphistry repo for design context. diff --git a/projects/g-context/package.json b/projects/client-api-context/package.json similarity index 93% rename from projects/g-context/package.json rename to projects/client-api-context/package.json index 185dbd0..5b876cb 100644 --- a/projects/g-context/package.json +++ b/projects/client-api-context/package.json @@ -1,5 +1,5 @@ { - "name": "@graphistry/g-context", + "name": "@graphistry/client-api-context", "version": "5.1.6", "description": "React provider + hooks over @graphistry/client-api", "type": "module", @@ -26,7 +26,7 @@ "type": "git", "url": "git+https://github.com/graphistry/graphistry-js.git" }, - "homepage": "https://github.com/graphistry/graphistry-js/projects/g-context#readme", + "homepage": "https://github.com/graphistry/graphistry-js/projects/client-api-context#readme", "author": "Graphistry, Inc ", "license": "Apache-2.0", "dependencies": { diff --git a/projects/g-context/src/context.ts b/projects/client-api-context/src/context.ts similarity index 100% rename from projects/g-context/src/context.ts rename to projects/client-api-context/src/context.ts diff --git a/projects/g-context/src/externalStore.ts b/projects/client-api-context/src/externalStore.ts similarity index 100% rename from projects/g-context/src/externalStore.ts rename to projects/client-api-context/src/externalStore.ts diff --git a/projects/g-context/src/hooks.ts b/projects/client-api-context/src/hooks.ts similarity index 100% rename from projects/g-context/src/hooks.ts rename to projects/client-api-context/src/hooks.ts diff --git a/projects/g-context/src/index.ts b/projects/client-api-context/src/index.ts similarity index 100% rename from projects/g-context/src/index.ts rename to projects/client-api-context/src/index.ts diff --git a/projects/g-context/tsconfig.json b/projects/client-api-context/tsconfig.json similarity index 100% rename from projects/g-context/tsconfig.json rename to projects/client-api-context/tsconfig.json diff --git a/projects/g-context/README.md b/projects/g-context/README.md deleted file mode 100644 index ad4e939..0000000 --- a/projects/g-context/README.md +++ /dev/null @@ -1,3 +0,0 @@ -# @graphistry/g-context - -React provider + hooks over `@graphistry/client-api`. See `ai_code_notes/architecture/external_bridge.md` in the graphistry repo for design context. From 0348091142c807ca24a0241ecbe330fbc516140f Mon Sep 17 00:00:00 2001 From: Alex Warren Date: Sun, 19 Apr 2026 17:56:05 +0000 Subject: [PATCH 03/20] feat(client-api): Protocol v2 RPC client with typed errors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds createRpcClient(iframe) — a Promise-returning thin wrapper over the Protocol v2 correlation-id envelope. Exposes three primitives matching the iframe-side dispatcher: call(path, args), get(...paths), set(json). Rejects with GraphistryRpcError carrying a typed kind ('invalid_op' | 'internal' | 'timeout' | 'disposed'), so consumers can distinguish failure modes without parsing message strings. Bumps CLIENT_SUBSCRIPTION_API_VERSION to 2 to declare support for the new envelope on the handshake. No rxjs in the RPC path — a junior dev wires up addFilter() without learning Observables. Co-Authored-By: Claude Opus 4.7 (1M context) --- projects/client-api/src/index.js | 6 +- projects/client-api/src/rpc.js | 118 +++++++++++++++++++++++++++++++ 2 files changed, 123 insertions(+), 1 deletion(-) create mode 100644 projects/client-api/src/rpc.js diff --git a/projects/client-api/src/index.js b/projects/client-api/src/index.js index 4d3129f..c9038ec 100644 --- a/projects/client-api/src/index.js +++ b/projects/client-api/src/index.js @@ -6,7 +6,11 @@ import { $ref, $atom, $value } from '@graphistry/falcor-json-graph'; import { Client as ClientBase, ClientPKey as ClientPKeyBase, Dataset as DatasetBase, File as FileBase, EdgeFile as EdgeFileBase, NodeFile as NodeFileBase } from '@graphistry/js-upload-api'; -const CLIENT_SUBSCRIPTION_API_VERSION = 1; +const CLIENT_SUBSCRIPTION_API_VERSION = 2; + +// Protocol v2: correlation-id RPC client for one-shot imperative calls +// (handle.addFilter(expr) returns a real Promise). See ./rpc.js. +export { createRpcClient, GraphistryRpcError } from './rpc'; // Warning: must export variable seperately from declaration as workaround for JSDoc parsing error diff --git a/projects/client-api/src/rpc.js b/projects/client-api/src/rpc.js new file mode 100644 index 0000000..cfad141 --- /dev/null +++ b/projects/client-api/src/rpc.js @@ -0,0 +1,118 @@ +// Correlation-id RPC client over the Protocol v2 envelope. +// +// The iframe side is a generic dispatcher over the three falcor primitives +// (call / get / set) — see graphistry apps/core/viz/src/client/falcor/LocalDataSink.js. +// This file is the host-side counterpart: mint an id, post the request, +// resolve/reject the Promise when the correlation-matched response arrives. +// +// No rxjs dependency. Returns plain Promises so consumers can await naturally +// without understanding falcor or rxjs. + +let nextSeq = 0; +const mintId = () => `rpc-${Date.now().toString(36)}-${(nextSeq++).toString(36)}`; + +/** + * Typed error for Protocol v2 RPC failures. + * Properties: + * - kind: 'invalid_op' | 'internal' | 'timeout' | 'disposed' | string + * - op: the op name at the time of failure ('call' | 'get' | 'set' | 'unknown') + * - message: human-readable message + * Subclass discriminated by `kind` so callers can `if (err.kind === 'timeout')`. + */ +export class GraphistryRpcError extends Error { + constructor({ kind, op, message, ...rest } = {}) { + super(message || `Graphistry RPC error (${kind ?? 'unknown'})`); + this.name = 'GraphistryRpcError'; + this.kind = kind; + this.op = op; + Object.assign(this, rest); + } +} + +/** + * Create a Protocol v2 RPC client bound to a specific iframe. + * + * @param {HTMLIFrameElement} iframe — the