next-supa-utils 0.1.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 ADDED
@@ -0,0 +1,326 @@
1
+ <p align="center">
2
+ <h1 align="center">next-supa-utils</h1>
3
+ <p align="center">
4
+ Eliminate Supabase boilerplate in Next.js App Router.<br/>
5
+ Hooks, middleware helpers, and server action wrappers — all type-safe.
6
+ </p>
7
+ </p>
8
+
9
+ <p align="center">
10
+ <a href="#installation">Installation</a> •
11
+ <a href="#quick-start">Quick Start</a> •
12
+ <a href="#api-reference">API Reference</a> •
13
+ <a href="#contributing">Contributing</a> •
14
+ <a href="#license">License</a>
15
+ </p>
16
+
17
+ ---
18
+
19
+ ## Why?
20
+
21
+ Every Next.js + Supabase project ends up with the same boilerplate:
22
+
23
+ - Creating Supabase clients with cookie handling for middleware, server components, and client components
24
+ - Writing try/catch wrappers around every server action
25
+ - Manually checking auth state in middleware and redirecting
26
+
27
+ **next-supa-utils** extracts these patterns into a single, type-safe library with separate entry points for client and server code — fully compatible with the Next.js App Router architecture.
28
+
29
+ ## Features
30
+
31
+ - 🔐 **`withSupaAuth`** — Drop-in middleware for route protection with redirect support
32
+ - ⚡ **`createAction`** — Higher-order function that wraps server actions with automatic Supabase client creation and error handling
33
+ - 👤 **`useSupaUser`** — React hook for real-time user state with auth change subscriptions
34
+ - 🔑 **`useSupaSession`** — React hook for real-time session state
35
+ - 🧩 **Separate entry points** — `next-supa-utils/client` and `next-supa-utils/server` to respect the `"use client"` boundary
36
+ - 📦 **Dual format** — Ships ESM and CJS with full TypeScript declarations
37
+
38
+ ## Installation
39
+
40
+ ```bash
41
+ npm install next-supa-utils
42
+ ```
43
+
44
+ ### Peer Dependencies
45
+
46
+ Make sure you have the following installed in your project:
47
+
48
+ ```bash
49
+ npm install react next @supabase/supabase-js @supabase/ssr
50
+ ```
51
+
52
+ | Package | Version |
53
+ |---|---|
54
+ | `react` | `>=18` |
55
+ | `next` | `>=14` |
56
+ | `@supabase/supabase-js` | `^2.0.0` |
57
+ | `@supabase/ssr` | `>=0.5.0` |
58
+
59
+ ## Environment Variables
60
+
61
+ Add these to your `.env.local`:
62
+
63
+ ```env
64
+ NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
65
+ NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
66
+ ```
67
+
68
+ Both variables are required. All helpers in this library read from these environment variables automatically.
69
+
70
+ ---
71
+
72
+ ## Quick Start
73
+
74
+ ### 1. Protect Routes with Middleware
75
+
76
+ ```ts
77
+ // middleware.ts
78
+ import { withSupaAuth } from "next-supa-utils/server";
79
+
80
+ export default withSupaAuth({
81
+ protectedRoutes: ["/dashboard", "/admin", "/settings"],
82
+ redirectTo: "/login",
83
+ publicRoutes: ["/admin/login"],
84
+ });
85
+
86
+ export const config = {
87
+ matcher: [
88
+ "/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
89
+ ],
90
+ };
91
+ ```
92
+
93
+ ### 2. Create Type-Safe Server Actions
94
+
95
+ ```ts
96
+ // app/actions/profile.ts
97
+ "use server";
98
+ import { createAction } from "next-supa-utils/server";
99
+
100
+ export const getProfile = createAction(async (supabase, userId: string) => {
101
+ const { data, error } = await supabase
102
+ .from("profiles")
103
+ .select("*")
104
+ .eq("id", userId)
105
+ .single();
106
+
107
+ if (error) throw error;
108
+ return data;
109
+ });
110
+ ```
111
+
112
+ ```tsx
113
+ // Usage in any component
114
+ const result = await getProfile("user-uuid");
115
+
116
+ if (result.error) {
117
+ console.error(result.error.message);
118
+ } else {
119
+ console.log(result.data);
120
+ }
121
+ ```
122
+
123
+ ### 3. Use Auth State in Client Components
124
+
125
+ ```tsx
126
+ "use client";
127
+ import { useSupaUser } from "next-supa-utils/client";
128
+
129
+ export default function Avatar() {
130
+ const { user, loading, error } = useSupaUser();
131
+
132
+ if (loading) return <p>Loading…</p>;
133
+ if (error) return <p>Error: {error.message}</p>;
134
+ if (!user) return <p>Not signed in</p>;
135
+
136
+ return <p>Hello, {user.email}!</p>;
137
+ }
138
+ ```
139
+
140
+ ---
141
+
142
+ ## API Reference
143
+
144
+ ### Server — `next-supa-utils/server`
145
+
146
+ #### `withSupaAuth(config)`
147
+
148
+ Creates a Next.js middleware function that handles session refresh and route protection.
149
+
150
+ **Parameters:**
151
+
152
+ | Property | Type | Required | Default | Description |
153
+ |---|---|---|---|---|
154
+ | `protectedRoutes` | `string[]` | ✅ | — | Route prefixes that require authentication |
155
+ | `redirectTo` | `string` | — | `"/login"` | Where to redirect unauthenticated users |
156
+ | `publicRoutes` | `string[]` | — | `[]` | Routes that are always public, even if matching a protected prefix |
157
+ | `onAuthSuccess` | `(user: { id: string; email?: string }) => void \| Promise<void>` | — | — | Optional callback after successful auth verification |
158
+
159
+ **Returns:** `(request: NextRequest) => Promise<NextResponse>`
160
+
161
+ **Behavior:**
162
+ 1. Creates a Supabase server client with proper cookie forwarding
163
+ 2. Calls `supabase.auth.getUser()` to refresh the session
164
+ 3. If the current path matches `publicRoutes`, allows access immediately
165
+ 4. If the current path matches `protectedRoutes` and the user is not authenticated, redirects to `redirectTo` with a `?next=<original_path>` query parameter
166
+ 5. Calls `onAuthSuccess` if the user is authenticated and the callback is provided
167
+
168
+ ---
169
+
170
+ #### `createAction(fn)`
171
+
172
+ Wraps an async function into a server action with automatic Supabase client initialization and error handling.
173
+
174
+ **Signature:**
175
+
176
+ ```ts
177
+ function createAction<TArgs extends unknown[], TResult>(
178
+ fn: (supabase: SupabaseClient, ...args: TArgs) => Promise<TResult>
179
+ ): (...args: TArgs) => Promise<ActionResponse<TResult>>
180
+ ```
181
+
182
+ **Returns:** `ActionResponse<TResult>` — a discriminated union:
183
+
184
+ ```ts
185
+ // On success:
186
+ { data: TResult; error: null }
187
+
188
+ // On failure:
189
+ { data: null; error: SupaError }
190
+ ```
191
+
192
+ **Behavior:**
193
+ 1. Creates a Supabase server client using `cookies()` from `next/headers`
194
+ 2. Passes the client as the first argument to your function
195
+ 3. Wraps execution in try/catch — any thrown error is normalized into a `SupaError`
196
+
197
+ ---
198
+
199
+ ### Client — `next-supa-utils/client`
200
+
201
+ > ⚠️ All client exports include the `"use client"` directive. They must be used inside Client Components only.
202
+
203
+ #### `useSupaUser()`
204
+
205
+ React hook that provides the current authenticated user and subscribes to auth state changes.
206
+
207
+ **Returns:** `UseSupaUserReturn`
208
+
209
+ | Property | Type | Description |
210
+ |---|---|---|
211
+ | `user` | `User \| null` | The current Supabase user object, or `null` if not authenticated |
212
+ | `loading` | `boolean` | `true` while the initial fetch is in progress |
213
+ | `error` | `SupaError \| null` | Error details if the fetch failed |
214
+
215
+ **Behavior:**
216
+ 1. Creates a browser client via `createBrowserClient` from `@supabase/ssr`
217
+ 2. Calls `supabase.auth.getUser()` on mount
218
+ 3. Subscribes to `onAuthStateChange` for real-time updates (sign in, sign out, token refresh)
219
+ 4. Cleans up the subscription on unmount
220
+
221
+ ---
222
+
223
+ #### `useSupaSession()`
224
+
225
+ React hook that provides the current session (access token, refresh token, expiry) and subscribes to auth state changes.
226
+
227
+ **Returns:** `UseSupaSessionReturn`
228
+
229
+ | Property | Type | Description |
230
+ |---|---|---|
231
+ | `session` | `Session \| null` | The current Supabase session, or `null` if not authenticated |
232
+ | `loading` | `boolean` | `true` while the initial fetch is in progress |
233
+ | `error` | `SupaError \| null` | Error details if the fetch failed |
234
+
235
+ ---
236
+
237
+ ### Shared — `next-supa-utils`
238
+
239
+ #### `handleSupaError(error)`
240
+
241
+ Normalizes any thrown value into a consistent `SupaError` shape. Used internally by `createAction` and the hooks, but also exported for direct use.
242
+
243
+ ```ts
244
+ function handleSupaError(error: unknown): SupaError
245
+ ```
246
+
247
+ **Handles:**
248
+ - Supabase `AuthError` / `PostgrestError` (extracts `message`, `code`, `status`)
249
+ - Standard `Error` instances
250
+ - Plain objects with a `message` property
251
+ - Strings
252
+ - Unknown values (fallback: `"An unknown error occurred"`)
253
+
254
+ ---
255
+
256
+ ### Types
257
+
258
+ ```ts
259
+ interface SupaError {
260
+ message: string;
261
+ code?: string;
262
+ status?: number;
263
+ }
264
+
265
+ type ActionResponse<T> =
266
+ | { data: T; error: null }
267
+ | { data: null; error: SupaError };
268
+
269
+ interface SupaAuthConfig {
270
+ protectedRoutes: string[];
271
+ redirectTo?: string;
272
+ publicRoutes?: string[];
273
+ onAuthSuccess?: (user: { id: string; email?: string }) => void | Promise<void>;
274
+ }
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Project Structure
280
+
281
+ ```
282
+ src/
283
+ ├── client/ # "use client" — browser-only code
284
+ │ ├── hooks/
285
+ │ │ ├── useSupaUser.ts
286
+ │ │ └── useSupaSession.ts
287
+ │ └── index.ts
288
+ ├── server/ # Server-only (Node/Edge runtime)
289
+ │ ├── middleware/
290
+ │ │ └── withSupaAuth.ts
291
+ │ ├── actions/
292
+ │ │ └── actionWrapper.ts
293
+ │ └── index.ts
294
+ ├── shared/ # Isomorphic utilities
295
+ │ ├── utils/
296
+ │ │ └── error-handler.ts
297
+ │ └── index.ts
298
+ └── types/
299
+ └── index.ts
300
+ ```
301
+
302
+ ## Import Paths
303
+
304
+ | Import | Environment | Contains |
305
+ |---|---|---|
306
+ | `next-supa-utils/client` | Client Components | `useSupaUser`, `useSupaSession` |
307
+ | `next-supa-utils/server` | Server Components, Middleware, Server Actions | `withSupaAuth`, `createAction` |
308
+ | `next-supa-utils` | Anywhere | `handleSupaError`, all types |
309
+
310
+ ## Contributing
311
+
312
+ Contributions are welcome! Please feel free to submit a Pull Request.
313
+
314
+ 1. Fork the repository
315
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
316
+ 3. Install dependencies (`npm install`)
317
+ 4. Make your changes
318
+ 5. Run the type checker (`npm run typecheck`)
319
+ 6. Build the project (`npm run build`)
320
+ 7. Commit your changes (`git commit -m 'feat: add amazing feature'`)
321
+ 8. Push to the branch (`git push origin feature/amazing-feature`)
322
+ 9. Open a Pull Request
323
+
324
+ ## License
325
+
326
+ [MIT](LICENSE)
@@ -0,0 +1,154 @@
1
+ "use client";
2
+ "use strict";
3
+ "use client";
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
+
22
+ // src/client/index.ts
23
+ var client_exports = {};
24
+ __export(client_exports, {
25
+ useSupaSession: () => useSupaSession,
26
+ useSupaUser: () => useSupaUser
27
+ });
28
+ module.exports = __toCommonJS(client_exports);
29
+
30
+ // src/client/hooks/useSupaUser.ts
31
+ var import_react = require("react");
32
+ var import_ssr = require("@supabase/ssr");
33
+
34
+ // src/shared/utils/error-handler.ts
35
+ function handleSupaError(error) {
36
+ if (error instanceof Error) {
37
+ const record = error;
38
+ return {
39
+ message: error.message,
40
+ code: typeof record.code === "string" ? record.code : void 0,
41
+ status: typeof record.status === "number" ? record.status : void 0
42
+ };
43
+ }
44
+ if (typeof error === "object" && error !== null && "message" in error && typeof error.message === "string") {
45
+ const err = error;
46
+ return {
47
+ message: err.message,
48
+ code: typeof err.code === "string" ? err.code : void 0,
49
+ status: typeof err.status === "number" ? err.status : void 0
50
+ };
51
+ }
52
+ if (typeof error === "string") {
53
+ return { message: error };
54
+ }
55
+ return { message: "An unknown error occurred" };
56
+ }
57
+
58
+ // src/client/hooks/useSupaUser.ts
59
+ function useSupaUser() {
60
+ const [state, setState] = (0, import_react.useState)({
61
+ user: null,
62
+ loading: true,
63
+ error: null
64
+ });
65
+ (0, import_react.useEffect)(() => {
66
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
67
+ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
68
+ if (!supabaseUrl || !supabaseAnonKey) {
69
+ setState({
70
+ user: null,
71
+ loading: false,
72
+ error: {
73
+ message: "Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.",
74
+ code: "CONFIG_ERROR"
75
+ }
76
+ });
77
+ return;
78
+ }
79
+ const supabase = (0, import_ssr.createBrowserClient)(supabaseUrl, supabaseAnonKey);
80
+ supabase.auth.getUser().then(({ data, error }) => {
81
+ setState({
82
+ user: data.user,
83
+ loading: false,
84
+ error: error ? handleSupaError(error) : null
85
+ });
86
+ });
87
+ const {
88
+ data: { subscription }
89
+ } = supabase.auth.onAuthStateChange((_event, session) => {
90
+ setState((prev) => ({
91
+ ...prev,
92
+ user: session?.user ?? null,
93
+ loading: false
94
+ }));
95
+ });
96
+ return () => {
97
+ subscription.unsubscribe();
98
+ };
99
+ }, []);
100
+ return state;
101
+ }
102
+
103
+ // src/client/hooks/useSupaSession.ts
104
+ var import_react2 = require("react");
105
+ var import_ssr2 = require("@supabase/ssr");
106
+ function useSupaSession() {
107
+ const [state, setState] = (0, import_react2.useState)({
108
+ session: null,
109
+ loading: true,
110
+ error: null
111
+ });
112
+ (0, import_react2.useEffect)(() => {
113
+ const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
114
+ const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
115
+ if (!supabaseUrl || !supabaseAnonKey) {
116
+ setState({
117
+ session: null,
118
+ loading: false,
119
+ error: {
120
+ message: "Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.",
121
+ code: "CONFIG_ERROR"
122
+ }
123
+ });
124
+ return;
125
+ }
126
+ const supabase = (0, import_ssr2.createBrowserClient)(supabaseUrl, supabaseAnonKey);
127
+ supabase.auth.getSession().then(({ data, error }) => {
128
+ setState({
129
+ session: data.session,
130
+ loading: false,
131
+ error: error ? handleSupaError(error) : null
132
+ });
133
+ });
134
+ const {
135
+ data: { subscription }
136
+ } = supabase.auth.onAuthStateChange((_event, session) => {
137
+ setState((prev) => ({
138
+ ...prev,
139
+ session,
140
+ loading: false
141
+ }));
142
+ });
143
+ return () => {
144
+ subscription.unsubscribe();
145
+ };
146
+ }, []);
147
+ return state;
148
+ }
149
+ // Annotate the CommonJS export names for ESM import in node:
150
+ 0 && (module.exports = {
151
+ useSupaSession,
152
+ useSupaUser
153
+ });
154
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/client/index.ts","../../src/client/hooks/useSupaUser.ts","../../src/shared/utils/error-handler.ts","../../src/client/hooks/useSupaSession.ts"],"sourcesContent":["\"use client\";\n\n// ── Client entry point ──────────────────────────────────────────────\n// This module MUST only be imported in Client Components.\n// The \"use client\" directive ensures Next.js treats the entire\n// sub-tree as client-side code.\n\nexport { useSupaUser } from \"./hooks/useSupaUser\";\nexport { useSupaSession } from \"./hooks/useSupaSession\";\n\n// Re-export types consumers commonly need alongside client helpers.\nexport type {\n UseSupaUserReturn,\n UseSupaSessionReturn,\n SupaError,\n} from \"../types\";\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { createBrowserClient } from \"@supabase/ssr\";\n\nimport type { UseSupaUserReturn } from \"../../types\";\nimport { handleSupaError } from \"../../shared/utils/error-handler\";\n\n/**\n * React hook that provides the current Supabase user and\n * subscribes to real-time auth state changes.\n *\n * Must be used inside a Client Component (`\"use client\"`).\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useSupaUser } from \"next-supa-utils/client\";\n *\n * export default function Avatar() {\n * const { user, loading, error } = useSupaUser();\n *\n * if (loading) return <p>Loading…</p>;\n * if (error) return <p>Error: {error.message}</p>;\n * if (!user) return <p>Not signed in</p>;\n *\n * return <p>Hello, {user.email}</p>;\n * }\n * ```\n */\nexport function useSupaUser(): UseSupaUserReturn {\n const [state, setState] = useState<UseSupaUserReturn>({\n user: null,\n loading: true,\n error: null,\n });\n\n useEffect(() => {\n const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseAnonKey) {\n setState({\n user: null,\n loading: false,\n error: {\n message:\n \"Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.\",\n code: \"CONFIG_ERROR\",\n },\n });\n return;\n }\n\n const supabase = createBrowserClient(supabaseUrl, supabaseAnonKey);\n\n // ── Initial fetch ─────────────────────────────────────────────\n supabase.auth.getUser().then(({ data, error }) => {\n setState({\n user: data.user,\n loading: false,\n error: error ? handleSupaError(error) : null,\n });\n });\n\n // ── Subscribe to auth state changes ───────────────────────────\n const {\n data: { subscription },\n } = supabase.auth.onAuthStateChange((_event, session) => {\n setState((prev) => ({\n ...prev,\n user: session?.user ?? null,\n loading: false,\n }));\n });\n\n return () => {\n subscription.unsubscribe();\n };\n }, []);\n\n return state;\n}\n","import type { SupaError } from \"../../types\";\n\n/**\n * Normalize any thrown value into a consistent `SupaError` shape.\n *\n * Handles:\n * - Supabase `AuthError` / `PostgrestError` (has `.message` and optional `.code` / `.status`)\n * - Standard `Error` instances\n * - Plain strings\n * - Unknown values (fallback)\n */\nexport function handleSupaError(error: unknown): SupaError {\n // ── Supabase errors & standard Error instances ──────────────────\n if (error instanceof Error) {\n const record = error as unknown as Record<string, unknown>;\n return {\n message: error.message,\n code: typeof record.code === \"string\" ? record.code : undefined,\n status: typeof record.status === \"number\" ? record.status : undefined,\n };\n }\n\n // ── Plain object with a message property ────────────────────────\n if (\n typeof error === \"object\" &&\n error !== null &&\n \"message\" in error &&\n typeof (error as Record<string, unknown>).message === \"string\"\n ) {\n const err = error as Record<string, unknown>;\n return {\n message: err.message as string,\n code: typeof err.code === \"string\" ? err.code : undefined,\n status: typeof err.status === \"number\" ? err.status : undefined,\n };\n }\n\n // ── String ──────────────────────────────────────────────────────\n if (typeof error === \"string\") {\n return { message: error };\n }\n\n // ── Fallback ────────────────────────────────────────────────────\n return { message: \"An unknown error occurred\" };\n}\n","\"use client\";\n\nimport { useEffect, useState } from \"react\";\nimport { createBrowserClient } from \"@supabase/ssr\";\nimport type { Session } from \"@supabase/supabase-js\";\n\nimport type { UseSupaSessionReturn } from \"../../types\";\nimport { handleSupaError } from \"../../shared/utils/error-handler\";\n\n/**\n * React hook that provides the current Supabase session and\n * subscribes to real-time auth state changes.\n *\n * Must be used inside a Client Component (`\"use client\"`).\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useSupaSession } from \"next-supa-utils/client\";\n *\n * export default function TokenDisplay() {\n * const { session, loading, error } = useSupaSession();\n *\n * if (loading) return <p>Loading…</p>;\n * if (error) return <p>Error: {error.message}</p>;\n * if (!session) return <p>No active session</p>;\n *\n * return <p>Token expires at: {session.expires_at}</p>;\n * }\n * ```\n */\nexport function useSupaSession(): UseSupaSessionReturn {\n const [state, setState] = useState<UseSupaSessionReturn>({\n session: null,\n loading: true,\n error: null,\n });\n\n useEffect(() => {\n const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;\n const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!supabaseUrl || !supabaseAnonKey) {\n setState({\n session: null,\n loading: false,\n error: {\n message:\n \"Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.\",\n code: \"CONFIG_ERROR\",\n },\n });\n return;\n }\n\n const supabase = createBrowserClient(supabaseUrl, supabaseAnonKey);\n\n // ── Initial fetch ─────────────────────────────────────────────\n supabase.auth.getSession().then(({ data, error }) => {\n setState({\n session: data.session,\n loading: false,\n error: error ? handleSupaError(error) : null,\n });\n });\n\n // ── Subscribe to auth state changes ───────────────────────────\n const {\n data: { subscription },\n } = supabase.auth.onAuthStateChange((_event, session: Session | null) => {\n setState((prev) => ({\n ...prev,\n session,\n loading: false,\n }));\n });\n\n return () => {\n subscription.unsubscribe();\n };\n }, []);\n\n return state;\n}\n"],"mappings":"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","names":["import_react","import_ssr"]}
@@ -0,0 +1,68 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+
3
+ /** Standardized error shape returned by all next-supa-utils helpers. */
4
+ interface SupaError {
5
+ message: string;
6
+ code?: string;
7
+ status?: number;
8
+ }
9
+ interface UseSupaUserReturn {
10
+ user: Awaited<ReturnType<SupabaseClient["auth"]["getUser"]>>["data"]["user"];
11
+ loading: boolean;
12
+ error: SupaError | null;
13
+ }
14
+ interface UseSupaSessionReturn {
15
+ session: Awaited<ReturnType<SupabaseClient["auth"]["getSession"]>>["data"]["session"];
16
+ loading: boolean;
17
+ error: SupaError | null;
18
+ }
19
+
20
+ /**
21
+ * React hook that provides the current Supabase user and
22
+ * subscribes to real-time auth state changes.
23
+ *
24
+ * Must be used inside a Client Component (`"use client"`).
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * "use client";
29
+ * import { useSupaUser } from "next-supa-utils/client";
30
+ *
31
+ * export default function Avatar() {
32
+ * const { user, loading, error } = useSupaUser();
33
+ *
34
+ * if (loading) return <p>Loading…</p>;
35
+ * if (error) return <p>Error: {error.message}</p>;
36
+ * if (!user) return <p>Not signed in</p>;
37
+ *
38
+ * return <p>Hello, {user.email}</p>;
39
+ * }
40
+ * ```
41
+ */
42
+ declare function useSupaUser(): UseSupaUserReturn;
43
+
44
+ /**
45
+ * React hook that provides the current Supabase session and
46
+ * subscribes to real-time auth state changes.
47
+ *
48
+ * Must be used inside a Client Component (`"use client"`).
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * "use client";
53
+ * import { useSupaSession } from "next-supa-utils/client";
54
+ *
55
+ * export default function TokenDisplay() {
56
+ * const { session, loading, error } = useSupaSession();
57
+ *
58
+ * if (loading) return <p>Loading…</p>;
59
+ * if (error) return <p>Error: {error.message}</p>;
60
+ * if (!session) return <p>No active session</p>;
61
+ *
62
+ * return <p>Token expires at: {session.expires_at}</p>;
63
+ * }
64
+ * ```
65
+ */
66
+ declare function useSupaSession(): UseSupaSessionReturn;
67
+
68
+ export { type SupaError, type UseSupaSessionReturn, type UseSupaUserReturn, useSupaSession, useSupaUser };
@@ -0,0 +1,68 @@
1
+ import { SupabaseClient } from '@supabase/supabase-js';
2
+
3
+ /** Standardized error shape returned by all next-supa-utils helpers. */
4
+ interface SupaError {
5
+ message: string;
6
+ code?: string;
7
+ status?: number;
8
+ }
9
+ interface UseSupaUserReturn {
10
+ user: Awaited<ReturnType<SupabaseClient["auth"]["getUser"]>>["data"]["user"];
11
+ loading: boolean;
12
+ error: SupaError | null;
13
+ }
14
+ interface UseSupaSessionReturn {
15
+ session: Awaited<ReturnType<SupabaseClient["auth"]["getSession"]>>["data"]["session"];
16
+ loading: boolean;
17
+ error: SupaError | null;
18
+ }
19
+
20
+ /**
21
+ * React hook that provides the current Supabase user and
22
+ * subscribes to real-time auth state changes.
23
+ *
24
+ * Must be used inside a Client Component (`"use client"`).
25
+ *
26
+ * @example
27
+ * ```tsx
28
+ * "use client";
29
+ * import { useSupaUser } from "next-supa-utils/client";
30
+ *
31
+ * export default function Avatar() {
32
+ * const { user, loading, error } = useSupaUser();
33
+ *
34
+ * if (loading) return <p>Loading…</p>;
35
+ * if (error) return <p>Error: {error.message}</p>;
36
+ * if (!user) return <p>Not signed in</p>;
37
+ *
38
+ * return <p>Hello, {user.email}</p>;
39
+ * }
40
+ * ```
41
+ */
42
+ declare function useSupaUser(): UseSupaUserReturn;
43
+
44
+ /**
45
+ * React hook that provides the current Supabase session and
46
+ * subscribes to real-time auth state changes.
47
+ *
48
+ * Must be used inside a Client Component (`"use client"`).
49
+ *
50
+ * @example
51
+ * ```tsx
52
+ * "use client";
53
+ * import { useSupaSession } from "next-supa-utils/client";
54
+ *
55
+ * export default function TokenDisplay() {
56
+ * const { session, loading, error } = useSupaSession();
57
+ *
58
+ * if (loading) return <p>Loading…</p>;
59
+ * if (error) return <p>Error: {error.message}</p>;
60
+ * if (!session) return <p>No active session</p>;
61
+ *
62
+ * return <p>Token expires at: {session.expires_at}</p>;
63
+ * }
64
+ * ```
65
+ */
66
+ declare function useSupaSession(): UseSupaSessionReturn;
67
+
68
+ export { type SupaError, type UseSupaSessionReturn, type UseSupaUserReturn, useSupaSession, useSupaUser };