@vexcms/next 0.1.0-alpha.27 → 0.1.0-alpha.28

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.
Files changed (40) hide show
  1. package/dist/HydrationBoundaryClient.d.ts +17 -0
  2. package/dist/HydrationBoundaryClient.d.ts.map +1 -0
  3. package/dist/HydrationBoundaryClient.js +9 -0
  4. package/dist/HydrationBoundaryClient.js.map +1 -0
  5. package/dist/NextAdminLayout.d.ts +21 -13
  6. package/dist/NextAdminLayout.d.ts.map +1 -1
  7. package/dist/NextAdminLayout.js +1 -1
  8. package/dist/NextAdminLayoutClient.d.ts +10 -4
  9. package/dist/NextAdminLayoutClient.d.ts.map +1 -1
  10. package/dist/NextAdminLayoutClient.js +104 -7
  11. package/dist/NextAdminLayoutClient.js.map +1 -1
  12. package/dist/NextAdminPage.d.ts +52 -5
  13. package/dist/NextAdminPage.d.ts.map +1 -1
  14. package/dist/NextAdminPage.js +1 -1
  15. package/dist/auth/VexAdminAuthAdapterContext.d.ts +66 -0
  16. package/dist/auth/VexAdminAuthAdapterContext.d.ts.map +1 -0
  17. package/dist/auth/index.d.ts +2 -0
  18. package/dist/auth/index.d.ts.map +1 -0
  19. package/dist/auth/index.js +12 -0
  20. package/dist/auth/index.js.map +1 -0
  21. package/dist/chunk-JB3GMZPF.js +525 -0
  22. package/dist/chunk-JB3GMZPF.js.map +1 -0
  23. package/dist/chunk-NE2VUFS2.js +15 -0
  24. package/dist/chunk-NE2VUFS2.js.map +1 -0
  25. package/dist/chunk-T7DNJLSX.js +21 -0
  26. package/dist/chunk-T7DNJLSX.js.map +1 -0
  27. package/dist/index.js +2 -2
  28. package/dist/navigation/index.d.ts +3 -0
  29. package/dist/navigation/index.d.ts.map +1 -0
  30. package/dist/navigation/useNextNavigate.d.ts +19 -0
  31. package/dist/navigation/useNextNavigate.d.ts.map +1 -0
  32. package/dist/navigation/useNextNavigationBlocker.d.ts +32 -0
  33. package/dist/navigation/useNextNavigationBlocker.d.ts.map +1 -0
  34. package/dist/serverQueryClient.d.ts +48 -0
  35. package/dist/serverQueryClient.d.ts.map +1 -0
  36. package/package.json +11 -4
  37. package/dist/chunk-DDH2TJTP.js +0 -195
  38. package/dist/chunk-DDH2TJTP.js.map +0 -1
  39. package/dist/chunk-UMJTAD5A.js +0 -22
  40. package/dist/chunk-UMJTAD5A.js.map +0 -1
@@ -0,0 +1,17 @@
1
+ import { HydrationBoundary } from "@tanstack/react-query";
2
+ /**
3
+ * Re-exports `@tanstack/react-query`'s own `HydrationBoundary` through an explicit
4
+ * `"use client"` module boundary, in its own tsup entry (`tsup.config.ts`'s client build
5
+ * target, `@tanstack/react-query` marked `external` there) — `NextAdminPage.tsx` (a Server
6
+ * Component, no banner) imports it from HERE, not directly from `@tanstack/react-query`.
7
+ *
8
+ * Importing `HydrationBoundary` directly into `NextAdminPage.tsx`'s own no-banner tsup
9
+ * bundle let tsup inline/bundle it into the SAME output chunk as the server component code
10
+ * — losing the "use client" boundary Next.js's RSC compiler needs to see at the MODULE
11
+ * level (boundary detection is per-file, not per-component-within-a-bundled-chunk),
12
+ * confirmed live: "createContext only works in Client Components" from inside
13
+ * `HydrationBoundary`'s own internals. The same class of issue `NextAdminLayoutClient.tsx`
14
+ * exists to avoid for `NextAdminLayout`'s client leaf.
15
+ */
16
+ export { HydrationBoundary as HydrationBoundaryClient };
17
+ //# sourceMappingURL=HydrationBoundaryClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HydrationBoundaryClient.d.ts","sourceRoot":"","sources":["../src/HydrationBoundaryClient.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAE1D;;;;;;;;;;;;;GAaG;AACH,OAAO,EAAE,iBAAiB,IAAI,uBAAuB,EAAE,CAAC"}
@@ -0,0 +1,9 @@
1
+ "use client";
2
+ "use client";
3
+
4
+ // src/HydrationBoundaryClient.tsx
5
+ import { HydrationBoundary } from "@tanstack/react-query";
6
+ export {
7
+ HydrationBoundary as HydrationBoundaryClient
8
+ };
9
+ //# sourceMappingURL=HydrationBoundaryClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/HydrationBoundaryClient.tsx"],"sourcesContent":["\"use client\";\n\nimport { HydrationBoundary } from \"@tanstack/react-query\";\n\n/**\n * Re-exports `@tanstack/react-query`'s own `HydrationBoundary` through an explicit\n * `\"use client\"` module boundary, in its own tsup entry (`tsup.config.ts`'s client build\n * target, `@tanstack/react-query` marked `external` there) — `NextAdminPage.tsx` (a Server\n * Component, no banner) imports it from HERE, not directly from `@tanstack/react-query`.\n *\n * Importing `HydrationBoundary` directly into `NextAdminPage.tsx`'s own no-banner tsup\n * bundle let tsup inline/bundle it into the SAME output chunk as the server component code\n * — losing the \"use client\" boundary Next.js's RSC compiler needs to see at the MODULE\n * level (boundary detection is per-file, not per-component-within-a-bundled-chunk),\n * confirmed live: \"createContext only works in Client Components\" from inside\n * `HydrationBoundary`'s own internals. The same class of issue `NextAdminLayoutClient.tsx`\n * exists to avoid for `NextAdminLayout`'s client leaf.\n */\nexport { HydrationBoundary as HydrationBoundaryClient };\n"],"mappings":";;;;AAEA,SAAS,yBAAyB;","names":[]}
@@ -1,36 +1,45 @@
1
1
  import type { ReactNode } from "react";
