@bractjs/bractjs 0.1.23 → 0.1.25

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bractjs/bractjs",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Production-grade SSR framework for Bun + React 19. File-based routing, streaming SSR, server actions, typed routes.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://github.com/bractjs/bractjs#readme",
@@ -50,7 +50,7 @@ describe("generateRouteRegistry", () => {
50
50
  expect(src).toContain(`import * as mod_routes__index_tsx from "../routes/_index.tsx";`);
51
51
  expect(src).toContain(`"root.tsx": mod_root_tsx,`);
52
52
  expect(src).toContain(`"routes/blog/layout.tsx": mod_routes_blog_layout_tsx,`);
53
- expect(src).toContain(`export const moduleRegistry: Record<string, RouteModule>`);
53
+ expect(src).toContain(`export const moduleRegistry: ModuleRegistry`);
54
54
  expect(src).toContain(`export const routeFiles: RouteFile[]`);
55
55
  });
56
56
 
@@ -1,4 +1,18 @@
1
1
  import type { BunPlugin } from "bun";
2
+ import { resolve } from "node:path";
3
+
4
+ // Lazy: this module is re-exported from the package barrel, so it may be
5
+ // statically pulled into client bundles. `import.meta.dir` is undefined in the
6
+ // browser, and a top-level `resolve(import.meta.dir, "..")` would throw
7
+ // "Path must be a string" at module load — before any plugin is even invoked.
8
+ // Defer the resolve until a plugin actually runs (always server-side).
9
+ let frameworkSrcRoot: string | undefined;
10
+ function getFrameworkSrcRoot(): string {
11
+ if (frameworkSrcRoot === undefined) {
12
+ frameworkSrcRoot = resolve(import.meta.dir, "..");
13
+ }
14
+ return frameworkSrcRoot;
15
+ }
2
16
 
3
17
  // ── Server-only import guard ───────────────────────────────────────────────
4
18
 
@@ -41,15 +55,24 @@ export function clientEnvPlugin(
41
55
  name: "bractjs-client-env",
42
56
  setup(build) {
43
57
  build.onLoad({ filter: /\.(ts|tsx|js|jsx)$/ }, async (args) => {
58
+ // Skip third-party packages and the framework's own source. The
59
+ // framework source contains literal strings like
60
+ // `"process.env.NODE_ENV"` (as keys of Bun.build's `define:` maps)
61
+ // that would otherwise be rewritten to `""undefined""`, breaking
62
+ // syntax. The framework also doesn't need allowlist enforcement —
63
+ // it accesses Bun.env directly on the server, not process.env on
64
+ // the client. Without this guard, linking the framework via `file:`
65
+ // produces a build that fails to parse its own source.
44
66
  if (args.path.includes("/node_modules/")) return undefined;
67
+ if (args.path.startsWith(getFrameworkSrcRoot())) return undefined;
45
68
  const src = await Bun.file(args.path).text();
46
69
  // SECURITY(medium): textual regex replace runs over the whole source,
47
70
  // including inside string literals and comments. A bare `process.env.X`
48
- // anywhere — even in a documentation string — becomes the literal value
49
- // (or "undefined"). This is acceptable for client builds because
50
- // unwanted occurrences only yield the string "undefined", never a
51
- // server secret. The allowedKeys gate is the authoritative leak check;
52
- // never widen it without auditing callers.
71
+ // anywhere in user code — even in a documentation string — becomes
72
+ // the literal value (or "undefined"). This is acceptable for client
73
+ // builds because unwanted occurrences only yield the string
74
+ // "undefined", never a server secret. The allowedKeys gate is the
75
+ // authoritative leak check; never widen it without auditing callers.
53
76
  const contents = src.replace(
54
77
  /process\.env\.([A-Z_][A-Z0-9_]*)/g,
55
78
  (_match, key: string) =>
@@ -161,15 +161,19 @@ export function generateRouteRegistry(input: RouteRegistryInput): string {
161
161
 
162
162
  return [
163
163
  HEADER,
164
- `import type { RouteFile } from "@bractjs/bractjs";`,
165
- `import type { RouteModule } from "@bractjs/bractjs";`,
164
+ `import type { RouteFile, ModuleRegistry } from "@bractjs/bractjs";`,
166
165
  "",
167
166
  imports.join("\n"),
168
167
  "",
169
168
  "// Modules keyed by appDir-relative path. The framework looks up route,",
170
169
  "// layout, and root modules here at request time instead of doing a",
171
170
  "// dynamic `import(absPath)` call that would fail in a compiled binary.",
172
- "export const moduleRegistry: Record<string, RouteModule> = {",
171
+ "//",
172
+ "// `ModuleRegistry` is intentionally `Record<string, RouteModule | Record<string, unknown>>`",
173
+ "// — strict `RouteModule` typing would reject route files whose exports",
174
+ "// (e.g. an ErrorBoundary typed for `{ error: Error }` instead of `{ error: unknown }`)",
175
+ "// diverge slightly from the framework signature. Runtime behaviour is unchanged.",
176
+ "export const moduleRegistry: ModuleRegistry = {",
173
177
  entries.join("\n"),
174
178
  "};",
175
179
  "",
package/types/config.d.ts CHANGED
@@ -46,8 +46,10 @@ export interface BractJSConfig {
46
46
  export interface ServerManifest {
47
47
  /** Hashed path to the main client entry bundle. */
48
48
  clientEntry: string;
49
+ /** Hashed path to the root.tsx chunk (when emitted as a separate entry). */
50
+ rootChunk?: string;
49
51
  /** Map of URL pattern → route asset info. */
50
- routes: Record<string, { file?: string; chunk?: string }>;
52
+ routes: Record<string, { file?: string; chunk?: string; imports?: string[] }>;
51
53
  }
52
54
 
53
55
  /** Subset of BractJSConfig used by the build pipeline. All fields optional. */