@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 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
- /** Find component classes that no page / component imports (orphans). */
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: the page/layout route modules that SHIP WHOLE to
492
- * the browser instead of being elided as carriers, each with the first
493
- * client-effecting blocker that pins it (or `null` when the module's own code
494
- * is the cause) plus a human reason. A reporting layer over `analyzeElision`,
495
- * consumed by the `webjs doctor` carrier-hygiene advisory (#646).
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
- appDir: string,
499
- ): Promise<{ analysed: boolean; shipped: Array<{ file: string; blocker: string | null; reason: string }> }>;
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
- /** Resolve the client IP for a request (honoring proxy-trust posture). */
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
- /** Evict the cached HTML for a path (the next request re-renders). */
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.58",
3
+ "version": "0.8.60",
4
4
  "type": "module",
5
- "description": "webjs dev/prod server: SSR, router, API, server actions, live reload",
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.46",
31
+ "@webjsdev/core": "^0.7.48",
32
32
  "ws": "^8.20.0"
33
33
  },
34
34
  "publishConfig": {
@@ -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 `registerSeedHooks`
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 (`registerSeedHooks` guards on `_registered`).
31
+ * responsibility (`registerActionHooks` guards on `_registered`).
32
32
  *
33
33
  * @param {{
34
34
  * isSeedCandidate: (specifier: string) => boolean,