2
- import type { AdminUser } from "@vexcms/react";
2
+ import { type VexApiAuth } from "@vexcms/core";
3
3
  /**
4
4
  * Next.js admin layout shell for VexCMS.
5
5
  *
6
- * A **server component** that reads the `sidebar_state` cookie server-side
7
- * (so the sidebar's open/closed state is correct on first paint, with no
8
- * client flash) and passes it, plus `user`/`organization`/`children`, to the
6
+ * A **server component** that reads the consolidated `vex-admin` cookie's
7
+ * `sidebarOpen` field server-side (Decision 51; previously a dedicated
8
+ * `sidebar_state` cookie) so the sidebar's open/closed state is correct on
9
+ * first paint, with no client flash, and passes it, plus `authPromise`/
10
+ * `children`, to the
9
11
  * client leaf `NextAdminLayoutClient`. Config no longer crosses this
10
12
  * boundary at all — the admin panel's client tree gets its config from the
11
13
  * app's own root `VexConfigProvider` mount
12
14
  * (`components/providers/client.tsx`), not from a prop threaded through this
13
15
  * layout.
14
16
  *
17
+ * Does **not** await `authPromise` itself (perf fix, Decision 44/streaming) — only
18
+ * `cookies()` (fast, no network). The sidebar resolves `authPromise` inside its
19
+ * OWN `<Suspense>` boundary (`AdminLayout`, `@vexcms/react`), so a slow
20
+ * `getUserOrg()` round trip only delays the sidebar nav, never `children` or
21
+ * this layout's own static chrome.
22
+ *
15
23
  * Render it from your admin `layout.tsx`. Because this is a server component,
16
- * your `layout.tsx` may itself be an `async` server component (e.g. to run auth
17
- * checks).
24
+ * your `layout.tsx` may itself call server-only auth helpers to build
25
+ * `authPromise` (e.g. `getUserOrg()` from `~/auth/server`) — started, not
26
+ * awaited, so `layout.tsx` itself stays synchronous and streams immediately.
18
27
  *
19
28
  * @param props - Layout props.
20
29
  * @param props.children - The page content from `[[...slug]]/page.tsx`.
21
- * @param props.user - Current user for the admin shell.
30
+ * @param props.authPromise - The in-flight `{ user, organization }` fetch (Decision 44),
31
+ * optional — omit it entirely when `config.access` is absent/disabled.
22
32
  * @returns The rendered admin shell, wrapping `children` in the client boundary
23
33
  * ({@link NextAdminLayoutClient}) with the server-read sidebar cookie state.
24
34
  * @example
25
35
  * ```tsx
26
36
  * // app/admin/layout.tsx
27
37
  * import { NextAdminLayout } from "@vexcms/next/client";
28
- * import { getCurrentUser } from "~/auth/serverUtils";
38
+ * import { getUserOrg } from "~/auth/server";
29
39
  *
30
- * export default async function AdminLayout({ children }: { children: ReactNode }) {
31
- * const user = await getCurrentUser();
40
+ * export default function AdminLayout({ children }: { children: ReactNode }) {
32
41
  * return (
33
- * <NextAdminLayout user={user ?? undefined}>
42
+ * <NextAdminLayout authPromise={getUserOrg()}>
34
43
  * {children}
35
44
  * </NextAdminLayout>
36
45
  * );
@@ -39,7 +48,6 @@ import type { AdminUser } from "@vexcms/react";
39
48
  */
40
49
  export declare function NextAdminLayout(props: {
41
50
  children: ReactNode;
42
- user?: AdminUser;
43
- organization?: Record<string, unknown>;
51
+ authPromise?: Promise<VexApiAuth>;
44
52
  }): Promise<import("react").JSX.Element>;
45
53
  //# sourceMappingURL=NextAdminLayout.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NextAdminLayout.d.ts","sourceRoot":"","sources":["../src/NextAdminLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAC;AAI/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,SAAS,CAAC;IACpB,IAAI,CAAC,EAAE,SAAS,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC,wCAaA"}
1
+ {"version":3,"file":"NextAdminLayout.d.ts","sourceRoot":"","sources":["../src/NextAdminLayout.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,EAAuC,KAAK,UAAU,EAAE,MAAM,cAAc,CAAC;AAIpF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AACH,wBAAsB,eAAe,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CACnC,wCASA"}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  NextAdminLayout
3
- } from "./chunk-UMJTAD5A.js";
3
+ } from "./chunk-NE2VUFS2.js";
4
4
  export {
5
5
  NextAdminLayout
6
6
  };
@@ -1,5 +1,5 @@
1
1
  import type { ReactNode } from "react";
