@nifrajs/web-svelte 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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +18 -0
  3. package/dist/Await.svelte +27 -0
  4. package/dist/Await.svelte.d.ts +21 -0
  5. package/dist/Chain.svelte +35 -0
  6. package/dist/Content.svelte +13 -0
  7. package/dist/Content.svelte.d.ts +17 -0
  8. package/dist/I18nProvider.svelte +20 -0
  9. package/dist/I18nProvider.svelte.d.ts +15 -0
  10. package/dist/Image.svelte +27 -0
  11. package/dist/Image.svelte.d.ts +29 -0
  12. package/dist/Router.svelte +27 -0
  13. package/dist/client.d.ts +12 -0
  14. package/dist/client.d.ts.map +1 -0
  15. package/dist/client.js +30 -0
  16. package/dist/client.js.map +1 -0
  17. package/dist/error.d.ts +13 -0
  18. package/dist/error.d.ts.map +1 -0
  19. package/dist/error.js +5 -0
  20. package/dist/error.js.map +1 -0
  21. package/dist/fetcher.d.ts +36 -0
  22. package/dist/fetcher.d.ts.map +1 -0
  23. package/dist/fetcher.js +55 -0
  24. package/dist/fetcher.js.map +1 -0
  25. package/dist/i18n.d.ts +11 -0
  26. package/dist/i18n.d.ts.map +1 -0
  27. package/dist/i18n.js +15 -0
  28. package/dist/i18n.js.map +1 -0
  29. package/dist/index.d.ts +13 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +40 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/mdx.d.ts +5 -0
  34. package/dist/mdx.d.ts.map +1 -0
  35. package/dist/mdx.js +74 -0
  36. package/dist/mdx.js.map +1 -0
  37. package/dist/plugin.d.ts +3 -0
  38. package/dist/plugin.d.ts.map +1 -0
  39. package/dist/plugin.js +58 -0
  40. package/dist/plugin.js.map +1 -0
  41. package/dist/query.d.ts +26 -0
  42. package/dist/query.d.ts.map +1 -0
  43. package/dist/query.js +55 -0
  44. package/dist/query.js.map +1 -0
  45. package/package.json +111 -0
  46. package/src/Await.svelte +27 -0
  47. package/src/Await.svelte.d.ts +21 -0
  48. package/src/Chain.svelte +35 -0
  49. package/src/Content.svelte +13 -0
  50. package/src/Content.svelte.d.ts +17 -0
  51. package/src/I18nProvider.svelte +20 -0
  52. package/src/I18nProvider.svelte.d.ts +15 -0
  53. package/src/Image.svelte +27 -0
  54. package/src/Image.svelte.d.ts +29 -0
  55. package/src/Router.svelte +27 -0
  56. package/src/client.ts +33 -0
  57. package/src/error.ts +15 -0
  58. package/src/fetcher.ts +66 -0
  59. package/src/i18n.ts +23 -0
  60. package/src/index.ts +51 -0
  61. package/src/mdx.ts +72 -0
  62. package/src/plugin.ts +60 -0
  63. package/src/query.ts +66 -0
  64. package/src/svelte-shim.d.ts +9 -0
