@mandujs/core 0.20.10 → 0.22.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/README.md +2 -1
- package/package.json +28 -3
- package/src/auth/__tests__/login.test.ts +419 -0
- package/src/auth/__tests__/password.test.ts +122 -0
- package/src/auth/__tests__/reset.test.ts +296 -0
- package/src/auth/__tests__/tokens.test.ts +274 -0
- package/src/auth/__tests__/verification.test.ts +274 -0
- package/src/auth/index.ts +76 -0
- package/src/auth/login.ts +225 -0
- package/src/auth/password.ts +120 -0
- package/src/auth/reset.ts +243 -0
- package/src/auth/tokens.ts +612 -0
- package/src/auth/verification.ts +253 -0
- package/src/bundler/__tests__/cli-bench-utils.test.ts +149 -0
- package/src/bundler/__tests__/cold-start.test.ts +504 -0
- package/src/bundler/__tests__/csp-nonce.test.ts +278 -0
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -0
- package/src/bundler/__tests__/extended-watch.test.ts +710 -0
- package/src/bundler/__tests__/fast-refresh.test.ts +596 -0
- package/src/bundler/__tests__/hdr.test.ts +353 -0
- package/src/bundler/__tests__/hmr-client.test.ts +532 -0
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -0
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -0
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -0
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -0
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -0
- package/src/bundler/build.test.ts +8 -1
- package/src/bundler/build.ts +495 -37
- package/src/bundler/css.ts +326 -323
- package/src/bundler/dev.ts +1671 -80
- package/src/bundler/fast-refresh-plugin.ts +307 -0
- package/src/bundler/hmr-types.ts +252 -0
- package/src/bundler/manifest-schema.ts +301 -0
- package/src/bundler/safe-build.test.ts +128 -0
- package/src/bundler/safe-build.ts +77 -0
- package/src/bundler/scenario-matrix.ts +229 -0
- package/src/bundler/types.ts +19 -0
- package/src/bundler/vendor-cache-types.ts +130 -0
- package/src/bundler/vendor-cache.ts +526 -0
- package/src/client/router.ts +214 -56
- package/src/config/validate.ts +1 -0
- package/src/db/__tests__/db.test.ts +485 -0
- package/src/db/index.ts +513 -0
- package/src/db/migrations/__tests__/runner.test.ts +661 -0
- package/src/db/migrations/history-table.ts +345 -0
- package/src/db/migrations/lock.ts +269 -0
- package/src/db/migrations/runner.ts +633 -0
- package/src/desktop/__tests__/smoke.test.ts +100 -0
- package/src/desktop/__tests__/window.test.ts +172 -0
- package/src/desktop/__tests__/worker.test.ts +266 -0
- package/src/desktop/index.ts +43 -0
- package/src/desktop/types.ts +158 -0
- package/src/desktop/window.ts +492 -0
- package/src/desktop/worker.ts +180 -0
- package/src/devtools/ai/mcp-connector.ts +18 -16
- package/src/devtools/client/components/mandu-character.tsx +4 -1
- package/src/devtools/client/components/panel/panel-container.tsx +20 -5
- package/src/email/__tests__/email.test.ts +355 -0
- package/src/email/index.ts +282 -0
- package/src/email/resend.ts +163 -0
- package/src/email/smtp.ts +64 -0
- package/src/filling/__tests__/session-sqlite.test.ts +454 -0
- package/src/filling/context.ts +72 -78
- package/src/filling/cookie-codec.ts +299 -0
- package/src/filling/deps.ts +25 -1
- package/src/filling/filling.ts +28 -3
- package/src/filling/session-sqlite.ts +617 -0
- package/src/filling/session.ts +265 -216
- package/src/guard/decision-memory.test.ts +52 -22
- package/src/id/__tests__/id.test.ts +120 -0
- package/src/id/index.ts +105 -0
- package/src/kitchen/index.ts +2 -2
- package/src/kitchen/kitchen-handler.ts +86 -0
- package/src/kitchen/stream/activity-sse.ts +2 -1
- package/src/middleware/csrf.ts +328 -0
- package/src/middleware/index.ts +40 -0
- package/src/middleware/oauth/__tests__/oauth.test.ts +574 -0
- package/src/middleware/oauth/index.ts +505 -0
- package/src/middleware/oauth/providers.ts +115 -0
- package/src/middleware/rate-limit/__tests__/rate-limit.test.ts +642 -0
- package/src/middleware/rate-limit/index.ts +522 -0
- package/src/middleware/rate-limit/sqlite-store.ts +382 -0
- package/src/middleware/secure/__tests__/secure.test.ts +360 -0
- package/src/middleware/secure/csp.ts +193 -0
- package/src/middleware/secure/index.ts +417 -0
- package/src/middleware/session.ts +174 -0
- package/src/observability/event-bus.ts +81 -79
- package/src/paths.ts +37 -0
- package/src/perf/hmr-markers.ts +215 -0
- package/src/perf/index.ts +104 -0
- package/src/resource/__tests__/generator.test.ts +603 -2
- package/src/resource/ddl/__tests__/diff.test.ts +639 -0
- package/src/resource/ddl/__tests__/emit.test.ts +799 -0
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -0
- package/src/resource/ddl/diff.ts +392 -0
- package/src/resource/ddl/emit.ts +548 -0
- package/src/resource/ddl/persistence-types.ts +218 -0
- package/src/resource/ddl/snapshot.ts +447 -0
- package/src/resource/ddl/type-map.ts +223 -0
- package/src/resource/ddl/types.ts +232 -0
- package/src/resource/generator-repo.ts +610 -0
- package/src/resource/generator-schema.ts +476 -0
- package/src/resource/generator.ts +117 -1
- package/src/resource/index.ts +17 -1
- package/src/resource/schema.ts +30 -0
- package/src/router/fs-scanner.ts +3 -0
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -0
- package/src/runtime/__tests__/hdr-client.test.ts +223 -0
- package/src/runtime/__tests__/http-errors.test.ts +117 -0
- package/src/runtime/__tests__/not-found.test.ts +152 -0
- package/src/runtime/boundary.tsx +21 -1
- package/src/runtime/fast-refresh-runtime.ts +322 -0
- package/src/runtime/fast-refresh-types.ts +128 -0
- package/src/runtime/hmr-client.ts +409 -0
- package/src/runtime/http-errors.ts +113 -0
- package/src/runtime/index.ts +6 -0
- package/src/runtime/logger.ts +678 -677
- package/src/runtime/not-found.ts +93 -0
- package/src/runtime/redirect.ts +133 -0
- package/src/runtime/server.ts +679 -23
- package/src/runtime/ssr.ts +340 -10
- package/src/runtime/streaming-ssr.ts +222 -19
- package/src/scheduler/__tests__/scheduler.test.ts +514 -0
- package/src/scheduler/index.ts +343 -0
- package/src/storage/s3/__tests__/s3.test.ts +479 -0
- package/src/storage/s3/index.ts +412 -0
- package/src/testing/index.ts +247 -189
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu notFound() helper for SSR loaders and handlers.
|
|
3
|
+
*
|
|
4
|
+
* Symmetric with `redirect()` — both short-circuit the SSR pipeline by
|
|
5
|
+
* returning (or throwing) a marked `Response`. Where `redirect()` tells
|
|
6
|
+
* the runtime "navigate somewhere else", `notFound()` tells the runtime
|
|
7
|
+
* "this resource does not exist; render the 404 surface".
|
|
8
|
+
*
|
|
9
|
+
* ## Usage
|
|
10
|
+
*
|
|
11
|
+
* ```ts
|
|
12
|
+
* import { Mandu, notFound } from "@mandujs/core";
|
|
13
|
+
*
|
|
14
|
+
* export const filling = Mandu.filling().loader(async (ctx) => {
|
|
15
|
+
* const post = await db.post.find(ctx.params.slug);
|
|
16
|
+
* if (!post) return notFound(); // or: throw notFound();
|
|
17
|
+
* return { post };
|
|
18
|
+
* });
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The SSR pipeline (server.ts `loadPageData`) checks each loader result
|
|
22
|
+
* with `isNotFoundResponse()`. On a hit it:
|
|
23
|
+
*
|
|
24
|
+
* 1. Prefers `app/not-found.tsx` (if registered) — rendered as a normal
|
|
25
|
+
* page with status 404 and any pending cookies preserved.
|
|
26
|
+
* 2. Falls back to the framework's built-in 404 JSON error.
|
|
27
|
+
*
|
|
28
|
+
* ## Why a branded Response
|
|
29
|
+
*
|
|
30
|
+
* A bare `new Response(null, { status: 404 })` is NOT treated as a
|
|
31
|
+
* notFound sentinel. That's intentional: a loader that accidentally
|
|
32
|
+
* returns a generic 404 Response (e.g. proxying an upstream fetch) must
|
|
33
|
+
* NOT hijack the page to show our 404 page. Only values minted through
|
|
34
|
+
* `notFound()` carry the internal brand.
|
|
35
|
+
*
|
|
36
|
+
* The brand is a non-enumerable WeakSet membership stamped on the
|
|
37
|
+
* Response object. Never serialised, never visible to clients.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** Internal brand — identifies Response objects minted by `notFound()`. */
|
|
41
|
+
export const NOT_FOUND_BRAND: unique symbol = Symbol.for("@mandujs/core/not-found");
|
|
42
|
+
|
|
43
|
+
/** WeakSet of Response instances tagged as notFound. Avoids property writes. */
|
|
44
|
+
const brandedNotFoundResponses = new WeakSet<Response>();
|
|
45
|
+
|
|
46
|
+
/** Options for tuning a notFound response. */
|
|
47
|
+
export interface NotFoundOptions {
|
|
48
|
+
/** Optional human-readable message. Surfaced to the 404 page via body. */
|
|
49
|
+
message?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Create a `Response` that signals "not found" to the Mandu SSR pipeline.
|
|
54
|
+
*
|
|
55
|
+
* Returns a real `Response` (status 404) for three reasons:
|
|
56
|
+
*
|
|
57
|
+
* 1. Loaders that `return` or `throw` it are treated identically — no
|
|
58
|
+
* extra plumbing for the "deep call stack wants to bail out" case.
|
|
59
|
+
* 2. Consumers outside an SSR loader (route handlers, middleware) can
|
|
60
|
+
* use the same helper without a separate API.
|
|
61
|
+
* 3. A hostile or buggy loader returning a bare `new Response(null, {status:404})`
|
|
62
|
+
* does NOT trigger the framework's 404 page path — it falls through
|
|
63
|
+
* the existing error channel like any other unexpected Response.
|
|
64
|
+
*
|
|
65
|
+
* @param options - Optional `{ message }`. Message is serialised into the
|
|
66
|
+
* response body as `text/plain; charset=utf-8`. If omitted, the body
|
|
67
|
+
* defaults to `"Not Found"`.
|
|
68
|
+
*/
|
|
69
|
+
export function notFound(options: NotFoundOptions = {}): Response {
|
|
70
|
+
const message = typeof options.message === "string" && options.message.length > 0
|
|
71
|
+
? options.message
|
|
72
|
+
: "Not Found";
|
|
73
|
+
|
|
74
|
+
const response = new Response(message, {
|
|
75
|
+
status: 404,
|
|
76
|
+
headers: { "Content-Type": "text/plain; charset=utf-8" },
|
|
77
|
+
});
|
|
78
|
+
brandedNotFoundResponses.add(response);
|
|
79
|
+
return response;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* True when `value` is a Response produced by `notFound()`.
|
|
84
|
+
*
|
|
85
|
+
* Deliberately strict — only branded responses match. A bare
|
|
86
|
+
* `new Response(null, { status: 404 })` is NOT recognised, nor is a
|
|
87
|
+
* redirect Response (even one with a 404-like status, which would be
|
|
88
|
+
* malformed but shouldn't confuse us).
|
|
89
|
+
*/
|
|
90
|
+
export function isNotFoundResponse(value: unknown): value is Response {
|
|
91
|
+
if (!(value instanceof Response)) return false;
|
|
92
|
+
return brandedNotFoundResponses.has(value);
|
|
93
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mandu redirect helper for SSR loaders.
|
|
3
|
+
*
|
|
4
|
+
* ## Usage
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import { Mandu, redirect } from "@mandujs/core";
|
|
8
|
+
*
|
|
9
|
+
* export const filling = Mandu.filling().loader(async (ctx) => {
|
|
10
|
+
* const uid = currentUserId(ctx);
|
|
11
|
+
* if (!uid) return redirect("/login"); // short-circuit → 302
|
|
12
|
+
* return { user: await fetchUser(uid) };
|
|
13
|
+
* });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* Both `return redirect(...)` and `throw redirect(...)` are supported — the
|
|
17
|
+
* SSR pipeline recognizes either shape. Thrown is idiomatic for bailing out
|
|
18
|
+
* of a deep call stack without threading the result back up.
|
|
19
|
+
*
|
|
20
|
+
* ## Design
|
|
21
|
+
*
|
|
22
|
+
* `redirect()` returns a real `Response` object (same shape Remix/Next.js
|
|
23
|
+
* use for their own loader-level redirects). This is preferred over a
|
|
24
|
+
* custom sentinel for three reasons:
|
|
25
|
+
*
|
|
26
|
+
* 1. `Response` is already understood by every layer of the runtime
|
|
27
|
+
* (cookies via `applyToResponse`, streaming headers, etc.).
|
|
28
|
+
* 2. A thrown `Response` is trivially distinguishable from a thrown
|
|
29
|
+
* `Error` — `instanceof Response` — so user bugs (`throw new Error(...)`)
|
|
30
|
+
* never get silently converted into redirects.
|
|
31
|
+
* 3. The helper can be used outside loaders (middleware, guards) without
|
|
32
|
+
* additional plumbing.
|
|
33
|
+
*
|
|
34
|
+
* A hidden symbol (`REDIRECT_BRAND`) is stamped on the Response so the
|
|
35
|
+
* runtime can distinguish redirects returned from a loader (for which we
|
|
36
|
+
* short-circuit SSR) from a loader that merely returned an unrelated
|
|
37
|
+
* Response (which would be a user error — loaders must return data). The
|
|
38
|
+
* brand is non-enumerable and scoped to the runtime — clients never see it.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
export type RedirectStatus = 301 | 302 | 303 | 307 | 308;
|
|
42
|
+
|
|
43
|
+
const DEFAULT_REDIRECT_STATUS: RedirectStatus = 302;
|
|
44
|
+
const VALID_REDIRECT_STATUS = new Set<number>([301, 302, 303, 307, 308]);
|
|
45
|
+
|
|
46
|
+
/** Internal brand — identifies Response objects minted by `redirect()`. */
|
|
47
|
+
export const REDIRECT_BRAND: unique symbol = Symbol.for("@mandujs/core/redirect");
|
|
48
|
+
|
|
49
|
+
/** Options for tuning a redirect response. */
|
|
50
|
+
export interface RedirectOptions {
|
|
51
|
+
/** HTTP status code. Defaults to 302 (Found). */
|
|
52
|
+
status?: RedirectStatus;
|
|
53
|
+
/** Additional headers to merge into the redirect response. */
|
|
54
|
+
headers?: HeadersInit;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a redirect `Response` suitable for returning or throwing from an
|
|
59
|
+
* SSR loader, handler, or middleware.
|
|
60
|
+
*
|
|
61
|
+
* @param url - Destination URL. Absolute or relative. Must be a non-empty
|
|
62
|
+
* string — empty/whitespace throws synchronously (catches silent bugs
|
|
63
|
+
* early rather than producing a useless redirect).
|
|
64
|
+
* @param options - Optional `{ status, headers }`. Status defaults to 302.
|
|
65
|
+
*
|
|
66
|
+
* @throws {TypeError} When `url` is not a non-empty string, or when
|
|
67
|
+
* `options.status` is not one of 301/302/303/307/308.
|
|
68
|
+
*/
|
|
69
|
+
export function redirect(url: string, options: RedirectOptions = {}): Response {
|
|
70
|
+
if (typeof url !== "string" || url.trim().length === 0) {
|
|
71
|
+
throw new TypeError(
|
|
72
|
+
`[Mandu] redirect() requires a non-empty URL string (got: ${JSON.stringify(url)})`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const status = options.status ?? DEFAULT_REDIRECT_STATUS;
|
|
77
|
+
if (!VALID_REDIRECT_STATUS.has(status)) {
|
|
78
|
+
throw new TypeError(
|
|
79
|
+
`[Mandu] redirect() status must be 301/302/303/307/308 (got: ${status})`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const headers = new Headers(options.headers);
|
|
84
|
+
headers.set("Location", url);
|
|
85
|
+
|
|
86
|
+
const response = new Response(null, { status, headers });
|
|
87
|
+
brandResponse(response);
|
|
88
|
+
return response;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Attach the redirect brand to a Response (non-enumerable).
|
|
93
|
+
* Kept separate so tests / advanced callers that mint their own
|
|
94
|
+
* redirect-status Responses can opt in.
|
|
95
|
+
*/
|
|
96
|
+
function brandResponse(response: Response): void {
|
|
97
|
+
Object.defineProperty(response, REDIRECT_BRAND, {
|
|
98
|
+
value: true,
|
|
99
|
+
enumerable: false,
|
|
100
|
+
configurable: false,
|
|
101
|
+
writable: false,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** True when `value` is a Response minted by `redirect()`. */
|
|
106
|
+
export function isManduRedirect(value: unknown): boolean {
|
|
107
|
+
return (
|
|
108
|
+
value instanceof Response &&
|
|
109
|
+
(value as unknown as { [REDIRECT_BRAND]?: boolean })[REDIRECT_BRAND] === true
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* True when `value` is any Response with a redirect-range status code.
|
|
115
|
+
*
|
|
116
|
+
* Covers both:
|
|
117
|
+
* - `redirect("/x")` from this module (preferred)
|
|
118
|
+
* - `new Response(null, { status: 302, headers: { Location: ... } })`
|
|
119
|
+
* (idiomatic Remix-style throw; we accept it so users aren't forced
|
|
120
|
+
* into our helper)
|
|
121
|
+
*
|
|
122
|
+
* Deliberately excludes thrown `Error` instances, so a user's
|
|
123
|
+
* `throw new Error("boom")` is never mistaken for a redirect — it falls
|
|
124
|
+
* through to the existing error-handling path.
|
|
125
|
+
*/
|
|
126
|
+
export function isRedirectResponse(value: unknown): value is Response {
|
|
127
|
+
if (!(value instanceof Response)) return false;
|
|
128
|
+
if (isManduRedirect(value)) return true;
|
|
129
|
+
if (!VALID_REDIRECT_STATUS.has(value.status)) return false;
|
|
130
|
+
// A redirect without a Location header is malformed — treat as a plain
|
|
131
|
+
// Response so we don't emit a broken 302 to the browser.
|
|
132
|
+
return value.headers.has("Location");
|
|
133
|
+
}
|