@bractjs/bractjs 0.1.24 → 0.1.26

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 (46) hide show
  1. package/README.md +755 -466
  2. package/bin/cli.ts +23 -3
  3. package/package.json +1 -1
  4. package/src/__tests__/compile-safety.test.ts +163 -0
  5. package/src/__tests__/compile-smoke.test.ts +276 -0
  6. package/src/__tests__/csp.test.ts +80 -0
  7. package/src/__tests__/fixtures/app/routes/_index.tsx +6 -2
  8. package/src/__tests__/fixtures/app/routes/protected.tsx +15 -0
  9. package/src/__tests__/fixtures/app/routes/redirect-action.tsx +13 -0
  10. package/src/__tests__/integration.test.ts +62 -0
  11. package/src/__tests__/layout-registry.test.ts +23 -0
  12. package/src/__tests__/loader.test.ts +23 -0
  13. package/src/__tests__/middleware.test.ts +22 -0
  14. package/src/__tests__/programmatic-api.test.ts +41 -2
  15. package/src/__tests__/response.test.ts +54 -1
  16. package/src/__tests__/security.test.ts +35 -0
  17. package/src/__tests__/server-module-stub.test.ts +145 -0
  18. package/src/__tests__/stream-handler.test.ts +36 -0
  19. package/src/build/bundler.ts +46 -20
  20. package/src/build/directives.ts +2 -2
  21. package/src/build/env-plugin.ts +76 -5
  22. package/src/build/react-dedupe.ts +41 -0
  23. package/src/client/ClientRouter.tsx +22 -8
  24. package/src/client/components/Form.tsx +10 -1
  25. package/src/client/hooks/useFetcher.ts +17 -1
  26. package/src/client/nav-utils.ts +54 -3
  27. package/src/client/types.ts +3 -0
  28. package/src/config/load.ts +50 -2
  29. package/src/dev/devtools.ts +72 -39
  30. package/src/dev/hmr-module-handler.ts +6 -4
  31. package/src/dev/rebuilder.ts +16 -1
  32. package/src/dev/server.ts +3 -0
  33. package/src/index.ts +13 -3
  34. package/src/server/csp.ts +92 -0
  35. package/src/server/csrf.ts +44 -6
  36. package/src/server/layout.ts +12 -2
  37. package/src/server/loader.ts +5 -7
  38. package/src/server/render.ts +29 -10
  39. package/src/server/request-handler.ts +15 -4
  40. package/src/server/response.ts +58 -5
  41. package/src/server/serve.ts +10 -0
  42. package/src/server/static.ts +11 -1
  43. package/src/server/stream-handler.ts +8 -7
  44. package/src/server/use-client-runtime.ts +62 -0
  45. package/src/shared/meta-tags.tsx +46 -0
  46. package/types/index.d.ts +20 -2
@@ -0,0 +1,46 @@
1
+ import { Fragment, createElement, type ReactElement } from "react";
2
+ import type { MetaDescriptor } from "./route-types.ts";
3
+
4
+ /**
5
+ * Renders route `meta()` descriptors as document-metadata elements.
6
+ *
7
+ * React 19 automatically hoists `<title>`, `<meta>`, and `<link>` rendered
8
+ * anywhere in the tree up into `<head>` — both during streaming SSR and on the
9
+ * client. We render the merged descriptors here (inside the SSR shell AND the
10
+ * ClientRouter tree) so:
11
+ * - crawlers and no-JS clients see real <title>/<meta> tags in the SSR HTML,
12
+ * - hydration matches the server tree (no mismatch warning),
13
+ * - soft navigation updates the document head by re-rendering this component.
14
+ *
15
+ * Keys are derived from the descriptor identity (title / name / property) so a
16
+ * later route can override an earlier one without React duplicating the node.
17
+ */
18
+ export function MetaTags({ meta }: { meta: MetaDescriptor[] }): ReactElement {
19
+ const children: ReactElement[] = [];
20
+
21
+ for (const d of meta) {
22
+ if ("title" in d && typeof (d as { title: unknown }).title === "string") {
23
+ const title = (d as { title: string }).title;
24
+ children.push(createElement("title", { key: "title" }, title));
25
+ } else if ("name" in d && "content" in d) {
26
+ const { name, content } = d as { name: string; content: string };
27
+ children.push(createElement("meta", { key: `name:${name}`, name, content }));
28
+ } else if ("property" in d && "content" in d) {
29
+ const { property, content } = d as { property: string; content: string };
30
+ children.push(createElement("meta", { key: `prop:${property}`, property, content }));
31
+ } else {
32
+ // Arbitrary descriptor: render each string field as a meta attribute set.
33
+ const entries = Object.entries(d).filter(
34
+ ([, v]) => typeof v === "string",
35
+ ) as Array<[string, string]>;
36
+ if (entries.length > 0) {
37
+ const props: Record<string, string> = {};
38
+ for (const [k, v] of entries) props[k] = v;
39
+ const key = entries.map(([k, v]) => `${k}=${v}`).join("&");
40
+ children.push(createElement("meta", { key: `raw:${key}`, ...props }));
41
+ }
42
+ }
43
+ }
44
+
45
+ return createElement(Fragment, null, ...children);
46
+ }
package/types/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { ReactNode, Context } from "react";
1
+ import type { ReactNode, Context, CSSProperties } from "react";
2
2
 
3
3
  // ── Route types ───────────────────────────────────────────────────────────
4
4
  export type {
@@ -34,7 +34,8 @@ export interface LifecycleHooks {
34
34
  export declare function defineLifecycle(hooks: LifecycleHooks): LifecycleHooks;
35
35
  export declare function createServer(config?: Partial<BractJSConfig>): { stop(): void };
36
36
  export declare function renderRoute(options: RenderOptions): Promise<Response>;
37
- export declare function redirect(url: string, status?: number): Response;
37
+ export interface RedirectOptions { allowExternal?: boolean; }
38
+ export declare function redirect(url: string, status?: number, headers?: HeadersInit, options?: RedirectOptions): Response;
38
39
  export declare function json<T>(data: T, init?: ResponseInit): Response;
39
40
  export declare function error(message: string, status?: number): Response;
40
41
 
@@ -142,6 +143,23 @@ export declare function Form(props: FormProps): ReactNode;
142
143
  export interface AwaitProps<T> { resolve: Promise<T>; fallback: ReactNode; children: (data: T) => ReactNode; }
143
144
  export declare function Await<T>(props: AwaitProps<T>): ReactNode;
144
145
 
146
+ export type ImageFormat = "webp" | "avif" | "jpeg" | "png";
147
+ export type ImageFit = "cover" | "contain" | "fill";
148
+ export interface ImageProps {
149
+ src: string;
150
+ alt: string;
151
+ width?: number;
152
+ height?: number;
153
+ quality?: number;
154
+ format?: ImageFormat;
155
+ fit?: ImageFit;
156
+ priority?: boolean;
157
+ sizes?: string;
158
+ className?: string;
159
+ style?: CSSProperties;
160
+ }
161
+ export declare function Image(props: ImageProps): ReactNode;
162
+
145
163
  // ── Client hooks ──────────────────────────────────────────────────────────
146
164
  export declare function useLoaderData<T = unknown>(): T;
147
165
  export declare function useActionData<T = unknown>(): T | null;