@epoch32/visage 0.0.0-stage → 0.0.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.
Files changed (138) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +110 -2
  3. package/dist/bundle.dev.js +4875 -0
  4. package/dist/index.js +9633 -0
  5. package/dist/src/api/index.js +4002 -0
  6. package/dist/src/charts/index.js +2717 -0
  7. package/dist/src/devtools/index.js +4677 -0
  8. package/dist/src/dom/index.js +1550 -0
  9. package/dist/src/form/index.js +1415 -0
  10. package/dist/src/index.js +11826 -0
  11. package/dist/src/jsx-runtime/index.js +1630 -0
  12. package/dist/src/query/index.js +2422 -0
  13. package/dist/src/router/index.js +1856 -0
  14. package/dist/src/rpc/index.js +3484 -0
  15. package/dist/src/server/index.js +1173 -0
  16. package/dist/src/signals/index.js +1077 -0
  17. package/dist/src/style/index.js +2340 -0
  18. package/dist/src/template/index.js +1149 -0
  19. package/dist/src/ui/index.js +4155 -0
  20. package/dist/src/virtual/index.js +1955 -0
  21. package/dist/src/vite/index.js +984 -0
  22. package/dist/vite/index.js +707 -0
  23. package/package.json +140 -4
  24. package/src/api/client.ts +371 -0
  25. package/src/api/file-router.ts +183 -0
  26. package/src/api/handler.ts +326 -0
  27. package/src/api/index.ts +71 -0
  28. package/src/api/middleware.ts +54 -0
  29. package/src/api/pragma.ts +155 -0
  30. package/src/api/procedure.ts +172 -0
  31. package/src/api/types.ts +249 -0
  32. package/src/charts/Area.tsx +111 -0
  33. package/src/charts/Axis.tsx +126 -0
  34. package/src/charts/Bar.tsx +86 -0
  35. package/src/charts/CartesianGrid.tsx +72 -0
  36. package/src/charts/Chart.tsx +269 -0
  37. package/src/charts/Cursor.tsx +42 -0
  38. package/src/charts/Legend.tsx +56 -0
  39. package/src/charts/Line.tsx +106 -0
  40. package/src/charts/Pie.tsx +88 -0
  41. package/src/charts/Tooltip.tsx +127 -0
  42. package/src/charts/context.ts +30 -0
  43. package/src/charts/index.ts +14 -0
  44. package/src/charts/path.ts +155 -0
  45. package/src/charts/scale.ts +133 -0
  46. package/src/charts/types.ts +50 -0
  47. package/src/devtools/DomTab.tsx +134 -0
  48. package/src/devtools/GraphInspector.tsx +196 -0
  49. package/src/devtools/GraphTab.tsx +315 -0
  50. package/src/devtools/QueryTab.tsx +200 -0
  51. package/src/devtools/RpcTab.tsx +190 -0
  52. package/src/devtools/SignalsTab.tsx +129 -0
  53. package/src/devtools/component.tsx +243 -0
  54. package/src/devtools/highlighter.ts +134 -0
  55. package/src/devtools/hub.ts +175 -0
  56. package/src/devtools/index.ts +20 -0
  57. package/src/devtools/styles.ts +313 -0
  58. package/src/devtools/types.ts +45 -0
  59. package/src/dom/async.ts +232 -0
  60. package/src/dom/control-flow.ts +353 -0
  61. package/src/dom/index.ts +32 -0
  62. package/src/dom/lazy.ts +82 -0
  63. package/src/dom/render.ts +286 -0
  64. package/src/dom/resource.ts +120 -0
  65. package/src/form/form.ts +218 -0
  66. package/src/form/index.ts +17 -0
  67. package/src/form/schema.ts +299 -0
  68. package/src/index.ts +413 -0
  69. package/src/jsx-dev-runtime/index.ts +1 -0
  70. package/src/jsx-runtime/index.ts +112 -0
  71. package/src/jsx-runtime/jsx.d.ts +278 -0
  72. package/src/query/create-mutation.ts +64 -0
  73. package/src/query/create-query.ts +426 -0
  74. package/src/query/focus-manager.ts +68 -0
  75. package/src/query/index.ts +63 -0
  76. package/src/query/mutation-cache.ts +138 -0
  77. package/src/query/online-manager.ts +67 -0
  78. package/src/query/query-cache.ts +369 -0
  79. package/src/query/query-client.ts +248 -0
  80. package/src/query/types.ts +221 -0
  81. package/src/query/utils.ts +132 -0
  82. package/src/router/index.ts +19 -0
  83. package/src/router/router.tsx +352 -0
  84. package/src/rpc/client.ts +317 -0
  85. package/src/rpc/error.ts +70 -0
  86. package/src/rpc/index.ts +52 -0
  87. package/src/rpc/procedure.ts +88 -0
  88. package/src/rpc/router.ts +105 -0
  89. package/src/rpc/server.ts +511 -0
  90. package/src/rpc/types.ts +328 -0
  91. package/src/server/hydrate.ts +21 -0
  92. package/src/server/index.ts +10 -0
  93. package/src/server/render-to-stream.ts +31 -0
  94. package/src/server/render-to-string.ts +125 -0
  95. package/src/signals/computed.ts +217 -0
  96. package/src/signals/context.ts +89 -0
  97. package/src/signals/effect.ts +161 -0
  98. package/src/signals/index.ts +57 -0
  99. package/src/signals/scope.ts +132 -0
  100. package/src/signals/signal.ts +203 -0
  101. package/src/signals/store.ts +163 -0
  102. package/src/signals/system.ts +124 -0
  103. package/src/signals/types.ts +64 -0
  104. package/src/style/component.tsx +122 -0
  105. package/src/style/create-style.ts +229 -0
  106. package/src/style/cx.ts +75 -0
  107. package/src/style/hash.ts +19 -0
  108. package/src/style/index.ts +8 -0
  109. package/src/style/layout.tsx +406 -0
  110. package/src/style/manager.ts +87 -0
  111. package/src/style/parser.ts +257 -0
  112. package/src/style/scale.ts +237 -0
  113. package/src/template/index.ts +4 -0
  114. package/src/template/template.ts +126 -0
  115. package/src/ui/accordion.tsx +286 -0
  116. package/src/ui/command-dialog.tsx +49 -0
  117. package/src/ui/command.tsx +400 -0
  118. package/src/ui/data-table.tsx +322 -0
  119. package/src/ui/dialog.tsx +362 -0
  120. package/src/ui/dropdown-menu.tsx +381 -0
  121. package/src/ui/icon.tsx +69 -0
  122. package/src/ui/index.ts +138 -0
  123. package/src/ui/layout.tsx +13 -0
  124. package/src/ui/popover.tsx +249 -0
  125. package/src/ui/switch.tsx +114 -0
  126. package/src/ui/tabs.tsx +297 -0
  127. package/src/ui/toast.tsx +280 -0
  128. package/src/ui/tooltip.tsx +262 -0
  129. package/src/ui/utils.ts +110 -0
  130. package/src/virtual/index.ts +12 -0
  131. package/src/virtual/virtual-list.tsx +185 -0
  132. package/src/virtual/virtualizer.ts +274 -0
  133. package/src/vite/hmr.ts +38 -0
  134. package/src/vite/index.ts +3 -0
  135. package/src/vite/lucide.ts +124 -0
  136. package/src/vite/plugin.ts +102 -0
  137. package/src/vite/rpc-middleware.ts +82 -0
  138. package/src/vite/types.ts +65 -0
