@anaemia/core 0.0.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 (59) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +3 -0
  3. package/dist/config.d.ts +63 -0
  4. package/dist/config.d.ts.map +1 -0
  5. package/dist/config.js +3 -0
  6. package/dist/index.d.ts +11 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +5 -0
  9. package/dist/plugins/index.d.ts +2 -0
  10. package/dist/plugins/index.d.ts.map +1 -0
  11. package/dist/plugins/index.js +1 -0
  12. package/dist/plugins/lightningcss.d.ts +5 -0
  13. package/dist/plugins/lightningcss.d.ts.map +1 -0
  14. package/dist/plugins/lightningcss.js +69 -0
  15. package/dist/runtime/context.browser.d.ts +4 -0
  16. package/dist/runtime/context.browser.d.ts.map +1 -0
  17. package/dist/runtime/context.browser.js +4 -0
  18. package/dist/runtime/context.d.ts +8 -0
  19. package/dist/runtime/context.d.ts.map +1 -0
  20. package/dist/runtime/context.js +25 -0
  21. package/dist/runtime/entry-client.d.ts +2 -0
  22. package/dist/runtime/entry-client.d.ts.map +1 -0
  23. package/dist/runtime/entry-client.jsx +19 -0
  24. package/dist/runtime/entry-server.d.ts +4 -0
  25. package/dist/runtime/entry-server.d.ts.map +1 -0
  26. package/dist/runtime/entry-server.jsx +346 -0
  27. package/dist/runtime/resources.d.ts +6 -0
  28. package/dist/runtime/resources.d.ts.map +1 -0
  29. package/dist/runtime/resources.js +36 -0
  30. package/dist/runtime/route-data.d.ts +15 -0
  31. package/dist/runtime/route-data.d.ts.map +1 -0
  32. package/dist/runtime/route-data.js +57 -0
  33. package/dist/runtime/route-request.d.ts +2 -0
  34. package/dist/runtime/route-request.d.ts.map +1 -0
  35. package/dist/runtime/route-request.js +11 -0
  36. package/dist/runtime/rpc-client.d.ts +6 -0
  37. package/dist/runtime/rpc-client.d.ts.map +1 -0
  38. package/dist/runtime/rpc-client.js +78 -0
  39. package/dist/types.d.ts +49 -0
  40. package/dist/types.d.ts.map +1 -0
  41. package/dist/types.js +1 -0
  42. package/package.json +61 -0
  43. package/src/config.ts +74 -0
  44. package/src/index.ts +13 -0
  45. package/src/plugins/index.ts +1 -0
  46. package/src/plugins/lightningcss.ts +82 -0
  47. package/src/runtime/context.browser.ts +5 -0
  48. package/src/runtime/context.ts +34 -0
  49. package/src/runtime/entry-client.tsx +34 -0
  50. package/src/runtime/entry-server.tsx +393 -0
  51. package/src/runtime/resources.ts +49 -0
  52. package/src/runtime/route-data.ts +93 -0
  53. package/src/runtime/route-request.ts +12 -0
  54. package/src/runtime/rpc-client.ts +87 -0
  55. package/src/runtime/webpack.d.ts +6 -0
  56. package/src/types.ts +32 -0
  57. package/test/integration/hmr.test.mjs +152 -0
  58. package/test/run-on-server.test.mjs +48 -0
  59. package/tsconfig.json +12 -0
