@parcae/sdk
Advanced tools
| import { Model, FrontendAdapter } from '@parcae/model'; | ||
| import SocketIO from 'socket.io-client'; | ||
| import pako from 'pako'; | ||
| import { decompress } from 'compress-json'; | ||
| import { EventEmitter } from 'eventemitter3'; | ||
| import ShortId from 'short-unique-id'; | ||
| var __defProp = Object.defineProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| // src/log.ts | ||
| var isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true; | ||
| function time() { | ||
| const d = /* @__PURE__ */ new Date(); | ||
| return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`; | ||
| } | ||
| var log = { | ||
| warn: (...args) => isDev && console.warn(`%c${time()} SDK`, "color: #fb3", ...args), | ||
| error: (...args) => console.error(`%c${time()} SDK`, "color: #f44", ...args), | ||
| debug: (...args) => isDev && console.debug(`%c${time()} SDK`, "color: #888", ...args) | ||
| }; | ||
| // src/auth-gate.ts | ||
| var AuthGate = class { | ||
| /** Reactive state — plain object, mutated in place. */ | ||
| state = { | ||
| status: "pending", | ||
| userId: null, | ||
| version: 0 | ||
| }; | ||
| /** Awaitable — resolves when auth is confirmed (either way) */ | ||
| ready; | ||
| _resolve = null; | ||
| _listeners = /* @__PURE__ */ new Set(); | ||
| constructor() { | ||
| this.ready = this._makePending(); | ||
| } | ||
| /** | ||
| * Subscribe to state changes. Returns an unsubscribe function. | ||
| * The callback is invoked synchronously whenever resolve / | ||
| * resolveUnauthenticated / reset mutates the state. | ||
| */ | ||
| subscribe(fn) { | ||
| this._listeners.add(fn); | ||
| return () => { | ||
| this._listeners.delete(fn); | ||
| }; | ||
| } | ||
| /** Auth confirmed — user is authenticated */ | ||
| resolve(userId) { | ||
| this.state.status = "authenticated"; | ||
| log.debug("auth: authenticated, userId:", userId); | ||
| this.state.userId = userId; | ||
| this.state.version++; | ||
| this._resolve?.(); | ||
| this._resolve = null; | ||
| this._notify(); | ||
| } | ||
| /** Auth confirmed — no user */ | ||
| resolveUnauthenticated() { | ||
| this.state.status = "unauthenticated"; | ||
| log.debug("auth: unauthenticated"); | ||
| this.state.userId = null; | ||
| this.state.version++; | ||
| this._resolve?.(); | ||
| this._resolve = null; | ||
| this._notify(); | ||
| } | ||
| /** Reset to pending (disconnect, token change) */ | ||
| reset() { | ||
| if (this.state.status !== "pending") { | ||
| this.state.status = "pending"; | ||
| log.debug("auth: reset to pending"); | ||
| this.state.userId = null; | ||
| this.state.version++; | ||
| this.ready = this._makePending(); | ||
| this._notify(); | ||
| } | ||
| } | ||
| _notify() { | ||
| for (const fn of this._listeners) fn(); | ||
| } | ||
| _makePending() { | ||
| return new Promise((r) => { | ||
| this._resolve = r; | ||
| }); | ||
| } | ||
| }; | ||
| // src/transports/socket.ts | ||
| var DEFAULT_TIMEOUT = 3e4; | ||
| var uid = new ShortId({ length: 10 }); | ||
| var SOCKETS = /* @__PURE__ */ new Map(); | ||
| var SocketTransport = class extends EventEmitter { | ||
| auth = new AuthGate(); | ||
| isConnected = false; | ||
| socket; | ||
| url; | ||
| version; | ||
| token; | ||
| inflight = /* @__PURE__ */ new Map(); | ||
| constructor(config) { | ||
| super(); | ||
| this.url = config.url; | ||
| this.version = config.version ?? "v1"; | ||
| this.token = config.token; | ||
| const socketPath = config.path ?? "/ws"; | ||
| const socketKey = `${this.url}:${socketPath}`; | ||
| if (SOCKETS.has(socketKey)) { | ||
| this.socket = SOCKETS.get(socketKey); | ||
| } else { | ||
| this.socket = SocketIO(this.url, { | ||
| path: socketPath, | ||
| transports: ["websocket"], | ||
| withCredentials: true | ||
| }); | ||
| SOCKETS.set(socketKey, this.socket); | ||
| } | ||
| this.socket.on("connect", () => { | ||
| this.isConnected = true; | ||
| log.debug("socket connected"); | ||
| this._doAuth(); | ||
| this.emit("connected"); | ||
| }); | ||
| this.socket.on("disconnect", () => { | ||
| this.isConnected = false; | ||
| log.debug("socket disconnected"); | ||
| this.auth.reset(); | ||
| this.emit("disconnected"); | ||
| }); | ||
| this.socket.on("error", (err) => this.emit("error", err)); | ||
| if (this.socket.connected) { | ||
| this.isConnected = true; | ||
| log.debug("socket connected"); | ||
| this._doAuth(); | ||
| } | ||
| if (this.token === null) { | ||
| this.auth.resolveUnauthenticated(); | ||
| } | ||
| } | ||
| _doAuth() { | ||
| if (this.token === void 0) return; | ||
| if (this.token === null) { | ||
| this.auth.resolveUnauthenticated(); | ||
| return; | ||
| } | ||
| const t0 = performance.now(); | ||
| log.debug("authenticating..."); | ||
| this.socket.emit("authenticate", this.token, (response) => { | ||
| const ms = (performance.now() - t0).toFixed(0); | ||
| const userId = response?.userId ?? null; | ||
| if (userId) { | ||
| this.auth.resolve(userId); | ||
| log.debug(`authenticated as ${userId} (${ms}ms)`); | ||
| } else { | ||
| this.auth.resolveUnauthenticated(); | ||
| log.debug(`auth rejected (${ms}ms)`); | ||
| } | ||
| }); | ||
| } | ||
| async authenticate(token) { | ||
| this.token = token; | ||
| this.auth.reset(); | ||
| if (token === null) { | ||
| this.auth.resolveUnauthenticated(); | ||
| return { userId: null }; | ||
| } | ||
| if (!this.socket.connected) { | ||
| await new Promise((resolve, reject) => { | ||
| const timeout = setTimeout(() => { | ||
| this.socket.off("connect", onConnect); | ||
| this.auth.resolveUnauthenticated(); | ||
| reject(new Error("Authentication timeout: socket not connected")); | ||
| }, DEFAULT_TIMEOUT); | ||
| const onConnect = () => { | ||
| clearTimeout(timeout); | ||
| resolve(); | ||
| }; | ||
| this.socket.once("connect", onConnect); | ||
| }); | ||
| } | ||
| return new Promise((resolve) => { | ||
| this.socket.emit("authenticate", token, (response) => { | ||
| const userId = response?.userId ?? null; | ||
| if (userId) this.auth.resolve(userId); | ||
| else this.auth.resolveUnauthenticated(); | ||
| resolve({ userId }); | ||
| }); | ||
| }); | ||
| } | ||
| // ── Request/Response ────────────────────────────────────────────── | ||
| async fetch(method, path, data = {}, options) { | ||
| await this.auth.ready; | ||
| if (!this.socket.connected) { | ||
| await new Promise((resolve, reject) => { | ||
| if (this.socket.connected) return resolve(); | ||
| const timeout = setTimeout(() => { | ||
| cleanup(); | ||
| reject(new Error("Connection timeout")); | ||
| }, DEFAULT_TIMEOUT); | ||
| const onConnect = () => { | ||
| cleanup(); | ||
| resolve(); | ||
| }; | ||
| const onError = (err) => { | ||
| cleanup(); | ||
| reject(err); | ||
| }; | ||
| const cleanup = () => { | ||
| clearTimeout(timeout); | ||
| this.socket.off("connect", onConnect); | ||
| this.socket.off("connect_error", onError); | ||
| }; | ||
| this.socket.once("connect", onConnect); | ||
| this.socket.once("connect_error", onError); | ||
| }); | ||
| } | ||
| const upper = method.toUpperCase(); | ||
| if (upper === "GET") { | ||
| const dedupeKey = `${path}:${JSON.stringify(data)}`; | ||
| const existing = this.inflight.get(dedupeKey); | ||
| if (existing) return existing; | ||
| const req = this._call(method, path, data, options); | ||
| this.inflight.set(dedupeKey, req); | ||
| req.finally(() => this.inflight.delete(dedupeKey)); | ||
| return req; | ||
| } | ||
| return this._call(method, path, data, options); | ||
| } | ||
| _call(method, path, data, options) { | ||
| const id = uid.rnd(); | ||
| const t0 = performance.now(); | ||
| const fullPath = `/${this.version}${path}`; | ||
| const timeoutMs = options?.timeout ?? DEFAULT_TIMEOUT; | ||
| log.debug(`\u2192 ${method.toUpperCase()} ${fullPath}`); | ||
| return new Promise((resolve, reject) => { | ||
| const timeout = setTimeout(() => { | ||
| this.socket.off(id); | ||
| log.debug( | ||
| `\u2717 ${method.toUpperCase()} ${fullPath} timeout (${(timeoutMs / 1e3).toFixed(0)}s)` | ||
| ); | ||
| reject(new Error(`RPC timeout: ${method} ${path}`)); | ||
| }, timeoutMs); | ||
| this.socket.once(id, (msg) => { | ||
| clearTimeout(timeout); | ||
| const ms = (performance.now() - t0).toFixed(0); | ||
| try { | ||
| const uncompressed = pako.ungzip(msg, { to: "string" }); | ||
| const parsed = decompress(JSON.parse(uncompressed)); | ||
| if (parsed.success) { | ||
| log.debug(`\u2190 ${method.toUpperCase()} ${fullPath} (${ms}ms)`); | ||
| resolve(parsed.result); | ||
| } else { | ||
| log.debug( | ||
| `\u2717 ${method.toUpperCase()} ${fullPath} (${ms}ms) ${parsed.error || parsed.message}` | ||
| ); | ||
| reject( | ||
| new Error( | ||
| parsed.message || parsed.error || `${method} ${path} failed` | ||
| ) | ||
| ); | ||
| } | ||
| } catch (err) { | ||
| log.debug( | ||
| `\u2717 ${method.toUpperCase()} ${fullPath} (${ms}ms) parse error` | ||
| ); | ||
| reject(err); | ||
| } | ||
| }); | ||
| this.socket.emit( | ||
| "call", | ||
| id, | ||
| method.toUpperCase(), | ||
| `/${this.version}${path}`, | ||
| data | ||
| ); | ||
| }); | ||
| } | ||
| async get(path, data, options) { | ||
| return this.fetch("GET", path, data, options); | ||
| } | ||
| async post(path, data, options) { | ||
| return this.fetch("POST", path, data, options); | ||
| } | ||
| async put(path, data, options) { | ||
| return this.fetch("PUT", path, data, options); | ||
| } | ||
| async patch(path, data, options) { | ||
| return this.fetch("PATCH", path, data, options); | ||
| } | ||
| async delete(path, data, options) { | ||
| return this.fetch("DELETE", path, data, options); | ||
| } | ||
| subscribe(event, handler) { | ||
| this.socket.on(event, handler); | ||
| return () => this.socket.off(event, handler); | ||
| } | ||
| unsubscribe(event, handler) { | ||
| this.socket.off(event, handler); | ||
| } | ||
| async send(event, ...args) { | ||
| await this.auth.ready; | ||
| this.socket.emit(event, ...args); | ||
| } | ||
| disconnect() { | ||
| this.socket.disconnect(); | ||
| this.isConnected = false; | ||
| log.debug("socket disconnected"); | ||
| } | ||
| async reconnect() { | ||
| this.socket.connect(); | ||
| } | ||
| }; | ||
| var SSETransport = class extends EventEmitter { | ||
| auth = new AuthGate(); | ||
| url; | ||
| version; | ||
| apiKey; | ||
| key = null; | ||
| eventSources = /* @__PURE__ */ new Map(); | ||
| isConnected = true; | ||
| // HTTP is "always connected" | ||
| loading; | ||
| constructor(config) { | ||
| super(); | ||
| this.url = config.url.replace(/\/$/, ""); | ||
| this.version = config.version ?? "v1"; | ||
| this.apiKey = config.key ?? null; | ||
| this.loading = this.resolveKey(); | ||
| } | ||
| async resolveKey() { | ||
| try { | ||
| this.key = typeof this.apiKey === "function" ? await this.apiKey() : this.apiKey; | ||
| this.isConnected = true; | ||
| this.emit("connected"); | ||
| } catch (err) { | ||
| this.emit("error", err); | ||
| } | ||
| } | ||
| headers() { | ||
| const h = { "Content-Type": "application/json" }; | ||
| if (this.key) h["Authorization"] = `Bearer ${this.key}`; | ||
| return h; | ||
| } | ||
| fullUrl(path) { | ||
| return `${this.url}/${this.version}${path}`; | ||
| } | ||
| // ── Auth ───────────────────────────────────────────────────────────── | ||
| async authenticate(token) { | ||
| this.auth.reset(); | ||
| if (token === null) { | ||
| this.key = null; | ||
| this.auth.resolveUnauthenticated(); | ||
| return { userId: null }; | ||
| } | ||
| this.key = token; | ||
| this.auth.resolve("sse-user"); | ||
| return { userId: "sse-user" }; | ||
| } | ||
| // ── Request/Response ────────────────────────────────────────────────── | ||
| async request(method, path, data, options) { | ||
| await this.loading; | ||
| const isGet = method.toUpperCase() === "GET"; | ||
| let url = this.fullUrl(path); | ||
| if (isGet && data) { | ||
| const params = new URLSearchParams(); | ||
| for (const [k, v] of Object.entries(data)) { | ||
| params.set(k, typeof v === "object" ? JSON.stringify(v) : String(v)); | ||
| } | ||
| url += `?${params.toString()}`; | ||
| } | ||
| const controller = options?.timeout ? new AbortController() : void 0; | ||
| const timer = options?.timeout ? setTimeout(() => controller.abort(), options.timeout) : void 0; | ||
| try { | ||
| const res = await fetch(url, { | ||
| method: method.toUpperCase(), | ||
| headers: this.headers(), | ||
| body: isGet ? void 0 : JSON.stringify(data), | ||
| signal: controller?.signal | ||
| }); | ||
| if (!res.ok) { | ||
| const body2 = await res.json().catch(() => ({ error: res.statusText })); | ||
| throw new Error(body2.error || body2.message || `HTTP ${res.status}`); | ||
| } | ||
| const body = await res.json(); | ||
| if (body.success === false) | ||
| throw new Error(body.error || "Request failed"); | ||
| return body.result ?? body; | ||
| } finally { | ||
| if (timer) clearTimeout(timer); | ||
| } | ||
| } | ||
| async get(path, data, options) { | ||
| return this.request("GET", path, data, options); | ||
| } | ||
| async post(path, data, options) { | ||
| return this.request("POST", path, data, options); | ||
| } | ||
| async put(path, data, options) { | ||
| return this.request("PUT", path, data, options); | ||
| } | ||
| async patch(path, data, options) { | ||
| return this.request("PATCH", path, data, options); | ||
| } | ||
| async delete(path, data, options) { | ||
| return this.request("DELETE", path, data, options); | ||
| } | ||
| // ── Subscriptions (via Server-Sent Events) ──────────────────────────── | ||
| subscribe(event, handler) { | ||
| const url = `${this.url}/${this.version}/__events/${encodeURIComponent(event)}`; | ||
| const source = new EventSource(url, { withCredentials: true }); | ||
| source.onmessage = (e) => { | ||
| try { | ||
| const data = JSON.parse(e.data); | ||
| handler(data); | ||
| } catch { | ||
| handler(e.data); | ||
| } | ||
| }; | ||
| source.onerror = () => { | ||
| }; | ||
| this.eventSources.set(event, source); | ||
| return () => { | ||
| source.close(); | ||
| this.eventSources.delete(event); | ||
| }; | ||
| } | ||
| unsubscribe(event) { | ||
| const source = this.eventSources.get(event); | ||
| if (source) { | ||
| source.close(); | ||
| this.eventSources.delete(event); | ||
| } | ||
| } | ||
| // ── Control messages ────────────────────────────────────────────────── | ||
| async send(event, ...args) { | ||
| await this.request("POST", "/__control", { event, args }); | ||
| } | ||
| // ── Lifecycle ───────────────────────────────────────────────────────── | ||
| disconnect() { | ||
| for (const [, source] of this.eventSources) source.close(); | ||
| this.eventSources.clear(); | ||
| this.isConnected = false; | ||
| this.emit("disconnected"); | ||
| } | ||
| async reconnect() { | ||
| this.loading = this.resolveKey(); | ||
| await this.loading; | ||
| } | ||
| }; | ||
| // src/client.ts | ||
| function createClient(config) { | ||
| const cacheKey = `${config.url}:${config.version ?? "v1"}`; | ||
| const existing = globalThis.__parcae_clients?.get(cacheKey); | ||
| if (existing) return existing; | ||
| const version = config.version ?? "v1"; | ||
| let transport; | ||
| if (config.transport && typeof config.transport === "object") { | ||
| transport = config.transport; | ||
| } else if (config.transport === "sse") { | ||
| transport = new SSETransport({ url: config.url, version }); | ||
| } else { | ||
| transport = new SocketTransport({ | ||
| url: config.url, | ||
| version, | ||
| token: config.token | ||
| }); | ||
| } | ||
| Model.use(new FrontendAdapter(transport)); | ||
| const client = { | ||
| transport, | ||
| get: (p, d, o) => transport.get(p, d, o), | ||
| post: (p, d, o) => transport.post(p, d, o), | ||
| put: (p, d, o) => transport.put(p, d, o), | ||
| patch: (p, d, o) => transport.patch(p, d, o), | ||
| delete: (p, d, o) => transport.delete(p, d, o), | ||
| subscribe: (e, h) => transport.subscribe?.(e, h) ?? (() => { | ||
| }), | ||
| unsubscribe: (e, h) => transport.unsubscribe?.(e, h), | ||
| send: (e, ...a) => transport.send?.(e, ...a), | ||
| get isConnected() { | ||
| return transport.isConnected ?? false; | ||
| }, | ||
| authenticate: (t) => transport.authenticate?.(t) ?? Promise.resolve({ userId: null }), | ||
| on: (e, h) => transport.on?.(e, h), | ||
| off: (e, h) => transport.off?.(e, h), | ||
| disconnect: () => transport.disconnect?.(), | ||
| reconnect: () => transport.reconnect?.() ?? Promise.resolve() | ||
| }; | ||
| if (!globalThis.__parcae_clients) { | ||
| globalThis.__parcae_clients = /* @__PURE__ */ new Map(); | ||
| } | ||
| globalThis.__parcae_clients.set(cacheKey, client); | ||
| return client; | ||
| } | ||
| export { __export, createClient, log }; | ||
| //# sourceMappingURL=chunk-B47IBEFK.js.map | ||
| //# sourceMappingURL=chunk-B47IBEFK.js.map |
| {"version":3,"sources":["../src/log.ts","../src/auth-gate.ts","../src/transports/socket.ts","../src/transports/sse.ts","../src/client.ts"],"names":["EventEmitter","body"],"mappings":"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","file":"chunk-B47IBEFK.js","sourcesContent":["/**\n * SDK logger — same format as backend.\n * Only logs in development. Silent in production.\n */\n\nconst isDev =\n typeof process !== \"undefined\" ? process.env.NODE_ENV !== \"production\" : true;\n\nfunction time(): string {\n const d = new Date();\n return `${String(d.getHours()).padStart(2, \"0\")}:${String(d.getMinutes()).padStart(2, \"0\")}:${String(d.getSeconds()).padStart(2, \"0\")}`;\n}\n\nexport const log = {\n warn: (...args: any[]) =>\n isDev && console.warn(`%c${time()} SDK`, \"color: #fb3\", ...args),\n error: (...args: any[]) =>\n console.error(`%c${time()} SDK`, \"color: #f44\", ...args),\n debug: (...args: any[]) =>\n isDev && console.debug(`%c${time()} SDK`, \"color: #888\", ...args),\n};\n","/**\n * AuthGate — auth state container with awaitable resolution.\n *\n * The transport writes to this directly. React reads via useAuthStatus()\n * which subscribes through gate.subscribe().\n */\n\nimport { log } from \"./log\";\n\nexport type AuthStatus = \"pending\" | \"authenticated\" | \"unauthenticated\";\n\nexport interface AuthState {\n status: AuthStatus;\n userId: string | null;\n version: number;\n}\n\nexport class AuthGate {\n /** Reactive state — plain object, mutated in place. */\n public state: AuthState = {\n status: \"pending\",\n userId: null,\n version: 0,\n };\n\n /** Awaitable — resolves when auth is confirmed (either way) */\n public ready: Promise<void>;\n\n private _resolve: (() => void) | null = null;\n private _listeners = new Set<() => void>();\n\n constructor() {\n this.ready = this._makePending();\n }\n\n /**\n * Subscribe to state changes. Returns an unsubscribe function.\n * The callback is invoked synchronously whenever resolve /\n * resolveUnauthenticated / reset mutates the state.\n */\n subscribe(fn: () => void): () => void {\n this._listeners.add(fn);\n return () => {\n this._listeners.delete(fn);\n };\n }\n\n /** Auth confirmed — user is authenticated */\n resolve(userId: string): void {\n this.state.status = \"authenticated\";\n log.debug(\"auth: authenticated, userId:\", userId);\n this.state.userId = userId;\n this.state.version++;\n this._resolve?.();\n this._resolve = null;\n this._notify();\n }\n\n /** Auth confirmed — no user */\n resolveUnauthenticated(): void {\n this.state.status = \"unauthenticated\";\n log.debug(\"auth: unauthenticated\");\n this.state.userId = null;\n this.state.version++;\n this._resolve?.();\n this._resolve = null;\n this._notify();\n }\n\n /** Reset to pending (disconnect, token change) */\n reset(): void {\n if (this.state.status !== \"pending\") {\n this.state.status = \"pending\";\n log.debug(\"auth: reset to pending\");\n this.state.userId = null;\n this.state.version++;\n this.ready = this._makePending();\n this._notify();\n }\n }\n\n private _notify(): void {\n for (const fn of this._listeners) fn();\n }\n\n private _makePending(): Promise<void> {\n return new Promise<void>((r) => {\n this._resolve = r;\n });\n }\n}\n","/**\n * SocketTransport — Socket.IO with Valtio-reactive AuthGate.\n *\n * Auth state lives in a Valtio proxy. React reads via useSnapshot().\n * Transport writes directly — no React involvement.\n */\n\nimport SocketIO from \"socket.io-client\";\nimport pako from \"pako\";\nimport { decompress } from \"compress-json\";\nimport { EventEmitter } from \"eventemitter3\";\nimport ShortId from \"short-unique-id\";\nimport type { Transport, RequestOptions } from \"@parcae/model\";\nimport { AuthGate } from \"../auth-gate\";\nimport { log } from \"../log\";\n\nconst DEFAULT_TIMEOUT = 30_000;\n\nconst uid = new ShortId({ length: 10 });\nconst SOCKETS = new Map<string, any>();\n\nexport interface SocketTransportConfig {\n url: string;\n version?: string;\n path?: string;\n token?: string | null;\n}\n\nexport class SocketTransport extends EventEmitter implements Transport {\n public auth = new AuthGate();\n public isConnected = false;\n\n private socket: any;\n private url: string;\n private version: string;\n private token: string | null | undefined;\n private inflight = new Map<string, Promise<any>>();\n\n constructor(config: SocketTransportConfig) {\n super();\n this.url = config.url;\n this.version = config.version ?? \"v1\";\n this.token = config.token;\n\n const socketPath = config.path ?? \"/ws\";\n const socketKey = `${this.url}:${socketPath}`;\n\n if (SOCKETS.has(socketKey)) {\n this.socket = SOCKETS.get(socketKey);\n } else {\n this.socket = SocketIO(this.url, {\n path: socketPath,\n transports: [\"websocket\"],\n withCredentials: true,\n });\n SOCKETS.set(socketKey, this.socket);\n }\n\n this.socket.on(\"connect\", () => {\n this.isConnected = true;\n log.debug(\"socket connected\");\n this._doAuth();\n this.emit(\"connected\");\n });\n\n this.socket.on(\"disconnect\", () => {\n this.isConnected = false;\n log.debug(\"socket disconnected\");\n this.auth.reset();\n this.emit(\"disconnected\");\n });\n\n this.socket.on(\"error\", (err: Error) => this.emit(\"error\", err));\n\n if (this.socket.connected) {\n this.isConnected = true;\n log.debug(\"socket connected\");\n this._doAuth();\n }\n\n if (this.token === null) {\n this.auth.resolveUnauthenticated();\n }\n }\n\n private _doAuth(): void {\n if (this.token === undefined) return;\n if (this.token === null) {\n this.auth.resolveUnauthenticated();\n return;\n }\n\n const t0 = performance.now();\n log.debug(\"authenticating...\");\n this.socket.emit(\"authenticate\", this.token, (response: any) => {\n const ms = (performance.now() - t0).toFixed(0);\n const userId = response?.userId ?? null;\n if (userId) {\n this.auth.resolve(userId);\n log.debug(`authenticated as ${userId} (${ms}ms)`);\n } else {\n this.auth.resolveUnauthenticated();\n log.debug(`auth rejected (${ms}ms)`);\n }\n });\n }\n\n async authenticate(token: string | null): Promise<{ userId: string | null }> {\n this.token = token;\n this.auth.reset();\n\n if (token === null) {\n this.auth.resolveUnauthenticated();\n return { userId: null };\n }\n\n // Wait for connection if not connected (with timeout)\n if (!this.socket.connected) {\n await new Promise<void>((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.socket.off(\"connect\", onConnect);\n this.auth.resolveUnauthenticated();\n reject(new Error(\"Authentication timeout: socket not connected\"));\n }, DEFAULT_TIMEOUT);\n const onConnect = () => {\n clearTimeout(timeout);\n resolve();\n };\n this.socket.once(\"connect\", onConnect);\n });\n }\n\n return new Promise((resolve) => {\n this.socket.emit(\"authenticate\", token, (response: any) => {\n const userId = response?.userId ?? null;\n if (userId) this.auth.resolve(userId);\n else this.auth.resolveUnauthenticated();\n resolve({ userId });\n });\n });\n }\n\n // ── Request/Response ──────────────────────────────────────────────\n\n private async fetch(\n method: string,\n path: string,\n data: any = {},\n options?: RequestOptions,\n ): Promise<any> {\n await this.auth.ready;\n\n if (!this.socket.connected) {\n await new Promise<void>((resolve, reject) => {\n if (this.socket.connected) return resolve();\n const timeout = setTimeout(() => {\n cleanup();\n reject(new Error(\"Connection timeout\"));\n }, DEFAULT_TIMEOUT);\n const onConnect = () => {\n cleanup();\n resolve();\n };\n const onError = (err: Error) => {\n cleanup();\n reject(err);\n };\n const cleanup = () => {\n clearTimeout(timeout);\n this.socket.off(\"connect\", onConnect);\n this.socket.off(\"connect_error\", onError);\n };\n this.socket.once(\"connect\", onConnect);\n this.socket.once(\"connect_error\", onError);\n });\n }\n\n const upper = method.toUpperCase();\n if (upper === \"GET\") {\n const dedupeKey = `${path}:${JSON.stringify(data)}`;\n const existing = this.inflight.get(dedupeKey);\n if (existing) return existing;\n const req = this._call(method, path, data, options);\n this.inflight.set(dedupeKey, req);\n req.finally(() => this.inflight.delete(dedupeKey));\n return req;\n }\n\n return this._call(method, path, data, options);\n }\n\n private _call(\n method: string,\n path: string,\n data: any,\n options?: RequestOptions,\n ): Promise<any> {\n const id = uid.rnd();\n const t0 = performance.now();\n const fullPath = `/${this.version}${path}`;\n const timeoutMs = options?.timeout ?? DEFAULT_TIMEOUT;\n log.debug(`→ ${method.toUpperCase()} ${fullPath}`);\n\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n this.socket.off(id);\n log.debug(\n `✗ ${method.toUpperCase()} ${fullPath} timeout (${(timeoutMs / 1000).toFixed(0)}s)`,\n );\n reject(new Error(`RPC timeout: ${method} ${path}`));\n }, timeoutMs);\n\n this.socket.once(id, (msg: any) => {\n clearTimeout(timeout);\n const ms = (performance.now() - t0).toFixed(0);\n try {\n const uncompressed = pako.ungzip(msg, { to: \"string\" });\n const parsed = decompress(JSON.parse(uncompressed));\n if (parsed.success) {\n log.debug(`← ${method.toUpperCase()} ${fullPath} (${ms}ms)`);\n resolve(parsed.result);\n } else {\n log.debug(\n `✗ ${method.toUpperCase()} ${fullPath} (${ms}ms) ${parsed.error || parsed.message}`,\n );\n reject(\n new Error(\n parsed.message || parsed.error || `${method} ${path} failed`,\n ),\n );\n }\n } catch (err) {\n log.debug(\n `✗ ${method.toUpperCase()} ${fullPath} (${ms}ms) parse error`,\n );\n reject(err);\n }\n });\n\n this.socket.emit(\n \"call\",\n id,\n method.toUpperCase(),\n `/${this.version}${path}`,\n data,\n );\n });\n }\n\n async get(path: string, data?: any, options?: RequestOptions): Promise<any> {\n return this.fetch(\"GET\", path, data, options);\n }\n async post(path: string, data?: any, options?: RequestOptions): Promise<any> {\n return this.fetch(\"POST\", path, data, options);\n }\n async put(path: string, data?: any, options?: RequestOptions): Promise<any> {\n return this.fetch(\"PUT\", path, data, options);\n }\n async patch(\n path: string,\n data?: any,\n options?: RequestOptions,\n ): Promise<any> {\n return this.fetch(\"PATCH\", path, data, options);\n }\n async delete(\n path: string,\n data?: any,\n options?: RequestOptions,\n ): Promise<any> {\n return this.fetch(\"DELETE\", path, data, options);\n }\n\n subscribe(event: string, handler: (...args: any[]) => void): () => void {\n this.socket.on(event, handler);\n return () => this.socket.off(event, handler);\n }\n\n unsubscribe(event: string, handler?: (...args: any[]) => void): void {\n this.socket.off(event, handler);\n }\n\n async send(event: string, ...args: any[]): Promise<void> {\n await this.auth.ready;\n this.socket.emit(event, ...args);\n }\n\n disconnect(): void {\n this.socket.disconnect();\n this.isConnected = false;\n log.debug(\"socket disconnected\");\n }\n async reconnect(): Promise<void> {\n this.socket.connect();\n }\n}\n\n/** @internal — clear socket cache (for testing) */\nexport function _resetSockets(): void {\n SOCKETS.clear();\n}\n","/**\n * SSETransport — HTTP + Server-Sent Events implementation of the Transport interface.\n *\n * Request/response via standard fetch(). Subscriptions via EventSource.\n * Simpler than Socket.IO — no websocket infra needed. Good for read-heavy\n * apps, serverless backends, or environments where WebSocket isn't available.\n *\n * Trade-offs vs SocketTransport:\n * - Simpler infra (no sticky sessions, works behind any CDN/proxy)\n * - Server → client streaming only (no client → server streaming)\n * - Request/response is standard HTTP (cacheable, observable, debuggable)\n * - No compression (relies on HTTP gzip)\n * - No request deduplication (relies on HTTP/2 multiplexing)\n */\n\nimport { EventEmitter } from \"eventemitter3\";\nimport type { Transport, RequestOptions } from \"@parcae/model\";\nimport { AuthGate } from \"../auth-gate\";\n\nexport interface SSETransportConfig {\n /** Base URL of the Parcae backend. */\n url: string;\n /** API key or async function returning a key. */\n key?: string | null | (() => Promise<string | null>);\n /** API version prefix. Default: \"v1\" */\n version?: string;\n}\n\nexport class SSETransport extends EventEmitter implements Transport {\n public auth = new AuthGate();\n\n private url: string;\n private version: string;\n private apiKey: string | null | (() => Promise<string | null>);\n private key: string | null = null;\n private eventSources = new Map<string, EventSource>();\n\n public isConnected = true; // HTTP is \"always connected\"\n public loading: Promise<void>;\n\n constructor(config: SSETransportConfig) {\n super();\n this.url = config.url.replace(/\\/$/, \"\");\n this.version = config.version ?? \"v1\";\n this.apiKey = config.key ?? null;\n this.loading = this.resolveKey();\n }\n\n private async resolveKey(): Promise<void> {\n try {\n this.key =\n typeof this.apiKey === \"function\" ? await this.apiKey() : this.apiKey;\n this.isConnected = true;\n this.emit(\"connected\");\n } catch (err) {\n this.emit(\"error\", err);\n }\n }\n\n private headers(): Record<string, string> {\n const h: Record<string, string> = { \"Content-Type\": \"application/json\" };\n if (this.key) h[\"Authorization\"] = `Bearer ${this.key}`;\n return h;\n }\n\n private fullUrl(path: string): string {\n return `${this.url}/${this.version}${path}`;\n }\n\n // ── Auth ─────────────────────────────────────────────────────────────\n\n async authenticate(\n token: string | null,\n ): Promise<{ userId: string | null }> {\n this.auth.reset();\n\n if (token === null) {\n this.key = null;\n this.auth.resolveUnauthenticated();\n return { userId: null };\n }\n\n // Set the bearer token for subsequent requests\n this.key = token;\n\n // Verify by hitting a lightweight endpoint (or just resolve as authenticated).\n // SSE doesn't have a socket handshake — the token is used per-request.\n // Resolve as authenticated; the server will reject individual requests\n // with 401 if the token is invalid.\n this.auth.resolve(\"sse-user\");\n return { userId: \"sse-user\" };\n }\n\n // ── Request/Response ──────────────────────────────────────────────────\n\n private async request(\n method: string,\n path: string,\n data?: any,\n options?: RequestOptions,\n ): Promise<any> {\n await this.loading;\n\n const isGet = method.toUpperCase() === \"GET\";\n let url = this.fullUrl(path);\n\n if (isGet && data) {\n const params = new URLSearchParams();\n for (const [k, v] of Object.entries(data)) {\n params.set(k, typeof v === \"object\" ? JSON.stringify(v) : String(v));\n }\n url += `?${params.toString()}`;\n }\n\n const controller = options?.timeout ? new AbortController() : undefined;\n const timer = options?.timeout\n ? setTimeout(() => controller!.abort(), options.timeout)\n : undefined;\n\n try {\n const res = await fetch(url, {\n method: method.toUpperCase(),\n headers: this.headers(),\n body: isGet ? undefined : JSON.stringify(data),\n signal: controller?.signal,\n });\n\n if (!res.ok) {\n const body = await res.json().catch(() => ({ error: res.statusText }));\n throw new Error(body.error || body.message || `HTTP ${res.status}`);\n }\n\n const body = await res.json();\n if (body.success === false)\n throw new Error(body.error || \"Request failed\");\n return body.result ?? body;\n } finally {\n if (timer) clearTimeout(timer);\n }\n }\n\n async get(path: string, data?: any, options?: RequestOptions): Promise<any> {\n return this.request(\"GET\", path, data, options);\n }\n async post(path: string, data?: any, options?: RequestOptions): Promise<any> {\n return this.request(\"POST\", path, data, options);\n }\n async put(path: string, data?: any, options?: RequestOptions): Promise<any> {\n return this.request(\"PUT\", path, data, options);\n }\n async patch(\n path: string,\n data?: any,\n options?: RequestOptions,\n ): Promise<any> {\n return this.request(\"PATCH\", path, data, options);\n }\n async delete(\n path: string,\n data?: any,\n options?: RequestOptions,\n ): Promise<any> {\n return this.request(\"DELETE\", path, data, options);\n }\n\n // ── Subscriptions (via Server-Sent Events) ────────────────────────────\n\n subscribe(event: string, handler: (...args: any[]) => void): () => void {\n const url = `${this.url}/${this.version}/__events/${encodeURIComponent(event)}`;\n const source = new EventSource(url, { withCredentials: true });\n\n source.onmessage = (e) => {\n try {\n const data = JSON.parse(e.data);\n handler(data);\n } catch {\n handler(e.data);\n }\n };\n\n source.onerror = () => {\n // EventSource auto-reconnects\n };\n\n this.eventSources.set(event, source);\n\n return () => {\n source.close();\n this.eventSources.delete(event);\n };\n }\n\n unsubscribe(event: string): void {\n const source = this.eventSources.get(event);\n if (source) {\n source.close();\n this.eventSources.delete(event);\n }\n }\n\n // ── Control messages ──────────────────────────────────────────────────\n\n async send(event: string, ...args: any[]): Promise<void> {\n await this.request(\"POST\", \"/__control\", { event, args });\n }\n\n // ── Lifecycle ─────────────────────────────────────────────────────────\n\n disconnect(): void {\n for (const [, source] of this.eventSources) source.close();\n this.eventSources.clear();\n this.isConnected = false;\n this.emit(\"disconnected\");\n }\n\n async reconnect(): Promise<void> {\n this.loading = this.resolveKey();\n await this.loading;\n }\n}\n","/**\n * @parcae/sdk — createClient()\n *\n * Auth is handled in the transport, not React.\n * Pass token at creation time, or call authenticate() later.\n */\n\nimport { Model, FrontendAdapter } from \"@parcae/model\";\nimport type { Transport, RequestOptions } from \"@parcae/model\";\nimport { SocketTransport } from \"./transports/socket\";\nimport { SSETransport } from \"./transports/sse\";\n\nexport interface ClientConfig {\n url: string;\n version?: string;\n transport?: \"socket\" | \"sse\" | Transport;\n /** Initial auth token. null = no auth. undefined = call authenticate() later. */\n token?: string | null;\n}\n\nexport interface ParcaeClient {\n transport: Transport;\n get(path: string, data?: any, options?: RequestOptions): Promise<any>;\n post(path: string, data?: any, options?: RequestOptions): Promise<any>;\n put(path: string, data?: any, options?: RequestOptions): Promise<any>;\n patch(path: string, data?: any, options?: RequestOptions): Promise<any>;\n delete(path: string, data?: any, options?: RequestOptions): Promise<any>;\n subscribe(event: string, handler: (...args: any[]) => void): () => void;\n unsubscribe(event: string, handler?: (...args: any[]) => void): void;\n send(event: string, ...args: any[]): void;\n readonly isConnected: boolean;\n authenticate(token: string | null): Promise<{ userId: string | null }>;\n on(event: string, handler: (...args: any[]) => void): void;\n off(event: string, handler?: (...args: any[]) => void): void;\n disconnect(): void;\n reconnect(): Promise<void>;\n}\n\nexport function createClient(config: ClientConfig): ParcaeClient {\n // Idempotent — return existing client if already created for this url\n const cacheKey = `${config.url}:${config.version ?? \"v1\"}`;\n const existing = (globalThis as any).__parcae_clients?.get(cacheKey);\n if (existing) return existing;\n\n const version = config.version ?? \"v1\";\n\n let transport: any;\n\n if (config.transport && typeof config.transport === \"object\") {\n transport = config.transport;\n } else if (config.transport === \"sse\") {\n transport = new SSETransport({ url: config.url, version });\n } else {\n transport = new SocketTransport({\n url: config.url,\n version,\n token: config.token,\n });\n }\n\n Model.use(new FrontendAdapter(transport));\n\n const client: ParcaeClient = {\n transport,\n get: (p, d, o) => transport.get(p, d, o),\n post: (p, d, o) => transport.post(p, d, o),\n put: (p, d, o) => transport.put(p, d, o),\n patch: (p, d, o) => transport.patch(p, d, o),\n delete: (p, d, o) => transport.delete(p, d, o),\n subscribe: (e, h) => transport.subscribe?.(e, h) ?? (() => {}),\n unsubscribe: (e, h) => transport.unsubscribe?.(e, h),\n send: (e, ...a) => transport.send?.(e, ...a),\n get isConnected() {\n return transport.isConnected ?? false;\n },\n authenticate: (t) =>\n transport.authenticate?.(t) ?? Promise.resolve({ userId: null }),\n on: (e, h) => transport.on?.(e, h),\n off: (e, h) => transport.off?.(e, h),\n disconnect: () => transport.disconnect?.(),\n reconnect: () => transport.reconnect?.() ?? Promise.resolve(),\n };\n\n // Cache the client globally\n if (!(globalThis as any).__parcae_clients) {\n (globalThis as any).__parcae_clients = new Map();\n }\n (globalThis as any).__parcae_clients.set(cacheKey, client);\n\n return client;\n}\n"]} |
+35
-85
@@ -1,103 +0,53 @@ | ||
| import { A as AuthGate } from './auth-adapter-D7KNjvNB.js'; | ||
| export { a as AuthClientAdapter, b as AuthState, c as AuthStatus, C as ClientConfig, P as ParcaeClient, d as createClient } from './auth-adapter-D7KNjvNB.js'; | ||
| import { EventEmitter } from 'eventemitter3'; | ||
| import { Transport } from '@parcae/model'; | ||
| export { FrontendAdapter, Model, Transport } from '@parcae/model'; | ||
| import { Transport, RequestOptions } from '@parcae/model'; | ||
| /** | ||
| * SocketTransport — Socket.IO with Valtio-reactive AuthGate. | ||
| * Client-side auth adapter interface. | ||
| * | ||
| * Auth state lives in a Valtio proxy. React reads via useSnapshot(). | ||
| * Transport writes directly — no React involvement. | ||
| * Parcae calls this internally to resolve sessions. | ||
| * The adapter receives the base URL from ParcaeProvider — no config needed. | ||
| */ | ||
| interface SocketTransportConfig { | ||
| url: string; | ||
| version?: string; | ||
| path?: string; | ||
| token?: string | null; | ||
| interface AuthClientAdapter { | ||
| /** Initialize with the Parcae backend URL. Called by ParcaeProvider. */ | ||
| init(baseUrl: string): void; | ||
| /** Get the current session token. null = no session. */ | ||
| getToken(): Promise<string | null>; | ||
| /** Subscribe to session changes. Returns unsubscribe. */ | ||
| onChange(callback: (token: string | null) => void): () => void; | ||
| } | ||
| declare class SocketTransport extends EventEmitter implements Transport { | ||
| auth: AuthGate; | ||
| isConnected: boolean; | ||
| private socket; | ||
| private url; | ||
| private version; | ||
| private token; | ||
| private inflight; | ||
| constructor(config: SocketTransportConfig); | ||
| private _doAuth; | ||
| authenticate(token: string | null): Promise<{ | ||
| userId: string | null; | ||
| }>; | ||
| private fetch; | ||
| private _call; | ||
| get(path: string, data?: any): Promise<any>; | ||
| post(path: string, data?: any): Promise<any>; | ||
| put(path: string, data?: any): Promise<any>; | ||
| patch(path: string, data?: any): Promise<any>; | ||
| delete(path: string, data?: any): Promise<any>; | ||
| /** The underlying socket ID (available after connect). */ | ||
| get socketId(): string | null; | ||
| subscribe(event: string, handler: (...args: any[]) => void): () => void; | ||
| unsubscribe(event: string, handler?: (...args: any[]) => void): void; | ||
| send(event: string, ...args: any[]): Promise<void>; | ||
| /** | ||
| * Emit a socket event with a callback (request-response pattern). | ||
| * Waits for auth and connection before emitting. | ||
| */ | ||
| emitWithCallback<T = any>(event: string, data: any): Promise<T>; | ||
| disconnect(): void; | ||
| reconnect(): Promise<void>; | ||
| } | ||
| /** | ||
| * SSETransport — HTTP + Server-Sent Events implementation of the Transport interface. | ||
| * @parcae/sdk — createClient() | ||
| * | ||
| * Request/response via standard fetch(). Subscriptions via EventSource. | ||
| * Simpler than Socket.IO — no websocket infra needed. Good for read-heavy | ||
| * apps, serverless backends, or environments where WebSocket isn't available. | ||
| * | ||
| * Trade-offs vs SocketTransport: | ||
| * - Simpler infra (no sticky sessions, works behind any CDN/proxy) | ||
| * - Server → client streaming only (no client → server streaming) | ||
| * - Request/response is standard HTTP (cacheable, observable, debuggable) | ||
| * - No compression (relies on HTTP gzip) | ||
| * - No request deduplication (relies on HTTP/2 multiplexing) | ||
| * Auth is handled in the transport, not React. | ||
| * Pass token at creation time, or call authenticate() later. | ||
| */ | ||
| interface SSETransportConfig { | ||
| /** Base URL of the Parcae backend. */ | ||
| interface ClientConfig { | ||
| url: string; | ||
| /** API key or async function returning a key. */ | ||
| key?: string | null | (() => Promise<string | null>); | ||
| /** API version prefix. Default: "v1" */ | ||
| version?: string; | ||
| transport?: "socket" | "sse" | Transport; | ||
| /** Initial auth token. null = no auth. undefined = call authenticate() later. */ | ||
| token?: string | null; | ||
| } | ||
| declare class SSETransport extends EventEmitter implements Transport { | ||
| private url; | ||
| private version; | ||
| private apiKey; | ||
| private key; | ||
| private eventSources; | ||
| isConnected: boolean; | ||
| isLoading: boolean; | ||
| loading: Promise<void>; | ||
| constructor(config: SSETransportConfig); | ||
| private resolveKey; | ||
| private headers; | ||
| private fullUrl; | ||
| private request; | ||
| get(path: string, data?: any): Promise<any>; | ||
| post(path: string, data?: any): Promise<any>; | ||
| put(path: string, data?: any): Promise<any>; | ||
| patch(path: string, data?: any): Promise<any>; | ||
| delete(path: string, data?: any): Promise<any>; | ||
| interface ParcaeClient { | ||
| transport: Transport; | ||
| get(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| post(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| put(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| patch(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| delete(path: string, data?: any, options?: RequestOptions): Promise<any>; | ||
| subscribe(event: string, handler: (...args: any[]) => void): () => void; | ||
| unsubscribe(event: string): void; | ||
| send(event: string, ...args: any[]): Promise<void>; | ||
| unsubscribe(event: string, handler?: (...args: any[]) => void): void; | ||
| send(event: string, ...args: any[]): void; | ||
| readonly isConnected: boolean; | ||
| authenticate(token: string | null): Promise<{ | ||
| userId: string | null; | ||
| }>; | ||
| on(event: string, handler: (...args: any[]) => void): void; | ||
| off(event: string, handler?: (...args: any[]) => void): void; | ||
| disconnect(): void; | ||
| reconnect(): Promise<void>; | ||
| } | ||
| declare function createClient(config: ClientConfig): ParcaeClient; | ||
| export { AuthGate, SSETransport, type SSETransportConfig, SocketTransport, type SocketTransportConfig }; | ||
| export { type AuthClientAdapter, type ClientConfig, type ParcaeClient, createClient }; |
+1
-2
@@ -1,4 +0,3 @@ | ||
| export { AuthGate, SSETransport, SocketTransport, createClient } from './chunk-USSSG27K.js'; | ||
| export { FrontendAdapter, Model } from '@parcae/model'; | ||
| export { createClient } from './chunk-B47IBEFK.js'; | ||
| //# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js","sourcesContent":[]} | ||
| {"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"} |
+38
-26
@@ -1,5 +0,5 @@ | ||
| import React$1 from 'react'; | ||
| import { P as ParcaeClient, a as AuthClientAdapter, C as ClientConfig, c as AuthStatus } from '../auth-adapter-D7KNjvNB.js'; | ||
| import React from 'react'; | ||
| import { ParcaeClient, AuthClientAdapter, ClientConfig } from '../index.js'; | ||
| import * as _parcae_model from '@parcae/model'; | ||
| import * as react_jsx_runtime from 'react/jsx-runtime'; | ||
| import '@parcae/model'; | ||
@@ -15,17 +15,10 @@ interface ParcaeProviderProps { | ||
| transport?: ClientConfig["transport"]; | ||
| children: React$1.ReactNode; | ||
| children: React.ReactNode; | ||
| onReady?: (client: ParcaeClient) => void; | ||
| onError?: (error: Error) => void; | ||
| } | ||
| declare const ParcaeProvider: React$1.FC<ParcaeProviderProps>; | ||
| declare const ParcaeProvider: React.FC<ParcaeProviderProps>; | ||
| declare const ParcaeContext: React.Context<ParcaeClient | null>; | ||
| declare function useParcae(): ParcaeClient; | ||
| interface QueryChain<T> { | ||
| find(): Promise<T[]>; | ||
| findAndSubscribe(): Promise<{ | ||
| items: T[]; | ||
| hash: string | null; | ||
| }>; | ||
| __steps?: any[]; | ||
@@ -46,10 +39,17 @@ __modelType?: string; | ||
| declare function useQuery<T>(chain: QueryChain<T> | null | undefined, options?: UseQueryOptions): UseQueryResult<T>; | ||
| declare function clearQueryCache(): void; | ||
| /** | ||
| * AuthGate — auth state container with awaitable resolution. | ||
| * | ||
| * The transport writes to this directly. React reads via useAuthStatus() | ||
| * which subscribes through gate.subscribe(). | ||
| */ | ||
| type AuthStatus = "pending" | "authenticated" | "unauthenticated"; | ||
| declare function useApi(): { | ||
| get: (path: string, data?: any) => Promise<any>; | ||
| post: (path: string, data?: any) => Promise<any>; | ||
| put: (path: string, data?: any) => Promise<any>; | ||
| patch: (path: string, data?: any) => Promise<any>; | ||
| delete: (path: string, data?: any) => Promise<any>; | ||
| get: (path: string, data?: any, options?: _parcae_model.RequestOptions) => Promise<any>; | ||
| post: (path: string, data?: any, options?: _parcae_model.RequestOptions) => Promise<any>; | ||
| put: (path: string, data?: any, options?: _parcae_model.RequestOptions) => Promise<any>; | ||
| patch: (path: string, data?: any, options?: _parcae_model.RequestOptions) => Promise<any>; | ||
| delete: (path: string, data?: any, options?: _parcae_model.RequestOptions) => Promise<any>; | ||
| }; | ||
@@ -61,8 +61,13 @@ declare function useSDK(): ParcaeClient; | ||
| }; | ||
| declare function useAuthState(): { | ||
| status: AuthStatus; | ||
| userId: string | null; | ||
| version: number; | ||
| }; | ||
| interface SocketHook { | ||
| /** Emit a Socket.IO event to the server. */ | ||
| emit: (event: string, ...args: any[]) => void; | ||
| /** Listen for a Socket.IO event. Returns an unsubscribe function. */ | ||
| on: (event: string, handler: (...args: any[]) => void) => () => void; | ||
| /** Remove a specific listener for a Socket.IO event. */ | ||
| off: (event: string, handler?: (...args: any[]) => void) => void; | ||
| } | ||
| declare function useSocket(): SocketHook; | ||
| declare function useSetting<T = string>(key: string, defaultValue: T): [T, (value: T) => Promise<void>, { | ||
@@ -72,5 +77,12 @@ isLoading: boolean; | ||
| interface AuthSnap { | ||
| status: AuthStatus; | ||
| userId: string | null; | ||
| version: number; | ||
| } | ||
| declare function useAuthStatus(): AuthSnap; | ||
| interface GateProps { | ||
| children: React$1.ReactNode; | ||
| fallback?: React$1.ReactNode; | ||
| children: React.ReactNode; | ||
| fallback?: React.ReactNode; | ||
| } | ||
@@ -81,2 +93,2 @@ declare function Authenticated({ children, fallback }: GateProps): react_jsx_runtime.JSX.Element; | ||
| export { AuthLoading, Authenticated, ParcaeContext, ParcaeProvider, type ParcaeProviderProps, Unauthenticated, clearQueryCache, useApi, useAuthState, useConnectionStatus, useParcae, useQuery, useSDK, useSetting }; | ||
| export { AuthLoading, Authenticated, ParcaeProvider, type SocketHook, Unauthenticated, useApi, useAuthStatus, useConnectionStatus, useQuery, useSDK, useSetting, useSocket }; |
+285
-147
@@ -1,4 +0,5 @@ | ||
| import { __export, createClient, log } from '../chunk-USSSG27K.js'; | ||
| import { createContext, useContext, useMemo, useRef, useEffect, useSyncExternalStore, useState, useCallback } from 'react'; | ||
| import { __export, createClient, log } from '../chunk-B47IBEFK.js'; | ||
| import { createContext, useMemo, useRef, useEffect, useCallback, useSyncExternalStore, useState, useContext } from 'react'; | ||
| import { jsx, Fragment } from 'react/jsx-runtime'; | ||
| import { SYM_SERVER_MERGE, Model } from '@parcae/model'; | ||
@@ -724,2 +725,11 @@ var KEY = "__parcae_context"; | ||
| }); | ||
| var DEFAULT_SNAP = { status: "pending", userId: null, version: 0 }; | ||
| function readState(gate) { | ||
| const s = gate.state; | ||
| return { | ||
| status: s.status, | ||
| userId: s.userId, | ||
| version: s.version | ||
| }; | ||
| } | ||
| function useAuthStatus() { | ||
@@ -729,19 +739,24 @@ const client = useParcae(); | ||
| const gate = transport?.auth; | ||
| const [, forceRender] = useState(0); | ||
| useEffect(() => { | ||
| if (!gate) return; | ||
| let mounted = true; | ||
| const check = () => { | ||
| if (mounted) forceRender((n) => n + 1); | ||
| }; | ||
| gate.ready.then(check); | ||
| return () => { | ||
| mounted = false; | ||
| }; | ||
| }, [gate, gate?.state?.version]); | ||
| return { | ||
| status: gate?.state?.status ?? "pending", | ||
| userId: gate?.state?.userId ?? null, | ||
| version: gate?.state?.version ?? 0 | ||
| }; | ||
| const ref = useRef(gate ? readState(gate) : DEFAULT_SNAP); | ||
| const subscribe = useCallback( | ||
| (onChange) => { | ||
| if (!gate) return () => { | ||
| }; | ||
| return gate.subscribe(() => { | ||
| ref.current = readState(gate); | ||
| onChange(); | ||
| }); | ||
| }, | ||
| [gate] | ||
| ); | ||
| const getSnapshot = useCallback(() => { | ||
| if (gate) { | ||
| const s = gate.state; | ||
| if (ref.current.version !== s.version || ref.current.status !== s.status) { | ||
| ref.current = readState(gate); | ||
| } | ||
| } | ||
| return ref.current; | ||
| }, [gate]); | ||
| return useSyncExternalStore(subscribe, getSnapshot, getSnapshot); | ||
| } | ||
@@ -753,3 +768,16 @@ | ||
| var EMPTY = []; | ||
| function getOrCreate(key, modelClass) { | ||
| var INITIAL_HASH = "L"; | ||
| var MAX_RETRIES = 3; | ||
| var RETRY_DELAYS = [1e3, 3e3, 1e4]; | ||
| function buildHash(e) { | ||
| if (e.loading) return "L"; | ||
| if (e.error) return `E:${e.error.message}`; | ||
| let h = `D:v${e.version}:`; | ||
| for (let i = 0; i < e.items.length; i++) { | ||
| if (i > 0) h += ","; | ||
| h += e.items[i]?.id ?? i; | ||
| } | ||
| return h; | ||
| } | ||
| function getOrCreate(key) { | ||
| let e = cache.get(key); | ||
@@ -759,5 +787,6 @@ if (!e) { | ||
| items: EMPTY, | ||
| itemMap: /* @__PURE__ */ new Map(), | ||
| loading: true, | ||
| error: null, | ||
| hash: INITIAL_HASH, | ||
| version: 0, | ||
| refs: 0, | ||
@@ -767,4 +796,7 @@ listeners: /* @__PURE__ */ new Set(), | ||
| gcTimer: null, | ||
| hash: null, | ||
| modelClass: modelClass ?? null | ||
| queryHash: null, | ||
| chain: null, | ||
| client: null, | ||
| retryCount: 0, | ||
| retryTimer: null | ||
| }; | ||
@@ -776,35 +808,146 @@ cache.set(key, e); | ||
| function notify(e) { | ||
| for (const fn of e.listeners) fn(); | ||
| const next = buildHash(e); | ||
| if (next !== e.hash) { | ||
| e.hash = next; | ||
| for (const fn of e.listeners) fn(); | ||
| } | ||
| } | ||
| function doFetchAndSubscribe(key, entry, chain, client) { | ||
| function applyOps(items, ops, modelClass, adapter) { | ||
| if (ops.length === 0) return { items, changed: false }; | ||
| const addOps = /* @__PURE__ */ new Map(); | ||
| const updateOps = /* @__PURE__ */ new Map(); | ||
| const removeIds = /* @__PURE__ */ new Set(); | ||
| for (const op of ops) { | ||
| switch (op.op) { | ||
| case "add": | ||
| if (op.data) addOps.set(op.id, op.data); | ||
| break; | ||
| case "update": | ||
| if (op.patch) { | ||
| const existing = updateOps.get(op.id); | ||
| if (existing) { | ||
| existing.push(...op.patch); | ||
| } else { | ||
| updateOps.set(op.id, [...op.patch]); | ||
| } | ||
| } | ||
| break; | ||
| case "remove": | ||
| removeIds.add(op.id); | ||
| break; | ||
| } | ||
| } | ||
| const byId = /* @__PURE__ */ new Map(); | ||
| for (const item of items) byId.set(item.id, item); | ||
| const hasRemoves = removeIds.size > 0; | ||
| const hasAdds = addOps.size > 0; | ||
| const updated = /* @__PURE__ */ new Map(); | ||
| for (const [id, patches] of updateOps) { | ||
| const existing = byId.get(id); | ||
| if (!existing) continue; | ||
| const snapshot = JSON.parse(JSON.stringify(existing.__data ?? {})); | ||
| applyPatch(snapshot, patches, false, true); | ||
| const serverMerge = existing[SYM_SERVER_MERGE]; | ||
| if (serverMerge) { | ||
| updated.set(id, serverMerge(snapshot)); | ||
| } else { | ||
| for (const [k, v] of Object.entries(snapshot)) { | ||
| existing[k] = v; | ||
| } | ||
| } | ||
| } | ||
| const hasRelevantUpdates = updated.size > 0; | ||
| if (!hasRemoves && !hasAdds && !hasRelevantUpdates) { | ||
| return { items, changed: false }; | ||
| } | ||
| const result = []; | ||
| for (const item of items) { | ||
| const id = item.id; | ||
| if (removeIds.has(id)) continue; | ||
| result.push(updated.get(id) ?? item); | ||
| } | ||
| for (const [id, data] of addOps) { | ||
| if (!byId.has(id)) { | ||
| result.push(new modelClass(adapter, data)); | ||
| } | ||
| } | ||
| return { items: result, changed: true }; | ||
| } | ||
| function reconcile(prev, next) { | ||
| if (prev === EMPTY || prev.length === 0) return next; | ||
| const prevById = /* @__PURE__ */ new Map(); | ||
| for (const item of prev) prevById.set(item.id, item); | ||
| let changed = false; | ||
| const result = new Array(next.length); | ||
| for (let i = 0; i < next.length; i++) { | ||
| const fresh = next[i]; | ||
| const existing = prevById.get(fresh.id); | ||
| if (existing) { | ||
| const serverMerge = existing[SYM_SERVER_MERGE]; | ||
| if (serverMerge) { | ||
| const freshData = fresh.__data ?? fresh; | ||
| result[i] = serverMerge(freshData); | ||
| changed = true; | ||
| } else { | ||
| result[i] = existing; | ||
| } | ||
| if (!changed && prev[i]?.id !== fresh.id) changed = true; | ||
| } else { | ||
| result[i] = fresh; | ||
| changed = true; | ||
| } | ||
| } | ||
| if (!changed && prev.length === next.length) return prev; | ||
| return result; | ||
| } | ||
| function resolveAdapter(chain) { | ||
| return chain.__adapter ?? (Model.hasAdapter() ? Model.getAdapter() : null); | ||
| } | ||
| function scheduleRetry(key, entry) { | ||
| if (entry.retryCount >= MAX_RETRIES) return; | ||
| if (!entry.chain || !entry.client) return; | ||
| if (entry.refs <= 0) return; | ||
| const delay = RETRY_DELAYS[Math.min(entry.retryCount, RETRY_DELAYS.length - 1)]; | ||
| log.debug(`useQuery: scheduling retry ${entry.retryCount + 1}/${MAX_RETRIES} in ${delay}ms`); | ||
| entry.retryTimer = setTimeout(() => { | ||
| entry.retryTimer = null; | ||
| if (entry.refs <= 0 || !entry.chain || !entry.client) return; | ||
| entry.retryCount++; | ||
| doFetch(key, entry, entry.chain, entry.client); | ||
| }, delay); | ||
| } | ||
| function doFetch(key, entry, chain, client) { | ||
| log.debug("useQuery: fetching", chain.__modelType); | ||
| entry.loading = true; | ||
| entry.chain = chain; | ||
| entry.client = client; | ||
| if (entry.items === EMPTY) { | ||
| entry.loading = true; | ||
| } | ||
| entry.error = null; | ||
| notify(entry); | ||
| chain.findAndSubscribe().then(({ items, hash }) => { | ||
| log.debug("useQuery: got", items.length, "items, hash:", hash); | ||
| entry.items = items; | ||
| entry.itemMap = /* @__PURE__ */ new Map(); | ||
| for (const item of items) { | ||
| const id = item.id; | ||
| if (id) entry.itemMap.set(id, item); | ||
| chain.find().then((result) => { | ||
| log.debug("useQuery: got", result.length, "items for", chain.__modelType); | ||
| entry.items = reconcile(entry.items, result); | ||
| entry.loading = false; | ||
| entry.retryCount = 0; | ||
| if (entry.retryTimer) { | ||
| clearTimeout(entry.retryTimer); | ||
| entry.retryTimer = null; | ||
| } | ||
| entry.loading = false; | ||
| entry.hash = hash; | ||
| const hash = result.__queryHash; | ||
| if (hash && hash !== entry.queryHash) { | ||
| entry.dispose?.(); | ||
| entry.queryHash = hash; | ||
| const adapter = resolveAdapter(chain); | ||
| const unsub = client.subscribe(`query:${hash}`, (ops) => { | ||
| if (!Array.isArray(ops) || ops.length === 0) return; | ||
| const result2 = applyOps(entry.items, ops, chain.__modelClass, adapter); | ||
| if (!result2.changed) return; | ||
| entry.items = result2.items; | ||
| entry.version++; | ||
| notify(entry); | ||
| }); | ||
| entry.dispose = unsub; | ||
| } | ||
| notify(entry); | ||
| if (hash) { | ||
| const diffHandler = (ops) => { | ||
| if (!Array.isArray(ops)) return; | ||
| applyOps(entry, ops); | ||
| }; | ||
| const unsubDiff = client.subscribe(`query:${hash}`, diffHandler); | ||
| const prevDispose = entry.dispose; | ||
| entry.dispose = () => { | ||
| unsubDiff(); | ||
| client.send?.("unsubscribe:query", hash); | ||
| entry.hash = null; | ||
| entry.dispose = null; | ||
| prevDispose?.(); | ||
| }; | ||
| } | ||
| }).catch((err) => { | ||
@@ -815,61 +958,24 @@ log.error("useQuery: error", err.message); | ||
| notify(entry); | ||
| scheduleRetry(key, entry); | ||
| }); | ||
| } | ||
| function applyOps(entry, ops) { | ||
| let changed = false; | ||
| const adapter = entry.items[0]?.[/* @__PURE__ */ Symbol.for?.("parcae:adapter")] ?? null; | ||
| for (const op of ops) { | ||
| switch (op.op) { | ||
| case "add": { | ||
| if (!entry.itemMap.has(op.id) && entry.modelClass && adapter) { | ||
| const instance = new entry.modelClass(adapter, op.data); | ||
| entry.itemMap.set(op.id, instance); | ||
| changed = true; | ||
| } | ||
| break; | ||
| } | ||
| case "remove": { | ||
| if (entry.itemMap.has(op.id)) { | ||
| entry.itemMap.delete(op.id); | ||
| changed = true; | ||
| } | ||
| break; | ||
| } | ||
| case "patch": { | ||
| const existing = entry.itemMap.get(op.id); | ||
| if (existing && op.patch?.length > 0) { | ||
| try { | ||
| const data = existing.__data; | ||
| applyPatch(data, op.patch, false, true); | ||
| for (const patchOp of op.patch) { | ||
| const key = patchOp.path?.split("/")?.[1]; | ||
| if (key && key in data) existing[key] = data[key]; | ||
| } | ||
| changed = true; | ||
| } catch (err) { | ||
| log.error("useQuery: patch apply failed", err); | ||
| } | ||
| } | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| if (changed) { | ||
| entry.items = [...entry.itemMap.values()]; | ||
| notify(entry); | ||
| } | ||
| } | ||
| function useQuery(chain, options = {}) { | ||
| const client = useParcae(); | ||
| const waitForAuth = options.waitForAuth ?? true; | ||
| const { status: authStatus, version: authVersion } = useAuthStatus(); | ||
| const { status: authStatus, userId } = useAuthStatus(); | ||
| const authReady = !waitForAuth || authStatus !== "pending"; | ||
| const key = chain && authReady ? `${chain.__modelType}:${authVersion}:${JSON.stringify(chain.__steps ?? [])}` : null; | ||
| const liveKey = chain && authReady ? `${chain.__modelType}:${userId ?? "anon"}:${JSON.stringify(chain.__steps ?? [])}` : null; | ||
| const lastKeyRef = useRef(null); | ||
| if (liveKey !== null) lastKeyRef.current = liveKey; | ||
| const key = liveKey ?? lastKeyRef.current; | ||
| const chainRef = useRef(chain); | ||
| chainRef.current = chain; | ||
| const clientRef = useRef(client); | ||
| clientRef.current = client; | ||
| const keyRef = useRef(key); | ||
| keyRef.current = key; | ||
| const subscribe = (onChange) => { | ||
| const k = keyRef.current; | ||
| if (!k) return () => { | ||
| const subscribe = useCallback((onChange) => { | ||
| if (!key) return () => { | ||
| }; | ||
| const e = getOrCreate(k, chain?.__modelClass); | ||
| const e = getOrCreate(key); | ||
| e.refs++; | ||
@@ -885,47 +991,66 @@ e.listeners.add(onChange); | ||
| if (e.refs <= 0) { | ||
| if (e.retryTimer) { | ||
| clearTimeout(e.retryTimer); | ||
| e.retryTimer = null; | ||
| } | ||
| e.gcTimer = setTimeout(() => { | ||
| e.dispose?.(); | ||
| cache.delete(k); | ||
| cache.delete(key); | ||
| }, GC_DELAY); | ||
| } | ||
| }; | ||
| }; | ||
| const getSnapshot = () => { | ||
| const k = keyRef.current; | ||
| if (!k) return EMPTY; | ||
| return cache.get(k)?.items ?? EMPTY; | ||
| }; | ||
| const items = useSyncExternalStore( | ||
| subscribe, | ||
| getSnapshot, | ||
| getSnapshot | ||
| ); | ||
| }, [key]); | ||
| const getSnapshot = useCallback(() => { | ||
| if (!key) return INITIAL_HASH; | ||
| return cache.get(key)?.hash ?? INITIAL_HASH; | ||
| }, [key]); | ||
| useSyncExternalStore(subscribe, getSnapshot, getSnapshot); | ||
| useEffect(() => { | ||
| if (!key || !chain) return; | ||
| const entry2 = getOrCreate(key, chain.__modelClass); | ||
| if (entry2.items === EMPTY && !entry2.dispose) { | ||
| doFetchAndSubscribe(key, entry2, chain, client); | ||
| if (!key) return; | ||
| const currentChain = chainRef.current; | ||
| if (!currentChain) return; | ||
| const entry2 = getOrCreate(key); | ||
| entry2.retryCount = 0; | ||
| if (entry2.retryTimer) { | ||
| clearTimeout(entry2.retryTimer); | ||
| entry2.retryTimer = null; | ||
| } | ||
| if (entry2.items === EMPTY && !entry2.loading) { | ||
| doFetch(key, entry2, currentChain, clientRef.current); | ||
| } else if (entry2.items === EMPTY && entry2.error === null && !entry2.dispose) { | ||
| doFetch(key, entry2, currentChain, clientRef.current); | ||
| } | ||
| }, [key]); | ||
| useEffect(() => { | ||
| const onReconnect = () => { | ||
| if (!cache.has(key)) return; | ||
| const e = cache.get(key); | ||
| e.dispose?.(); | ||
| e.loading = true; | ||
| notify(e); | ||
| doFetchAndSubscribe(key, e, chain, client); | ||
| const k = keyRef.current; | ||
| const currentChain = chainRef.current; | ||
| if (!k || !currentChain) return; | ||
| const entry2 = cache.get(k); | ||
| if (!entry2) return; | ||
| entry2.retryCount = 0; | ||
| if (entry2.retryTimer) { | ||
| clearTimeout(entry2.retryTimer); | ||
| entry2.retryTimer = null; | ||
| } | ||
| doFetch(k, entry2, currentChain, clientRef.current); | ||
| }; | ||
| client.on?.("connected", onReconnect); | ||
| client.on("connected", onReconnect); | ||
| return () => { | ||
| client.off?.("connected", onReconnect); | ||
| client.off("connected", onReconnect); | ||
| }; | ||
| }, [key]); | ||
| const refetch = () => { | ||
| if (!key || !chain) return; | ||
| const entry2 = getOrCreate(key, chain.__modelClass); | ||
| entry2.dispose?.(); | ||
| entry2.loading = true; | ||
| notify(entry2); | ||
| doFetchAndSubscribe(key, entry2, chain, client); | ||
| }; | ||
| if (!key) { | ||
| }, [client]); | ||
| const refetch = useCallback(() => { | ||
| const k = keyRef.current; | ||
| const currentChain = chainRef.current; | ||
| if (!k || !currentChain) return; | ||
| const entry2 = getOrCreate(k); | ||
| entry2.retryCount = 0; | ||
| if (entry2.retryTimer) { | ||
| clearTimeout(entry2.retryTimer); | ||
| entry2.retryTimer = null; | ||
| } | ||
| doFetch(k, entry2, currentChain, clientRef.current); | ||
| }, []); | ||
| if (!key) | ||
| return { | ||
@@ -938,6 +1063,5 @@ items: EMPTY, | ||
| }; | ||
| } | ||
| const entry = cache.get(key); | ||
| return { | ||
| items, | ||
| items: entry?.items ?? EMPTY, | ||
| loading: entry?.loading ?? true, | ||
@@ -948,9 +1072,2 @@ error: entry?.error ?? null, | ||
| } | ||
| function clearQueryCache() { | ||
| for (const [, entry] of cache) { | ||
| entry.dispose?.(); | ||
| if (entry.gcTimer) clearTimeout(entry.gcTimer); | ||
| } | ||
| cache.clear(); | ||
| } | ||
| function useApi() { | ||
@@ -975,6 +1092,27 @@ const client = useParcae(); | ||
| const { status } = useAuthStatus(); | ||
| return { isConnected: client.isConnected, authStatus: status }; | ||
| const subscribe = useCallback( | ||
| (onChange) => { | ||
| client.on("connected", onChange); | ||
| client.on("disconnected", onChange); | ||
| return () => { | ||
| client.off("connected", onChange); | ||
| client.off("disconnected", onChange); | ||
| }; | ||
| }, | ||
| [client] | ||
| ); | ||
| const getSnapshot = useCallback(() => client.isConnected, [client]); | ||
| const isConnected = useSyncExternalStore(subscribe, getSnapshot, () => false); | ||
| return { isConnected, authStatus: status }; | ||
| } | ||
| function useAuthState() { | ||
| return useAuthStatus(); | ||
| function useSocket() { | ||
| const client = useParcae(); | ||
| return useMemo( | ||
| () => ({ | ||
| emit: (event, ...args) => client.send(event, ...args), | ||
| on: (event, handler) => client.subscribe(event, handler), | ||
| off: (event, handler) => client.unsubscribe(event, handler) | ||
| }), | ||
| [client] | ||
| ); | ||
| } | ||
@@ -1043,4 +1181,4 @@ function useSetting(key, defaultValue) { | ||
| export { AuthLoading, Authenticated, ParcaeContext, ParcaeProvider, Unauthenticated, clearQueryCache, useApi, useAuthState, useConnectionStatus, useParcae, useQuery, useSDK, useSetting }; | ||
| export { AuthLoading, Authenticated, ParcaeProvider, Unauthenticated, useApi, useAuthStatus, useConnectionStatus, useQuery, useSDK, useSetting, useSocket }; | ||
| //# sourceMappingURL=index.js.map | ||
| //# sourceMappingURL=index.js.map |
+4
-3
| { | ||
| "name": "@parcae/sdk", | ||
| "version": "0.7.0", | ||
| "version": "0.8.0", | ||
| "description": "Parcae SDK — client transport and React hooks for Parcae backends", | ||
@@ -27,6 +27,8 @@ "type": "module", | ||
| "eventemitter3": "^5.0.1", | ||
| "fast-json-patch": "^3.1.1", | ||
| "pako": "^2.1.0", | ||
| "short-unique-id": "^5.2.0", | ||
| "socket.io-client": "^4.8.0", | ||
| "@parcae/model": "0.7.0" | ||
| "valtio": "^2.1.0", | ||
| "@parcae/model": "0.8.0" | ||
| }, | ||
@@ -52,3 +54,2 @@ "devDependencies": { | ||
| "build": "tsup", | ||
| "dev": "tsup --watch", | ||
| "typecheck": "tsc --noEmit", | ||
@@ -55,0 +56,0 @@ "clean": "rm -rf dist", |
+7
-4
@@ -23,3 +23,6 @@ # @parcae/sdk | ||
| // Custom transport | ||
| const client = createClient({ url: "http://localhost:3000", transport: myTransport }); | ||
| const client = createClient({ | ||
| url: "http://localhost:3000", | ||
| transport: myTransport, | ||
| }); | ||
| ``` | ||
@@ -33,4 +36,4 @@ | ||
| key?: string | null | (() => Promise<string | null>); | ||
| version?: string; // default: "v1" | ||
| transport?: "socket" | "sse" | Transport; // default: "socket" | ||
| version?: string; // default: "v1" | ||
| transport?: "socket" | "sse" | Transport; // default: "socket" | ||
| } | ||
@@ -137,3 +140,3 @@ ``` | ||
| const { items, loading, error, refetch } = useQuery( | ||
| Post.where({ published: true }).orderBy("createdAt", "desc") | ||
| Post.where({ published: true }).orderBy("createdAt", "desc"), | ||
| ); | ||
@@ -140,0 +143,0 @@ |
| import { Transport } from '@parcae/model'; | ||
| /** | ||
| * @parcae/sdk — createClient() | ||
| * | ||
| * Auth is handled in the transport, not React. | ||
| * Pass token at creation time, or call authenticate() later. | ||
| */ | ||
| interface ClientConfig { | ||
| url: string; | ||
| version?: string; | ||
| transport?: "socket" | "sse" | Transport; | ||
| /** Initial auth token. null = no auth. undefined = call authenticate() later. */ | ||
| token?: string | null; | ||
| } | ||
| interface ParcaeClient { | ||
| transport: Transport; | ||
| get(path: string, data?: any): Promise<any>; | ||
| post(path: string, data?: any): Promise<any>; | ||
| put(path: string, data?: any): Promise<any>; | ||
| patch(path: string, data?: any): Promise<any>; | ||
| delete(path: string, data?: any): Promise<any>; | ||
| subscribe(event: string, handler: (...args: any[]) => void): () => void; | ||
| unsubscribe(event: string, handler?: (...args: any[]) => void): void; | ||
| send(event: string, ...args: any[]): void; | ||
| readonly isConnected: boolean; | ||
| authenticate(token: string | null): Promise<{ | ||
| userId: string | null; | ||
| }>; | ||
| on(event: string, handler: (...args: any[]) => void): void; | ||
| off(event: string, handler?: (...args: any[]) => void): void; | ||
| disconnect(): void; | ||
| reconnect(): Promise<void>; | ||
| } | ||
| declare function createClient(config: ClientConfig): ParcaeClient; | ||
| /** | ||
| * AuthGate — reactive auth state via Valtio proxy. | ||
| * | ||
| * The transport writes to this directly. React reads via useSnapshot(). | ||
| * No useState, no useEffect, no manual syncing. | ||
| */ | ||
| type AuthStatus = "pending" | "authenticated" | "unauthenticated"; | ||
| interface AuthState { | ||
| status: AuthStatus; | ||
| userId: string | null; | ||
| version: number; | ||
| } | ||
| declare class AuthGate { | ||
| /** Reactive state — subscribe with valtio useSnapshot() */ | ||
| state: AuthState; | ||
| /** Awaitable — resolves when auth is confirmed (either way) */ | ||
| ready: Promise<void>; | ||
| private _resolve; | ||
| constructor(); | ||
| /** Auth confirmed — user is authenticated */ | ||
| resolve(userId: string): void; | ||
| /** Auth confirmed — no user */ | ||
| resolveUnauthenticated(): void; | ||
| /** Reset to pending (disconnect, token change) */ | ||
| reset(): void; | ||
| private _makePending; | ||
| } | ||
| /** | ||
| * Client-side auth adapter interface. | ||
| * | ||
| * Parcae calls this internally to resolve sessions. | ||
| * The adapter receives the base URL from ParcaeProvider — no config needed. | ||
| */ | ||
| interface AuthClientAdapter { | ||
| /** Initialize with the Parcae backend URL. Called by ParcaeProvider. */ | ||
| init(baseUrl: string): void; | ||
| /** Get the current session token. null = no session. */ | ||
| getToken(): Promise<string | null>; | ||
| /** Subscribe to session changes. Returns unsubscribe. */ | ||
| onChange(callback: (token: string | null) => void): () => void; | ||
| } | ||
| export { AuthGate as A, type ClientConfig as C, type ParcaeClient as P, type AuthClientAdapter as a, type AuthState as b, type AuthStatus as c, createClient as d }; |
| import SocketIO from 'socket.io-client'; | ||
| import pako from 'pako'; | ||
| import { decompress } from 'compress-json'; | ||
| import { EventEmitter } from 'eventemitter3'; | ||
| import ShortId from 'short-unique-id'; | ||
| import { Model, FrontendAdapter } from '@parcae/model'; | ||
| var __defProp = Object.defineProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| // src/log.ts | ||
| var isDev = typeof process !== "undefined" ? process.env.NODE_ENV !== "production" : true; | ||
| function time() { | ||
| const d = /* @__PURE__ */ new Date(); | ||
| return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}:${String(d.getSeconds()).padStart(2, "0")}`; | ||
| } | ||
| var log = { | ||
| info: (...args) => isDev && console.log(`%c${time()} SDK`, "color: #6b7", ...args), | ||
| warn: (...args) => isDev && console.warn(`%c${time()} SDK`, "color: #fb3", ...args), | ||
| error: (...args) => console.error(`%c${time()} SDK`, "color: #f44", ...args), | ||
| debug: (...args) => isDev && console.debug(`%c${time()} SDK`, "color: #888", ...args) | ||
| }; | ||
| // ../../node_modules/.pnpm/proxy-compare@3.0.1/node_modules/proxy-compare/dist/index.js | ||
| var GET_ORIGINAL_SYMBOL = /* @__PURE__ */ Symbol(); | ||
| var getProto = Object.getPrototypeOf; | ||
| var objectsToTrack = /* @__PURE__ */ new WeakMap(); | ||
| var isObjectToTrack = (obj) => obj && (objectsToTrack.has(obj) ? objectsToTrack.get(obj) : getProto(obj) === Object.prototype || getProto(obj) === Array.prototype); | ||
| var getUntracked = (obj) => { | ||
| if (isObjectToTrack(obj)) { | ||
| return obj[GET_ORIGINAL_SYMBOL] || null; | ||
| } | ||
| return null; | ||
| }; | ||
| // ../../node_modules/.pnpm/valtio@2.3.1_@types+react@19.2.14_react@19.2.4/node_modules/valtio/esm/vanilla.mjs | ||
| var isObject = (x) => typeof x === "object" && x !== null; | ||
| var canProxyDefault = (x) => isObject(x) && !refSet.has(x) && (Array.isArray(x) || !(Symbol.iterator in x)) && !(x instanceof WeakMap) && !(x instanceof WeakSet) && !(x instanceof Error) && !(x instanceof Number) && !(x instanceof Date) && !(x instanceof String) && !(x instanceof RegExp) && !(x instanceof ArrayBuffer) && !(x instanceof Promise); | ||
| var createHandlerDefault = (isInitializing, addPropListener, removePropListener, notifyUpdate) => ({ | ||
| deleteProperty(target, prop) { | ||
| Reflect.get(target, prop); | ||
| removePropListener(prop); | ||
| const deleted = Reflect.deleteProperty(target, prop); | ||
| if (deleted) { | ||
| notifyUpdate(void 0 ); | ||
| } | ||
| return deleted; | ||
| }, | ||
| set(target, prop, value, receiver) { | ||
| const hasPrevValue = !isInitializing() && Reflect.has(target, prop); | ||
| const prevValue = Reflect.get(target, prop, receiver); | ||
| if (hasPrevValue && (objectIs(prevValue, value) || proxyCache.has(value) && objectIs(prevValue, proxyCache.get(value)))) { | ||
| return true; | ||
| } | ||
| removePropListener(prop); | ||
| if (isObject(value)) { | ||
| value = getUntracked(value) || value; | ||
| } | ||
| const nextValue = !proxyStateMap.has(value) && canProxy(value) ? proxy(value) : value; | ||
| addPropListener(prop, nextValue); | ||
| Reflect.set(target, prop, nextValue, receiver); | ||
| notifyUpdate(void 0 ); | ||
| return true; | ||
| } | ||
| }); | ||
| var proxyStateMap = /* @__PURE__ */ new WeakMap(); | ||
| var refSet = /* @__PURE__ */ new WeakSet(); | ||
| var versionHolder = [1]; | ||
| var proxyCache = /* @__PURE__ */ new WeakMap(); | ||
| var objectIs = Object.is; | ||
| var newProxy = (target, handler) => new Proxy(target, handler); | ||
| var canProxy = canProxyDefault; | ||
| var createHandler = createHandlerDefault; | ||
| function proxy(baseObject = {}) { | ||
| if (!isObject(baseObject)) { | ||
| throw new Error("object required"); | ||
| } | ||
| const found = proxyCache.get(baseObject); | ||
| if (found) { | ||
| return found; | ||
| } | ||
| let version = versionHolder[0]; | ||
| const listeners = /* @__PURE__ */ new Set(); | ||
| const notifyUpdate = (op, nextVersion = ++versionHolder[0]) => { | ||
| if (version !== nextVersion) { | ||
| checkVersion = version = nextVersion; | ||
| listeners.forEach((listener) => listener(op, nextVersion)); | ||
| } | ||
| }; | ||
| let checkVersion = version; | ||
| const ensureVersion = (nextCheckVersion = versionHolder[0]) => { | ||
| if (checkVersion !== nextCheckVersion) { | ||
| checkVersion = nextCheckVersion; | ||
| propProxyStates.forEach(([propProxyState]) => { | ||
| const propVersion = propProxyState[1](nextCheckVersion); | ||
| if (propVersion > version) { | ||
| version = propVersion; | ||
| } | ||
| }); | ||
| } | ||
| return version; | ||
| }; | ||
| const createPropListener = (prop) => (op, nextVersion) => { | ||
| let newOp; | ||
| if (op) { | ||
| newOp = [...op]; | ||
| newOp[1] = [prop, ...newOp[1]]; | ||
| } | ||
| notifyUpdate(newOp, nextVersion); | ||
| }; | ||
| const propProxyStates = /* @__PURE__ */ new Map(); | ||
| const addPropListener = (prop, propValue) => { | ||
| const propProxyState = !refSet.has(propValue) && proxyStateMap.get(propValue); | ||
| if (propProxyState) { | ||
| if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && propProxyStates.has(prop)) { | ||
| throw new Error("prop listener already exists"); | ||
| } | ||
| if (listeners.size) { | ||
| const remove = propProxyState[2](createPropListener(prop)); | ||
| propProxyStates.set(prop, [propProxyState, remove]); | ||
| } else { | ||
| propProxyStates.set(prop, [propProxyState]); | ||
| } | ||
| } | ||
| }; | ||
| const removePropListener = (prop) => { | ||
| var _a; | ||
| const entry = propProxyStates.get(prop); | ||
| if (entry) { | ||
| propProxyStates.delete(prop); | ||
| (_a = entry[1]) == null ? void 0 : _a.call(entry); | ||
| } | ||
| }; | ||
| const addListener = (listener) => { | ||
| listeners.add(listener); | ||
| if (listeners.size === 1) { | ||
| propProxyStates.forEach(([propProxyState, prevRemove], prop) => { | ||
| if ((import.meta.env ? import.meta.env.MODE : void 0) !== "production" && prevRemove) { | ||
| throw new Error("remove already exists"); | ||
| } | ||
| const remove = propProxyState[2](createPropListener(prop)); | ||
| propProxyStates.set(prop, [propProxyState, remove]); | ||
| }); | ||
| } | ||
| const removeListener = () => { | ||
| listeners.delete(listener); | ||
| if (listeners.size === 0) { | ||
| propProxyStates.forEach(([propProxyState, remove], prop) => { | ||
| if (remove) { | ||
| remove(); | ||
| propProxyStates.set(prop, [propProxyState]); | ||
| } | ||
| }); | ||
| } | ||
| }; | ||
| return removeListener; | ||
| }; | ||
| let initializing = true; | ||
| const handler = createHandler( | ||
| () => initializing, | ||
| addPropListener, | ||
| removePropListener, | ||
| notifyUpdate | ||
| ); | ||
| const proxyObject = newProxy(baseObject, handler); | ||
| proxyCache.set(baseObject, proxyObject); | ||
| const proxyState = [baseObject, ensureVersion, addListener]; | ||
| proxyStateMap.set(proxyObject, proxyState); | ||
| Reflect.ownKeys(baseObject).forEach((key) => { | ||
| const desc = Object.getOwnPropertyDescriptor( | ||
| baseObject, | ||
| key | ||
| ); | ||
| if ("value" in desc && desc.writable) { | ||
| proxyObject[key] = baseObject[key]; | ||
| } | ||
| }); | ||
| initializing = false; | ||
| return proxyObject; | ||
| } | ||
| // src/auth-gate.ts | ||
| var AuthGate = class { | ||
| /** Reactive state — subscribe with valtio useSnapshot() */ | ||
| state = proxy({ | ||
| status: "pending", | ||
| userId: null, | ||
| version: 0 | ||
| }); | ||
| /** Awaitable — resolves when auth is confirmed (either way) */ | ||
| ready; | ||
| _resolve = null; | ||
| constructor() { | ||
| this.ready = this._makePending(); | ||
| } | ||
| /** Auth confirmed — user is authenticated */ | ||
| resolve(userId) { | ||
| this.state.status = "authenticated"; | ||
| log.debug("auth: authenticated, userId:", userId); | ||
| this.state.userId = userId; | ||
| this.state.version++; | ||
| this._resolve?.(); | ||
| this._resolve = null; | ||
| } | ||
| /** Auth confirmed — no user */ | ||
| resolveUnauthenticated() { | ||
| this.state.status = "unauthenticated"; | ||
| log.debug("auth: unauthenticated"); | ||
| this.state.userId = null; | ||
| this.state.version++; | ||
| this._resolve?.(); | ||
| this._resolve = null; | ||
| } | ||
| /** Reset to pending (disconnect, token change) */ | ||
| reset() { | ||
| if (this.state.status !== "pending") { | ||
| this.state.status = "pending"; | ||
| log.debug("auth: reset to pending"); | ||
| this.state.userId = null; | ||
| this.ready = this._makePending(); | ||
| } | ||
| } | ||
| _makePending() { | ||
| return new Promise((r) => { | ||
| this._resolve = r; | ||
| }); | ||
| } | ||
| }; | ||
| var uid = new ShortId({ length: 10 }); | ||
| var SOCKETS = /* @__PURE__ */ new Map(); | ||
| var SocketTransport = class extends EventEmitter { | ||
| auth = new AuthGate(); | ||
| isConnected = false; | ||
| socket; | ||
| url; | ||
| version; | ||
| token; | ||
| inflight = /* @__PURE__ */ new Map(); | ||
| constructor(config) { | ||
| super(); | ||
| this.url = config.url; | ||
| this.version = config.version ?? "v1"; | ||
| this.token = config.token; | ||
| const socketPath = config.path ?? "/ws"; | ||
| const socketKey = `${this.url}:${socketPath}`; | ||
| if (SOCKETS.has(socketKey)) { | ||
| this.socket = SOCKETS.get(socketKey); | ||
| } else { | ||
| this.socket = SocketIO(this.url, { | ||
| path: socketPath, | ||
| transports: ["websocket"], | ||
| withCredentials: true | ||
| }); | ||
| SOCKETS.set(socketKey, this.socket); | ||
| } | ||
| this.socket.on("connect", () => { | ||
| this.isConnected = true; | ||
| log.debug("socket connected"); | ||
| this._doAuth(); | ||
| this.emit("connected"); | ||
| }); | ||
| this.socket.on("disconnect", () => { | ||
| this.isConnected = false; | ||
| log.debug("socket disconnected"); | ||
| this.auth.reset(); | ||
| this.emit("disconnected"); | ||
| }); | ||
| this.socket.on("error", (err) => this.emit("error", err)); | ||
| if (this.socket.connected) { | ||
| this.isConnected = true; | ||
| log.debug("socket connected"); | ||
| this._doAuth(); | ||
| } | ||
| if (this.token === null) { | ||
| this.auth.resolveUnauthenticated(); | ||
| } | ||
| } | ||
| _doAuth() { | ||
| if (this.token === void 0) return; | ||
| if (this.token === null) { | ||
| this.auth.resolveUnauthenticated(); | ||
| return; | ||
| } | ||
| const t0 = performance.now(); | ||
| log.debug("authenticating..."); | ||
| this.socket.emit("authenticate", this.token, (response) => { | ||
| const ms = (performance.now() - t0).toFixed(0); | ||
| const userId = response?.userId ?? null; | ||
| if (userId) { | ||
| this.auth.resolve(userId); | ||
| log.debug(`authenticated as ${userId} (${ms}ms)`); | ||
| } else { | ||
| this.auth.resolveUnauthenticated(); | ||
| log.debug(`auth rejected (${ms}ms)`); | ||
| } | ||
| }); | ||
| } | ||
| async authenticate(token) { | ||
| this.token = token; | ||
| this.auth.reset(); | ||
| if (token === null) { | ||
| this.auth.resolveUnauthenticated(); | ||
| return { userId: null }; | ||
| } | ||
| if (!this.socket.connected) { | ||
| return new Promise((resolve) => { | ||
| const handler = () => { | ||
| this.socket.off("connect", handler); | ||
| this.socket.emit("authenticate", token, (response) => { | ||
| const userId = response?.userId ?? null; | ||
| if (userId) this.auth.resolve(userId); | ||
| else this.auth.resolveUnauthenticated(); | ||
| resolve({ userId }); | ||
| }); | ||
| }; | ||
| this.socket.once("connect", handler); | ||
| }); | ||
| } | ||
| return new Promise((resolve) => { | ||
| this.socket.emit("authenticate", token, (response) => { | ||
| const userId = response?.userId ?? null; | ||
| if (userId) this.auth.resolve(userId); | ||
| else this.auth.resolveUnauthenticated(); | ||
| resolve({ userId }); | ||
| }); | ||
| }); | ||
| } | ||
| // ── Request/Response ────────────────────────────────────────────── | ||
| async fetch(method, path, data = {}) { | ||
| await this.auth.ready; | ||
| if (!this.socket.connected) { | ||
| await new Promise((resolve, reject) => { | ||
| if (this.socket.connected) return resolve(); | ||
| const timeout = setTimeout(() => { | ||
| cleanup(); | ||
| reject(new Error("Connection timeout")); | ||
| }, 3e4); | ||
| const onConnect = () => { | ||
| cleanup(); | ||
| resolve(); | ||
| }; | ||
| const onError = (err) => { | ||
| cleanup(); | ||
| reject(err); | ||
| }; | ||
| const cleanup = () => { | ||
| clearTimeout(timeout); | ||
| this.socket.off("connect", onConnect); | ||
| this.socket.off("connect_error", onError); | ||
| }; | ||
| this.socket.once("connect", onConnect); | ||
| this.socket.once("connect_error", onError); | ||
| }); | ||
| } | ||
| const upper = method.toUpperCase(); | ||
| if (upper === "GET") { | ||
| const dedupeKey = `${path}:${JSON.stringify(data)}`; | ||
| const existing = this.inflight.get(dedupeKey); | ||
| if (existing) return existing; | ||
| const req = this._call(method, path, data); | ||
| this.inflight.set(dedupeKey, req); | ||
| req.finally(() => this.inflight.delete(dedupeKey)); | ||
| return req; | ||
| } | ||
| return this._call(method, path, data); | ||
| } | ||
| _call(method, path, data) { | ||
| const id = uid.rnd(); | ||
| const t0 = performance.now(); | ||
| const fullPath = `/${this.version}${path}`; | ||
| log.debug(`\u2192 ${method.toUpperCase()} ${fullPath}`); | ||
| return new Promise((resolve, reject) => { | ||
| const timeout = setTimeout(() => { | ||
| this.socket.off(id); | ||
| log.debug(`\u2717 ${method.toUpperCase()} ${fullPath} timeout (30s)`); | ||
| reject(new Error(`RPC timeout: ${method} ${path}`)); | ||
| }, 3e4); | ||
| this.socket.once(id, (msg) => { | ||
| clearTimeout(timeout); | ||
| const ms = (performance.now() - t0).toFixed(0); | ||
| try { | ||
| const uncompressed = pako.ungzip(msg, { to: "string" }); | ||
| const parsed = decompress(JSON.parse(uncompressed)); | ||
| if (parsed.success) { | ||
| log.debug(`\u2190 ${method.toUpperCase()} ${fullPath} (${ms}ms)`); | ||
| resolve(parsed.result); | ||
| } else { | ||
| log.debug( | ||
| `\u2717 ${method.toUpperCase()} ${fullPath} (${ms}ms) ${parsed.error || parsed.message}` | ||
| ); | ||
| reject( | ||
| new Error( | ||
| parsed.message || parsed.error || `${method} ${path} failed` | ||
| ) | ||
| ); | ||
| } | ||
| } catch (err) { | ||
| log.debug( | ||
| `\u2717 ${method.toUpperCase()} ${fullPath} (${ms}ms) parse error` | ||
| ); | ||
| reject(err); | ||
| } | ||
| }); | ||
| this.socket.emit( | ||
| "call", | ||
| id, | ||
| method.toUpperCase(), | ||
| `/${this.version}${path}`, | ||
| data | ||
| ); | ||
| }); | ||
| } | ||
| async get(path, data) { | ||
| return this.fetch("GET", path, data); | ||
| } | ||
| async post(path, data) { | ||
| return this.fetch("POST", path, data); | ||
| } | ||
| async put(path, data) { | ||
| return this.fetch("PUT", path, data); | ||
| } | ||
| async patch(path, data) { | ||
| return this.fetch("PATCH", path, data); | ||
| } | ||
| async delete(path, data) { | ||
| return this.fetch("DELETE", path, data); | ||
| } | ||
| /** The underlying socket ID (available after connect). */ | ||
| get socketId() { | ||
| return this.socket?.id ?? null; | ||
| } | ||
| subscribe(event, handler) { | ||
| this.socket.on(event, handler); | ||
| return () => this.socket.off(event, handler); | ||
| } | ||
| unsubscribe(event, handler) { | ||
| this.socket.off(event, handler); | ||
| } | ||
| async send(event, ...args) { | ||
| await this.auth.ready; | ||
| this.socket.emit(event, ...args); | ||
| } | ||
| /** | ||
| * Emit a socket event with a callback (request-response pattern). | ||
| * Waits for auth and connection before emitting. | ||
| */ | ||
| async emitWithCallback(event, data) { | ||
| await this.auth.ready; | ||
| if (!this.socket.connected) { | ||
| await new Promise((resolve, reject) => { | ||
| if (this.socket.connected) return resolve(); | ||
| const timeout = setTimeout(() => { | ||
| cleanup(); | ||
| reject(new Error("Connection timeout")); | ||
| }, 3e4); | ||
| const onConnect = () => { | ||
| cleanup(); | ||
| resolve(); | ||
| }; | ||
| const onError = (err) => { | ||
| cleanup(); | ||
| reject(err); | ||
| }; | ||
| const cleanup = () => { | ||
| clearTimeout(timeout); | ||
| this.socket.off("connect", onConnect); | ||
| this.socket.off("connect_error", onError); | ||
| }; | ||
| this.socket.once("connect", onConnect); | ||
| this.socket.once("connect_error", onError); | ||
| }); | ||
| } | ||
| return new Promise((resolve, reject) => { | ||
| const timeout = setTimeout(() => { | ||
| reject(new Error(`emitWithCallback timeout: ${event}`)); | ||
| }, 3e4); | ||
| this.socket.emit(event, data, (response) => { | ||
| clearTimeout(timeout); | ||
| if (response?.error) { | ||
| reject(new Error(response.error)); | ||
| } else { | ||
| resolve(response); | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| disconnect() { | ||
| this.socket.disconnect(); | ||
| this.isConnected = false; | ||
| log.debug("socket disconnected"); | ||
| } | ||
| async reconnect() { | ||
| this.socket.connect(); | ||
| } | ||
| }; | ||
| var SSETransport = class extends EventEmitter { | ||
| url; | ||
| version; | ||
| apiKey; | ||
| key = null; | ||
| eventSources = /* @__PURE__ */ new Map(); | ||
| isConnected = true; | ||
| // HTTP is "always connected" | ||
| isLoading = true; | ||
| loading; | ||
| constructor(config) { | ||
| super(); | ||
| this.url = config.url.replace(/\/$/, ""); | ||
| this.version = config.version ?? "v1"; | ||
| this.apiKey = config.key ?? null; | ||
| this.loading = this.resolveKey(); | ||
| } | ||
| async resolveKey() { | ||
| try { | ||
| this.key = typeof this.apiKey === "function" ? await this.apiKey() : this.apiKey; | ||
| this.isLoading = false; | ||
| this.emit("connected"); | ||
| } catch (err) { | ||
| this.isLoading = false; | ||
| this.emit("error", err); | ||
| } | ||
| } | ||
| headers() { | ||
| const h = { "Content-Type": "application/json" }; | ||
| if (this.key) h["Authorization"] = `Bearer ${this.key}`; | ||
| return h; | ||
| } | ||
| fullUrl(path) { | ||
| return `${this.url}/${this.version}${path}`; | ||
| } | ||
| // ── Request/Response ────────────────────────────────────────────────── | ||
| async request(method, path, data) { | ||
| await this.loading; | ||
| const isGet = method.toUpperCase() === "GET"; | ||
| let url = this.fullUrl(path); | ||
| if (isGet && data) { | ||
| const params = new URLSearchParams(); | ||
| for (const [k, v] of Object.entries(data)) { | ||
| params.set(k, typeof v === "object" ? JSON.stringify(v) : String(v)); | ||
| } | ||
| url += `?${params.toString()}`; | ||
| } | ||
| const res = await fetch(url, { | ||
| method: method.toUpperCase(), | ||
| headers: this.headers(), | ||
| body: isGet ? void 0 : JSON.stringify(data) | ||
| }); | ||
| if (!res.ok) { | ||
| const body2 = await res.json().catch(() => ({ error: res.statusText })); | ||
| throw new Error(body2.error || body2.message || `HTTP ${res.status}`); | ||
| } | ||
| const body = await res.json(); | ||
| if (body.success === false) throw new Error(body.error || "Request failed"); | ||
| return body.result ?? body; | ||
| } | ||
| async get(path, data) { | ||
| return this.request("GET", path, data); | ||
| } | ||
| async post(path, data) { | ||
| return this.request("POST", path, data); | ||
| } | ||
| async put(path, data) { | ||
| return this.request("PUT", path, data); | ||
| } | ||
| async patch(path, data) { | ||
| return this.request("PATCH", path, data); | ||
| } | ||
| async delete(path, data) { | ||
| return this.request("DELETE", path, data); | ||
| } | ||
| // ── Subscriptions (via Server-Sent Events) ──────────────────────────── | ||
| subscribe(event, handler) { | ||
| const url = `${this.url}/${this.version}/__events/${encodeURIComponent(event)}`; | ||
| const source = new EventSource(url, { withCredentials: true }); | ||
| source.onmessage = (e) => { | ||
| try { | ||
| const data = JSON.parse(e.data); | ||
| handler(data); | ||
| } catch { | ||
| handler(e.data); | ||
| } | ||
| }; | ||
| source.onerror = () => { | ||
| }; | ||
| this.eventSources.set(event, source); | ||
| return () => { | ||
| source.close(); | ||
| this.eventSources.delete(event); | ||
| }; | ||
| } | ||
| unsubscribe(event) { | ||
| const source = this.eventSources.get(event); | ||
| if (source) { | ||
| source.close(); | ||
| this.eventSources.delete(event); | ||
| } | ||
| } | ||
| // ── Control messages ────────────────────────────────────────────────── | ||
| async send(event, ...args) { | ||
| await this.request("POST", "/__control", { event, args }); | ||
| } | ||
| // ── Lifecycle ───────────────────────────────────────────────────────── | ||
| disconnect() { | ||
| for (const [, source] of this.eventSources) source.close(); | ||
| this.eventSources.clear(); | ||
| this.isConnected = false; | ||
| this.emit("disconnected"); | ||
| } | ||
| async reconnect() { | ||
| this.loading = this.resolveKey(); | ||
| await this.loading; | ||
| } | ||
| }; | ||
| function createClient(config) { | ||
| const cacheKey = `${config.url}:${config.version ?? "v1"}`; | ||
| const existing = globalThis.__parcae_clients?.get(cacheKey); | ||
| if (existing) return existing; | ||
| const version = config.version ?? "v1"; | ||
| let transport; | ||
| if (config.transport && typeof config.transport === "object") { | ||
| transport = config.transport; | ||
| } else if (config.transport === "sse") { | ||
| transport = new SSETransport({ url: config.url, version }); | ||
| } else { | ||
| transport = new SocketTransport({ | ||
| url: config.url, | ||
| version, | ||
| token: config.token | ||
| }); | ||
| } | ||
| Model.use(new FrontendAdapter(transport)); | ||
| const client = { | ||
| transport, | ||
| get: (p, d) => transport.get(p, d), | ||
| post: (p, d) => transport.post(p, d), | ||
| put: (p, d) => transport.put(p, d), | ||
| patch: (p, d) => transport.patch(p, d), | ||
| delete: (p, d) => transport.delete(p, d), | ||
| subscribe: (e, h) => transport.subscribe?.(e, h) ?? (() => { | ||
| }), | ||
| unsubscribe: (e, h) => transport.unsubscribe?.(e, h), | ||
| send: (e, ...a) => transport.send?.(e, ...a), | ||
| get isConnected() { | ||
| return transport.isConnected ?? false; | ||
| }, | ||
| authenticate: (t) => transport.authenticate?.(t) ?? Promise.resolve({ userId: null }), | ||
| on: (e, h) => transport.on?.(e, h), | ||
| off: (e, h) => transport.off?.(e, h), | ||
| disconnect: () => transport.disconnect?.(), | ||
| reconnect: () => transport.reconnect?.() ?? Promise.resolve() | ||
| }; | ||
| if (!globalThis.__parcae_clients) { | ||
| globalThis.__parcae_clients = /* @__PURE__ */ new Map(); | ||
| } | ||
| globalThis.__parcae_clients.set(cacheKey, client); | ||
| return client; | ||
| } | ||
| export { AuthGate, SSETransport, SocketTransport, __export, createClient, log }; | ||
| //# sourceMappingURL=chunk-USSSG27K.js.map | ||
| //# sourceMappingURL=chunk-USSSG27K.js.map |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
268
1.13%7
-22.22%212354
-8.27%9
28.57%10
-9.09%1788
-7.26%+ Added
+ Added
+ Added
- Removed
Updated