@mauroandre/velojs 0.0.59 → 0.0.60

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 CHANGED
@@ -170,6 +170,7 @@ veloPlugin({
170
170
  serverInit: "server.tsx", // default
171
171
  clientInit: "client.tsx", // default
172
172
  hostname: "127.0.0.1", // bind interface, dev and production (the HOST env wins)
173
+ serverOnly: ["server/**"], // declares server-only modules to the leak diagnostic
173
174
  });
174
175
  ```
175
176
 
@@ -1780,13 +1781,15 @@ Callbacks queue until the server starts. If called after startup, executes immed
1780
1781
 
1781
1782
  ## Vite Plugin Architecture
1782
1783
 
1783
- `veloPlugin()` returns 6 plugins:
1784
+ `veloPlugin()` returns 8 plugins:
1784
1785
 
1785
1786
  | Plugin | Purpose |
1786
1787
  |--------|---------|
1787
1788
  | `velo:config` | Build config (client/server modes, aliases, defines) |
1788
1789
  | `velo:transform` | AST transforms (metadata injection, action stubs, loader removal) |
1789
1790
  | `velo:static-url` | Rewrites CSS `url(/path)` to `url(STATIC_BASE_URL/path)` at build time |
1791
+ | `velo:graph` | Generates `.velojs/graph.json` (route tree + dependency graph) on build and dev |
1792
+ | `velo:leak-diagnostic` | Prints the server→client leak report on build and dev (read-only) |
1790
1793
  | `@preact/preset-vite` | Preact JSX support |
1791
1794
  | `@hono/vite-dev-server` | Dev server with SSR |
1792
1795
  | `velo:ws-bridge` | Exposes Vite's HTTP server for WebSocket handlers in dev mode |
@@ -1829,6 +1832,55 @@ velojs build
1829
1832
 
1830
1833
  When `routes.tsx` changes, the plugin rebuilds the fullPath map and triggers a full page reload (not partial HMR).
1831
1834
 
1835
+ ### Server→Client Leak Diagnostic
1836
+
1837
+ What ships to the browser is checked on every `velojs build` (including `build --static`) and `velojs dev`: the terminal prints a report of every server module the client entry can reach — a leak of the server→client boundary.
1838
+
1839
+ A **server module** is a module that touches a Node builtin in its own code — static import, re-export or `await import()`, by any import path — or that matches a `serverOnly` pattern declared in the plugin config.
1840
+ A **leak** is a server module reachable from what the browser loads, following static and dynamic imports.
1841
+ The analysis reads the code *after* the client transforms — the same ones the build applies (loaders, actions, streams, sockets, middlewares and endpoints stripped, orphaned imports pruned) — so the normal VeloJS shape (a `loader` importing server code) is silent by construction: what the framework removes never travels.
1842
+
1843
+ Each leak in the report brings:
1844
+
1845
+ - the leaked module and what qualifies it as server (the Node builtin touched, or the `serverOnly` pattern matched);
1846
+ - the full import chain from the client entry to it — file and line at every link;
1847
+ - the **entry point** — the import where the chain pulls the first server module, marked `entry point (cut here)`: exactly where the cut happens.
1848
+
1849
+ ```text
1850
+ [velojs] server->client leak analysis: 1 leak(s) in what ships to the client.
1851
+
1852
+ 1) app/server-side.ts
1853
+ server because: imports Node builtin "node:fs"
1854
+ import chain from the client entry:
1855
+ virtual:velo/client-entry (generated) -> app/routes.tsx
1856
+ app/routes.tsx:3 -> app/pages/Home.tsx
1857
+ app/pages/Home.tsx:6 -> app/server-side.ts <- entry point (cut here)
1858
+
1859
+ The report is read-only: nothing is blocked or rewritten; the cut is yours.
1860
+ ```
1861
+
1862
+ Two chains to the same module are two items — every cut point shows up — and the list is stable between runs.
1863
+ When there is nothing to report, one line says so (`0 leaks — what ships to the client is clean.`), so silence is distinguishable from the analysis not running.
1864
+ In dev the report appears when the server comes up (no navigation needed) and is re-emitted whenever a file change shifts what travels to the client — an import added stitches a new path into the next report, an import removed takes the leak out of it.
1865
+
1866
+ The report is the light, never the hand: nothing is blocked, rewritten, stubbed or moved, `await import()` included; the build finishes with success, the dev keeps serving, and the decision to cut any edge is entirely yours.
1867
+
1868
+ #### Declaring server-only code: `serverOnly`
1869
+
1870
+ A module that touches no Node builtin is invisible to the analysis — the framework does not guess your app's domain.
1871
+ Declare it:
1872
+
1873
+ ```typescript
1874
+ veloPlugin({
1875
+ serverOnly: ["src/fsm/**", "server/**"],
1876
+ });
1877
+ ```
1878
+
1879
+ Patterns are globs (`*`, `**`, `?`) matched against the module's file path relative to the project root, always with `/` as separator.
1880
+ The declaration feeds the diagnostic only — nothing is blocked or altered because of it.
1881
+
1882
+ **Limitation (craved):** server code that touches no Node builtin and matches no `serverOnly` pattern generates no item; that is what the declaration is for.
1883
+
1832
1884
  ---
1833
1885
 
1834
1886
  ## Request Isolation
@@ -1986,6 +2038,7 @@ interface VeloConfig {
1986
2038
  clientInit?: string; // default: "client.tsx"
1987
2039
  port?: number; // default: 3000; the PORT env wins
1988
2040
  hostname?: string; // bind interface, dev and production; the HOST env wins
2041
+ serverOnly?: string[]; // glob patterns declaring server-only modules to the leak diagnostic
1989
2042
  }
1990
2043
  ```
