heliumts 0.2.2

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 (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +356 -0
  3. package/dist/bin/helium.d.ts +3 -0
  4. package/dist/bin/helium.d.ts.map +1 -0
  5. package/dist/bin/helium.js +236 -0
  6. package/dist/bin/helium.js.map +1 -0
  7. package/dist/client/Router.d.ts +83 -0
  8. package/dist/client/Router.d.ts.map +1 -0
  9. package/dist/client/Router.js +329 -0
  10. package/dist/client/Router.js.map +1 -0
  11. package/dist/client/cache.d.ts +8 -0
  12. package/dist/client/cache.d.ts.map +1 -0
  13. package/dist/client/cache.js +82 -0
  14. package/dist/client/cache.js.map +1 -0
  15. package/dist/client/index.d.ts +10 -0
  16. package/dist/client/index.d.ts.map +1 -0
  17. package/dist/client/index.js +9 -0
  18. package/dist/client/index.js.map +1 -0
  19. package/dist/client/prefetch.d.ts +12 -0
  20. package/dist/client/prefetch.d.ts.map +1 -0
  21. package/dist/client/prefetch.js +33 -0
  22. package/dist/client/prefetch.js.map +1 -0
  23. package/dist/client/routerManifest.d.ts +28 -0
  24. package/dist/client/routerManifest.d.ts.map +1 -0
  25. package/dist/client/routerManifest.js +242 -0
  26. package/dist/client/routerManifest.js.map +1 -0
  27. package/dist/client/rpcClient.d.ts +33 -0
  28. package/dist/client/rpcClient.d.ts.map +1 -0
  29. package/dist/client/rpcClient.js +383 -0
  30. package/dist/client/rpcClient.js.map +1 -0
  31. package/dist/client/transitions.d.ts +85 -0
  32. package/dist/client/transitions.d.ts.map +1 -0
  33. package/dist/client/transitions.js +92 -0
  34. package/dist/client/transitions.js.map +1 -0
  35. package/dist/client/types.d.ts +6 -0
  36. package/dist/client/types.d.ts.map +1 -0
  37. package/dist/client/types.js +2 -0
  38. package/dist/client/types.js.map +1 -0
  39. package/dist/client/useCall.d.ts +31 -0
  40. package/dist/client/useCall.d.ts.map +1 -0
  41. package/dist/client/useCall.js +40 -0
  42. package/dist/client/useCall.js.map +1 -0
  43. package/dist/client/useFetch.d.ts +34 -0
  44. package/dist/client/useFetch.d.ts.map +1 -0
  45. package/dist/client/useFetch.js +152 -0
  46. package/dist/client/useFetch.js.map +1 -0
  47. package/dist/index.d.ts +4 -0
  48. package/dist/index.d.ts.map +1 -0
  49. package/dist/index.js +4 -0
  50. package/dist/index.js.map +1 -0
  51. package/dist/runtime/protocol.d.ts +25 -0
  52. package/dist/runtime/protocol.d.ts.map +1 -0
  53. package/dist/runtime/protocol.js +2 -0
  54. package/dist/runtime/protocol.js.map +1 -0
  55. package/dist/server/config.d.ts +216 -0
  56. package/dist/server/config.d.ts.map +1 -0
  57. package/dist/server/config.js +130 -0
  58. package/dist/server/config.js.map +1 -0
  59. package/dist/server/context.d.ts +38 -0
  60. package/dist/server/context.d.ts.map +1 -0
  61. package/dist/server/context.js +2 -0
  62. package/dist/server/context.js.map +1 -0
  63. package/dist/server/defineHTTPRequest.d.ts +49 -0
  64. package/dist/server/defineHTTPRequest.d.ts.map +1 -0
  65. package/dist/server/defineHTTPRequest.js +34 -0
  66. package/dist/server/defineHTTPRequest.js.map +1 -0
  67. package/dist/server/defineMethod.d.ts +20 -0
  68. package/dist/server/defineMethod.d.ts.map +1 -0
  69. package/dist/server/defineMethod.js +23 -0
  70. package/dist/server/defineMethod.js.map +1 -0
  71. package/dist/server/devServer.d.ts +15 -0
  72. package/dist/server/devServer.d.ts.map +1 -0
  73. package/dist/server/devServer.js +219 -0
  74. package/dist/server/devServer.js.map +1 -0
  75. package/dist/server/handlerLoader.d.ts +1 -0
  76. package/dist/server/handlerLoader.d.ts.map +1 -0
  77. package/dist/server/handlerLoader.js +2 -0
  78. package/dist/server/handlerLoader.js.map +1 -0
  79. package/dist/server/httpRouter.d.ts +17 -0
  80. package/dist/server/httpRouter.d.ts.map +1 -0
  81. package/dist/server/httpRouter.js +227 -0
  82. package/dist/server/httpRouter.js.map +1 -0
  83. package/dist/server/index.d.ts +11 -0
  84. package/dist/server/index.d.ts.map +1 -0
  85. package/dist/server/index.js +13 -0
  86. package/dist/server/index.js.map +1 -0
  87. package/dist/server/middleware.d.ts +30 -0
  88. package/dist/server/middleware.d.ts.map +1 -0
  89. package/dist/server/middleware.js +23 -0
  90. package/dist/server/middleware.js.map +1 -0
  91. package/dist/server/prodServer.d.ts +27 -0
  92. package/dist/server/prodServer.d.ts.map +1 -0
  93. package/dist/server/prodServer.js +301 -0
  94. package/dist/server/prodServer.js.map +1 -0
  95. package/dist/server/rateLimiter.d.ts +36 -0
  96. package/dist/server/rateLimiter.d.ts.map +1 -0
  97. package/dist/server/rateLimiter.js +113 -0
  98. package/dist/server/rateLimiter.js.map +1 -0
  99. package/dist/server/rpcRegistry.d.ts +34 -0
  100. package/dist/server/rpcRegistry.d.ts.map +1 -0
  101. package/dist/server/rpcRegistry.js +231 -0
  102. package/dist/server/rpcRegistry.js.map +1 -0
  103. package/dist/server/security.d.ts +5 -0
  104. package/dist/server/security.d.ts.map +1 -0
  105. package/dist/server/security.js +59 -0
  106. package/dist/server/security.js.map +1 -0
  107. package/dist/server/serializer.d.ts +9 -0
  108. package/dist/server/serializer.d.ts.map +1 -0
  109. package/dist/server/serializer.js +40 -0
  110. package/dist/server/serializer.js.map +1 -0
  111. package/dist/utils/envLoader.d.ts +27 -0
  112. package/dist/utils/envLoader.d.ts.map +1 -0
  113. package/dist/utils/envLoader.js +68 -0
  114. package/dist/utils/envLoader.js.map +1 -0
  115. package/dist/utils/ipExtractor.d.ts +59 -0
  116. package/dist/utils/ipExtractor.d.ts.map +1 -0
  117. package/dist/utils/ipExtractor.js +126 -0
  118. package/dist/utils/ipExtractor.js.map +1 -0
  119. package/dist/utils/logger.d.ts +2 -0
  120. package/dist/utils/logger.d.ts.map +1 -0
  121. package/dist/utils/logger.js +24 -0
  122. package/dist/utils/logger.js.map +1 -0
  123. package/dist/vite/heliumPlugin.d.ts +3 -0
  124. package/dist/vite/heliumPlugin.d.ts.map +1 -0
  125. package/dist/vite/heliumPlugin.js +294 -0
  126. package/dist/vite/heliumPlugin.js.map +1 -0
  127. package/dist/vite/index.d.ts +3 -0
  128. package/dist/vite/index.d.ts.map +1 -0
  129. package/dist/vite/index.js +3 -0
  130. package/dist/vite/index.js.map +1 -0
  131. package/dist/vite/paths.d.ts +8 -0
  132. package/dist/vite/paths.d.ts.map +1 -0
  133. package/dist/vite/paths.js +8 -0
  134. package/dist/vite/paths.js.map +1 -0
  135. package/dist/vite/scanner.d.ts +35 -0
  136. package/dist/vite/scanner.d.ts.map +1 -0
  137. package/dist/vite/scanner.js +167 -0
  138. package/dist/vite/scanner.js.map +1 -0
  139. package/dist/vite/ssg.d.ts +22 -0
  140. package/dist/vite/ssg.d.ts.map +1 -0
  141. package/dist/vite/ssg.js +547 -0
  142. package/dist/vite/ssg.js.map +1 -0
  143. package/dist/vite/virtualServerModule.d.ts +6 -0
  144. package/dist/vite/virtualServerModule.d.ts.map +1 -0
  145. package/dist/vite/virtualServerModule.js +82 -0
  146. package/dist/vite/virtualServerModule.js.map +1 -0
  147. package/package.json +103 -0
@@ -0,0 +1,242 @@
1
+ import React from "react";
2
+ import { log } from "../utils/logger.js";
3
+ /**
4
+ * Convert a file path to a route pattern
5
+ * Examples:
6
+ * - /src/pages/index.tsx → /
7
+ * - /src/pages/tasks/index.tsx → /tasks
8
+ * - /src/pages/tasks/[id].tsx → /tasks/:id
9
+ * - /src/pages/settings/profile.tsx → /settings/profile
10
+ * - /src/pages/blog/[...slug].tsx → /blog/*slug
11
+ * - /src/pages/404.tsx → __404__
12
+ * - /src/pages/(website)/contact.tsx → /contact
13
+ * - /src/pages/(portal)/dashboard.tsx → /dashboard
14
+ */
15
+ function pathFromFile(file) {
16
+ // Remove /src/pages prefix and file extension
17
+ const withoutPrefix = file.replace("/src/pages", "").replace(/\.(tsx|jsx|ts|js)$/, "");
18
+ // Handle special files
19
+ if (withoutPrefix === "/404") {
20
+ return "__404__";
21
+ }
22
+ // Remove route groups (folders in parentheses)
23
+ // E.g., /(website)/contact → /contact
24
+ let pattern = withoutPrefix.replace(/\/\([^)]+\)/g, "");
25
+ // Convert /index to /
26
+ pattern = pattern.replace(/\/index$/, "") || "/";
27
+ // Convert [...param] to *param (catch-all)
28
+ pattern = pattern.replace(/\[\.\.\.(.+?)\]/g, "*$1");
29
+ // Convert [param] to :param (single dynamic segment)
30
+ pattern = pattern.replace(/\[(.+?)\]/g, ":$1");
31
+ return pattern;
32
+ }
33
+ /**
34
+ * Create a matcher function for a route pattern
35
+ * Supports dynamic segments like :id, :slug, etc.
36
+ * Supports catch-all segments like *slug for [...slug]
37
+ */
38
+ function createMatcher(pattern) {
39
+ const segments = pattern.split("/").filter(Boolean);
40
+ return (path) => {
41
+ // Remove query string and hash
42
+ const cleanPath = path.split("?")[0].split("#")[0];
43
+ const pathSegments = cleanPath.split("/").filter(Boolean);
44
+ // For root path, handle specially
45
+ if (pattern === "/" && cleanPath === "/") {
46
+ return { params: {} };
47
+ }
48
+ const params = {};
49
+ // Check for catch-all segment (must be last)
50
+ const hasCatchAll = segments.some((seg) => seg.startsWith("*"));
51
+ if (hasCatchAll) {
52
+ const catchAllIndex = segments.findIndex((seg) => seg.startsWith("*"));
53
+ // Catch-all must be the last segment
54
+ if (catchAllIndex !== segments.length - 1) {
55
+ log("warn", `Catch-all segment must be last: ${pattern}`);
56
+ return null;
57
+ }
58
+ const catchAllParam = segments[catchAllIndex].slice(1); // Remove *
59
+ // Match segments before catch-all
60
+ for (let i = 0; i < catchAllIndex; i++) {
61
+ const seg = segments[i];
62
+ const value = pathSegments[i];
63
+ if (!value) {
64
+ return null;
65
+ } // Not enough path segments
66
+ if (seg.startsWith(":")) {
67
+ params[seg.slice(1)] = decodeURIComponent(value);
68
+ }
69
+ else if (seg !== value) {
70
+ return null; // Static segment must match
71
+ }
72
+ }
73
+ // Collect remaining path segments into catch-all param as an array
74
+ const remainingSegments = pathSegments.slice(catchAllIndex);
75
+ params[catchAllParam] = remainingSegments.map((s) => decodeURIComponent(s));
76
+ return { params };
77
+ }
78
+ // Regular matching (no catch-all)
79
+ // If segment counts don't match, no match
80
+ if (segments.length !== pathSegments.length) {
81
+ return null;
82
+ }
83
+ for (let i = 0; i < segments.length; i++) {
84
+ const seg = segments[i];
85
+ const value = pathSegments[i];
86
+ if (seg.startsWith(":")) {
87
+ // Dynamic segment
88
+ params[seg.slice(1)] = decodeURIComponent(value);
89
+ }
90
+ else if (seg !== value) {
91
+ // Static segment must match exactly
92
+ return null;
93
+ }
94
+ }
95
+ return { params };
96
+ };
97
+ }
98
+ /**
99
+ * Get directory path from file path
100
+ * /src/pages/tasks/[id].tsx → /tasks
101
+ * /src/pages/(website)/contact.tsx → /(website)
102
+ */
103
+ function getDirectoryPath(file) {
104
+ const withoutPrefix = file.replace("/src/pages", "").replace(/\.(tsx|jsx|ts|js)$/, "");
105
+ const parts = withoutPrefix.split("/");
106
+ parts.pop(); // Remove filename
107
+ return parts.join("/") || "/";
108
+ }
109
+ /**
110
+ * Build route manifest from pages directory
111
+ * Uses import.meta.glob to discover all page files
112
+ * Supports both eager and lazy loading for Suspense compatibility
113
+ */
114
+ export function buildRoutes() {
115
+ // SSR check - return empty routes if running server-side
116
+ if (typeof window === "undefined") {
117
+ return { routes: [], NotFound: undefined, AppShell: undefined };
118
+ }
119
+ // Eagerly load all page components (for initial render and layouts)
120
+ const eagerPages = import.meta.glob("/src/pages/**/*.{tsx,jsx,ts,js}", {
121
+ eager: true,
122
+ });
123
+ // Lazy load all page components (for Suspense support)
124
+ const lazyPages = import.meta.glob("/src/pages/**/*.{tsx,jsx,ts,js}");
125
+ // Debug mode: set localStorage.setItem('helium_debug_routes', 'true') to enable
126
+ const debugRoutes = typeof localStorage !== "undefined" && localStorage.getItem("helium_debug_routes") === "true";
127
+ if (debugRoutes) {
128
+ console.group("[Helium Router] Discovered pages:");
129
+ for (const file of Object.keys(eagerPages)) {
130
+ console.log(` ${file} → ${pathFromFile(file)}`);
131
+ }
132
+ console.groupEnd();
133
+ }
134
+ const routes = [];
135
+ let NotFound;
136
+ let AppShell;
137
+ // Build layout map: directory path -> layout component
138
+ const layoutMap = new Map();
139
+ // Track route patterns to detect collisions: pattern -> file path
140
+ const routePatternMap = new Map();
141
+ // First pass: collect all layouts
142
+ for (const [file, mod] of Object.entries(eagerPages)) {
143
+ if (file.includes("/_layout.")) {
144
+ const Component = mod.default;
145
+ if (Component) {
146
+ const dirPath = getDirectoryPath(file);
147
+ layoutMap.set(dirPath, Component);
148
+ }
149
+ }
150
+ }
151
+ // Second pass: build routes with their layouts
152
+ for (const [file, mod] of Object.entries(eagerPages)) {
153
+ // Skip layout files
154
+ if (file.includes("/_layout.")) {
155
+ continue;
156
+ }
157
+ const Component = mod.default;
158
+ if (!Component) {
159
+ log("warn", `No default export found in ${file}`);
160
+ continue;
161
+ }
162
+ const pathPattern = pathFromFile(file);
163
+ // Handle special pages
164
+ if (pathPattern === "__404__") {
165
+ NotFound = Component;
166
+ continue;
167
+ }
168
+ // Detect route collisions
169
+ const existingFile = routePatternMap.get(pathPattern);
170
+ if (existingFile) {
171
+ log("error", `Route collision detected! Multiple files resolve to the same path "${pathPattern}":`);
172
+ log("error", ` - ${existingFile}`);
173
+ log("error", ` - ${file}`);
174
+ log("error", `Only the first file will be used. Consider using different file names or paths.`);
175
+ }
176
+ else {
177
+ routePatternMap.set(pathPattern, file);
178
+ }
179
+ // Find all layouts for this route (from root to leaf)
180
+ const layouts = [];
181
+ const dirPath = getDirectoryPath(file);
182
+ const pathParts = dirPath.split("/").filter(Boolean);
183
+ // Always check for root layout first (applies to all pages)
184
+ const rootLayout = layoutMap.get("/");
185
+ if (rootLayout) {
186
+ layouts.push(rootLayout);
187
+ }
188
+ // Then check for layouts at each nested level
189
+ // This will include route group layouts and any nested folder layouts
190
+ for (let i = 1; i <= pathParts.length; i++) {
191
+ const checkPath = "/" + pathParts.slice(0, i).join("/");
192
+ const layout = layoutMap.get(checkPath);
193
+ if (layout) {
194
+ layouts.push(layout);
195
+ }
196
+ }
197
+ // Get the lazy loader for this page
198
+ const lazyLoader = lazyPages[file];
199
+ const LazyComponent = React.lazy(lazyLoader);
200
+ // Create route entry with both eager and lazy components
201
+ routes.push({
202
+ pathPattern,
203
+ matcher: createMatcher(pathPattern),
204
+ Component,
205
+ LazyComponent,
206
+ layouts,
207
+ preload: lazyLoader,
208
+ });
209
+ }
210
+ // Sort routes by specificity (static > dynamic > catch-all)
211
+ routes.sort((a, b) => {
212
+ const aHasCatchAll = a.pathPattern.includes("*");
213
+ const bHasCatchAll = b.pathPattern.includes("*");
214
+ const aHasDynamic = a.pathPattern.includes(":");
215
+ const bHasDynamic = b.pathPattern.includes(":");
216
+ // Catch-all routes should be last (least specific)
217
+ if (aHasCatchAll && !bHasCatchAll) {
218
+ return 1;
219
+ }
220
+ if (!aHasCatchAll && bHasCatchAll) {
221
+ return -1;
222
+ }
223
+ // Dynamic routes come after static routes
224
+ if (aHasDynamic && !bHasDynamic) {
225
+ return 1;
226
+ }
227
+ if (!aHasDynamic && bHasDynamic) {
228
+ return -1;
229
+ }
230
+ // Longer paths first (more specific)
231
+ return b.pathPattern.length - a.pathPattern.length;
232
+ });
233
+ if (debugRoutes) {
234
+ console.group("[Helium Router] Final route order:");
235
+ for (const route of routes) {
236
+ console.log(` ${route.pathPattern}`);
237
+ }
238
+ console.groupEnd();
239
+ }
240
+ return { routes, NotFound, AppShell };
241
+ }
242
+ //# sourceMappingURL=routerManifest.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routerManifest.js","sourceRoot":"","sources":["../../src/client/routerManifest.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { RpcStats } from "../runtime/protocol.js";
2
+ export type RpcResult<T> = {
3
+ data: T;
4
+ stats: RpcStats;
5
+ };
6
+ /**
7
+ * Transport mode for RPC calls.
8
+ * - "http": Uses HTTP POST requests (faster on mobile/high-latency networks, benefits from HTTP/2)
9
+ * - "websocket": Uses persistent WebSocket connection (lower latency on desktop/low-latency networks)
10
+ * - "auto": Automatically selects based on connection type
11
+ */
12
+ export type RpcTransport = "http" | "websocket" | "auto";
13
+ /**
14
+ * Get the configured RPC transport mode (from helium.config.js).
15
+ */
16
+ export declare function getRpcTransport(): RpcTransport;
17
+ /**
18
+ * Check if auto HTTP on mobile is enabled (from helium.config.js).
19
+ */
20
+ export declare function isAutoHttpOnMobileEnabled(): boolean;
21
+ /**
22
+ * Make an RPC call using the appropriate transport.
23
+ * Automatically selects HTTP or WebSocket based on network conditions and configuration.
24
+ */
25
+ export declare function rpcCall<TResult = unknown, TArgs = unknown>(methodId: string, args?: TArgs): Promise<RpcResult<TResult>>;
26
+ /**
27
+ * Pre-establishes the WebSocket connection.
28
+ * Call this early (e.g., on page load) to avoid connection latency on first RPC call.
29
+ * This is especially beneficial on high-latency networks like mobile LTE.
30
+ * Note: Only effective when using WebSocket transport (not HTTP transport).
31
+ */
32
+ export declare function preconnect(): void;
33
+ //# sourceMappingURL=rpcClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpcClient.d.ts","sourceRoot":"","sources":["../../src/client/rpcClient.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAA2B,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAEhF,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI;IACvB,IAAI,EAAE,CAAC,CAAC;IACR,KAAK,EAAE,QAAQ,CAAC;CACnB,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAUzD;;GAEG;AACH,wBAAgB,eAAe,IAAI,YAAY,CAE9C;AAED;;GAEG;AACH,wBAAgB,yBAAyB,IAAI,OAAO,CAEnD;AA4XD;;;GAGG;AACH,wBAAsB,OAAO,CAAC,OAAO,GAAG,OAAO,EAAE,KAAK,GAAG,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,CAY7H;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAYjC"}
@@ -0,0 +1,383 @@
1
+ import { decode as msgpackDecode, encode as msgpackEncode } from "@msgpack/msgpack";
2
+ // Read build-time config with fallback defaults
3
+ const configuredTransport = typeof __HELIUM_RPC_TRANSPORT__ !== "undefined" ? __HELIUM_RPC_TRANSPORT__ : "websocket";
4
+ const configuredAutoHttpOnMobile = typeof __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ !== "undefined" ? __HELIUM_RPC_AUTO_HTTP_ON_MOBILE__ : false;
5
+ /**
6
+ * Get the configured RPC transport mode (from helium.config.js).
7
+ */
8
+ export function getRpcTransport() {
9
+ return configuredTransport;
10
+ }
11
+ /**
12
+ * Check if auto HTTP on mobile is enabled (from helium.config.js).
13
+ */
14
+ export function isAutoHttpOnMobileEnabled() {
15
+ return configuredAutoHttpOnMobile;
16
+ }
17
+ // Detect if we should prefer HTTP transport (mobile/slow networks)
18
+ function shouldUseHttpTransport() {
19
+ if (configuredTransport === "http") {
20
+ return true;
21
+ }
22
+ if (configuredTransport === "websocket") {
23
+ return false;
24
+ }
25
+ // Auto mode: check if mobile HTTP optimization is enabled
26
+ if (!configuredAutoHttpOnMobile) {
27
+ return false;
28
+ }
29
+ // Prefer HTTP on mobile/slow connections
30
+ if (typeof navigator !== "undefined") {
31
+ const conn = navigator.connection;
32
+ if (conn) {
33
+ // Use HTTP for cellular connections or slow effective types
34
+ const slowTypes = ["slow-2g", "2g", "3g"];
35
+ if (conn.type === "cellular" || (conn.effectiveType && slowTypes.includes(conn.effectiveType))) {
36
+ return true;
37
+ }
38
+ }
39
+ }
40
+ return false;
41
+ }
42
+ let pendingBatch = [];
43
+ let isBatchScheduled = false;
44
+ function scheduleBatch() {
45
+ if (isBatchScheduled) {
46
+ return;
47
+ }
48
+ isBatchScheduled = true;
49
+ queueMicrotask(() => {
50
+ isBatchScheduled = false;
51
+ flushBatch();
52
+ });
53
+ }
54
+ async function flushBatch() {
55
+ const batch = pendingBatch;
56
+ pendingBatch = [];
57
+ if (batch.length === 0) {
58
+ return;
59
+ }
60
+ try {
61
+ if (shouldUseHttpTransport()) {
62
+ await sendBatchHttp(batch);
63
+ }
64
+ else {
65
+ await sendBatchWebSocket(batch);
66
+ }
67
+ }
68
+ catch (err) {
69
+ // Transport error, fail all
70
+ for (const item of batch) {
71
+ item.reject(err);
72
+ }
73
+ }
74
+ }
75
+ async function sendBatchHttp(batch) {
76
+ const requests = batch.map((b) => b.req);
77
+ const encoded = msgpackEncode(requests);
78
+ const response = await fetch("/__helium__/rpc", {
79
+ method: "POST",
80
+ headers: {
81
+ "Content-Type": "application/msgpack",
82
+ Accept: "application/msgpack",
83
+ },
84
+ body: encoded,
85
+ });
86
+ if (!response.ok) {
87
+ throw new Error(`HTTP RPC failed: ${response.status}`);
88
+ }
89
+ const responseBuffer = await response.arrayBuffer();
90
+ const msg = msgpackDecode(new Uint8Array(responseBuffer));
91
+ const responses = Array.isArray(msg) ? msg : [msg];
92
+ const responseMap = new Map(responses.map((r) => [r.id, r]));
93
+ for (const item of batch) {
94
+ const res = responseMap.get(item.req.id);
95
+ if (res) {
96
+ if (res.ok) {
97
+ item.resolve({ data: res.result, stats: res.stats });
98
+ }
99
+ else {
100
+ item.reject({ error: res.error, stats: res.stats });
101
+ }
102
+ }
103
+ else {
104
+ item.reject(new Error("No response for request"));
105
+ }
106
+ }
107
+ }
108
+ async function sendBatchWebSocket(batch) {
109
+ const ws = await ensureSocketReady();
110
+ const requests = batch.map((b) => b.req);
111
+ // Register pending promises
112
+ batch.forEach((item) => {
113
+ pending.set(item.req.id, {
114
+ resolve: (v) => item.resolve(v),
115
+ reject: item.reject,
116
+ });
117
+ });
118
+ try {
119
+ // Always use msgpack encoding
120
+ const encoded = msgpackEncode(requests);
121
+ ws.send(encoded);
122
+ }
123
+ catch (err) {
124
+ batch.forEach((item) => {
125
+ pending.delete(item.req.id);
126
+ item.reject(err);
127
+ });
128
+ }
129
+ }
130
+ // ============================================================================
131
+ // WebSocket Transport (original implementation)
132
+ // ============================================================================
133
+ let socket = null;
134
+ let connectionPromise = null;
135
+ const pending = new Map();
136
+ // Clean up WebSocket connection on HMR (Hot Module Replacement)
137
+ if (import.meta.hot) {
138
+ import.meta.hot.dispose(() => {
139
+ if (socket) {
140
+ // Close the socket gracefully
141
+ socket.close();
142
+ socket = null;
143
+ connectionPromise = null;
144
+ }
145
+ // Reject all pending requests
146
+ pending.forEach((entry) => {
147
+ entry.reject(new Error("Module reloaded"));
148
+ });
149
+ pending.clear();
150
+ });
151
+ }
152
+ let msgId = 0;
153
+ function nextId() {
154
+ return msgId++;
155
+ }
156
+ async function fetchFreshToken() {
157
+ try {
158
+ const response = await fetch("/__helium__/refresh-token");
159
+ if (!response.ok) {
160
+ console.warn("Failed to fetch fresh token:", response.status);
161
+ return undefined;
162
+ }
163
+ const data = await response.json();
164
+ return data.token;
165
+ }
166
+ catch (error) {
167
+ console.warn("Error fetching fresh token:", error);
168
+ return undefined;
169
+ }
170
+ }
171
+ async function createSocket() {
172
+ // Fetch a fresh token before creating the WebSocket connection
173
+ const token = await fetchFreshToken();
174
+ // Use the same protocol, hostname and port as the current page
175
+ const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
176
+ const host = window.location.host; // includes hostname and port
177
+ const url = `${protocol}//${host}/rpc${token ? `?token=${token}` : ""}`;
178
+ const ws = new WebSocket(url);
179
+ ws.binaryType = "arraybuffer";
180
+ ws.onmessage = async (event) => {
181
+ let data = new Uint8Array(event.data);
182
+ // Check for Gzip header (0x1f 0x8b) to detect compressed messages
183
+ if (data.length > 2 && data[0] === 0x1f && data[1] === 0x8b) {
184
+ try {
185
+ // Use DecompressionStream if available (Chrome 80+, Firefox 113+, Safari 16.4+)
186
+ if (typeof DecompressionStream !== "undefined") {
187
+ const ds = new DecompressionStream("gzip");
188
+ const stream = new Response(data).body;
189
+ if (stream) {
190
+ const decompressed = stream.pipeThrough(ds);
191
+ const buffer = await new Response(decompressed).arrayBuffer();
192
+ data = new Uint8Array(buffer);
193
+ }
194
+ }
195
+ }
196
+ catch (err) {
197
+ console.error("Failed to decompress WebSocket message:", err);
198
+ return;
199
+ }
200
+ }
201
+ // Always expect binary MessagePack
202
+ const msg = msgpackDecode(data);
203
+ const handleResponse = (res) => {
204
+ const entry = pending.get(res.id);
205
+ if (!entry) {
206
+ return;
207
+ }
208
+ pending.delete(res.id);
209
+ if (res.ok) {
210
+ entry.resolve({ data: res.result, stats: res.stats });
211
+ }
212
+ else {
213
+ entry.reject({ error: res.error, stats: res.stats });
214
+ }
215
+ };
216
+ if (Array.isArray(msg)) {
217
+ msg.forEach(handleResponse);
218
+ }
219
+ else {
220
+ handleResponse(msg);
221
+ }
222
+ };
223
+ ws.onclose = () => {
224
+ if (socket === ws) {
225
+ socket = null;
226
+ connectionPromise = null;
227
+ }
228
+ };
229
+ return ws;
230
+ }
231
+ async function ensureSocketReady() {
232
+ // If we have an open socket, return it immediately
233
+ if (socket && socket.readyState === WebSocket.OPEN) {
234
+ return socket;
235
+ }
236
+ // If we have a connection in progress, reuse that promise
237
+ if (connectionPromise) {
238
+ return connectionPromise;
239
+ }
240
+ // If we have a socket that's connecting, wait for it
241
+ if (socket && socket.readyState === WebSocket.CONNECTING) {
242
+ connectionPromise = new Promise((resolve, reject) => {
243
+ const cleanup = () => {
244
+ socket.removeEventListener("open", handleOpen);
245
+ socket.removeEventListener("error", handleError);
246
+ socket.removeEventListener("close", handleClose);
247
+ };
248
+ const handleOpen = () => {
249
+ cleanup();
250
+ connectionPromise = null;
251
+ resolve(socket);
252
+ };
253
+ const handleError = () => {
254
+ cleanup();
255
+ socket = null;
256
+ connectionPromise = null;
257
+ reject(new Error("WebSocket connection failed"));
258
+ };
259
+ const handleClose = () => {
260
+ cleanup();
261
+ socket = null;
262
+ connectionPromise = null;
263
+ reject(new Error("WebSocket closed before opening"));
264
+ };
265
+ socket.addEventListener("open", handleOpen);
266
+ socket.addEventListener("error", handleError);
267
+ socket.addEventListener("close", handleClose);
268
+ });
269
+ return connectionPromise;
270
+ }
271
+ // Create a new socket and connection promise
272
+ connectionPromise = (async () => {
273
+ socket = await createSocket();
274
+ return new Promise((resolve, reject) => {
275
+ const cleanup = () => {
276
+ socket.removeEventListener("open", handleOpen);
277
+ socket.removeEventListener("error", handleError);
278
+ socket.removeEventListener("close", handleClose);
279
+ };
280
+ const handleOpen = () => {
281
+ cleanup();
282
+ connectionPromise = null;
283
+ resolve(socket);
284
+ };
285
+ const handleError = () => {
286
+ cleanup();
287
+ socket = null;
288
+ connectionPromise = null;
289
+ reject(new Error("WebSocket connection failed"));
290
+ };
291
+ const handleClose = () => {
292
+ cleanup();
293
+ socket = null;
294
+ connectionPromise = null;
295
+ reject(new Error("WebSocket closed before opening"));
296
+ };
297
+ socket.addEventListener("open", handleOpen);
298
+ socket.addEventListener("error", handleError);
299
+ socket.addEventListener("close", handleClose);
300
+ });
301
+ })();
302
+ return connectionPromise;
303
+ }
304
+ async function rpcCallWebSocket(methodId, args) {
305
+ // Optimization: If socket is open, send immediately without awaiting ensureSocketReady (which adds a microtask tick)
306
+ if (socket && socket.readyState === WebSocket.OPEN) {
307
+ const id = nextId();
308
+ const req = { id, method: methodId, args };
309
+ return new Promise((resolve, reject) => {
310
+ pending.set(id, {
311
+ resolve: (v) => resolve(v),
312
+ reject,
313
+ });
314
+ try {
315
+ const encoded = msgpackEncode(req);
316
+ socket.send(encoded);
317
+ }
318
+ catch (err) {
319
+ pending.delete(id);
320
+ reject(err);
321
+ }
322
+ });
323
+ }
324
+ const ws = await ensureSocketReady();
325
+ const id = nextId();
326
+ const req = { id, method: methodId, args };
327
+ return new Promise((resolve, reject) => {
328
+ pending.set(id, {
329
+ resolve: (v) => resolve(v),
330
+ reject,
331
+ });
332
+ try {
333
+ // Always use msgpack encoding
334
+ const encoded = msgpackEncode(req);
335
+ ws.send(encoded);
336
+ }
337
+ catch (err) {
338
+ pending.delete(id);
339
+ reject(err);
340
+ }
341
+ });
342
+ }
343
+ /**
344
+ * Make an RPC call using the appropriate transport.
345
+ * Automatically selects HTTP or WebSocket based on network conditions and configuration.
346
+ */
347
+ export async function rpcCall(methodId, args) {
348
+ if (shouldUseHttpTransport()) {
349
+ const id = nextId();
350
+ const req = { id, method: methodId, args };
351
+ return new Promise((resolve, reject) => {
352
+ pendingBatch.push({ req, resolve: resolve, reject });
353
+ scheduleBatch();
354
+ });
355
+ }
356
+ return rpcCallWebSocket(methodId, args);
357
+ }
358
+ /**
359
+ * Pre-establishes the WebSocket connection.
360
+ * Call this early (e.g., on page load) to avoid connection latency on first RPC call.
361
+ * This is especially beneficial on high-latency networks like mobile LTE.
362
+ * Note: Only effective when using WebSocket transport (not HTTP transport).
363
+ */
364
+ export function preconnect() {
365
+ if (typeof window === "undefined") {
366
+ return;
367
+ }
368
+ // Only preconnect if we're using WebSocket transport
369
+ if (shouldUseHttpTransport()) {
370
+ return;
371
+ }
372
+ // Fire and forget - establishes connection in background
373
+ ensureSocketReady().catch(() => {
374
+ // Silently ignore preconnect failures, will retry on actual call
375
+ });
376
+ }
377
+ // Auto-preconnect when the module loads (browser only, WebSocket transport only)
378
+ if (typeof window !== "undefined" && typeof document !== "undefined") {
379
+ // Use requestIdleCallback if available, otherwise setTimeout
380
+ const schedulePreconnect = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
381
+ schedulePreconnect(() => preconnect());
382
+ }
383
+ //# sourceMappingURL=rpcClient.js.map