@@ -0,0 +1,346 @@
1
+ import { Hono } from "hono";
2
+ import { serve } from "@hono/node-server";
3
+ import { serveStatic } from "@hono/node-server/serve-static";
4
+ import { compress } from "hono/compress";
5
+ import { renderToStringAsync, generateHydrationScript } from "solid-js/web";
6
+ import { Router } from "@solidjs/router";
7
+ import { ssrStorage, serverFunctionsRegistry } from "./context.js";
8
+ import fs from "node:fs";
9
+ import path from "path";
10
+ // @ts-ignore - mapped by Rspack
11
+ import App from "anaemia-user-app";
12
+ // @ts-ignore - mapped by Rspack
13
+ import { preloadActiveClientRoute, serverLoaderRegistry, serverGuardRegistry } from "anaemia-user-app";
14
+ // @ts-ignore - mapped by Rspack
15
+ import { registerServerRoutes } from "__anaemia_server_routes__";
16
+ const port = Number(process.env.PORT) || 3000;
17
+ const isDev = process.env.NODE_ENV !== "production";
18
+ const devPort = Number(process.env.RSPACK_DEV_PORT) || 4445;
19
+ const devServerUrl = `http://localhost:${devPort}`;
20
+ let sortedRoutes = null;
21
+ const ENTRY_TAG_REGEX = /(<([a-zA-Z0-9\-]+)[^>]*anaemia-entry[^>]*>)(.*?)(<\/\2>)/is;
22
+ const app = new Hono();
23
+ app.use("*", compress());
24
+ app.use("*", async (c, next) => {
25
+ const store = new Map();
26
+ store.set("honoContext", c);
27
+ return await ssrStorage.run(store, next);
28
+ });
29
+ if (isDev) {
30
+ const devAssetProxy = async (c) => {
31
+ const targetUrl = `${devServerUrl}${c.req.path}`;
32
+ try {
33
+ const response = await fetch(targetUrl);
34
+ if (!response.ok)
35
+ return c.text("asset not found in Rspack memory", 404);
36
+ const contentType = response.headers.get("content-type");
37
+ if (contentType)
38
+ c.header("content-type", contentType);
39
+ c.header("Cache-Control", "no-cache, no-store, must-revalidate");
40
+ c.header("Pragma", "no-cache");
41
+ c.header("Expires", "0");
42
+ return c.body(await response.arrayBuffer());
43
+ }
44
+ catch (err) {
45
+ return c.text("failed to connect to Rspack dev server asset bridge", 500);
46
+ }
47
+ };
48
+ app.get("/assets/*", devAssetProxy);
49
+ }
50
+ else {
51
+ app.use("/assets/*", async (c, next) => {
52
+ await next();
53
+ if (c.res.ok)
54
+ c.res.headers.set("Cache-Control", "public, max-age=31536000, immutable");
55
+ });
56
+ app.use("/assets/*", serveStatic({
57
+ root: path.resolve(process.cwd(), "./dist/client"),
58
+ }));
59
+ }
60
+ app.post("/_rpc", async (c) => {
61
+ const functionId = c.req.query("id");
62
+ if (!functionId || !serverFunctionsRegistry.has(functionId)) {
63
+ return c.json({ error: "RPC function not found" }, 404);
64
+ }
65
+ const contentLength = Number(c.req.header("content-length") ?? 0);
66
+ if (contentLength > 512_000) {
67
+ return c.json({ error: "Payload too large" }, 413);
68
+ }
69
+ let argumentsArray;
70
+ try {
71
+ const body = await c.req.json();
72
+ if (!Array.isArray(body))
73
+ throw new Error("Expected array");
74
+ argumentsArray = body;
75
+ }
76
+ catch {
77
+ return c.json({ error: "Invalid request body" }, 400);
78
+ }
79
+ try {
80
+ const result = await serverFunctionsRegistry.get(functionId)(...argumentsArray);
81
+ return c.json(result);
82
+ }
83
+ catch (error) {
84
+ return c.json({ error: error.message }, 500);
85
+ }
86
+ });
87
+ app.use(async (c, next) => {
88
+ const p = c.req.path;
89
+ if (isDev && p.includes(".hot-update.")) {
90
+ const targetUrl = `${devServerUrl}${p}`;
91
+ try {
92
+ const response = await fetch(targetUrl);
93
+ if (!response.ok)
94
+ return c.text("hot update not found", 404);
95
+ const contentType = response.headers.get("content-type");
96
+ if (contentType)
97
+ c.header("content-type", contentType);
98
+ c.header("Cache-Control", "no-cache, no-store, must-revalidate");
99
+ return c.body(await response.arrayBuffer());
100
+ }
101
+ catch {
102
+ return c.text("failed to fetch hot update", 500);
103
+ }
104
+ }
105
+ await next();
106
+ });
107
+ registerServerRoutes(app);
108
+ let memoizedHtmlTemplate = "";
109
+ let memoizedManifest = null;
110
+ const templatePath = path.resolve(process.cwd(), "./dist/client/index.html");
111
+ const manifestPath = path.resolve(process.cwd(), "./dist/route-manifest.json");
112
+ const loadManifestAndTemplate = async () => {
113
+ if (isDev) {
114
+ try {
115
+ memoizedHtmlTemplate = await fetch(`${devServerUrl}/index.html`).then((r) => {
116
+ if (!r.ok)
117
+ throw new Error(`index.html fetch failed: ${r.status}`);
118
+ return r.text();
119
+ });
120
+ }
121
+ catch (err) {
122
+ console.error("[anaemia engine sync error - HTML fetch failed]:", err);
123
+ memoizedHtmlTemplate = "";
124
+ }
125
+ try {
126
+ if (fs.existsSync(manifestPath)) {
127
+ memoizedManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
128
+ }
129
+ else {
130
+ memoizedManifest = { routes: [], chunks: {}, errors: {} };
131
+ }
132
+ }
133
+ catch (err) {
134
+ console.error("[anaemia engine sync error - manifest read failed]:", err);
135
+ memoizedManifest = { routes: [], chunks: {}, errors: {} };
136
+ }
137
+ }
138
+ else {
139
+ try {
140
+ if (fs.existsSync(templatePath))
141
+ memoizedHtmlTemplate = fs.readFileSync(templatePath, "utf-8");
142
+ if (fs.existsSync(manifestPath))
143
+ memoizedManifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
144
+ }
145
+ catch (err) {
146
+ console.warn("build assets not fully initialized during bootstrapping cycle.");
147
+ }
148
+ }
149
+ sortedRoutes = null;
150
+ };
151
+ const normalizeAssetUrl = (url) => {
152
+ if (!url || typeof url !== "string")
153
+ return "";
154
+ if (url.startsWith("http://") || url.startsWith("https://"))
155
+ return url;
156
+ return url.startsWith("/") ? url : `/${url}`;
157
+ };
158
+ async function runGuards(pattern, ctx) {
159
+ const chain = serverGuardRegistry.get(pattern) ?? [];
160
+ for (const loadGuards of chain) {
161
+ const guards = await loadGuards();
162
+ for (const guard of guards) {
163
+ const result = await guard(ctx);
164
+ if (result && ("redirect" in result || "status" in result))
165
+ return result;
166
+ }
167
+ }
168
+ return null;
169
+ }
170
+ function matchRoute(manifest, reqPath) {
171
+ if (!sortedRoutes) {
172
+ sortedRoutes = [...manifest.routes].sort((a, b) => {
173
+ const score = (pattern) => {
174
+ const segments = pattern.split("/").filter(Boolean);
175
+ return segments.reduce((acc, s) => {
176
+ if (s.startsWith(":"))
177
+ return acc - 1;
178
+ if (s === "*" || s.startsWith("*"))
179
+ return acc - 2;
180
+ return acc;
181
+ }, segments.length * 10);
182
+ };
183
+ return score(b.urlPattern) - score(a.urlPattern);
184
+ });
185
+ }
186
+ for (const route of sortedRoutes) {
187
+ const regexStr = route.urlPattern.replace(/:([a-zA-Z0-9_-]+)/g, "(?<$1>[^/]+)").replace(/\*([a-zA-Z0-9_-]*)/g, "(?<catchall>.*)");
188
+ const match = new RegExp(`^${regexStr}$`).exec(reqPath);
189
+ if (match) {
190
+ return {
191
+ activeChunk: route.chunkName,
192
+ targetPattern: route.urlPattern,
193
+ statusCode: 200,
194
+ params: match.groups ? { ...match.groups } : {},
195
+ };
196
+ }
197
+ }
198
+ return {
199
+ activeChunk: "route-404",
200
+ targetPattern: manifest.errors?.["404"] || "",
201
+ statusCode: 404,
202
+ params: {},
203
+ };
204
+ }
205
+ // Look at your app.get("*") loop and update the processing logic:
206
+ app.get("*", async (c) => {
207
+ if (isDev)
208
+ await loadManifestAndTemplate();
209
+ let template = memoizedHtmlTemplate;
210
+ let manifest = memoizedManifest;
211
+ if (!template || !manifest) {
212
+ return c.text("anaemia engine error: build assets are missing", 500);
213
+ }
214
+ const reqPath = c.req.path;
215
+ const { activeChunk, targetPattern, statusCode: matchedStatus, params } = matchRoute(manifest, reqPath);
216
+ let statusCode = matchedStatus;
217
+ const loaderArgs = { params, request: c.req.raw };
218
+ // Re-verify and isolate our store reference map instance
219
+ const store = ssrStorage.getStore() || new Map();
220
+ let htmlPayload = "";
221
+ if (targetPattern) {
222
+ try {
223
+ const guardResult = await runGuards(targetPattern, { params, request: c.req.raw, url: reqPath });
224
+ if (guardResult) {
225
+ if ("redirect" in guardResult)
226
+ return c.redirect(guardResult.redirect, (guardResult.status ?? 302));
227
+ if ("status" in guardResult)
228
+ statusCode = guardResult.status;
229
+ }
230
+ }
231
+ catch (err) {
232
+ console.error("[anaemia] guard threw unexpectedly:", err);
233
+ return c.text("Internal Server Error", 500);
234
+ }
235
+ }
236
+ // ─── THE ARCHITECTURE WRAPPER FIX ───
237
+ // We force both the awaitable loader execution AND the Solid rendering cycle
238
+ // to run explicitly inside a fresh execution slice of the tracking store.
239
+ try {
240
+ htmlPayload = await ssrStorage.run(store, async () => {
241
+ if (targetPattern) {
242
+ const executableLoader = serverLoaderRegistry.get(targetPattern);
243
+ if (executableLoader) {
244
+ const initialLoaderData = await executableLoader(loaderArgs);
245
+ store.set("__LOADER_DATA__", initialLoaderData);
246
+ }
247
+ }
248
+ await preloadActiveClientRoute(reqPath);
249
+ // Now when Solid calls $$executeClientRpc, the store is 100% active and tracked!
250
+ return await renderToStringAsync(() => (<Router url={reqPath}>
251
+ <App />
252
+ </Router>));
253
+ });
254
+ }
255
+ catch (err) {
256
+ statusCode = 500;
257
+ console.error("[anaemia framework] runtime execution crash handled:", err);
258
+ const error500Pattern = manifest.errors?.["500"];
259
+ const error500Loader = error500Pattern ? serverLoaderRegistry.get(error500Pattern) : null;
260
+ if (error500Loader) {
261
+ const runtimeContextPayload = { message: err.message, stack: isDev ? err.stack : undefined };
262
+ store.set("__LOADER_DATA__", runtimeContextPayload);
263
+ try {
264
+ htmlPayload = await ssrStorage.run(store, async () => {
265
+ return await renderToStringAsync(() => (<Router url={error500Pattern}>
266
+ <App />
267
+ </Router>));
268
+ });
269
+ }
270
+ catch {
271
+ htmlPayload = `<h1>500 Internal Server Error</h1>`;
272
+ }
273
+ }
274
+ else {
275
+ htmlPayload = `<h1>500 Internal Server Error</h1><pre>${isDev ? err.stack : ""}</pre>`;
276
+ }
277
+ }
278
+ // ─── THE REMAINING INJECTIONS (Keep this exactly as you had it) ───
279
+ let assetScripts = "";
280
+ let assetStyles = "";
281
+ if (manifest.chunks) {
282
+ const processChunkAssets = (chunk) => {
283
+ if (!chunk)
284
+ return;
285
+ if (chunk.js) {
286
+ const jsSpecs = Array.isArray(chunk.js) ? chunk.js : [chunk.js];
287
+ jsSpecs.forEach((jsFile) => {
288
+ assetScripts += `<script type="module" src="${normalizeAssetUrl(jsFile)}"></script>\n`;
289
+ });
290
+ }
291
+ if (chunk.css) {
292
+ const cssSpecs = Array.isArray(chunk.css) ? chunk.css : [chunk.css];
293
+ cssSpecs.forEach((cssFile) => {
294
+ assetStyles += `<link rel="stylesheet" href="${normalizeAssetUrl(cssFile)}">\n`;
295
+ });
296
+ }
297
+ };
298
+ processChunkAssets(manifest.chunks["client"]);
299
+ if (manifest.chunks["commons"])
300
+ processChunkAssets(manifest.chunks["commons"]);
301
+ if (manifest.chunks["vendors"])
302
+ processChunkAssets(manifest.chunks["vendors"]);
303
+ if (activeChunk && activeChunk !== "client")
304
+ processChunkAssets(manifest.chunks[activeChunk]);
305
+ }
306
+ const hydrationScript = generateHydrationScript();
307
+ const rawStorePayload = Object.fromEntries(store);
308
+ const finalHydrationStatePayload = {
309
+ __LOADER_DATA__: rawStorePayload.__LOADER_DATA__ || {},
310
+ __SERVER_FUNCTION_DATA__: rawStorePayload.__SERVER_FUNCTION_DATA__ || {}
311
+ };
312
+ const serializedData = JSON.stringify(finalHydrationStatePayload)
313
+ .replace(/&/g, "\\u0026")
314
+ .replace(/</g, "\\u003c")
315
+ .replace(/>/g, "\\u003e")
316
+ .replace(/\//g, "\\u002f");
317
+ const dataScript = `<script id="__ANAEMIA_DATA__" type="application/json">${serializedData}</script>\n`;
318
+ const devNoCacheTag = isDev
319
+ ? `<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`
320
+ : "";
321
+ const combinedHeadInjections = `${devNoCacheTag}${assetStyles}${dataScript}${hydrationScript}`;
322
+ const sanitizedPayload = htmlPayload.trim();
323
+ let completeHtmlOutput = ENTRY_TAG_REGEX.test(template)
324
+ ? template.replace(ENTRY_TAG_REGEX, (_, open, _tag, _inner, close) => `${open}${sanitizedPayload}${close}`)
325
+ : template.replace("</body>", () => `<div anaemia-entry>${sanitizedPayload}</div></body>`);
326
+ completeHtmlOutput = completeHtmlOutput.replace("<head>", `<head>${combinedHeadInjections}`);
327
+ completeHtmlOutput = completeHtmlOutput.replace("</body>", `${assetScripts}</body>`);
328
+ if (isDev) {
329
+ c.header("Cache-Control", "no-cache, no-store, must-revalidate");
330
+ c.header("Pragma", "no-cache");
331
+ c.header("Expires", "0");
332
+ }
333
+ c.status(statusCode);
334
+ return c.html(completeHtmlOutput);
335
+ });
336
+ loadManifestAndTemplate()
337
+ .then(() => {
338
+ serve({ fetch: app.fetch, port }, (info) => {
339
+ console.log(`[anaemia framework] server live at http://localhost:${info.port}`);
340
+ });
341
+ })
342
+ .catch((err) => {
343
+ console.error("[anaemia] failed to initialize:", err);
344
+ process.exit(1);
345
+ });
346
+ export default app;
@@ -0,0 +1,6 @@
1
+ import { type ResourceOptions, type ResourceReturn } from "solid-js";
2
+ export declare function createServerResource<Source, Return>(source: () => Source, serverFn: ((sourceData: Source) => Promise<Return>) & {
3
+ readHydrationCache?: (s: any) => any;
4
+ id?: string;
5
+ }, options?: ResourceOptions<Return, Source>): ResourceReturn<Return, unknown>;
6
+ //# sourceMappingURL=resources.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resources.d.ts","sourceRoot":"","sources":["../../src/runtime/resources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,MAAM,UAAU,CAAC;AAGrF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EACjD,MAAM,EAAE,MAAM,MAAM,EACpB,QAAQ,EAAE,CAAC,CAAC,UAAU,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG;IACpD,kBAAkB,CAAC,EAAE,CAAC,CAAC,EAAE,GAAG,KAAK,GAAG,CAAC;IACrC,EAAE,CAAC,EAAE,MAAM,CAAC;CACb,EACD,OAAO,CAAC,EAAE,eAAe,CAAC,MAAM,EAAE,MAAM,CAAC,GACxC,cAAc,CAAC,MAAM,EAAE,OAAO,CAAC,CAsCjC"}
@@ -0,0 +1,36 @@
1
+ import { createResource } from "solid-js";
2
+ import { isServer } from "solid-js/web";
3
+ export function createServerResource(source, serverFn, options) {
4
+ if (isServer) {
5
+ let ssrInitialValue = undefined;
6
+ const store = globalThis.__ANAEMIA_SERVER_STORAGE__?.getStore?.();
7
+ if (store && serverFn.id) {
8
+ const fnCache = store.get("__SERVER_FUNCTION_DATA__")?.[serverFn.id];
9
+ if (fnCache) {
10
+ const key = JSON.stringify([source()]);
11
+ if (fnCache[key] !== undefined)
12
+ ssrInitialValue = fnCache[key];
13
+ }
14
+ }
15
+ return createResource(source, serverFn, {
16
+ ...options,
17
+ initialValue: ssrInitialValue !== undefined ? ssrInitialValue : options?.initialValue,
18
+ ssrLoadFrom: ssrInitialValue !== undefined ? "initial" : options?.ssrLoadFrom,
19
+ });
20
+ }
21
+ let hydrationChecked = false;
22
+ const wrappedFetcher = (s) => {
23
+ if (!hydrationChecked) {
24
+ hydrationChecked = true;
25
+ if (typeof serverFn.readHydrationCache === "function") {
26
+ const cached = serverFn.readHydrationCache(s);
27
+ if (cached !== undefined)
28
+ return Promise.resolve(cached);
29
+ }
30
+ }
31
+ return serverFn(s);
32
+ };
33
+ wrappedFetcher.id = serverFn.id;
34
+ wrappedFetcher.readHydrationCache = serverFn.readHydrationCache;
35
+ return createResource(source, wrappedFetcher, options);
36
+ }
@@ -0,0 +1,15 @@
1
+ import { type JSX } from "solid-js";
2
+ import { type Params } from "@solidjs/router";
3
+ import type { Location } from "@solidjs/router";
4
+ type RouteDataControllerProps<TParams extends Params = Params> = {
5
+ loader: (args: {
6
+ params: TParams;
7
+ location: Location;
8
+ request: Request;
9
+ }) => any | Promise<any>;
10
+ children: JSX.Element;
11
+ };
12
+ export declare function RouteDataController<TParams extends Params = Params>(props: RouteDataControllerProps<TParams>): JSX.Element;
13
+ export declare function useRouteData<T = any>(): () => T;
14
+ export {};
15
+ //# sourceMappingURL=route-data.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-data.d.ts","sourceRoot":"","sources":["../../src/runtime/route-data.ts"],"names":[],"mappings":"AAAA,OAAO,EAKL,KAAK,GAAG,EACT,MAAM,UAAU,CAAC;AAElB,OAAO,EAA0B,KAAK,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACtE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAIhD,KAAK,wBAAwB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,IAAI;IAC/D,MAAM,EAAE,CAAC,IAAI,EAAE;QACb,MAAM,EAAE,OAAO,CAAC;QAChB,QAAQ,EAAE,QAAQ,CAAC;QACnB,OAAO,EAAE,OAAO,CAAC;KAClB,KAAK,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC;IACzB,QAAQ,EAAE,GAAG,CAAC,OAAO,CAAC;CACvB,CAAC;AA2BF,wBAAgB,mBAAmB,CAAC,OAAO,SAAS,MAAM,GAAG,MAAM,EACjE,KAAK,EAAE,wBAAwB,CAAC,OAAO,CAAC,eAkCzC;AAED,wBAAgB,YAAY,CAAC,CAAC,GAAG,GAAG,KAAK,MAAM,CAAC,CAQ/C"}
@@ -0,0 +1,57 @@
1
+ import { createContext, useContext, createResource, createComponent } from "solid-js";
2
+ import { isServer } from "solid-js/web";
3
+ import { useParams, useLocation } from "@solidjs/router";
4
+ import { ssrStorage } from "./context.js";
5
+ import { createRouteRequest } from "./route-request.js";
6
+ const RouteDataContext = createContext();
7
+ let hasReadClientHydrationData = false;
8
+ function readSSRData() {
9
+ if (isServer) {
10
+ return ssrStorage.getStore()?.get("__LOADER_DATA__");
11
+ }
12
+ if (hasReadClientHydrationData)
13
+ return undefined;
14
+ hasReadClientHydrationData = true;
15
+ const el = document.getElementById("__ANAEMIA_DATA__");
16
+ if (!el?.textContent)
17
+ return undefined;
18
+ try {
19
+ return JSON.parse(el.textContent).__LOADER_DATA__;
20
+ }
21
+ catch {
22
+ return undefined;
23
+ }
24
+ }
25
+ export function RouteDataController(props) {
26
+ const params = useParams();
27
+ const location = useLocation();
28
+ const ssrData = readSSRData();
29
+ const [resource] = createResource(() => location.pathname, () => {
30
+ if (isServer && ssrData !== undefined) {
31
+ return ssrData;
32
+ }
33
+ return props.loader({
34
+ params,
35
+ location,
36
+ request: createRouteRequest(location.pathname)
37
+ });
38
+ }, {
39
+ initialValue: ssrData,
40
+ ssrLoadFrom: "initial"
41
+ });
42
+ return createComponent(RouteDataContext.Provider, {
43
+ value: {
44
+ data: resource
45
+ },
46
+ get children() {
47
+ return props.children;
48
+ }
49
+ });
50
+ }
51
+ export function useRouteData() {
52
+ const ctx = useContext(RouteDataContext);
53
+ if (!ctx) {
54
+ throw new Error("useRouteData must be used inside RouteDataController");
55
+ }
56
+ return ctx.data;
57
+ }
@@ -0,0 +1,2 @@
1
+ export declare function createRouteRequest(pathname: string): any;
2
+ //# sourceMappingURL=route-request.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"route-request.d.ts","sourceRoot":"","sources":["../../src/runtime/route-request.ts"],"names":[],"mappings":"AAGA,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,OAQlD"}
@@ -0,0 +1,11 @@
1
+ import { isServer } from "solid-js/web";
2
+ import { ssrStorage } from "./context.js";
3
+ export function createRouteRequest(pathname) {
4
+ if (isServer) {
5
+ const request = ssrStorage.getStore()?.get("honoContext")?.req?.raw;
6
+ if (request)
7
+ return request;
8
+ return new Request(new URL(pathname, "http://localhost").toString());
9
+ }
10
+ return new Request(new URL(pathname, window.location.origin).toString());
11
+ }
@@ -0,0 +1,6 @@
1
+ export declare function $$executeClientRpc(hashId: string): {
2
+ (...args: unknown[]): Promise<any>;
3
+ id: string;
4
+ readHydrationCache(...args: unknown[]): any;
5
+ };
6
+ //# sourceMappingURL=rpc-client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/runtime/rpc-client.ts"],"names":[],"mappings":"AA4BA,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM;cACD,OAAO,EAAE;;gCAoCF,OAAO,EAAE;EAqB/D"}
@@ -0,0 +1,78 @@
1
+ import { isServer } from "solid-js/web";
2
+ let _clientCache = null;
3
+ function ensureCacheInitialized() {
4
+ if (isServer || _clientCache)
5
+ return;
6
+ const script = document.getElementById("__ANAEMIA_DATA__");
7
+ try {
8
+ _clientCache = JSON.parse(script?.textContent || "{}");
9
+ }
10
+ catch {
11
+ _clientCache = {};
12
+ }
13
+ }
14
+ function findLooseCacheMatch(serverFunctionData, targetArg) {
15
+ if (!serverFunctionData)
16
+ return undefined;
17
+ const strictKey = JSON.stringify([targetArg]);
18
+ if (strictKey in serverFunctionData) {
19
+ return { matchingKey: strictKey, data: serverFunctionData[strictKey] };
20
+ }
21
+ const lookUpString = `["${targetArg}"`;
22
+ const matchedKey = Object.keys(serverFunctionData).find(key => key.startsWith(lookUpString));
23
+ return matchedKey ? { matchingKey: matchedKey, data: serverFunctionData[matchedKey] } : undefined;
24
+ }
25
+ export function $$executeClientRpc(hashId) {
26
+ const asyncRpcCall = async function (...args) {
27
+ if (isServer) {
28
+ const globalStorage = globalThis.__ANAEMIA_SERVER_STORAGE__;
29
+ const store = globalStorage?.getStore();
30
+ if (store) {
31
+ const functionCache = store.get("__SERVER_FUNCTION_DATA__");
32
+ if (functionCache && functionCache[hashId]) {
33
+ const match = findLooseCacheMatch(functionCache[hashId], args[0]);
34
+ if (match)
35
+ return match.data;
36
+ }
37
+ }
38
+ return undefined;
39
+ }
40
+ ensureCacheInitialized();
41
+ const serverFunctionData = _clientCache.__SERVER_FUNCTION_DATA__?.[hashId];
42
+ const match = findLooseCacheMatch(serverFunctionData, args[0]);
43
+ if (match) {
44
+ const { matchingKey, data } = match;
45
+ delete serverFunctionData[matchingKey];
46
+ return data;
47
+ }
48
+ const response = await fetch(`/_rpc?id=${hashId}`, {
49
+ method: "POST",
50
+ headers: { "Content-Type": "application/json" },
51
+ body: JSON.stringify(args),
52
+ });
53
+ if (!response.ok)
54
+ throw new Error(`[anaemia] RPC execution failed: ${response.status}`);
55
+ return await response.json();
56
+ };
57
+ asyncRpcCall.id = hashId;
58
+ asyncRpcCall.readHydrationCache = function (...args) {
59
+ if (isServer) {
60
+ const globalStorage = globalThis.__ANAEMIA_SERVER_STORAGE__;
61
+ const store = globalStorage?.getStore();
62
+ if (store) {
63
+ const functionCache = store.get("__SERVER_FUNCTION_DATA__");
64
+ if (functionCache && functionCache[hashId]) {
65
+ const match = findLooseCacheMatch(functionCache[hashId], args[0]);
66
+ if (match)
67
+ return match.data;
68
+ }
69
+ }
70
+ return undefined;
71
+ }
72
+ ensureCacheInitialized();
73
+ const serverFunctionData = _clientCache.__SERVER_FUNCTION_DATA__?.[hashId];
74
+ const match = findLooseCacheMatch(serverFunctionData, args[0]);
75
+ return match ? match.data : undefined;
76
+ };
77
+ return asyncRpcCall;
78
+ }
@@ -0,0 +1,49 @@
1
+ import type { RedirectStatusCode } from "hono/utils/http-status";
2
+ /**
3
+ * the standard argument object injected into every server-side page loader.
4
+ */
5
+ export interface LoaderArgs<Params extends Record<string, string> = Record<string, string>> {
6
+ /** parsed URL dynamic variables (e.g., { id: "123" } from /users/[id]) */
7
+ params: Params;
8
+ /** the native underlying Web API Request object from the Hono server instance */
9
+ request: Request;
10
+ }
11
+ /**
12
+ * represents an application page loader function.
13
+ */
14
+ export type LoaderFunction<ResponseData = any, Params extends Record<string, string> = Record<string, string>> = (args: LoaderArgs<Params>) => Promise<ResponseData> | ResponseData;
15
+ /**
16
+ * extracts and unwraps the true data structure returned by a server function or loader.
17
+ * essential for typing useRouteData() effortlessly in user-space.
18
+ */
19
+ export type InferServerData<T extends (...args: any[]) => any> = Awaited<ReturnType<T>>;
20
+ export type GuardContext = {
21
+ params: Record<string, string>;
22
+ request: Request;
23
+ url: string;
24
+ };
25
+ export type GuardResult = void | undefined | {
26
+ redirect: string;
27
+ status?: 301 | 302 | 307 | 308;
28
+ } | {
29
+ status: 401 | 403 | 404 | 500;
30
+ body?: string;
31
+ };
32
+ export type GuardFn = (ctx: {
33
+ params: Record<string, string>;
34
+ request: Request;
35
+ url: string;
36
+ }) => void | undefined | {
37
+ redirect: string;
38
+ status?: RedirectStatusCode;
39
+ } | {
40
+ status: number;
41
+ body?: string;
42
+ } | Promise<void | undefined | {
43
+ redirect: string;
44
+ status?: RedirectStatusCode;
45
+ } | {
46
+ status: number;
47
+ body?: string;
48
+ }>;
49
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAEjE;;GAEG;AACH,MAAM,WAAW,UAAU,CAAC,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC;IACxF,0EAA0E;IAC1E,MAAM,EAAE,MAAM,CAAC;IACf,iFAAiF;IACjF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,cAAc,CAAC,YAAY,GAAG,GAAG,EAAE,MAAM,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,UAAU,CAAC,MAAM,CAAC,KAAK,OAAO,CAAC,YAAY,CAAC,GAAG,YAAY,CAAC;AAEpL;;;GAGG;AACH,MAAM,MAAM,eAAe,CAAC,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,IAAI,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC;AAExF,MAAM,MAAM,YAAY,GAAG;IACzB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,EAAE,MAAM,CAAC;CACb,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC;AAErJ,MAAM,MAAM,OAAO,GAAG,CAAC,GAAG,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,KAAK,IAAI,GAAG,SAAS,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,GAAG,SAAS,GAAG;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};