@zap-js/client 0.0.2 → 0.0.5

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 (115) hide show
  1. package/README.md +310 -24
  2. package/bin/zap +0 -0
  3. package/bin/zap-codegen +0 -0
  4. package/dist/cli/commands/build.d.ts +11 -0
  5. package/dist/cli/commands/build.js +282 -0
  6. package/dist/cli/commands/codegen.d.ts +8 -0
  7. package/dist/cli/commands/codegen.js +95 -0
  8. package/dist/cli/commands/dev.d.ts +20 -0
  9. package/dist/cli/commands/dev.js +78 -0
  10. package/dist/cli/commands/new.d.ts +9 -0
  11. package/dist/cli/commands/new.js +307 -0
  12. package/dist/cli/commands/routes-old.d.ts +9 -0
  13. package/dist/cli/commands/routes-old.js +106 -0
  14. package/dist/cli/commands/routes.d.ts +11 -0
  15. package/dist/cli/commands/routes.js +280 -0
  16. package/dist/cli/commands/serve.d.ts +17 -0
  17. package/dist/cli/commands/serve.js +386 -0
  18. package/dist/cli/index.d.ts +2 -0
  19. package/dist/cli/index.js +76 -0
  20. package/dist/cli/utils/index.d.ts +2 -0
  21. package/dist/cli/utils/index.js +2 -0
  22. package/dist/cli/utils/logger.d.ts +84 -0
  23. package/dist/cli/utils/logger.js +181 -0
  24. package/dist/cli/utils/port-finder.d.ts +8 -0
  25. package/dist/cli/utils/port-finder.js +48 -0
  26. package/dist/dev-server/codegen-runner.d.ts +41 -0
  27. package/dist/dev-server/codegen-runner.js +172 -0
  28. package/dist/dev-server/hot-reload.d.ts +72 -0
  29. package/dist/dev-server/hot-reload.js +280 -0
  30. package/dist/dev-server/index.d.ts +8 -0
  31. package/dist/dev-server/index.js +8 -0
  32. package/dist/dev-server/route-scanner.d.ts +84 -0
  33. package/dist/dev-server/route-scanner.js +113 -0
  34. package/dist/dev-server/rust-builder.d.ts +66 -0
  35. package/dist/dev-server/rust-builder.js +286 -0
  36. package/dist/dev-server/server.d.ts +147 -0
  37. package/dist/dev-server/server.js +660 -0
  38. package/dist/dev-server/vite-proxy.d.ts +56 -0
  39. package/dist/dev-server/vite-proxy.js +212 -0
  40. package/dist/dev-server/watcher.d.ts +48 -0
  41. package/dist/dev-server/watcher.js +127 -0
  42. package/dist/router/codegen-enhanced.d.ts +5 -0
  43. package/dist/router/codegen-enhanced.js +275 -0
  44. package/dist/router/codegen.d.ts +17 -0
  45. package/dist/router/codegen.js +654 -0
  46. package/dist/router/index.d.ts +16 -0
  47. package/dist/router/index.js +19 -0
  48. package/dist/router/scanner.d.ts +86 -0
  49. package/dist/router/scanner.js +689 -0
  50. package/dist/router/ssg.d.ts +115 -0
  51. package/dist/router/ssg.js +202 -0
  52. package/dist/router/types.d.ts +124 -0
  53. package/dist/router/types.js +9 -0
  54. package/dist/router/watch.d.ts +38 -0
  55. package/dist/router/watch.js +135 -0
  56. package/dist/runtime/csrf.d.ts +146 -0
  57. package/dist/runtime/csrf.js +166 -0
  58. package/dist/runtime/error-boundary.d.ts +129 -0
  59. package/dist/runtime/error-boundary.js +287 -0
  60. package/dist/runtime/hooks.d.ts +83 -0
  61. package/dist/runtime/hooks.js +96 -0
  62. package/dist/runtime/index.d.ts +229 -0
  63. package/dist/runtime/index.js +449 -0
  64. package/dist/runtime/ipc-client.d.ts +144 -0
  65. package/dist/runtime/ipc-client.js +621 -0
  66. package/dist/runtime/logger.d.ts +71 -0
  67. package/dist/runtime/logger.js +164 -0
  68. package/dist/runtime/middleware.d.ts +66 -0
  69. package/dist/runtime/middleware.js +114 -0
  70. package/dist/runtime/process-manager.d.ts +51 -0
  71. package/dist/runtime/process-manager.js +207 -0
  72. package/dist/runtime/router-simple.d.ts +98 -0
  73. package/dist/runtime/router-simple.js +330 -0
  74. package/dist/runtime/router.d.ts +103 -0
  75. package/dist/runtime/router.js +435 -0
  76. package/dist/runtime/rpc-client.d.ts +35 -0
  77. package/dist/runtime/rpc-client.js +140 -0
  78. package/dist/runtime/streaming-utils.d.ts +86 -0
  79. package/dist/runtime/streaming-utils.js +150 -0
  80. package/dist/runtime/types.d.ts +465 -0
  81. package/dist/runtime/types.js +60 -0
  82. package/dist/runtime/websockets-utils.d.ts +50 -0
  83. package/dist/runtime/websockets-utils.js +92 -0
  84. package/package.json +30 -20
  85. package/index.js +0 -29
  86. package/internal/cli/package.json +0 -46
  87. package/internal/cli/tsconfig.tsbuildinfo +0 -1
  88. package/internal/dev-server/node_modules/ora/index.d.ts +0 -332
  89. package/internal/dev-server/node_modules/ora/index.js +0 -416
  90. package/internal/dev-server/node_modules/ora/license +0 -9
  91. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.d.ts +0 -36
  92. package/internal/dev-server/node_modules/ora/node_modules/string-width/index.js +0 -65
  93. package/internal/dev-server/node_modules/ora/node_modules/string-width/license +0 -9
  94. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/LICENSE-MIT.txt +0 -20
  95. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/README.md +0 -107
  96. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.d.ts +0 -3
  97. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.js +0 -4
  98. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/index.mjs +0 -4
  99. package/internal/dev-server/node_modules/ora/node_modules/string-width/node_modules/emoji-regex/package.json +0 -46
  100. package/internal/dev-server/node_modules/ora/node_modules/string-width/package.json +0 -60
  101. package/internal/dev-server/node_modules/ora/node_modules/string-width/readme.md +0 -62
  102. package/internal/dev-server/node_modules/ora/package.json +0 -66
  103. package/internal/dev-server/node_modules/ora/readme.md +0 -325
  104. package/internal/dev-server/package.json +0 -41
  105. package/internal/router/package.json +0 -28
  106. package/internal/runtime/package.json +0 -41
  107. package/internal/runtime/src/error-boundary.tsx +0 -476
  108. package/internal/runtime/src/router-simple.tsx +0 -640
  109. package/internal/runtime/src/router.tsx +0 -771
  110. package/internal/runtime/tsconfig.tsbuildinfo +0 -1
  111. package/src/errors.js +0 -33
  112. package/src/logger.js +0 -10
  113. package/src/middleware.js +0 -32
  114. package/src/router.js +0 -41
  115. package/src/types.js +0 -39
