@fluixi/start 0.1.0-alpha.73 → 0.1.0-alpha.75

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 (123) hide show
  1. package/dist/adapter.cjs +750 -0
  2. package/dist/adapter.d.ts +9 -9
  3. package/dist/adapter.d.ts.map +1 -1
  4. package/dist/adapter.js +10 -10
  5. package/dist/adapter.mjs +719 -0
  6. package/dist/adapters/entry.cjs +122 -0
  7. package/dist/adapters/entry.js +1 -1
  8. package/dist/adapters/entry.mjs +95 -0
  9. package/dist/adapters/platforms.cjs +311 -0
  10. package/dist/adapters/platforms.d.ts +2 -2
  11. package/dist/adapters/platforms.js +8 -8
  12. package/dist/adapters/platforms.mjs +284 -0
  13. package/dist/api-HD7KU3BS.mjs +114 -0
  14. package/dist/api.cjs +145 -0
  15. package/dist/api.js +1 -1
  16. package/dist/api.mjs +116 -0
  17. package/dist/commands/api-HD7KU3BS.mjs +114 -0
  18. package/dist/commands/api-PBT4QZ34.mjs +16 -0
  19. package/dist/commands/api-PJL37LV3.mjs +17 -0
  20. package/dist/commands/build.cjs +877 -0
  21. package/dist/commands/build.js +6 -6
  22. package/dist/commands/build.mjs +717 -0
  23. package/dist/commands/chunk-GFQ4MUIC.mjs +117 -0
  24. package/dist/commands/chunk-MTM6M3TC.mjs +115 -0
  25. package/dist/commands/dev.cjs +670 -0
  26. package/dist/commands/dev.js +3 -3
  27. package/dist/commands/dev.mjs +512 -0
  28. package/dist/commands/index.cjs +1145 -0
  29. package/dist/commands/index.d.ts +1 -1
  30. package/dist/commands/index.js +1 -1
  31. package/dist/commands/index.mjs +982 -0
  32. package/dist/commands/prerender.cjs +404 -0
  33. package/dist/commands/prerender.d.ts +1 -1
  34. package/dist/commands/prerender.js +2 -2
  35. package/dist/commands/prerender.mjs +377 -0
  36. package/dist/commands/start.cjs +463 -0
  37. package/dist/commands/start.d.ts +1 -1
  38. package/dist/commands/start.js +2 -2
  39. package/dist/commands/start.mjs +428 -0
  40. package/dist/config.cjs +150 -0
  41. package/dist/config.d.ts +24 -7
  42. package/dist/config.d.ts.map +1 -1
  43. package/dist/config.js +8 -6
  44. package/dist/config.mjs +114 -0
  45. package/dist/define-config.cjs +32 -0
  46. package/dist/define-config.d.ts +12 -0
  47. package/dist/define-config.d.ts.map +1 -0
  48. package/dist/define-config.js +3 -0
  49. package/dist/define-config.mjs +7 -0
  50. package/dist/di.cjs +200 -0
  51. package/dist/di.d.ts +4 -4
  52. package/dist/di.d.ts.map +1 -1
  53. package/dist/di.js +9 -9
  54. package/dist/di.mjs +169 -0
  55. package/dist/document.cjs +115 -0
  56. package/dist/document.d.ts +1 -1
  57. package/dist/document.js +1 -1
  58. package/dist/document.mjs +86 -0
  59. package/dist/generated-api.cjs +43 -0
  60. package/dist/generated-api.js +1 -1
  61. package/dist/generated-api.mjs +18 -0
  62. package/dist/generated-server-fns.cjs +43 -0
  63. package/dist/generated-server-fns.mjs +18 -0
  64. package/dist/handler-core.cjs +211 -0
  65. package/dist/handler-core.d.ts +2 -2
  66. package/dist/handler-core.js +2 -2
  67. package/dist/handler-core.mjs +185 -0
  68. package/dist/handler.cjs +182 -0
  69. package/dist/handler.d.ts +3 -3
  70. package/dist/handler.js +3 -3
  71. package/dist/handler.mjs +152 -0
  72. package/dist/head.cjs +25 -0
  73. package/dist/head.d.ts +1 -1
  74. package/dist/head.js +1 -1
  75. package/dist/head.mjs +4 -0
  76. package/dist/image-loader.cjs +37 -0
  77. package/dist/image-loader.d.ts +25 -0
  78. package/dist/image-loader.d.ts.map +1 -0
  79. package/dist/image-loader.js +18 -0
  80. package/dist/image-loader.mjs +12 -0
  81. package/dist/image.cjs +151 -0
  82. package/dist/image.d.ts +2 -19
  83. package/dist/image.d.ts.map +1 -1
  84. package/dist/image.js +5 -21
  85. package/dist/image.mjs +126 -0
  86. package/dist/index.cjs +303 -0
  87. package/dist/index.d.ts +28 -8
  88. package/dist/index.d.ts.map +1 -1
  89. package/dist/index.js +33 -17
  90. package/dist/index.mjs +262 -0
  91. package/dist/interceptors.cjs +72 -0
  92. package/dist/interceptors.js +1 -1
  93. package/dist/interceptors.mjs +44 -0
  94. package/dist/internal.cjs +393 -0
  95. package/dist/internal.d.ts +6 -6
  96. package/dist/internal.d.ts.map +1 -1
  97. package/dist/internal.js +23 -15
  98. package/dist/internal.mjs +223 -0
  99. package/dist/middleware.cjs +58 -0
  100. package/dist/middleware.d.ts +3 -3
  101. package/dist/middleware.d.ts.map +1 -1
  102. package/dist/middleware.js +1 -1
  103. package/dist/middleware.mjs +31 -0
  104. package/dist/preload.cjs +42 -0
  105. package/dist/preload.js +1 -1
  106. package/dist/preload.mjs +18 -0
  107. package/dist/router.cjs +25 -0
  108. package/dist/router.d.ts +3 -3
  109. package/dist/router.js +3 -3
  110. package/dist/router.mjs +4 -0
  111. package/dist/server-fn-setup.cjs +46 -0
  112. package/dist/server-fn-setup.d.ts +1 -1
  113. package/dist/server-fn-setup.js +2 -2
  114. package/dist/server-fn-setup.mjs +22 -0
  115. package/dist/server-fn.cjs +109 -0
  116. package/dist/server-fn.d.ts +2 -2
  117. package/dist/server-fn.js +2 -2
  118. package/dist/server-fn.mjs +79 -0
  119. package/dist/tsconfig.lib.tsbuildinfo +1 -1
  120. package/dist/ui.cjs +123 -0
  121. package/dist/ui.js +1 -1
  122. package/dist/ui.mjs +93 -0
  123. package/package.json +63 -25