1991
2044
 
package/agent/AGENTS.md CHANGED
@@ -39,6 +39,27 @@ hard constraints, not style.
39
39
 
40
40
  Never pass a module id to `useLoader()` / `Loader()` yourself — the plugin injects it.
41
41
 
42
+ ## The server→client leak report
43
+
44
+ `velojs build` (and `build --static`, `velojs dev`) prints a report of every **server module** the browser can reach — a module that touches a Node builtin in its own code (static import, re-export or `await import()`), or that matches a `serverOnly` pattern.
45
+ The analysis reads the code **after** the client transforms (the table above describes what those remove), so a `loader`/`action_*`/`stream_*`/`socket_*`/`middlewares` importing server code where the framework strips it is silent by construction.
46
+
47
+ What the warning means: the module named in the item reaches what ships to the browser; the chain lists every import from the client entry to it (file and line per link); `entry point (cut here)` marks the import where the chain pulls the first server module — that is where the cut happens.
48
+ The report never blocks, rewrites, stubs or moves anything: the build finishes with success, the dev keeps serving, and cutting any edge is always your call.
49
+ Two chains to the same module are two items — every cut point shows up.
50
+
51
+ Code that touches **no** Node builtin is invisible to the analysis — the framework does not guess your app's domain. Declare it:
52
+
53
+ ```typescript
54
+ veloPlugin({
55
+ serverOnly: ["src/fsm/**", "server/**"],
56
+ });
57
+ ```
58
+
59
+ Patterns are globs (`*`, `**`, `?`) matched against the module's file path relative to the project root, always with `/` as separator.
60
+ The declaration feeds the diagnostic only — nothing is blocked or altered because of it.
61
+ A server module with no builtin and no declaration generates no item: that limitation is craved, and the declaration is how you lift it.
62
+
42
63
  ## Sharing a layout's data with its children
43
64
 
44
65
  `Loader()` is the importable handle for a module's loader data; `useLoader()` is the
@@ -139,5 +139,6 @@ interface VeloConfig {
139
139
  clientInit?: string; // default: "client.tsx"
140
140
  port?: number; // default: 3000 (the PORT env always wins)
141
141
  hostname?: string; // bind interface, dev and production; the HOST env wins
142
+ serverOnly?: string[]; // glob patterns declaring server-only modules to the leak diagnostic
142
143
  }
