@nifrajs/web-solid 0.1.0-alpha.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 (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/dist/await.d.ts +29 -0
  4. package/dist/await.d.ts.map +1 -0
  5. package/dist/await.js +46 -0
  6. package/dist/await.js.map +1 -0
  7. package/dist/client.d.ts +18 -0
  8. package/dist/client.d.ts.map +1 -0
  9. package/dist/client.js +85 -0
  10. package/dist/client.js.map +1 -0
  11. package/dist/compose.d.ts +9 -0
  12. package/dist/compose.d.ts.map +1 -0
  13. package/dist/compose.js +17 -0
  14. package/dist/compose.js.map +1 -0
  15. package/dist/content.d.ts +21 -0
  16. package/dist/content.d.ts.map +1 -0
  17. package/dist/content.js +18 -0
  18. package/dist/content.js.map +1 -0
  19. package/dist/error.d.ts +8 -0
  20. package/dist/error.d.ts.map +1 -0
  21. package/dist/error.js +25 -0
  22. package/dist/error.js.map +1 -0
  23. package/dist/fetcher.d.ts +37 -0
  24. package/dist/fetcher.d.ts.map +1 -0
  25. package/dist/fetcher.js +48 -0
  26. package/dist/fetcher.js.map +1 -0
  27. package/dist/i18n.d.ts +21 -0
  28. package/dist/i18n.d.ts.map +1 -0
  29. package/dist/i18n.js +31 -0
  30. package/dist/i18n.js.map +1 -0
  31. package/dist/image.d.ts +23 -0
  32. package/dist/image.d.ts.map +1 -0
  33. package/dist/image.js +49 -0
  34. package/dist/image.js.map +1 -0
  35. package/dist/index.d.ts +12 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +67 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/mdx-runtime.d.ts +4 -0
  40. package/dist/mdx-runtime.d.ts.map +1 -0
  41. package/dist/mdx-runtime.js +52 -0
  42. package/dist/mdx-runtime.js.map +1 -0
  43. package/dist/mdx.d.ts +5 -0
  44. package/dist/mdx.d.ts.map +1 -0
  45. package/dist/mdx.js +59 -0
  46. package/dist/mdx.js.map +1 -0
  47. package/dist/query.d.ts +28 -0
  48. package/dist/query.d.ts.map +1 -0
  49. package/dist/query.js +54 -0
  50. package/dist/query.js.map +1 -0
  51. package/package.json +112 -0
  52. package/src/await.ts +58 -0
  53. package/src/client.ts +89 -0
  54. package/src/compose.ts +18 -0
  55. package/src/content.ts +30 -0
  56. package/src/error.ts +26 -0
  57. package/src/fetcher.ts +63 -0
  58. package/src/i18n.ts +46 -0
  59. package/src/image.ts +68 -0
  60. package/src/index.ts +75 -0
  61. package/src/mdx-runtime.ts +54 -0
  62. package/src/mdx.ts +60 -0
  63. package/src/query.ts +63 -0
@@ -0,0 +1,12 @@
1
+ import type { RenderAdapter } from "@nifrajs/web";
2
+ import type { BunPlugin } from "bun";
3
+ /** The Solid server render adapter — pass to @nifrajs/web's `renderPage`. */
4
+ export declare const solidAdapter: RenderAdapter;
5
+ /**
6
+ * Bun build/runtime plugin that compiles Solid components with Babel — `generate: "ssr"`
7
+ * for the server, `"dom"` for the client, `hydratable` so SSR and hydrate align. Solid's
8
+ * reactive-JSX compiler ships only as a Babel plugin (no swc/native port); this runs at
9
+ * build time, on `.tsx` files only.
10
+ */
11
+ export declare function solidBunPlugin(generate: "dom" | "ssr"): BunPlugin;
12
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAA;AAGjD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAUpC,6EAA6E;AAC7E,eAAO,MAAM,YAAY,EAAE,aAsB1B,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAqBjE"}
package/dist/index.js ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * @nifrajs/web-solid — the Solid render adapter for @nifrajs/web (server side) + the Solid Babel
3
+ * Bun-plugin. Streaming SSR via `renderToStream` + `generateHydrationScript`; the layout-chain
4
+ * fold is in `./compose`. Client hydration lives in `@nifrajs/web-solid/client` (Solid's browser
5
+ * build).
6
+ */
7
+ import { transformAsync } from "@babel/core";
8
+ // @ts-expect-error — no type declarations published
9
+ import presetTypeScript from "@babel/preset-typescript";
10
+ // @ts-expect-error — no type declarations published
11
+ import presetSolid from "babel-preset-solid";
12
+ import { generateHydrationScript, renderToStream as solidRenderToStream, renderToString as solidRenderToString, } from "solid-js/web";
13
+ import { compose } from "./compose.js";
14
+ const HYDRATION_HEAD = generateHydrationScript();
15
+ /** The Solid server render adapter — pass to @nifrajs/web's `renderPage`. */
16
+ export const solidAdapter = {
17
+ // Synchronous one-pass render for non-deferred pages (renderPage's buffered fast path). Solid's
18
+ // `renderToString` emits the same hydratable markup (same `data-hk` keys seeded by
19
+ // `generateHydrationScript()`) as `renderToStream`, but skips the TransformStream + Solid's
20
+ // streaming machinery — the heaviest of the five renderers on Bun. A page that defer()s/Suspends
21
+ // takes `renderToStream` below (progressive resolution needs it).
22
+ renderToString(chain, props) {
23
+ return solidRenderToString(compose(chain, props));
24
+ },
25
+ renderToStream(chain, props) {
26
+ // Solid's `renderToStream` streams `Uint8Array` chunks into a Web `WritableStream` via
27
+ // `pipeTo` (fire-and-forget — returns void); pipe into a TransformStream and hand back the
28
+ // readable side. Suspense boundaries stream as they resolve; `generateHydrationScript()` (in
29
+ // <head>) seeds client hydration. A render failure errors `ts.readable`, which `renderPage`
30
+ // surfaces on the response body.
31
+ const ts = new TransformStream();
32
+ solidRenderToStream(compose(chain, props)).pipeTo(ts.writable);
33
+ return ts.readable;
34
+ },
35
+ hydrationHead() {
36
+ return HYDRATION_HEAD;
37
+ },
38
+ };
39
+ /**
40
+ * Bun build/runtime plugin that compiles Solid components with Babel — `generate: "ssr"`
41
+ * for the server, `"dom"` for the client, `hydratable` so SSR and hydrate align. Solid's
42
+ * reactive-JSX compiler ships only as a Babel plugin (no swc/native port); this runs at
43
+ * build time, on `.tsx` files only.
44
+ */
45
+ export function solidBunPlugin(generate) {
46
+ return {
47
+ name: `nifra-solid-${generate}`,
48
+ setup(build) {
49
+ // Match `.tsx`, tolerating a `?query` suffix (dev servers append one to bust Bun's import
50
+ // cache); strip it before reading the file off disk.
51
+ build.onLoad({ filter: /\.tsx(\?|$)/ }, async (args) => {
52
+ const path = args.path.split("?")[0] ?? args.path;
53
+ const source = await Bun.file(path).text();
54
+ const result = await transformAsync(source, {
55
+ filename: path,
56
+ // babel applies presets last→first: strip TS first, then Solid transforms JSX.
57
+ presets: [
58
+ [presetSolid, { generate, hydratable: true }],
59
+ [presetTypeScript, { onlyRemoveTypeImports: true }],
60
+ ],
61
+ });
62
+ return { contents: result?.code ?? "", loader: "js" };
63
+ });
64
+ },
65
+ };
66
+ }
67
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,oDAAoD;AACpD,OAAO,gBAAgB,MAAM,0BAA0B,CAAA;AAEvD,oDAAoD;AACpD,OAAO,WAAW,MAAM,oBAAoB,CAAA;AAE5C,OAAO,EACL,uBAAuB,EACvB,cAAc,IAAI,mBAAmB,EACrC,cAAc,IAAI,mBAAmB,GACtC,MAAM,cAAc,CAAA;AACrB,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,MAAM,cAAc,GAAG,uBAAuB,EAAE,CAAA;AAEhD,6EAA6E;AAC7E,MAAM,CAAC,MAAM,YAAY,GAAkB;IACzC,gGAAgG;IAChG,mFAAmF;IACnF,4FAA4F;IAC5F,iGAAiG;IACjG,kEAAkE;IAClE,cAAc,CAAC,KAAK,EAAE,KAAK;QACzB,OAAO,mBAAmB,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAA;IACnD,CAAC;IACD,cAAc,CAAC,KAAK,EAAE,KAAK;QACzB,uFAAuF;QACvF,2FAA2F;QAC3F,6FAA6F;QAC7F,4FAA4F;QAC5F,iCAAiC;QACjC,MAAM,EAAE,GAAG,IAAI,eAAe,EAA0B,CAAA;QACxD,mBAAmB,CAAC,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,QAAQ,CAAC,CAAA;QAC9D,OAAO,EAAE,CAAC,QAAQ,CAAA;IACpB,CAAC;IACD,aAAa;QACX,OAAO,cAAc,CAAA;IACvB,CAAC;CACF,CAAA;AAED;;;;;GAKG;AACH,MAAM,UAAU,cAAc,CAAC,QAAuB;IACpD,OAAO;QACL,IAAI,EAAE,eAAe,QAAQ,EAAE;QAC/B,KAAK,CAAC,KAAK;YACT,0FAA0F;YAC1F,qDAAqD;YACrD,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBACrD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAA;gBACjD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,CAAA;gBAC1C,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,MAAM,EAAE;oBAC1C,QAAQ,EAAE,IAAI;oBACd,+EAA+E;oBAC/E,OAAO,EAAE;wBACP,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;wBAC7C,CAAC,gBAAgB,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC;qBACpD;iBACF,CAAC,CAAA;gBACF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;YACvD,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,4 @@
1
+ /** Returns the intrinsic-element → Solid-component map MDX content uses. Merge in your own overrides by
2
+ * passing `components` to the MDX content component (they take precedence). */
3
+ export declare function useMDXComponents(): Record<string, (props: Record<string, unknown>) => unknown>;
4
+ //# sourceMappingURL=mdx-runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdx-runtime.d.ts","sourceRoot":"","sources":["../src/mdx-runtime.ts"],"names":[],"mappings":"AAiDA;+EAC+E;AAC/E,wBAAgB,gBAAgB,IAAI,MAAM,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,CAE9F"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Solid MDX runtime — the `useMDXComponents` provider that `@nifrajs/web-solid/mdx`'s compiled MDX imports
3
+ * (via `@mdx-js`'s `providerImportSource`). MDX emits intrinsic elements (`h1`, `p`, …) as *string*
4
+ * component references, but Solid can't `createComponent("h1")` — its JSX is compile-time. So we map
5
+ * each Markdown-output tag to a Solid component that renders it via `<Dynamic component={tag}>`.
6
+ */
7
+ import { createComponent } from "solid-js";
8
+ import { Dynamic } from "solid-js/web";
9
+ // The HTML element set CommonMark + GFM produce. An explicit map (not a Proxy): `@mdx-js` *spreads* the
10
+ // provider's result into a plain object, so only own-enumerable keys survive.
11
+ const MARKDOWN_TAGS = [
12
+ "a",
13
+ "blockquote",
14
+ "br",
15
+ "code",
16
+ "del",
17
+ "em",
18
+ "h1",
19
+ "h2",
20
+ "h3",
21
+ "h4",
22
+ "h5",
23
+ "h6",
24
+ "hr",
25
+ "img",
26
+ "input",
27
+ "li",
28
+ "ol",
29
+ "p",
30
+ "pre",
31
+ "section",
32
+ "span",
33
+ "strong",
34
+ "sup",
35
+ "table",
36
+ "tbody",
37
+ "td",
38
+ "th",
39
+ "thead",
40
+ "tr",
41
+ "ul",
42
+ ];
43
+ const components = {};
44
+ for (const tag of MARKDOWN_TAGS) {
45
+ components[tag] = (props) => createComponent(Dynamic, Object.assign({ component: tag }, props));
46
+ }
47
+ /** Returns the intrinsic-element → Solid-component map MDX content uses. Merge in your own overrides by
48
+ * passing `components` to the MDX content component (they take precedence). */
49
+ export function useMDXComponents() {
50
+ return components;
51
+ }
52
+ //# sourceMappingURL=mdx-runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdx-runtime.js","sourceRoot":"","sources":["../src/mdx-runtime.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAA;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAA;AAEtC,wGAAwG;AACxG,8EAA8E;AAC9E,MAAM,aAAa,GAAG;IACpB,GAAG;IACH,YAAY;IACZ,IAAI;IACJ,MAAM;IACN,KAAK;IACL,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,IAAI;IACJ,KAAK;IACL,OAAO;IACP,IAAI;IACJ,IAAI;IACJ,GAAG;IACH,KAAK;IACL,SAAS;IACT,MAAM;IACN,QAAQ;IACR,KAAK;IACL,OAAO;IACP,OAAO;IACP,IAAI;IACJ,IAAI;IACJ,OAAO;IACP,IAAI;IACJ,IAAI;CACI,CAAA;AAEV,MAAM,UAAU,GAAgE,EAAE,CAAA;AAClF,KAAK,MAAM,GAAG,IAAI,aAAa,EAAE,CAAC;IAChC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,eAAe,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,KAAK,CAAC,CAAC,CAAA;AACjG,CAAC;AAED;+EAC+E;AAC/E,MAAM,UAAU,gBAAgB;IAC9B,OAAO,UAAU,CAAA;AACnB,CAAC"}
package/dist/mdx.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ import type { BunPlugin } from "bun";
2
+ /** Build a `Bun.build` plugin that loads `.mdx` files as Solid components. `generate`: `"ssr"` for the
3
+ * server build, `"dom"` for the client (matches `solidBunPlugin`). */
4
+ export declare function solidMdxBunPlugin(generate: "dom" | "ssr"): BunPlugin;
5
+ //# sourceMappingURL=mdx.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdx.d.ts","sourceRoot":"","sources":["../src/mdx.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AASpC;sEACsE;AACtE,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAmCpE"}
package/dist/mdx.js ADDED
@@ -0,0 +1,59 @@
1
+ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
2
+ if (typeof path === "string" && /^\.\.?\//.test(path)) {
3
+ return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
4
+ return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
5
+ });
6
+ }
7
+ return path;
8
+ };
9
+ /**
10
+ * `solidMdxBunPlugin` — compile `.mdx` routes/files to **Solid** components. MDX is JSX-oriented but
11
+ * Solid's JSX is compile-time, so this: (1) compiles MDX → JSX with `@mdx-js` (keeping JSX), pointing
12
+ * intrinsic elements at `@nifrajs/web-solid/mdx-runtime` (which renders them via Solid's `<Dynamic>`), then
13
+ * (2) runs `babel-preset-solid` — the same transform `solidBunPlugin` applies to `.tsx`. Pass it to
14
+ * `buildClient`/`buildServer`'s `plugins` (`"dom"` for the client, `"ssr"` for the server), like
15
+ * `solidBunPlugin`. `@mdx-js/mdx` is an optional peer (lazy-loaded at build time).
16
+ */
17
+ import { transformAsync } from "@babel/core";
18
+ // @ts-expect-error — no type declarations published
19
+ import presetTypeScript from "@babel/preset-typescript";
20
+ // @ts-expect-error — no type declarations published
21
+ import presetSolid from "babel-preset-solid";
22
+ // Non-literal so tsc/consumers don't need `@mdx-js/mdx`'s types to typecheck this file (optional peer).
23
+ const MDX_MODULE = "@mdx-js/mdx";
24
+ /** Build a `Bun.build` plugin that loads `.mdx` files as Solid components. `generate`: `"ssr"` for the
25
+ * server build, `"dom"` for the client (matches `solidBunPlugin`). */
26
+ export function solidMdxBunPlugin(generate) {
27
+ return {
28
+ name: `nifra-solid-mdx-${generate}`,
29
+ async setup(build) {
30
+ let compile;
31
+ try {
32
+ compile = (await import(__rewriteRelativeImportExtension(MDX_MODULE))).compile;
33
+ }
34
+ catch {
35
+ throw new Error("@nifrajs/web-solid/mdx: compiling `.mdx` needs the `@mdx-js/mdx` package — install it (`bun add @mdx-js/mdx`).");
36
+ }
37
+ build.onLoad({ filter: /\.mdx(\?|$)/ }, async (args) => {
38
+ const path = args.path.split("?")[0] ?? args.path;
39
+ // 1. MDX → JSX, with intrinsics provided by the Solid runtime (rendered via `<Dynamic>`).
40
+ const jsx = String(await compile(await Bun.file(path).text(), {
41
+ jsx: true,
42
+ providerImportSource: "@nifrajs/web-solid/mdx-runtime",
43
+ }));
44
+ // 2. babel-preset-solid (TS stripped first), exactly like `solidBunPlugin`.
45
+ const result = await transformAsync(jsx, {
46
+ filename: `${path}.tsx`,
47
+ presets: [
48
+ [presetSolid, { generate, hydratable: true }],
49
+ [presetTypeScript, { onlyRemoveTypeImports: true }],
50
+ ],
51
+ babelrc: false,
52
+ configFile: false,
53
+ });
54
+ return { contents: result?.code ?? "", loader: "js" };
55
+ });
56
+ },
57
+ };
58
+ }
59
+ //# sourceMappingURL=mdx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdx.js","sourceRoot":"","sources":["../src/mdx.ts"],"names":[],"mappings":";;;;;;;;AAAA;;;;;;;GAOG;AACH,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,oDAAoD;AACpD,OAAO,gBAAgB,MAAM,0BAA0B,CAAA;AACvD,oDAAoD;AACpD,OAAO,WAAW,MAAM,oBAAoB,CAAA;AAG5C,wGAAwG;AACxG,MAAM,UAAU,GAAG,aAAa,CAAA;AAMhC;sEACsE;AACtE,MAAM,UAAU,iBAAiB,CAAC,QAAuB;IACvD,OAAO;QACL,IAAI,EAAE,mBAAmB,QAAQ,EAAE;QACnC,KAAK,CAAC,KAAK,CAAC,KAAK;YACf,IAAI,OAA+B,CAAA;YACnC,IAAI,CAAC;gBACH,OAAO,GAAI,CAAC,MAAM,MAAM,kCAAC,UAAU,EAAC,CAAiB,CAAC,OAAO,CAAA;YAC/D,CAAC;YAAC,MAAM,CAAC;gBACP,MAAM,IAAI,KAAK,CACb,gHAAgH,CACjH,CAAA;YACH,CAAC;YACD,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,aAAa,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBACrD,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,IAAI,CAAA;gBACjD,0FAA0F;gBAC1F,MAAM,GAAG,GAAG,MAAM,CAChB,MAAM,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE;oBACzC,GAAG,EAAE,IAAI;oBACT,oBAAoB,EAAE,gCAAgC;iBACvD,CAAC,CACH,CAAA;gBACD,4EAA4E;gBAC5E,MAAM,MAAM,GAAG,MAAM,cAAc,CAAC,GAAG,EAAE;oBACvC,QAAQ,EAAE,GAAG,IAAI,MAAM;oBACvB,OAAO,EAAE;wBACP,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;wBAC7C,CAAC,gBAAgB,EAAE,EAAE,qBAAqB,EAAE,IAAI,EAAE,CAAC;qBACpD;oBACD,OAAO,EAAE,KAAK;oBACd,UAAU,EAAE,KAAK;iBAClB,CAAC,CAAA;gBACF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,IAAI,EAAE,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;YACvD,CAAC,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,28 @@
1
+ import { type QueryClient, type QueryState } from "@nifrajs/web";
2
+ /**
3
+ * `@nifrajs/web-solid/query` — Solid bindings for the keyed query-cache. `createQuery(key, fn)` bridges a
4
+ * query's `subscribe`/`snapshot` store into a Solid signal and fetches on mount; `useQueryClient`
5
+ * exposes `invalidateQueries` for after a mutation. Imports only `solid-js` + `@nifrajs/web` (never
6
+ * `solid-js/web`), so route components use it on the server *and* client. No JSX.
7
+ *
8
+ * The query client is created lazily **client-side only** (`typeof window`) — on the server the
9
+ * accessor returns the idle/pending state (queries are client-first; loaders are the SSR data source).
10
+ * The `key` is captured once (Solid setup runs once); a reactive key is a documented future enhancement.
11
+ */
12
+ import { type Accessor } from "solid-js";
13
+ /** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
14
+ export declare function useQueryClient(): Pick<QueryClient, "invalidateQueries">;
15
+ /** A query's reactive state accessor plus `refetch`. */
16
+ export interface CreateQueryResult<T> {
17
+ /** Reactive accessor for the {@link QueryState} (`status`/`data`/`error`/`isFetching`/`updatedAt`). */
18
+ readonly state: Accessor<QueryState<T>>;
19
+ /** Force a refetch (ignores `staleTime`). */
20
+ readonly refetch: () => Promise<T>;
21
+ }
22
+ /**
23
+ * Bind the keyed query for `key`, fetched via `fn`. Returns a reactive `state()` accessor + `refetch`.
24
+ * Concurrent `createQuery`s with the same key share one cache entry + one in-flight fetch (dedup).
25
+ * Fetches on mount; SSR-idle. Call inside a component (owns the subscription).
26
+ */
27
+ export declare function createQuery<T>(key: unknown, fn: () => Promise<T>): CreateQueryResult<T>;
28
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAqB,KAAK,WAAW,EAAE,KAAK,UAAU,EAAE,MAAM,cAAc,CAAA;AACnF;;;;;;;;;GASG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAoC,MAAM,UAAU,CAAA;AAS1E,wGAAwG;AACxG,wBAAgB,cAAc,IAAI,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAEvE;AAUD,wDAAwD;AACxD,MAAM,WAAW,iBAAiB,CAAC,CAAC;IAClC,uGAAuG;IACvG,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,CAAA;IACvC,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAA;CACnC;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,iBAAiB,CAAC,CAAC,CAAC,CAgBvF"}
package/dist/query.js ADDED
@@ -0,0 +1,54 @@
1
+ import { createQueryClient } from "@nifrajs/web";
2
+ /**
3
+ * `@nifrajs/web-solid/query` — Solid bindings for the keyed query-cache. `createQuery(key, fn)` bridges a
4
+ * query's `subscribe`/`snapshot` store into a Solid signal and fetches on mount; `useQueryClient`
5
+ * exposes `invalidateQueries` for after a mutation. Imports only `solid-js` + `@nifrajs/web` (never
6
+ * `solid-js/web`), so route components use it on the server *and* client. No JSX.
7
+ *
8
+ * The query client is created lazily **client-side only** (`typeof window`) — on the server the
9
+ * accessor returns the idle/pending state (queries are client-first; loaders are the SSR data source).
10
+ * The `key` is captured once (Solid setup runs once); a reactive key is a documented future enhancement.
11
+ */
12
+ import { createSignal, onCleanup, onMount } from "solid-js";
13
+ let client;
14
+ function getClient() {
15
+ if (typeof window === "undefined")
16
+ return undefined; // SSR → no client (queries render idle)
17
+ if (client === undefined)
18
+ client = createQueryClient({ now: () => Date.now() });
19
+ return client;
20
+ }
21
+ /** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
22
+ export function useQueryClient() {
23
+ return getClient() ?? { invalidateQueries: () => { } };
24
+ }
25
+ const IDLE = Object.freeze({
26
+ status: "pending",
27
+ data: undefined,
28
+ error: undefined,
29
+ isFetching: false,
30
+ updatedAt: Number.NEGATIVE_INFINITY,
31
+ });
32
+ /**
33
+ * Bind the keyed query for `key`, fetched via `fn`. Returns a reactive `state()` accessor + `refetch`.
34
+ * Concurrent `createQuery`s with the same key share one cache entry + one in-flight fetch (dedup).
35
+ * Fetches on mount; SSR-idle. Call inside a component (owns the subscription).
36
+ */
37
+ export function createQuery(key, fn) {
38
+ const handle = getClient()?.query(key, fn);
39
+ const [state, setState] = createSignal(handle ? handle.snapshot() : IDLE);
40
+ if (handle !== undefined) {
41
+ onCleanup(handle.subscribe(() => setState(() => handle.snapshot())));
42
+ onMount(() => {
43
+ handle.fetch().catch(() => { });
44
+ });
45
+ return { state, refetch: handle.refetch };
46
+ }
47
+ return {
48
+ state,
49
+ refetch: async () => {
50
+ throw new Error("[nifra/web-solid] createQuery.refetch called with no query client (server?)");
51
+ },
52
+ };
53
+ }
54
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAqC,MAAM,cAAc,CAAA;AACnF;;;;;;;;;GASG;AACH,OAAO,EAAiB,YAAY,EAAE,SAAS,EAAE,OAAO,EAAE,MAAM,UAAU,CAAA;AAE1E,IAAI,MAA+B,CAAA;AACnC,SAAS,SAAS;IAChB,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,SAAS,CAAA,CAAC,wCAAwC;IAC5F,IAAI,MAAM,KAAK,SAAS;QAAE,MAAM,GAAG,iBAAiB,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAA;IAC/E,OAAO,MAAM,CAAA;AACf,CAAC;AAED,wGAAwG;AACxG,MAAM,UAAU,cAAc;IAC5B,OAAO,SAAS,EAAE,IAAI,EAAE,iBAAiB,EAAE,GAAG,EAAE,GAAE,CAAC,EAAE,CAAA;AACvD,CAAC;AAED,MAAM,IAAI,GAAsB,MAAM,CAAC,MAAM,CAAC;IAC5C,MAAM,EAAE,SAAS;IACjB,IAAI,EAAE,SAAS;IACf,KAAK,EAAE,SAAS;IAChB,UAAU,EAAE,KAAK;IACjB,SAAS,EAAE,MAAM,CAAC,iBAAiB;CACpC,CAAC,CAAA;AAUF;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAI,GAAY,EAAE,EAAoB;IAC/D,MAAM,MAAM,GAAG,SAAS,EAAE,EAAE,KAAK,CAAI,GAAG,EAAE,EAAE,CAAC,CAAA;IAC7C,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,YAAY,CAAgB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;IACxF,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACzB,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAA;QACpE,OAAO,CAAC,GAAG,EAAE;YACX,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA;QAChC,CAAC,CAAC,CAAA;QACF,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAA;IAC3C,CAAC;IACD,OAAO;QACL,KAAK;QACL,OAAO,EAAE,KAAK,IAAI,EAAE;YAClB,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAA;QAChG,CAAC;KACF,CAAA;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,112 @@
1
+ {
2
+ "name": "@nifrajs/web-solid",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Solid render adapter for @nifrajs/web — SSR + hydration + the Solid Babel build plugin.",
5
+ "keywords": [
6
+ "bun",
7
+ "typescript",
8
+ "nifra",
9
+ "web-framework",
10
+ "full-stack",
11
+ "edge",
12
+ "ssr"
13
+ ],
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "bun": "./src/index.ts",
21
+ "types": "./dist/index.d.ts",
22
+ "default": "./dist/index.js"
23
+ },
24
+ "./client": {
25
+ "bun": "./src/client.ts",
26
+ "types": "./dist/client.d.ts",
27
+ "default": "./dist/client.js"
28
+ },
29
+ "./await": {
30
+ "bun": "./src/await.ts",
31
+ "types": "./dist/await.d.ts",
32
+ "default": "./dist/await.js"
33
+ },
34
+ "./fetcher": {
35
+ "bun": "./src/fetcher.ts",
36
+ "types": "./dist/fetcher.d.ts",
37
+ "default": "./dist/fetcher.js"
38
+ },
39
+ "./query": {
40
+ "bun": "./src/query.ts",
41
+ "types": "./dist/query.d.ts",
42
+ "default": "./dist/query.js"
43
+ },
44
+ "./i18n": {
45
+ "bun": "./src/i18n.ts",
46
+ "types": "./dist/i18n.d.ts",
47
+ "default": "./dist/i18n.js"
48
+ },
49
+ "./image": {
50
+ "bun": "./src/image.ts",
51
+ "types": "./dist/image.d.ts",
52
+ "default": "./dist/image.js"
53
+ },
54
+ "./content": {
55
+ "bun": "./src/content.ts",
56
+ "types": "./dist/content.d.ts",
57
+ "default": "./dist/content.js"
58
+ },
59
+ "./mdx": {
60
+ "bun": "./src/mdx.ts",
61
+ "types": "./dist/mdx.d.ts",
62
+ "default": "./dist/mdx.js"
63
+ },
64
+ "./mdx-runtime": {
65
+ "bun": "./src/mdx-runtime.ts",
66
+ "types": "./dist/mdx-runtime.d.ts",
67
+ "default": "./dist/mdx-runtime.js"
68
+ }
69
+ },
70
+ "files": [
71
+ "dist",
72
+ "src"
73
+ ],
74
+ "sideEffects": false,
75
+ "scripts": {
76
+ "build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist"
77
+ },
78
+ "license": "MIT",
79
+ "publishConfig": {
80
+ "access": "public"
81
+ },
82
+ "dependencies": {
83
+ "@babel/core": "^7.26.0",
84
+ "@babel/preset-typescript": "^7.26.0",
85
+ "@nifrajs/i18n": "workspace:*",
86
+ "@nifrajs/image": "workspace:*",
87
+ "@nifrajs/web": "workspace:*",
88
+ "babel-preset-solid": "^1.9.3"
89
+ },
90
+ "peerDependencies": {
91
+ "@mdx-js/mdx": "^3",
92
+ "solid-js": "^1.9.0"
93
+ },
94
+ "peerDependenciesMeta": {
95
+ "@mdx-js/mdx": {
96
+ "optional": true
97
+ }
98
+ },
99
+ "devDependencies": {
100
+ "@types/babel__core": "^7.20.5",
101
+ "solid-js": "^1.9.3"
102
+ },
103
+ "repository": {
104
+ "type": "git",
105
+ "url": "git+https://github.com/nifrajs/nifra.git",
106
+ "directory": "packages/web-solid"
107
+ },
108
+ "bugs": {
109
+ "url": "https://github.com/nifrajs/nifra/issues"
110
+ },
111
+ "homepage": "https://github.com/nifrajs/nifra#readme"
112
+ }
package/src/await.ts ADDED
@@ -0,0 +1,58 @@
1
+ import type { Deferred } from "@nifrajs/web"
2
+ /**
3
+ * `@nifrajs/web-solid/await` — the `<Await>` primitive for deferred loader data (`defer()`). Isomorphic
4
+ * and imports only `solid-js` + a type from `@nifrajs/web` (never `solid-js/web`'s server or the Babel
5
+ * plugin), so route components can use it without dragging server/build code into the client bundle.
6
+ * No JSX (the package builds with plain `tsc`).
7
+ *
8
+ * Solid streams the boundary natively: on the server `createResource` awaits the deferred promise,
9
+ * `<Suspense>` flushes the fallback then the resolved content mid-stream, and Solid serializes the
10
+ * resource value into its hydration data — so the client hydrates the streamed content without a
11
+ * re-fetch (it reads `_$HY`, not the registry promise).
12
+ */
13
+ import { createComponent, createResource, type JSX, Suspense } from "solid-js"
14
+
15
+ export interface AwaitProps<T> {
16
+ /** A `Deferred<T>` from a loader's `defer(...)`, or an already-resolved value (client nav). */
17
+ readonly resolve: Deferred<T> | T
18
+ readonly fallback?: JSX.Element
19
+ /** Rendered if the deferred rejects. Without it the error propagates to the nearest
20
+ * `<ErrorBoundary>`. */
21
+ readonly errorFallback?: (error: unknown) => JSX.Element
22
+ readonly children: (value: T) => JSX.Element
23
+ }
24
+
25
+ function isDeferred<T>(value: Deferred<T> | T): value is Deferred<T> {
26
+ return (
27
+ typeof value === "object" &&
28
+ value !== null &&
29
+ (value as { __nifra_deferred?: unknown }).__nifra_deferred === true
30
+ )
31
+ }
32
+
33
+ /**
34
+ * Render deferred loader data: show `fallback` until the `Deferred` settles (streamed in by the
35
+ * server), then `children(value)`. An already-resolved `resolve` (a client navigation awaited it)
36
+ * renders immediately. Pairs with a loader's `defer(...)`.
37
+ */
38
+ export function Await<T>(props: AwaitProps<T>): JSX.Element {
39
+ if (!isDeferred(props.resolve)) return props.children(props.resolve)
40
+ const marker = props.resolve
41
+ const [data] = createResource(() => marker.promise)
42
+ return createComponent(Suspense, {
43
+ get fallback() {
44
+ return props.fallback
45
+ },
46
+ get children() {
47
+ // Errored? Render `errorFallback`, else rethrow to an outer <ErrorBoundary>. Check `data.error`
48
+ // BEFORE `data()` (which would rethrow). Otherwise: `data()` suspends until the resource
49
+ // settles (on the client Solid reads the serialized value — no re-fetch).
50
+ if (data.error !== undefined) {
51
+ if (props.errorFallback) return props.errorFallback(data.error)
52
+ throw data.error
53
+ }
54
+ const value = data()
55
+ return value === undefined ? null : props.children(value)
56
+ },
57
+ })
58
+ }
package/src/client.ts ADDED
@@ -0,0 +1,89 @@
1
+ import type { MountRouterOptions, RenderProps } from "@nifrajs/web"
2
+ /**
3
+ * @nifrajs/web-solid/client — Solid client runtime. `hydrate` hydrates a single SSR'd route;
4
+ * `mountRouter` hydrates the initial route then drives navigation through a Solid signal — so a
5
+ * same-route update (revalidation, a param change, an optimistic submit) updates **in place** via
6
+ * fine-grained reactivity, preserving the layout chain + focus + scroll. Runs against Solid's client
7
+ * build; kept in its own entry so SSR code never ships to the client bundle. No JSX — Solid's package
8
+ * build is plain `tsc`, so this uses Solid's runtime functions directly, like `compose`.
9
+ */
10
+ import { createSignal } from "solid-js"
11
+ import { hydrate as solidHydrate, render as solidRender } from "solid-js/web"
12
+ import { compose } from "./compose.ts"
13
+ import { setMountedRouter } from "./fetcher.ts"
14
+
15
+ // The `_error` boundary chain element — defined in its own module, re-exported here so nifra's client
16
+ // codegen resolves it from `@nifrajs/web-solid/client` alongside `mountRouter`.
17
+ export { errorBoundary } from "./error.ts"
18
+
19
+ /** Hydrate a server-rendered Solid layout `chain` (with the loader `props`) inside `container`. */
20
+ export function hydrate(chain: readonly unknown[], props: RenderProps, container: unknown): void {
21
+ solidHydrate(compose(chain, props), container as Element)
22
+ }
23
+
24
+ /**
25
+ * Mount a Solid Router driven by the agnostic store. The first render *hydrates* the SSR'd chain. The
26
+ * route's props are exposed as **getters over a signal** created inside the render root, so:
27
+ *
28
+ * - **Same route** (revalidation, a param change, an optimistic submit) — the snapshot signal is
29
+ * updated and only the components reading the changed props re-run; the layout chain stays mounted,
30
+ * so **focus and scroll are preserved**. This is the case the old "dispose + re-render on every
31
+ * navigation" implementation regressed.
32
+ * - **Different route** — the matched chain genuinely changed (different page + layouts), so the root
33
+ * is disposed and the new chain is rendered. (Getter-props are a transparent reactive boundary, so
34
+ * they don't shift Solid's structural hydration keys — unlike a `<Show>`, which would.)
35
+ */
36
+ export function mountRouter(options: MountRouterOptions): void {
37
+ const { router, routes, container } = options
38
+ setMountedRouter(router) // expose it to createFetcher/useFetchers (same page, client-only)
39
+ const el = container as Element
40
+
41
+ let dispose: (() => void) | undefined
42
+ let mountedRouteId: string | null = null
43
+ // The setter for the currently-mounted route's reactive props — same-route settles push through it.
44
+ let update: ((snapshot: ReturnType<typeof router.snapshot>) => void) | null = null
45
+
46
+ const mount = (first: boolean): void => {
47
+ const initial = router.snapshot()
48
+ mountedRouteId = initial.routeId
49
+ const renderer = first ? solidHydrate : solidRender
50
+ dispose = renderer(() => {
51
+ // Signal owned by THIS render root (so it's never ownerless). `props` are getters over it; a
52
+ // same-route settle calls `update(snapshot)` and the reading components update in place — no
53
+ // recompose, so the layout chain + focus + scroll survive. Cast bridges
54
+ // `exactOptionalPropertyTypes` (a getter is always present, returning `undefined` when idle —
55
+ // the documented "absent on idle" semantics for `submission`).
56
+ const [snapshot, setSnapshot] = createSignal(initial)
57
+ update = setSnapshot
58
+ const props = {
59
+ get data() {
60
+ return snapshot().data
61
+ },
62
+ get actionData() {
63
+ return snapshot().actionData
64
+ },
65
+ get pending() {
66
+ return snapshot().pending
67
+ },
68
+ get submission() {
69
+ return snapshot().submission
70
+ },
71
+ } as RenderProps
72
+ // The matched chain is fixed for this mount (routeId is constant here); a route *change* disposes
73
+ // and re-mounts below, so the root render fn reads no signal and never re-runs on its own.
74
+ return compose(routes[initial.routeId] ?? [], props)()
75
+ }, el)
76
+ }
77
+
78
+ mount(true)
79
+ router.subscribe(() => {
80
+ const snapshot = router.snapshot()
81
+ if (snapshot.pending) return // skip the in-flight tick; act once settled
82
+ if (snapshot.routeId === mountedRouteId) {
83
+ update?.(snapshot) // same route → reactive in-place update (preserves layout/focus/scroll)
84
+ } else {
85
+ dispose?.() // route changed → tear down and render the new chain
86
+ mount(false)
87
+ }
88
+ })
89
+ }
package/src/compose.ts ADDED
@@ -0,0 +1,18 @@
1
+ import type { RenderProps } from "@nifrajs/web"
2
+ import { type Component, createComponent, type JSX } from "solid-js"
3
+
4
+ /**
5
+ * Fold a layout chain (outermost layout → page) into a single Solid tree: the page
6
+ * (innermost) receives `props` (the loader data); each layout wraps the child via its
7
+ * `children`. Shared by the server adapter (renderToString) and the client (hydrate).
8
+ */
9
+ export function compose(chain: readonly unknown[], props: RenderProps): () => JSX.Element {
10
+ const last = chain.length - 1
11
+ let node: () => JSX.Element = () => createComponent(chain[last] as Component<RenderProps>, props)
12
+ for (let i = last - 1; i >= 0; i--) {
13
+ const Layout = chain[i] as Component<{ children: JSX.Element }>
14
+ const child = node
15
+ node = () => createComponent(Layout, { children: child() })
16
+ }
17
+ return node
18
+ }
package/src/content.ts ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * `@nifrajs/web-solid/content` — render pre-rendered HTML (e.g. a `@nifrajs/content` collection entry's
3
+ * `.html`) into the DOM. Solid sets `innerHTML` reactively on a `<Dynamic>` element. Pairs with content
4
+ * collections: `<Content html={entry.html} />`. No JSX (`createComponent`/`Dynamic`), so it builds with
5
+ * plain `tsc`.
6
+ *
7
+ * Security: injects raw HTML, so `html` MUST be trusted (your own Markdown/content, rendered at
8
+ * build/server time) — never pass user-supplied HTML without sanitizing it first.
9
+ */
10
+ import { createComponent, type JSX, mergeProps, splitProps } from "solid-js"
11
+ import { Dynamic } from "solid-js/web"
12
+
13
+ export interface ContentProps {
14
+ /** Trusted, pre-rendered HTML (e.g. `entry.html`). */
15
+ readonly html: string
16
+ /** Wrapper element (default `"div"`). */
17
+ readonly as?: string
18
+ /** DOM passthrough (`class`, `style`, `id`, `data-*`, handlers). */
19
+ readonly [key: string]: unknown
20
+ }
21
+
22
+ /** Render trusted HTML into a wrapper element. Extra props pass through reactively. */
23
+ export function Content(props: ContentProps): JSX.Element {
24
+ const [, rest] = splitProps(props, ["html", "as"])
25
+ // A function source keeps `component`/`innerHTML` reactive (matches the `<Image>` pattern).
26
+ return createComponent(
27
+ Dynamic,
28
+ mergeProps(rest, () => ({ component: props.as ?? "div", innerHTML: props.html })),
29
+ )
30
+ }