@webjsdev/server 0.8.58 → 0.8.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/index.d.ts +125 -12
- package/index.js +2 -0
- package/package.json +3 -3
- package/src/action-seed-bun.js +2 -2
- package/src/action-seed.js +438 -70
- package/src/actions.js +10 -2
- package/src/base-path.js +4 -1
- package/src/body-limit.js +4 -4
- package/src/check.js +123 -2
- package/src/component-elision.js +75 -16
- package/src/component-scanner.js +82 -26
- package/src/context.js +1 -1
- package/src/csp.js +22 -6
- package/src/csrf.js +33 -12
- package/src/dev-error.js +13 -2
- package/src/dev-overlay.js +218 -6
- package/src/dev.js +151 -33
- package/src/elision-differential.js +101 -0
- package/src/elision-report.js +133 -30
- package/src/form-action-identity.js +290 -0
- package/src/form-dispatch.js +666 -0
- package/src/forwarded.js +68 -10
- package/src/headers.js +9 -7
- package/src/html-cache.js +117 -13
- package/src/js-scan.js +41 -13
- package/src/listener-bun.js +1 -1
- package/src/listener-core.js +35 -5
- package/src/rate-limit.js +31 -3
- package/src/route-types.js +1 -1
- package/src/ssr.js +93 -17
- package/src/stream.js +1 -1
- package/src/testing.d.ts +19 -0
- package/src/testing.js +113 -0
- package/src/vendor.js +85 -22
- package/webjs-config.schema.json +19 -3
- package/src/page-action.js +0 -259
package/index.d.ts
CHANGED
|
@@ -478,25 +478,114 @@ export declare function primeComponentRegistry(
|
|
|
478
478
|
): Promise<{ count: number }>;
|
|
479
479
|
/** Extract `{ className, tag }` pairs from a component module's source. */
|
|
480
480
|
export declare function extractComponents(src: string): Array<{ className: string; tag: string }>;
|
|
481
|
-
/**
|
|
481
|
+
/**
|
|
482
|
+
* Find ORPHAN component classes: a `class X extends WebComponent` that its own
|
|
483
|
+
* file declares and NOTHING in the app registers with a LITERAL tag, either
|
|
484
|
+
* because there is no registration call anywhere or because the tag is
|
|
485
|
+
* computed. The registration cross-reference is app-wide, so a class a sibling
|
|
486
|
+
* module registers is not an orphan. Neither is visible to the
|
|
487
|
+
* scanner, so both always lose the elision verdict, the tag-to-module registry
|
|
488
|
+
* entry, and the preload hint; assume the element does not upgrade either.
|
|
489
|
+
* This is about REGISTRATION, never about import reachability. The full
|
|
490
|
+
* semantics, including the one case where a computed tag still upgrades, live
|
|
491
|
+
* on `findOrphanComponents` in `src/component-scanner.js`.
|
|
492
|
+
*/
|
|
482
493
|
export declare function findOrphanComponents(
|
|
483
494
|
appDir: string,
|
|
484
495
|
): Promise<Array<{ className: string; file: string }>>;
|
|
485
496
|
|
|
486
497
|
// ---------------------------------------------------------------------------
|
|
487
|
-
// elision-report.js (#646)
|
|
498
|
+
// elision-report.js (#646, #1308)
|
|
488
499
|
// ---------------------------------------------------------------------------
|
|
489
500
|
|
|
501
|
+
/** Which rule in the analyser forced a component to ship. First match wins. */
|
|
502
|
+
export type ElisionEvidence = 'own' | 'observed' | 'closure' | 'render' | 'import' | 'unreadable';
|
|
503
|
+
|
|
504
|
+
/** One component module's verdict. `evidence` / `reason` / `by` are null when elided. */
|
|
505
|
+
export interface ElisionComponentRow {
|
|
506
|
+
/** App-relative path of the component module. */
|
|
507
|
+
file: string;
|
|
508
|
+
/** Every tag this file registers, sorted. */
|
|
509
|
+
tags: string[];
|
|
510
|
+
verdict: 'elided' | 'shipped';
|
|
511
|
+
evidence: ElisionEvidence | null;
|
|
512
|
+
reason: string | null;
|
|
513
|
+
/** App-relative path of the module that forced the ship, where one did. */
|
|
514
|
+
by: string | null;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** One page/layout route module's verdict. */
|
|
518
|
+
export interface ElisionRouteModuleRow {
|
|
519
|
+
file: string;
|
|
520
|
+
verdict: 'inert' | 'import-only' | 'shipped';
|
|
521
|
+
/** For `import-only`: the component modules the boot emits in its place. */
|
|
522
|
+
emits: string[];
|
|
523
|
+
/** For `shipped`: the first client-effecting blocker, or null when its own code is the cause. */
|
|
524
|
+
blocker: string | null;
|
|
525
|
+
reason: string | null;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/** A `class X extends WebComponent` with no literal-tag registration. */
|
|
529
|
+
export interface ElisionOrphanRow {
|
|
530
|
+
file: string;
|
|
531
|
+
className: string;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
export interface ElisionSummary {
|
|
535
|
+
components: number;
|
|
536
|
+
elided: number;
|
|
537
|
+
shipped: number;
|
|
538
|
+
routeModules: number;
|
|
539
|
+
inert: number;
|
|
540
|
+
importOnly: number;
|
|
541
|
+
shippedWhole: number;
|
|
542
|
+
orphans: number;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/** The whole app-level elision verdict. Every path is app-relative; every array is sorted by `file`. */
|
|
546
|
+
export interface ElisionReport {
|
|
547
|
+
analysed: boolean;
|
|
548
|
+
/** Why nothing was analysed, when `analysed` is false. */
|
|
549
|
+
skipped: 'no-app' | 'elide-off' | 'unanalysable' | null;
|
|
550
|
+
components: ElisionComponentRow[];
|
|
551
|
+
routeModules: ElisionRouteModuleRow[];
|
|
552
|
+
orphans: ElisionOrphanRow[];
|
|
553
|
+
summary: ElisionSummary;
|
|
554
|
+
}
|
|
555
|
+
|
|
490
556
|
/**
|
|
491
|
-
* App-level elision report
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
557
|
+
* App-level elision report, BOTH directions (#646, #1308): every component
|
|
558
|
+
* module as elided or shipped (a shipped one naming the evidence and the module
|
|
559
|
+
* that forced it), every page/layout as inert / import-only / shipped (a
|
|
560
|
+
* shipped one naming the first client-effecting blocker), and every orphan
|
|
561
|
+
* component class that gets no verdict at all. A reporting layer over
|
|
562
|
+
* `analyzeElision`, never a second analysis and never a build artifact.
|
|
563
|
+
* Consumed by `webjs elision`, the MCP `list_elision` tool, and both `webjs
|
|
564
|
+
* doctor` elision checks.
|
|
496
565
|
*/
|
|
497
|
-
export declare function analyzeAppElision(
|
|
498
|
-
|
|
499
|
-
|
|
566
|
+
export declare function analyzeAppElision(appDir: string): Promise<ElisionReport>;
|
|
567
|
+
|
|
568
|
+
// ---------------------------------------------------------------------------
|
|
569
|
+
// elision-differential.js (#1308)
|
|
570
|
+
// ---------------------------------------------------------------------------
|
|
571
|
+
|
|
572
|
+
/**
|
|
573
|
+
* Mask the JS-loaded set (importmap, boot script, modulepreload hints, vendor
|
|
574
|
+
* connection hints, content stamps) out of an SSR response so a diff sees only
|
|
575
|
+
* observable output. The ONE definition shared by the framework's own
|
|
576
|
+
* differential guard and `webjs elision --verify`, so an app can diff its own
|
|
577
|
+
* two captures with exactly the framework's comparator.
|
|
578
|
+
*/
|
|
579
|
+
export declare function maskJsSet(html: string): string;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* The URL paths of every STATIC page route in a `buildRouteTable` result,
|
|
583
|
+
* sorted and deduped. Dynamic routes are excluded (rendering one would mean
|
|
584
|
+
* inventing param values).
|
|
585
|
+
*/
|
|
586
|
+
export declare function staticPageRoutes(table: {
|
|
587
|
+
pages?: Array<{ routeDir?: string; paramNames?: string[] }>;
|
|
588
|
+
}): string[];
|
|
500
589
|
|
|
501
590
|
// ---------------------------------------------------------------------------
|
|
502
591
|
// context.js (per-request context helpers)
|
|
@@ -537,11 +626,26 @@ export declare function rateLimit(opts?: {
|
|
|
537
626
|
key?: string | ((req: Request) => string | Promise<string>);
|
|
538
627
|
message?: string;
|
|
539
628
|
store?: CacheStore;
|
|
629
|
+
/**
|
|
630
|
+
* Bucket on the forwarded-IP headers (`X-Forwarded-For`, `CF-Connecting-IP`,
|
|
631
|
+
* `X-Real-IP`) instead of the framework-stamped peer. Inert while
|
|
632
|
+
* `WEBJS_NO_TRUST_PROXY=1` is set: that env switch is the operator's
|
|
633
|
+
* statement about the topology and it outranks this option, so the limiter
|
|
634
|
+
* falls back to the stamped peer and logs once. Only safe behind a proxy
|
|
635
|
+
* that STRIPS inbound `X-Forwarded-For` before adding its own.
|
|
636
|
+
*/
|
|
540
637
|
trustProxy?: boolean;
|
|
541
638
|
}): Middleware;
|
|
542
639
|
/** Parse a window string (`'1m'`, `'30s'`) to milliseconds. */
|
|
543
640
|
export declare function parseWindow(w: number | string): number;
|
|
544
|
-
/**
|
|
641
|
+
/**
|
|
642
|
+
* Resolve the client IP for a request (honoring proxy-trust posture).
|
|
643
|
+
*
|
|
644
|
+
* Defaults to the framework-stamped peer, or `_anon_` when there is none.
|
|
645
|
+
* `trustProxy: true` reads the forwarded-IP headers instead, UNLESS
|
|
646
|
+
* `WEBJS_NO_TRUST_PROXY=1` is set, which overrides the option back to the
|
|
647
|
+
* stamped peer and logs once per process.
|
|
648
|
+
*/
|
|
545
649
|
export declare function clientIp(req: Request, opts?: { trustProxy?: boolean }): string;
|
|
546
650
|
/** Stamp the socket remote address onto a request for `clientIp` to read. */
|
|
547
651
|
export declare function stampRemoteIp(req: Request, remoteAddress: string): void;
|
|
@@ -613,7 +717,16 @@ export declare function cache<T extends (...args: any[]) => Promise<any>>(
|
|
|
613
717
|
export declare function revalidateTag(tag: string): Promise<void>;
|
|
614
718
|
/** Evict every `cache()` entry tagged with any of `tags`. */
|
|
615
719
|
export declare function revalidateTags(tags: string[]): Promise<void>;
|
|
616
|
-
/**
|
|
720
|
+
/**
|
|
721
|
+
* Evict the cached HTML for a path (the next request re-renders).
|
|
722
|
+
*
|
|
723
|
+
* Keys carry the request origin, so a bare path resolves it from the calling
|
|
724
|
+
* request, which is exact from a server action. Pass an absolute url
|
|
725
|
+
* (`https://app.example/blog`) from a caller with no request in scope (a cron
|
|
726
|
+
* job, a queue worker); a bare path there warns and evicts nothing. Under
|
|
727
|
+
* `webjs.basePath` the absolute url is the PUBLIC one and its mount prefix is
|
|
728
|
+
* stripped, so it evicts the entry the write actually stored.
|
|
729
|
+
*/
|
|
617
730
|
export declare function revalidatePath(path: string): Promise<void>;
|
|
618
731
|
/** Evict all cached HTML (per-process generation bump). */
|
|
619
732
|
export declare function revalidateAll(): Promise<void>;
|
package/index.js
CHANGED
|
@@ -44,6 +44,7 @@ export {
|
|
|
44
44
|
export { buildModuleGraph, transitiveDeps } from './src/module-graph.js';
|
|
45
45
|
export { scanComponents, primeComponentRegistry, extractComponents, findOrphanComponents } from './src/component-scanner.js';
|
|
46
46
|
export { analyzeAppElision } from './src/elision-report.js';
|
|
47
|
+
export { maskJsSet, staticPageRoutes } from './src/elision-differential.js';
|
|
47
48
|
export { headers, cookies, getRequest, withRequest, cspNonce, requestId } from './src/context.js';
|
|
48
49
|
export { defaultLogger } from './src/logger.js';
|
|
49
50
|
export { rateLimit, parseWindow, clientIp, stampRemoteIp } from './src/rate-limit.js';
|
|
@@ -75,6 +76,7 @@ export {
|
|
|
75
76
|
withCookies,
|
|
76
77
|
withSessionCookie,
|
|
77
78
|
loginAndGetCookies,
|
|
79
|
+
submitForm,
|
|
78
80
|
actionEndpoint,
|
|
79
81
|
invokeActionForTest,
|
|
80
82
|
rawActionRequest,
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@webjsdev/server",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.60",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "The server for WebJs, a full-stack JavaScript framework built on web components with server-side rendering and no build step. Provides the file-based router, SSR, server actions, route handlers, middleware, and live reload on Node 24+ or Bun.",
|
|
6
6
|
"main": "index.js",
|
|
7
7
|
"types": "./index.d.ts",
|
|
8
8
|
"exports": {
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"README.md"
|
|
29
29
|
],
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@webjsdev/core": "^0.7.
|
|
31
|
+
"@webjsdev/core": "^0.7.48",
|
|
32
32
|
"ws": "^8.20.0"
|
|
33
33
|
},
|
|
34
34
|
"publishConfig": {
|
package/src/action-seed-bun.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Bun has no `module.registerHooks`, so the seed facade is installed via a
|
|
5
5
|
* `Bun.plugin` `onLoad`, the Bun analog of the Node load hook in
|
|
6
|
-
* `action-seed.js`. This module is dynamically imported by `
|
|
6
|
+
* `action-seed.js`. This module is dynamically imported by `registerActionHooks`
|
|
7
7
|
* ONLY when `serverRuntime()` is `'bun'`, so the `Bun.*` global is never
|
|
8
8
|
* referenced on Node (the same isolation as `listener-bun.js`).
|
|
9
9
|
*
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
|
|
29
29
|
/**
|
|
30
30
|
* Install the `Bun.plugin` seed `onLoad`. Idempotency is the caller's
|
|
31
|
-
* responsibility (`
|
|
31
|
+
* responsibility (`registerActionHooks` guards on `_registered`).
|
|
32
32
|
*
|
|
33
33
|
* @param {{
|
|
34
34
|
* isSeedCandidate: (specifier: string) => boolean,
|