@webjsdev/server 0.8.45 → 0.8.46
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 +1 -1
- package/src/action-route.js +57 -9
- package/src/actions.js +2 -2
- package/src/auth.js +1 -1
- package/src/body-limit.js +1 -1
- package/src/check.js +4 -4
- package/src/component-elision.js +3 -3
- package/src/component-scanner.js +2 -2
- package/src/cors.js +2 -2
- package/src/csp.js +1 -1
- package/src/csrf.js +1 -1
- package/src/dev-error.js +1 -1
- package/src/dev.js +6 -6
- package/src/elision-report.js +1 -1
- package/src/env-schema.js +1 -1
- package/src/file-storage.js +2 -2
- package/src/forwarded.js +1 -1
- package/src/headers.js +1 -1
- package/src/json.js +2 -2
- package/src/listener-bun.js +2 -2
- package/src/logger.js +1 -1
- package/src/node-version.js +1 -1
- package/src/redirects.js +1 -1
- package/src/serializer.js +2 -2
- package/src/testing.js +3 -3
- package/src/ts-strip.js +2 -2
- package/src/vendor.js +5 -5
package/package.json
CHANGED
package/src/action-route.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* The optional `route(action, opts?)` convenience adapter (#488).
|
|
3
3
|
*
|
|
4
|
-
* REST endpoints in
|
|
4
|
+
* REST endpoints in WebJs go through `route.ts` (the framework's first-class
|
|
5
5
|
* HTTP handler). The ALWAYS-WORKS baseline is a hand-written `route.ts` that
|
|
6
6
|
* imports a `'use server'` action and calls it:
|
|
7
7
|
*
|
|
@@ -28,6 +28,22 @@
|
|
|
28
28
|
* export const POST = route(createPost, { validate: createPostSchema });
|
|
29
29
|
* ```
|
|
30
30
|
*
|
|
31
|
+
* Passing the action's MODULE NAMESPACE instead of the function auto-applies the
|
|
32
|
+
* action's declared `export const middleware` and `export const validate`, so a
|
|
33
|
+
* guard declared once next to the action protects the REST boundary the same way
|
|
34
|
+
* it protects the RPC boundary (#876). This is the way to keep "declare once,
|
|
35
|
+
* protected on every entry point" true without re-listing the config:
|
|
36
|
+
*
|
|
37
|
+
* ```js
|
|
38
|
+
* // app/api/posts/route.ts
|
|
39
|
+
* import { route } from '@webjsdev/server';
|
|
40
|
+
* import * as postActions from '../../../modules/posts/actions/create-post.server.ts';
|
|
41
|
+
* export const POST = route(postActions); // its declared middleware + validate apply
|
|
42
|
+
* ```
|
|
43
|
+
*
|
|
44
|
+
* The function form still takes explicit `opts`, and an explicit `opts.middleware`
|
|
45
|
+
* / `opts.validate` overrides the module's declared config when both are given.
|
|
46
|
+
*
|
|
31
47
|
* Adapter rules when invoked over HTTP:
|
|
32
48
|
* - URL query params, route params (`ctx.params`), and the parsed JSON body
|
|
33
49
|
* (for a method with a body) are merged into a single object argument
|
|
@@ -51,6 +67,7 @@
|
|
|
51
67
|
import { runValidate } from './actions.js';
|
|
52
68
|
import { runWithActionSignal } from './action-signal.js';
|
|
53
69
|
import { runActionChain } from './action-middleware.js';
|
|
70
|
+
import { actionFunctionNames, actionMiddleware } from './action-config.js';
|
|
54
71
|
import { readTextBounded, payloadTooLarge, DEFAULT_MAX_BODY_BYTES } from './body-limit.js';
|
|
55
72
|
import { getBodyLimits } from './context.js';
|
|
56
73
|
|
|
@@ -69,16 +86,48 @@ function jsonBodyLimit() {
|
|
|
69
86
|
* Expose a plain `'use server'` action over REST as a `route.ts`-style handler.
|
|
70
87
|
*
|
|
71
88
|
* @template A, R
|
|
72
|
-
* @param {(input: A, ctx: { req: Request, params: Record<string,string> }) => R | Promise<R>}
|
|
73
|
-
* the server action to call
|
|
74
|
-
*
|
|
89
|
+
* @param {((input: A, ctx: { req: Request, params: Record<string,string> }) => R | Promise<R>) | Record<string, unknown>} actionOrModule
|
|
90
|
+
* the server action to call, OR the action's module namespace (`import * as m`)
|
|
91
|
+
* so its declared `middleware` / `validate` config is auto-applied (#876). Its
|
|
92
|
+
* source is unchanged; this only wraps the HTTP-to-action adapter around it.
|
|
75
93
|
* @param {{
|
|
76
94
|
* validate?: (input: any) => any,
|
|
77
95
|
* middleware?: Function[],
|
|
78
96
|
* }} [opts]
|
|
79
97
|
* @returns {(req: Request, ctx?: { params?: Record<string,string> }) => Promise<Response>}
|
|
80
98
|
*/
|
|
81
|
-
export function route(
|
|
99
|
+
export function route(actionOrModule, opts = {}) {
|
|
100
|
+
// Resolve the callable action plus any config the caller did not pass
|
|
101
|
+
// explicitly. A function is used verbatim (the original form). A module
|
|
102
|
+
// namespace exposes the single action function and its declared config, so
|
|
103
|
+
// `route(mod)` inherits the same `middleware` / `validate` the RPC boundary
|
|
104
|
+
// reads (#876). An explicit `opts` value still wins over the declared one.
|
|
105
|
+
let action;
|
|
106
|
+
/** @type {{ middleware?: Function[], validate?: Function }} */
|
|
107
|
+
let declared = {};
|
|
108
|
+
if (typeof actionOrModule === 'function') {
|
|
109
|
+
action = actionOrModule;
|
|
110
|
+
} else if (actionOrModule && typeof actionOrModule === 'object') {
|
|
111
|
+
const names = actionFunctionNames(actionOrModule);
|
|
112
|
+
if (names.length !== 1) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
`route(module) expects exactly one action function in the module, found ${names.length}` +
|
|
115
|
+
(names.length ? ` (${names.join(', ')})` : ''),
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
action = /** @type {Function} */ (actionOrModule[names[0]]);
|
|
119
|
+
declared = {
|
|
120
|
+
middleware: actionMiddleware(actionOrModule),
|
|
121
|
+
validate: typeof actionOrModule.validate === 'function'
|
|
122
|
+
? /** @type {Function} */ (actionOrModule.validate)
|
|
123
|
+
: undefined,
|
|
124
|
+
};
|
|
125
|
+
} else {
|
|
126
|
+
throw new Error('route() expects an action function or a module namespace');
|
|
127
|
+
}
|
|
128
|
+
const validate = opts.validate ?? declared.validate;
|
|
129
|
+
const middlewareChain = opts.middleware ?? declared.middleware ?? [];
|
|
130
|
+
|
|
82
131
|
return async function handler(req, ctx) {
|
|
83
132
|
const params = (ctx && ctx.params) || {};
|
|
84
133
|
const url = new URL(req.url);
|
|
@@ -102,13 +151,13 @@ export function route(action, opts = {}) {
|
|
|
102
151
|
}
|
|
103
152
|
|
|
104
153
|
let arg = { ...query, ...params, ...body };
|
|
105
|
-
if (typeof
|
|
154
|
+
if (typeof validate === 'function') {
|
|
106
155
|
// Run the validator through the SHARED contract (#245) so the REST adapter
|
|
107
156
|
// and the RPC path interpret a `{ success, fieldErrors }` envelope, a
|
|
108
157
|
// throw, and a transform-return identically. A structured failure becomes
|
|
109
158
|
// a 422 JSON; a throw stays a 400 (keeping a schema lib's `issues`); a
|
|
110
159
|
// transform-return replaces the input.
|
|
111
|
-
const v = runValidate(
|
|
160
|
+
const v = runValidate(validate, arg);
|
|
112
161
|
if (!v.ok) {
|
|
113
162
|
if (v.thrown !== undefined) {
|
|
114
163
|
const msg = v.result.error || 'Invalid input';
|
|
@@ -126,10 +175,9 @@ export function route(action, opts = {}) {
|
|
|
126
175
|
// The action runs inside the request signal scope (#492) and the per-action
|
|
127
176
|
// middleware chain (#490). `ranAction` distinguishes a real completion from
|
|
128
177
|
// a middleware short-circuit (the action never ran).
|
|
129
|
-
const middleware = opts.middleware || [];
|
|
130
178
|
let ranAction = false;
|
|
131
179
|
const result = await runWithActionSignal(req.signal, () =>
|
|
132
|
-
runActionChain(
|
|
180
|
+
runActionChain(middlewareChain, { request: req, args: [arg], signal: req.signal }, () => {
|
|
133
181
|
ranAction = true;
|
|
134
182
|
return action(/** @type any */ (arg), { req, params });
|
|
135
183
|
}));
|
package/src/actions.js
CHANGED
|
@@ -33,12 +33,12 @@ function jsonBodyLimit() {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
/**
|
|
36
|
-
* Internal RPC wire-format content type. Distinguishes
|
|
36
|
+
* Internal RPC wire-format content type. Distinguishes WebJs action
|
|
37
37
|
* responses from plain `application/json` so the stub can pick the right
|
|
38
38
|
* parser and external JSON consumers aren't confused.
|
|
39
39
|
*
|
|
40
40
|
* Uses the content type from the active serializer (defaults to
|
|
41
|
-
* `application/vnd.webjs+json` with the built-in
|
|
41
|
+
* `application/vnd.webjs+json` with the built-in WebJs serializer).
|
|
42
42
|
*/
|
|
43
43
|
export const RPC_CONTENT_TYPE = 'application/vnd.webjs+json';
|
|
44
44
|
|
package/src/auth.js
CHANGED
package/src/body-limit.js
CHANGED
|
@@ -252,7 +252,7 @@ export async function readBytesBounded(req, limit) {
|
|
|
252
252
|
/**
|
|
253
253
|
* Read a request body as text, bounded by `limit`. Used by the RPC endpoint,
|
|
254
254
|
* `readBody`, and the exposed-action REST path, all of which then parse the
|
|
255
|
-
* text (
|
|
255
|
+
* text (WebJs wire or JSON).
|
|
256
256
|
*
|
|
257
257
|
* @param {Request} req
|
|
258
258
|
* @param {number} limit max bytes (0 / negative = unlimited)
|
package/src/check.js
CHANGED
|
@@ -14,7 +14,7 @@ import { analyzeElision } from './component-elision.js';
|
|
|
14
14
|
import { RESERVED_CONFIG } from './action-config.js';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
|
-
* Convention validator for
|
|
17
|
+
* Convention validator for WebJs apps.
|
|
18
18
|
*
|
|
19
19
|
* Scans an app directory and reports correctness violations: things that
|
|
20
20
|
* crash the app, leak a secret, or fail the build / type-strip. Designed to be
|
|
@@ -380,7 +380,7 @@ function findBrowserMemberUses(code) {
|
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
/**
|
|
383
|
-
* Scan a
|
|
383
|
+
* Scan a WebJs app directory and report convention violations.
|
|
384
384
|
*
|
|
385
385
|
* Every rule is a correctness check (a crash, a security leak, or a
|
|
386
386
|
* build/type-strip failure), so they all run unconditionally. There is no
|
|
@@ -436,7 +436,7 @@ export async function checkConventions(appDir) {
|
|
|
436
436
|
// redactor leaves them alone.
|
|
437
437
|
if (!/class\s+\w+\s+extends\s+WebComponent/.test(scan)) continue;
|
|
438
438
|
// Accept either registration pattern:
|
|
439
|
-
// Counter.register('tag') (
|
|
439
|
+
// Counter.register('tag') (WebJs idiom)
|
|
440
440
|
// customElements.define('tag', Counter) (native)
|
|
441
441
|
if (/\b[A-Z][A-Za-z0-9_$]*\.register\s*\(\s*['"`]/.test(scan)) continue;
|
|
442
442
|
if (/\bcustomElements\.define\s*\(/.test(scan)) continue;
|
|
@@ -1208,7 +1208,7 @@ function findImportChain(graph, from, to) {
|
|
|
1208
1208
|
* @param {Violation[]} violations appended to in place
|
|
1209
1209
|
*/
|
|
1210
1210
|
async function checkServerImportInBrowserModule(appDir, violations) {
|
|
1211
|
-
// No `app/` directory means this is not a routable
|
|
1211
|
+
// No `app/` directory means this is not a routable WebJs app (e.g. a bare
|
|
1212
1212
|
// component library, or a fixture with only `lib/`); nothing ships, so the
|
|
1213
1213
|
// rule has nothing to police. Skip rather than do the heavy analysis.
|
|
1214
1214
|
if (!(await pathExists(join(appDir, 'app')))) return;
|
package/src/component-elision.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Static analyser deciding whether a
|
|
2
|
+
* Static analyser deciding whether a WebJs component module can be
|
|
3
3
|
* ELIDED from the browser, that is, never downloaded as JS because its
|
|
4
4
|
* SSR'd HTML is the complete, final output and the component does no
|
|
5
5
|
* client-side work.
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* packages/core/AGENTS.md and the MAINTAINER NOTE on the
|
|
35
35
|
* WebComponent lifecycle in packages/core/src/component.js both
|
|
36
36
|
* direct anyone adding an interactivity surface to update these
|
|
37
|
-
* lists, since
|
|
37
|
+
* lists, since WebJs development is largely AI-agent driven.
|
|
38
38
|
*/
|
|
39
39
|
|
|
40
40
|
import {
|
|
@@ -101,7 +101,7 @@ export const CLIENT_LIFECYCLE_HOOKS = [
|
|
|
101
101
|
'connectedCallback',
|
|
102
102
|
'disconnectedCallback',
|
|
103
103
|
'attributeChangedCallback',
|
|
104
|
-
// Standard custom-element callback
|
|
104
|
+
// Standard custom-element callback WebJs does not itself define (so it
|
|
105
105
|
// is absent from the prototype guard's CLASSIFICATION), but an author
|
|
106
106
|
// can still override it to do client work. Kept for conservatism.
|
|
107
107
|
'adoptedCallback',
|
package/src/component-scanner.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server-side scanner that walks the app tree and records the
|
|
3
|
-
* browser-visible URL for every
|
|
3
|
+
* browser-visible URL for every WebJs component module.
|
|
4
4
|
*
|
|
5
5
|
* Called once on the first request (lazily, via `ensureReady`), then memoized. Results are used to prime the core
|
|
6
6
|
* registry (`primeModuleUrl`) BEFORE any SSR render: so when a page
|
|
7
7
|
* renders a component tag, `lookupModuleUrl(tag)` already has the URL
|
|
8
8
|
* ready for `<link rel="modulepreload">` hints.
|
|
9
9
|
*
|
|
10
|
-
* The convention
|
|
10
|
+
* The convention WebJs uses is the web-standard one:
|
|
11
11
|
*
|
|
12
12
|
* class Counter extends WebComponent { … }
|
|
13
13
|
* customElements.define('my-counter', Counter);
|
package/src/cors.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Reusable CORS primitive for
|
|
2
|
+
* Reusable CORS primitive for WebJs.
|
|
3
3
|
*
|
|
4
|
-
* `cors(options)` returns a
|
|
4
|
+
* `cors(options)` returns a WebJs MIDDLEWARE `(req, next) => Response`,
|
|
5
5
|
* usable in `middleware.js` (root or per-segment) or wrapped around a
|
|
6
6
|
* `route.js` handler. This is the public app-facing API. The shared
|
|
7
7
|
* origin-resolution + header-building core (`resolveOrigin` /
|
package/src/csp.js
CHANGED
|
@@ -181,7 +181,7 @@ export function readCspConfig(pkg) {
|
|
|
181
181
|
export function mintNonce() {
|
|
182
182
|
const bytes = new Uint8Array(16);
|
|
183
183
|
crypto.getRandomValues(bytes);
|
|
184
|
-
// Buffer is available in every
|
|
184
|
+
// Buffer is available in every WebJs server runtime (Node 24+). Avoids a
|
|
185
185
|
// hand-rolled base64 of a binary string.
|
|
186
186
|
return Buffer.from(bytes).toString('base64');
|
|
187
187
|
}
|
package/src/csrf.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* `cop-middleware` and Go 1.25's `http.CrossOriginProtection` use, and the
|
|
5
5
|
* spiritual sibling of Next.js / Astro's Origin-vs-Host check.
|
|
6
6
|
*
|
|
7
|
-
* Why not a token cookie:
|
|
7
|
+
* Why not a token cookie: WebJs previously issued a per-request `webjs_csrf`
|
|
8
8
|
* double-submit cookie on every SSR response. That made SSR HTML
|
|
9
9
|
* un-cacheable at a CDN (a CDN skips a response with `Set-Cookie`, and a
|
|
10
10
|
* cached one would share / poison the token across visitors). A header check
|
package/src/dev-error.js
CHANGED
|
@@ -19,7 +19,7 @@ import { readFileSync } from 'node:fs';
|
|
|
19
19
|
/**
|
|
20
20
|
* Pull the first useful `file:line:column` out of an error stack. Prefers a
|
|
21
21
|
* frame under `appDir` (the user's own code) over a framework / node_modules
|
|
22
|
-
* frame, so the overlay points at the edit that broke, not
|
|
22
|
+
* frame, so the overlay points at the edit that broke, not WebJs internals.
|
|
23
23
|
*
|
|
24
24
|
* Handles the V8 stack forms `at fn (/abs/file.ts:12:5)`, `at /abs/file.ts:12:5`,
|
|
25
25
|
* and `at file:///abs/file.ts:12:5`.
|
package/src/dev.js
CHANGED
|
@@ -136,7 +136,7 @@ const MIME = {
|
|
|
136
136
|
* `namespace` with values, parameter properties, legacy decorators
|
|
137
137
|
* with `emitDecoratorMetadata`, `import = require`) throws at strip
|
|
138
138
|
* time. The `erasable-typescript-only` and `no-non-erasable-typescript`
|
|
139
|
-
* lint rules catch these at edit time.
|
|
139
|
+
* lint rules catch these at edit time. WebJs is buildless end-to-end:
|
|
140
140
|
* there is no bundler fallback.
|
|
141
141
|
*
|
|
142
142
|
* The transformed bytes are cached per request handler in `state.tsCache`
|
|
@@ -441,7 +441,7 @@ export async function readServerTimeoutsFromApp(appDir) {
|
|
|
441
441
|
}
|
|
442
442
|
|
|
443
443
|
/**
|
|
444
|
-
* Create a reusable, framework-agnostic request handler for a
|
|
444
|
+
* Create a reusable, framework-agnostic request handler for a WebJs app.
|
|
445
445
|
* The returned `handle(req)` takes a standard `Request` and resolves to a
|
|
446
446
|
* standard `Response`: suitable for Node http, Deno, Bun, Cloudflare Workers,
|
|
447
447
|
* or embedding inside an Express/Fastify app.
|
|
@@ -456,7 +456,7 @@ export async function readServerTimeoutsFromApp(appDir) {
|
|
|
456
456
|
* }} opts
|
|
457
457
|
*/
|
|
458
458
|
export async function createRequestHandler(opts) {
|
|
459
|
-
// Preflight:
|
|
459
|
+
// Preflight: WebJs needs Node 24+ (built-in TS strip + recursive fs.watch).
|
|
460
460
|
// Throw a clear Error here so an embedded host (Express/Fastify/Bun/Deno)
|
|
461
461
|
// gets the actionable message at boot, not a cryptic API failure mid-request.
|
|
462
462
|
assertNodeVersion({ onFail: 'throw' });
|
|
@@ -1464,7 +1464,7 @@ export async function createRequestHandler(opts) {
|
|
|
1464
1464
|
}
|
|
1465
1465
|
|
|
1466
1466
|
/**
|
|
1467
|
-
* Start a
|
|
1467
|
+
* Start a WebJs HTTP server. Thin wrapper around `createRequestHandler`.
|
|
1468
1468
|
*
|
|
1469
1469
|
* Speaks plain HTTP/1.1. TLS termination + HTTP/2 to the browser is
|
|
1470
1470
|
* expected to be handled by a reverse proxy (PaaS edge, nginx, Caddy,
|
|
@@ -2138,7 +2138,7 @@ async function loadMiddleware(appDir, dev, logger) {
|
|
|
2138
2138
|
* @param {import('./logger.js').Logger} logger
|
|
2139
2139
|
*/
|
|
2140
2140
|
/**
|
|
2141
|
-
* Create a plain HTTP/1.1 server.
|
|
2141
|
+
* Create a plain HTTP/1.1 server. WebJs deploys are expected to sit
|
|
2142
2142
|
* behind a reverse proxy (PaaS edge, nginx, Caddy, etc.) that handles
|
|
2143
2143
|
* TLS termination and speaks HTTP/2 to clients: Node's http2 module
|
|
2144
2144
|
* doesn't need to be involved on the framework side.
|
|
@@ -2327,7 +2327,7 @@ async function exists(p) {
|
|
|
2327
2327
|
* dev server returns the error to the caller. The
|
|
2328
2328
|
* `erasable-typescript-only` and `no-non-erasable-typescript` lint
|
|
2329
2329
|
* rules catch these at edit time. There is no bundler fallback;
|
|
2330
|
-
*
|
|
2330
|
+
* WebJs is buildless end-to-end.
|
|
2331
2331
|
*
|
|
2332
2332
|
* The backend is the runtime-appropriate stripper (#508): Node 24+'s built-in
|
|
2333
2333
|
* `module.stripTypeScriptTypes`, or `amaro` on Bun (byte-identical, equally
|
package/src/elision-report.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* App-level elision report (#646).
|
|
3
3
|
*
|
|
4
4
|
* A reporting layer over the `analyzeElision` verdict, NOT a second analysis
|
|
5
|
-
* and NOT a build (
|
|
5
|
+
* and NOT a build (WebJs is no-build; elision is the server's analysis pass,
|
|
6
6
|
* run at dev-server start and re-derived after each fs.watch rebuild). It
|
|
7
7
|
* builds the module graph, runs `analyzeElision`, and returns the page / layout
|
|
8
8
|
* route modules that SHIP WHOLE to the browser, each with the first
|
package/src/env-schema.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Startup env-var validation with a typed schema hook (issue #236).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* WebJs auto-loads `<appDir>/.env` into `process.env` at boot, but does no
|
|
5
5
|
* validation, so a missing or misconfigured required var (DATABASE_URL,
|
|
6
6
|
* AUTH_SECRET, ...) fails late and cryptically (a database connect error
|
|
7
7
|
* mid-request, an undefined secret signing a token). This module adds an
|
package/src/file-storage.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* File storage primitive: a pluggable `FileStore` with a streaming local-disk
|
|
3
3
|
* default adapter (issue #247).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* WebJs already round-trips a native `File` / `Blob` / `FormData` over the RPC
|
|
6
6
|
* wire (the serializer), but nothing decided WHERE the bytes land. This module
|
|
7
7
|
* is that answer. The model mirrors `cache.js` exactly: a documented interface,
|
|
8
8
|
* a default adapter (`diskStore`), and a module singleton (`setFileStore` /
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
* (`File` / `Blob` / `ReadableStream` / `Uint8Array` in, a `{ body, size,
|
|
36
36
|
* contentType }` handle out), so an S3 / R2 / GCS adapter is a drop-in: it
|
|
37
37
|
* implements the same `put` / `get` / `delete` / `url` and the call sites do
|
|
38
|
-
* not change.
|
|
38
|
+
* not change. WebJs ships no S3 SDK; see the JSDoc on `FileStore` below.
|
|
39
39
|
*
|
|
40
40
|
* @module file-storage
|
|
41
41
|
*/
|
package/src/forwarded.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Build a full URL from a Node IncomingMessage, respecting standard
|
|
3
3
|
* reverse-proxy headers (`X-Forwarded-Proto`, `X-Forwarded-Host`).
|
|
4
4
|
*
|
|
5
|
-
* Why:
|
|
5
|
+
* Why: WebJs apps are almost always deployed behind a reverse proxy
|
|
6
6
|
* (Railway, Fly, Render, Vercel, Cloudflare, nginx, Caddy, Traefik -
|
|
7
7
|
* see the no-build architecture docs). The proxy terminates TLS and
|
|
8
8
|
* speaks plain HTTP/1.1 to the container, so `req.url` inside the
|
package/src/headers.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Secure-by-default response headers plus a small declarative per-path
|
|
3
3
|
* header config (issue #232).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* WebJs sets a baseline of standard security headers on every document
|
|
6
6
|
* and asset response, so a scaffolded app is not clickjackable or
|
|
7
7
|
* MIME-sniffable out of the box (no reverse proxy required for the
|
|
8
8
|
* baseline). The defaults are LITERAL HTTP headers, no abstraction.
|
package/src/json.js
CHANGED
|
@@ -15,7 +15,7 @@ import { readTextBounded, BodyLimitError, DEFAULT_MAX_BODY_BYTES } from './body-
|
|
|
15
15
|
* The helper reads the in-flight Request from the AsyncLocalStorage
|
|
16
16
|
* request context. If the caller sent `Accept: application/vnd.webjs+json`
|
|
17
17
|
* (e.g. via the `richFetch` client helper), the response body is
|
|
18
|
-
* encoded with the
|
|
18
|
+
* encoded with the WebJs serializer so rich types (Date, Map, Set,
|
|
19
19
|
* BigInt, TypedArrays, Blob, File, FormData, cycles) survive. Otherwise
|
|
20
20
|
* the response is plain `application/json`, unchanged behaviour for
|
|
21
21
|
* curl / external consumers.
|
|
@@ -47,7 +47,7 @@ export async function json(data, init = {}) {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
/**
|
|
50
|
-
* Parse a request body using the
|
|
50
|
+
* Parse a request body using the WebJs serializer when the client sent
|
|
51
51
|
* our content type, otherwise as plain JSON. Handy for route handlers
|
|
52
52
|
* that accept rich bodies from the `richFetch` helper but plain JSON
|
|
53
53
|
* from everyone else.
|
package/src/listener-bun.js
CHANGED
|
@@ -73,7 +73,7 @@ export function startBunListener(ctx) {
|
|
|
73
73
|
const server = Bun.serve({
|
|
74
74
|
port,
|
|
75
75
|
idleTimeout: bunIdleTimeout(timeouts),
|
|
76
|
-
//
|
|
76
|
+
// WebJs owns its dev error overlay (over SSE) and handles thrown errors in
|
|
77
77
|
// `fetch`, so Bun's own development error page must never interfere; keep it
|
|
78
78
|
// deterministic (off) rather than tied to NODE_ENV.
|
|
79
79
|
development: false,
|
|
@@ -172,7 +172,7 @@ export function startBunListener(ctx) {
|
|
|
172
172
|
* the `open` handler; messages/close are forwarded to its emitter from the
|
|
173
173
|
* `websocket.message` / `websocket.close` handlers.
|
|
174
174
|
*
|
|
175
|
-
* Only `'message'` and `'close'` are forwarded (the events
|
|
175
|
+
* Only `'message'` and `'close'` are forwarded (the events WebJs WS handlers and
|
|
176
176
|
* `broadcast()` use). Bun exposes no per-socket `'error'` / `'ping'` / `'pong'`
|
|
177
177
|
* handler to bridge; a socket failure on Bun surfaces as a `'close'`, so
|
|
178
178
|
* `'close'`-keyed cleanup still runs. Documented in the WebSockets doc.
|
package/src/logger.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Minimal pluggable logger.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* WebJs doesn't pick pino/winston/bunyan for you. The default in prod emits
|
|
5
5
|
* one JSON object per line to stdout: trivially ingestable by any log
|
|
6
6
|
* aggregator. In dev, lines are plain text for readability.
|
|
7
7
|
*
|
package/src/node-version.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Node-version preflight guard (issue #238).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* WebJs depends on Node 24+ built-ins: `module.stripTypeScriptTypes` (the
|
|
5
5
|
* no-build TypeScript strip) and recursive `fs.watch` (dev live-reload). On an
|
|
6
6
|
* older Node the failure surfaces late and cryptically (a strip error or a
|
|
7
7
|
* missing API deep inside a request), not as a clear "you need Node 24+". This
|
package/src/redirects.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Declarative permanent / temporary redirects (issue #254).
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* WebJs already ships `redirect(url)` (a per-request throw sentinel) for
|
|
5
5
|
* imperative, request-time redirects. This module adds the missing
|
|
6
6
|
* DECLARATIVE surface: a config of old-path -> new-path rules an app
|
|
7
7
|
* declares once and the framework applies at the very start of request
|
package/src/serializer.js
CHANGED
|
@@ -2,7 +2,7 @@ import { stringify, parse } from '@webjsdev/core';
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* @typedef {Object} Serializer
|
|
5
|
-
* A pluggable serializer that controls how
|
|
5
|
+
* A pluggable serializer that controls how WebJs server actions encode and
|
|
6
6
|
* decode values on the RPC wire.
|
|
7
7
|
*
|
|
8
8
|
* **AI hint:** The default serializer uses webjs's built-in
|
|
@@ -50,7 +50,7 @@ let current = defaultSerializer;
|
|
|
50
50
|
*
|
|
51
51
|
* **AI hint:** Use this in server-side code that needs to encode or decode
|
|
52
52
|
* RPC payloads. It returns whatever serializer was set via `setSerializer`,
|
|
53
|
-
* or the default
|
|
53
|
+
* or the default WebJs serializer if none was set.
|
|
54
54
|
*
|
|
55
55
|
* @returns {Serializer}
|
|
56
56
|
*/
|
package/src/testing.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Server-side test harness helpers (issue #267).
|
|
3
3
|
*
|
|
4
|
-
* `createRequestHandler(...).handle(request)` drives the FULL
|
|
4
|
+
* `createRequestHandler(...).handle(request)` drives the FULL WebJs pipeline
|
|
5
5
|
* (middleware, routing, SSR, page actions, server-action RPC, auth + CSRF), and
|
|
6
6
|
* the framework's own suite already drives it. These helpers are THIN builders
|
|
7
7
|
* over the native `Request` / `Response` around that `handle()`, so an app test
|
|
@@ -350,7 +350,7 @@ export async function rawActionRequest(app, serverFilePath, fnName, args = [], o
|
|
|
350
350
|
}
|
|
351
351
|
|
|
352
352
|
/**
|
|
353
|
-
* Browser-test harness (#806): build a
|
|
353
|
+
* Browser-test harness (#806): build a WebJs handler that a `web-test-runner`
|
|
354
354
|
* config can proxy module requests to, so a `test/**/browser/*.test.js` file
|
|
355
355
|
* can import a real `.ts` component that imports a `'use server'` action and it
|
|
356
356
|
* loads + hydrates in a real browser. Plain WTR serves raw TS with no webjs
|
|
@@ -369,7 +369,7 @@ export async function rawActionRequest(app, serverFilePath, fnName, args = [], o
|
|
|
369
369
|
* of `@webjsdev/server/testing` stays light (they pull the full server + the
|
|
370
370
|
* `ws` WebSocket subsystem).
|
|
371
371
|
*
|
|
372
|
-
* @param {string} appDir the
|
|
372
|
+
* @param {string} appDir the WebJs app root (usually `process.cwd()`)
|
|
373
373
|
* @returns {Promise<{ handle: Handle, warmup: () => Promise<void>, importmapHtml: () => string }>}
|
|
374
374
|
*/
|
|
375
375
|
export async function createBrowserTestHandler(appDir) {
|
package/src/ts-strip.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Pluggable TypeScript stripper (#508), the one seam that lets
|
|
2
|
+
* Pluggable TypeScript stripper (#508), the one seam that lets WebJs run on both
|
|
3
3
|
* Node and Bun.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* WebJs serves `.ts` / `.mts` source to the browser as JavaScript by ERASING the
|
|
6
6
|
* type syntax in place (position-preserving whitespace replacement, so no
|
|
7
7
|
* sourcemap is shipped and stack traces stay byte-exact). On Node 24+ that is the
|
|
8
8
|
* built-in `module.stripTypeScriptTypes`. Bun has no such built-in, so this
|
package/src/vendor.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Resolve bare npm imports to browser-loadable URLs via jspm.io.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* WebJs follows the Rails 7 + importmap-rails posture exactly. When user
|
|
5
5
|
* code imports a bare specifier (e.g. `import dayjs from 'dayjs'`), the
|
|
6
6
|
* browser can't resolve it natively. The framework's job is to emit an
|
|
7
7
|
* importmap that translates each bare specifier to a real URL.
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* importmap: { "dayjs": "https://ga.jspm.io/npm:dayjs@1.11.13/index.js" }
|
|
12
12
|
*
|
|
13
|
-
* The browser fetches the bundle directly from jspm.io. The
|
|
13
|
+
* The browser fetches the bundle directly from jspm.io. The WebJs server
|
|
14
14
|
* does not proxy, cache, or bundle anything. jspm.io has done the work
|
|
15
15
|
* server-side (CJS-to-ESM conversion, transitive bundling, browser
|
|
16
16
|
* polyfills).
|
|
@@ -85,7 +85,7 @@ const FRAMEWORK_SERVER_ONLY = new Set(['@webjsdev/cli', '@webjsdev/server', '@we
|
|
|
85
85
|
* Excludes:
|
|
86
86
|
* - `node_modules`, `.webjs`, `public` directories
|
|
87
87
|
* - Any directory starting with `_` (webjs `_private/` convention)
|
|
88
|
-
* - `test/` and `tests/` (server-only by
|
|
88
|
+
* - `test/` and `tests/` (server-only by WebJs convention)
|
|
89
89
|
* - Files whose name marks them as server-only:
|
|
90
90
|
* * `*.server.{js,ts,mjs,mts}` (path-level boundary)
|
|
91
91
|
* * `route.{js,ts,mjs,mts}` (file-router HTTP handler)
|
|
@@ -1030,7 +1030,7 @@ export async function readPinFile(appDir) {
|
|
|
1030
1030
|
* When `integrity` is provided and non-empty, it's included alongside
|
|
1031
1031
|
* `imports` as a sibling key (matching the browser importmap-integrity
|
|
1032
1032
|
* spec: a flat `{url: 'sha384-...'}` map). Omitted entirely when empty
|
|
1033
|
-
* so older
|
|
1033
|
+
* so older WebJs versions read the file as before.
|
|
1034
1034
|
*
|
|
1035
1035
|
* `provider` is persisted alongside imports when non-default. It lets
|
|
1036
1036
|
* `webjs vendor update` know which CDN to re-resolve against, and
|
|
@@ -1343,7 +1343,7 @@ export async function pinAll(appDir, opts = {}) {
|
|
|
1343
1343
|
}
|
|
1344
1344
|
|
|
1345
1345
|
// The app legitimately has zero bare-specifier imports (or the
|
|
1346
|
-
// scanner is running outside a
|
|
1346
|
+
// scanner is running outside a WebJs project). Don't create an
|
|
1347
1347
|
// empty `.webjs/vendor/importmap.json`. Without this guard the file
|
|
1348
1348
|
// gets written as `{ imports: {} }` in whatever cwd the CLI was
|
|
1349
1349
|
// invoked from, then immediately rejected by readPinFile's empty
|