🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@parcae/sdk

Package Overview
Dependencies
Maintainers
1
Versions
38
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@parcae/sdk - npm Package Compare versions

Comparing version
0.7.0
to
0.8.0
+503
dist/chunk-B47IBEFK.js
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,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"}

@@ -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 };

@@ -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
{
"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",

@@ -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