@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()` / `.authenticate("optional")` | Auth gate |
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
- - `.authenticate("optional")` — user may be null; `ctx.auth` is `null` for anonymous requests.
466
- - Other arguments are callerData for `authenticateFn`: `.authenticate("admin")` is required, `.authenticate("optional", "viewer")` is optional.
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lovrozagar/flare",
3
- "version": "0.4.2",
3
+ "version": "0.5.0",
4
4
  "description": "Solid meta-framework. Server-driven, NDJSON streaming, renderToStream.",
5
5
  "keywords": [
6
6
  "flare",
@@ -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 { AuthenticateMode } from "../route-builder/types.ts";
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
- /** Auth mode of a builder chain; mirrors `resolveAuthenticateArgs` in the route builder. */
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 AuthenticateModeFromArgs,
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
- resolveAuthenticateArgs,
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
- /** Required by default; `.authenticate("optional")` lets anonymous requests through with `ctx.auth === null`. Other arguments are callerData for `authenticateFn`. */
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, AuthenticateModeFromArgs<TArgs>, TPreloaderContext>;
324
- /** @deprecated Use `.authenticate("optional", ...callerData)`. */
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<const TArgs extends readonly unknown[]>(...args: TArgs) {
611
- return createBuilderAfterAuthenticate<
612
- TPath,
613
- TParams,
614
- TSearch,
615
- AuthenticateModeFromArgs<TArgs>,
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 AuthenticateModeFromArgs,
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
- resolveAuthenticateArgs,
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
- /** Required by default; `.authenticate("optional")` lets anonymous requests through with `ctx.auth === null`. Other arguments are callerData for `authenticateFn`. */
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, AuthenticateModeFromArgs<TArgs>, TPreloaderContext>;
394
- /** @deprecated Use `.authenticate("optional", ...callerData)`. */
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<const TArgs extends readonly unknown[]>(...args: TArgs) {
736
- return createBuilderAfterAuthenticate<
737
- TPath,
738
- TParams,
739
- TSearch,
740
- AuthenticateModeFromArgs<TArgs>,
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 AuthenticateModeFromArgs,
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
- resolveAuthenticateArgs,
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
- /** Required by default; `.authenticate("optional")` lets anonymous requests through with `ctx.auth === null`. Other arguments are callerData for `authenticateFn`. */
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, AuthenticateModeFromArgs<TArgs>>;
311
- /** @deprecated Use `.authenticate("optional", ...callerData)`. */
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<const TArgs extends readonly unknown[]>(...args: TArgs) {
579
- return createBuilderAfterAuthenticate<TPath, TParams, TSearch, AuthenticateModeFromArgs<TArgs>>({
578
+ authenticate(...args: readonly unknown[]) {
579
+ return createBuilderAfterAuthenticate<TPath, TParams, TSearch, true>({
580
580
  ...state,
581
- ...resolveAuthenticateArgs(args),
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
- /** `.authenticate("optional", ...callerData)` is optional; any other argument list is callerData for a required gate. */
12
- export type AuthenticateModeFromArgs<TArgs extends readonly unknown[]> = TArgs extends readonly [
13
- "optional",
14
- ...unknown[],
15
- ]
16
- ? "optional"
17
- : true;
18
-
19
- /** Runtime half of `AuthenticateModeFromArgs`: splits the mode from the callerData forwarded to `authenticateFn`. */
20
- export function resolveAuthenticateArgs(args: readonly unknown[]): {
21
- authenticate: unknown[];
22
- authenticateMode: "optional" | true;
23
- } {
24
- return args[0] === "optional"
25
- ? { authenticate: args.slice(1), authenticateMode: "optional" }
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
  /**