@ai-matrx/desktop-protocol 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/LICENSE +21 -0
- package/README.md +90 -0
- package/dist/client.cjs +1309 -0
- package/dist/client.cjs.map +1 -0
- package/dist/client.d.cts +181 -0
- package/dist/client.d.ts +181 -0
- package/dist/client.js +1286 -0
- package/dist/client.js.map +1 -0
- package/dist/frame.cjs +132 -0
- package/dist/frame.cjs.map +1 -0
- package/dist/frame.d.cts +46 -0
- package/dist/frame.d.ts +46 -0
- package/dist/frame.js +109 -0
- package/dist/frame.js.map +1 -0
- package/dist/index.cjs +876 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +935 -0
- package/dist/index.d.ts +935 -0
- package/dist/index.js +853 -0
- package/dist/index.js.map +1 -0
- package/dist/react.cjs +129 -0
- package/dist/react.cjs.map +1 -0
- package/dist/react.d.cts +924 -0
- package/dist/react.d.ts +924 -0
- package/dist/react.js +109 -0
- package/dist/react.js.map +1 -0
- package/dist/registry-DTcTGycI.d.cts +2330 -0
- package/dist/registry-DTcTGycI.d.ts +2330 -0
- package/package.json +124 -0
package/dist/react.js
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/react.ts
|
|
4
|
+
import { createContext, createElement, useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from "react";
|
|
5
|
+
|
|
6
|
+
// src/error.ts
|
|
7
|
+
var DesktopProtocolError = class extends Error {
|
|
8
|
+
code;
|
|
9
|
+
retryable;
|
|
10
|
+
data;
|
|
11
|
+
constructor(code, message, options = {}) {
|
|
12
|
+
super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
|
|
13
|
+
this.name = "DesktopProtocolError";
|
|
14
|
+
this.code = code;
|
|
15
|
+
this.retryable = options.retryable ?? false;
|
|
16
|
+
this.data = options.data;
|
|
17
|
+
}
|
|
18
|
+
/** The wire body (ProtocolErrorBody) — what a core or relay sends for this error. */
|
|
19
|
+
toBody() {
|
|
20
|
+
return this.data === void 0 ? { code: this.code, message: this.message, retryable: this.retryable } : { code: this.code, message: this.message, retryable: this.retryable, data: this.data };
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// src/react.ts
|
|
25
|
+
var CONTEXT_SLOT = /* @__PURE__ */ Symbol.for("ai-matrx.desktop-protocol.client-context");
|
|
26
|
+
function clientContext() {
|
|
27
|
+
const slot = globalThis;
|
|
28
|
+
return slot[CONTEXT_SLOT] ??= createContext(null);
|
|
29
|
+
}
|
|
30
|
+
function DesktopClientProvider(props) {
|
|
31
|
+
return createElement(clientContext().Provider, { value: props.client }, props.children);
|
|
32
|
+
}
|
|
33
|
+
function useDesktopClient(client) {
|
|
34
|
+
const fromContext = useContext(clientContext());
|
|
35
|
+
const resolved = client ?? fromContext;
|
|
36
|
+
if (!resolved) throw new Error("useDesktopClient: wrap the tree in <DesktopClientProvider client={\u2026}> or pass a client");
|
|
37
|
+
return resolved;
|
|
38
|
+
}
|
|
39
|
+
function useDesktopConnection(client) {
|
|
40
|
+
const c = useDesktopClient(client);
|
|
41
|
+
const state = useSyncExternalStore(c.subscribe, c.getState, c.getState);
|
|
42
|
+
useEffect(() => {
|
|
43
|
+
if (c.getState().status === "idle") c.connect();
|
|
44
|
+
}, [c]);
|
|
45
|
+
return state;
|
|
46
|
+
}
|
|
47
|
+
function useDesktopRequest(op, params, options = {}) {
|
|
48
|
+
const c = useDesktopClient(options.client);
|
|
49
|
+
const enabled = options.enabled ?? true;
|
|
50
|
+
const key = JSON.stringify(params);
|
|
51
|
+
const [data, setData] = useState(void 0);
|
|
52
|
+
const [error, setError] = useState(void 0);
|
|
53
|
+
const [loading, setLoading] = useState(enabled);
|
|
54
|
+
const paramsRef = useRef(params);
|
|
55
|
+
paramsRef.current = params;
|
|
56
|
+
const runRef = useRef(0);
|
|
57
|
+
const abortRef = useRef(null);
|
|
58
|
+
const timeoutMs = options.timeoutMs;
|
|
59
|
+
const run = useCallback(async () => {
|
|
60
|
+
const runId = ++runRef.current;
|
|
61
|
+
abortRef.current?.abort();
|
|
62
|
+
const abort = new AbortController();
|
|
63
|
+
abortRef.current = abort;
|
|
64
|
+
setLoading(true);
|
|
65
|
+
try {
|
|
66
|
+
const result = await c.request(op, paramsRef.current, timeoutMs === void 0 ? { signal: abort.signal } : { signal: abort.signal, timeoutMs });
|
|
67
|
+
if (runId === runRef.current) {
|
|
68
|
+
setData(result);
|
|
69
|
+
setError(void 0);
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
} catch (caught) {
|
|
73
|
+
const err = caught instanceof DesktopProtocolError ? caught : new DesktopProtocolError("INTERNAL", String(caught), { cause: caught });
|
|
74
|
+
if (runId === runRef.current && err.code !== "CANCELLED") setError(err);
|
|
75
|
+
return void 0;
|
|
76
|
+
} finally {
|
|
77
|
+
if (runId === runRef.current) setLoading(false);
|
|
78
|
+
}
|
|
79
|
+
}, [c, op, key, timeoutMs]);
|
|
80
|
+
useEffect(() => {
|
|
81
|
+
if (!enabled) {
|
|
82
|
+
setLoading(false);
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
void run();
|
|
86
|
+
return () => {
|
|
87
|
+
abortRef.current?.abort();
|
|
88
|
+
};
|
|
89
|
+
}, [enabled, run]);
|
|
90
|
+
const status = useSyncExternalStore(c.subscribe, () => c.getState().status, () => c.getState().status);
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (enabled && status === "open" && error?.retryable) void run();
|
|
93
|
+
}, [status]);
|
|
94
|
+
return { data, error, loading, refetch: run };
|
|
95
|
+
}
|
|
96
|
+
function useDesktopEvent(name, handler, options = {}) {
|
|
97
|
+
const c = useDesktopClient(options.client);
|
|
98
|
+
const handlerRef = useRef(handler);
|
|
99
|
+
handlerRef.current = handler;
|
|
100
|
+
useEffect(() => c.on(name, ((payload, resourceId) => handlerRef.current(payload, resourceId))), [c, name]);
|
|
101
|
+
}
|
|
102
|
+
export {
|
|
103
|
+
DesktopClientProvider,
|
|
104
|
+
useDesktopClient,
|
|
105
|
+
useDesktopConnection,
|
|
106
|
+
useDesktopEvent,
|
|
107
|
+
useDesktopRequest
|
|
108
|
+
};
|
|
109
|
+
//# sourceMappingURL=react.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/react.ts","../src/error.ts"],"sourcesContent":["/**\n * `@ai-matrx/desktop-protocol/react` — hooks over the client: provider, connection state, typed\n * one-shot requests, events. The client owns every hard part (reconnect, reattach, credits); these\n * hooks only bind it to React's lifecycle.\n */\nimport { createContext, createElement, useCallback, useContext, useEffect, useRef, useState, useSyncExternalStore } from \"react\";\nimport type { Context, ReactNode } from \"react\";\nimport type { DesktopClient, DesktopClientState, EventHandler, EventName, RequestOptions } from \"./client/types\";\nimport { DesktopProtocolError } from \"./error\";\nimport type { OpName, OpParams, OpResult } from \"./schema/registry\";\n\n// The context lives on globalThis: an ESM and a CJS copy of this entry in one app (Next.js\n// does this) would otherwise create two contexts and a provider the hooks cannot see.\nconst CONTEXT_SLOT = Symbol.for(\"ai-matrx.desktop-protocol.client-context\");\nfunction clientContext(): Context<DesktopClient | null> {\n const slot = globalThis as { [CONTEXT_SLOT]?: Context<DesktopClient | null> };\n return (slot[CONTEXT_SLOT] ??= createContext<DesktopClient | null>(null));\n}\n\nexport function DesktopClientProvider(props: { client: DesktopClient; children?: ReactNode }): ReactNode {\n return createElement(clientContext().Provider, { value: props.client }, props.children);\n}\n\n/** The client from the nearest provider (or the one passed in). Throws when there is neither. */\nexport function useDesktopClient(client?: DesktopClient): DesktopClient {\n const fromContext = useContext(clientContext());\n const resolved = client ?? fromContext;\n if (!resolved) throw new Error(\"useDesktopClient: wrap the tree in <DesktopClientProvider client={…}> or pass a client\");\n return resolved;\n}\n\n/** Live connection state (status, welcome, attempts, retryInMs, lastError). Connects on mount when idle. */\nexport function useDesktopConnection(client?: DesktopClient): DesktopClientState {\n const c = useDesktopClient(client);\n const state = useSyncExternalStore(c.subscribe, c.getState, c.getState);\n useEffect(() => {\n if (c.getState().status === \"idle\") c.connect();\n }, [c]);\n return state;\n}\n\nexport interface DesktopRequestState<N extends OpName> {\n data: OpResult<N> | undefined;\n error: DesktopProtocolError | undefined;\n loading: boolean;\n /** Re-run now; resolves with the fresh result. */\n refetch: () => Promise<OpResult<N> | undefined>;\n}\n\nexport interface UseDesktopRequestOptions extends Omit<RequestOptions, \"signal\"> {\n /** Default true. False = do not run until refetch() or enabled flips true. */\n enabled?: boolean;\n client?: DesktopClient;\n}\n\n/**\n * Run a unary op and keep its latest result. Re-runs when `op` or the params' JSON changes, and\n * after a reconnect if the last attempt failed with a retryable error. Aborts on unmount.\n */\nexport function useDesktopRequest<N extends OpName>(op: N, params: OpParams<N>, options: UseDesktopRequestOptions = {}): DesktopRequestState<N> {\n const c = useDesktopClient(options.client);\n const enabled = options.enabled ?? true;\n const key = JSON.stringify(params);\n const [data, setData] = useState<OpResult<N> | undefined>(undefined);\n const [error, setError] = useState<DesktopProtocolError | undefined>(undefined);\n const [loading, setLoading] = useState<boolean>(enabled);\n const paramsRef = useRef(params);\n paramsRef.current = params;\n const runRef = useRef(0);\n const abortRef = useRef<AbortController | null>(null);\n const timeoutMs = options.timeoutMs;\n\n const run = useCallback(async (): Promise<OpResult<N> | undefined> => {\n const runId = ++runRef.current;\n abortRef.current?.abort();\n const abort = new AbortController();\n abortRef.current = abort;\n setLoading(true);\n try {\n const result = await c.request(op, paramsRef.current, timeoutMs === undefined ? { signal: abort.signal } : { signal: abort.signal, timeoutMs });\n if (runId === runRef.current) {\n setData(result);\n setError(undefined);\n }\n return result;\n } catch (caught) {\n const err = caught instanceof DesktopProtocolError ? caught : new DesktopProtocolError(\"INTERNAL\", String(caught), { cause: caught });\n if (runId === runRef.current && err.code !== \"CANCELLED\") setError(err);\n return undefined;\n } finally {\n if (runId === runRef.current) setLoading(false);\n }\n // `key` stands for the params' content: a new object with equal JSON is not a new request.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [c, op, key, timeoutMs]);\n\n useEffect(() => {\n if (!enabled) {\n setLoading(false);\n return;\n }\n void run();\n return () => {\n abortRef.current?.abort();\n };\n }, [enabled, run]);\n\n // A retryable failure (device offline, connection lost) re-runs once the client is open again.\n const status = useSyncExternalStore(c.subscribe, () => c.getState().status, () => c.getState().status);\n useEffect(() => {\n if (enabled && status === \"open\" && error?.retryable) void run();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [status]);\n\n return { data, error, loading, refetch: run };\n}\n\n/** Subscribe to one protocol event (fs.changed, exec.exited, relay.device_status, …) for the component's life. */\nexport function useDesktopEvent<E extends EventName>(name: E, handler: EventHandler<E>, options: { client?: DesktopClient } = {}): void {\n const c = useDesktopClient(options.client);\n const handlerRef = useRef(handler);\n handlerRef.current = handler;\n useEffect(() => c.on(name, ((payload, resourceId) => handlerRef.current(payload, resourceId)) as EventHandler<E>), [c, name]);\n}\n","/**\n * The one error type every package entry throws. It carries the wire ErrorCode (SPEC §5), so a\n * caller branches on `code`/`retryable`/`data.reason` and never parses a message. Zod-free on\n * purpose: `./frame` (the relay hot path) throws it without pulling the schema graph.\n */\nimport type { ErrorCodeName, ErrorData } from \"./types\";\n\nexport class DesktopProtocolError extends Error {\n readonly code: ErrorCodeName;\n readonly retryable: boolean;\n readonly data: ErrorData | undefined;\n\n constructor(code: ErrorCodeName, message: string, options: { retryable?: boolean; data?: ErrorData; cause?: unknown } = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = \"DesktopProtocolError\";\n this.code = code;\n this.retryable = options.retryable ?? false;\n this.data = options.data;\n }\n\n /** The wire body (ProtocolErrorBody) — what a core or relay sends for this error. */\n toBody(): { code: ErrorCodeName; message: string; retryable: boolean; data?: ErrorData } {\n return this.data === undefined\n ? { code: this.code, message: this.message, retryable: this.retryable }\n : { code: this.code, message: this.message, retryable: this.retryable, data: this.data };\n }\n}\n\nexport function isDesktopProtocolError(value: unknown): value is DesktopProtocolError {\n return value instanceof DesktopProtocolError || (value instanceof Error && value.name === \"DesktopProtocolError\" && \"code\" in value);\n}\n"],"mappings":"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","names":[]}
|