2
- import { type AdminUser } from "@vexcms/react";
2
+ import type { VexApiAuth } from "@vexcms/core";
3
3
  /**
4
4
  * Client leaf for the Next.js admin layout.
5
5
  *
@@ -10,6 +10,11 @@ import { type AdminUser } from "@vexcms/react";
10
10
  * admin routes.
11
11
  * - `NextLink` / `NextImage` passed as framework components so every
12
12
  * `VexLink`/`VexImage` uses Next.js routing and image optimisation.
13
+ * - `useVexAdminAuthAdapter()` (`./auth/VexAdminAuthAdapterContext`) read here and
14
+ * forwarded into `components.auth`, so `AdminTopNav`'s sign-in/out control picks up
15
+ * whatever adapter the app mounted via `VexAdminAuthAdapterProvider`
16
+ * (`@vexcms/next/auth`) — `undefined` when the app hasn't mounted one, same as the
17
+ * control simply not rendering.
13
18
  *
14
19
  * Config reaches `AdminLayout`'s children through the app's own
15
20
  * `VexConfigProvider` mount, not through a prop on this component — this
@@ -20,15 +25,16 @@ import { type AdminUser } from "@vexcms/react";
20
25
  *
21
26
  * @param props - Layout props.
22
27
  * @param props.children - The page content from `[[...slug]]/page.tsx`.
23
- * @param props.user - Current user for the admin shell.
28
+ * @param props.authPromise - The in-flight `{ user, organization }` fetch (Decision 44),
29
+ * forwarded to `AdminLayout` so the sidebar resolves it in its OWN `<Suspense>` boundary —
30
+ * a slow auth round trip streams in the sidebar nav without blocking `children`.
24
31
  * @returns The admin shell rendered from `children`, wrapped in `NuqsAdapter`
25
32
  * and the shared `AdminLayout`, wired with the active slug/document id derived
26
33
  * from `usePathname()` and Next.js `Link`/`Image` components.
27
34
  */
28
35
  export declare function NextAdminLayoutClient(props: {
29
36
  children: ReactNode;
30
- organization?: Record<string, unknown>;
37
+ authPromise?: Promise<VexApiAuth>;
31
38
  sidebarOpen?: boolean;
32
- user?: AdminUser;
33
39
  }): import("react").JSX.Element;
34
40
  //# sourceMappingURL=NextAdminLayoutClient.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NextAdminLayoutClient.d.ts","sourceRoot":"","sources":["../src/NextAdminLayoutClient.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,EAAe,KAAK,SAAS,EAAE,MAAM,eAAe,CAAC;AAM5D;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,SAAS,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,IAAI,CAAC,EAAE,SAAS,CAAC;CAClB,+BAuBA"}
