@anaemia/core 0.0.1 → 0.1.1

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 (42) hide show
  1. package/dist/config.d.ts +3 -2
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/index.d.ts +1 -5
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/plugins/lightningcss.d.ts.map +1 -1
  6. package/dist/plugins/lightningcss.js +1 -2
  7. package/dist/runtime/context.browser.d.ts.map +1 -1
  8. package/dist/runtime/context.browser.js +3 -1
  9. package/dist/runtime/context.d.ts +5 -4
  10. package/dist/runtime/context.d.ts.map +1 -1
  11. package/dist/runtime/entry-client.jsx +1 -1
  12. package/dist/runtime/entry-server.d.ts.map +1 -1
  13. package/dist/runtime/entry-server.jsx +18 -29
  14. package/dist/runtime/resources.d.ts +1 -1
  15. package/dist/runtime/resources.d.ts.map +1 -1
  16. package/dist/runtime/resources.js +5 -3
  17. package/dist/runtime/route-data.d.ts +7 -6
  18. package/dist/runtime/route-data.d.ts.map +1 -1
  19. package/dist/runtime/route-data.js +5 -8
  20. package/dist/runtime/route-request.d.ts +1 -1
  21. package/dist/runtime/route-request.d.ts.map +1 -1
  22. package/dist/runtime/route-request.js +2 -1
  23. package/dist/runtime/rpc-client.d.ts +2 -2
  24. package/dist/runtime/rpc-client.d.ts.map +1 -1
  25. package/dist/runtime/rpc-client.js +12 -11
  26. package/dist/types.d.ts +6 -2
  27. package/dist/types.d.ts.map +1 -1
  28. package/package.json +3 -1
  29. package/src/config.ts +3 -2
  30. package/src/index.ts +1 -6
  31. package/src/plugins/lightningcss.ts +7 -3
  32. package/src/runtime/context.browser.ts +3 -2
  33. package/src/runtime/context.ts +8 -13
  34. package/src/runtime/entry-client.tsx +1 -1
  35. package/src/runtime/entry-server.tsx +47 -47
  36. package/src/runtime/resources.ts +28 -16
  37. package/src/runtime/route-data.ts +24 -36
  38. package/src/runtime/route-request.ts +5 -4
  39. package/src/runtime/rpc-client.ts +44 -24
  40. package/src/runtime/webpack.d.ts +1 -1
  41. package/src/types.ts +7 -2
  42. package/test/integration/hmr.test.mjs +16 -22
@@ -1,34 +1,29 @@
1
1
  import { AsyncLocalStorage } from "node:async_hooks";
2
2
 
3
- export const serverFunctionsRegistry = new Map<string, Function>();
4
- export const ssrStorage = new AsyncLocalStorage<Map<string, any>>();
3
+ type AnyFn = (...args: unknown[]) => unknown;
5
4
 
6
- (globalThis as any).__ANAEMIA_SERVER_STORAGE__ = ssrStorage;
5
+ export const serverFunctionsRegistry = new Map<string, AnyFn>();
6
+ export const ssrStorage = new AsyncLocalStorage<Map<string, unknown>>();
7
+ (globalThis as unknown as Record<string, unknown>).__ANAEMIA_SERVER_STORAGE__ = ssrStorage;
7
8
 