@@ -0,0 +1,352 @@
1
+ import { type Child, Show } from "../dom";
2
+ import {
3
+ computed,
4
+ createContext,
5
+ onCleanup,
6
+ type ReadonlySignal,
7
+ signal,
8
+ useContext,
9
+ type WritableSignal
10
+ } from "../signals";
11
+
12
+ export interface LocationState {
13
+ pathname: ReadonlySignal<string>;
14
+ search: ReadonlySignal<string>;
15
+ hash: ReadonlySignal<string>;
16
+ }
17
+
18
+ export interface RouterContextValue {
19
+ location: LocationState;
20
+ navigate: (to: string, options?: { replace?: boolean }) => void;
21
+ params: ReadonlySignal<Record<string, string>>;
22
+ base: string;
23
+ matchedRoute: ReadonlySignal<MatchedRoute | null>;
24
+ routes: RouteDefinition[];
25
+ registerRoute: (route: RouteDefinition) => void;
26
+ }
27
+
28
+ export interface MatchedRoute {
29
+ path: string;
30
+ params: Record<string, string>;
31
+ component?: (props?: any) => Child;
32
+ children?: Child;
33
+ }
34
+
35
+ export interface RouteDefinition {
36
+ path: string;
37
+ component?: (props?: any) => Child;
38
+ children?: Child;
39
+ matcher: RegExp;
40
+ paramNames: string[];
41
+ }
42
+
43
+ const RouterContext = createContext<RouterContextValue | null>(null);
44
+
45
+ function compilePath(path: string): { matcher: RegExp; paramNames: string[] } {
46
+ const paramNames: string[] = [];
47
+ const normalized = path.replace(/\/+$/, "") || "/";
48
+
49
+ const regexStr = normalized
50
+ .replace(/:([a-zA-Z0-9_]+)/g, (_, name) => {
51
+ paramNames.push(name);
52
+ return "([^/]+)";
53
+ })
54
+ .replace(/\*/g, "(.*)");
55
+
56
+ const matcher = new RegExp(`^${regexStr}(?:/)?$`);
57
+ return { matcher, paramNames };
58
+ }
59
+
60
+ function matchRoute(routes: RouteDefinition[], pathname: string, base = ""): MatchedRoute | null {
61
+ const cleanPath =
62
+ base && pathname.startsWith(base) ? pathname.slice(base.length) || "/" : pathname;
63
+
64
+ for (const route of routes) {
65
+ const match = cleanPath.match(route.matcher);
66
+ if (match) {
67
+ const params: Record<string, string> = {};
68
+ const captures = match.slice(1);
69
+ route.paramNames.forEach((name, i) => {
70
+ params[name] = decodeURIComponent(captures[i] ?? "");
71
+ });
72
+
73
+ return {
74
+ path: route.path,
75
+ params,
76
+ component: route.component,
77
+ children: route.children
78
+ };
79
+ }
80
+ }
81
+
82
+ return null;
83
+ }
84
+
85
+ export interface RouterProps {
86
+ base?: string;
87
+ mode?: "history" | "hash";
88
+ children: Child;
89
+ }
90
+
91
+ /**
92
+ * Root Router provider managing browser location state and URL synchronization
93
+ */
94
+ export function Router(props: RouterProps): Child {
95
+ const base = props.base ?? "";
96
+ const mode = props.mode ?? "history";
97
+
98
+ const getPathname = () => {
99
+ if (typeof window === "undefined" || !window.location) return "/";
100
+ if (mode === "hash") {
101
+ const hash = window.location.hash.slice(1) || "/";
102
+ return hash.split("?")[0] || "/";
103
+ }
104
+ return window.location.pathname || "/";
105
+ };
106
+
107
+ const getSearch = () => {
108
+ if (typeof window === "undefined" || !window.location) return "";
109
+ if (mode === "hash") {
110
+ const hash = window.location.hash.slice(1);
111
+ const queryIdx = hash.indexOf("?");
112
+ return queryIdx !== -1 ? hash.slice(queryIdx) : "";
113
+ }
114
+ return window.location.search || "";
115
+ };
116
+
117
+ const getHash = () => {
118
+ if (typeof window === "undefined" || !window.location) return "";
119
+ return window.location.hash || "";
120
+ };
121
+
122
+ const pathname: WritableSignal<string> = signal(getPathname());
123
+ const search: WritableSignal<string> = signal(getSearch());
124
+ const hash: WritableSignal<string> = signal(getHash());
125
+
126
+ const routes: RouteDefinition[] = [];
127
+ const registeredRoutes = signal<RouteDefinition[]>([]);
128
+
129
+ const registerRoute = (route: RouteDefinition) => {
130
+ routes.push(route);
131
+ registeredRoutes.set([...routes]);
132
+ };
133
+
134
+ const syncLocation = () => {
135
+ pathname.set(getPathname());
136
+ search.set(getSearch());
137
+ hash.set(getHash());
138
+ };
139
+
140
+ if (typeof window !== "undefined") {
141
+ const eventName = mode === "hash" ? "hashchange" : "popstate";
142
+ window.addEventListener(eventName, syncLocation);
143
+ onCleanup(() => window.removeEventListener(eventName, syncLocation));
144
+ }
145
+
146
+ const navigate = (to: string, options?: { replace?: boolean }) => {
147
+ if (typeof window === "undefined" || !window.history) {
148
+ pathname.set(to);
149
+ return;
150
+ }
151
+
152
+ const target = base ? `${base.replace(/\/$/, "")}/${to.replace(/^\//, "")}` : to;
153
+
154
+ if (mode === "hash") {
155
+ if (options?.replace) {
156
+ window.location.replace(`#${to}`);
157
+ } else {
158
+ window.location.hash = `#${to}`;
159
+ }
160
+ } else {
161
+ if (options?.replace) {
162
+ window.history.replaceState(null, "", target);
163
+ } else {
164
+ window.history.pushState(null, "", target);
165
+ }
166
+ }
167
+ syncLocation();
168
+ };
169
+
170
+ const matchedRoute = computed(() => {
171
+ const currentPath = pathname();
172
+ const routeList = registeredRoutes();
173
+ return matchRoute(routeList, currentPath, base);
174
+ });
175
+
176
+ const params = computed(() => matchedRoute()?.params ?? {});
177
+
178
+ const routerValue: RouterContextValue = {
179
+ location: { pathname, search, hash },
180
+ navigate,
181
+ params,
182
+ base,
183
+ matchedRoute,
184
+ routes,
185
+ registerRoute
186
+ };
187
+
188
+ return RouterContext.Provider({
189
+ value: routerValue,
190
+ children: props.children
191
+ });
192
+ }
193
+
194
+ export interface RouteProps {
195
+ path: string;
196
+ component?: (props: any) => Child;
197
+ children?: Child;
198
+ }
199
+
200
+ /**
201
+ * Route declaration registering URL path pattern and view component
202
+ */
203
+ export function Route(props: RouteProps): Child {
204
+ const router = useContext(RouterContext);
205
+ if (!router) {
206
+ throw new Error("<Route> must be rendered inside a <Router>");
207
+ }
208
+
209
+ const { matcher, paramNames } = compilePath(props.path);
210
+ router.registerRoute({
211
+ path: props.path,
212
+ component: props.component,
213
+ children: props.children,
214
+ matcher,
215
+ paramNames
216
+ });
217
+
218
+ return null;
219
+ }
220
+
221
+ export interface RoutesProps {
222
+ children: Child;
223
+ fallback?: Child;
224
+ }
225
+
226
+ /**
227
+ * Routes outlet matching the current URL to registered Route definitions
228
+ */
229
+ export function Routes(props: RoutesProps): Child {
230
+ const router = useContext(RouterContext);
231
+ if (!router) {
232
+ throw new Error("<Routes> must be rendered inside a <Router>");
233
+ }
234
+
235
+ const _ =
236
+ typeof props.children === "function" ? (props.children as () => Child)() : props.children;
237
+
238
+ return Show({
239
+ when: () => router.matchedRoute() !== null,
240
+ fallback: props.fallback ?? (() => document.createTextNode("404 Not Found")),
241
+ children: () => {
242
+ const match = router.matchedRoute();
243
+ if (!match) return null;
244
+
245
+ if (match.component) {
246
+ return match.component({ params: match.params });
247
+ }
248
+ return match.children ?? null;
249
+ }
250
+ });
251
+ }
252
+
253
+ export interface LinkProps {
254
+ href: string;
255
+ class?: string | (() => string);
256
+ activeClass?: string;
257
+ replace?: boolean;
258
+ children: Child;
259
+ [key: string]: any;
260
+ }
261
+
262
+ /**
263
+ * Accessible client-side navigation Link with active state highlighting
264
+ */
265
+ export function Link(props: LinkProps): Child {
266
+ const router = useContext(RouterContext);
267
+ const { href, class: className, activeClass = "active", replace, children, ...rest } = props;
268
+
269
+ const handleClick = (e: MouseEvent) => {
270
+ if (e.ctrlKey || e.metaKey || e.altKey || e.shiftKey || e.button !== 0) {
271
+ return;
272
+ }
273
+
274
+ e.preventDefault();
275
+ if (router) {
276
+ router.navigate(href, { replace });
277
+ } else if (typeof window !== "undefined") {
278
+ window.location.href = href;
279
+ }
280
+ };
281
+
282
+ const isActive = computed(() => {
283
+ if (!router) return false;
284
+ const current = router.location.pathname();
285
+ return current === href || (href !== "/" && current.startsWith(href));
286
+ });
287
+
288
+ const computedClass = computed(() => {
289
+ const baseCls = typeof className === "function" ? className() : (className ?? "");
290
+ if (activeClass && isActive()) {
291
+ return baseCls ? `${baseCls} ${activeClass}` : activeClass;
292
+ }
293
+ return baseCls;
294
+ });
295
+
296
+ return (
297
+ <a href={href} class={computedClass} onClick={handleClick} {...rest}>
298
+ {children}
299
+ </a>
300
+ );
301
+ }
302
+
303
+ /**
304
+ * Access the active router location state signals
305
+ */
306
+ export function useLocation(): LocationState {
307
+ const router = useContext(RouterContext);
308
+ if (!router) throw new Error("useLocation must be used within a <Router>");
309
+ return router.location;
310
+ }
311
+
312
+ /**
313
+ * Access the imperative navigate function
314
+ */
315
+ export function useNavigate(): (to: string, options?: { replace?: boolean }) => void {
316
+ const router = useContext(RouterContext);
317
+ if (!router) throw new Error("useNavigate must be used within a <Router>");
318
+ return router.navigate;
319
+ }
320
+
321
+ /**
322
+ * Access reactive URL parameter signals
323
+ */
324
+ export function useParams<T extends Record<string, string> = Record<string, string>>(): () => T {
325
+ const router = useContext(RouterContext);
326
+ if (!router) throw new Error("useParams must be used within a <Router>");
327
+ return () => router.params() as T;
328
+ }
329
+
330
+ /**
331
+ * Access and manipulate URL query search parameters reactively
332
+ */
333
+ export function useSearchParams(): [
334
+ () => URLSearchParams,
335
+ (params: Record<string, string> | URLSearchParams) => void
336
+ ] {
337
+ const router = useContext(RouterContext);
338
+ if (!router) throw new Error("useSearchParams must be used within a <Router>");
339
+
340
+ const params = computed(() => new URLSearchParams(router.location.search()));
341
+
342
+ const setSearchParams = (next: Record<string, string> | URLSearchParams) => {
343
+ const sp = next instanceof URLSearchParams ? next : new URLSearchParams(next);
344
+ const queryString = sp.toString();
345
+ const newUrl = `${router.location.pathname()}${queryString ? `?${queryString}` : ""}${router.location.hash()}`;
346
+ router.navigate(newUrl);
347
+ };
348
+
349
+ return [params, setSearchParams];
350
+ }
351
+
352
+ export { lazy } from "../dom";
@@ -0,0 +1,317 @@
1
+ import { createMutation } from "../query/create-mutation";
2
+ import { createQuery } from "../query/create-query";
3
+ import { RPCError } from "./error";
4
+ import { matchesRulePattern } from "./server";
5
+ import type {
6
+ ClientOptions,
7
+ ClientRouter,
8
+ Router,
9
+ RPCResponse,
10
+ SubscriptionCallbacks,
11
+ SubscriptionHandle
12
+ } from "./types";
13
+
14
+ async function resolveHeaders(
15
+ headers?:
16
+ | Record<string, string>
17
+ | Record<string, any>
18
+ | (() => Promise<Record<string, string>> | Record<string, string>),
19
+ fullPath = ""
20
+ ): Promise<Record<string, string>> {
21
+ if (!headers) return {};
22
+ if (typeof headers === "function") {
23
+ return await headers();
24
+ }
25
+
26
+ const result: Record<string, string> = {};
27
+ for (const [key, val] of Object.entries(headers)) {
28
+ if (typeof val === "object" || typeof val === "function") {
29
+ if (matchesRulePattern(key, fullPath, fullPath)) {
30
+ const resolved = typeof val === "function" ? await val() : val;
31
+ if (resolved && typeof resolved === "object") {
32
+ Object.assign(result, resolved);
33
+ }
34
+ }
35
+ } else if (typeof val === "string") {
36
+ result[key] = val;
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+
42
+ function createProxyPath(options: ClientOptions, path: string[] = []): any {
43
+ return new Proxy(
44
+ {},
45
+ {
46
+ get(_target, prop: string) {
47
+ if (prop === "then") return undefined;
48
+
49
+ if (prop === "query") {
50
+ return async (
51
+ input?: unknown,
52
+ callOpts?: { signal?: AbortSignal; headers?: Record<string, string> }
53
+ ) => {
54
+ const fetchFn = options.fetch ?? globalThis.fetch;
55
+ const baseUrl = options.baseUrl.replace(/\/+$/, "");
56
+ const fullPath = path.join(".");
57
+ let url = `${baseUrl}/${fullPath}`;
58
+
59
+ if (input !== undefined) {
60
+ const serialized = JSON.stringify(input);
61
+ url += `${url.includes("?") ? "&" : "?"}input=${encodeURIComponent(serialized)}`;
62
+ }
63
+
64
+ const dynamicHeaders = await resolveHeaders(options.headers, fullPath);
65
+ const headers = {
66
+ Accept: "application/json",
67
+ ...dynamicHeaders,
68
+ ...callOpts?.headers
69
+ };
70
+
71
+ const devtools =
72
+ typeof window !== "undefined" ? (window as any).__VISAGE_DEVTOOLS__ : null;
73
+ const logId = devtools?.logRpcCall({
74
+ id: `rpc_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
75
+ path: fullPath,
76
+ type: "query",
77
+ input
78
+ });
79
+
80
+ try {
81
+ const res = await fetchFn(url, {
82
+ method: "GET",
83
+ headers,
84
+ signal: callOpts?.signal
85
+ });
86
+
87
+ const body: RPCResponse = await res.json();
88
+ if ("error" in body) {
89
+ devtools?.updateRpcCall(logId, { status: "error", error: body.error });
90
+ throw new RPCError({
91
+ code: body.error.code,
92
+ message: body.error.message,
93
+ issues: body.error.issues
94
+ });
95
+ }
96
+
97
+ devtools?.updateRpcCall(logId, { status: "success", response: body.result.data });
98
+ return body.result.data;
99
+ } catch (err) {
100
+ devtools?.updateRpcCall(logId, { status: "error", error: err });
101
+ throw err;
102
+ }
103
+ };
104
+ }
105
+
106
+ if (prop === "mutate") {
107
+ return async (
108
+ input?: unknown,
109
+ callOpts?: { signal?: AbortSignal; headers?: Record<string, string> }
110
+ ) => {
111
+ const fetchFn = options.fetch ?? globalThis.fetch;
112
+ const baseUrl = options.baseUrl.replace(/\/+$/, "");
113
+ const fullPath = path.join(".");
114
+ const url = `${baseUrl}/${fullPath}`;
115
+
116
+ const dynamicHeaders = await resolveHeaders(options.headers, fullPath);
117
+ const headers = {
118
+ "Content-Type": "application/json",
119
+ Accept: "application/json",
120
+ ...dynamicHeaders,
121
+ ...callOpts?.headers
122
+ };
123
+
124
+ const devtools =
125
+ typeof window !== "undefined" ? (window as any).__VISAGE_DEVTOOLS__ : null;
126
+ const logId = devtools?.logRpcCall({
127
+ id: `rpc_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
128
+ path: fullPath,
129
+ type: "mutation",
130
+ input
131
+ });
132
+
133
+ try {
134
+ const res = await fetchFn(url, {
135
+ method: "POST",
136
+ headers,
137
+ body: JSON.stringify({ input }),
138
+ signal: callOpts?.signal
139
+ });
140
+
141
+ const body: RPCResponse = await res.json();
142
+ if ("error" in body) {
143
+ devtools?.updateRpcCall(logId, { status: "error", error: body.error });
144
+ throw new RPCError({
145
+ code: body.error.code,
146
+ message: body.error.message,
147
+ issues: body.error.issues
148
+ });
149
+ }
150
+
151
+ devtools?.updateRpcCall(logId, { status: "success", response: body.result.data });
152
+ return body.result.data;
153
+ } catch (err) {
154
+ devtools?.updateRpcCall(logId, { status: "error", error: err });
155
+ throw err;
156
+ }
157
+ };
158
+ }
159
+
160
+ if (prop === "subscribe") {
161
+ return (input: unknown, callbacks: SubscriptionCallbacks): SubscriptionHandle => {
162
+ const fetchFn = options.fetch ?? globalThis.fetch;
163
+ const baseUrl = options.baseUrl.replace(/\/+$/, "");
164
+ const fullPath = path.join(".");
165
+ let url = `${baseUrl}/${fullPath}`;
166
+
167
+ if (input !== undefined) {
168
+ const serialized = JSON.stringify(input);
169
+ url += `${url.includes("?") ? "&" : "?"}input=${encodeURIComponent(serialized)}`;
170
+ }
171
+
172
+ const controller = new AbortController();
173
+ const devtools =
174
+ typeof window !== "undefined" ? (window as any).__VISAGE_DEVTOOLS__ : null;
175
+ const logId = devtools?.logRpcCall({
176
+ id: `rpc_${Date.now()}_${Math.random().toString(36).slice(2, 6)}`,
177
+ path: fullPath,
178
+ type: "subscription",
179
+ input
180
+ });
181
+
182
+ (async () => {
183
+ try {
184
+ const dynamicHeaders = await resolveHeaders(options.headers, fullPath);
185
+ const headers = {
186
+ Accept: "text/event-stream",
187
+ ...dynamicHeaders
188
+ };
189
+
190
+ const res = await fetchFn(url, {
191
+ method: "GET",
192
+ headers,
193
+ signal: controller.signal
194
+ });
195
+
196
+ if (!res.ok || !res.body) {
197
+ throw new Error(`Subscription connection failed with status ${res.status}`);
198
+ }
199
+
200
+ const reader = res.body.getReader();
201
+ const decoder = new TextDecoder();
202
+ let buffer = "";
203
+
204
+ while (true) {
205
+ const { done, value } = await reader.read();
206
+ if (done) break;
207
+
208
+ buffer += decoder.decode(value, { stream: true });
209
+ const lines = buffer.split("\n\n");
210
+ buffer = lines.pop() ?? "";
211
+
212
+ for (const line of lines) {
213
+ const trimmed = line.trim();
214
+ if (trimmed.startsWith("data:")) {
215
+ const jsonStr = trimmed.slice(5).trim();
216
+ try {
217
+ const parsed = JSON.parse(jsonStr);
218
+ devtools?.logRpcEvent(logId, parsed);
219
+ callbacks.onData(parsed);
220
+ } catch (parseErr) {
221
+ devtools?.updateRpcCall(logId, { status: "error", error: parseErr });
222
+ callbacks.onError?.(parseErr);
223
+ }
224
+ } else if (trimmed.startsWith("event: error")) {
225
+ devtools?.updateRpcCall(logId, { status: "error" });
226
+ callbacks.onError?.(new Error("Server event stream error"));
227
+ }
228
+ }
229
+ }
230
+
231
+ callbacks.onComplete?.();
232
+ } catch (err: any) {
233
+ if (!controller.signal.aborted) {
234
+ callbacks.onError?.(err);
235
+ }
236
+ }
237
+ })();
238
+
239
+ return {
240
+ unsubscribe: () => {
241
+ controller.abort();
242
+ }
243
+ };
244
+ };
245
+ }
246
+
247
+ if (prop === "queryKey") {
248
+ return (input?: unknown) => {
249
+ const resolvedInput = typeof input === "function" ? (input as () => unknown)() : input;
250
+ const key: unknown[] = [...path];
251
+ if (resolvedInput !== undefined) {
252
+ key.push(resolvedInput);
253
+ }
254
+ return key;
255
+ };
256
+ }
257
+
258
+ if (prop === "queryOptions") {
259
+ return (input?: unknown, extra?: Record<string, unknown>) => {
260
+ const resolvedInput = typeof input === "function" ? (input as () => unknown)() : input;
261
+ const key: unknown[] = [...path];
262
+ if (resolvedInput !== undefined) {
263
+ key.push(resolvedInput);
264
+ }
265
+ return {
266
+ queryKey: key,
267
+ queryFn: ({ signal }: { signal: AbortSignal }) => {
268
+ const queryFn = createProxyPath(options, path).query;
269
+ return queryFn(resolvedInput, { signal });
270
+ },
271
+ ...extra
272
+ };
273
+ };
274
+ }
275
+
276
+ if (prop === "createQuery" || prop === "useQuery") {
277
+ return (input?: unknown, extra?: Record<string, unknown>) => {
278
+ const proxy = createProxyPath(options, path);
279
+ return createQuery(() => proxy.queryOptions(input, extra));
280
+ };
281
+ }
282
+
283
+ if (prop === "mutationKey") {
284
+ return () => [...path];
285
+ }
286
+
287
+ if (prop === "mutationOptions") {
288
+ return (extra?: Record<string, unknown>) => {
289
+ return {
290
+ mutationKey: [...path],
291
+ mutationFn: (input: unknown) => {
292
+ const mutateFn = createProxyPath(options, path).mutate;
293
+ return mutateFn(input);
294
+ },
295
+ ...extra
296
+ };
297
+ };
298
+ }
299
+
300
+ if (prop === "createMutation" || prop === "useMutation") {
301
+ return (extra?: Record<string, unknown>) => {
302
+ const proxy = createProxyPath(options, path);
303
+ return createMutation(proxy.mutationOptions(extra));
304
+ };
305
+ }
306
+
307
+ return createProxyPath(options, [...path, prop]);
308
+ }
309
+ }
310
+ );
311
+ }
312
+
313
+ export function createClient<TRouter extends Router<any, any>>(
314
+ options: ClientOptions
315
+ ): ClientRouter<TRouter> {
316
+ return createProxyPath(options, []) as ClientRouter<TRouter>;
317
+ }