1
+ {"version":3,"file":"NextAdminLayoutClient.d.ts","sourceRoot":"","sources":["../src/NextAdminLayoutClient.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AACvC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAY/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE;IAC3C,QAAQ,EAAE,SAAS,CAAC;IACpB,WAAW,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAClC,WAAW,CAAC,EAAE,OAAO,CAAC;CACvB,+BA8BA"}
@@ -1,5 +1,8 @@
1
1
  "use client";
2
2
  "use client";
3
+ import {
4
+ useVexAdminAuthAdapter
5
+ } from "./chunk-T7DNJLSX.js";
3
6
 
4
7
  // src/NextAdminLayoutClient.tsx
5
8
  import { AdminLayout } from "@vexcms/react";
@@ -7,22 +10,116 @@ import NextImage from "next/image";
7
10
  import NextLink from "next/link";
8
11
  import { usePathname } from "next/navigation";
9
12
  import { NuqsAdapter } from "nuqs/adapters/next/app";
13
+
14
+ // src/navigation/useNextNavigationBlocker.ts
15
+ import { useEffect, useRef } from "react";
16
+ import { useRouter } from "next/navigation";
17
+ function useNextNavigationBlocker(props) {
18
+ const router = useRouter();
19
+ const routerRef = useRef(router);
20
+ routerRef.current = router;
21
+ const whenRef = useRef(props.when);
22
+ whenRef.current = props.when;
23
+ const onBlockRef = useRef(props.onBlock);
24
+ onBlockRef.current = props.onBlock;
25
+ const popstateReplayRef = useRef(false);
26
+ const sentinelActiveRef = useRef(false);
27
+ useEffect(() => {
28
+ function onClick(event) {
29
+ const anchor = event.target.closest("a");
30
+ if (!anchor || event.defaultPrevented || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey || anchor.target || anchor.hasAttribute("download")) {
31
+ return;
32
+ }
33
+ const url = new URL(anchor.href, window.location.href);
34
+ if (url.origin !== window.location.origin) {
35
+ return;
36
+ }
37
+ if (url.pathname + url.search === window.location.pathname + window.location.search) {
38
+ return;
39
+ }
40
+ if (!whenRef.current) {
41
+ return;
42
+ }
43
+ event.preventDefault();
44
+ onBlockRef.current(() => {
45
+ const href = url.pathname + url.search;
46
+ if (isSentinelEntry()) {
47
+ sentinelActiveRef.current = false;
48
+ routerRef.current.replace(href);
49
+ return;
50
+ }
51
+ routerRef.current.push(href);
52
+ });
53
+ }
54
+ function onPopState() {
55
+ if (popstateReplayRef.current) {
56
+ popstateReplayRef.current = false;
57
+ return;
58
+ }
59
+ const leftSentinel = sentinelActiveRef.current;
60
+ sentinelActiveRef.current = false;
61
+ if (!whenRef.current || !leftSentinel) {
62
+ return;
63
+ }
64
+ pushSentinel();
65
+ onBlockRef.current(() => {
66
+ sentinelActiveRef.current = false;
67
+ popstateReplayRef.current = true;
68
+ window.history.go(-2);
69
+ });
70
+ }
71
+ document.addEventListener("click", onClick, true);
72
+ window.addEventListener("popstate", onPopState);
73
+ return () => {
74
+ document.removeEventListener("click", onClick, true);
75
+ window.removeEventListener("popstate", onPopState);
76
+ sentinelActiveRef.current = false;
77
+ };
78
+ }, []);
79
+ useEffect(() => {
80
+ if (!props.when || sentinelActiveRef.current) return;
81
+ pushSentinel();
82
+ }, [props.when]);
83
+ function pushSentinel() {
84
+ window.history.pushState({ [SENTINEL_STATE_KEY]: true }, "", window.location.href);
85
+ sentinelActiveRef.current = true;
86
+ }
87
+ }
88
+ var SENTINEL_STATE_KEY = "__vexNavigationSentinel";
89
+ function isSentinelEntry() {
90
+ const state = window.history.state;
91
+ return typeof state === "object" && state !== null && SENTINEL_STATE_KEY in state;
92
+ }
93
+
94
+ // src/navigation/useNextNavigate.ts
95
+ import { useRouter as useRouter2 } from "next/navigation";
96
+ function useNextNavigate() {
97
+ const router = useRouter2();
98
+ return router.push;
99
+ }
100
+
101
+ // src/NextAdminLayoutClient.tsx
10
102
  import { jsx } from "react/jsx-runtime";
11
103
  function NextAdminLayoutClient(props) {
12
104
  const pathname = usePathname();
13
- const segments = pathname.split("/").filter(Boolean);
14
- const activeSlug = segments[1];
15
- const activeDocID = segments[2];
105
+ const auth = useVexAdminAuthAdapter();
106
+ const [, section, slug, documentId] = pathname.split("/").filter(Boolean);
107
+ const activeSlug = section === "collections" ? slug : section;
108
+ const activeDocID = section === "collections" ? documentId : slug;
16
109
  return /* @__PURE__ */ jsx(NuqsAdapter, { children: /* @__PURE__ */ jsx(
17
110
  AdminLayout,
18
111
  {
19
112
  activeDocID,
20
113
  activeSlug,
21
- components: { Link: NextLink, Image: NextImage },
22
- organization: props.organization,
23
- pathname,
114
+ authPromise: props.authPromise,
115
+ components: {
116
+ Link: NextLink,
117
+ Image: NextImage,
118
+ navigationBlocker: { useBlocker: useNextNavigationBlocker },
119
+ useNavigate: useNextNavigate,
120
+ auth
121
+ },
24
122
  sidebarOpen: props.sidebarOpen,
25
- user: props.user,
26
123
  children: props.children
27
124
  }
28
125
  ) });
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/NextAdminLayoutClient.tsx"],"sourcesContent":["\"use client\";\n// \"use client\" is injected by the tsup build banner for this entry (see\n// tsup.config.ts) so the emitted module carries the client boundary.\nimport type { ReactNode } from \"react\";\n\nimport { AdminLayout, type AdminUser } from \"@vexcms/react\";\nimport NextImage from \"next/image\";\nimport NextLink from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { NuqsAdapter } from \"nuqs/adapters/next/app\";\n\n/**\n * Client leaf for the Next.js admin layout.\n *\n * Owns all client-only wiring that cannot run on the server:\n * - `usePathname()` to derive the active collection slug / document id for\n * sidebar highlighting (Next.js layouts do not receive route params).\n * - `NuqsAdapter` (`nuqs/adapters/next/app`) so URL state works inside the\n * admin routes.\n * - `NextLink` / `NextImage` passed as framework components so every\n * `VexLink`/`VexImage` uses Next.js routing and image optimisation.\n *\n * Config reaches `AdminLayout`'s children through the app's own\n * `VexConfigProvider` mount, not through a prop on this component — this\n * leaf never touches config at all.\n *\n * This component is internal to `@vexcms/next` — applications render\n * {@link NextAdminLayout} instead.\n *\n * @param props - Layout props.\n * @param props.children - The page content from `[[...slug]]/page.tsx`.\n * @param props.user - Current user for the admin shell.\n * @returns The admin shell rendered from `children`, wrapped in `NuqsAdapter`\n * and the shared `AdminLayout`, wired with the active slug/document id derived\n * from `usePathname()` and Next.js `Link`/`Image` components.\n */\nexport function NextAdminLayoutClient(props: {\n children: ReactNode;\n organization?: Record<string, unknown>;\n sidebarOpen?: boolean;\n user?: AdminUser;\n}) {\n const pathname = usePathname();\n // pathname: \"/admin\", \"/admin/posts\", \"/admin/posts/123\"\n // Split on \"/\" and take the segment after \"admin\"\n const segments = pathname.split(\"/\").filter(Boolean);\n const activeSlug = segments[1]; // undefined on /admin, \"posts\" on /admin/posts\n const activeDocID = segments[2];\n\n return (\n <NuqsAdapter>\n <AdminLayout\n activeDocID={activeDocID}\n activeSlug={activeSlug}\n components={{ Link: NextLink, Image: NextImage }}\n organization={props.organization}\n pathname={pathname}\n sidebarOpen={props.sidebarOpen}\n user={props.user}\n >\n {props.children}\n </AdminLayout>\n </NuqsAdapter>\n );\n}\n"],"mappings":";;;;AAKA,SAAS,mBAAmC;AAC5C,OAAO,eAAe;AACtB,OAAO,cAAc;AACrB,SAAS,mBAAmB;AAC5B,SAAS,mBAAmB;AA0CtB;AAfC,SAAS,sBAAsB,OAKnC;AACD,QAAM,WAAW,YAAY;AAG7B,QAAM,WAAW,SAAS,MAAM,GAAG,EAAE,OAAO,OAAO;AACnD,QAAM,aAAa,SAAS,CAAC;AAC7B,QAAM,cAAc,SAAS,CAAC;AAE9B,SACE,oBAAC,eACC;AAAA,IAAC;AAAA;AAAA,MACC;AAAA,MACA;AAAA,MACA,YAAY,EAAE,MAAM,UAAU,OAAO,UAAU;AAAA,MAC/C,cAAc,MAAM;AAAA,MACpB;AAAA,MACA,aAAa,MAAM;AAAA,MACnB,MAAM,MAAM;AAAA,MAEX,gBAAM;AAAA;AAAA,EACT,GACF;AAEJ;","names":[]}
1
+ {"version":3,"sources":["../src/NextAdminLayoutClient.tsx","../src/navigation/useNextNavigationBlocker.ts","../src/navigation/useNextNavigate.ts"],"sourcesContent":["\"use client\";\n// \"use client\" is injected by the tsup build banner for this entry (see\n// tsup.config.ts) so the emitted module carries the client boundary.\nimport type { ReactNode } from \"react\";\nimport type { VexApiAuth } from \"@vexcms/core\";\n\nimport { AdminLayout } from \"@vexcms/react\";\nimport NextImage from \"next/image\";\nimport NextLink from \"next/link\";\nimport { usePathname } from \"next/navigation\";\nimport { NuqsAdapter } from \"nuqs/adapters/next/app\";\n\nimport { useNextNavigationBlocker } from \"./navigation/useNextNavigationBlocker\";\nimport { useNextNavigate } from \"./navigation/useNextNavigate\";\nimport { useVexAdminAuthAdapter } from \"./auth/VexAdminAuthAdapterContext\";\n\n/**\n * Client leaf for the Next.js admin layout.\n *\n * Owns all client-only wiring that cannot run on the server:\n * - `usePathname()` to derive the active collection slug / document id for\n * sidebar highlighting (Next.js layouts do not receive route params).\n * - `NuqsAdapter` (`nuqs/adapters/next/app`) so URL state works inside the\n * admin routes.\n * - `NextLink` / `NextImage` passed as framework components so every\n * `VexLink`/`VexImage` uses Next.js routing and image optimisation.\n * - `useVexAdminAuthAdapter()` (`./auth/VexAdminAuthAdapterContext`) read here and\n * forwarded into `components.auth`, so `AdminTopNav`'s sign-in/out control picks up\n * whatever adapter the app mounted via `VexAdminAuthAdapterProvider`\n * (`@vexcms/next/auth`) — `undefined` when the app hasn't mounted one, same as the\n * control simply not rendering.\n *\n * Config reaches `AdminLayout`'s children through the app's own\n * `VexConfigProvider` mount, not through a prop on this component — this\n * leaf never touches config at all.\n *\n * This component is internal to `@vexcms/next` — applications render\n * {@link NextAdminLayout} instead.\n *\n * @param props - Layout props.\n * @param props.children - The page content from `[[...slug]]/page.tsx`.\n * @param props.authPromise - The in-flight `{ user, organization }` fetch (Decision 44),\n * forwarded to `AdminLayout` so the sidebar resolves it in its OWN `<Suspense>` boundary —\n * a slow auth round trip streams in the sidebar nav without blocking `children`.\n * @returns The admin shell rendered from `children`, wrapped in `NuqsAdapter`\n * and the shared `AdminLayout`, wired with the active slug/document id derived\n * from `usePathname()` and Next.js `Link`/`Image` components.\n */\nexport function NextAdminLayoutClient(props: {\n children: ReactNode;\n authPromise?: Promise<VexApiAuth>;\n sidebarOpen?: boolean;\n}) {\n const pathname = usePathname();\n const auth = useVexAdminAuthAdapter();\n // pathname: \"/admin\", \"/admin/collections/posts[/<id>]\", \"/admin/globals[/<slug>]\"\n // Split on \"/\" and read the segments after \"admin\". Collections report their\n // slug + document id; globals keep the \"globals\" section as the active slug\n // and the global's slug as the doc id (the sidebar/top nav contract).\n const [, section, slug, documentId] = pathname.split(\"/\").filter(Boolean);\n const activeSlug = section === \"collections\" ? slug : section;\n const activeDocID = section === \"collections\" ? documentId : slug;\n\n return (\n <NuqsAdapter>\n <AdminLayout\n activeDocID={activeDocID}\n activeSlug={activeSlug}\n authPromise={props.authPromise}\n components={{\n Link: NextLink,\n Image: NextImage,\n navigationBlocker: { useBlocker: useNextNavigationBlocker },\n useNavigate: useNextNavigate,\n auth,\n }}\n sidebarOpen={props.sidebarOpen}\n >\n {props.children}\n </AdminLayout>\n </NuqsAdapter>\n );\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { useRouter } from \"next/navigation\";\nimport type { NavigationBlockerAdapter } from \"@vexcms/react\";\n\n/**\n * Next.js App Router implementation of `NavigationBlockerAdapter[\"useBlocker\"]`.\n *\n * The App Router has no `routeChangeStart`-equivalent event and (spec decision 3)\n * programmatic navigation cannot be intercepted by patching\n * `history.pushState`/`replaceState` — Next's own `HistoryUpdater` calls\n * `pushState` from a `useInsertionEffect` AFTER the route has already\n * committed, and patches those methods itself. This hook therefore covers\n * only the two surfaces that fire BEFORE a route change commits: same-origin\n * `<a>` clicks (capture-phase `click` listener) and browser back/forward (a\n * `popstate` listener plus a duplicate-history-entry trap). Programmatic\n * navigation is guarded separately by `@vexcms/react`'s `useVexNavigate`,\n * which consults the guard itself before calling this adapter's\n * `useNavigate` (`useNextNavigate`, this package). `beforeunload` (tab close\n * / hard reload) is also NOT handled here — `@vexcms/react`'s\n * `useUnsavedChangesGuard` registers it directly, framework-independent.\n *\n * Provide the resulting adapter from `NextAdminLayoutClient`:\n * `components={{ …, navigationBlocker: { useBlocker: useNextNavigationBlocker } }}`.\n * `useUnsavedChangesGuard` then calls it once per mounted edit view with the\n * live dirty/autosave state.\n *\n * @param props - See `NavigationBlockerAdapter[\"useBlocker\"]` in\n * `@vexcms/react` (`packages/react/src/config/useFrameworkComponents.ts`).\n * @returns Nothing — every intercepted navigation surfaces through\n * `props.onBlock`, never a return value.\n * @throws Never under normal operation; a blocked navigation reports through\n * `onBlock`, not a throw.\n */\nexport function useNextNavigationBlocker(\n props: Parameters<NavigationBlockerAdapter[\"useBlocker\"]>[0],\n): void {\n const router = useRouter();\n\n // Refs, not state: the click/popstate listeners below are installed ONCE\n // per mount (empty dep array) so `when` flipping dirty<->clean never tears\n // down and reinstalls them, and the router's method identity (not\n // guaranteed stable across renders) never matters. Every handler reads\n // these refs instead of closing over the render's values.\n const routerRef = useRef(router);\n routerRef.current = router;\n const whenRef = useRef(props.when);\n whenRef.current = props.when;\n const onBlockRef = useRef(props.onBlock);\n onBlockRef.current = props.onBlock;\n\n // Set just before `history.go(-2)` on a blocked back/forward's Leave, so\n // the `popstate` that fires as a result of `history.go` is treated as the\n // replay instead of re-blocking it.\n const popstateReplayRef = useRef(false);\n // Whether the top-of-stack entry is our same-URL sentinel (pushed when\n // `when` turns true, re-pushed on a blocked popstate).\n const sentinelActiveRef = useRef(false);\n\n useEffect(() => {\n function onClick(event: MouseEvent) {\n const anchor = (event.target as Element).closest(\"a\");\n if (\n !anchor ||\n event.defaultPrevented ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey ||\n anchor.target ||\n anchor.hasAttribute(\"download\")\n ) {\n return;\n }\n\n const url = new URL(anchor.href, window.location.href);\n if (url.origin !== window.location.origin) {\n // External link — let the browser handle it.\n return;\n }\n\n if (\n url.pathname + url.search ===\n window.location.pathname + window.location.search\n ) {\n // Same-page `#hash` anchor — not a route change.\n return;\n }\n\n if (!whenRef.current) {\n return;\n }\n\n event.preventDefault();\n onBlockRef.current(() => {\n const href = url.pathname + url.search;\n if (isSentinelEntry()) {\n // The sentinel is a same-URL duplicate of the guarded page: replace\n // it, so history matches a navigation made with no guard mounted.\n sentinelActiveRef.current = false;\n routerRef.current.replace(href);\n return;\n }\n routerRef.current.push(href);\n });\n }\n\n function onPopState() {\n if (popstateReplayRef.current) {\n popstateReplayRef.current = false;\n return;\n }\n\n const leftSentinel = sentinelActiveRef.current;\n sentinelActiveRef.current = false;\n // Without a sentinel the browser already left the guarded URL — the\n // traversal can no longer be undone, so let it through.\n if (!whenRef.current || !leftSentinel) {\n return;\n }\n\n // Back/forward only stepped off the sentinel onto the guarded page's own\n // entry (same URL). Re-push the sentinel so a Stay leaves history as it was.\n pushSentinel();\n onBlockRef.current(() => {\n // \"Leave\": step past the re-pushed sentinel AND the guarded page's own\n // entry, landing where the original back press intended.\n sentinelActiveRef.current = false;\n popstateReplayRef.current = true;\n window.history.go(-2);\n });\n }\n\n document.addEventListener(\"click\", onClick, true);\n window.addEventListener(\"popstate\", onPopState);\n\n return () => {\n document.removeEventListener(\"click\", onClick, true);\n window.removeEventListener(\"popstate\", onPopState);\n // The sentinel is deliberately left in place: it is a traversable\n // same-URL entry (Next merged its router state into it), and popping it\n // here would start an async traversal that races — and cancels — the\n // navigation that usually caused this unmount (e.g. Delete's redirect\n // after the document query already returned null).\n sentinelActiveRef.current = false;\n };\n // Installs once per mount; handlers read refs.\n }, []);\n\n // A same-URL sentinel entry, pushed as soon as the guard starts blocking, so\n // the first back press lands on the guarded page's own entry instead of\n // leaving it. Never popped (popping triggers a traversal); a Leave through a\n // link replaces it instead.\n useEffect(() => {\n if (!props.when || sentinelActiveRef.current) return;\n pushSentinel();\n }, [props.when]);\n\n function pushSentinel(): void {\n // Next's patched `pushState` merges its own router state into this object,\n // so the entry stays traversable by the App Router.\n window.history.pushState({ [SENTINEL_STATE_KEY]: true }, \"\", window.location.href);\n sentinelActiveRef.current = true;\n }\n}\n\n/** History-state key marking the guard's same-URL sentinel entry. */\nconst SENTINEL_STATE_KEY = \"__vexNavigationSentinel\";\n\nfunction isSentinelEntry(): boolean {\n const state: unknown = window.history.state;\n return typeof state === \"object\" && state !== null && SENTINEL_STATE_KEY in state;\n}\n","\"use client\";\n\nimport { useRouter } from \"next/navigation\";\n\n/**\n * Next.js App Router implementation of `FrameworkComponents[\"useNavigate\"]`\n * (`packages/react/src/config/useFrameworkComponents.ts`, Step 7).\n *\n * Returns the App Router's own `router.push`, unwrapped — guarding is not\n * this hook's job. `@vexcms/react`'s `useVexNavigate`\n * (`packages/react/src/config/useVexNavigate.ts`) is the only caller: it\n * consults `useUnsavedChangesGuard`'s blocking state itself before invoking\n * the function this hook returns, so every built-in post-mutation\n * navigation (after delete, after a duplicate's create resolves) is guarded\n * without this hook knowing anything about dirty state.\n *\n * Provide it from `NextAdminLayoutClient`:\n * `components={{ …, useNavigate: useNextNavigate }}`.\n *\n * @returns A function that navigates to `href` via the App Router.\n */\nexport function useNextNavigate(): (href: string) => void {\n const router = useRouter();\n return router.push;\n}\n"],"mappings":"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","names":["useRouter"]}
@@ -1,4 +1,4 @@
1
- import type { VexConfig } from "@vexcms/core";
1
+ import type { VexApiAuth, VexConfig } from "@vexcms/core";
2
2
  /**
3
3
  * VexCMS admin page server component for Next.js.
4
4
  *
@@ -15,25 +15,65 @@ import type { VexConfig } from "@vexcms/core";
15
15
  * | `[collectionSlug, "new"]` | `CollectionEditView` (empty form) |
16
16
  * | `[collectionSlug, documentId]` | `CollectionEditView` with preloaded doc |
17
17
  *
18
+ * Takes `tokenPromise`/`authPromise`, not resolved values (perf fix, developer-binding): the
19
+ * caller (`page.tsx`) starts both without awaiting them immediately, so `NextAdminPage`'s own
20
+ * data fetches can begin as soon as `tokenPromise` resolves (what every `fetchQuery` call
21
+ * actually needs) rather than waiting on the slower `authPromise` (which itself depends on
22
+ * the token, plus one more Convex round trip). The caller's own page-level access gate
23
+ * (`canAccessAdminPanel`) should `await authPromise` and redirect BEFORE rendering
24
+ * `NextAdminPage` at all (Polish Decision 8, developer-reported — a generic page-level
25
+ * skeleton rendered while the gate was still in flight made the loading state worse than no
26
+ * gate-level fallback at all: shifts, row-selection spinners showing where real checkboxes
27
+ * belong, skeletons with no matching settled element, content popping in unannounced).
28
+ * `NextAdminPage` streams its own real chrome (title/tabs/toolbar/controls) immediately once
29
+ * rendered and wraps only its OWN data-dependent regions (e.g. table rows) in their own
30
+ * `<Suspense>` boundaries — no page-level fallback needed or wanted.
31
+ *
18
32
  * @param props - Component props
19
33
  * @param props.config - The resolved server `VexConfig`, typically the default export of
20
34
  * `vex.config.server.ts` — not the client-safe `vex.config.ts`.
21
35
  * @param props.params - Next.js 15 async params `{ path?: string[] }`
22
- * @param props.token - Optional session token forwarded to `fetchQuery` for authenticated reads.
36
+ * @param props.tokenPromise - The in-flight (or already-resolved) session token (e.g.
37
+ * `getToken()`), forwarded to every `fetchQuery` call for authenticated reads. Optional —
38
+ * a project with no auth at all omits it, every `fetchQuery` call runs unauthenticated.
39
+ * @param props.authPromise - The in-flight (or already-resolved — e.g. `Promise.resolve(auth)`
40
+ * after the caller's own gate already awaited it) server-resolved caller (Decision 44),
41
+ * OPTIONAL. Wraps the entire return in `VexAuthProvider` and narrows the collection list
42
+ * route's search field by read permission. Omit it when `config.access` is absent or
43
+ * disabled — nothing is restricted either way. An `access`-enabled project that omits it is
44
+ * treated as an anonymous caller (fail-closed) with a one-time dev console warning.
23
45
  * @returns The appropriate admin view for the current URL path.
24
46
  *
25
47
  * @example
26
48
  * ```tsx
27
49
  * // app/admin/[[...slug]]/page.tsx
50
+ * import { canAccessAdminPanel } from "@vexcms/core";
28
51
  * import { NextAdminPage } from "@vexcms/next/server";
52
+ * import { redirect } from "next/navigation";
53
+ * import { getToken, getUserOrg } from "~/auth/server";
29
54
  * import config from "../../../../vex.config.server";
30
55
  *
31
- * export default function AdminPage({
56
+ * export default async function AdminPage({
32
57
  * params,
33
58
  * }: {
34
59
  * params: Promise<{ path?: string[] }>;
35
60
  * }) {
36
- * return <NextAdminPage config={config} params={params} />;
61
+ * const token = await getToken();
62
+ * if (!token) redirect("/auth/sign-in?redirectTo=/admin");
63
+ * const auth = await getUserOrg();
64
+ * if (
65
+ * !canAccessAdminPanel({ access: config.access, user: auth.user, organization: auth.organization })
66
+ * ) {
67
+ * redirect("/unauthorized");
68
+ * }
69
+ * return (
70
+ * <NextAdminPage
71
+ * config={config}
72
+ * params={params}
73
+ * tokenPromise={Promise.resolve(token)}
74
+ * authPromise={Promise.resolve(auth)}
75
+ * />
76
+ * );
37
77
  * }
38
78
  * ```
39
79
  */
