@lovrozagar/flare 0.4.2 → 0.5.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
CHANGED
|
@@ -321,7 +321,7 @@ Chain order (typical): `intercept` → `cache` → `authenticate` → `input`
|
|
|
321
321
|
| Method | Role |
|
|
322
322
|
| --------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |
|
|
323
323
|
| `.cache(config)` | Client / SSR / ISR / SSG / CDN |
|
|
324
|
-
| `.authenticate()` / `.
|
|
324
|
+
| `.authenticate()` / `.authenticateOptional()` | Auth gate |
|
|
325
325
|
| `.authorize(fn)` | Role check; inherits parent unless overridden |
|
|
326
326
|
| `.input({ params, searchParams })` | Standard Schema or parse fn |
|
|
327
327
|
| `.effects({ loaderDeps, shouldRefetch })` | When to rerun the loader on search change |
|
|
@@ -462,10 +462,9 @@ export const route = createPage("_root_/dashboard")
|
|
|
462
462
|
```
|
|
463
463
|
|
|
464
464
|
- `.authenticate()` — required; null user → `UnauthenticatedError` (401).
|
|
465
|
-
- `.
|
|
466
|
-
-
|
|
465
|
+
- `.authenticateOptional()` — user may be null; `ctx.auth` is `null` for anonymous requests.
|
|
466
|
+
- The method is the mode. Arguments are only callerData for `authenticateFn`: `.authenticate("admin")` is required, `.authenticateOptional("viewer")` is optional. A leading `"optional"` argument does not compile and throws at definition — use `.authenticateOptional()`.
|
|
467
467
|
- A required layout gates every route below it; a child cannot loosen it.
|
|
468
|
-
- `.authenticateOptional()` is a deprecated alias of `.authenticate("optional")`.
|
|
469
468
|
- `createServer(router).authenticateFn(fn)` is the app-wide hook (cookie, header, JWT).
|
|
470
469
|
|
|
471
470
|
Throw helpers: `ctx.notFound()`, `ctx.redirect({ to, params, search, status, replace })`, `ctx.unauthenticated()`, `ctx.unauthorized()`.
|
package/package.json
CHANGED
package/src/generators/index.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, unlinkSync,
|
|
|
3
3
|
import { dirname, extname, join, posix, relative, resolve } from "node:path";
|
|
4
4
|
import { parse as babelParse } from "@babel/parser";
|
|
5
5
|
import { parseSeconds } from "../duration/index.ts";
|
|
6
|
-
import type
|
|
6
|
+
import { AUTHENTICATE_OPTIONAL_ARGUMENT_ERROR, type AuthenticateMode } from "../route-builder/types.ts";
|
|
7
7
|
import { extractLayoutKey } from "../router-primitives/paths.ts";
|
|
8
8
|
|
|
9
9
|
export type PrefetchStrategy = false | "intent" | "render" | "viewport";
|
|
@@ -638,10 +638,15 @@ function relativeImportPath(from: string, to: string): string {
|
|
|
638
638
|
return `${prefix}/${downs.join("/")}`;
|
|
639
639
|
}
|
|
640
640
|
|
|
641
|
-
/**
|
|
641
|
+
/**
|
|
642
|
+
* Auth mode of a builder chain. The mode is the method name, so this never evaluates an argument; the removed
|
|
643
|
+
* `.authenticate("optional")` form fails codegen the same way `assertCallerData` fails the builder.
|
|
644
|
+
*/
|
|
642
645
|
export function detectAuthenticateMode(chainText: string): AuthenticateMode {
|
|
646
|
+
if (/\.authenticate\s*\(\s*(["'`])optional\1\s*[,)]/.test(chainText)) {
|
|
647
|
+
throw new Error(AUTHENTICATE_OPTIONAL_ARGUMENT_ERROR);
|
|
648
|
+
}
|
|
643
649
|
if (/\.authenticateOptional\s*\(/.test(chainText)) return "optional";
|
|
644
|
-
if (/\.authenticate\s*\(\s*(["'`])optional\1\s*[,)]/.test(chainText)) return "optional";
|
|
645
650
|
return /\.authenticate\s*\(/.test(chainText);
|
|
646
651
|
}
|
|
647
652
|
|
|
@@ -12,7 +12,7 @@ import type {
|
|
|
12
12
|
} from "./register.ts";
|
|
13
13
|
import {
|
|
14
14
|
type AuthenticateMode,
|
|
15
|
-
type
|
|
15
|
+
type CallerDataGuard,
|
|
16
16
|
BUILDER_MARKER,
|
|
17
17
|
type CacheConfig,
|
|
18
18
|
type DeferFn,
|
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
type SearchParamsValidator,
|
|
27
27
|
type ServerThrowHelpers,
|
|
28
28
|
type ServerUrlHelpers,
|
|
29
|
-
|
|
29
|
+
assertCallerData,
|
|
30
30
|
} from "./types.ts";
|
|
31
31
|
|
|
32
32
|
/* ── callback context types ─────────────────────────────────────── */
|
|
@@ -317,11 +317,11 @@ interface LayoutBuilderAfterInput<
|
|
|
317
317
|
TAuth extends AuthenticateMode,
|
|
318
318
|
TPreloaderContext,
|
|
319
319
|
> extends LayoutBuilderAfterAuthenticate<TPath, TParams, TSearch, TAuth, TPreloaderContext> {
|
|
320
|
-
/**
|
|
320
|
+
/** Requires a user; anonymous requests get the unauthenticated boundary. Arguments are callerData for `authenticateFn`. */
|
|
321
321
|
authenticate<const TArgs extends readonly unknown[]>(
|
|
322
|
-
...args: TArgs
|
|
323
|
-
): LayoutBuilderAfterAuthenticate<TPath, TParams, TSearch,
|
|
324
|
-
/**
|
|
322
|
+
...args: TArgs & CallerDataGuard<TArgs>
|
|
323
|
+
): LayoutBuilderAfterAuthenticate<TPath, TParams, TSearch, true, TPreloaderContext>;
|
|
324
|
+
/** Lets anonymous requests through with `ctx.auth === null`. Arguments are callerData for `authenticateFn`. */
|
|
325
325
|
authenticateOptional(
|
|
326
326
|
...args: unknown[]
|
|
327
327
|
): LayoutBuilderAfterAuthenticate<TPath, TParams, TSearch, "optional", TPreloaderContext>;
|
|
@@ -607,14 +607,12 @@ function createBuilderAfterInput<
|
|
|
607
607
|
>(state: BuilderStateInternal): LayoutBuilderAfterInput<TPath, TParams, TSearch, TAuth, TPreloaderContext> {
|
|
608
608
|
return {
|
|
609
609
|
...createBuilderAfterAuthenticate<TPath, TParams, TSearch, TAuth, TPreloaderContext>(state),
|
|
610
|
-
authenticate
|
|
611
|
-
return createBuilderAfterAuthenticate<
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
TPreloaderContext
|
|
617
|
-
>({ ...state, ...resolveAuthenticateArgs(args) });
|
|
610
|
+
authenticate(...args: readonly unknown[]) {
|
|
611
|
+
return createBuilderAfterAuthenticate<TPath, TParams, TSearch, true, TPreloaderContext>({
|
|
612
|
+
...state,
|
|
613
|
+
authenticate: assertCallerData(args),
|
|
614
|
+
authenticateMode: true,
|
|
615
|
+
});
|
|
618
616
|
},
|
|
619
617
|
authenticateOptional(...args: unknown[]) {
|
|
620
618
|
return createBuilderAfterAuthenticate<TPath, TParams, TSearch, "optional", TPreloaderContext>({
|
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
} from "./register.ts";
|
|
12
12
|
import {
|
|
13
13
|
type AuthenticateMode,
|
|
14
|
-
type
|
|
14
|
+
type CallerDataGuard,
|
|
15
15
|
BUILDER_MARKER,
|
|
16
16
|
type CacheConfig,
|
|
17
17
|
type DeferFn,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
type SearchParamsValidator,
|
|
26
26
|
type ServerThrowHelpers,
|
|
27
27
|
type ServerUrlHelpers,
|
|
28
|
-
|
|
28
|
+
assertCallerData,
|
|
29
29
|
} from "./types.ts";
|
|
30
30
|
|
|
31
31
|
/* ── callback context types ─────────────────────────────────────── */
|
|
@@ -387,11 +387,11 @@ interface PageBuilderAfterInput<
|
|
|
387
387
|
TAuth extends AuthenticateMode,
|
|
388
388
|
TPreloaderContext,
|
|
389
389
|
> extends PageBuilderAfterAuthenticate<TPath, TParams, TSearch, TAuth, TPreloaderContext> {
|
|
390
|
-
/**
|
|
390
|
+
/** Requires a user; anonymous requests get the unauthenticated boundary. Arguments are callerData for `authenticateFn`. */
|
|
391
391
|
authenticate<const TArgs extends readonly unknown[]>(
|
|
392
|
-
...args: TArgs
|
|
393
|
-
): PageBuilderAfterAuthenticate<TPath, TParams, TSearch,
|
|
394
|
-
/**
|
|
392
|
+
...args: TArgs & CallerDataGuard<TArgs>
|
|
393
|
+
): PageBuilderAfterAuthenticate<TPath, TParams, TSearch, true, TPreloaderContext>;
|
|
394
|
+
/** Lets anonymous requests through with `ctx.auth === null`. Arguments are callerData for `authenticateFn`. */
|
|
395
395
|
authenticateOptional(
|
|
396
396
|
...args: unknown[]
|
|
397
397
|
): PageBuilderAfterAuthenticate<TPath, TParams, TSearch, "optional", TPreloaderContext>;
|
|
@@ -732,14 +732,12 @@ function createBuilderAfterInput<
|
|
|
732
732
|
>(state: BuilderStateInternal): PageBuilderAfterInput<TPath, TParams, TSearch, TAuth, TPreloaderContext> {
|
|
733
733
|
return {
|
|
734
734
|
...createBuilderAfterAuthenticate<TPath, TParams, TSearch, TAuth, TPreloaderContext>(state),
|
|
735
|
-
authenticate
|
|
736
|
-
return createBuilderAfterAuthenticate<
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
TPreloaderContext
|
|
742
|
-
>({ ...state, ...resolveAuthenticateArgs(args) });
|
|
735
|
+
authenticate(...args: readonly unknown[]) {
|
|
736
|
+
return createBuilderAfterAuthenticate<TPath, TParams, TSearch, true, TPreloaderContext>({
|
|
737
|
+
...state,
|
|
738
|
+
authenticate: assertCallerData(args),
|
|
739
|
+
authenticateMode: true,
|
|
740
|
+
});
|
|
743
741
|
},
|
|
744
742
|
authenticateOptional(...args: unknown[]) {
|
|
745
743
|
return createBuilderAfterAuthenticate<TPath, TParams, TSearch, "optional", TPreloaderContext>({
|
|
@@ -4,7 +4,7 @@ import type { Location } from "../router-primitives/index.ts";
|
|
|
4
4
|
import type { ResolvedAuth, ResolvedEnv, ResolvedQueryClient, ResolvedServerContext } from "./register.ts";
|
|
5
5
|
import {
|
|
6
6
|
type AuthenticateMode,
|
|
7
|
-
type
|
|
7
|
+
type CallerDataGuard,
|
|
8
8
|
BUILDER_MARKER,
|
|
9
9
|
type CacheConfig,
|
|
10
10
|
type DeferFn,
|
|
@@ -18,7 +18,7 @@ import {
|
|
|
18
18
|
type SearchParamsValidator,
|
|
19
19
|
type ServerThrowHelpers,
|
|
20
20
|
type ServerUrlHelpers,
|
|
21
|
-
|
|
21
|
+
assertCallerData,
|
|
22
22
|
} from "./types.ts";
|
|
23
23
|
|
|
24
24
|
/* ── callback context types (root-specific) ─────────────────────── */
|
|
@@ -304,11 +304,11 @@ interface RootBuilderAfterInput<TPath extends string, TParams, TSearch> extends
|
|
|
304
304
|
TSearch,
|
|
305
305
|
false
|
|
306
306
|
> {
|
|
307
|
-
/**
|
|
307
|
+
/** Requires a user; anonymous requests get the unauthenticated boundary. Arguments are callerData for `authenticateFn`. */
|
|
308
308
|
authenticate<const TArgs extends readonly unknown[]>(
|
|
309
|
-
...args: TArgs
|
|
310
|
-
): RootBuilderAfterAuthenticate<TPath, TParams, TSearch,
|
|
311
|
-
/**
|
|
309
|
+
...args: TArgs & CallerDataGuard<TArgs>
|
|
310
|
+
): RootBuilderAfterAuthenticate<TPath, TParams, TSearch, true>;
|
|
311
|
+
/** Lets anonymous requests through with `ctx.auth === null`. Arguments are callerData for `authenticateFn`. */
|
|
312
312
|
authenticateOptional(...args: unknown[]): RootBuilderAfterAuthenticate<TPath, TParams, TSearch, "optional">;
|
|
313
313
|
}
|
|
314
314
|
|
|
@@ -575,10 +575,11 @@ function createBuilderAfterInput<TPath extends string, TParams, TSearch>(
|
|
|
575
575
|
): RootBuilderAfterInput<TPath, TParams, TSearch> {
|
|
576
576
|
return {
|
|
577
577
|
...createBuilderAfterAuthenticate<TPath, TParams, TSearch, false>(state),
|
|
578
|
-
authenticate
|
|
579
|
-
return createBuilderAfterAuthenticate<TPath, TParams, TSearch,
|
|
578
|
+
authenticate(...args: readonly unknown[]) {
|
|
579
|
+
return createBuilderAfterAuthenticate<TPath, TParams, TSearch, true>({
|
|
580
580
|
...state,
|
|
581
|
-
|
|
581
|
+
authenticate: assertCallerData(args),
|
|
582
|
+
authenticateMode: true,
|
|
582
583
|
});
|
|
583
584
|
},
|
|
584
585
|
authenticateOptional(...args: unknown[]) {
|
|
@@ -8,22 +8,21 @@ export type { FlareStore, FlareStoreEntry, StaticEntryData };
|
|
|
8
8
|
|
|
9
9
|
export type AuthenticateMode = false | "optional" | true;
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
export
|
|
21
|
-
authenticate:
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
: { authenticate: [...args], authenticateMode: true };
|
|
11
|
+
/**
|
|
12
|
+
* Arguments to `.authenticate()` / `.authenticateOptional()` are callerData for `authenticateFn`; the auth mode is
|
|
13
|
+
* the method name, never an argument. A leading `"optional"` is the old mode form, which once compiled to a
|
|
14
|
+
* required route: it does not type-check (`never`), and `assertCallerData` throws at definition.
|
|
15
|
+
*/
|
|
16
|
+
export type CallerDataGuard<TArgs extends readonly unknown[]> = TArgs extends readonly ["optional", ...unknown[]]
|
|
17
|
+
? never
|
|
18
|
+
: unknown;
|
|
19
|
+
|
|
20
|
+
export const AUTHENTICATE_OPTIONAL_ARGUMENT_ERROR =
|
|
21
|
+
'.authenticate("optional") is not an auth mode: arguments are callerData for authenticateFn. Use .authenticateOptional(...callerData) for optional auth.';
|
|
22
|
+
|
|
23
|
+
export function assertCallerData(args: readonly unknown[]): unknown[] {
|
|
24
|
+
if (args[0] === "optional") throw new Error(AUTHENTICATE_OPTIONAL_ARGUMENT_ERROR);
|
|
25
|
+
return [...args];
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
/**
|