alabjs 0.1.0
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.
- package/dist/adapters/cloudflare.d.ts +31 -0
- package/dist/adapters/cloudflare.d.ts.map +1 -0
- package/dist/adapters/cloudflare.js +30 -0
- package/dist/adapters/cloudflare.js.map +1 -0
- package/dist/adapters/deno.d.ts +22 -0
- package/dist/adapters/deno.d.ts.map +1 -0
- package/dist/adapters/deno.js +21 -0
- package/dist/adapters/deno.js.map +1 -0
- package/dist/adapters/web.d.ts +47 -0
- package/dist/adapters/web.d.ts.map +1 -0
- package/dist/adapters/web.js +212 -0
- package/dist/adapters/web.js.map +1 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +61 -0
- package/dist/cli.js.map +1 -0
- package/dist/client/hooks.d.ts +119 -0
- package/dist/client/hooks.d.ts.map +1 -0
- package/dist/client/hooks.js +220 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client/hooks.test.d.ts +2 -0
- package/dist/client/hooks.test.d.ts.map +1 -0
- package/dist/client/hooks.test.js +45 -0
- package/dist/client/hooks.test.js.map +1 -0
- package/dist/client/index.d.ts +6 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/offline.d.ts +52 -0
- package/dist/client/offline.d.ts.map +1 -0
- package/dist/client/offline.js +90 -0
- package/dist/client/offline.js.map +1 -0
- package/dist/client/provider.d.ts +12 -0
- package/dist/client/provider.d.ts.map +1 -0
- package/dist/client/provider.js +10 -0
- package/dist/client/provider.js.map +1 -0
- package/dist/commands/build.d.ts +18 -0
- package/dist/commands/build.d.ts.map +1 -0
- package/dist/commands/build.js +173 -0
- package/dist/commands/build.js.map +1 -0
- package/dist/commands/dev.d.ts +8 -0
- package/dist/commands/dev.d.ts.map +1 -0
- package/dist/commands/dev.js +447 -0
- package/dist/commands/dev.js.map +1 -0
- package/dist/commands/info.d.ts +6 -0
- package/dist/commands/info.d.ts.map +1 -0
- package/dist/commands/info.js +92 -0
- package/dist/commands/info.js.map +1 -0
- package/dist/commands/ssg.d.ts +8 -0
- package/dist/commands/ssg.d.ts.map +1 -0
- package/dist/commands/ssg.js +124 -0
- package/dist/commands/ssg.js.map +1 -0
- package/dist/commands/start.d.ts +7 -0
- package/dist/commands/start.d.ts.map +1 -0
- package/dist/commands/start.js +26 -0
- package/dist/commands/start.js.map +1 -0
- package/dist/commands/test.d.ts +24 -0
- package/dist/commands/test.d.ts.map +1 -0
- package/dist/commands/test.js +87 -0
- package/dist/commands/test.js.map +1 -0
- package/dist/components/ErrorBoundary.d.ts +38 -0
- package/dist/components/ErrorBoundary.d.ts.map +1 -0
- package/dist/components/ErrorBoundary.js +46 -0
- package/dist/components/ErrorBoundary.js.map +1 -0
- package/dist/components/Font.d.ts +57 -0
- package/dist/components/Font.d.ts.map +1 -0
- package/dist/components/Font.js +33 -0
- package/dist/components/Font.js.map +1 -0
- package/dist/components/Image.d.ts +74 -0
- package/dist/components/Image.d.ts.map +1 -0
- package/dist/components/Image.js +85 -0
- package/dist/components/Image.js.map +1 -0
- package/dist/components/Link.d.ts +23 -0
- package/dist/components/Link.d.ts.map +1 -0
- package/dist/components/Link.js +48 -0
- package/dist/components/Link.js.map +1 -0
- package/dist/components/Script.d.ts +37 -0
- package/dist/components/Script.d.ts.map +1 -0
- package/dist/components/Script.js +70 -0
- package/dist/components/Script.js.map +1 -0
- package/dist/components/index.d.ts +10 -0
- package/dist/components/index.d.ts.map +1 -0
- package/dist/components/index.js +6 -0
- package/dist/components/index.js.map +1 -0
- package/dist/i18n/i18n.test.d.ts +2 -0
- package/dist/i18n/i18n.test.d.ts.map +1 -0
- package/dist/i18n/i18n.test.js +132 -0
- package/dist/i18n/i18n.test.js.map +1 -0
- package/dist/i18n/index.d.ts +135 -0
- package/dist/i18n/index.d.ts.map +1 -0
- package/dist/i18n/index.js +189 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/router/code-router.d.ts +204 -0
- package/dist/router/code-router.d.ts.map +1 -0
- package/dist/router/code-router.js +258 -0
- package/dist/router/code-router.js.map +1 -0
- package/dist/router/code-router.test.d.ts +2 -0
- package/dist/router/code-router.test.d.ts.map +1 -0
- package/dist/router/code-router.test.js +128 -0
- package/dist/router/code-router.test.js.map +1 -0
- package/dist/router/index.d.ts +4 -0
- package/dist/router/index.d.ts.map +1 -0
- package/dist/router/index.js +2 -0
- package/dist/router/index.js.map +1 -0
- package/dist/router/manifest.d.ts +12 -0
- package/dist/router/manifest.d.ts.map +1 -0
- package/dist/router/manifest.js +2 -0
- package/dist/router/manifest.js.map +1 -0
- package/dist/server/app.d.ts +13 -0
- package/dist/server/app.d.ts.map +1 -0
- package/dist/server/app.js +407 -0
- package/dist/server/app.js.map +1 -0
- package/dist/server/cache.d.ts +99 -0
- package/dist/server/cache.d.ts.map +1 -0
- package/dist/server/cache.js +161 -0
- package/dist/server/cache.js.map +1 -0
- package/dist/server/cache.test.d.ts +2 -0
- package/dist/server/cache.test.d.ts.map +1 -0
- package/dist/server/cache.test.js +150 -0
- package/dist/server/cache.test.js.map +1 -0
- package/dist/server/csrf.d.ts +28 -0
- package/dist/server/csrf.d.ts.map +1 -0
- package/dist/server/csrf.js +66 -0
- package/dist/server/csrf.js.map +1 -0
- package/dist/server/csrf.test.d.ts +2 -0
- package/dist/server/csrf.test.d.ts.map +1 -0
- package/dist/server/csrf.test.js +154 -0
- package/dist/server/csrf.test.js.map +1 -0
- package/dist/server/image.d.ts +18 -0
- package/dist/server/image.d.ts.map +1 -0
- package/dist/server/image.js +97 -0
- package/dist/server/image.js.map +1 -0
- package/dist/server/index.d.ts +57 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +58 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/middleware.d.ts +53 -0
- package/dist/server/middleware.d.ts.map +1 -0
- package/dist/server/middleware.js +80 -0
- package/dist/server/middleware.js.map +1 -0
- package/dist/server/middleware.test.d.ts +2 -0
- package/dist/server/middleware.test.d.ts.map +1 -0
- package/dist/server/middleware.test.js +125 -0
- package/dist/server/middleware.test.js.map +1 -0
- package/dist/server/revalidate.d.ts +49 -0
- package/dist/server/revalidate.d.ts.map +1 -0
- package/dist/server/revalidate.js +62 -0
- package/dist/server/revalidate.js.map +1 -0
- package/dist/server/revalidate.test.d.ts +2 -0
- package/dist/server/revalidate.test.d.ts.map +1 -0
- package/dist/server/revalidate.test.js +93 -0
- package/dist/server/revalidate.test.js.map +1 -0
- package/dist/server/server-fn.test.d.ts +2 -0
- package/dist/server/server-fn.test.d.ts.map +1 -0
- package/dist/server/server-fn.test.js +105 -0
- package/dist/server/server-fn.test.js.map +1 -0
- package/dist/server/sitemap.d.ts +9 -0
- package/dist/server/sitemap.d.ts.map +1 -0
- package/dist/server/sitemap.js +26 -0
- package/dist/server/sitemap.js.map +1 -0
- package/dist/server/sitemap.test.d.ts +2 -0
- package/dist/server/sitemap.test.d.ts.map +1 -0
- package/dist/server/sitemap.test.js +61 -0
- package/dist/server/sitemap.test.js.map +1 -0
- package/dist/server/sse.d.ts +59 -0
- package/dist/server/sse.d.ts.map +1 -0
- package/dist/server/sse.js +91 -0
- package/dist/server/sse.js.map +1 -0
- package/dist/server/sse.test.d.ts +2 -0
- package/dist/server/sse.test.d.ts.map +1 -0
- package/dist/server/sse.test.js +68 -0
- package/dist/server/sse.test.js.map +1 -0
- package/dist/signals/index.d.ts +101 -0
- package/dist/signals/index.d.ts.map +1 -0
- package/dist/signals/index.js +149 -0
- package/dist/signals/index.js.map +1 -0
- package/dist/signals/signals.test.d.ts +2 -0
- package/dist/signals/signals.test.d.ts.map +1 -0
- package/dist/signals/signals.test.js +146 -0
- package/dist/signals/signals.test.js.map +1 -0
- package/dist/ssr/html.d.ts +27 -0
- package/dist/ssr/html.d.ts.map +1 -0
- package/dist/ssr/html.js +107 -0
- package/dist/ssr/html.js.map +1 -0
- package/dist/ssr/html.test.d.ts +2 -0
- package/dist/ssr/html.test.d.ts.map +1 -0
- package/dist/ssr/html.test.js +178 -0
- package/dist/ssr/html.test.js.map +1 -0
- package/dist/ssr/render.d.ts +46 -0
- package/dist/ssr/render.d.ts.map +1 -0
- package/dist/ssr/render.js +87 -0
- package/dist/ssr/render.js.map +1 -0
- package/dist/ssr/router-dev.d.ts +60 -0
- package/dist/ssr/router-dev.d.ts.map +1 -0
- package/dist/ssr/router-dev.js +205 -0
- package/dist/ssr/router-dev.js.map +1 -0
- package/dist/ssr/router-dev.test.d.ts +2 -0
- package/dist/ssr/router-dev.test.d.ts.map +1 -0
- package/dist/ssr/router-dev.test.js +189 -0
- package/dist/ssr/router-dev.test.js.map +1 -0
- package/dist/test/index.d.ts +93 -0
- package/dist/test/index.d.ts.map +1 -0
- package/dist/test/index.js +146 -0
- package/dist/test/index.js.map +1 -0
- package/dist/types/index.d.ts +117 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +2 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/napi.d.ts +15 -0
- package/dist/types/napi.d.ts.map +1 -0
- package/dist/types/napi.js +2 -0
- package/dist/types/napi.js.map +1 -0
- package/package.json +107 -0
- package/src/adapters/cloudflare.ts +30 -0
- package/src/adapters/deno.ts +21 -0
- package/src/adapters/web.ts +259 -0
- package/src/cli.ts +68 -0
- package/src/client/hooks.test.ts +54 -0
- package/src/client/hooks.ts +329 -0
- package/src/client/index.ts +5 -0
- package/src/client/offline-sw.ts +191 -0
- package/src/client/offline.ts +114 -0
- package/src/client/provider.tsx +14 -0
- package/src/commands/build.ts +201 -0
- package/src/commands/dev.ts +509 -0
- package/src/commands/info.ts +111 -0
- package/src/commands/ssg.ts +177 -0
- package/src/commands/start.ts +32 -0
- package/src/commands/test.ts +102 -0
- package/src/components/ErrorBoundary.tsx +73 -0
- package/src/components/Font.tsx +100 -0
- package/src/components/Image.tsx +141 -0
- package/src/components/Link.tsx +64 -0
- package/src/components/Script.tsx +97 -0
- package/src/components/index.ts +9 -0
- package/src/i18n/i18n.test.tsx +169 -0
- package/src/i18n/index.tsx +256 -0
- package/src/index.ts +10 -0
- package/src/router/code-router.test.ts +146 -0
- package/src/router/code-router.tsx +459 -0
- package/src/router/index.ts +18 -0
- package/src/router/manifest.ts +13 -0
- package/src/server/app.ts +466 -0
- package/src/server/cache.test.ts +192 -0
- package/src/server/cache.ts +195 -0
- package/src/server/csrf.test.ts +199 -0
- package/src/server/csrf.ts +80 -0
- package/src/server/image.ts +112 -0
- package/src/server/index.ts +144 -0
- package/src/server/middleware.test.ts +151 -0
- package/src/server/middleware.ts +95 -0
- package/src/server/revalidate.test.ts +106 -0
- package/src/server/revalidate.ts +75 -0
- package/src/server/server-fn.test.ts +127 -0
- package/src/server/sitemap.test.ts +68 -0
- package/src/server/sitemap.ts +30 -0
- package/src/server/sse.test.ts +81 -0
- package/src/server/sse.ts +110 -0
- package/src/signals/index.ts +177 -0
- package/src/signals/signals.test.ts +164 -0
- package/src/ssr/html.test.ts +200 -0
- package/src/ssr/html.ts +140 -0
- package/src/ssr/render.ts +144 -0
- package/src/ssr/router-dev.test.ts +230 -0
- package/src/ssr/router-dev.ts +229 -0
- package/src/test/index.ts +206 -0
- package/src/types/compiler.d.ts +25 -0
- package/src/types/index.ts +147 -0
- package/src/types/napi.ts +20 -0
- package/src/types/plugins.d.ts +3 -0
- package/tsconfig.json +11 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/vitest.config.ts +32 -0
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { readFile, access } from "node:fs/promises";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Handle `/_alabjs/image` requests using the Rust image optimiser (alab-napi).
|
|
5
|
+
*
|
|
6
|
+
* Node.js reads the source file from `public/` then passes the raw bytes to
|
|
7
|
+
* Rust — same pattern as snapbolt-cli. Rust decodes, resizes, and encodes to
|
|
8
|
+
* WebP (libwebp-sys with `native` feature, or pure-Rust fallback).
|
|
9
|
+
*
|
|
10
|
+
* Query params:
|
|
11
|
+
* src — path relative to the project's `public/` directory (required)
|
|
12
|
+
* w — target width in pixels (required)
|
|
13
|
+
* q — quality 1–100 (default: 80)
|
|
14
|
+
* fmt — "webp" (default) | "jpeg" | "png"
|
|
15
|
+
*
|
|
16
|
+
* Cache-Control is set to 1 year / immutable for optimised responses.
|
|
17
|
+
*/
|
|
18
|
+
export async function handleImageRequest(req, res, publicDir) {
|
|
19
|
+
const url = new URL(req.url ?? "/", "http://localhost");
|
|
20
|
+
const src = url.searchParams.get("src");
|
|
21
|
+
const wParam = url.searchParams.get("w");
|
|
22
|
+
const quality = Math.max(1, Math.min(100, parseInt(url.searchParams.get("q") ?? "80", 10)));
|
|
23
|
+
const fmt = url.searchParams.get("fmt") ?? "webp";
|
|
24
|
+
if (!src || !wParam) {
|
|
25
|
+
res.statusCode = 400;
|
|
26
|
+
res.end("[alabjs] Missing src or w parameter");
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const width = parseInt(wParam, 10);
|
|
30
|
+
if (!Number.isFinite(width) || width < 1 || width > 4096) {
|
|
31
|
+
res.statusCode = 400;
|
|
32
|
+
res.end("[alabjs] Invalid width — must be 1–4096");
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Prevent path traversal
|
|
36
|
+
const safeSrc = src.replace(/\.\./g, "").replace(/^\/+/, "");
|
|
37
|
+
const filePath = resolve(publicDir, safeSrc);
|
|
38
|
+
if (!filePath.startsWith(publicDir)) {
|
|
39
|
+
res.statusCode = 403;
|
|
40
|
+
res.end("[alabjs] Forbidden");
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
await access(filePath);
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
res.statusCode = 404;
|
|
48
|
+
res.end("[alabjs] Image not found");
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
const input = await readFile(filePath);
|
|
52
|
+
// Load napi binding (built by `cargo build --release -p alab-napi`).
|
|
53
|
+
// Fall back to serving the raw file when the binary isn't available so that
|
|
54
|
+
// images still load during development without the Rust toolchain.
|
|
55
|
+
let napi = null;
|
|
56
|
+
try {
|
|
57
|
+
const mod = await import("@alabjs/compiler");
|
|
58
|
+
napi = (mod.default ?? mod);
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// napi not built — will serve raw file below.
|
|
62
|
+
}
|
|
63
|
+
if (!napi) {
|
|
64
|
+
const ext = safeSrc.split(".").pop()?.toLowerCase() ?? "";
|
|
65
|
+
const mime = ext === "jpg" || ext === "jpeg" ? "image/jpeg"
|
|
66
|
+
: ext === "png" ? "image/png"
|
|
67
|
+
: ext === "gif" ? "image/gif"
|
|
68
|
+
: ext === "webp" ? "image/webp"
|
|
69
|
+
: ext === "avif" ? "image/avif"
|
|
70
|
+
: "application/octet-stream";
|
|
71
|
+
res.statusCode = 200;
|
|
72
|
+
res.setHeader("content-type", mime);
|
|
73
|
+
res.setHeader("cache-control", "no-store");
|
|
74
|
+
res.setHeader("content-length", input.length);
|
|
75
|
+
res.end(input);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
// Pass raw bytes to Rust — decode + resize + encode on a blocking thread pool.
|
|
80
|
+
const optimised = await napi.optimizeImage(input, quality, width, undefined, fmt);
|
|
81
|
+
const mime = fmt === "jpeg" || fmt === "jpg" ? "image/jpeg"
|
|
82
|
+
: fmt === "png" ? "image/png"
|
|
83
|
+
: "image/webp";
|
|
84
|
+
res.statusCode = 200;
|
|
85
|
+
res.setHeader("content-type", mime);
|
|
86
|
+
res.setHeader("cache-control", "public, max-age=31536000, immutable");
|
|
87
|
+
res.setHeader("content-length", optimised.length);
|
|
88
|
+
res.end(optimised);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
92
|
+
console.error(`[alabjs] image optimisation failed — src=${safeSrc} w=${width} fmt=${fmt}: ${msg}`);
|
|
93
|
+
res.statusCode = 500;
|
|
94
|
+
res.end("[alabjs] Image optimisation failed — check server logs");
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=image.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/server/image.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import type { ServerFn, ServerFnContext } from "../types/index.js";
|
|
2
|
+
export interface ServerFnCacheOptions<Input> {
|
|
3
|
+
/** How long to keep the result in seconds. Required when `cache` is set. */
|
|
4
|
+
ttl: number;
|
|
5
|
+
/**
|
|
6
|
+
* Tags for group invalidation via `invalidateCache({ tags })`.
|
|
7
|
+
* Can be a static array or a function that receives the input and returns tags,
|
|
8
|
+
* allowing per-argument granularity like `post:${input.id}`.
|
|
9
|
+
*/
|
|
10
|
+
tags?: string[] | ((input: Input) => string[]);
|
|
11
|
+
}
|
|
12
|
+
export interface DefineServerFnOptions<Input> {
|
|
13
|
+
/** Opt-in result caching. Nothing is cached unless this is specified. */
|
|
14
|
+
cache?: ServerFnCacheOptions<Input>;
|
|
15
|
+
}
|
|
16
|
+
interface ZodLike<T> {
|
|
17
|
+
safeParse(input: unknown): {
|
|
18
|
+
success: true;
|
|
19
|
+
data: T;
|
|
20
|
+
} | {
|
|
21
|
+
success: false;
|
|
22
|
+
error: unknown;
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Define a server-only function (no input schema).
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* export const getPosts = defineServerFn(async () => db.posts.findAll());
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare function defineServerFn<Input = undefined, Output = unknown>(handler: (ctx: ServerFnContext, input: Input) => Promise<Output>, options?: DefineServerFnOptions<Input>): ServerFn<Input, Output>;
|
|
34
|
+
/**
|
|
35
|
+
* Define a server-only function with **Zod input validation**.
|
|
36
|
+
*
|
|
37
|
+
* If validation fails, an HTTP 422 response with the Zod error is returned
|
|
38
|
+
* automatically — the handler is never called with invalid data.
|
|
39
|
+
*
|
|
40
|
+
* The client's `useMutation` / `useServerData` will receive
|
|
41
|
+
* `{ zodError: ZodError }` instead of throwing an untyped error.
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```ts
|
|
45
|
+
* import { z } from "zod";
|
|
46
|
+
*
|
|
47
|
+
* export const createPost = defineServerFn(
|
|
48
|
+
* z.object({ title: z.string().min(1), body: z.string() }),
|
|
49
|
+
* async ({ params }, input) => db.posts.create(input),
|
|
50
|
+
* { cache: { ttl: 0, tags: ["posts"] } },
|
|
51
|
+
* );
|
|
52
|
+
* ```
|
|
53
|
+
*/
|
|
54
|
+
export declare function defineServerFn<Schema extends ZodLike<unknown>, Output = unknown>(schema: Schema, handler: (ctx: ServerFnContext, input: Schema extends ZodLike<infer T> ? T : never) => Promise<Output>, options?: DefineServerFnOptions<Schema extends ZodLike<infer T> ? T : never>): ServerFn<Schema extends ZodLike<infer T> ? T : never, Output>;
|
|
55
|
+
export { defineSSEHandler } from "./sse.js";
|
|
56
|
+
export type { SSEEvent } from "./sse.js";
|
|
57
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAKnE,MAAM,WAAW,oBAAoB,CAAC,KAAK;IACzC,4EAA4E;IAC5E,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,EAAE,CAAC,CAAC;CAChD;AAED,MAAM,WAAW,qBAAqB,CAAC,KAAK;IAC1C,yEAAyE;IACzE,KAAK,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;CACrC;AAID,UAAU,OAAO,CAAC,CAAC;IACjB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG;QAAE,OAAO,EAAE,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,CAAA;KAAE,GAAG;QAAE,OAAO,EAAE,KAAK,CAAC;QAAC,KAAK,EAAE,OAAO,CAAA;KAAE,CAAC;CAC5F;AAaD;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,KAAK,GAAG,SAAS,EAAE,MAAM,GAAG,OAAO,EAChE,OAAO,EAAE,CAAC,GAAG,EAAE,eAAe,EAAE,KAAK,EAAE,KAAK,KAAK,OAAO,CAAC,MAAM,CAAC,EAChE,OAAO,CAAC,EAAE,qBAAqB,CAAC,KAAK,CAAC,GACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;AAE3B;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,cAAc,CAAC,MAAM,SAAS,OAAO,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,EAC9E,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,CACP,GAAG,EAAE,eAAe,EACpB,KAAK,EAAE,MAAM,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,KAC/C,OAAO,CAAC,MAAM,CAAC,EACpB,OAAO,CAAC,EAAE,qBAAqB,CAAC,MAAM,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC,GAC3E,QAAQ,CAAC,MAAM,SAAS,OAAO,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,KAAK,EAAE,MAAM,CAAC,CAAC;AAgEjE,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC;AAC5C,YAAY,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { getCached, setCache, CACHE_MISS } from "./cache.js";
|
|
2
|
+
function isZodSchema(v) {
|
|
3
|
+
return (v !== null &&
|
|
4
|
+
typeof v === "object" &&
|
|
5
|
+
"safeParse" in v &&
|
|
6
|
+
typeof v["safeParse"] === "function");
|
|
7
|
+
}
|
|
8
|
+
// ─── Implementation ───────────────────────────────────────────────────────────
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export function defineServerFn(...args) {
|
|
11
|
+
const [schemaOrHandler, handlerOrOptions, maybeOptions] = args;
|
|
12
|
+
let schema = null;
|
|
13
|
+
let handler;
|
|
14
|
+
let options;
|
|
15
|
+
if (isZodSchema(schemaOrHandler)) {
|
|
16
|
+
schema = schemaOrHandler;
|
|
17
|
+
handler = handlerOrOptions;
|
|
18
|
+
options = maybeOptions;
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
handler = schemaOrHandler;
|
|
22
|
+
options = handlerOrOptions;
|
|
23
|
+
}
|
|
24
|
+
const cacheOpts = options?.cache;
|
|
25
|
+
const wrapped = async (ctx, input) => {
|
|
26
|
+
// ── Zod validation ───────────────────────────────────────────────────────
|
|
27
|
+
let validatedInput = input;
|
|
28
|
+
if (schema) {
|
|
29
|
+
const result = schema.safeParse(input);
|
|
30
|
+
if (!result.success) {
|
|
31
|
+
// Throw a structured validation error that the dev/prod server will
|
|
32
|
+
// serialise as { zodError: ... } with HTTP 422.
|
|
33
|
+
const err = new Error("[alabjs] Validation failed");
|
|
34
|
+
err.zodError = result.error;
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
validatedInput = result.data;
|
|
38
|
+
}
|
|
39
|
+
// ── Cache lookup ─────────────────────────────────────────────────────────
|
|
40
|
+
if (cacheOpts) {
|
|
41
|
+
const cacheKey = `${handler.name}:${JSON.stringify(validatedInput)}`;
|
|
42
|
+
const cached = getCached(cacheKey);
|
|
43
|
+
if (cached !== CACHE_MISS)
|
|
44
|
+
return cached;
|
|
45
|
+
const data = await handler(ctx, validatedInput);
|
|
46
|
+
const tags = typeof cacheOpts.tags === "function"
|
|
47
|
+
? cacheOpts.tags(validatedInput)
|
|
48
|
+
: (cacheOpts.tags ?? []);
|
|
49
|
+
setCache(cacheKey, data, { ttl: cacheOpts.ttl, tags });
|
|
50
|
+
return data;
|
|
51
|
+
}
|
|
52
|
+
return handler(ctx, validatedInput);
|
|
53
|
+
};
|
|
54
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
55
|
+
return wrapped;
|
|
56
|
+
}
|
|
57
|
+
export { defineSSEHandler } from "./sse.js";
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA0B7D,SAAS,WAAW,CAAC,CAAU;IAC7B,OAAO,CACL,CAAC,KAAK,IAAI;QACV,OAAO,CAAC,KAAK,QAAQ;QACrB,WAAW,IAAI,CAAC;QAChB,OAAQ,CAA6B,CAAC,WAAW,CAAC,KAAK,UAAU,CAClE,CAAC;AACJ,CAAC;AA8CD,iFAAiF;AAEjF,8DAA8D;AAC9D,MAAM,UAAU,cAAc,CAAC,GAAG,IAAW;IAC3C,MAAM,CAAC,eAAe,EAAE,gBAAgB,EAAE,YAAY,CAAC,GAAG,IAIzD,CAAC;IACF,IAAI,MAAM,GAA4B,IAAI,CAAC;IAC3C,IAAI,OAAiD,CAAC;IACtD,IAAI,OAAmD,CAAC;IAExD,IAAI,WAAW,CAAC,eAAe,CAAC,EAAE,CAAC;QACjC,MAAM,GAAG,eAAe,CAAC;QACzB,OAAO,GAAG,gBAA4D,CAAC;QACvE,OAAO,GAAG,YAAY,CAAC;IACzB,CAAC;SAAM,CAAC;QACN,OAAO,GAAG,eAA2D,CAAC;QACtE,OAAO,GAAG,gBAA8D,CAAC;IAC3E,CAAC;IAED,MAAM,SAAS,GAAG,OAAO,EAAE,KAAK,CAAC;IAEjC,MAAM,OAAO,GAAG,KAAK,EAAE,GAAoB,EAAE,KAAc,EAAoB,EAAE;QAC/E,4EAA4E;QAC5E,IAAI,cAAc,GAAG,KAAK,CAAC;QAC3B,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,MAAM,GAAG,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;YACvC,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,oEAAoE;gBACpE,gDAAgD;gBAChD,MAAM,GAAG,GAAG,IAAI,KAAK,CAAC,4BAA4B,CAAkC,CAAC;gBACrF,GAAG,CAAC,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC;gBAC5B,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,cAAc,GAAG,MAAM,CAAC,IAAI,CAAC;QAC/B,CAAC;QAED,4EAA4E;QAC5E,IAAI,SAAS,EAAE,CAAC;YACd,MAAM,QAAQ,GAAG,GAAG,OAAO,CAAC,IAAI,IAAI,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,EAAE,CAAC;YACrE,MAAM,MAAM,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAC;YACnC,IAAI,MAAM,KAAK,UAAU;gBAAE,OAAO,MAAM,CAAC;YAEzC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;YAEhD,MAAM,IAAI,GACR,OAAO,SAAS,CAAC,IAAI,KAAK,UAAU;gBAClC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC,cAAc,CAAC;gBAChC,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;YAC7B,QAAQ,CAAC,QAAQ,EAAE,IAAI,EAAE,EAAE,GAAG,EAAE,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC;YACvD,OAAO,IAAI,CAAC;QACd,CAAC;QAED,OAAO,OAAO,CAAC,GAAG,EAAE,cAAc,CAAC,CAAC;IACtC,CAAC,CAAC;IAEF,8DAA8D;IAC9D,OAAO,OAA6B,CAAC;AACvC,CAAC;AAED,OAAO,EAAE,gBAAgB,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware runner — loads and executes the user's `middleware.ts` file.
|
|
3
|
+
*
|
|
4
|
+
* Convention (mirrors Next.js):
|
|
5
|
+
* ```ts
|
|
6
|
+
* // middleware.ts (project root)
|
|
7
|
+
* export async function middleware(req: Request): Promise<Response | void> {
|
|
8
|
+
* if (!isAuthed(req)) return Response.redirect(new URL("/login", req.url));
|
|
9
|
+
* // return nothing (or return NextResponse.next()) to continue
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* // Optional — restrict which paths the middleware runs on.
|
|
13
|
+
* // Patterns support * and ** wildcards (like path-to-regexp lite).
|
|
14
|
+
* export const config = {
|
|
15
|
+
* matcher: ["/dashboard/:path*", "/api/:path*"],
|
|
16
|
+
* };
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
/** Redirect the request to a new URL (defaults to 307 Temporary Redirect). */
|
|
20
|
+
export declare function redirect(url: string, status?: 301 | 302 | 307 | 308): Response;
|
|
21
|
+
/**
|
|
22
|
+
* Pass the request through to the next handler (no-op).
|
|
23
|
+
* Return the result of `next()` from your middleware to signal "continue".
|
|
24
|
+
*/
|
|
25
|
+
export declare function next(): null;
|
|
26
|
+
export interface MiddlewareModule {
|
|
27
|
+
middleware: (req: Request) => Promise<Response | null | void> | Response | null | void;
|
|
28
|
+
config?: {
|
|
29
|
+
matcher?: string[];
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Convert a matcher pattern like `/dashboard/:path*` or `/api/*` to a RegExp.
|
|
34
|
+
*
|
|
35
|
+
* Supported syntax:
|
|
36
|
+
* - `:param` — one path segment (any chars except `/`)
|
|
37
|
+
* - `*` — one path segment wildcard
|
|
38
|
+
* - `**` — zero or more segments (greedy)
|
|
39
|
+
* - `:param*` — zero or more remaining segments (Next.js `:path*` style)
|
|
40
|
+
*/
|
|
41
|
+
export declare function matcherToRegex(pattern: string): RegExp;
|
|
42
|
+
/**
|
|
43
|
+
* Test whether a pathname matches any of the given matcher patterns.
|
|
44
|
+
* If no patterns are provided, the middleware runs on every request.
|
|
45
|
+
*/
|
|
46
|
+
export declare function matchesMiddleware(pathname: string, matchers?: string[]): boolean;
|
|
47
|
+
/**
|
|
48
|
+
* Run the user middleware against the current request.
|
|
49
|
+
* Returns a `Response` if the middleware handled the request (redirect, early
|
|
50
|
+
* return, etc.) or `null` if it passed through (return nothing / undefined).
|
|
51
|
+
*/
|
|
52
|
+
export declare function runMiddleware(mod: MiddlewareModule, req: Request): Promise<Response | null>;
|
|
53
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,8EAA8E;AAC9E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,GAAE,GAAG,GAAG,GAAG,GAAG,GAAG,GAAG,GAAS,GAAG,QAAQ,CAEnF;AAED;;;GAGG;AACH,wBAAgB,IAAI,IAAI,IAAI,CAE3B;AAID,MAAM,WAAW,gBAAgB;IAC/B,UAAU,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC,GAAG,QAAQ,GAAG,IAAI,GAAG,IAAI,CAAC;IACvF,MAAM,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAgBtD;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAGhF;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CACjC,GAAG,EAAE,gBAAgB,EACrB,GAAG,EAAE,OAAO,GACX,OAAO,CAAC,QAAQ,GAAG,IAAI,CAAC,CAS1B"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Middleware runner — loads and executes the user's `middleware.ts` file.
|
|
3
|
+
*
|
|
4
|
+
* Convention (mirrors Next.js):
|
|
5
|
+
* ```ts
|
|
6
|
+
* // middleware.ts (project root)
|
|
7
|
+
* export async function middleware(req: Request): Promise<Response | void> {
|
|
8
|
+
* if (!isAuthed(req)) return Response.redirect(new URL("/login", req.url));
|
|
9
|
+
* // return nothing (or return NextResponse.next()) to continue
|
|
10
|
+
* }
|
|
11
|
+
*
|
|
12
|
+
* // Optional — restrict which paths the middleware runs on.
|
|
13
|
+
* // Patterns support * and ** wildcards (like path-to-regexp lite).
|
|
14
|
+
* export const config = {
|
|
15
|
+
* matcher: ["/dashboard/:path*", "/api/:path*"],
|
|
16
|
+
* };
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
// ─── Public helpers (importable from "alabjs/middleware") ───────────────────────
|
|
20
|
+
/** Redirect the request to a new URL (defaults to 307 Temporary Redirect). */
|
|
21
|
+
export function redirect(url, status = 307) {
|
|
22
|
+
return Response.redirect(url, status);
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Pass the request through to the next handler (no-op).
|
|
26
|
+
* Return the result of `next()` from your middleware to signal "continue".
|
|
27
|
+
*/
|
|
28
|
+
export function next() {
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Convert a matcher pattern like `/dashboard/:path*` or `/api/*` to a RegExp.
|
|
33
|
+
*
|
|
34
|
+
* Supported syntax:
|
|
35
|
+
* - `:param` — one path segment (any chars except `/`)
|
|
36
|
+
* - `*` — one path segment wildcard
|
|
37
|
+
* - `**` — zero or more segments (greedy)
|
|
38
|
+
* - `:param*` — zero or more remaining segments (Next.js `:path*` style)
|
|
39
|
+
*/
|
|
40
|
+
export function matcherToRegex(pattern) {
|
|
41
|
+
// Escape regex special chars except the ones we handle manually
|
|
42
|
+
const escaped = pattern
|
|
43
|
+
.replace(/[.+*^${}()|[\]\\]/g, "\\$&")
|
|
44
|
+
// :param* → zero-or-more segments (greedy, optional slash)
|
|
45
|
+
.replace(/\/:[a-zA-Z_][a-zA-Z0-9_]*\\\*/g, "(?:/.*)?")
|
|
46
|
+
.replace(/:[a-zA-Z_][a-zA-Z0-9_]*\\\*/g, "(?:/.*)?")
|
|
47
|
+
// :param → single segment
|
|
48
|
+
.replace(/:[a-zA-Z_][a-zA-Z0-9_]*/g, "[^/]+")
|
|
49
|
+
// ** → zero-or-more segments (greedy, optional slash)
|
|
50
|
+
.replace(/\/\*\*\\\*/g, "(?:/.*)?")
|
|
51
|
+
.replace(/\\\*\\\*/g, ".*")
|
|
52
|
+
// * → single segment
|
|
53
|
+
.replace(/\\\*/g, "[^/]+");
|
|
54
|
+
return new RegExp(`^${escaped}\\/?$`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Test whether a pathname matches any of the given matcher patterns.
|
|
58
|
+
* If no patterns are provided, the middleware runs on every request.
|
|
59
|
+
*/
|
|
60
|
+
export function matchesMiddleware(pathname, matchers) {
|
|
61
|
+
if (!matchers || matchers.length === 0)
|
|
62
|
+
return true;
|
|
63
|
+
return matchers.some((pattern) => matcherToRegex(pattern).test(pathname));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Run the user middleware against the current request.
|
|
67
|
+
* Returns a `Response` if the middleware handled the request (redirect, early
|
|
68
|
+
* return, etc.) or `null` if it passed through (return nothing / undefined).
|
|
69
|
+
*/
|
|
70
|
+
export async function runMiddleware(mod, req) {
|
|
71
|
+
const { middleware, config } = mod;
|
|
72
|
+
const pathname = new URL(req.url).pathname;
|
|
73
|
+
if (!matchesMiddleware(pathname, config?.matcher))
|
|
74
|
+
return null;
|
|
75
|
+
const result = await middleware(req);
|
|
76
|
+
if (result instanceof Response)
|
|
77
|
+
return result;
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/server/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,mFAAmF;AAEnF,8EAA8E;AAC9E,MAAM,UAAU,QAAQ,CAAC,GAAW,EAAE,SAAgC,GAAG;IACvE,OAAO,QAAQ,CAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,IAAI;IAClB,OAAO,IAAI,CAAC;AACd,CAAC;AASD;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,OAAe;IAC5C,gEAAgE;IAChE,MAAM,OAAO,GAAG,OAAO;SACpB,OAAO,CAAC,oBAAoB,EAAE,MAAM,CAAC;QACtC,2DAA2D;SAC1D,OAAO,CAAC,gCAAgC,EAAE,UAAU,CAAC;SACrD,OAAO,CAAC,8BAA8B,EAAE,UAAU,CAAC;QACpD,2BAA2B;SAC1B,OAAO,CAAC,0BAA0B,EAAE,OAAO,CAAC;QAC7C,sDAAsD;SACrD,OAAO,CAAC,aAAa,EAAE,UAAU,CAAC;SAClC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC;QAC3B,qBAAqB;SACpB,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;IAE7B,OAAO,IAAI,MAAM,CAAC,IAAI,OAAO,OAAO,CAAC,CAAC;AACxC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAAC,QAAgB,EAAE,QAAmB;IACrE,IAAI,CAAC,QAAQ,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpD,OAAO,QAAQ,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;AAC5E,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,GAAqB,EACrB,GAAY;IAEZ,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC;IACnC,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,CAAC;IAE3C,IAAI,CAAC,iBAAiB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,CAAC;QAAE,OAAO,IAAI,CAAC;IAE/D,MAAM,MAAM,GAAG,MAAM,UAAU,CAAC,GAAG,CAAC,CAAC;IACrC,IAAI,MAAM,YAAY,QAAQ;QAAE,OAAO,MAAM,CAAC;IAC9C,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.test.d.ts","sourceRoot":"","sources":["../../src/server/middleware.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { matcherToRegex, matchesMiddleware, runMiddleware, redirect, next, } from "./middleware.js";
|
|
3
|
+
// ─── matcherToRegex ───────────────────────────────────────────────────────────
|
|
4
|
+
describe("matcherToRegex", () => {
|
|
5
|
+
it("matches exact static path", () => {
|
|
6
|
+
const re = matcherToRegex("/dashboard");
|
|
7
|
+
expect(re.test("/dashboard")).toBe(true);
|
|
8
|
+
expect(re.test("/dashboard/")).toBe(true);
|
|
9
|
+
expect(re.test("/other")).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
it("handles :param as single segment", () => {
|
|
12
|
+
const re = matcherToRegex("/users/:id");
|
|
13
|
+
expect(re.test("/users/123")).toBe(true);
|
|
14
|
+
expect(re.test("/users/abc")).toBe(true);
|
|
15
|
+
expect(re.test("/users/")).toBe(false);
|
|
16
|
+
expect(re.test("/users/123/extra")).toBe(false);
|
|
17
|
+
});
|
|
18
|
+
it("handles * as single segment wildcard", () => {
|
|
19
|
+
const re = matcherToRegex("/api/*");
|
|
20
|
+
expect(re.test("/api/health")).toBe(true);
|
|
21
|
+
expect(re.test("/api/users")).toBe(true);
|
|
22
|
+
expect(re.test("/api/")).toBe(false);
|
|
23
|
+
expect(re.test("/api/a/b")).toBe(false);
|
|
24
|
+
});
|
|
25
|
+
it("handles ** as zero-or-more segments", () => {
|
|
26
|
+
const re = matcherToRegex("/docs/**");
|
|
27
|
+
expect(re.test("/docs/")).toBe(true);
|
|
28
|
+
expect(re.test("/docs/intro")).toBe(true);
|
|
29
|
+
expect(re.test("/docs/guides/auth")).toBe(true);
|
|
30
|
+
});
|
|
31
|
+
it("handles :param* (Next.js :path* style)", () => {
|
|
32
|
+
const re = matcherToRegex("/dashboard/:path*");
|
|
33
|
+
expect(re.test("/dashboard")).toBe(true);
|
|
34
|
+
expect(re.test("/dashboard/")).toBe(true);
|
|
35
|
+
expect(re.test("/dashboard/settings")).toBe(true);
|
|
36
|
+
expect(re.test("/dashboard/users/42")).toBe(true);
|
|
37
|
+
});
|
|
38
|
+
it("escapes special regex characters in static segments", () => {
|
|
39
|
+
const re = matcherToRegex("/search.html");
|
|
40
|
+
expect(re.test("/search.html")).toBe(true);
|
|
41
|
+
expect(re.test("/searchXhtml")).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
// ─── matchesMiddleware ────────────────────────────────────────────────────────
|
|
45
|
+
describe("matchesMiddleware", () => {
|
|
46
|
+
it("matches all paths when no matchers provided", () => {
|
|
47
|
+
expect(matchesMiddleware("/anything")).toBe(true);
|
|
48
|
+
expect(matchesMiddleware("/anything", undefined)).toBe(true);
|
|
49
|
+
expect(matchesMiddleware("/anything", [])).toBe(true);
|
|
50
|
+
});
|
|
51
|
+
it("matches when pathname fits a pattern", () => {
|
|
52
|
+
const matchers = ["/dashboard/:path*", "/api/*"];
|
|
53
|
+
expect(matchesMiddleware("/dashboard/settings", matchers)).toBe(true);
|
|
54
|
+
expect(matchesMiddleware("/api/health", matchers)).toBe(true);
|
|
55
|
+
expect(matchesMiddleware("/login", matchers)).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
// ─── redirect and next helpers ────────────────────────────────────────────────
|
|
59
|
+
describe("redirect", () => {
|
|
60
|
+
it("returns a Response with redirect status", () => {
|
|
61
|
+
const res = redirect("https://example.com/login");
|
|
62
|
+
expect(res).toBeInstanceOf(Response);
|
|
63
|
+
expect(res.status).toBe(307);
|
|
64
|
+
});
|
|
65
|
+
it("supports custom status codes", () => {
|
|
66
|
+
const res = redirect("https://example.com/login", 301);
|
|
67
|
+
expect(res.status).toBe(301);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
describe("next", () => {
|
|
71
|
+
it("returns null", () => {
|
|
72
|
+
expect(next()).toBe(null);
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
// ─── runMiddleware ────────────────────────────────────────────────────────────
|
|
76
|
+
describe("runMiddleware", () => {
|
|
77
|
+
it("returns null if pathname does not match the matcher", async () => {
|
|
78
|
+
const mod = {
|
|
79
|
+
middleware: () => redirect("https://example.com/login"),
|
|
80
|
+
config: { matcher: ["/dashboard/:path*"] },
|
|
81
|
+
};
|
|
82
|
+
const req = new Request("http://localhost/login");
|
|
83
|
+
const result = await runMiddleware(mod, req);
|
|
84
|
+
expect(result).toBe(null);
|
|
85
|
+
});
|
|
86
|
+
it("returns Response when middleware redirects", async () => {
|
|
87
|
+
const mod = {
|
|
88
|
+
middleware: () => redirect("https://example.com/login"),
|
|
89
|
+
config: { matcher: ["/dashboard/:path*"] },
|
|
90
|
+
};
|
|
91
|
+
const req = new Request("http://localhost/dashboard/settings");
|
|
92
|
+
const result = await runMiddleware(mod, req);
|
|
93
|
+
expect(result).toBeInstanceOf(Response);
|
|
94
|
+
expect(result.status).toBe(307);
|
|
95
|
+
});
|
|
96
|
+
it("returns null when middleware passes through", async () => {
|
|
97
|
+
const mod = {
|
|
98
|
+
middleware: () => undefined,
|
|
99
|
+
};
|
|
100
|
+
const req = new Request("http://localhost/anything");
|
|
101
|
+
const result = await runMiddleware(mod, req);
|
|
102
|
+
expect(result).toBe(null);
|
|
103
|
+
});
|
|
104
|
+
it("returns null when middleware returns null (via next())", async () => {
|
|
105
|
+
const mod = {
|
|
106
|
+
middleware: () => next(),
|
|
107
|
+
};
|
|
108
|
+
const req = new Request("http://localhost/anything");
|
|
109
|
+
const result = await runMiddleware(mod, req);
|
|
110
|
+
expect(result).toBe(null);
|
|
111
|
+
});
|
|
112
|
+
it("runs on all paths when no config.matcher is set", async () => {
|
|
113
|
+
let called = false;
|
|
114
|
+
const mod = {
|
|
115
|
+
middleware: () => {
|
|
116
|
+
called = true;
|
|
117
|
+
return null;
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
const req = new Request("http://localhost/random/path");
|
|
121
|
+
await runMiddleware(mod, req);
|
|
122
|
+
expect(called).toBe(true);
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
//# sourceMappingURL=middleware.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.test.js","sourceRoot":"","sources":["../../src/server/middleware.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand ISR revalidation handler.
|
|
3
|
+
*
|
|
4
|
+
* Exposes `POST /_alabjs/revalidate` so CMS webhooks, deploy scripts, or
|
|
5
|
+
* API routes can purge the page HTML and/or server-function data cache
|
|
6
|
+
* without restarting the server.
|
|
7
|
+
*
|
|
8
|
+
* Authentication
|
|
9
|
+
* --------------
|
|
10
|
+
* Set `ALAB_REVALIDATE_SECRET` in your environment. Every request must then
|
|
11
|
+
* include `Authorization: Bearer <secret>`. If the env var is not set the
|
|
12
|
+
* endpoint is open — useful in development, unsafe in production.
|
|
13
|
+
*
|
|
14
|
+
* Request body (JSON) — supply one or more fields:
|
|
15
|
+
* ```json
|
|
16
|
+
* { "path": "/posts/1" } // purge a single page
|
|
17
|
+
* { "prefix": "/posts" } // purge /posts, /posts/1, /posts/2, …
|
|
18
|
+
* { "tags": ["posts", "post:1"] } // purge all entries tagged with any tag
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Response (200):
|
|
22
|
+
* ```json
|
|
23
|
+
* { "revalidated": true, "path": "/posts/1" }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export interface RevalidateBody {
|
|
27
|
+
/** Purge a single cached page path. */
|
|
28
|
+
path?: string;
|
|
29
|
+
/** Purge all cached pages whose path starts with this prefix. */
|
|
30
|
+
prefix?: string;
|
|
31
|
+
/** Purge all server-function and page cache entries carrying any of these tags. */
|
|
32
|
+
tags?: string[];
|
|
33
|
+
}
|
|
34
|
+
/** Returns `true` when the request is authorised to call the revalidate endpoint. */
|
|
35
|
+
export declare function checkRevalidateAuth(authorizationHeader: string | null | undefined): boolean;
|
|
36
|
+
/**
|
|
37
|
+
* Apply a revalidation request. Returns a result object on success or an
|
|
38
|
+
* `{ error }` object (with an HTTP status hint) on failure.
|
|
39
|
+
*/
|
|
40
|
+
export declare function applyRevalidate(body: unknown): {
|
|
41
|
+
revalidated: true;
|
|
42
|
+
path?: string;
|
|
43
|
+
prefix?: string;
|
|
44
|
+
tags?: string[];
|
|
45
|
+
} | {
|
|
46
|
+
status: number;
|
|
47
|
+
error: string;
|
|
48
|
+
};
|
|
49
|
+
//# sourceMappingURL=revalidate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revalidate.d.ts","sourceRoot":"","sources":["../../src/server/revalidate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAIH,MAAM,WAAW,cAAc;IAC7B,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,iEAAiE;IACjE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,mFAAmF;IACnF,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,qFAAqF;AACrF,wBAAgB,mBAAmB,CAAC,mBAAmB,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAO3F;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,OAAO,GACZ;IAAE,WAAW,EAAE,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAqB5G"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-demand ISR revalidation handler.
|
|
3
|
+
*
|
|
4
|
+
* Exposes `POST /_alabjs/revalidate` so CMS webhooks, deploy scripts, or
|
|
5
|
+
* API routes can purge the page HTML and/or server-function data cache
|
|
6
|
+
* without restarting the server.
|
|
7
|
+
*
|
|
8
|
+
* Authentication
|
|
9
|
+
* --------------
|
|
10
|
+
* Set `ALAB_REVALIDATE_SECRET` in your environment. Every request must then
|
|
11
|
+
* include `Authorization: Bearer <secret>`. If the env var is not set the
|
|
12
|
+
* endpoint is open — useful in development, unsafe in production.
|
|
13
|
+
*
|
|
14
|
+
* Request body (JSON) — supply one or more fields:
|
|
15
|
+
* ```json
|
|
16
|
+
* { "path": "/posts/1" } // purge a single page
|
|
17
|
+
* { "prefix": "/posts" } // purge /posts, /posts/1, /posts/2, …
|
|
18
|
+
* { "tags": ["posts", "post:1"] } // purge all entries tagged with any tag
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* Response (200):
|
|
22
|
+
* ```json
|
|
23
|
+
* { "revalidated": true, "path": "/posts/1" }
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
import { revalidatePath, revalidatePathPrefix, revalidateTag } from "./cache.js";
|
|
27
|
+
/** Returns `true` when the request is authorised to call the revalidate endpoint. */
|
|
28
|
+
export function checkRevalidateAuth(authorizationHeader) {
|
|
29
|
+
const secret = process.env["ALAB_REVALIDATE_SECRET"];
|
|
30
|
+
if (!secret)
|
|
31
|
+
return true; // no secret configured — open endpoint
|
|
32
|
+
const provided = authorizationHeader?.startsWith("Bearer ")
|
|
33
|
+
? authorizationHeader.slice(7)
|
|
34
|
+
: null;
|
|
35
|
+
return provided === secret;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Apply a revalidation request. Returns a result object on success or an
|
|
39
|
+
* `{ error }` object (with an HTTP status hint) on failure.
|
|
40
|
+
*/
|
|
41
|
+
export function applyRevalidate(body) {
|
|
42
|
+
if (typeof body !== "object" || body === null) {
|
|
43
|
+
return { status: 400, error: "Request body must be a JSON object." };
|
|
44
|
+
}
|
|
45
|
+
const { path, prefix, tags } = body;
|
|
46
|
+
if (!path && !prefix && (!tags || tags.length === 0)) {
|
|
47
|
+
return { status: 400, error: "Provide at least one of: path, prefix, tags." };
|
|
48
|
+
}
|
|
49
|
+
if (path)
|
|
50
|
+
revalidatePath(path);
|
|
51
|
+
if (prefix)
|
|
52
|
+
revalidatePathPrefix(prefix);
|
|
53
|
+
if (tags?.length)
|
|
54
|
+
revalidateTag({ tags });
|
|
55
|
+
return {
|
|
56
|
+
revalidated: true,
|
|
57
|
+
...(path !== undefined && { path }),
|
|
58
|
+
...(prefix !== undefined && { prefix }),
|
|
59
|
+
...(tags !== undefined && { tags }),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=revalidate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"revalidate.js","sourceRoot":"","sources":["../../src/server/revalidate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,EAAE,cAAc,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAWjF,qFAAqF;AACrF,MAAM,UAAU,mBAAmB,CAAC,mBAA8C;IAChF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;IACrD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC,CAAC,uCAAuC;IACjE,MAAM,QAAQ,GAAG,mBAAmB,EAAE,UAAU,CAAC,SAAS,CAAC;QACzD,CAAC,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC,CAAC;QAC9B,CAAC,CAAC,IAAI,CAAC;IACT,OAAO,QAAQ,KAAK,MAAM,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAC7B,IAAa;IAEb,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,qCAAqC,EAAE,CAAC;IACvE,CAAC;IAED,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,IAAsB,CAAC;IAEtD,IAAI,CAAC,IAAI,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,CAAC,EAAE,CAAC;QACrD,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,8CAA8C,EAAE,CAAC;IAChF,CAAC;IAED,IAAI,IAAI;QAAE,cAAc,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,MAAM;QAAE,oBAAoB,CAAC,MAAM,CAAC,CAAC;IACzC,IAAI,IAAI,EAAE,MAAM;QAAE,aAAa,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAE1C,OAAO;QACL,WAAW,EAAE,IAAI;QACjB,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;QACnC,GAAG,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,CAAC;QACvC,GAAG,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,CAAC;KACpC,CAAC;AACJ,CAAC"}
|