@@ -42,6 +82,13 @@ export declare function NextAdminPage(props: {
42
82
  params: Promise<{
43
83
  path?: string[];
44
84
  }>;
45
- token?: string;
85
+ /**
86
+ * Next.js's own `searchParams` prop, forwarded by the app route (`AdminPage`). Only the
87
+ * collection list route reads it (`parseListViewUrlState`, Design Decision 40) — every other
88
+ * route ignores it.
89
+ */
90
+ searchParams?: Promise<Record<string, string | string[] | undefined>>;
91
+ tokenPromise?: Promise<string | undefined>;
92
+ authPromise?: Promise<VexApiAuth>;
46
93
  }): Promise<import("react").JSX.Element>;
47
94
  //# sourceMappingURL=NextAdminPage.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"NextAdminPage.d.ts","sourceRoot":"","sources":["../src/NextAdminPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAoC,SAAS,EAAoB,MAAM,cAAc,CAAC;AAuDlG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE;IACzC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB,wCAmLA"}
1
+ {"version":3,"file":"NextAdminPage.d.ts","sourceRoot":"","sources":["../src/NextAdminPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,UAAU,EACV,SAAS,EAIV,MAAM,cAAc,CAAC;AA4KtB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6EG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE;IACzC,MAAM,EAAE,SAAS,CAAC;IAClB,MAAM,EAAE,OAAO,CAAC;QAAE,IAAI,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IACrC;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;IACtE,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CACnC,wCAonBA"}
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  NextAdminPage
3
- } from "./chunk-DDH2TJTP.js";
3
+ } from "./chunk-JB3GMZPF.js";
4
4
  export {
5
5
  NextAdminPage
6
6
  };