8
- export function runOnServer(backendFn: Function, id?: string) {
9
+ export function runOnServer<T extends AnyFn>(backendFn: T, id?: string): T & { id: string } {
9
10
  const hashId = id || "";
10
-
11
- const rpcProxy = async function (...args: any[]) {
11
+ const rpcProxy = async function (...args: unknown[]) {
12
12
  const result = await backendFn(...args);
13
-
14
13
  const store = ssrStorage.getStore();
15
14
  if (store && hashId) {
16
15
  if (!store.has("__SERVER_FUNCTION_DATA__")) {
17
16
  store.set("__SERVER_FUNCTION_DATA__", {});
18
17
  }
19
-
20
- const functionCache = store.get("__SERVER_FUNCTION_DATA__");
18
+ const functionCache = store.get("__SERVER_FUNCTION_DATA__") as Record<string, Record<string, unknown>>;
21
19
  if (!functionCache[hashId]) {
22
20
  functionCache[hashId] = {};
23
21
  }
24
-
25
22
  const paramKey = JSON.stringify(args);
26
23
  functionCache[hashId][paramKey] = result;
27
24
  }
28
-
29
25
  return result;
30
26
  };
31
-
32
27
  rpcProxy.id = hashId;
33
- return rpcProxy;
28
+ return rpcProxy as unknown as T & { id: string };
34
29
  }
@@ -1,7 +1,7 @@
1
1
  import { hydrate, render } from "solid-js/web";
2
2
  import { Router } from "@solidjs/router";
3
3
 
4
- // @ts-ignore
4
+ // @ts-expect-error - resolved by Rspack
5
5
  import App, { preloadActiveClientRoute } from "anaemia-user-app";
6
6
 
7
7
  const mountTarget = document.querySelector(
@@ -1,5 +1,6 @@
1
1
  import { Hono } from "hono";
2
2
  import { serve } from "@hono/node-server";
3
+ import type { Context } from "hono";
3
4
  import { serveStatic } from "@hono/node-server/serve-static";
4
5
  import { compress } from "hono/compress";
5
6
  import { renderToStringAsync, generateHydrationScript } from "solid-js/web";
@@ -9,13 +10,13 @@ import fs from "node:fs";
9
10
  import path from "path";
10
11
  import type { StatusCode, RedirectStatusCode } from "hono/utils/http-status";
11
12
 
12
- // @ts-ignore - mapped by Rspack
13
+ // @ts-expect-error - resolved by Rspack
13
14
  import App from "anaemia-user-app";
14
15
 
15
- // @ts-ignore - mapped by Rspack
16
+ // @ts-expect-error - resolved by Rspack
16
17
  import { preloadActiveClientRoute, serverLoaderRegistry, serverGuardRegistry } from "anaemia-user-app";
17
18
 
18
- // @ts-ignore - mapped by Rspack
19
+ // @ts-expect-error - resolved by Rspack
19
20
  import { registerServerRoutes } from "__anaemia_server_routes__";
20
21
 
21
22
  const port = Number(process.env.PORT) || 3000;
@@ -24,22 +25,34 @@ const isDev = process.env.NODE_ENV !== "production";
24
25
  const devPort = Number(process.env.RSPACK_DEV_PORT) || 4445;
25
26
  const devServerUrl = `http://localhost:${devPort}`;
26
27
 
27
- let sortedRoutes: any[] | null = null;
28
+ interface ChunkAssets {
29
+ js?: string[];
30
+ css?: string[];
31
+ }
32
+
33
+ interface RouteManifest {
34
+ routes: Array<{ urlPattern: string; chunkName: string; params: string[] }>;
35
+ chunks: Record<string, ChunkAssets>;
36
+ errors?: Record<string, string>;
37
+ }
38
+
39
+ type SortedRoute = { urlPattern: string; chunkName: string; params: string[] };
40
+ let sortedRoutes: SortedRoute[] | null = null;
28
41
 
29
- const ENTRY_TAG_REGEX = /(<([a-zA-Z0-9\-]+)[^>]*anaemia-entry[^>]*>)(.*?)(<\/\2>)/is;
42
+ const ENTRY_TAG_REGEX = /(<([a-zA-Z0-9-]+)[^>]*anaemia-entry[^>]*>)(.*?)(<\/\2>)/is;
30
43
 
31
44
  const app = new Hono();
32
45
 
33
46
  app.use("*", compress());
34
47
 
35
48
  app.use("*", async (c, next) => {
36
- const store = new Map<string, any>();
49
+ const store = new Map<string, unknown>();
37
50
  store.set("honoContext", c);
38
51
  return await ssrStorage.run(store, next);
39
52
  });
40
53
 
41
54
  if (isDev) {
42
- const devAssetProxy = async (c: any) => {
55
+ const devAssetProxy = async (c: Context) => {
43
56
  const targetUrl = `${devServerUrl}${c.req.path}`;
44
57
  try {
45
58
  const response = await fetch(targetUrl);
@@ -53,7 +66,7 @@ if (isDev) {
53
66
  c.header("Expires", "0");
54
67
 
55
68
  return c.body(await response.arrayBuffer());
56
- } catch (err) {
69
+ } catch {
57
70
  return c.text("failed to connect to Rspack dev server asset bridge", 500);
58
71
  }
59
72
  };
@@ -96,8 +109,9 @@ app.post("/_rpc", async (c) => {
96
109
  try {
97
110
  const result = await serverFunctionsRegistry.get(functionId)!(...argumentsArray);
98
111
  return c.json(result);
99
- } catch (error: any) {
100
- return c.json({ error: error.message }, 500);
112
+ } catch (error) {
113
+ const message = error instanceof Error ? error.message : "Internal server error";
114
+ return c.json({ error: message }, 500);
101
115
  }
102
116
  });
103
117
 
@@ -108,12 +122,12 @@ app.use(async (c, next) => {
108
122
  try {
109
123
  const response = await fetch(targetUrl);
110
124
  if (!response.ok) return c.text("hot update not found", 404);
111
-
125
+
112
126
  const contentType = response.headers.get("content-type");
113
127
  if (contentType) c.header("content-type", contentType);
114
-
128
+
115
129
  c.header("Cache-Control", "no-cache, no-store, must-revalidate");
116
-
130
+
117
131
  return c.body(await response.arrayBuffer());
118
132
  } catch {
119
133
  return c.text("failed to fetch hot update", 500);
@@ -122,11 +136,10 @@ app.use(async (c, next) => {
122
136
  await next();
123
137
  });
124
138
 
125
-
126
139
  registerServerRoutes(app);
127
140
 
128
141
  let memoizedHtmlTemplate = "";
129
- let memoizedManifest: any = null;
142
+ let memoizedManifest: RouteManifest | null = null;
130
143
 
131
144
  const templatePath = path.resolve(process.cwd(), "./dist/client/index.html");
132
145
  const manifestPath = path.resolve(process.cwd(), "./dist/route-manifest.json");
@@ -157,7 +170,7 @@ const loadManifestAndTemplate = async () => {
157
170
  try {
158
171
  if (fs.existsSync(templatePath)) memoizedHtmlTemplate = fs.readFileSync(templatePath, "utf-8");
159
172
  if (fs.existsSync(manifestPath)) memoizedManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
160
- } catch (err) {
173
+ } catch {
161
174
  console.warn("build assets not fully initialized during bootstrapping cycle.");
162
175
  }
163
176
  }
@@ -192,9 +205,9 @@ async function runGuards(pattern: string, ctx: { params: Record<string, string>;
192
205
  return null;
193
206
  }
194
207
 
195
- function matchRoute(manifest: any, reqPath: string): RouteMatch {
208
+ function matchRoute(manifest: RouteManifest, reqPath: string): RouteMatch {
196
209
  if (!sortedRoutes) {
197
- sortedRoutes = [...manifest.routes].sort((a: any, b: any) => {
210
+ sortedRoutes = [...manifest.routes].sort((a, b) => {
198
211
  const score = (pattern: string) => {
199
212
  const segments = pattern.split("/").filter(Boolean);
200
213
  return segments.reduce((acc, s) => {
@@ -229,8 +242,6 @@ function matchRoute(manifest: any, reqPath: string): RouteMatch {
229
242
  };
230
243
  }
231
244
 
232
- // Look at your app.get("*") loop and update the processing logic:
233
-
234
245
  app.get("*", async (c) => {
235
246
  if (isDev) await loadManifestAndTemplate();
236
247
 
@@ -246,9 +257,8 @@ app.get("*", async (c) => {
246
257
  let statusCode: StatusCode = matchedStatus;
247
258
  const loaderArgs = { params, request: c.req.raw };
248
259
 
249
- // Re-verify and isolate our store reference map instance
250
- const store = ssrStorage.getStore() || new Map<string, any>();
251
- let htmlPayload = "";
260
+ const store = ssrStorage.getStore() || new Map<string, unknown>();
261
+ let htmlPayload;
252
262
 
253
263
  if (targetPattern) {
254
264
  try {
@@ -263,9 +273,6 @@ app.get("*", async (c) => {
263
273
  }
264
274
  }
265
275
 
266
- // ─── THE ARCHITECTURE WRAPPER FIX ───
267
- // We force both the awaitable loader execution AND the Solid rendering cycle
268
- // to run explicitly inside a fresh execution slice of the tracking store.
269
276
  try {
270
277
  htmlPayload = await ssrStorage.run(store, async () => {
271
278
  if (targetPattern) {
@@ -278,14 +285,13 @@ app.get("*", async (c) => {
278
285
 
279
286
  await preloadActiveClientRoute(reqPath);
280
287
 
281
- // Now when Solid calls $$executeClientRpc, the store is 100% active and tracked!
282
288
  return await renderToStringAsync(() => (
283
289
  <Router url={reqPath}>
284
290
  <App />
285
291
  </Router>
286
292
  ));
287
293
  });
288
- } catch (err: any) {
294
+ } catch (err) {
289
295
  statusCode = 500;
290
296
  console.error("[anaemia framework] runtime execution crash handled:", err);
291
297
 
@@ -293,7 +299,9 @@ app.get("*", async (c) => {
293
299
  const error500Loader = error500Pattern ? serverLoaderRegistry.get(error500Pattern) : null;
294
300
 
295
301
  if (error500Loader) {
296
- const runtimeContextPayload = { message: err.message, stack: isDev ? err.stack : undefined };
302
+ const message = err instanceof Error ? err.message : String(err);
303
+ const stack = err instanceof Error ? err.stack : undefined;
304
+ const runtimeContextPayload = { message, stack: isDev ? stack : undefined };
297
305
  store.set("__LOADER_DATA__", runtimeContextPayload);
298
306
 
299
307
  try {
@@ -308,16 +316,16 @@ app.get("*", async (c) => {
308
316
  htmlPayload = `<h1>500 Internal Server Error</h1>`;
309
317
  }
310
318
  } else {
311
- htmlPayload = `<h1>500 Internal Server Error</h1><pre>${isDev ? err.stack : ""}</pre>`;
319
+ const stack = err instanceof Error ? err.stack : String(err);
320
+ htmlPayload = `<h1>500 Internal Server Error</h1><pre>${isDev ? stack : ""}</pre>`;
312
321
  }
313
322
  }
314
323
 
315
- // ─── THE REMAINING INJECTIONS (Keep this exactly as you had it) ───
316
324
  let assetScripts = "";
317
325
  let assetStyles = "";
318
326
 
319
327
  if (manifest.chunks) {
320
- const processChunkAssets = (chunk: any) => {
328
+ const processChunkAssets = (chunk: ChunkAssets | undefined) => {
321
329
  if (!chunk) return;
322
330
  if (chunk.js) {
323
331
  const jsSpecs = Array.isArray(chunk.js) ? chunk.js : [chunk.js];
@@ -338,33 +346,25 @@ app.get("*", async (c) => {
338
346
  if (manifest.chunks["vendors"]) processChunkAssets(manifest.chunks["vendors"]);
339
347
  if (activeChunk && activeChunk !== "client") processChunkAssets(manifest.chunks[activeChunk]);
340
348
  }
341
-
349
+
342
350
  const hydrationScript = generateHydrationScript();
343
351
  const rawStorePayload = Object.fromEntries(store);
344
-
352
+
345
353
  const finalHydrationStatePayload = {
346
354
  __LOADER_DATA__: rawStorePayload.__LOADER_DATA__ || {},
347
- __SERVER_FUNCTION_DATA__: rawStorePayload.__SERVER_FUNCTION_DATA__ || {}
355
+ __SERVER_FUNCTION_DATA__: rawStorePayload.__SERVER_FUNCTION_DATA__ || {},
348
356
  };
349
357
 
350
- const serializedData = JSON.stringify(finalHydrationStatePayload)
351
- .replace(/&/g, "\\u0026")
352
- .replace(/</g, "\\u003c")
353
- .replace(/>/g, "\\u003e")
354
- .replace(/\//g, "\\u002f");
355
-
358
+ const serializedData = JSON.stringify(finalHydrationStatePayload).replace(/&/g, "\\u0026").replace(/</g, "\\u003c").replace(/>/g, "\\u003e").replace(/\//g, "\\u002f");
359
+
356
360
  const dataScript = `<script id="__ANAEMIA_DATA__" type="application/json">${serializedData}</script>\n`;
357
361
 
358
- const devNoCacheTag = isDev
359
- ? `<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">\n<meta http-equiv="Pragma" content="no-cache">\n<meta http-equiv="Expires" content="0">\n`
360
- : "";
362
+ const devNoCacheTag = isDev ? `<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">\n<meta http-equiv="Pragma" content="no-cache">\n<meta http-equiv="Expires" content="0">\n` : "";
361
363
 
362
364
  const combinedHeadInjections = `${devNoCacheTag}${assetStyles}${dataScript}${hydrationScript}`;
363
365
  const sanitizedPayload = htmlPayload.trim();
364
366
 
365
- let completeHtmlOutput = ENTRY_TAG_REGEX.test(template)
366
- ? template.replace(ENTRY_TAG_REGEX, (_, open, _tag, _inner, close) => `${open}${sanitizedPayload}${close}`)
367
- : template.replace("</body>", () => `<div anaemia-entry>${sanitizedPayload}</div></body>`);
367
+ let completeHtmlOutput = ENTRY_TAG_REGEX.test(template) ? template.replace(ENTRY_TAG_REGEX, (_, open, _tag, _inner, close) => `${open}${sanitizedPayload}${close}`) : template.replace("</body>", () => `<div anaemia-entry>${sanitizedPayload}</div></body>`);
368
368
 
369
369
  completeHtmlOutput = completeHtmlOutput.replace("<head>", `<head>${combinedHeadInjections}`);
370
370
  completeHtmlOutput = completeHtmlOutput.replace("</body>", `${assetScripts}</body>`);
@@ -1,37 +1,48 @@
1
1
  import { createResource, type ResourceOptions, type ResourceReturn } from "solid-js";
2
2
  import { isServer } from "solid-js/web";
3
3
 
4
+ interface ServerStorage {
5
+ getStore?: () => Map<string, unknown> | undefined;
6
+ }
7
+
8
+ interface AnaemiaGlobal {
9
+ __ANAEMIA_SERVER_STORAGE__?: ServerStorage;
10
+ }
11
+
12
+ type FnCache = Record<string, unknown>;
13
+ type ServerFunctionData = Record<string, FnCache>;
14
+
4
15
  export function createServerResource<Source, Return>(
5
16
  source: () => Source,
6
17
  serverFn: ((sourceData: Source) => Promise<Return>) & {
7
- readHydrationCache?: (s: any) => any;
18
+ readHydrationCache?: (s: Source) => Return | undefined;
8
19
  id?: string;
9
20
  },
10
21
  options?: ResourceOptions<Return, Source>
11
22
  ): ResourceReturn<Return, unknown> {
12
23
  if (isServer) {
13
- let ssrInitialValue: any = undefined;
14
- const store: Map<string, any> | undefined =
15
- (globalThis as any).__ANAEMIA_SERVER_STORAGE__?.getStore?.();
24
+ let ssrInitialValue: Return | undefined = undefined;
25
+ const store = (globalThis as unknown as AnaemiaGlobal).__ANAEMIA_SERVER_STORAGE__?.getStore?.();
16
26
 
17
27
  if (store && serverFn.id) {
18
- const fnCache = store.get("__SERVER_FUNCTION_DATA__")?.[serverFn.id];
28
+ const fnData = store.get("__SERVER_FUNCTION_DATA__") as ServerFunctionData | undefined;
29
+ const fnCache = fnData?.[serverFn.id];
19
30
  if (fnCache) {
20
- const key = JSON.stringify([source()]);
21
- if (fnCache[key] !== undefined) ssrInitialValue = fnCache[key];
31
+ const sourceValue = source();
32
+ const key = sourceValue === undefined ? JSON.stringify([]) : JSON.stringify([sourceValue]);
33
+ if (fnCache[key] !== undefined) ssrInitialValue = fnCache[key] as Return;
22
34
  }
23
35
  }
24
36
 
25
- return createResource(source, serverFn as any, {
37
+ return createResource(source, serverFn, {
26
38
  ...options,
27
39
  initialValue: ssrInitialValue !== undefined ? ssrInitialValue : options?.initialValue,
28
40
  ssrLoadFrom: ssrInitialValue !== undefined ? "initial" : options?.ssrLoadFrom,
29
- } as any) as ResourceReturn<Return, unknown>;
41
+ }) as ResourceReturn<Return, unknown>;
30
42
  }
31
43
 
32
44
  let hydrationChecked = false;
33
-
34
- const wrappedFetcher = (s: Source) => {
45
+ const wrappedFetcher = (s: Source): Promise<Return> => {
35
46
  if (!hydrationChecked) {
36
47
  hydrationChecked = true;
37
48
  if (typeof serverFn.readHydrationCache === "function") {
@@ -39,11 +50,12 @@ export function createServerResource<Source, Return>(
39
50
  if (cached !== undefined) return Promise.resolve(cached);
40
51
  }
41
52
  }
42
- return serverFn(s);
53
+
54
+ return s === undefined ? (serverFn as () => Promise<Return>)() : serverFn(s);
43
55
  };
44
56
 
45
- (wrappedFetcher as any).id = serverFn.id;
46
- (wrappedFetcher as any).readHydrationCache = serverFn.readHydrationCache;
57
+ (wrappedFetcher as typeof serverFn).id = serverFn.id;
58
+ (wrappedFetcher as typeof serverFn).readHydrationCache = serverFn.readHydrationCache;
47
59
 
48
- return createResource(source, wrappedFetcher as any, options) as ResourceReturn<Return, unknown>;
49
- }
60
+ return createResource(source, wrappedFetcher, options) as ResourceReturn<Return, unknown>;
61
+ }
@@ -1,93 +1,81 @@
1
- import {
2
- createContext,
3
- useContext,
4
- createResource,
5
- createComponent,
6
- type JSX
7
- } from "solid-js";
1
+ import { createContext, useContext, createResource, createComponent, type JSX } from "solid-js";
8
2
  import { isServer } from "solid-js/web";
9
3
  import { useParams, useLocation, type Params } from "@solidjs/router";
10
4
  import type { Location } from "@solidjs/router";
11
5
  import { ssrStorage } from "./context.js";
12
6
  import { createRouteRequest } from "./route-request.js";
13
7
 
8
+ type LoaderArgs<TParams extends Params> = {
9
+ params: TParams;
10
+ location: Location;
11
+ request: Request;
12
+ };
13
+
14
14
  type RouteDataControllerProps<TParams extends Params = Params> = {
15
- loader: (args: {
16
- params: TParams;
17
- location: Location;
18
- request: Request;
19
- }) => any | Promise<any>;
15
+ loader: (args: LoaderArgs<TParams>) => unknown | Promise<unknown>;
20
16
  children: JSX.Element;
21
17
  };
22
18
 
23
- type RouteDataContextValue<T = any> = {
19
+ type RouteDataContextValue<T = unknown> = {
24
20
  data: () => T;
25
21
  };
26
22
 
27
23
  const RouteDataContext = createContext<RouteDataContextValue>();
24
+
28
25
  let hasReadClientHydrationData = false;
29
26
 
30
- function readSSRData() {
27
+ interface AnaemiaHydrationData {
28
+ __LOADER_DATA__?: unknown;
29
+ }
30
+
31
+ function readSSRData(): unknown {
31
32
  if (isServer) {
32
33
  return ssrStorage.getStore()?.get("__LOADER_DATA__");
33
34
  }
34
-
35
35
  if (hasReadClientHydrationData) return undefined;
36
36
  hasReadClientHydrationData = true;
37
-
38
37
  const el = document.getElementById("__ANAEMIA_DATA__");
39
38
  if (!el?.textContent) return undefined;
40
-
41
39
  try {
42
- return JSON.parse(el.textContent).__LOADER_DATA__;
40
+ return (JSON.parse(el.textContent) as AnaemiaHydrationData).__LOADER_DATA__;
43
41
  } catch {
44
42
  return undefined;
45
43
  }
46
44
  }
47
45
 
48
- export function RouteDataController<TParams extends Params = Params>(
49
- props: RouteDataControllerProps<TParams>
50
- ) {
46
+ export function RouteDataController<TParams extends Params = Params>(props: RouteDataControllerProps<TParams>) {
51
47
  const params = useParams<TParams>();
52
48
  const location = useLocation();
53
-
54
49
  const ssrData = readSSRData();
55
50
 
56
51
  const [resource] = createResource(
57
52
  () => location.pathname,
58
53
  () => {
59
- if (isServer && ssrData !== undefined) {
60
- return ssrData;
61
- }
62
-
54
+ if (isServer && ssrData !== undefined) return ssrData;
63
55
  return props.loader({
64
56
  params,
65
57
  location,
66
- request: createRouteRequest(location.pathname)
58
+ request: createRouteRequest(location.pathname),
67
59
  });
68
60
  },
69
61
  {
70
62
  initialValue: ssrData,
71
- ssrLoadFrom: "initial"
63
+ ssrLoadFrom: "initial",
72
64
  }
73
65
  );
74
66
 
75
67
  return createComponent(RouteDataContext.Provider, {
76
- value: {
77
- data: resource
78
- },
68
+ value: { data: resource },
79
69
  get children() {
80
70
  return props.children;
81
- }
71
+ },
82
72
  });
83
73
  }
84
74
 
85
- export function useRouteData<T = any>(): () => T {
75
+ export function useRouteData<T = unknown>(): () => T {
86
76
  const ctx = useContext(RouteDataContext);
87
-
88
77
  if (!ctx) {
89
78
  throw new Error("useRouteData must be used inside RouteDataController");
90
79
  }
91
-
92
- return ctx.data;
80
+ return ctx.data as () => T;
93
81
  }
@@ -1,12 +1,13 @@
1
1
  import { isServer } from "solid-js/web";
2
+ import type { Context } from "hono";
2
3
  import { ssrStorage } from "./context.js";
3
4
 
4
- export function createRouteRequest(pathname: string) {
5
+ export function createRouteRequest(pathname: string): Request {
5
6
  if (isServer) {
6
- const request = ssrStorage.getStore()?.get("honoContext")?.req?.raw;
7
+ const honoContext = ssrStorage.getStore()?.get("honoContext") as Context | undefined;
8
+ const request = honoContext?.req?.raw;
7
9
  if (request) return request;
8
10
  return new Request(new URL(pathname, "http://localhost").toString());
9
11
  }
10
-
11
12
  return new Request(new URL(pathname, window.location.origin).toString());
12
- }
13
+ }
@@ -1,39 +1,63 @@
1
1
  import { isServer } from "solid-js/web";
2
2
 
3
- let _clientCache: any = null;
3
+ interface CacheMatch {
4
+ matchingKey: string;
5
+ data: unknown;
6
+ }
7
+
8
+ interface ServerFunctionCache {
9
+ [hashId: string]: Record<string, unknown>;
10
+ }
11
+
12
+ interface AnaemiaClientCache {
13
+ __SERVER_FUNCTION_DATA__?: ServerFunctionCache;
14
+ }
15
+
16
+ interface AnaemiaServerStorage {
17
+ getStore?: () => Map<string, unknown> | undefined;
18
+ }
19
+
20
+ interface AnaemiaGlobal {
21
+ __ANAEMIA_SERVER_STORAGE__?: AnaemiaServerStorage;
22
+ }
23
+
24
+ let _clientCache: AnaemiaClientCache | null = null;
4
25
 
5
26
  function ensureCacheInitialized() {
6
27
  if (isServer || _clientCache) return;
7
28
  const script = document.getElementById("__ANAEMIA_DATA__");
8
29
  try {
9
- _clientCache = JSON.parse(script?.textContent || "{}");
30
+ _clientCache = JSON.parse(script?.textContent || "{}") as AnaemiaClientCache;
10
31
  } catch {
11
32
  _clientCache = {};
12
33
  }
13
34
  }
14
35
 
15
- function findLooseCacheMatch(serverFunctionData: Record<string, any>, targetArg: string) {
36
+ function findLooseCacheMatch(
37
+ serverFunctionData: Record<string, unknown>,
38
+ targetArg: string
39
+ ): CacheMatch | undefined {
16
40
  if (!serverFunctionData) return undefined;
17
-
18
41
  const strictKey = JSON.stringify([targetArg]);
19
42
  if (strictKey in serverFunctionData) {
20
43
  return { matchingKey: strictKey, data: serverFunctionData[strictKey] };
21
44
  }
22
-
23
45
  const lookUpString = `["${targetArg}"`;
24
- const matchedKey = Object.keys(serverFunctionData).find(key => key.startsWith(lookUpString));
25
-
46
+ const matchedKey = Object.keys(serverFunctionData).find((key) => key.startsWith(lookUpString));
26
47
  return matchedKey ? { matchingKey: matchedKey, data: serverFunctionData[matchedKey] } : undefined;
27
48
  }
28
49
 
50
+ function getServerStore(): Map<string, unknown> | undefined {
51
+ return (globalThis as unknown as AnaemiaGlobal).__ANAEMIA_SERVER_STORAGE__?.getStore?.();
52
+ }
53
+
29
54
  export function $$executeClientRpc(hashId: string) {
30
55
  const asyncRpcCall = async function (...args: unknown[]) {
31
56
  if (isServer) {
32
- const globalStorage = (globalThis as any).__ANAEMIA_SERVER_STORAGE__;
33
- const store = globalStorage?.getStore();
57
+ const store = getServerStore();
34
58
  if (store) {
35
- const functionCache = store.get("__SERVER_FUNCTION_DATA__");
36
- if (functionCache && functionCache[hashId]) {
59
+ const functionCache = store.get("__SERVER_FUNCTION_DATA__") as ServerFunctionCache | undefined;
60
+ if (functionCache?.[hashId]) {
37
61
  const match = findLooseCacheMatch(functionCache[hashId], args[0] as string);
38
62
  if (match) return match.data;
39
63
  }
@@ -42,12 +66,11 @@ export function $$executeClientRpc(hashId: string) {
42
66
  }
43
67
 
44
68
  ensureCacheInitialized();
45
- const serverFunctionData = _clientCache.__SERVER_FUNCTION_DATA__?.[hashId];
46
- const match = findLooseCacheMatch(serverFunctionData, args[0] as string);
47
-
69
+ const serverFunctionData = _clientCache?.__SERVER_FUNCTION_DATA__?.[hashId];
70
+ const match = findLooseCacheMatch(serverFunctionData ?? {}, args[0] as string);
48
71
  if (match) {
49
72
  const { matchingKey, data } = match;
50
- delete serverFunctionData[matchingKey];
73
+ delete serverFunctionData![matchingKey];
51
74
  return data;
52
75
  }
53
76
 
@@ -56,20 +79,17 @@ export function $$executeClientRpc(hashId: string) {
56
79
  headers: { "Content-Type": "application/json" },
57
80
  body: JSON.stringify(args),
58
81
  });
59
-
60
82
  if (!response.ok) throw new Error(`[anaemia] RPC execution failed: ${response.status}`);
61
- return await response.json();
83
+ return await response.json() as unknown;
62
84
  };
63
85
 
64
86
  asyncRpcCall.id = hashId;
65
-
66
87
  asyncRpcCall.readHydrationCache = function (...args: unknown[]) {
67
88
  if (isServer) {
68
- const globalStorage = (globalThis as any).__ANAEMIA_SERVER_STORAGE__;
69
- const store = globalStorage?.getStore();
89
+ const store = getServerStore();
70
90
  if (store) {
71
- const functionCache = store.get("__SERVER_FUNCTION_DATA__");
72
- if (functionCache && functionCache[hashId]) {
91
+ const functionCache = store.get("__SERVER_FUNCTION_DATA__") as ServerFunctionCache | undefined;
92
+ if (functionCache?.[hashId]) {
73
93
  const match = findLooseCacheMatch(functionCache[hashId], args[0] as string);
74
94
  if (match) return match.data;
75
95
  }
@@ -78,8 +98,8 @@ export function $$executeClientRpc(hashId: string) {
78
98
  }
79
99
 
80
100
  ensureCacheInitialized();
81
- const serverFunctionData = _clientCache.__SERVER_FUNCTION_DATA__?.[hashId];
82
- const match = findLooseCacheMatch(serverFunctionData, args[0] as string);
101
+ const serverFunctionData = _clientCache?.__SERVER_FUNCTION_DATA__?.[hashId];
102
+ const match = findLooseCacheMatch(serverFunctionData ?? {}, args[0] as string);
83
103
  return match ? match.data : undefined;
84
104
  };
85
105
 
@@ -1,6 +1,6 @@
1
1
  interface ImportMeta {
2
2
  webpackHot?: {
3
3
  accept(dep: string | string[], callback?: () => void): void;
4
- dispose(callback: (data: any) => void): void;
4
+ dispose(callback: (data: unknown) => void): void;
5
5
  };
6
6
  }