143
144
  ```
@@ -5,9 +5,9 @@ description: "The `veloPlugin` build pipeline: the 9 AST transforms that inject
5
5
 
6
6
  # Vite Plugin
7
7
 
8
- The `veloPlugin()` is the core of VeloJS. It's a single function that returns 6 Vite plugins working together to handle routing, SSR, code transforms, and more.
8
+ The `veloPlugin()` is the core of VeloJS. It's a single function that returns 8 Vite plugins working together to handle routing, SSR, code transforms, and more.
9
9
 
10
- ## The 7 plugins
10
+ ## The 8 plugins
11
11
 
12
12
  | Plugin | What it does |
13
13
  |--------|-------------|
@@ -15,6 +15,7 @@ The `veloPlugin()` is the core of VeloJS. It's a single function that returns 6
15
15
  | `velo:transform` | Performs AST transforms on your code (the most important plugin) |
16
16
  | `velo:static-url` | Rewrites CSS `url(/path)` to use `STATIC_BASE_URL` in production builds |
17
17
  | `velo:graph` | Generates `.velojs/graph.json` (route tree + dependency graph) on build and dev |
18
+ | `velo:leak-diagnostic` | Prints the server→client leak report on build and dev (read-only) |
18
19
  | `@preact/preset-vite` | Adds Preact JSX support |
19
20
  | `@hono/vite-dev-server` | Runs the SSR dev server |
20
21
  | `velo:ws-bridge` | Exposes Vite's HTTP server for WebSocket handlers during development |
@@ -79,6 +80,54 @@ VeloJS uses Vite virtual modules to generate the server and client entry points
79
80
 
80
81
  You never import these directly — they're generated by the plugin.
81
82
 
83
+ ## The server→client leak diagnostic
84
+
85
+ What ships to the browser is checked on every `velojs build` (including `build --static`) and `velojs dev`: the terminal prints a report of every server module the client entry can reach — a leak of the server→client boundary.
86
+
87
+ A **server module** is a module that touches a Node builtin in its own code — static import, re-export or `await import()`, by any import path — or that matches a `serverOnly` pattern declared in the plugin config.
88
+ A **leak** is a server module reachable from what the browser loads, following static and dynamic imports.
89
+ The analysis reads the code *after* the client transforms above — loaders, actions, streams, sockets, middlewares and endpoints stripped, orphaned imports pruned — never the raw source, so a `loader` importing server code is silent by construction.
90
+
91
+ Each leak in the report brings:
92
+
93
+ - the leaked module and what qualifies it as server (the Node builtin touched, or the `serverOnly` pattern matched);
94
+ - the full import chain from the client entry to it — file and line at every link;
95
+ - the **entry point** — the import where the chain pulls the first server module, marked `entry point (cut here)`: exactly where the cut happens.
96
+
97
+ ```text
98
+ [velojs] server->client leak analysis: 1 leak(s) in what ships to the client.
99
+
100
+ 1) app/server-side.ts
101
+ server because: imports Node builtin "node:fs"
102
+ import chain from the client entry:
103
+ virtual:velo/client-entry (generated) -> app/routes.tsx
104
+ app/routes.tsx:3 -> app/pages/Home.tsx
105
+ app/pages/Home.tsx:6 -> app/server-side.ts <- entry point (cut here)
106
+
107
+ The report is read-only: nothing is blocked or rewritten; the cut is yours.
108
+ ```
109
+
110
+ Two chains to the same module are two items — every cut point shows up — and the list is stable between runs.
111
+ When nothing leaks, one line says so (`0 leaks — what ships to the client is clean.`), so silence is distinguishable from the analysis not running.
112
+ In dev the report appears when the server comes up (no navigation needed) and is re-emitted whenever a file change shifts what travels to the client.
113
+
114
+ ### Declaring server-only code: `serverOnly`
115
+
116
+ A module that touches no Node builtin is invisible to the analysis — the framework does not guess your app's domain.
117
+ Declare it:
118
+
119
+ ```typescript
120
+ veloPlugin({
121
+ serverOnly: ["src/fsm/**", "server/**"],
122
+ });
123
+ ```
124
+
125
+ Patterns are globs (`*`, `**`, `?`) matched against the module's file path relative to the project root, always with `/` as separator.
126
+ The declaration feeds the diagnostic only — nothing is blocked or altered because of it.
127
+
128
+ **Limitation (craved):** server code that touches no Node builtin and matches no `serverOnly` pattern generates no item; that is what the declaration is for.
129
+ The diagnostic is the light, never the hand: nothing is blocked, rewritten, stubbed or moved, `await import()` included; the build finishes with success and the dev keeps serving.
130
+
82
131
  ## Hot reload
83
132
 
84
133
  When you edit `routes.tsx`, the plugin detects the change, rebuilds the path map, and triggers a full page reload. Other file changes use Vite's standard HMR (hot module replacement).
package/dist/config.d.ts CHANGED
@@ -40,5 +40,13 @@ export interface VeloConfig {
40
40
  * @hono/vite-dev-server, não os substituem.
41
41
  */
42
42
  devServerExclude?: (string | RegExp)[];
43
+ /**
44
+ * Padrões de glob (`*`, `**`, `?`) que declaram módulos só de servidor ao
45
+ * diagnóstico de vazamento server->client, casados contra o caminho do
46
+ * arquivo do módulo relativo à raiz do projeto, com `/` como separador
47
+ * (ex.: `serverOnly: ["src/fsm/**", "server/**"]`). Serve só ao
48
+ * diagnóstico — nada é bloqueado ou alterado por causa da declaração.
49
+ */
50
+ serverOnly?: string[];
43
51
  }
44
52
  export declare function defineConfig(config: VeloConfig): VeloConfig;
@@ -0,0 +1,77 @@
1
+ /** One import in the chain: `from` imports `to` at `line` (`null` = generated). */
2
+ export interface LeakLink {
3
+ from: string;
4
+ line: number | null;
5
+ to: string;
6
+ }
7
+ export interface LeakItem {
8
+ /** Display path of the leaked server module. */
9
+ target: string;
10
+ /** Why it qualifies as a server module (English). */
11
+ qualifiers: string[];
12
+ /** Full import chain, client entry -> target, one link per import. */
13
+ chain: LeakLink[];
14
+ /** Index in `chain` of the entry-point link — the cut. */
15
+ entryIndex: number;
16
+ }
17
+ export interface LeakReport {
18
+ leaks: LeakItem[];
19
+ /** True when the enumeration stopped at its safety budget. */
20
+ truncated: boolean;
21
+ /** Stable fingerprint of what ships to the client (dev re-emit trigger). */
22
+ signature: string;
23
+ /** How many modules the walk visited (informational). */
24
+ moduleCount: number;
25
+ }
26
+ export type ResolveFn = (spec: string, importer: string) => string | null;
27
+ export interface ModuleInfo {
28
+ file: string;
29
+ display: string;
30
+ edges: RawEdge[];
31
+ builtins: string[];
32
+ patterns: string[];
33
+ }
34
+ export interface CachedModule {
35
+ mtimeMs: number;
36
+ info: ModuleInfo;
37
+ }
38
+ export interface LeakAnalyzerOptions {
39
+ /** Project root — display paths and `serverOnly` matching are relative to it. */
40
+ rootDir: string;
41
+ /** Absolute app directory (velo:transform's scope). */
42
+ appDir: string;
43
+ /** Routes file name inside appDir (default "routes.tsx"). */
44
+ routesFile?: string | undefined;
45
+ /** The client entry's import specifiers (the virtual entry's imports). */
46
+ entrySpecs: string[];
47
+ /** Glob patterns declaring server-only modules (VeloConfig.serverOnly). */
48
+ serverOnly?: readonly string[] | undefined;
49
+ /** Import resolver: (specifier, importer file) -> absolute file or null. */
50
+ resolve?: ResolveFn | undefined;
51
+ /** Optional parse cache shared across runs (dev). */
52
+ cache?: Map<string, CachedModule> | undefined;
53
+ }
54
+ interface RawEdge {
55
+ spec: string;
56
+ line: number;
57
+ kind: "static" | "dynamic";
58
+ locals: string[];
59
+ }
60
+ /** True when the specifier names a Node builtin (with or without the `node:` prefix). */
61
+ export declare function isBuiltinSpec(spec: string): boolean;
62
+ /**
63
+ * Compiles a glob (`*` = within a segment, `**` across segments, `?` = one
64
+ * char) into a full-match regex over the module path relative to the project
65
+ * root, always with `/` separators.
66
+ */
67
+ export declare function globToRegex(glob: string): RegExp;
68
+ /**
69
+ * Resolution mirroring what the client build follows: relative specifiers with
70
+ * TS extension completion, bare specifiers through `node_modules` with the
71
+ * package's browser/import `exports` conditions. Returns null for anything
72
+ * that does not resolve to a file (external, virtual or unresolvable).
73
+ */
74
+ export declare function defaultResolve(spec: string, importer: string): string | null;
75
+ export declare function analyzeClientLeaks(options: LeakAnalyzerOptions): LeakReport;
76
+ export declare function formatLeakReport(report: LeakReport): string;
77
+ export {};
@@ -0,0 +1,14 @@
1
+ import "node:fs";
2
+ import "node:path";
3
+ import "node:module";
4
+ import "@babel/parser";
5
+ import "@babel/traverse";
6
+ import "@babel/types";
7
+ import { a as s, d as l, f, g, i as n } from "./vite.js";
8
+ export {
9
+ s as analyzeClientLeaks,
10
+ l as defaultResolve,
11
+ f as formatLeakReport,
12
+ g as globToRegex,
13
+ n as isBuiltinSpec
14
+ };
package/dist/vite.d.ts CHANGED
@@ -81,6 +81,42 @@ export declare function declaredChannelNames(code: string): string[] | null;
81
81
  * A missing file yields an empty set, which makes every declaration unknown.
82
82
  */
83
83
  export declare function readChannelMapNames(file: string): Set<string>;
84
+ export interface ModuleTransformOptions {
85
+ /** Absolute module id (the Vite module id). */
86
+ id: string;
87
+ /** Absolute app directory (velo:transform's scope). */
88
+ appDir: string;
89
+ /** Whether this is the SSR/server pass. */
90
+ isSSR: boolean;
91
+ /** Routes file name inside appDir (default "routes.tsx"). */
92
+ routesFile?: string | undefined;
93
+ /** Route path info (metadata injection only — never changes imports). */
94
+ pathInfo?: PathInfo | undefined;
95
+ }
96
+ export interface ModuleTransformResult {
97
+ code: string;
98
+ moduleId: string;
99
+ }
100
+ /** The moduleId velo:transform derives for a module (appDir-relative, no ext). */
101
+ export declare function moduleIdFor(appDir: string, id: string): string;
102
+ /**
103
+ * The exact module transform velo:transform applies — one pipeline, two users:
104
+ * the build itself and the server->client leak diagnostic (which must see what
105
+ * travels, never a reprint that could diverge). Returns null when the module is
106
+ * out of scope or no transformation fired; the build then ships the source
107
+ * untouched.
108
+ */
109
+ export declare function transformModuleCode(code: string, options: ModuleTransformOptions): ModuleTransformResult | null;
110
+ /**
111
+ * The client entry's import specifiers — the same list the generated
112
+ * `virtual:velo/client-entry` module imports. Shared so the leak diagnostic
113
+ * starts its walk at exactly what the browser loads.
114
+ */
115
+ export declare function clientEntryModulePaths(appDir: string, veloConfig: VeloConfig): {
116
+ clientInit: string;
117
+ routes: string;
118
+ framework: string;
119
+ };
84
120
  /**
85
121
  * @hono/vite-dev-server treats `exclude` as all-or-nothing: passing one
86
122
  * replaces the defaults entirely. We compose instead — the upstream defaults