@@ -0,0 +1,66 @@
1
+ import { type ReactNode } from "react";
2
+ import type { FrameworkAuthAdapter } from "@vexcms/react";
3
+ /**
4
+ * Carries the app's sign-in/out actions (`@vexcms/react`'s `FrameworkComponents.auth`,
5
+ * Next.js adapter) from wherever the app mounts {@link VexAdminAuthAdapterProvider} down
6
+ * to `NextAdminLayoutClient`, which forwards it into `AdminLayout`'s `components` prop.
7
+ *
8
+ * A context, not a prop threaded through `NextAdminLayout`/your admin `layout.tsx`,
9
+ * because `layout.tsx` and `NextAdminLayout` are BOTH server components — the sign-in
10
+ * href builder and `signOut` function are app-specific client values (closures over your
11
+ * own `~/auth/client` module) that cannot cross a Server→Client Component boundary as
12
+ * props. Mounting this provider anywhere above `NextAdminLayout` in your own CLIENT tree
13
+ * (e.g. alongside `VexConfigProvider`/your Better Auth client provider) sidesteps that
14
+ * boundary entirely — both ends are client components.
15
+ *
16
+ * `undefined` default (not an empty object) distinguishes "no provider mounted" from "a
17
+ * provider mounted with no adapter" — `AdminTopNav`'s own control renders nothing either
18
+ * way, but the distinction matters for anyone debugging why it's missing.
19
+ */
20
+ export declare const VexAdminAuthAdapterContext: import("react").Context<FrameworkAuthAdapter | undefined>;
21
+ /**
22
+ * Mounts the sign-in/out adapter for the admin panel's top-bar control.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * // ~/components/providers/client.tsx ("use client")
27
+ * import { VexAdminAuthAdapterProvider } from "@vexcms/next/auth";
28
+ * import { useRouter } from "next/navigation";
29
+ * import { signOut } from "~/auth/client";
30
+ *
31
+ * function AuthAdapterProvider({ children }: { children: React.ReactNode }) {
32
+ * const router = useRouter();
33
+ * return (
34
+ * <VexAdminAuthAdapterProvider
35
+ * value={{
36
+ * signInHref: (redirectTo) => `/auth/sign-in?redirectTo=${encodeURIComponent(redirectTo)}`,
37
+ * signOut: () =>
38
+ * signOut()
39
+ * .then(() => router.refresh())
40
+ * .catch(() => router.refresh()), // a stale client cache after a failed
41
+ * // sign-out is worse than a redundant refresh — refresh either way.
42
+ * }}
43
+ * >
44
+ * {children}
45
+ * </VexAdminAuthAdapterProvider>
46
+ * );
47
+ * }
48
+ * ```
49
+ *
50
+ * @param props.value - The sign-in href builder and `signOut` function.
51
+ * @param props.children - The subtree that should see this adapter — must contain
52
+ * `NextAdminLayout` for `AdminTopNav`'s control to pick it up.
53
+ * @returns The context provider wrapping `props.children`.
54
+ */
55
+ export declare function VexAdminAuthAdapterProvider(props: {
56
+ value: FrameworkAuthAdapter;
57
+ children: ReactNode;
58
+ }): import("react").JSX.Element;
59
+ /**
60
+ * Reads the sign-in/out adapter mounted by {@link VexAdminAuthAdapterProvider}, if any.
61
+ * Consumed internally by `NextAdminLayoutClient` — not typically called from app code.
62
+ *
63
+ * @returns The adapter, or `undefined` when no provider is mounted above this point.
64
+ */
65
+ export declare function useVexAdminAuthAdapter(): FrameworkAuthAdapter | undefined;
66
+ //# sourceMappingURL=VexAdminAuthAdapterContext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"VexAdminAuthAdapterContext.d.ts","sourceRoot":"","sources":["../../src/auth/VexAdminAuthAdapterContext.tsx"],"names":[],"mappings":"AAKA,OAAO,EAA6B,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAClE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAE1D;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,0BAA0B,2DAEtC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAgB,2BAA2B,CAAC,KAAK,EAAE;IACjD,KAAK,EAAE,oBAAoB,CAAC;IAC5B,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAMA;AAED;;;;;GAKG;AACH,wBAAgB,sBAAsB,IAAI,oBAAoB,GAAG,SAAS,CAEzE"}
@@ -0,0 +1,2 @@
1
+ export { VexAdminAuthAdapterContext, VexAdminAuthAdapterProvider, useVexAdminAuthAdapter, } from "./VexAdminAuthAdapterContext";
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,8BAA8B,CAAC"}
@@ -0,0 +1,12 @@
1
+ "use client";
2
+ import {
3
+ VexAdminAuthAdapterContext,
4
+ VexAdminAuthAdapterProvider,
5
+ useVexAdminAuthAdapter
6
+ } from "../chunk-T7DNJLSX.js";
7
+ export {
8
+ VexAdminAuthAdapterContext,
9
+ VexAdminAuthAdapterProvider,
10
+ useVexAdminAuthAdapter
11
+ };
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}