@webjsdev/server 0.9.0 → 0.10.0
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 +4 -1
- package/index.d.ts +427 -109
- package/index.js +8 -4
- package/package.json +3 -3
- package/src/action-flash.js +185 -0
- package/src/action-params.js +156 -0
- package/src/action-route.js +8 -9
- package/src/action-seed.js +44 -5
- package/src/actions.js +96 -14
- package/src/api.js +67 -10
- package/src/app-layout.js +133 -0
- package/src/asset-hash.js +82 -49
- package/src/auth.js +612 -108
- package/src/browser-entries.js +2 -17
- package/src/build-epoch.js +1 -1
- package/src/cache-tags.js +5 -1
- package/src/check/rules-actions.js +37 -1
- package/src/check/rules-components.js +114 -40
- package/src/check/rules-imports.js +175 -4
- package/src/check/rules-route-modules.js +200 -0
- package/src/check/rules-routes.js +55 -0
- package/src/check/rules-routing.js +143 -95
- package/src/check/rules-typescript.js +24 -2
- package/src/check/rules.js +104 -9
- package/src/check/runner-support.js +53 -171
- package/src/check/runner.js +22 -6
- package/src/client-signals.js +171 -0
- package/src/component-scanner.js +22 -16
- package/src/context.js +152 -16
- package/src/cookie-store.js +262 -0
- package/src/core-exports.js +190 -0
- package/src/dev/bun-app-source.js +95 -5
- package/src/dev/config.js +7 -51
- package/src/dev/dev-service-worker.js +48 -0
- package/src/dev/esm-hot-hooks.js +373 -0
- package/src/dev/handler.js +502 -155
- package/src/dev/helpers.js +127 -96
- package/src/dev/hot-host.js +33 -6
- package/src/dev/poke-state.js +110 -0
- package/src/dev/serve.js +177 -129
- package/src/dev/server.js +92 -33
- package/src/dev/specifier-versions.js +125 -0
- package/src/dev/watch-ignore.js +49 -4
- package/src/dev/watch-poll.js +78 -0
- package/src/dev/watch-tree.js +59 -22
- package/src/dev-classify.js +59 -26
- package/src/dev-error.js +104 -3
- package/src/dev-hot-classify.js +291 -0
- package/src/dev-hot-client.js +245 -0
- package/src/dev-import.js +22 -5
- package/src/dev-overlay.js +26 -0
- package/src/dev-ready-probe.js +69 -0
- package/src/dev-regenerate.js +2 -2
- package/src/dev-reload-hold.js +49 -20
- package/src/dev-reload-reason.js +61 -0
- package/src/dev-reload-worker.js +62 -13
- package/src/dev-secret.js +73 -0
- package/src/dev-source-locations-hook.js +4 -1
- package/src/dev-source-locations.js +33 -2
- package/src/dev.js +0 -1
- package/src/env-files.js +124 -0
- package/src/file-storage.js +2 -2
- package/src/foreign-imports.js +228 -0
- package/src/form-action-identity.js +52 -25
- package/src/form-dispatch.js +140 -65
- package/src/html-cache.js +35 -4
- package/src/importmap.js +2 -2
- package/src/instrumentation.js +59 -3
- package/src/js-scan.js +5 -4
- package/src/jwe.js +155 -0
- package/src/lazy-components.js +166 -0
- package/src/listener-core.js +4 -2
- package/src/lit-hints.js +172 -0
- package/src/middleware-load.js +248 -0
- package/src/module-graph.js +73 -47
- package/src/module-side-effect.js +284 -0
- package/src/public-files.js +73 -0
- package/src/redirect-location.js +19 -0
- package/src/redirects.js +9 -1
- package/src/route-frontier.js +151 -0
- package/src/route-report.js +130 -0
- package/src/router.js +120 -13
- package/src/session.js +32 -10
- package/src/ssr/client-router-flag.js +1 -1
- package/src/ssr/document.js +37 -13
- package/src/ssr/head.js +78 -23
- package/src/ssr/preloads.js +31 -45
- package/src/ssr/render.js +241 -55
- package/src/ssr/responses.js +51 -4
- package/src/ssr/shipped-modules.js +22 -16
- package/src/testing.d.ts +31 -2
- package/src/testing.js +80 -17
- package/src/thenable-params.js +26 -0
- package/src/vendor/jspm.js +12 -1
- package/src/vendor/pins.js +9 -8
- package/src/vendor/scanner.js +14 -9
- package/src/vendor.js +5 -3
- package/webjs-config.schema.json +1 -6
- package/src/component-elision.js +0 -1828
- package/src/elision-differential.js +0 -101
- package/src/elision-report.js +0 -193
package/index.d.ts
CHANGED
|
@@ -34,6 +34,13 @@ export * from './src/testing.js';
|
|
|
34
34
|
|
|
35
35
|
/** A webjs middleware: receives the request + a `next()` continuation. */
|
|
36
36
|
export type Middleware = (req: Request, next: () => Promise<Response>) => Promise<Response> | Response;
|
|
37
|
+
/**
|
|
38
|
+
* The request an app's `middleware.ts` / `proxy.ts` (and a segment middleware)
|
|
39
|
+
* receives: the platform `Request` plus Next's `request.cookies`, the same
|
|
40
|
+
* store `cookies()` returns. Annotate with it where Next.js code writes
|
|
41
|
+
* `NextRequest`.
|
|
42
|
+
*/
|
|
43
|
+
export type MiddlewareRequest = Request & { readonly cookies: MiddlewareRequestCookies };
|
|
37
44
|
|
|
38
45
|
// `Handle` is re-exported from ./src/testing.js (the `export *` above), so it
|
|
39
46
|
// is not re-declared here. It is IMPORTED as well, because `export *` re-exports
|
|
@@ -45,16 +52,49 @@ export type Middleware = (req: Request, next: () => Promise<Response>) => Promis
|
|
|
45
52
|
* Mirrors AGENTS.md's documented shape; `@webjsdev/core` does not export it,
|
|
46
53
|
* so it is defined here (the one server-owned shared type).
|
|
47
54
|
*/
|
|
48
|
-
export type ActionResult<T =
|
|
49
|
-
| {
|
|
55
|
+
export type ActionResult<T = any> =
|
|
56
|
+
| {
|
|
57
|
+
success: true;
|
|
58
|
+
data?: T;
|
|
59
|
+
redirect?: string;
|
|
60
|
+
message?: string;
|
|
61
|
+
// The failure fields, absent here, so a read before narrowing on
|
|
62
|
+
// `success` (`result.error ?? result.fieldErrors?.body`) type-checks.
|
|
63
|
+
error?: undefined;
|
|
64
|
+
errors?: undefined;
|
|
65
|
+
fieldErrors?: undefined;
|
|
66
|
+
values?: undefined;
|
|
67
|
+
status?: undefined;
|
|
68
|
+
}
|
|
50
69
|
| {
|
|
51
70
|
success: false;
|
|
52
71
|
error?: string;
|
|
72
|
+
/** Next.js's `{ errors }` spelling, read as a failure like `fieldErrors`. */
|
|
73
|
+
errors?: Record<string, string | string[]>;
|
|
74
|
+
message?: string;
|
|
53
75
|
fieldErrors?: Record<string, string>;
|
|
54
76
|
values?: Record<string, string>;
|
|
55
77
|
status?: number;
|
|
56
78
|
};
|
|
57
79
|
|
|
80
|
+
/**
|
|
81
|
+
* What a form-bound action receives: a real `FormData` (so `get`, `getAll` and
|
|
82
|
+
* `instanceof FormData` work, as in Next) that also carries each field as a
|
|
83
|
+
* property, so `input.title` reads it too. A `name[]` field arrives as an array
|
|
84
|
+
* under `name`; a file input arrives as a `File`. Pass the field shape for
|
|
85
|
+
* typed property reads.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* 'use server';
|
|
89
|
+
* import type { FormInput } from '@webjsdev/server';
|
|
90
|
+
* export async function createTask(input: FormInput<{ title: string; tags: string[] }>) {
|
|
91
|
+
* const title = input.title.trim(); // or input.get('title')
|
|
92
|
+
* ...
|
|
93
|
+
* }
|
|
94
|
+
*/
|
|
95
|
+
export type FormInput<Fields extends Record<string, unknown> = Record<string, string | string[] | File>> =
|
|
96
|
+
FormData & Fields;
|
|
97
|
+
|
|
58
98
|
/** The pluggable cache store interface (`memoryStore` / `redisStore` / custom). */
|
|
59
99
|
export interface CacheStore {
|
|
60
100
|
get(key: string): Promise<string | null>;
|
|
@@ -248,6 +288,14 @@ export declare function matchApi(
|
|
|
248
288
|
table: unknown,
|
|
249
289
|
pathname: string,
|
|
250
290
|
): { route: { file: string }; params: Record<string, string> } | null;
|
|
291
|
+
/**
|
|
292
|
+
* The URL paths of every STATIC page route in a route table, sorted and
|
|
293
|
+
* deduped. Dynamic routes are excluded (listing one would mean inventing
|
|
294
|
+
* param values).
|
|
295
|
+
*/
|
|
296
|
+
export declare function staticPageRoutes(table: {
|
|
297
|
+
pages?: Array<{ routeDir?: string; paramNames?: string[] }>;
|
|
298
|
+
} | undefined): string[];
|
|
251
299
|
|
|
252
300
|
// ---------------------------------------------------------------------------
|
|
253
301
|
// route-types.js (#258)
|
|
@@ -342,8 +390,7 @@ export declare function setVendorEntries(
|
|
|
342
390
|
/**
|
|
343
391
|
* Bare-specifier npm imports an app could load in the browser, found by walking
|
|
344
392
|
* the module graph from every browser-bound entry rather than by scanning the
|
|
345
|
-
* directory.
|
|
346
|
-
* running server serves.
|
|
393
|
+
* directory. The result is a superset of what the running server serves.
|
|
347
394
|
*/
|
|
348
395
|
export declare function scanBareImports(dir: string): Promise<Set<string>>;
|
|
349
396
|
/** Extract the package name from an import specifier (`dayjs/plugin/utc` -> `dayjs`). */
|
|
@@ -492,8 +539,8 @@ export declare function extractComponents(src: string): Array<{ className: strin
|
|
|
492
539
|
* because there is no registration call anywhere or because the tag is
|
|
493
540
|
* computed. The registration cross-reference is app-wide, so a class a sibling
|
|
494
541
|
* module registers is not an orphan. Neither is visible to the
|
|
495
|
-
* scanner, so both always lose the
|
|
496
|
-
*
|
|
542
|
+
* scanner, so both always lose the tag-to-module registry entry, the boot import
|
|
543
|
+
* and the preload hint; assume the element does not upgrade either.
|
|
497
544
|
* This is about REGISTRATION, never about import reachability. The full
|
|
498
545
|
* semantics, including the one case where a computed tag still upgrades, live
|
|
499
546
|
* on `findOrphanComponents` in `src/component-scanner.js`.
|
|
@@ -503,75 +550,53 @@ export declare function findOrphanComponents(
|
|
|
503
550
|
): Promise<Array<{ className: string; file: string }>>;
|
|
504
551
|
|
|
505
552
|
// ---------------------------------------------------------------------------
|
|
506
|
-
//
|
|
553
|
+
// route-report.js (#1686)
|
|
507
554
|
// ---------------------------------------------------------------------------
|
|
508
555
|
|
|
509
|
-
/**
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
export interface
|
|
514
|
-
/** App-relative path of the component module. */
|
|
515
|
-
file: string;
|
|
516
|
-
/** Every tag this file registers, sorted. */
|
|
517
|
-
tags: string[];
|
|
518
|
-
verdict: 'elided' | 'shipped';
|
|
519
|
-
evidence: ElisionEvidence | null;
|
|
520
|
-
reason: string | null;
|
|
521
|
-
/** App-relative path of the module that forced the ship, where one did. */
|
|
522
|
-
by: string | null;
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
/** One page/layout route module's verdict. */
|
|
526
|
-
export interface ElisionRouteModuleRow {
|
|
556
|
+
/**
|
|
557
|
+
* One route module (page, layout or boundary). A route module never ships to
|
|
558
|
+
* the browser; the boot imports `emits`, its component frontier, in its place.
|
|
559
|
+
*/
|
|
560
|
+
export interface RouteModuleRow {
|
|
527
561
|
file: string;
|
|
528
|
-
|
|
529
|
-
/**
|
|
562
|
+
kind: 'page' | 'layout' | 'error' | 'loading' | 'not-found' | 'forbidden' | 'unauthorized' | 'global-not-found';
|
|
563
|
+
/** The component modules the boot imports in this module's place (empty: zero JS). */
|
|
530
564
|
emits: string[];
|
|
531
|
-
/**
|
|
532
|
-
|
|
533
|
-
reason: string | null;
|
|
565
|
+
/** Advisory notes on code that looks meant for the browser but runs only on the server. */
|
|
566
|
+
hints: string[];
|
|
534
567
|
}
|
|
535
568
|
|
|
536
569
|
/** A `class X extends WebComponent` with no literal-tag registration. */
|
|
537
|
-
export interface
|
|
570
|
+
export interface OrphanRow {
|
|
538
571
|
file: string;
|
|
539
572
|
className: string;
|
|
540
573
|
}
|
|
541
574
|
|
|
542
|
-
export interface
|
|
543
|
-
components: number;
|
|
544
|
-
elided: number;
|
|
545
|
-
shipped: number;
|
|
575
|
+
export interface RouteReportSummary {
|
|
546
576
|
routeModules: number;
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
577
|
+
/** Route modules whose frontier is empty: the browser loads nothing for them. */
|
|
578
|
+
zeroJs: number;
|
|
579
|
+
/** Route modules whose frontier names at least one component. */
|
|
580
|
+
carriers: number;
|
|
550
581
|
orphans: number;
|
|
551
582
|
}
|
|
552
583
|
|
|
553
|
-
/**
|
|
554
|
-
export interface
|
|
584
|
+
/** What each route costs the browser. Every path is app-relative; every array is sorted by `file`. */
|
|
585
|
+
export interface RouteReport {
|
|
555
586
|
analysed: boolean;
|
|
556
587
|
/** Why nothing was analysed, when `analysed` is false. */
|
|
557
|
-
skipped: 'no-app' | '
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
summary: ElisionSummary;
|
|
588
|
+
skipped: 'no-app' | 'unanalysable' | null;
|
|
589
|
+
routeModules: RouteModuleRow[];
|
|
590
|
+
orphans: OrphanRow[];
|
|
591
|
+
summary: RouteReportSummary;
|
|
562
592
|
}
|
|
563
593
|
|
|
564
594
|
/**
|
|
565
|
-
*
|
|
566
|
-
*
|
|
567
|
-
*
|
|
568
|
-
* shipped one naming the first client-effecting blocker), and every orphan
|
|
569
|
-
* component class that gets no verdict at all. A reporting layer over
|
|
570
|
-
* `analyzeElision`, never a second analysis and never a build artifact.
|
|
571
|
-
* Consumed by `webjs elision`, the MCP `list_elision` tool, and both `webjs
|
|
572
|
-
* doctor` elision checks.
|
|
595
|
+
* Every route module with the components the boot loads in its place and any
|
|
596
|
+
* hints about browser-looking code in it, plus every orphan component class.
|
|
597
|
+
* Consumed by both `webjs doctor` route checks.
|
|
573
598
|
*/
|
|
574
|
-
export declare function
|
|
599
|
+
export declare function analyzeRouteModules(appDir: string): Promise<RouteReport>;
|
|
575
600
|
|
|
576
601
|
// ---------------------------------------------------------------------------
|
|
577
602
|
// dev-regenerate.js (#967, #1672)
|
|
@@ -615,39 +640,80 @@ export declare function regenerateStatus(
|
|
|
615
640
|
): Promise<Array<{ output: string; command: string; state: RegenerateState }>>;
|
|
616
641
|
|
|
617
642
|
// ---------------------------------------------------------------------------
|
|
618
|
-
//
|
|
643
|
+
// context.js (per-request context helpers)
|
|
619
644
|
// ---------------------------------------------------------------------------
|
|
620
645
|
|
|
621
646
|
/**
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
* observable output. The ONE definition shared by the framework's own
|
|
625
|
-
* differential guard and `webjs elision --verify`, so an app can diff its own
|
|
626
|
-
* two captures with exactly the framework's comparator.
|
|
647
|
+
* The in-flight request's headers, read-only as Next.js's `ReadonlyHeaders`:
|
|
648
|
+
* `set`, `append` and `delete` throw at runtime and are absent from the type.
|
|
627
649
|
*/
|
|
628
|
-
export
|
|
629
|
-
|
|
650
|
+
export type ReadonlyHeaders = Omit<Headers, 'set' | 'append' | 'delete'>;
|
|
651
|
+
/** Read-only headers for the in-flight request. Throws outside a request scope. */
|
|
652
|
+
export declare function headers(): ReadonlyHeaders;
|
|
653
|
+
/** A cookie as read from the request. */
|
|
654
|
+
export interface RequestCookie { name: string; value: string }
|
|
655
|
+
/** A cookie to write (Next's `ResponseCookie`; `maxAge` in seconds, `path` defaults to `/`). */
|
|
656
|
+
export interface ResponseCookie {
|
|
657
|
+
name: string;
|
|
658
|
+
value: string;
|
|
659
|
+
expires?: Date | number;
|
|
660
|
+
maxAge?: number;
|
|
661
|
+
domain?: string;
|
|
662
|
+
path?: string;
|
|
663
|
+
secure?: boolean;
|
|
664
|
+
httpOnly?: boolean;
|
|
665
|
+
sameSite?: 'lax' | 'strict' | 'none' | 'Lax' | 'Strict' | 'None' | boolean;
|
|
666
|
+
partitioned?: boolean;
|
|
667
|
+
priority?: 'low' | 'medium' | 'high';
|
|
668
|
+
}
|
|
669
|
+
/** The request's cookie store (Next.js's `cookies()` shape). */
|
|
630
670
|
/**
|
|
631
|
-
*
|
|
632
|
-
*
|
|
633
|
-
*
|
|
671
|
+
* Next.js's `RequestCookies`, `request.cookies` in middleware and a route
|
|
672
|
+
* handler. Reads are the request's cookies; `set`, `delete` and `clear` change
|
|
673
|
+
* what the rest of this request reads (the page's `cookies()` included) and
|
|
674
|
+
* send nothing to the browser. Set a response cookie with `cookies().set()`.
|
|
634
675
|
*/
|
|
635
|
-
export
|
|
636
|
-
|
|
637
|
-
}):
|
|
638
|
-
|
|
639
|
-
// ---------------------------------------------------------------------------
|
|
640
|
-
// context.js (per-request context helpers)
|
|
641
|
-
// ---------------------------------------------------------------------------
|
|
642
|
-
|
|
643
|
-
/** Read-only headers for the in-flight request. Throws outside a request scope. */
|
|
644
|
-
export declare function headers(): Headers;
|
|
645
|
-
/** Read-only cookie jar for the in-flight request. Throws outside a request scope. */
|
|
646
|
-
export declare function cookies(): {
|
|
647
|
-
get(name: string): string | undefined;
|
|
676
|
+
export interface MiddlewareRequestCookies {
|
|
677
|
+
get(name: string | { name: string }): RequestCookie | undefined;
|
|
678
|
+
getAll(name?: string | { name: string }): RequestCookie[];
|
|
648
679
|
has(name: string): boolean;
|
|
649
|
-
|
|
650
|
-
|
|
680
|
+
readonly size: number;
|
|
681
|
+
set(name: string, value: string): MiddlewareRequestCookies;
|
|
682
|
+
set(cookie: RequestCookie): MiddlewareRequestCookies;
|
|
683
|
+
delete(names: string): boolean;
|
|
684
|
+
delete(names: string[]): boolean[];
|
|
685
|
+
clear(): MiddlewareRequestCookies;
|
|
686
|
+
toString(): string;
|
|
687
|
+
[Symbol.iterator](): IterableIterator<[string, RequestCookie]>;
|
|
688
|
+
}
|
|
689
|
+
export interface RequestCookies {
|
|
690
|
+
get(name: string | { name: string }): RequestCookie | undefined;
|
|
691
|
+
getAll(name?: string | { name: string }): RequestCookie[];
|
|
692
|
+
has(name: string): boolean;
|
|
693
|
+
readonly size: number;
|
|
694
|
+
set(name: string, value: string, options?: Omit<ResponseCookie, 'name' | 'value'>): RequestCookies;
|
|
695
|
+
set(cookie: ResponseCookie): RequestCookies;
|
|
696
|
+
delete(name: string | { name: string; path?: string; domain?: string }): RequestCookies;
|
|
697
|
+
toString(): string;
|
|
698
|
+
[Symbol.iterator](): IterableIterator<[string, RequestCookie]>;
|
|
699
|
+
}
|
|
700
|
+
/**
|
|
701
|
+
* The in-flight request's cookies, Next.js's `cookies()`: `cookies().get('x')?.value`
|
|
702
|
+
* reads, `set` / `delete` write a `Set-Cookie` onto this request's response. Also
|
|
703
|
+
* awaitable (`(await cookies()).get('x')`). Throws outside a request scope.
|
|
704
|
+
*
|
|
705
|
+
* `get` returns a {@link RequestCookie} (`{ name, value }`) or `undefined`, as
|
|
706
|
+
* in Next.js, never the bare string.
|
|
707
|
+
*
|
|
708
|
+
* @example
|
|
709
|
+
* ```ts
|
|
710
|
+
* import { cookies } from '@webjsdev/server';
|
|
711
|
+
* const theme: string = cookies().get('theme')?.value ?? 'light';
|
|
712
|
+
* cookies().set('theme', 'dark', { path: '/', maxAge: 60 * 60 * 24 * 365, httpOnly: true });
|
|
713
|
+
* cookies().delete('theme');
|
|
714
|
+
* ```
|
|
715
|
+
*/
|
|
716
|
+
export declare function cookies(): RequestCookies & Promise<RequestCookies>;
|
|
651
717
|
/** The raw `Request` for the in-flight scope, or null at module top-level. */
|
|
652
718
|
export declare function getRequest(): Request | null;
|
|
653
719
|
/** Run `fn` within a request scope. */
|
|
@@ -786,8 +852,13 @@ export declare function cache<T extends (...args: any[]) => Promise<any>>(
|
|
|
786
852
|
// cache-tags.js (#242) + html-cache.js (#241): server cache invalidation
|
|
787
853
|
// ---------------------------------------------------------------------------
|
|
788
854
|
|
|
789
|
-
/**
|
|
790
|
-
|
|
855
|
+
/**
|
|
856
|
+
* Evict every `cache()` entry tagged with `tag`. Takes Next.js 16's second
|
|
857
|
+
* argument, `revalidateTag(tag, 'max')` (a cache-life profile, or
|
|
858
|
+
* `{ expire }`), for source compatibility: WebJs evicts at once whatever the
|
|
859
|
+
* profile, since `cache()` has no stale-while-revalidate window to honour.
|
|
860
|
+
*/
|
|
861
|
+
export declare function revalidateTag(tag: string, profile?: string | { expire?: number }): Promise<void>;
|
|
791
862
|
/** Evict every `cache()` entry tagged with any of `tags`. */
|
|
792
863
|
export declare function revalidateTags(tags: string[]): Promise<void>;
|
|
793
864
|
/**
|
|
@@ -800,7 +871,7 @@ export declare function revalidateTags(tags: string[]): Promise<void>;
|
|
|
800
871
|
* `webjs.basePath` the absolute url is the PUBLIC one and its mount prefix is
|
|
801
872
|
* stripped, so it evicts the entry the write actually stored.
|
|
802
873
|
*/
|
|
803
|
-
export declare function revalidatePath(path: string): Promise<void>;
|
|
874
|
+
export declare function revalidatePath(path: string, type?: 'page' | 'layout'): Promise<void>;
|
|
804
875
|
/** Evict all cached HTML (per-process generation bump). */
|
|
805
876
|
export declare function revalidateAll(): Promise<void>;
|
|
806
877
|
|
|
@@ -826,12 +897,34 @@ export declare class Session {
|
|
|
826
897
|
set(key: string, value: unknown): void;
|
|
827
898
|
has(key: string): boolean;
|
|
828
899
|
unset(key: string): void;
|
|
829
|
-
/** Set a value
|
|
900
|
+
/** Set a value that lasts until a `get()` reads it (Remix's flash); `has()` does not consume it. */
|
|
830
901
|
flash(key: string, value: unknown): void;
|
|
831
902
|
destroy(): void;
|
|
903
|
+
/**
|
|
904
|
+
* Give the session a new id, keeping its data. Call it after a login to
|
|
905
|
+
* prevent session fixation; `deleteOld` also deletes the old id's stored data.
|
|
906
|
+
*/
|
|
907
|
+
regenerateId(deleteOld?: boolean): void;
|
|
832
908
|
}
|
|
833
909
|
|
|
834
|
-
/**
|
|
910
|
+
/**
|
|
911
|
+
* Session middleware factory: a signed session cookie for apps that do not use
|
|
912
|
+
* `createAuth`. It throws at import without `secret` or `SESSION_SECRET`.
|
|
913
|
+
*
|
|
914
|
+
* @example
|
|
915
|
+
* ```ts
|
|
916
|
+
* // middleware.ts
|
|
917
|
+
* import { session } from '@webjsdev/server';
|
|
918
|
+
* export default session({ secret: process.env.SESSION_SECRET });
|
|
919
|
+
*
|
|
920
|
+
* // In a 'use server' action or a page:
|
|
921
|
+
* import { getRequest, getSession } from '@webjsdev/server';
|
|
922
|
+
* const s = getSession(getRequest()!);
|
|
923
|
+
* s.regenerateId(true); // after a login, against session fixation
|
|
924
|
+
* s.set('userId', user.id);
|
|
925
|
+
* const userId = s.get<string>('userId');
|
|
926
|
+
* ```
|
|
927
|
+
*/
|
|
835
928
|
export declare function session(opts?: {
|
|
836
929
|
storage?: SessionStorage;
|
|
837
930
|
cookieName?: string;
|
|
@@ -850,8 +943,26 @@ export declare function storeSessionStorage(opts?: Record<string, unknown>): Ses
|
|
|
850
943
|
export declare const cookieSession: typeof cookieSessionStorage;
|
|
851
944
|
/** Alias of `storeSessionStorage`. */
|
|
852
945
|
export declare const storeSession: typeof storeSessionStorage;
|
|
853
|
-
/**
|
|
854
|
-
|
|
946
|
+
/**
|
|
947
|
+
* The session for the current request. With no argument it reads the request
|
|
948
|
+
* in scope (as `cookies()` and `headers()` do); passing `getRequest()` also works.
|
|
949
|
+
* Needs `session()` as root middleware; throws outside it.
|
|
950
|
+
*
|
|
951
|
+
* @example
|
|
952
|
+
* ```ts
|
|
953
|
+
* // middleware.ts
|
|
954
|
+
* import { session } from '@webjsdev/server';
|
|
955
|
+
* export default session(); // SESSION_SECRET from .env
|
|
956
|
+
*
|
|
957
|
+
* // in a page, action or route handler
|
|
958
|
+
* import { getSession } from '@webjsdev/server';
|
|
959
|
+
* const s = getSession();
|
|
960
|
+
* s.set('cart', [1, 2]); // persists across requests
|
|
961
|
+
* s.flash('notice', 'Saved'); // read once by the next request
|
|
962
|
+
* const notice = s.get('notice'); // unknown; narrow it before use
|
|
963
|
+
* ```
|
|
964
|
+
*/
|
|
965
|
+
export declare function getSession(req?: Request | null): Session;
|
|
855
966
|
|
|
856
967
|
// ---------------------------------------------------------------------------
|
|
857
968
|
// broadcast.js
|
|
@@ -868,9 +979,14 @@ export declare function broadcast(
|
|
|
868
979
|
// file-storage.js (#247)
|
|
869
980
|
// ---------------------------------------------------------------------------
|
|
870
981
|
|
|
871
|
-
/**
|
|
982
|
+
/**
|
|
983
|
+
* A streaming handle returned by `FileStore.get` (body is a stream, never
|
|
984
|
+
* buffered). The body is a web `ReadableStream`, so `new Response(file.body)`
|
|
985
|
+
* serves it as is; an adapter whose SDK returns a Node stream converts it with
|
|
986
|
+
* `Readable.toWeb`.
|
|
987
|
+
*/
|
|
872
988
|
export interface StoredObjectHandle {
|
|
873
|
-
body: ReadableStream
|
|
989
|
+
body: ReadableStream<Uint8Array>;
|
|
874
990
|
size: number;
|
|
875
991
|
contentType: string;
|
|
876
992
|
}
|
|
@@ -904,7 +1020,26 @@ export declare const DEFAULT_UPLOAD_DIR: string;
|
|
|
904
1020
|
export declare function diskStore(opts?: { dir?: string; baseUrl?: string }): FileStore;
|
|
905
1021
|
/** Set the default file store (call at startup to use a custom dir or S3). */
|
|
906
1022
|
export declare function setFileStore(store: FileStore): void;
|
|
907
|
-
/**
|
|
1023
|
+
/**
|
|
1024
|
+
* Get the default file store (`diskStore` under `<cwd>/.webjs/uploads` unless set).
|
|
1025
|
+
*
|
|
1026
|
+
* @example
|
|
1027
|
+
* ```ts
|
|
1028
|
+
* // In a 'use server' action bound to <form action=${save} enctype="multipart/form-data">
|
|
1029
|
+
* // with <input type="file" name="photo">: the field arrives as a File.
|
|
1030
|
+
* import { getFileStore, generateKey } from '@webjsdev/server';
|
|
1031
|
+
* const key = generateKey(input.photo.name); // opaque, traversal-safe
|
|
1032
|
+
* await getFileStore().put(key, input.photo); // streams to disk
|
|
1033
|
+
*
|
|
1034
|
+
* // app/uploads/[key]/route.ts serves it back:
|
|
1035
|
+
* export async function GET(_req: Request, { params }: { params: { key: string } }) {
|
|
1036
|
+
* let file;
|
|
1037
|
+
* try { file = await getFileStore().get(params.key); } catch { file = null; } // an unsafe key throws
|
|
1038
|
+
* if (!file) return new Response('Not found', { status: 404 });
|
|
1039
|
+
* return new Response(file.body, { headers: { 'content-type': file.contentType } });
|
|
1040
|
+
* }
|
|
1041
|
+
* ```
|
|
1042
|
+
*/
|
|
908
1043
|
export declare function getFileStore(): FileStore;
|
|
909
1044
|
/** Generate a random, opaque, traversal-safe key, preserving a whitelisted extension. */
|
|
910
1045
|
export declare function generateKey(filename?: string): string;
|
|
@@ -1080,13 +1215,102 @@ export type ResolvedAuthUser = keyof AuthUser extends never
|
|
|
1080
1215
|
: AuthUser;
|
|
1081
1216
|
|
|
1082
1217
|
/** Auth configuration for `createAuth`. */
|
|
1218
|
+
/**
|
|
1219
|
+
* The Auth.js adapter methods `createAuth` calls, the shape `@auth/*-adapter`
|
|
1220
|
+
* packages implement (`@auth/core/adapters`).
|
|
1221
|
+
*/
|
|
1222
|
+
export interface AuthAdapter {
|
|
1223
|
+
createUser(user: Record<string, any>): Promise<any> | any;
|
|
1224
|
+
getUser?(id: string): Promise<any> | any;
|
|
1225
|
+
getUserByEmail(email: string): Promise<any> | any;
|
|
1226
|
+
getUserByAccount(account: { provider: string; providerAccountId: string }): Promise<any> | any;
|
|
1227
|
+
updateUser?(user: Record<string, any>): Promise<any> | any;
|
|
1228
|
+
linkAccount(account: Record<string, any>): Promise<any> | any;
|
|
1229
|
+
createSession(session: { sessionToken: string; userId: string; expires: Date }): Promise<any> | any;
|
|
1230
|
+
getSessionAndUser(sessionToken: string): Promise<{ session: { sessionToken: string; userId: string; expires: Date }; user: any } | null> | { session: any; user: any } | null;
|
|
1231
|
+
updateSession?(session: { sessionToken: string; expires?: Date }): Promise<any> | any;
|
|
1232
|
+
deleteSession(sessionToken: string): Promise<any> | any;
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* The token the `jwt` and `session` callbacks see, Auth.js's `JWT`: the
|
|
1236
|
+
* standard claims plus whatever the `jwt` callback put on it.
|
|
1237
|
+
*/
|
|
1238
|
+
export interface AuthJWT {
|
|
1239
|
+
sub?: string;
|
|
1240
|
+
name?: string | null;
|
|
1241
|
+
email?: string | null;
|
|
1242
|
+
image?: string | null;
|
|
1243
|
+
iat?: number;
|
|
1244
|
+
exp?: number;
|
|
1245
|
+
[key: string]: unknown;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
/** The user the callbacks see, Auth.js's `User`: what `authorize` or the provider returned. */
|
|
1249
|
+
export interface AuthCallbackUser {
|
|
1250
|
+
id?: string;
|
|
1251
|
+
name?: string | null;
|
|
1252
|
+
email?: string | null;
|
|
1253
|
+
image?: string | null;
|
|
1254
|
+
[key: string]: unknown;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/** The session the `session` callback receives and returns, Auth.js's `Session`. */
|
|
1258
|
+
export interface AuthCallbackSession {
|
|
1259
|
+
user: ResolvedAuthUser & { name?: string | null; email?: string | null; image?: string | null };
|
|
1260
|
+
expires: string;
|
|
1261
|
+
[key: string]: unknown;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
type AuthAwaitable<T> = T | Promise<T>;
|
|
1265
|
+
|
|
1266
|
+
/**
|
|
1267
|
+
* Auth.js's callbacks, with the parameters Auth.js passes, so a callback
|
|
1268
|
+
* written as Auth.js documents it (`jwt({ token, user })`,
|
|
1269
|
+
* `session({ session, token })`) is typed with no annotation. Declared as
|
|
1270
|
+
* methods so a callback annotated with its own narrower types still compiles.
|
|
1271
|
+
*/
|
|
1272
|
+
export interface AuthCallbacks {
|
|
1273
|
+
/** At sign-in (`trigger: 'signIn'`, with `user`, `account` and `profile`) and on every session read. */
|
|
1274
|
+
jwt?(params: { token: AuthJWT; user: AuthCallbackUser; account?: Record<string, any> | null; profile?: Record<string, any>; trigger?: 'signIn' | 'signUp' | 'update'; session?: any }): AuthAwaitable<AuthJWT | null>;
|
|
1275
|
+
/** On every session read. `token` under the jwt strategy, `user` under the database strategy. */
|
|
1276
|
+
session?(params: { session: AuthCallbackSession; token: AuthJWT; user: AuthCallbackUser }): AuthAwaitable<any>;
|
|
1277
|
+
/** Return true to allow, a url string to redirect there instead, anything else to deny (Auth.js's rule). */
|
|
1278
|
+
signIn?(params: { user: AuthCallbackUser; account?: Record<string, any> | null; profile?: Record<string, any>; credentials?: Record<string, any> }): AuthAwaitable<boolean | string>;
|
|
1279
|
+
redirect?(params: { url: string; baseUrl: string }): AuthAwaitable<string>;
|
|
1280
|
+
/** Run by `auth` as middleware: true continues, false redirects to sign in, a Response is sent as is. */
|
|
1281
|
+
authorized?: (params: { auth: { user: Record<string, unknown> } | null; request: Request }) => boolean | Response | Promise<boolean | Response>;
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1083
1284
|
export interface AuthConfig {
|
|
1084
|
-
providers:
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1285
|
+
/** A provider config, or the provider function uncalled (`providers: [GitHub]`), as Auth.js accepts. */
|
|
1286
|
+
providers: Array<ProviderConfig | (() => ProviderConfig)>;
|
|
1287
|
+
/**
|
|
1288
|
+
* `strategy` defaults to `'database'` when an Auth.js adapter is given, else
|
|
1289
|
+
* `'jwt'`. `updateAge` (seconds, default a day) is how often a database
|
|
1290
|
+
* session's expiry is pushed forward, as in Auth.js.
|
|
1291
|
+
*/
|
|
1292
|
+
session?: { strategy?: 'jwt' | 'database'; maxAge?: number; updateAge?: number };
|
|
1293
|
+
/** Signs the session. Defaults to `process.env.AUTH_SECRET`, as in Auth.js. */
|
|
1294
|
+
secret?: string | undefined;
|
|
1295
|
+
callbacks?: AuthCallbacks;
|
|
1296
|
+
/**
|
|
1297
|
+
* An Auth.js database adapter (`DrizzleAdapter(db)` from `@auth/drizzle-adapter`,
|
|
1298
|
+
* and the rest), or WebJs's own session store for the database strategy.
|
|
1299
|
+
*/
|
|
1300
|
+
adapter?: AuthAdapter | { load?: Function; save?: Function; destroy?: Function };
|
|
1089
1301
|
pages?: { signIn?: string; signOut?: string; error?: string };
|
|
1302
|
+
/**
|
|
1303
|
+
* Auth.js's `useSecureCookies`: force the Secure flag and the `__Host-`
|
|
1304
|
+
* cookie names on or off. Unset, they follow the auth URL's protocol
|
|
1305
|
+
* (`AUTH_URL`'s when set, else the request's), never `NODE_ENV`.
|
|
1306
|
+
*/
|
|
1307
|
+
useSecureCookies?: boolean;
|
|
1308
|
+
/**
|
|
1309
|
+
* Where the auth routes are mounted, as Auth.js's `basePath`. Defaults to
|
|
1310
|
+
* the path of `AUTH_URL` when it has one, else `/api/auth`. `AUTH_URL` (or
|
|
1311
|
+
* `NEXTAUTH_URL`) also sets the origin OAuth callback URLs use.
|
|
1312
|
+
*/
|
|
1313
|
+
basePath?: string;
|
|
1090
1314
|
}
|
|
1091
1315
|
|
|
1092
1316
|
/**
|
|
@@ -1097,26 +1321,120 @@ export interface AuthConfig {
|
|
|
1097
1321
|
* via `createAuth<MyUser>(...)`.
|
|
1098
1322
|
*/
|
|
1099
1323
|
export interface AuthInstance<TUser = ResolvedAuthUser> {
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1324
|
+
/**
|
|
1325
|
+
* Auth.js's `auth`, in its three shapes: `auth()` / `auth(req)` reads the
|
|
1326
|
+
* session; `export { auth as middleware }` runs as middleware (it sets
|
|
1327
|
+
* `req.auth` and applies `callbacks.authorized`); `auth((req) => ...)` wraps
|
|
1328
|
+
* a middleware whose `req.auth` holds the session.
|
|
1329
|
+
*/
|
|
1330
|
+
auth: {
|
|
1331
|
+
(req?: Request | null): Promise<{ user: TUser } | null>;
|
|
1332
|
+
(req: Request, next: unknown): Promise<Response | undefined>;
|
|
1333
|
+
<R>(handler: (req: Request & { auth: { user: TUser } | null }, ...rest: any[]) => R): (req: Request, ...rest: any[]) => Promise<Response | Awaited<R>>;
|
|
1334
|
+
};
|
|
1335
|
+
/**
|
|
1336
|
+
* Auth.js v5's `signIn`. Inside a request (a server action, a route handler,
|
|
1337
|
+
* middleware) it sets the session cookie on the response and throws a
|
|
1338
|
+
* redirect, so `await signIn('credentials', formData)` in a form action signs
|
|
1339
|
+
* in and navigates; a credentials failure throws {@link CredentialsSignin}.
|
|
1340
|
+
* `{ redirect: false }` (in the options or, as Auth.js accepts it, in the
|
|
1341
|
+
* data) returns the target url instead of throwing. With no request in scope
|
|
1342
|
+
* it returns the Response the `/api/auth/*` routes send. The redirecting
|
|
1343
|
+
* form is typed `Promise<Response>`, so `return signIn(...)` fits an action
|
|
1344
|
+
* that returns `ActionResult | Response`.
|
|
1345
|
+
*/
|
|
1346
|
+
signIn: {
|
|
1347
|
+
(
|
|
1348
|
+
provider: string,
|
|
1349
|
+
data: (FormData | Record<string, unknown>) | undefined,
|
|
1350
|
+
opts: { redirectTo?: string; redirect: false; req?: Request },
|
|
1351
|
+
): Promise<string>;
|
|
1352
|
+
(
|
|
1353
|
+
provider: string,
|
|
1354
|
+
data: Record<string, unknown> & { redirect: false },
|
|
1355
|
+
opts?: { redirectTo?: string; req?: Request },
|
|
1356
|
+
): Promise<string>;
|
|
1357
|
+
(
|
|
1358
|
+
provider: string,
|
|
1359
|
+
data?: FormData | Record<string, unknown>,
|
|
1360
|
+
opts?: { redirectTo?: string; redirect?: true; req?: Request },
|
|
1361
|
+
): Promise<Response>;
|
|
1362
|
+
};
|
|
1363
|
+
/** Auth.js v5's `signOut`: clears the session and throws a redirect inside a request. */
|
|
1364
|
+
signOut: {
|
|
1365
|
+
(opts: { redirectTo?: string; redirect: false; req?: Request }): Promise<string>;
|
|
1366
|
+
(opts?: { redirectTo?: string; redirect?: true; req?: Request }): Promise<Response>;
|
|
1367
|
+
};
|
|
1107
1368
|
handlers: { GET: (req: Request) => Promise<Response>; POST: (req: Request) => Promise<Response> };
|
|
1108
1369
|
}
|
|
1109
1370
|
|
|
1110
1371
|
/**
|
|
1111
|
-
* Create the auth system.
|
|
1112
|
-
*
|
|
1113
|
-
*
|
|
1114
|
-
*
|
|
1115
|
-
*
|
|
1372
|
+
* Create the auth system, Auth.js's API. `createAuth<MyUser>(...)` (or an
|
|
1373
|
+
* augmented {@link AuthUser}) types `auth().user` with no cast.
|
|
1374
|
+
*
|
|
1375
|
+
* @example
|
|
1376
|
+
* ```ts
|
|
1377
|
+
* // modules/auth/auth.server.ts
|
|
1378
|
+
* export const { auth, signIn, signOut, handlers } = createAuth({
|
|
1379
|
+
* providers: [Credentials({
|
|
1380
|
+
* async authorize(c: { email: string; password: string }) {
|
|
1381
|
+
* const user = await findUserByEmail(c.email); // your own query
|
|
1382
|
+
* if (!user || !(await verifyPassword(c.password, user.passwordHash))) return null;
|
|
1383
|
+
* return { id: String(user.id), email: user.email };
|
|
1384
|
+
* },
|
|
1385
|
+
* })],
|
|
1386
|
+
* pages: { error: '/login' }, // an uncaught failure lands on /login?error=CredentialsSignin
|
|
1387
|
+
* });
|
|
1388
|
+
* // app/api/auth/[...path]/route.ts: export const { GET, POST } = handlers;
|
|
1389
|
+
* // Read the user anywhere on the server: const session = await auth();
|
|
1390
|
+
*
|
|
1391
|
+
* // A 'use server' action bound as <form action=${logIn}>, as with Auth.js in Next:
|
|
1392
|
+
* export async function logIn(formData: FormData) {
|
|
1393
|
+
* try {
|
|
1394
|
+
* await signIn('credentials', formData, { redirectTo: '/dashboard' });
|
|
1395
|
+
* } catch (error) {
|
|
1396
|
+
* if (error instanceof AuthError) return { success: false, error: 'Wrong email or password.' };
|
|
1397
|
+
* throw error; // the success redirect
|
|
1398
|
+
* }
|
|
1399
|
+
* }
|
|
1400
|
+
* // Sign out the same way: await signOut({ redirectTo: '/' }).
|
|
1401
|
+
* ```
|
|
1116
1402
|
*/
|
|
1117
1403
|
export declare function createAuth<TUser = ResolvedAuthUser>(config: AuthConfig): AuthInstance<TUser>;
|
|
1118
|
-
/**
|
|
1119
|
-
export declare
|
|
1404
|
+
/** Next.js's navigation helpers (`next/navigation`), re-exported from `@webjsdev/core` so server code can import them from either package. */
|
|
1405
|
+
export declare const notFound: typeof import('@webjsdev/core').notFound;
|
|
1406
|
+
export declare const forbidden: typeof import('@webjsdev/core').forbidden;
|
|
1407
|
+
export declare const unauthorized: typeof import('@webjsdev/core').unauthorized;
|
|
1408
|
+
export declare const redirect: typeof import('@webjsdev/core').redirect;
|
|
1409
|
+
export declare const permanentRedirect: typeof import('@webjsdev/core').permanentRedirect;
|
|
1410
|
+
|
|
1411
|
+
/** Auth.js's base error: `error instanceof AuthError` and `error.type` say why a sign-in failed. */
|
|
1412
|
+
export declare class AuthError extends Error {
|
|
1413
|
+
type: string;
|
|
1414
|
+
}
|
|
1415
|
+
/** Thrown by `signIn('credentials', ...)` when `authorize` returns null. */
|
|
1416
|
+
export declare class CredentialsSignin extends AuthError {
|
|
1417
|
+
type: 'CredentialsSignin';
|
|
1418
|
+
code: string;
|
|
1419
|
+
}
|
|
1420
|
+
/**
|
|
1421
|
+
* Options for the credentials provider, Auth.js's `Credentials`. `C` is the
|
|
1422
|
+
* shape of the submitted fields, inferred from `authorize`'s annotation
|
|
1423
|
+
* (`authorize(credentials: { email: string; password: string })`).
|
|
1424
|
+
*/
|
|
1425
|
+
export interface CredentialsOptions<C extends Record<string, unknown> = Record<string, unknown>> {
|
|
1426
|
+
/**
|
|
1427
|
+
* Check the submitted fields (the sign-in form's fields, or the object passed
|
|
1428
|
+
* to `signIn('credentials', data)`). Return the session user, or `null` to
|
|
1429
|
+
* refuse the sign-in.
|
|
1430
|
+
*/
|
|
1431
|
+
authorize: (credentials: C) => Promise<Record<string, unknown> | null> | Record<string, unknown> | null;
|
|
1432
|
+
[key: string]: unknown;
|
|
1433
|
+
}
|
|
1434
|
+
/** The credentials (email/password) provider, Auth.js's `Credentials`. */
|
|
1435
|
+
export declare function Credentials<C extends Record<string, unknown> = Record<string, unknown>>(
|
|
1436
|
+
opts: CredentialsOptions<C>,
|
|
1437
|
+
): ProviderConfig;
|
|
1120
1438
|
/** The Google OAuth provider. */
|
|
1121
1439
|
export declare function Google(opts?: Record<string, unknown>): ProviderConfig;
|
|
1122
1440
|
/** The GitHub OAuth provider. */
|