package/dist/mdx.js ADDED
@@ -0,0 +1,74 @@
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
+ import { compile } from "svelte/compiler";
10
+ /**
11
+ * `@nifrajs/web-svelte/mdx` — compile `.mdx` routes/files to **Svelte** components. Svelte has no JSX
12
+ * runtime, so (unlike the JSX-family `mdxBunPlugin`) MDX-for-Svelte goes through `mdsvex` — Svelte's
13
+ * own Markdown compiler — which produces a `.svelte` module, then the standard `svelte/compiler` runs
14
+ * (`generate: "server"` for SSR, `"client"` for the browser). Pass it to `buildClient`/`buildServer`'s
15
+ * `plugins` alongside `svelteBunPlugin` (one handles `.svelte`, this one `.mdx`). `mdsvex` is an
16
+ * optional peer, lazy-loaded at build time.
17
+ *
18
+ * `<style>` blocks inside MDX are scoped + bundled exactly like `svelteBunPlugin`'s (own CSS namespace
19
+ * so the two plugins don't collide).
20
+ */
21
+ const MDSVEX_MODULE = "mdsvex"; // non-literal so consumers don't need mdsvex's types to typecheck
22
+ const STYLE_SUFFIX = "?svelte-mdx-css";
23
+ const STYLE_NS = "nifra-svelte-mdx-css";
24
+ /** Build a `Bun.build` plugin that loads `.mdx` files as Svelte components via mdsvex. `generate`:
25
+ * `"ssr"` for the server build, `"dom"` for the client (matches `svelteBunPlugin`). */
26
+ export function svelteMdxBunPlugin(generate) {
27
+ const cssByPath = new Map();
28
+ return {
29
+ name: `nifra-svelte-mdx-${generate}`,
30
+ async setup(build) {
31
+ let mdsvexCompile;
32
+ try {
33
+ mdsvexCompile = (await import(__rewriteRelativeImportExtension(MDSVEX_MODULE))).compile;
34
+ }
35
+ catch {
36
+ throw new Error("@nifrajs/web-svelte/mdx: compiling `.mdx` for Svelte needs the `mdsvex` package — install it (`bun add mdsvex`).");
37
+ }
38
+ build.onLoad({ filter: /\.mdx(\?|$)/ }, async (args) => {
39
+ const path = args.path.split("?")[0] ?? args.path;
40
+ // 1. Markdown/MDX → Svelte source (mdsvex). `extensions` must include `.mdx` or mdsvex skips the
41
+ // file (returns undefined — it only processes its configured extensions, default `.svx`).
42
+ const pre = await mdsvexCompile(await Bun.file(path).text(), {
43
+ filename: path,
44
+ extensions: [".mdx", ".svx", ".md"],
45
+ });
46
+ if (!pre)
47
+ throw new Error(`@nifrajs/web-svelte/mdx: mdsvex produced no output for ${path}`);
48
+ // 2. Svelte source → JS, exactly like `svelteBunPlugin`.
49
+ const { js, css } = compile(pre.code, {
50
+ generate: generate === "ssr" ? "server" : "client",
51
+ filename: path,
52
+ css: "external",
53
+ });
54
+ if (generate === "dom" && css?.code) {
55
+ cssByPath.set(path, css.code);
56
+ return {
57
+ contents: `${js.code}\nimport ${JSON.stringify(path + STYLE_SUFFIX)}\n`,
58
+ loader: "js",
59
+ };
60
+ }
61
+ return { contents: js.code, loader: "js" };
62
+ });
63
+ build.onResolve({ filter: /\?svelte-mdx-css$/ }, (args) => ({
64
+ path: args.path,
65
+ namespace: STYLE_NS,
66
+ }));
67
+ build.onLoad({ filter: /.*/, namespace: STYLE_NS }, (args) => ({
68
+ contents: cssByPath.get(args.path.slice(0, -STYLE_SUFFIX.length)) ?? "",
69
+ loader: "css",
70
+ }));
71
+ },
72
+ };
73
+ }
74
+ //# sourceMappingURL=mdx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mdx.js","sourceRoot":"","sources":["../src/mdx.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { BunPlugin } from "bun";
2
+ export declare function svelteBunPlugin(generate: "dom" | "ssr"): BunPlugin;
3
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,KAAK,CAAA;AAsBpC,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,GAAG,KAAK,GAAG,SAAS,CAqClE"}
package/dist/plugin.js ADDED
@@ -0,0 +1,58 @@
1
+ import { compile } from "svelte/compiler";
2
+ /**
3
+ * `@nifrajs/web-svelte/plugin` — the `.svelte` compiler Bun-plugin, in its OWN module (no `.svelte`
4
+ * imports). This matters for the SSR preload: `bun --preload` must register the plugin BEFORE any
5
+ * `.svelte` file is loaded. Importing it from the package root (`./index`) would eagerly load
6
+ * `Chain.svelte` (the adapter's fold) before the plugin is registered — so it lives here instead.
7
+ *
8
+ * `generate: "server"` for SSR, `"client"` for the browser (Svelte 5's client output is hydratable).
9
+ * Preload it for SSR (`bun --preload`) and pass it to `buildClient({ plugins: [...] })` for the client
10
+ * bundle. The compiler runs at build time, on `.svelte` files only.
11
+ *
12
+ * `<style>` blocks: Svelte scopes selectors (`.foo.svelte-<hash>`) and bakes the matching classes into
13
+ * the markup (both client + server output), so SSR HTML is already scoped. The **client** build emits
14
+ * the scoped stylesheet as a virtual `?svelte-css` module that `Bun.build`'s CSS bundler folds into the
15
+ * app stylesheet (served as a `<link>`); the SSR build drops it (the stylesheet ships from the client
16
+ * build — no double-emit). With `nifra dev`, `@sveltejs/vite-plugin-svelte` injects the CSS instead.
17
+ */
18
+ const STYLE_SUFFIX = "?svelte-css";
19
+ const STYLE_NS = "nifra-svelte-css";
20
+ export function svelteBunPlugin(generate) {
21
+ // Compiled scoped CSS per `.svelte` file (client build only) — read back by the virtual-module loader.
22
+ const cssByPath = new Map();
23
+ return {
24
+ name: `nifra-svelte-${generate}`,
25
+ setup(build) {
26
+ // Match `.svelte`, tolerating a `?query` suffix (dev servers append one to bust Bun's import
27
+ // cache); strip it before reading the file off disk.
28
+ build.onLoad({ filter: /\.svelte(\?|$)/ }, async (args) => {
29
+ const path = args.path.split("?")[0] ?? args.path;
30
+ const source = await Bun.file(path).text();
31
+ const { js, css } = compile(source, {
32
+ generate: generate === "ssr" ? "server" : "client",
33
+ filename: path,
34
+ css: "external", // emit the scoped stylesheet separately (not injected into the JS)
35
+ });
36
+ if (generate === "dom" && css?.code) {
37
+ cssByPath.set(path, css.code);
38
+ // Import the virtual style module so the bundler pulls the scoped CSS into the app stylesheet.
39
+ return {
40
+ contents: `${js.code}\nimport ${JSON.stringify(path + STYLE_SUFFIX)}\n`,
41
+ loader: "js",
42
+ };
43
+ }
44
+ return { contents: js.code, loader: "js" };
45
+ });
46
+ // Virtual CSS module: `<file>.svelte?svelte-css` → the compiled scoped stylesheet (css loader).
47
+ build.onResolve({ filter: /\?svelte-css$/ }, (args) => ({
48
+ path: args.path,
49
+ namespace: STYLE_NS,
50
+ }));
51
+ build.onLoad({ filter: /.*/, namespace: STYLE_NS }, (args) => ({
52
+ contents: cssByPath.get(args.path.slice(0, -STYLE_SUFFIX.length)) ?? "",
53
+ loader: "css",
54
+ }));
55
+ },
56
+ };
57
+ }
58
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,iBAAiB,CAAA;AAEzC;;;;;;;;;;;;;;;GAeG;AACH,MAAM,YAAY,GAAG,aAAa,CAAA;AAClC,MAAM,QAAQ,GAAG,kBAAkB,CAAA;AAEnC,MAAM,UAAU,eAAe,CAAC,QAAuB;IACrD,uGAAuG;IACvG,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAA;IAC3C,OAAO;QACL,IAAI,EAAE,gBAAgB,QAAQ,EAAE;QAChC,KAAK,CAAC,KAAK;YACT,6FAA6F;YAC7F,qDAAqD;YACrD,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,gBAAgB,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;gBACxD,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,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE;oBAClC,QAAQ,EAAE,QAAQ,KAAK,KAAK,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ;oBAClD,QAAQ,EAAE,IAAI;oBACd,GAAG,EAAE,UAAU,EAAE,mEAAmE;iBACrF,CAAC,CAAA;gBACF,IAAI,QAAQ,KAAK,KAAK,IAAI,GAAG,EAAE,IAAI,EAAE,CAAC;oBACpC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC,CAAA;oBAC7B,+FAA+F;oBAC/F,OAAO;wBACL,QAAQ,EAAE,GAAG,EAAE,CAAC,IAAI,YAAY,IAAI,CAAC,SAAS,CAAC,IAAI,GAAG,YAAY,CAAC,IAAI;wBACvE,MAAM,EAAE,IAAI;qBACb,CAAA;gBACH,CAAC;gBACD,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAA;YAC5C,CAAC,CAAC,CAAA;YACF,gGAAgG;YAChG,KAAK,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,eAAe,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBACtD,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,SAAS,EAAE,QAAQ;aACpB,CAAC,CAAC,CAAA;YACH,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;gBAC7D,QAAQ,EAAE,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE;gBACvE,MAAM,EAAE,KAAK;aACd,CAAC,CAAC,CAAA;QACL,CAAC;KACF,CAAA;AACH,CAAC"}
@@ -0,0 +1,26 @@
1
+ import { type QueryClient, type QueryState } from "@nifrajs/web";
2
+ /**
3
+ * `@nifrajs/web-svelte/query` — Svelte bindings for the keyed query-cache, as **Svelte stores** (plain
4
+ * `.ts`). `useQuery(key, fn)` returns a `Readable<QueryState<T>>` augmented with `refetch` — read it
5
+ * reactively with `$query`. The store fetches on mount (the start notifier fires on the first
6
+ * `$`-subscription) and subscribes to the cache entry; concurrent `useQuery`s with the same key share
7
+ * one entry + one in-flight fetch (dedup). `useQueryClient` exposes `invalidateQueries`.
8
+ *
9
+ * The query client is created lazily **client-side only** (the `typeof window` guard) — on the server
10
+ * there is none, so `useQuery` holds the idle/pending state (queries are client-first; loaders are the
11
+ * SSR data source). The first client render uses the same idle snapshot, so no mismatch.
12
+ */
13
+ import { type Readable } from "svelte/store";
14
+ /** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
15
+ export declare function useQueryClient(): Pick<QueryClient, "invalidateQueries">;
16
+ /** A query store: a `Readable<QueryState<T>>` (read via `$`) plus `refetch`. */
17
+ export type QueryStore<T> = Readable<QueryState<T>> & {
18
+ /** Force a refetch (ignores `staleTime`). */
19
+ readonly refetch: () => Promise<T>;
20
+ };
21
+ /**
22
+ * Subscribe to the keyed query for `key`, fetched via `fn`. Returns a store of `{ status, data, error,
23
+ * isFetching, updatedAt }` augmented with `refetch`. Fetches on mount (first `$`-subscription); SSR-idle.
24
+ */
25
+ export declare function useQuery<T>(key: unknown, fn: () => Promise<T>): QueryStore<T>;
26
+ //# sourceMappingURL=query.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.d.ts","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,WAAW,EAEhB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAA;AACrB;;;;;;;;;;GAUG;AACH,OAAO,EAAE,KAAK,QAAQ,EAAY,MAAM,cAAc,CAAA;AAStD,wGAAwG;AACxG,wBAAgB,cAAc,IAAI,IAAI,CAAC,WAAW,EAAE,mBAAmB,CAAC,CAEvE;AAWD,gFAAgF;AAChF,MAAM,MAAM,UAAU,CAAC,CAAC,IAAI,QAAQ,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG;IACpD,6CAA6C;IAC7C,QAAQ,CAAC,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAA;CACnC,CAAA;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,UAAU,CAAC,CAAC,CAAC,CAe7E"}
package/dist/query.js ADDED
@@ -0,0 +1,55 @@
1
+ import { createQueryClient, } from "@nifrajs/web";
2
+ /**
3
+ * `@nifrajs/web-svelte/query` — Svelte bindings for the keyed query-cache, as **Svelte stores** (plain
4
+ * `.ts`). `useQuery(key, fn)` returns a `Readable<QueryState<T>>` augmented with `refetch` — read it
5
+ * reactively with `$query`. The store fetches on mount (the start notifier fires on the first
6
+ * `$`-subscription) and subscribes to the cache entry; concurrent `useQuery`s with the same key share
7
+ * one entry + one in-flight fetch (dedup). `useQueryClient` exposes `invalidateQueries`.
8
+ *
9
+ * The query client is created lazily **client-side only** (the `typeof window` guard) — on the server
10
+ * there is none, so `useQuery` holds the idle/pending state (queries are client-first; loaders are the
11
+ * SSR data source). The first client render uses the same idle snapshot, so no mismatch.
12
+ */
13
+ import { readable } from "svelte/store";
14
+ let client;
15
+ function getClient() {
16
+ if (typeof window === "undefined")
17
+ return undefined; // SSR → no client (queries render idle)
18
+ if (client === undefined)
19
+ client = createQueryClient({ now: () => Date.now() });
20
+ return client;
21
+ }
22
+ /** Access the query client to imperatively `invalidateQueries(keyOrPrefix)` (e.g. after a mutation). */
23
+ export function useQueryClient() {
24
+ return getClient() ?? { invalidateQueries: () => { } };
25
+ }
26
+ // Stable idle snapshot for the server / pre-fetch render.
27
+ const IDLE = Object.freeze({
28
+ status: "pending",
29
+ data: undefined,
30
+ error: undefined,
31
+ isFetching: false,
32
+ updatedAt: Number.NEGATIVE_INFINITY,
33
+ });
34
+ /**
35
+ * Subscribe to the keyed query for `key`, fetched via `fn`. Returns a store of `{ status, data, error,
36
+ * isFetching, updatedAt }` augmented with `refetch`. Fetches on mount (first `$`-subscription); SSR-idle.
37
+ */
38
+ export function useQuery(key, fn) {
39
+ const handle = getClient()?.query(key, fn);
40
+ const store = readable(handle ? handle.snapshot() : IDLE, (set) => {
41
+ if (handle === undefined)
42
+ return;
43
+ set(handle.snapshot());
44
+ const unsubscribe = handle.subscribe(() => set(handle.snapshot()));
45
+ handle.fetch().catch(() => { }); // fetch on mount (the first subscriber attached)
46
+ return unsubscribe;
47
+ });
48
+ const refetch = handle
49
+ ? handle.refetch
50
+ : (async () => {
51
+ throw new Error("[nifra/web-svelte] useQuery.refetch called with no query client (server?)");
52
+ });
53
+ return Object.assign(store, { refetch });
54
+ }
55
+ //# sourceMappingURL=query.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"query.js","sourceRoot":"","sources":["../src/query.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,iBAAiB,GAIlB,MAAM,cAAc,CAAA;AACrB;;;;;;;;;;GAUG;AACH,OAAO,EAAiB,QAAQ,EAAE,MAAM,cAAc,CAAA;AAEtD,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,0DAA0D;AAC1D,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;AAQF;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAI,GAAY,EAAE,EAAoB;IAC5D,MAAM,MAAM,GAA+B,SAAS,EAAE,EAAE,KAAK,CAAI,GAAG,EAAE,EAAE,CAAC,CAAA;IACzE,MAAM,KAAK,GAAG,QAAQ,CAAgB,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,GAAG,EAAE,EAAE;QAC/E,IAAI,MAAM,KAAK,SAAS;YAAE,OAAM;QAChC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAA;QACtB,MAAM,WAAW,GAAG,MAAM,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAA;QAClE,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAA,CAAC,iDAAiD;QAChF,OAAO,WAAW,CAAA;IACpB,CAAC,CAAC,CAAA;IACF,MAAM,OAAO,GAAG,MAAM;QACpB,CAAC,CAAC,MAAM,CAAC,OAAO;QAChB,CAAC,CAAE,CAAC,KAAK,IAAI,EAAE;YACX,MAAM,IAAI,KAAK,CAAC,2EAA2E,CAAC,CAAA;QAC9F,CAAC,CAAsB,CAAA;IAC3B,OAAO,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,EAAE,OAAO,EAAE,CAAC,CAAA;AAC1C,CAAC"}
package/package.json ADDED
@@ -0,0 +1,111 @@
1
+ {
2
+ "name": "@nifrajs/web-svelte",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "Svelte 5 render adapter for @nifrajs/web — SSR + hydration + the .svelte compiler Bun-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
+ "./plugin": {
30
+ "bun": "./src/plugin.ts",
31
+ "types": "./dist/plugin.d.ts",
32
+ "default": "./dist/plugin.js"
33
+ },
34
+ "./await": {
35
+ "types": "./dist/Await.svelte.d.ts",
36
+ "bun": "./src/Await.svelte",
37
+ "svelte": "./dist/Await.svelte",
38
+ "default": "./dist/Await.svelte"
39
+ },
40
+ "./fetcher": {
41
+ "bun": "./src/fetcher.ts",
42
+ "types": "./dist/fetcher.d.ts",
43
+ "default": "./dist/fetcher.js"
44
+ },
45
+ "./query": {
46
+ "bun": "./src/query.ts",
47
+ "types": "./dist/query.d.ts",
48
+ "default": "./dist/query.js"
49
+ },
50
+ "./image": {
51
+ "types": "./dist/Image.svelte.d.ts",
52
+ "bun": "./src/Image.svelte",
53
+ "svelte": "./dist/Image.svelte",
54
+ "default": "./dist/Image.svelte"
55
+ },
56
+ "./content": {
57
+ "types": "./dist/Content.svelte.d.ts",
58
+ "bun": "./src/Content.svelte",
59
+ "svelte": "./dist/Content.svelte",
60
+ "default": "./dist/Content.svelte"
61
+ },
62
+ "./i18n": {
63
+ "bun": "./src/i18n.ts",
64
+ "types": "./dist/i18n.d.ts",
65
+ "default": "./dist/i18n.js"
66
+ },
67
+ "./mdx": {
68
+ "bun": "./src/mdx.ts",
69
+ "types": "./dist/mdx.d.ts",
70
+ "default": "./dist/mdx.js"
71
+ }
72
+ },
73
+ "files": [
74
+ "dist",
75
+ "src"
76
+ ],
77
+ "sideEffects": false,
78
+ "scripts": {
79
+ "build": "tsc -p tsconfig.build.json && bun run ../../scripts/fix-dts.ts dist && cp src/*.svelte dist/ && cp src/*.svelte.d.ts dist/"
80
+ },
81
+ "license": "MIT",
82
+ "publishConfig": {
83
+ "access": "public"
84
+ },
85
+ "dependencies": {
86
+ "@nifrajs/i18n": "workspace:*",
87
+ "@nifrajs/image": "workspace:*",
88
+ "@nifrajs/web": "workspace:*"
89
+ },
90
+ "peerDependencies": {
91
+ "mdsvex": "^0.12",
92
+ "svelte": "^5.3.0"
93
+ },
94
+ "peerDependenciesMeta": {
95
+ "mdsvex": {
96
+ "optional": true
97
+ }
98
+ },
99
+ "devDependencies": {
100
+ "svelte": "^5.3.0"
101
+ },
102
+ "repository": {
103
+ "type": "git",
104
+ "url": "git+https://github.com/nifrajs/nifra.git",
105
+ "directory": "packages/web-svelte"
106
+ },
107
+ "bugs": {
108
+ "url": "https://github.com/nifrajs/nifra/issues"
109
+ },
110
+ "homepage": "https://github.com/nifrajs/nifra#readme"
111
+ }
@@ -0,0 +1,27 @@
1
+ <!--
2
+ Await.svelte — the `<Await>` primitive for deferred loader/action data (`defer()`), built on Svelte's
3
+ native `{#await}` block + scoped-snippet props: `children(value)` (resolved), `pending`, `error(err)`.
4
+
5
+ Svelte SSR renders the PENDING branch of `{#await}` (it does not block on the promise), so the server
6
+ emits the fallback and the client resolves it (hydration / soft-nav) — matching nifra's non-blocking
7
+ `defer()` (like the Vue adapter; contrast React/Preact, which stream the resolved content into the SSR
8
+ HTML). An already-resolved `resolve` (a plain value, or a client nav that awaited it) renders directly.
9
+ Plain-JS script (no TS) so it needs no svelte TS preprocessor.
10
+ -->
11
+ <script>
12
+ let { resolve, children, pending, error } = $props()
13
+ const isDeferred = (v) =>
14
+ v != null && typeof v === "object" && v.__velo_deferred === true
15
+ </script>
16
+
17
+ {#if isDeferred(resolve)}
18
+ {#await resolve.promise}
19
+ {@render pending?.()}
20
+ {:then value}
21
+ {@render children?.(value)}
22
+ {:catch err}
23
+ {@render error?.(err)}
24
+ {/await}
25
+ {:else}
26
+ {@render children?.(resolve)}
27
+ {/if}
@@ -0,0 +1,21 @@
1
+ import type { Deferred } from "@nifrajs/web"
2
+ import type { Component, Snippet } from "svelte"
3
+
4
+ /**
5
+ * Hand-written types for `Await.svelte` (svelte-package would generate this from the component). The
6
+ * internal build resolves `*.svelte` via the ambient `svelte-shim.d.ts`; this file is what *consumers*
7
+ * resolve through the `./await` export's `types` condition.
8
+ */
9
+ export interface AwaitProps {
10
+ /** A `Deferred<T>` from a loader/action's `defer(...)`, or an already-resolved value. */
11
+ resolve: Deferred<unknown> | unknown
12
+ /** Rendered with the resolved value (the default snippet). */
13
+ children?: Snippet<[unknown]>
14
+ /** Rendered while the deferred is pending (the SSR + pre-resolve state). */
15
+ pending?: Snippet
16
+ /** Rendered if the deferred rejects. */
17
+ error?: Snippet<[unknown]>
18
+ }
19
+
20
+ declare const Await: Component<AwaitProps>
21
+ export default Await
@@ -0,0 +1,35 @@
1
+ <!--
2
+ Chain.svelte — folds a nifra layout chain (outermost layout → page) into nested Svelte components:
3
+ the page (innermost, chain[last]) receives `props` (the loader data); each layout wraps the rest via
4
+ its `children` snippet ({@render children()} in the layout). Recurses on itself. This is the Svelte
5
+ analogue of the React/Vue `compose` — Svelte components are compiled (not callable), so the fold is a
6
+ component, not a function returning a node.
7
+ -->
8
+ <script>
9
+ import Self from "./Chain.svelte"
10
+ let { chain, props } = $props()
11
+ </script>
12
+
13
+ {#if chain.length <= 1}
14
+ {#if chain[0]}
15
+ {@const Page = chain[0]}
16
+ <Page {...props} />
17
+ {/if}
18
+ {:else if chain[0] && chain[0].__veloErrorBoundary}
19
+ <!-- nifra `_error` boundary marker (from errorBoundary()): wrap the rest in <svelte:boundary>; the
20
+ `failed` snippet renders the route's _error component with the serialized error. -->
21
+ {@const Fallback = chain[0].__veloErrorBoundary}
22
+ <svelte:boundary>
23
+ <Self chain={chain.slice(1)} {props} />
24
+ {#snippet failed(error)}
25
+ <Fallback data={{ name: error?.name ?? "Error", message: String(error?.message ?? error) }} />
26
+ {/snippet}
27
+ </svelte:boundary>
28
+ {:else}
29
+ {@const Layout = chain[0]}
30
+ <Layout>
31
+ {#snippet children()}
32
+ <Self chain={chain.slice(1)} {props} />
33
+ {/snippet}
34
+ </Layout>
35
+ {/if}
@@ -0,0 +1,13 @@
1
+ <!--
2
+ Content.svelte — render trusted pre-rendered HTML (e.g. a @nifrajs/content collection entry's `.html`)
3
+ into the DOM via Svelte's {@html}. Pairs with content collections: <Content html={entry.html} />.
4
+ `as` picks the wrapper element; extra attributes in `...rest` (class/style/id/data-*) pass through.
5
+
6
+ Security: {@html} injects raw HTML, so `html` MUST be trusted (your own Markdown/content, rendered at
7
+ build/server time) — never pass user-supplied HTML without sanitizing it first. Plain-JS script.
8
+ -->
9
+ <script>
10
+ let { html, as = "div", ...rest } = $props()
11
+ </script>
12
+
13
+ <svelte:element this={as} {...rest}>{@html html}</svelte:element>
@@ -0,0 +1,17 @@
1
+ import type { Component } from "svelte"
2
+
3
+ /**
4
+ * Hand-written types for `Content.svelte` (svelte-package would generate this). Consumers resolve this
5
+ * through the `./content` export's `types` condition.
6
+ */
7
+ export interface ContentProps {
8
+ /** Trusted, pre-rendered HTML (e.g. `entry.html`). */
9
+ html: string
10
+ /** Wrapper element (default `"div"`). */
11
+ as?: string
12
+ /** DOM passthrough (`class`, `style`, `id`, `data-*`, …). */
13
+ [key: string]: unknown
14
+ }
15
+
16
+ declare const Content: Component<ContentProps>
17
+ export default Content
@@ -0,0 +1,20 @@
1
+ <!--
2
+ I18nProvider.svelte — provides a nifra i18n `Formatter` (built from `locale` + `messages`) to the
3
+ subtree via Svelte context; consumers read it with `useT()` from `@nifrajs/web-svelte/i18n`. The
4
+ formatter is `$derived`, so switching locale rebuilds it. `locale` + `messages` are serializable, so
5
+ SSR renders the negotiated catalog and the client rebuilds the same formatter on hydrate (no
6
+ mismatch). Plain-JS script. The context key is a string (kept in sync with i18n.ts's `useT`) so this
7
+ component needs no `.ts` import.
8
+ -->
9
+ <script>
10
+ import { createFormatter } from "@nifrajs/i18n"
11
+ import { setContext } from "svelte"
12
+
13
+ let { locale, messages, children } = $props()
14
+
15
+ const formatter = $derived(createFormatter(locale, messages))
16
+ // A getter (not the value) so consumers read the current `$derived` formatter reactively.
17
+ setContext("@nifrajs/web-svelte:i18n", () => formatter)
18
+ </script>
19
+
20
+ {@render children?.()}
@@ -0,0 +1,15 @@
1
+ import type { Messages } from "@nifrajs/i18n"
2
+ import type { Component, Snippet } from "svelte"
3
+
4
+ /** Hand-written types for `I18nProvider.svelte` (consumers resolve these via the `./i18n` re-export). */
5
+ export interface I18nProviderProps {
6
+ /** The active locale (e.g. `"en"`, `"fr-CA"`). */
7
+ locale: string
8
+ /** The ICU message catalog for `locale`. */
9
+ messages: Messages
10
+ /** The subtree that reads the formatter via `useT()`. */
11
+ children?: Snippet
12
+ }
13
+
14
+ declare const I18nProvider: Component<I18nProviderProps>
15
+ export default I18nProvider
@@ -0,0 +1,27 @@
1
+ <!--
2
+ Image.svelte — a CLS-safe responsive <Image> for nifra + Svelte. A thin wrapper over `resolveImage` +
3
+ `toHtmlAttrs` from @nifrajs/image: it computes the responsive <img> attributes (src/srcset/sizes/width/
4
+ height/loading/decoding/fetchpriority — lowercase HTML names) and renders an <img>. Any extra
5
+ attributes (class/style/id/data-*/handlers in `...rest`) pass through. `width`+`height` are required
6
+ (reserve layout space — no CLS); `priority` marks the LCP image. Resizing is delegated to the
7
+ `loader` (an image CDN); nifra bundles no codec. Plain-JS script (no TS preprocessor needed).
8
+ -->
9
+ <script>
10
+ import { resolveImage, toHtmlAttrs } from "@nifrajs/image"
11
+
12
+ let { src, width, height, alt, sizes, widths, quality, loading, priority, loader, ...rest } = $props()
13
+
14
+ // `$derived` keeps the attributes in sync if props change. Build ImageProps omitting unset optionals;
15
+ // resolveImage validates width/height > 0 (the CLS contract) and builds the responsive srcSet.
16
+ const attrs = $derived.by(() => {
17
+ const input = { src, width, height, alt }
18
+ if (sizes !== undefined) input.sizes = sizes
19
+ if (widths !== undefined) input.widths = widths
20
+ if (quality !== undefined) input.quality = quality
21
+ if (loading !== undefined) input.loading = loading
22
+ if (priority !== undefined) input.priority = priority
23
+ return toHtmlAttrs(resolveImage(input, loader))
24
+ })
25
+ </script>
26
+
27
+ <img {...rest} {...attrs} />
@@ -0,0 +1,29 @@
1
+ import type { ImageLoader, ImageProps } from "@nifrajs/image"
2
+ import type { Component } from "svelte"
3
+ import type { HTMLImgAttributes } from "svelte/elements"
4
+
5
+ /**
6
+ * Hand-written types for `Image.svelte` (svelte-package would generate this). The internal build
7
+ * resolves `*.svelte` via the ambient `svelte-shim.d.ts`; this file is what *consumers* resolve through
8
+ * the `./image` export's `types` condition.
9
+ */
10
+ type ComputedAttrs =
11
+ | "src"
12
+ | "srcset"
13
+ | "sizes"
14
+ | "width"
15
+ | "height"
16
+ | "alt"
17
+ | "loading"
18
+ | "decoding"
19
+ | "fetchpriority"
20
+
21
+ export interface ImageComponentProps
22
+ extends ImageProps,
23
+ Omit<HTMLImgAttributes, ComputedAttrs | keyof ImageProps> {
24
+ /** CDN URL builder. Defaults to the identity loader (no transform — still CLS-safe + lazy). */
25
+ loader?: ImageLoader
26
+ }
27
+
28
+ declare const Image: Component<ImageComponentProps>
29
+ export default Image
@@ -0,0 +1,27 @@
1
+ <!--
2
+ Router.svelte — the reactive root for client navigation. Holds the agnostic store's snapshot in
3
+ `$state`, subscribes via `$effect` (cleaned up on unmount), and renders the matched layout chain
4
+ through `Chain.svelte`. `$derived` recomputes the chain + props on every store change, so client
5
+ navigations swap routes without a full reload. The initial snapshot matches the SSR markup (the
6
+ server rendered `Chain` for the same matched route), so `hydrate` reconciles cleanly.
7
+
8
+ Runes (`$state`/`$effect`/`$derived`) only work in compiled `.svelte` files — which is why this
9
+ reactive root is a component, not a function in `client.ts`.
10
+ -->
11
+ <script>
12
+ import Chain from "./Chain.svelte"
13
+ let { router, routes } = $props()
14
+
15
+ let snapshot = $state(router.snapshot())
16
+ $effect(() => router.subscribe(() => { snapshot = router.snapshot() }))
17
+
18
+ let chain = $derived(routes[snapshot.routeId] ?? [])
19
+ let props = $derived({
20
+ data: snapshot.data,
21
+ actionData: snapshot.actionData,
22
+ pending: snapshot.pending,
23
+ ...(snapshot.submission ? { submission: snapshot.submission } : {}),
24
+ })
25
+ </script>
26
+
27
+ <Chain {chain} {props} />
package/src/client.ts ADDED
@@ -0,0 +1,33 @@
1
+ import type { MountRouterOptions, RenderProps } from "@nifrajs/web"
2
+ /**
3
+ * @nifrajs/web-svelte/client — Svelte 5 client runtime. `hydrate` hydrates a single SSR'd route via the
4
+ * recursive `Chain`; `mountRouter` hydrates the reactive `Router` component, which subscribes to the
5
+ * agnostic store and re-renders the matched chain on every client navigation (no full reload). Both
6
+ * use Svelte's `hydrate` (reconcile against the SSR markup). Kept in its own entry so server code
7
+ * stays out of the client bundle.
8
+ */
9
+ import { hydrate as svelteHydrate } from "svelte"
10
+ import Chain from "./Chain.svelte"
11
+ import { setMountedRouter } from "./fetcher.ts"
12
+ import Router from "./Router.svelte"
13
+
14
+ // The `_error` boundary marker — Chain.svelte renders it as a <svelte:boundary>. Re-exported here so
15
+ // nifra's client codegen resolves it from `@nifrajs/web-svelte/client` alongside `mountRouter`.
16
+ export { errorBoundary } from "./error.ts"
17
+
18
+ /** Hydrate a server-rendered Svelte layout `chain` (with the loader `props`) inside `container`. */
19
+ export function hydrate(chain: readonly unknown[], props: RenderProps, container: unknown): void {
20
+ svelteHydrate(Chain, { target: container as Element, props: { chain, props } })
21
+ }
22
+
23
+ /**
24
+ * Hydrate a stateful Svelte Router. The `Router` component holds the store snapshot in `$state` and
25
+ * re-renders the matched layout chain on each store change — so client navigations swap routes without
26
+ * a full reload. Its initial render matches the SSR markup (the server rendered `Chain` for the same
27
+ * matched route), so hydration reconciles cleanly.
28
+ */
29
+ export function mountRouter(options: MountRouterOptions): void {
30
+ const { router, routes, container } = options
31
+ setMountedRouter(router) // expose it to useFetcher/useFetchers (same page, client-only)
32
+ svelteHydrate(Router, { target: container as Element, props: { router, routes } })
33
+ }