@@ -0,0 +1,435 @@
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ /**
3
+ * ZapJS Production Router
4
+ *
5
+ * Features:
6
+ * - Nested layouts
7
+ * - Route-level middleware
8
+ * - Code splitting
9
+ * - Error boundaries
10
+ * - Type-safe navigation
11
+ */
12
+ import React, { createContext, useContext, useState, useEffect, useCallback, useMemo, useTransition, Suspense, memo, } from 'react';
13
+ import { composeMiddleware } from './middleware.js';
14
+ const RouterContext = createContext(null);
15
+ // ============================================================================
16
+ // Route Matching
17
+ // ============================================================================
18
+ function matchRoute(pathname, routes) {
19
+ const normalizedPath = pathname === '' ? '/' : pathname;
20
+ for (const route of routes) {
21
+ const match = normalizedPath.match(route.pattern);
22
+ if (match) {
23
+ const params = {};
24
+ route.paramNames.forEach((name, index) => {
25
+ const value = match[index + 1];
26
+ if (value !== undefined && value !== '') {
27
+ params[name] = decodeURIComponent(value);
28
+ }
29
+ });
30
+ return { route, params, pathname: normalizedPath };
31
+ }
32
+ }
33
+ return null;
34
+ }
35
+ function parseUrl(url) {
36
+ try {
37
+ const parsed = new URL(url, window.location.origin);
38
+ return {
39
+ pathname: parsed.pathname,
40
+ search: parsed.search,
41
+ hash: parsed.hash,
42
+ };
43
+ }
44
+ catch {
45
+ const hashIndex = url.indexOf('#');
46
+ const searchIndex = url.indexOf('?');
47
+ let pathname = url;
48
+ let search = '';
49
+ let hash = '';
50
+ if (hashIndex !== -1) {
51
+ hash = url.slice(hashIndex);
52
+ pathname = url.slice(0, hashIndex);
53
+ }
54
+ if (searchIndex !== -1 && (hashIndex === -1 || searchIndex < hashIndex)) {
55
+ search = pathname.slice(searchIndex, hashIndex !== -1 ? hashIndex - searchIndex : undefined);
56
+ pathname = pathname.slice(0, searchIndex);
57
+ }
58
+ return { pathname: pathname || '/', search, hash };
59
+ }
60
+ }
61
+ // ============================================================================
62
+ // Middleware Runner
63
+ // ============================================================================
64
+ async function runRouteMiddleware(match, pathname, search, hash, state) {
65
+ if (!match.route.middleware) {
66
+ return { allowed: true };
67
+ }
68
+ try {
69
+ const middlewares = await match.route.middleware();
70
+ const composedMiddleware = composeMiddleware(middlewares);
71
+ const context = {
72
+ match,
73
+ pathname,
74
+ search,
75
+ hash,
76
+ state,
77
+ };
78
+ const result = await composedMiddleware(context);
79
+ switch (result.type) {
80
+ case 'continue':
81
+ return { allowed: true, data: result.data };
82
+ case 'redirect':
83
+ return { allowed: false, redirectTo: result.redirectTo };
84
+ case 'block':
85
+ return { allowed: false, error: result.error };
86
+ default:
87
+ return { allowed: true };
88
+ }
89
+ }
90
+ catch (error) {
91
+ console.error('Middleware execution failed:', error);
92
+ return { allowed: true }; // Allow navigation on middleware error
93
+ }
94
+ }
95
+ const LayoutWrapper = memo(({ layouts, layoutPath, children }) => {
96
+ if (!layoutPath) {
97
+ return _jsx(_Fragment, { children: children });
98
+ }
99
+ const layoutChain = [];
100
+ let currentPath = layoutPath;
101
+ while (currentPath) {
102
+ const layout = layouts.find(l => l.path === currentPath);
103
+ if (layout) {
104
+ layoutChain.unshift(layout);
105
+ currentPath = layout.parentLayout;
106
+ }
107
+ else {
108
+ break;
109
+ }
110
+ }
111
+ return layoutChain.reduce((content, layout) => {
112
+ const LayoutComponent = layout.component;
113
+ return (_jsx(Suspense, { fallback: _jsx("div", { children: "Loading layout..." }), children: _jsx(LayoutComponent, { children: content }) }));
114
+ }, children);
115
+ });
116
+ LayoutWrapper.displayName = 'LayoutWrapper';
117
+ export function RouterProvider({ routes, layouts = [], children, notFound: NotFound, fallback = null, onRouteError, }) {
118
+ const [isPending, startTransition] = useTransition();
119
+ const [state, setState] = useState(() => {
120
+ const { pathname, search, hash } = parseUrl(window.location.href);
121
+ return {
122
+ pathname,
123
+ search,
124
+ hash,
125
+ match: matchRoute(pathname, routes),
126
+ };
127
+ });
128
+ const navigate = useCallback(async (path, options = {}) => {
129
+ const { replace = false, scroll = true } = options;
130
+ const { pathname, search, hash } = parseUrl(path);
131
+ // Find route match
132
+ const match = matchRoute(pathname, routes);
133
+ if (match) {
134
+ // Run middleware
135
+ const { allowed, redirectTo, error, data } = await runRouteMiddleware(match, pathname, search, hash, options.state);
136
+ if (!allowed) {
137
+ if (redirectTo) {
138
+ // Redirect instead
139
+ navigate(redirectTo, { replace: true });
140
+ return;
141
+ }
142
+ if (error) {
143
+ onRouteError?.(error);
144
+ throw error;
145
+ }
146
+ return;
147
+ }
148
+ // Update URL
149
+ const url = pathname + search + hash;
150
+ if (replace) {
151
+ window.history.replaceState(options.state ?? null, '', url);
152
+ }
153
+ else {
154
+ window.history.pushState(options.state ?? null, '', url);
155
+ }
156
+ // Update state with middleware data
157
+ startTransition(() => {
158
+ setState({
159
+ pathname,
160
+ search,
161
+ hash,
162
+ match,
163
+ middlewareData: data,
164
+ });
165
+ });
166
+ if (scroll) {
167
+ if (hash) {
168
+ const element = document.querySelector(hash);
169
+ element?.scrollIntoView();
170
+ }
171
+ else {
172
+ window.scrollTo(0, 0);
173
+ }
174
+ }
175
+ }
176
+ else {
177
+ // No match - update anyway to show 404
178
+ const url = pathname + search + hash;
179
+ if (replace) {
180
+ window.history.replaceState(options.state ?? null, '', url);
181
+ }
182
+ else {
183
+ window.history.pushState(options.state ?? null, '', url);
184
+ }
185
+ startTransition(() => {
186
+ setState({
187
+ pathname,
188
+ search,
189
+ hash,
190
+ match: null,
191
+ });
192
+ });
193
+ }
194
+ }, [routes, onRouteError]);
195
+ const router = useMemo(() => ({
196
+ push: (path, options) => navigate(path, options),
197
+ replace: (path, options) => navigate(path, { ...options, replace: true }),
198
+ back: () => window.history.back(),
199
+ forward: () => window.history.forward(),
200
+ refresh: () => {
201
+ startTransition(() => {
202
+ setState((prev) => ({ ...prev, match: matchRoute(prev.pathname, routes) }));
203
+ });
204
+ },
205
+ prefetch: (path) => {
206
+ const { pathname } = parseUrl(path);
207
+ const match = matchRoute(pathname, routes);
208
+ if (match?.route.component) {
209
+ const component = match.route.component;
210
+ if (component._payload && component._init) {
211
+ try {
212
+ component._init(component._payload);
213
+ }
214
+ catch {
215
+ // Component will load when rendered
216
+ }
217
+ }
218
+ }
219
+ },
220
+ }), [navigate, routes]);
221
+ useEffect(() => {
222
+ const handlePopState = async () => {
223
+ const { pathname, search, hash } = parseUrl(window.location.href);
224
+ const match = matchRoute(pathname, routes);
225
+ if (match) {
226
+ // Run middleware for browser navigation too
227
+ const { allowed, redirectTo, error, data } = await runRouteMiddleware(match, pathname, search, hash);
228
+ if (!allowed) {
229
+ if (redirectTo) {
230
+ navigate(redirectTo, { replace: true });
231
+ return;
232
+ }
233
+ if (error) {
234
+ onRouteError?.(error);
235
+ return;
236
+ }
237
+ }
238
+ startTransition(() => {
239
+ setState({
240
+ pathname,
241
+ search,
242
+ hash,
243
+ match,
244
+ middlewareData: data,
245
+ });
246
+ });
247
+ }
248
+ else {
249
+ startTransition(() => {
250
+ setState({
251
+ pathname,
252
+ search,
253
+ hash,
254
+ match: null,
255
+ });
256
+ });
257
+ }
258
+ };
259
+ window.addEventListener('popstate', handlePopState);
260
+ return () => window.removeEventListener('popstate', handlePopState);
261
+ }, [routes, navigate, onRouteError]);
262
+ // Update document meta on route change
263
+ useEffect(() => {
264
+ if (state.match?.route.meta) {
265
+ state.match.route.meta().then((meta) => {
266
+ if (meta.title) {
267
+ document.title = meta.title;
268
+ }
269
+ if (meta.description) {
270
+ const metaDesc = document.querySelector('meta[name="description"]');
271
+ if (metaDesc) {
272
+ metaDesc.setAttribute('content', meta.description);
273
+ }
274
+ }
275
+ }).catch(() => {
276
+ // Ignore meta errors
277
+ });
278
+ }
279
+ }, [state.match]);
280
+ const contextValue = useMemo(() => ({
281
+ state,
282
+ router,
283
+ routes,
284
+ layouts,
285
+ isPending,
286
+ }), [state, router, routes, layouts, isPending]);
287
+ return (_jsx(RouterContext.Provider, { value: contextValue, children: _jsx(Suspense, { fallback: fallback, children: children }) }));
288
+ }
289
+ // ============================================================================
290
+ // Hooks
291
+ // ============================================================================
292
+ export function useRouter() {
293
+ const context = useContext(RouterContext);
294
+ if (!context) {
295
+ throw new Error('useRouter must be used within a RouterProvider');
296
+ }
297
+ return context.router;
298
+ }
299
+ export function useParams() {
300
+ const context = useContext(RouterContext);
301
+ if (!context) {
302
+ throw new Error('useParams must be used within a RouterProvider');
303
+ }
304
+ return (context.state.match?.params ?? {});
305
+ }
306
+ export function usePathname() {
307
+ const context = useContext(RouterContext);
308
+ if (!context) {
309
+ throw new Error('usePathname must be used within a RouterProvider');
310
+ }
311
+ return context.state.pathname;
312
+ }
313
+ export function useSearchParams() {
314
+ const context = useContext(RouterContext);
315
+ if (!context) {
316
+ throw new Error('useSearchParams must be used within a RouterProvider');
317
+ }
318
+ const searchParams = useMemo(() => new URLSearchParams(context.state.search), [context.state.search]);
319
+ const setSearchParams = useCallback((params) => {
320
+ const newParams = new URLSearchParams(params);
321
+ const newSearch = newParams.toString();
322
+ const path = context.state.pathname + (newSearch ? `?${newSearch}` : '') + context.state.hash;
323
+ context.router.push(path, { scroll: false });
324
+ }, [context.router, context.state.pathname, context.state.hash]);
325
+ return [searchParams, setSearchParams];
326
+ }
327
+ export function useMiddlewareData() {
328
+ const context = useContext(RouterContext);
329
+ if (!context) {
330
+ throw new Error('useMiddlewareData must be used within a RouterProvider');
331
+ }
332
+ return context.state.middlewareData;
333
+ }
334
+ export function useRouteMatch() {
335
+ const context = useContext(RouterContext);
336
+ if (!context) {
337
+ throw new Error('useRouteMatch must be used within a RouterProvider');
338
+ }
339
+ return context.state.match;
340
+ }
341
+ export function useIsPending() {
342
+ const context = useContext(RouterContext);
343
+ if (!context) {
344
+ throw new Error('useIsPending must be used within a RouterProvider');
345
+ }
346
+ return context.isPending;
347
+ }
348
+ export function Link({ to, replace = false, prefetch = true, scroll = true, children, onClick, onMouseEnter, ...props }) {
349
+ const context = useContext(RouterContext);
350
+ const handleClick = useCallback((e) => {
351
+ onClick?.(e);
352
+ if (e.defaultPrevented ||
353
+ e.button !== 0 ||
354
+ e.metaKey ||
355
+ e.ctrlKey ||
356
+ e.shiftKey ||
357
+ e.altKey) {
358
+ return;
359
+ }
360
+ const href = to;
361
+ if (href.startsWith('http://') || href.startsWith('https://') || href.startsWith('//')) {
362
+ return;
363
+ }
364
+ e.preventDefault();
365
+ context?.router[replace ? 'replace' : 'push'](to, { scroll });
366
+ }, [context?.router, to, replace, scroll, onClick]);
367
+ const handleMouseEnter = useCallback((e) => {
368
+ onMouseEnter?.(e);
369
+ if (prefetch && context) {
370
+ context.router.prefetch(to);
371
+ }
372
+ }, [context, to, prefetch, onMouseEnter]);
373
+ return (_jsx("a", { href: to, onClick: handleClick, onMouseEnter: handleMouseEnter, ...props, children: children }));
374
+ }
375
+ export function Outlet({ notFound: NotFound, fallback = null }) {
376
+ const context = useContext(RouterContext);
377
+ if (!context) {
378
+ throw new Error('Outlet must be used within a RouterProvider');
379
+ }
380
+ const { match, middlewareData } = context.state;
381
+ if (!match) {
382
+ return NotFound ? _jsx(NotFound, {}) : null;
383
+ }
384
+ const { route, params } = match;
385
+ const Component = route.component;
386
+ const ErrorComponent = route.errorComponent;
387
+ const PendingComponent = route.pendingComponent;
388
+ const routeElement = (_jsx(Suspense, { fallback: PendingComponent ? _jsx(PendingComponent, {}) : fallback, children: _jsx(Component, { params: params, ...middlewareData }) }));
389
+ const wrappedElement = (_jsx(LayoutWrapper, { layouts: context.layouts, layoutPath: route.layoutPath, children: ErrorComponent ? (_jsx(RouteErrorBoundary, { fallback: _jsx(ErrorComponent, {}), children: routeElement })) : routeElement }));
390
+ return wrappedElement;
391
+ }
392
+ class RouteErrorBoundary extends React.Component {
393
+ constructor(props) {
394
+ super(props);
395
+ this.state = { hasError: false, error: null };
396
+ }
397
+ static getDerivedStateFromError(error) {
398
+ return { hasError: true, error };
399
+ }
400
+ componentDidCatch(error, errorInfo) {
401
+ console.error('Route error:', error, errorInfo);
402
+ }
403
+ render() {
404
+ if (this.state.hasError) {
405
+ return this.props.fallback;
406
+ }
407
+ return this.props.children;
408
+ }
409
+ }
410
+ export function NavLink({ to, activeClassName, activeStyle, exact = false, pending = false, pendingClassName, pendingStyle, className, style, ...props }) {
411
+ const pathname = usePathname();
412
+ const isPending = useIsPending();
413
+ const isActive = exact
414
+ ? pathname === to
415
+ : pathname.startsWith(to) && (to === '/' ? pathname === '/' : true);
416
+ const isPendingRoute = pending && isPending;
417
+ const combinedClassName = [
418
+ className,
419
+ isActive && activeClassName,
420
+ isPendingRoute && pendingClassName,
421
+ ].filter(Boolean).join(' ').trim() || undefined;
422
+ const combinedStyle = {
423
+ ...style,
424
+ ...(isActive ? activeStyle : {}),
425
+ ...(isPendingRoute ? pendingStyle : {}),
426
+ };
427
+ return (_jsx(Link, { to: to, className: combinedClassName, style: combinedStyle, ...props }));
428
+ }
429
+ export function Redirect({ to, replace = true }) {
430
+ const router = useRouter();
431
+ useEffect(() => {
432
+ router[replace ? 'replace' : 'push'](to);
433
+ }, [router, to, replace]);
434
+ return null;
435
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * RPC Client for calling Rust server functions from TypeScript
3
+ */
4
+ import { IpcClient } from './ipc-client.js';
5
+ /**
6
+ * Custom error class for RPC errors
7
+ */
8
+ export declare class RpcError extends Error {
9
+ readonly errorType: string;
10
+ constructor(errorType: string, message: string);
11
+ }
12
+ /**
13
+ * Initialize the RPC client with a socket path
14
+ */
15
+ export declare function initRpcClient(socketPath: string): void;
16
+ /**
17
+ * Call a Rust server function via RPC
18
+ */
19
+ export declare function rpcCall<T = unknown>(functionName: string, params?: Record<string, unknown>, timeoutMs?: number): Promise<T>;
20
+ /**
21
+ * Wait for a response from a specific request
22
+ */
23
+ export declare function waitForResponse<T = unknown>(requestId: string, timeoutMs?: number): Promise<T>;
24
+ /**
25
+ * Close the RPC client connection
26
+ */
27
+ export declare function closeRpcClient(): Promise<void>;
28
+ /**
29
+ * Check if RPC client is initialized
30
+ */
31
+ export declare function isRpcClientInitialized(): boolean;
32
+ /**
33
+ * Get current RPC client instance (for advanced usage)
34
+ */
35
+ export declare function getRpcClient(): IpcClient | null;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * RPC Client for calling Rust server functions from TypeScript
3
+ */
4
+ import { IpcClient } from './ipc-client.js';
5
+ import { isRpcResponseMessage, isRpcErrorMessage } from './types.js';
6
+ let ipcClient = null;
7
+ let requestCounter = 0;
8
+ const pendingRequests = new Map();
9
+ /**
10
+ * Custom error class for RPC errors
11
+ */
12
+ export class RpcError extends Error {
13
+ constructor(errorType, message) {
14
+ super(message);
15
+ this.errorType = errorType;
16
+ this.name = 'RpcError';
17
+ // Restore prototype chain for proper instanceof checks
18
+ Object.setPrototypeOf(this, RpcError.prototype);
19
+ }
20
+ }
21
+ /**
22
+ * Initialize the RPC client with a socket path
23
+ */
24
+ export function initRpcClient(socketPath) {
25
+ if (ipcClient) {
26
+ throw new Error('RPC client already initialized');
27
+ }
28
+ ipcClient = new IpcClient(socketPath);
29
+ // Setup response handler
30
+ ipcClient.on('message', (message) => {
31
+ // Validate message structure
32
+ if (!message || typeof message !== 'object') {
33
+ return;
34
+ }
35
+ const msg = message;
36
+ if (isRpcResponseMessage(msg) && msg.request_id) {
37
+ const pending = pendingRequests.get(msg.request_id);
38
+ if (pending) {
39
+ clearTimeout(pending.timeout);
40
+ pending.resolve(msg.result);
41
+ pendingRequests.delete(msg.request_id);
42
+ }
43
+ }
44
+ else if (isRpcErrorMessage(msg) && msg.request_id) {
45
+ const pending = pendingRequests.get(msg.request_id);
46
+ if (pending) {
47
+ clearTimeout(pending.timeout);
48
+ const error = new RpcError(msg.error_type || 'UnknownError', msg.error || 'Unknown error');
49
+ pending.reject(error);
50
+ pendingRequests.delete(msg.request_id);
51
+ }
52
+ }
53
+ });
54
+ ipcClient.on('error', (error) => {
55
+ // Reject all pending requests on connection error
56
+ for (const [, pending] of pendingRequests) {
57
+ clearTimeout(pending.timeout);
58
+ pending.reject(error);
59
+ }
60
+ pendingRequests.clear();
61
+ });
62
+ }
63
+ /**
64
+ * Call a Rust server function via RPC
65
+ */
66
+ export async function rpcCall(functionName, params = {}, timeoutMs = 30000) {
67
+ if (!ipcClient) {
68
+ throw new Error('RPC client not initialized. Call initRpcClient() first.');
69
+ }
70
+ const requestId = `req_${Date.now()}_${requestCounter++}`;
71
+ const message = {
72
+ type: 'rpc_call',
73
+ function_name: functionName,
74
+ params,
75
+ request_id: requestId,
76
+ };
77
+ return new Promise((resolve, reject) => {
78
+ const timeout = setTimeout(() => {
79
+ pendingRequests.delete(requestId);
80
+ reject(new RpcError('TimeoutError', `RPC call to ${functionName} timed out after ${timeoutMs}ms`));
81
+ }, timeoutMs);
82
+ pendingRequests.set(requestId, {
83
+ resolve: resolve,
84
+ reject,
85
+ timeout,
86
+ });
87
+ try {
88
+ ipcClient.send(message);
89
+ }
90
+ catch (error) {
91
+ clearTimeout(timeout);
92
+ pendingRequests.delete(requestId);
93
+ reject(error instanceof Error ? error : new Error(String(error)));
94
+ }
95
+ });
96
+ }
97
+ /**
98
+ * Wait for a response from a specific request
99
+ */
100
+ export async function waitForResponse(requestId, timeoutMs = 30000) {
101
+ return new Promise((resolve, reject) => {
102
+ const timeout = setTimeout(() => {
103
+ pendingRequests.delete(requestId);
104
+ reject(new RpcError('TimeoutError', `Request ${requestId} timed out`));
105
+ }, timeoutMs);
106
+ const handler = {
107
+ resolve: resolve,
108
+ reject,
109
+ timeout,
110
+ };
111
+ pendingRequests.set(requestId, handler);
112
+ });
113
+ }
114
+ /**
115
+ * Close the RPC client connection
116
+ */
117
+ export async function closeRpcClient() {
118
+ if (ipcClient) {
119
+ // Reject all pending requests
120
+ for (const [, pending] of pendingRequests) {
121
+ clearTimeout(pending.timeout);
122
+ pending.reject(new Error('RPC client closed'));
123
+ }
124
+ pendingRequests.clear();
125
+ await ipcClient.close();
126
+ ipcClient = null;
127
+ }
128
+ }
129
+ /**
130
+ * Check if RPC client is initialized
131
+ */
132
+ export function isRpcClientInitialized() {
133
+ return ipcClient !== null;
134
+ }
135
+ /**
136
+ * Get current RPC client instance (for advanced usage)
137
+ */
138
+ export function getRpcClient() {
139
+ return ipcClient;
140
+ }
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Streaming utilities for ZapJS
3
+ * Helper functions for working with streaming responses
4
+ */
5
+ import type { StreamChunk } from './types.js';
6
+ import { isAsyncIterable } from './types.js';
7
+ export { isAsyncIterable };
8
+ /**
9
+ * Create a StreamChunk from string data
10
+ * @param data - String data to send
11
+ * @returns StreamChunk with data field
12
+ */
13
+ export declare function createChunk(data: string): StreamChunk;
14
+ /**
15
+ * Create a StreamChunk from binary data
16
+ * @param bytes - Binary data to send
17
+ * @returns StreamChunk with bytes field
18
+ */
19
+ export declare function createChunk(bytes: Uint8Array): StreamChunk;
20
+ /**
21
+ * Create a streaming response from an array of strings
22
+ * @param items - Array of strings to stream
23
+ * @param delimiter - Optional delimiter to add after each item (default: newline)
24
+ * @returns Async iterable of stream chunks
25
+ */
26
+ export declare function createStream(items: string[], delimiter?: string): AsyncIterable<StreamChunk>;
27
+ /**
28
+ * Create a streaming JSON response (NDJSON format)
29
+ * Each object is sent as a separate JSON line
30
+ * @param objects - Array of objects to stream
31
+ * @returns Async iterable of stream chunks
32
+ */
33
+ export declare function streamJson<T = any>(objects: T[]): AsyncIterable<StreamChunk>;
34
+ /**
35
+ * Stream Server-Sent Events (SSE) format
36
+ * @param events - Array of SSE events
37
+ * @returns Async iterable of stream chunks
38
+ */
39
+ export declare function streamSSE(events: Array<{
40
+ data: any;
41
+ event?: string;
42
+ id?: string | number;
43
+ retry?: number;
44
+ }>): AsyncIterable<StreamChunk>;
45
+ /**
46
+ * Transform an async iterable with a mapper function
47
+ * @param source - Source async iterable
48
+ * @param mapper - Function to transform each item
49
+ * @returns Async iterable of transformed items
50
+ */
51
+ export declare function mapStream<T, U>(source: AsyncIterable<T>, mapper: (item: T) => U | Promise<U>): AsyncIterable<U>;
52
+ /**
53
+ * Filter an async iterable with a predicate function
54
+ * @param source - Source async iterable
55
+ * @param predicate - Function to test each item
56
+ * @returns Async iterable of filtered items
57
+ */
58
+ export declare function filterStream<T>(source: AsyncIterable<T>, predicate: (item: T) => boolean | Promise<boolean>): AsyncIterable<T>;
59
+ /**
60
+ * Batch stream chunks together
61
+ * @param source - Source async iterable
62
+ * @param batchSize - Number of items per batch
63
+ * @returns Async iterable of batched items
64
+ */
65
+ export declare function batchStream<T>(source: AsyncIterable<T>, batchSize: number): AsyncIterable<T[]>;
66
+ /**
67
+ * Add delay between stream chunks
68
+ * @param source - Source async iterable
69
+ * @param delayMs - Delay in milliseconds between chunks
70
+ * @returns Async iterable with delays
71
+ */
72
+ export declare function delayStream<T>(source: AsyncIterable<T>, delayMs: number): AsyncIterable<T>;
73
+ /**
74
+ * Convert a ReadableStream to an async iterable
75
+ * @param stream - ReadableStream to convert
76
+ * @returns Async iterable
77
+ */
78
+ export declare function fromReadableStream<T>(stream: ReadableStream<T>): AsyncIterable<T>;
79
+ /**
80
+ * Create a streaming response that emits at regular intervals
81
+ * @param interval - Interval in milliseconds
82
+ * @param maxCount - Maximum number of emissions (optional)
83
+ * @param generator - Function to generate data for each emission
84
+ * @returns Async iterable of stream chunks
85
+ */
86
+ export declare function intervalStream<T>(interval: number, generator: (count: number) => T, maxCount?: number): AsyncIterable<StreamChunk>;