@@ -0,0 +1,152 @@
1
+ /*! @fluixi/start v0.1.0-alpha.75 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+
3
+ // src/handler.ts
4
+ import { createServer as createHttpServer } from "node:http";
5
+ import { stat } from "node:fs/promises";
6
+ import { createReadStream } from "node:fs";
7
+ import { Readable } from "node:stream";
8
+ import { join, extname } from "node:path";
9
+ function streamDocument(head, body, tail) {
10
+ const enc = new TextEncoder();
11
+ const stream = new ReadableStream({
12
+ async start(controller) {
13
+ try {
14
+ controller.enqueue(enc.encode(head));
15
+ const reader = body.getReader();
16
+ for (; ; ) {
17
+ const { done, value } = await reader.read();
18
+ if (done) break;
19
+ controller.enqueue(value);
20
+ }
21
+ controller.enqueue(enc.encode(tail));
22
+ controller.close();
23
+ } catch (err) {
24
+ controller.error(err);
25
+ }
26
+ }
27
+ });
28
+ return new Response(stream, {
29
+ status: 200,
30
+ headers: { "content-type": "text/html; charset=utf-8" }
31
+ });
32
+ }
33
+ function createRequestHandler(render) {
34
+ return async (request) => {
35
+ const url = new URL(request.url);
36
+ try {
37
+ const html = await render(url.pathname + url.search, request);
38
+ return new Response(html, {
39
+ status: 200,
40
+ headers: { "content-type": "text/html; charset=utf-8" }
41
+ });
42
+ } catch (e) {
43
+ return new Response(String(e?.stack || e), {
44
+ status: 500,
45
+ headers: { "content-type": "text/plain; charset=utf-8" }
46
+ });
47
+ }
48
+ };
49
+ }
50
+ function nodeToRequest(req) {
51
+ const host = req.headers.host ?? "localhost";
52
+ const url = `http://${host}${req.url ?? "/"}`;
53
+ const headers = new Headers();
54
+ for (const [k, v] of Object.entries(req.headers)) {
55
+ if (Array.isArray(v)) v.forEach((x) => headers.append(k, x));
56
+ else if (v != null) headers.set(k, v);
57
+ }
58
+ const method = req.method ?? "GET";
59
+ const init = { method, headers };
60
+ if (method !== "GET" && method !== "HEAD") {
61
+ init.body = req;
62
+ init.duplex = "half";
63
+ }
64
+ return new Request(url, init);
65
+ }
66
+ async function sendResponse(res, response) {
67
+ res.statusCode = response.status;
68
+ response.headers.forEach((value, key) => res.setHeader(key, value));
69
+ const body = response.body;
70
+ if (!body) {
71
+ res.end(await response.text().catch(() => ""));
72
+ return;
73
+ }
74
+ const reader = body.getReader();
75
+ try {
76
+ for (; ; ) {
77
+ const { done, value } = await reader.read();
78
+ if (done) break;
79
+ res.write(value);
80
+ }
81
+ } finally {
82
+ res.end();
83
+ }
84
+ }
85
+ function toNodeHandler(handler) {
86
+ return async (req, res) => {
87
+ const response = await handler(nodeToRequest(req));
88
+ await sendResponse(res, response);
89
+ };
90
+ }
91
+ function serveNode(handler, opts = {}) {
92
+ const port = opts.port ?? 3e3;
93
+ const node = toNodeHandler(handler);
94
+ const server = createHttpServer((req, res) => {
95
+ node(req, res).catch((e) => {
96
+ res.statusCode = 500;
97
+ res.end(String(e?.stack || e));
98
+ });
99
+ });
100
+ server.listen(
101
+ port,
102
+ () => opts.onListen ? opts.onListen(port) : console.log(` fluixi → http://localhost:${port}`)
103
+ );
104
+ return server;
105
+ }
106
+ var MIME = {
107
+ ".js": "text/javascript",
108
+ ".mjs": "text/javascript",
109
+ ".css": "text/css",
110
+ ".html": "text/html",
111
+ ".json": "application/json",
112
+ ".svg": "image/svg+xml",
113
+ ".png": "image/png",
114
+ ".jpg": "image/jpeg",
115
+ ".jpeg": "image/jpeg",
116
+ ".webp": "image/webp",
117
+ ".ico": "image/x-icon",
118
+ ".woff": "font/woff",
119
+ ".woff2": "font/woff2",
120
+ ".map": "application/json"
121
+ };
122
+ function withStaticFiles(handler, dir) {
123
+ return async (request) => {
124
+ const { pathname } = new URL(request.url);
125
+ if (pathname !== "/" && extname(pathname)) {
126
+ const file = join(dir, decodeURIComponent(pathname));
127
+ try {
128
+ const s = await stat(file);
129
+ if (s.isFile()) {
130
+ const body = Readable.toWeb(createReadStream(file));
131
+ return new Response(body, {
132
+ headers: {
133
+ "content-type": MIME[extname(file).toLowerCase()] ?? "application/octet-stream",
134
+ "content-length": String(s.size)
135
+ }
136
+ });
137
+ }
138
+ } catch {
139
+ }
140
+ }
141
+ return handler(request);
142
+ };
143
+ }
144
+ export {
145
+ createRequestHandler,
146
+ nodeToRequest,
147
+ sendResponse,
148
+ serveNode,
149
+ streamDocument,
150
+ toNodeHandler,
151
+ withStaticFiles
152
+ };
package/dist/head.cjs ADDED
@@ -0,0 +1,25 @@
1
+ /*! @fluixi/start v0.1.0-alpha.75 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __copyProps = (to, from, except, desc) => {
8
+ if (from && typeof from === "object" || typeof from === "function") {
9
+ for (let key of __getOwnPropNames(from))
10
+ if (!__hasOwnProp.call(to, key) && key !== except)
11
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
12
+ }
13
+ return to;
14
+ };
15
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget && __copyProps(secondTarget, mod, "default"));
16
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
17
+
18
+ // src/head.ts
19
+ var head_exports = {};
20
+ module.exports = __toCommonJS(head_exports);
21
+ __reExport(head_exports, require("@fluixi/head"), module.exports);
22
+ // Annotate the CommonJS export names for ESM import in node:
23
+ 0 && (module.exports = {
24
+ ...require("@fluixi/head")
25
+ });
package/dist/head.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@fluixi/start/head` the app-facing SEO & document-head surface, re-exported from the
2
+ * `@fluixi/start/head`: the app-facing SEO & document-head surface, re-exported from the
3
3
  * framework-agnostic `@fluixi/head` engine. Importing from the meta-framework gives apps a single,
4
4
  * stable dependency (like `@fluixi/start/router`) instead of reaching into the engine directly:
5
5
  *
package/dist/head.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `@fluixi/start/head` the app-facing SEO & document-head surface, re-exported from the
2
+ * `@fluixi/start/head`: the app-facing SEO & document-head surface, re-exported from the
3
3
  * framework-agnostic `@fluixi/head` engine. Importing from the meta-framework gives apps a single,
4
4
  * stable dependency (like `@fluixi/start/router`) instead of reaching into the engine directly:
5
5
  *
package/dist/head.mjs ADDED
@@ -0,0 +1,4 @@
1
+ /*! @fluixi/start v0.1.0-alpha.75 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+
3
+ // src/head.ts
4
+ export * from "@fluixi/head";
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/image-loader.ts
21
+ var image_loader_exports = {};
22
+ __export(image_loader_exports, {
23
+ imageLoader: () => imageLoader
24
+ });
25
+ module.exports = __toCommonJS(image_loader_exports);
26
+ function imageLoader(options = {}) {
27
+ const path = options.path ?? "/api/image";
28
+ return ({ src, width, quality }) => {
29
+ const params = new URLSearchParams({ url: src, w: String(width) });
30
+ if (quality != null) params.set("q", String(quality));
31
+ return `${path}?${params.toString()}`;
32
+ };
33
+ }
34
+ // Annotate the CommonJS export names for ESM import in node:
35
+ 0 && (module.exports = {
36
+ imageLoader
37
+ });
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The client half of the image support.
3
+ *
4
+ * `imageLoader` builds a URL; `createImageHandler` reads files and transforms them. Only
5
+ * the first belongs in a page, so it lives apart from the half that imports `node:fs`.
6
+ */
7
+ export interface ImageLoaderOptions {
8
+ /** Path the image handler route is mounted at. Default `/api/image`. */
9
+ path?: string;
10
+ }
11
+ /**
12
+ * A loader for `@fluixi-ui/image` that targets a `createImageHandler` route.
13
+ *
14
+ * ```tsx
15
+ * import { imageLoader } from '@fluixi/start';
16
+ * const loader = imageLoader({ path: '/api/image' });
17
+ * <Image src="/mug.jpg" loader={loader} widths={[120, 240, 480]} />
18
+ * ```
19
+ */
20
+ export declare function imageLoader(options?: ImageLoaderOptions): (args: {
21
+ src: string;
22
+ width: number;
23
+ quality?: number;
24
+ }) => string;
25
+ //# sourceMappingURL=image-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"image-loader.d.ts","sourceRoot":"","sources":["../src/image-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC,wEAAwE;IACxE,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,OAAO,GAAE,kBAAuB,GAC/B,CAAC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,MAAM,CAOpE"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * A loader for `@fluixi-ui/image` that targets a `createImageHandler` route.
3
+ *
4
+ * ```tsx
5
+ * import { imageLoader } from '@fluixi/start';
6
+ * const loader = imageLoader({ path: '/api/image' });
7
+ * <Image src="/mug.jpg" loader={loader} widths={[120, 240, 480]} />
8
+ * ```
9
+ */
10
+ export function imageLoader(options = {}) {
11
+ const path = options.path ?? '/api/image';
12
+ return ({ src, width, quality }) => {
13
+ const params = new URLSearchParams({ url: src, w: String(width) });
14
+ if (quality != null)
15
+ params.set('q', String(quality));
16
+ return `${path}?${params.toString()}`;
17
+ };
18
+ }
@@ -0,0 +1,12 @@
1
+ // src/image-loader.ts
2
+ function imageLoader(options = {}) {
3
+ const path = options.path ?? "/api/image";
4
+ return ({ src, width, quality }) => {
5
+ const params = new URLSearchParams({ url: src, w: String(width) });
6
+ if (quality != null) params.set("q", String(quality));
7
+ return `${path}?${params.toString()}`;
8
+ };
9
+ }
10
+ export {
11
+ imageLoader
12
+ };
package/dist/image.cjs ADDED
@@ -0,0 +1,151 @@
1
+ /*! @fluixi/start v0.1.0-alpha.75 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+ "use strict";
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
20
+
21
+ // src/image.ts
22
+ var image_exports = {};
23
+ __export(image_exports, {
24
+ createImageHandler: () => createImageHandler,
25
+ imageLoader: () => imageLoader
26
+ });
27
+ module.exports = __toCommonJS(image_exports);
28
+ var import_promises = require("node:fs/promises");
29
+ var import_node_path = require("node:path");
30
+
31
+ // src/image-loader.ts
32
+ function imageLoader(options = {}) {
33
+ const path = options.path ?? "/api/image";
34
+ return ({ src, width, quality }) => {
35
+ const params = new URLSearchParams({ url: src, w: String(width) });
36
+ if (quality != null) params.set("q", String(quality));
37
+ return `${path}?${params.toString()}`;
38
+ };
39
+ }
40
+
41
+ // src/image.ts
42
+ var DEFAULT_SIZES = [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840];
43
+ var MIME = {
44
+ ".jpg": "image/jpeg",
45
+ ".jpeg": "image/jpeg",
46
+ ".png": "image/png",
47
+ ".webp": "image/webp",
48
+ ".avif": "image/avif",
49
+ ".gif": "image/gif",
50
+ ".svg": "image/svg+xml"
51
+ };
52
+ var sharpModule;
53
+ async function loadSharp() {
54
+ if (sharpModule !== void 0) return sharpModule;
55
+ try {
56
+ const name = "sharp";
57
+ sharpModule = (await import(
58
+ /* @vite-ignore */
59
+ name
60
+ )).default;
61
+ } catch {
62
+ sharpModule = null;
63
+ }
64
+ return sharpModule;
65
+ }
66
+ var defaultTransform = async (input, { width, quality, format }) => {
67
+ const sharp = await loadSharp();
68
+ if (!sharp) return null;
69
+ const pipe = sharp(input).rotate().resize({ width, withoutEnlargement: true });
70
+ if (format === "avif") return { data: new Uint8Array(await pipe.avif({ quality }).toBuffer()), contentType: "image/avif" };
71
+ if (format === "webp") return { data: new Uint8Array(await pipe.webp({ quality }).toBuffer()), contentType: "image/webp" };
72
+ if (format === "png") return { data: new Uint8Array(await pipe.png().toBuffer()), contentType: "image/png" };
73
+ return { data: new Uint8Array(await pipe.jpeg({ quality, mozjpeg: true }).toBuffer()), contentType: "image/jpeg" };
74
+ };
75
+ function imageResponse(body, contentType, maxAge) {
76
+ return new Response(body, {
77
+ headers: {
78
+ "content-type": contentType,
79
+ "cache-control": `public, max-age=${maxAge}, immutable`,
80
+ vary: "Accept"
81
+ }
82
+ });
83
+ }
84
+ function createImageHandler(options = {}) {
85
+ const root = (0, import_node_path.resolve)(options.root ?? (0, import_node_path.join)(process.cwd(), "public"));
86
+ const domains = new Set((options.domains ?? []).map((d) => d.toLowerCase()));
87
+ const sizes = options.sizes ?? DEFAULT_SIZES;
88
+ const maxQuality = options.quality ?? 80;
89
+ const maxAge = options.cacheMaxAge ?? 60 * 60 * 24 * 365;
90
+ const transform = options.transform ?? defaultTransform;
91
+ const cacheMax = options.cache === false ? 0 : typeof options.cache === "number" ? options.cache : 100;
92
+ const cache = /* @__PURE__ */ new Map();
93
+ return async (request) => {
94
+ const url = new URL(request.url);
95
+ const src = url.searchParams.get("url") ?? url.searchParams.get("src");
96
+ const width = Number(url.searchParams.get("w"));
97
+ const quality = Math.min(Math.max(Number(url.searchParams.get("q")) || maxQuality, 1), 100);
98
+ if (!src) return new Response('missing "url"', { status: 400 });
99
+ if (!Number.isInteger(width) || !sizes.includes(width)) return new Response('invalid "w"', { status: 400 });
100
+ const accept = request.headers.get("accept") ?? "";
101
+ const format = accept.includes("image/avif") ? "avif" : accept.includes("image/webp") ? "webp" : "original";
102
+ const key = `${src}|${width}|${quality}|${format}`;
103
+ const hit = cache.get(key);
104
+ if (hit) return imageResponse(hit.body, hit.contentType, maxAge);
105
+ let input;
106
+ let sourceType;
107
+ if (/^https?:\/\//i.test(src)) {
108
+ let host;
109
+ try {
110
+ host = new URL(src).hostname.toLowerCase();
111
+ } catch {
112
+ return new Response("bad url", { status: 400 });
113
+ }
114
+ if (!domains.has(host)) return new Response("domain not allowed", { status: 403 });
115
+ const res = await fetch(src);
116
+ if (!res.ok) return new Response("upstream error", { status: 502 });
117
+ input = new Uint8Array(await res.arrayBuffer());
118
+ sourceType = res.headers.get("content-type") ?? "application/octet-stream";
119
+ } else {
120
+ const filePath = (0, import_node_path.resolve)(root, src.startsWith("/") ? src.slice(1) : src);
121
+ if (filePath !== root && !filePath.startsWith(root + import_node_path.sep)) return new Response("forbidden", { status: 403 });
122
+ try {
123
+ input = new Uint8Array(await (0, import_promises.readFile)(filePath));
124
+ } catch {
125
+ return new Response("not found", { status: 404 });
126
+ }
127
+ sourceType = MIME[(0, import_node_path.extname)(filePath).toLowerCase()] ?? "application/octet-stream";
128
+ }
129
+ if (sourceType === "image/svg+xml" || sourceType === "image/gif") {
130
+ return imageResponse(input, sourceType, maxAge);
131
+ }
132
+ let out = null;
133
+ try {
134
+ out = await transform(input, { width, quality, format, contentType: sourceType });
135
+ } catch {
136
+ out = null;
137
+ }
138
+ const body = out?.data ?? input;
139
+ const contentType = out?.contentType ?? sourceType;
140
+ if (cacheMax > 0) {
141
+ if (cache.size >= cacheMax) cache.delete(cache.keys().next().value);
142
+ cache.set(key, { body, contentType });
143
+ }
144
+ return imageResponse(body, contentType, maxAge);
145
+ };
146
+ }
147
+ // Annotate the CommonJS export names for ESM import in node:
148
+ 0 && (module.exports = {
149
+ createImageHandler,
150
+ imageLoader
151
+ });
package/dist/image.d.ts CHANGED
@@ -35,7 +35,7 @@ export interface ImageHandlerOptions {
35
35
  *
36
36
  * ```ts
37
37
  * // src/api/image.ts
38
- * import { createImageHandler } from '@fluixi/start';
38
+ * import { createImageHandler } from '@fluixi/start/image';
39
39
  * export const GET = createImageHandler({ root: 'public', domains: ['cdn.example.com'] });
40
40
  * ```
41
41
  *
@@ -43,22 +43,5 @@ export interface ImageHandlerOptions {
43
43
  * through unchanged (so it's safe to ship either way).
44
44
  */
45
45
  export declare function createImageHandler(options?: ImageHandlerOptions): (request: Request) => Promise<Response>;
46
- export interface ImageLoaderOptions {
47
- /** Path the {@link createImageHandler} route is mounted at. Default `/api/image`. */
48
- path?: string;
49
- }
50
- /**
51
- * A loader for `@fluixi-ui/image` that targets a {@link createImageHandler} route.
52
- *
53
- * ```tsx
54
- * import { imageLoader } from '@fluixi/start';
55
- * const loader = imageLoader({ path: '/api/image' });
56
- * <Image src="/mug.jpg" loader={loader} widths={[120, 240, 480]} />
57
- * ```
58
- */
59
- export declare function imageLoader(options?: ImageLoaderOptions): (args: {
60
- src: string;
61
- width: number;
62
- quality?: number;
63
- }) => string;
46
+ export { imageLoader, type ImageLoaderOptions } from './image-loader.js';
64
47
  //# sourceMappingURL=image.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;IACtD,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,sFAAsF;AACtF,MAAM,MAAM,cAAc,GAAG,CAC3B,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,kBAAkB,KACrB,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAAC;AAE/D,MAAM,WAAW,mBAAmB;IAClC,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,2FAA2F;IAC3F,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAyCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAoE7G;AAED,MAAM,WAAW,kBAAkB;IACjC,qFAAqF;IACrF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,CAAC,IAAI,EAAE;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KAAK,MAAM,CAOhI"}
1
+ {"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../src/image.ts"],"names":[],"mappings":"AAQA,MAAM,WAAW,kBAAkB;IACjC,0BAA0B;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,qBAAqB;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,GAAG,KAAK,GAAG,UAAU,CAAC;IACtD,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,sFAAsF;AACtF,MAAM,MAAM,cAAc,GAAG,CAC3B,KAAK,EAAE,UAAU,EACjB,IAAI,EAAE,kBAAkB,KACrB,OAAO,CAAC;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CAAC,CAAC;AAE/D,MAAM,WAAW,mBAAmB;IAClC,2EAA2E;IAC3E,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,uEAAuE;IACvE,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,wEAAwE;IACxE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,SAAS,CAAC,EAAE,cAAc,CAAC;IAC3B,2FAA2F;IAC3F,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CAC1B;AAyCD;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAoE7G;AAGD,OAAO,EAAE,WAAW,EAAE,KAAK,kBAAkB,EAAE,MAAM,mBAAmB,CAAC"}
package/dist/image.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // On-demand image optimization: a Web `Request` → `Response` handler that resizes
2
2
  // and re-encodes images (to AVIF/WebP by content negotiation), plus a matching
3
- // `imageLoader` for `@fluixi-ui/image`. The actual codec is pluggable the
3
+ // `imageLoader` for `@fluixi-ui/image`. The actual codec is pluggable, the
4
4
  // default uses `sharp` if it's installed, otherwise it streams the source through
5
5
  // unchanged (so the framework never hard-depends on a native module).
6
6
  import { readFile } from 'node:fs/promises';
@@ -52,7 +52,7 @@ function imageResponse(body, contentType, maxAge) {
52
52
  *
53
53
  * ```ts
54
54
  * // src/api/image.ts
55
- * import { createImageHandler } from '@fluixi/start';
55
+ * import { createImageHandler } from '@fluixi/start/image';
56
56
  * export const GET = createImageHandler({ root: 'public', domains: ['cdn.example.com'] });
57
57
  * ```
58
58
  *
@@ -113,7 +113,7 @@ export function createImageHandler(options = {}) {
113
113
  }
114
114
  sourceType = MIME[extname(filePath).toLowerCase()] ?? 'application/octet-stream';
115
115
  }
116
- // Vector / animated sources aren't rasterized stream them through.
116
+ // Vector / animated sources aren't rasterized, stream them through.
117
117
  if (sourceType === 'image/svg+xml' || sourceType === 'image/gif') {
118
118
  return imageResponse(input, sourceType, maxAge);
119
119
  }
@@ -134,21 +134,5 @@ export function createImageHandler(options = {}) {
134
134
  return imageResponse(body, contentType, maxAge);
135
135
  };
136
136
  }
137
- /**
138
- * A loader for `@fluixi-ui/image` that targets a {@link createImageHandler} route.
139
- *
140
- * ```tsx
141
- * import { imageLoader } from '@fluixi/start';
142
- * const loader = imageLoader({ path: '/api/image' });
143
- * <Image src="/mug.jpg" loader={loader} widths={[120, 240, 480]} />
144
- * ```
145
- */
146
- export function imageLoader(options = {}) {
147
- const path = options.path ?? '/api/image';
148
- return ({ src, width, quality }) => {
149
- const params = new URLSearchParams({ url: src, w: String(width) });
150
- if (quality != null)
151
- params.set('q', String(quality));
152
- return `${path}?${params.toString()}`;
153
- };
154
- }
137
+ // The loader lives apart, so a page can import it without this module's `node:fs`.
138
+ export { imageLoader } from './image-loader.js';
package/dist/image.mjs ADDED
@@ -0,0 +1,126 @@
1
+ /*! @fluixi/start v0.1.0-alpha.75 | (c) 2026 Ibrahima Touré and Fluixi contributors | MIT */
2
+
3
+ // src/image.ts
4
+ import { readFile } from "node:fs/promises";
5
+ import { resolve as resolvePath, join, extname, sep } from "node:path";
6
+
7
+ // src/image-loader.ts
8
+ function imageLoader(options = {}) {
9
+ const path = options.path ?? "/api/image";
10
+ return ({ src, width, quality }) => {
11
+ const params = new URLSearchParams({ url: src, w: String(width) });
12
+ if (quality != null) params.set("q", String(quality));
13
+ return `${path}?${params.toString()}`;
14
+ };
15
+ }
16
+
17
+ // src/image.ts
18
+ var DEFAULT_SIZES = [16, 32, 48, 64, 96, 128, 256, 384, 640, 750, 828, 1080, 1200, 1920, 2048, 3840];
19
+ var MIME = {
20
+ ".jpg": "image/jpeg",
21
+ ".jpeg": "image/jpeg",
22
+ ".png": "image/png",
23
+ ".webp": "image/webp",
24
+ ".avif": "image/avif",
25
+ ".gif": "image/gif",
26
+ ".svg": "image/svg+xml"
27
+ };
28
+ var sharpModule;
29
+ async function loadSharp() {
30
+ if (sharpModule !== void 0) return sharpModule;
31
+ try {
32
+ const name = "sharp";
33
+ sharpModule = (await import(
34
+ /* @vite-ignore */
35
+ name
36
+ )).default;
37
+ } catch {
38
+ sharpModule = null;
39
+ }
40
+ return sharpModule;
41
+ }
42
+ var defaultTransform = async (input, { width, quality, format }) => {
43
+ const sharp = await loadSharp();
44
+ if (!sharp) return null;
45
+ const pipe = sharp(input).rotate().resize({ width, withoutEnlargement: true });
46
+ if (format === "avif") return { data: new Uint8Array(await pipe.avif({ quality }).toBuffer()), contentType: "image/avif" };
47
+ if (format === "webp") return { data: new Uint8Array(await pipe.webp({ quality }).toBuffer()), contentType: "image/webp" };
48
+ if (format === "png") return { data: new Uint8Array(await pipe.png().toBuffer()), contentType: "image/png" };
49
+ return { data: new Uint8Array(await pipe.jpeg({ quality, mozjpeg: true }).toBuffer()), contentType: "image/jpeg" };
50
+ };
51
+ function imageResponse(body, contentType, maxAge) {
52
+ return new Response(body, {
53
+ headers: {
54
+ "content-type": contentType,
55
+ "cache-control": `public, max-age=${maxAge}, immutable`,
56
+ vary: "Accept"
57
+ }
58
+ });
59
+ }
60
+ function createImageHandler(options = {}) {
61
+ const root = resolvePath(options.root ?? join(process.cwd(), "public"));
62
+ const domains = new Set((options.domains ?? []).map((d) => d.toLowerCase()));
63
+ const sizes = options.sizes ?? DEFAULT_SIZES;
64
+ const maxQuality = options.quality ?? 80;
65
+ const maxAge = options.cacheMaxAge ?? 60 * 60 * 24 * 365;
66
+ const transform = options.transform ?? defaultTransform;
67
+ const cacheMax = options.cache === false ? 0 : typeof options.cache === "number" ? options.cache : 100;
68
+ const cache = /* @__PURE__ */ new Map();
69
+ return async (request) => {
70
+ const url = new URL(request.url);
71
+ const src = url.searchParams.get("url") ?? url.searchParams.get("src");
72
+ const width = Number(url.searchParams.get("w"));
73
+ const quality = Math.min(Math.max(Number(url.searchParams.get("q")) || maxQuality, 1), 100);
74
+ if (!src) return new Response('missing "url"', { status: 400 });
75
+ if (!Number.isInteger(width) || !sizes.includes(width)) return new Response('invalid "w"', { status: 400 });
76
+ const accept = request.headers.get("accept") ?? "";
77
+ const format = accept.includes("image/avif") ? "avif" : accept.includes("image/webp") ? "webp" : "original";
78
+ const key = `${src}|${width}|${quality}|${format}`;
79
+ const hit = cache.get(key);
80
+ if (hit) return imageResponse(hit.body, hit.contentType, maxAge);
81
+ let input;
82
+ let sourceType;
83
+ if (/^https?:\/\//i.test(src)) {
84
+ let host;
85
+ try {
86
+ host = new URL(src).hostname.toLowerCase();
87
+ } catch {
88
+ return new Response("bad url", { status: 400 });
89
+ }
90
+ if (!domains.has(host)) return new Response("domain not allowed", { status: 403 });
91
+ const res = await fetch(src);
92
+ if (!res.ok) return new Response("upstream error", { status: 502 });
93
+ input = new Uint8Array(await res.arrayBuffer());
94
+ sourceType = res.headers.get("content-type") ?? "application/octet-stream";
95
+ } else {
96
+ const filePath = resolvePath(root, src.startsWith("/") ? src.slice(1) : src);
97
+ if (filePath !== root && !filePath.startsWith(root + sep)) return new Response("forbidden", { status: 403 });
98
+ try {
99
+ input = new Uint8Array(await readFile(filePath));
100
+ } catch {
101
+ return new Response("not found", { status: 404 });
102
+ }
103
+ sourceType = MIME[extname(filePath).toLowerCase()] ?? "application/octet-stream";
104
+ }
105
+ if (sourceType === "image/svg+xml" || sourceType === "image/gif") {
106
+ return imageResponse(input, sourceType, maxAge);
107
+ }
108
+ let out = null;
109
+ try {
110
+ out = await transform(input, { width, quality, format, contentType: sourceType });
111
+ } catch {
112
+ out = null;
113
+ }
114
+ const body = out?.data ?? input;
115
+ const contentType = out?.contentType ?? sourceType;
116
+ if (cacheMax > 0) {
117
+ if (cache.size >= cacheMax) cache.delete(cache.keys().next().value);
118
+ cache.set(key, { body, contentType });
119
+ }
120
+ return imageResponse(body, contentType, maxAge);
121
+ };
122
+ }
123
+ export {
124
+ createImageHandler,
125
+ imageLoader
126
+ };