@celestia-island/hikari 0.41.6 → 0.41.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celestia-island/hikari",
3
- "version": "0.41.6",
3
+ "version": "0.41.8",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Hikari Vue 3 component library — production-grade UI components based on shittim-chest design system",
@@ -56,9 +56,12 @@ interface GuardRouter {
56
56
  // Accepts any guard signature (vue-router's NavigationGuard takes extra
57
57
  // args; slimmer routers take fewer) — the callback just inspects `to`.
58
58
  // The guard callback receives the route; vue-router also passes from/next
59
- // which we ignore. `never`-parameter variance keeps both sides assignable.
60
- beforeEach: (fn: (to: GuardRoute, ...rest: unknown[]) => unknown) => void;
61
- onError?: (fn: (error: unknown, to: GuardRoute) => void) => void;
59
+ // which we ignore. METHOD syntax keeps both sides assignable cast-free:
60
+ // property-function syntax would fall under strictFunctionTypes' strict
61
+ // contravariance and reject a real vue-router Router (field report e.cw
62
+ // #84).
63
+ beforeEach(fn: (to: GuardRoute, ...rest: unknown[]) => unknown): void;
64
+ onError?(fn: (error: unknown, to: GuardRoute) => void): void;
62
65
  }
63
66
 
64
67
  export function createAuthGuard(router: GuardRouter, opts: AuthGuardOptions) {
@@ -0,0 +1,74 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import { createPoisonedLocationGuard, type GuardRouter } from "./navigationGuard";
4
+ import { createAuthGuard } from "./createAuthGuard";
5
+
6
+ /**
7
+ * Cast-free interoperability with a REAL vue-router Router shape.
8
+ *
9
+ * Field report e.cw #84: with GuardRouter.beforeEach declared as a
10
+ * PROPERTY function, strictFunctionTypes applies strict parameter
11
+ * contravariance and vue-router's Router (whose beforeEach is
12
+ * method-typed, taking a NavigationGuard with a full RouteNormalized
13
+ * `to`) can never satisfy GuardRouter — every consumer needed an
14
+ * `as GuardRouter` cast. Method syntax (bivariant parameters) is the
15
+ * contract under test here: the assignments below must compile with NO
16
+ * casts, which vue-tsc (the package gate) enforces on every run.
17
+ */
18
+
19
+ /** Minimal structural mimic of vue-router's method-typed Router —
20
+ * deliberately NOT GuardRouter-shaped (extra params, richer `to`,
21
+ * non-void return) so a variance regression fails compilation. */
22
+ interface VueRouterLike {
23
+ beforeEach(
24
+ guard: (
25
+ to: { path: string; fullPath: string; name?: string | symbol | null; meta?: Record<string, unknown> },
26
+ from: unknown,
27
+ next: unknown,
28
+ ) => unknown,
29
+ ): () => void;
30
+ onError?(handler: (error: unknown, to: { fullPath: string }) => void): () => void;
31
+ }
32
+
33
+ function makeVueRouterLike(): VueRouterLike {
34
+ type Guard = Parameters<VueRouterLike["beforeEach"]>[0];
35
+ const guards: Guard[] = [];
36
+ return {
37
+ beforeEach(guard: Guard) {
38
+ guards.push(guard);
39
+ return () => { const i = guards.indexOf(guard); if (i >= 0) guards.splice(i, 1); };
40
+ },
41
+ onError() { return () => undefined; },
42
+ };
43
+ }
44
+
45
+ // The contract: both assignments must be cast-free. If GuardRouter ever
46
+ // regresses to property-function syntax, vue-tsc rejects these lines.
47
+ const routerForGuard: GuardRouter = makeVueRouterLike();
48
+ const routerForAuth: Parameters<typeof createAuthGuard>[0] = makeVueRouterLike();
49
+
50
+ describe("GuardRouter cast-free interop", () => {
51
+ it("registers the poisoned-location guard on a vue-router-shaped router", () => {
52
+ const warn = vi.spyOn(console, "warn").mockImplementation(() => {});
53
+ createPoisonedLocationGuard(routerForGuard);
54
+ expect(routerForGuard).toBeDefined();
55
+ warn.mockRestore();
56
+ });
57
+
58
+ it("createAuthGuard accepts a vue-router-shaped router cast-free", () => {
59
+ const auth = {
60
+ isAuthenticated: false,
61
+ user: null,
62
+ tryRestoreSession: () => Promise.resolve(),
63
+ checkSetup: () => Promise.resolve({ needs_setup: false }),
64
+ fetchUser: () => Promise.resolve({}),
65
+ };
66
+ createAuthGuard(routerForAuth, {
67
+ loginRoute: "login",
68
+ homeRoute: "home",
69
+ setupRoute: "setup",
70
+ useAuthStore: () => auth,
71
+ });
72
+ expect(routerForAuth).toBeDefined();
73
+ });
74
+ });
@@ -21,13 +21,18 @@ const POISONED_LITERALS = new Set(["undefined", "null", "nan"]);
21
21
  * beforeEach, so any router-shaped object works (no vue-router dep:
22
22
  * hikari stays UI-library-scoped). */
23
23
  export interface GuardRouter {
24
- beforeEach: (fn: (to: GuardRoute, ...rest: unknown[]) => unknown) => void;
24
+ // METHOD syntax on purpose: property-function syntax falls under
25
+ // strictFunctionTypes' strict contravariance, which makes a real
26
+ // vue-router Router (whose beforeEach is itself method-typed with a
27
+ // NavigationGuard parameter) unassignable — consumers then need
28
+ // `as GuardRouter` casts (field report e.cw #84). Method parameters
29
+ // are bivariant, so both shapes interoperate cast-free.
30
+ beforeEach(fn: (to: GuardRoute, ...rest: unknown[]) => unknown): void;
25
31
  }
26
32
 
27
33
  export interface GuardRoute {
28
34
  path?: string | null;
29
35
  fullPath?: string;
30
- [k: string]: unknown;
31
36
  }
32
37
 
33
38
  export interface NavigationSafetyNetOptions extends HistorySafetyNetOptions {