next-supa-utils 0.1.5 → 0.1.7

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 aryaintarann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to doকাজso, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -33,7 +33,7 @@ npm install next-supa-utils
33
33
 
34
34
  *Requires `react >=18`, `next >=14`, `@supabase/supabase-js ^2`, and `@supabase/ssr >=0.5`.*
35
35
 
36
- ### Environment Variables
36
+ ### Environment Variables (Default)
37
37
 
38
38
  Add these to your `.env.local`:
39
39
 
@@ -42,6 +42,33 @@ NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
42
42
  NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
43
43
  ```
44
44
 
45
+ ### Explicit Configuration (Optional)
46
+
47
+ If you are using a self-hosted Supabase instance or need to pass credentials dynamically, you can skip the environment variables and pass them explicitly:
48
+
49
+ **Client Setup:**
50
+ Wrap your application in `<SupaProvider>` to inject credentials into all client hooks.
51
+
52
+ ```tsx
53
+ // app/layout.tsx
54
+ import { SupaProvider } from "next-supa-utils/client";
55
+
56
+ export default function RootLayout({ children }) {
57
+ return (
58
+ <html>
59
+ <body>
60
+ <SupaProvider supabaseUrl="https://custom..." supabaseAnonKey="ey...">
61
+ {children}
62
+ </SupaProvider>
63
+ </body>
64
+ </html>
65
+ );
66
+ }
67
+ ```
68
+
69
+ **Server Setup:**
70
+ All server-side helpers accept `supabaseUrl` and `supabaseAnonKey` in their options.
71
+
45
72
  ---
46
73
 
47
74
  ## ⚡ Quick Start
@@ -144,6 +171,8 @@ Creates a Next.js middleware function that handles session refresh and route pro
144
171
  | `redirectTo` | `string` | — | `"/login"` | Where to redirect unauthenticated users |
145
172
  | `publicRoutes` | `string[]` | — | `[]` | Routes that are always public, even if matching a protected prefix |
146
173
  | `onAuthSuccess` | `(user: { id: string; email?: string }) => void \| Promise<void>` | — | — | Optional callback after successful auth verification |
174
+ | `supabaseUrl` | `string` | — | `process.env` | Explicit Supabase URL (overrides env vars) |
175
+ | `supabaseAnonKey` | `string` | — | `process.env` | Explicit Supabase Anon Key (overrides env vars) |
147
176
 
148
177
  **Returns:** `(request: NextRequest) => Promise<NextResponse>`
149
178
 
@@ -200,6 +229,8 @@ function routeWrapper<TContext>(
200
229
 
201
230
  **Options (`RouteWrapperOptions`):**
202
231
  - `requireAuth` (`boolean`): If `true`, returns a `401 Unauthorized` response if the user has no valid session.
232
+ - `supabaseUrl` (`string`): Explicit Supabase URL.
233
+ - `supabaseAnonKey` (`string`): Explicit Supabase Anon Key.
203
234
 
204
235
  **Context (`RouteHandlerContext`):**
205
236
  - `params`: Auto-resolved dynamic route params (e.g., `{ id: "123" }`).
@@ -212,6 +243,18 @@ function routeWrapper<TContext>(
212
243
 
213
244
  > ⚠️ All client exports include the `"use client"` directive. They must be used inside Client Components only.
214
245
 
246
+ #### `<SupaProvider>`
247
+
248
+ A React Context Provider to explicitly inject your Supabase URL and Anon Key into the React tree. It is **optional** if you are using the standard `NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY` environment variables.
249
+
250
+ ```tsx
251
+ <SupaProvider supabaseUrl="https://..." supabaseAnonKey="ey...">
252
+ {children}
253
+ </SupaProvider>
254
+ ```
255
+
256
+ ---
257
+
215
258
  #### `useSupaUser()`
216
259
 
217
260
  React hook that provides the current authenticated user and subscribes to auth state changes.
@@ -360,6 +403,12 @@ src/
360
403
 
361
404
  ## Changelog
362
405
 
406
+ ### v0.1.6
407
+
408
+ - **⚙️ Explicit Configuration Support** — Added support for custom/self-hosted Supabase instances. You are no longer strictly required to use `process.env`.
409
+ - Added `<SupaProvider>` for client components.
410
+ - Added `supabaseUrl` and `supabaseAnonKey` options to `withSupaAuth`, `routeWrapper`, and `createAction`.
411
+
363
412
  ### v0.1.5
364
413
 
365
414
  - **🚀 New Features:**
@@ -22,6 +22,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
22
22
  // src/client/index.ts
23
23
  var client_exports = {};
24
24
  __export(client_exports, {
25
+ SupaProvider: () => SupaProvider,
26
+ useSupaConfig: () => useSupaConfig,
25
27
  useSupaRealtime: () => useSupaRealtime,
26
28
  useSupaSession: () => useSupaSession,
27
29
  useSupaUpload: () => useSupaUpload,
@@ -29,8 +31,31 @@ __export(client_exports, {
29
31
  });
30
32
  module.exports = __toCommonJS(client_exports);
31
33
 
32
- // src/client/hooks/useSupaUser.ts
34
+ // src/client/SupaProvider.tsx
33
35
  var import_react = require("react");
36
+ var import_jsx_runtime = require("react/jsx-runtime");
37
+ var SupaContext = (0, import_react.createContext)(void 0);
38
+ function SupaProvider({
39
+ children,
40
+ supabaseUrl,
41
+ supabaseAnonKey
42
+ }) {
43
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(SupaContext.Provider, { value: { supabaseUrl, supabaseAnonKey }, children });
44
+ }
45
+ function useSupaConfig() {
46
+ const context = (0, import_react.useContext)(SupaContext);
47
+ const url = context?.supabaseUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL;
48
+ const key = context?.supabaseAnonKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
49
+ if (!url || !key) {
50
+ throw new Error(
51
+ "[next-supa-utils] Missing Supabase configuration. Provide NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables, or wrap your tree in <SupaProvider supabaseUrl={...} supabaseAnonKey={...}>."
52
+ );
53
+ }
54
+ return { url, key };
55
+ }
56
+
57
+ // src/client/hooks/useSupaUser.ts
58
+ var import_react2 = require("react");
34
59
  var import_ssr = require("@supabase/ssr");
35
60
 
36
61
  // src/shared/utils/error-handler.ts
@@ -59,25 +84,13 @@ function handleSupaError(error) {
59
84
 
60
85
  // src/client/hooks/useSupaUser.ts
61
86
  function useSupaUser() {
62
- const [state, setState] = (0, import_react.useState)({
87
+ const [state, setState] = (0, import_react2.useState)({
63
88
  user: null,
64
89
  loading: true,
65
90
  error: null
66
91
  });
67
- (0, import_react.useEffect)(() => {
68
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
69
- const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
70
- if (!supabaseUrl || !supabaseAnonKey) {
71
- setState({
72
- user: null,
73
- loading: false,
74
- error: {
75
- message: "Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.",
76
- code: "CONFIG_ERROR"
77
- }
78
- });
79
- return;
80
- }
92
+ const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();
93
+ (0, import_react2.useEffect)(() => {
81
94
  const supabase = (0, import_ssr.createBrowserClient)(supabaseUrl, supabaseAnonKey);
82
95
  supabase.auth.getUser().then(({ data, error }) => {
83
96
  setState({
@@ -103,28 +116,16 @@ function useSupaUser() {
103
116
  }
104
117
 
105
118
  // src/client/hooks/useSupaSession.ts
106
- var import_react2 = require("react");
119
+ var import_react3 = require("react");
107
120
  var import_ssr2 = require("@supabase/ssr");
108
121
  function useSupaSession() {
109
- const [state, setState] = (0, import_react2.useState)({
122
+ const [state, setState] = (0, import_react3.useState)({
110
123
  session: null,
111
124
  loading: true,
112
125
  error: null
113
126
  });
114
- (0, import_react2.useEffect)(() => {
115
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
116
- const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
117
- if (!supabaseUrl || !supabaseAnonKey) {
118
- setState({
119
- session: null,
120
- loading: false,
121
- error: {
122
- message: "Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.",
123
- code: "CONFIG_ERROR"
124
- }
125
- });
126
- return;
127
- }
127
+ const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();
128
+ (0, import_react3.useEffect)(() => {
128
129
  const supabase = (0, import_ssr2.createBrowserClient)(supabaseUrl, supabaseAnonKey);
129
130
  supabase.auth.getSession().then(({ data, error }) => {
130
131
  setState({
@@ -150,28 +151,22 @@ function useSupaSession() {
150
151
  }
151
152
 
152
153
  // src/client/hooks/useSupaUpload.ts
153
- var import_react3 = require("react");
154
+ var import_react4 = require("react");
154
155
  var import_ssr3 = require("@supabase/ssr");
155
156
  function useSupaUpload(bucketName) {
156
- const [isUploading, setIsUploading] = (0, import_react3.useState)(false);
157
- const [progress, setProgress] = (0, import_react3.useState)(0);
158
- const [data, setData] = (0, import_react3.useState)(null);
159
- const [error, setError] = (0, import_react3.useState)(null);
160
- const xhrRef = (0, import_react3.useRef)(null);
161
- const supabaseRef = (0, import_react3.useRef)(null);
157
+ const [isUploading, setIsUploading] = (0, import_react4.useState)(false);
158
+ const [progress, setProgress] = (0, import_react4.useState)(0);
159
+ const [data, setData] = (0, import_react4.useState)(null);
160
+ const [error, setError] = (0, import_react4.useState)(null);
161
+ const xhrRef = (0, import_react4.useRef)(null);
162
+ const supabaseRef = (0, import_react4.useRef)(null);
163
+ const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();
162
164
  function getClient() {
163
165
  if (supabaseRef.current) return supabaseRef.current;
164
- const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
165
- const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
166
- if (!url || !key) {
167
- throw new Error(
168
- "Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables."
169
- );
170
- }
171
- supabaseRef.current = (0, import_ssr3.createBrowserClient)(url, key);
166
+ supabaseRef.current = (0, import_ssr3.createBrowserClient)(supabaseUrl, supabaseAnonKey);
172
167
  return supabaseRef.current;
173
168
  }
174
- const upload = (0, import_react3.useCallback)(
169
+ const upload = (0, import_react4.useCallback)(
175
170
  async (file, options) => {
176
171
  setIsUploading(true);
177
172
  setProgress(0);
@@ -179,8 +174,6 @@ function useSupaUpload(bucketName) {
179
174
  setError(null);
180
175
  try {
181
176
  const supabase = getClient();
182
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
183
- const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
184
177
  const {
185
178
  data: { session }
186
179
  } = await supabase.auth.getSession();
@@ -257,13 +250,13 @@ function useSupaUpload(bucketName) {
257
250
  },
258
251
  [bucketName]
259
252
  );
260
- const cancel = (0, import_react3.useCallback)(() => {
253
+ const cancel = (0, import_react4.useCallback)(() => {
261
254
  if (xhrRef.current) {
262
255
  xhrRef.current.abort();
263
256
  xhrRef.current = null;
264
257
  }
265
258
  }, []);
266
- const reset = (0, import_react3.useCallback)(() => {
259
+ const reset = (0, import_react4.useCallback)(() => {
267
260
  cancel();
268
261
  setIsUploading(false);
269
262
  setProgress(0);
@@ -274,21 +267,14 @@ function useSupaUpload(bucketName) {
274
267
  }
275
268
 
276
269
  // src/client/hooks/useSupaRealtime.ts
277
- var import_react4 = require("react");
270
+ var import_react5 = require("react");
278
271
  var import_ssr4 = require("@supabase/ssr");
279
272
  function useSupaRealtime(table, event, callback, schema = "public") {
280
- const callbackRef = (0, import_react4.useRef)(callback);
273
+ const callbackRef = (0, import_react5.useRef)(callback);
281
274
  callbackRef.current = callback;
282
- const channelRef = (0, import_react4.useRef)(null);
283
- (0, import_react4.useEffect)(() => {
284
- const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
285
- const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
286
- if (!supabaseUrl || !supabaseAnonKey) {
287
- console.error(
288
- "[next-supa-utils] Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables."
289
- );
290
- return;
291
- }
275
+ const channelRef = (0, import_react5.useRef)(null);
276
+ const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();
277
+ (0, import_react5.useEffect)(() => {
292
278
  const supabase = (0, import_ssr4.createBrowserClient)(supabaseUrl, supabaseAnonKey);
293
279
  const channelName = `realtime:${schema}:${table}:${event}:${Date.now()}`;
294
280
  const channel = supabase.channel(channelName).on(
@@ -313,6 +299,8 @@ function useSupaRealtime(table, event, callback, schema = "public") {
313
299
  }
314
300
  // Annotate the CommonJS export names for ESM import in node:
315
301
  0 && (module.exports = {
302
+ SupaProvider,
303
+ useSupaConfig,
316
304
  useSupaRealtime,
317
305
  useSupaSession,
318
306
  useSupaUpload,
@@ -1 +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","../../src/client/hooks/useSupaUpload.ts","../../src/client/hooks/useSupaRealtime.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\";\nexport { useSupaUpload } from \"./hooks/useSupaUpload\";\nexport { useSupaRealtime } from \"./hooks/useSupaRealtime\";\n\n// Re-export types consumers commonly need alongside client helpers.\nexport type {\n UseSupaUserReturn,\n UseSupaSessionReturn,\n UseSupaUploadReturn,\n UploadOptions,\n RealtimeEvent,\n RealtimePayload,\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","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport { createBrowserClient } from \"@supabase/ssr\";\n\nimport type { UseSupaUploadReturn, UploadOptions } from \"../../types\";\nimport { handleSupaError } from \"../../shared/utils/error-handler\";\n\n/**\n * React hook that simplifies uploading files to Supabase Storage\n * with **real-time progress tracking**.\n *\n * Uses `XMLHttpRequest` against the Supabase Storage REST API so that\n * `progress` updates smoothly from 0 → 100 as bytes are sent, without\n * requiring `tus-js-client` or any extra dependencies.\n *\n * @param bucketName - The name of the Supabase Storage bucket to upload to.\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useSupaUpload } from \"next-supa-utils/client\";\n *\n * export default function AvatarUploader() {\n * const { upload, isUploading, progress, data, error } = useSupaUpload(\"avatars\");\n *\n * const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {\n * const file = e.target.files?.[0];\n * if (!file) return;\n * await upload(file, { path: `users/${file.name}`, upsert: true });\n * };\n *\n * return (\n * <div>\n * <input type=\"file\" onChange={handleChange} disabled={isUploading} />\n * {isUploading && <progress value={progress} max={100} />}\n * {isUploading && <p>{progress}%</p>}\n * {error && <p>Error: {error.message}</p>}\n * {data && <p>Uploaded to: {data.path}</p>}\n * </div>\n * );\n * }\n * ```\n */\nexport function useSupaUpload(bucketName: string): UseSupaUploadReturn {\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [data, setData] = useState<UseSupaUploadReturn[\"data\"]>(null);\n const [error, setError] = useState<UseSupaUploadReturn[\"error\"]>(null);\n\n // Abort controller reference so we can cancel in-flight uploads.\n const xhrRef = useRef<XMLHttpRequest | null>(null);\n\n // Stable reference to the Supabase client (used only to get the session token).\n const supabaseRef = useRef<ReturnType<typeof createBrowserClient> | null>(null);\n\n function getClient() {\n if (supabaseRef.current) return supabaseRef.current;\n\n const url = process.env.NEXT_PUBLIC_SUPABASE_URL;\n const key = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!url || !key) {\n throw new Error(\n \"Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.\",\n );\n }\n\n supabaseRef.current = createBrowserClient(url, key);\n return supabaseRef.current;\n }\n\n const upload = useCallback(\n async (file: File, options?: UploadOptions) => {\n // ── Reset state ──────────────────────────────────────────────\n setIsUploading(true);\n setProgress(0);\n setData(null);\n setError(null);\n\n try {\n const supabase = getClient();\n const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;\n const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!;\n\n // Retrieve the current session token for authorization.\n const {\n data: { session },\n } = await supabase.auth.getSession();\n\n const accessToken = session?.access_token ?? supabaseAnonKey;\n const filePath = options?.path ?? file.name;\n\n // ── Build the Storage REST API URL ─────────────────────────\n // POST /storage/v1/object/:bucket/:path\n const uploadUrl = `${supabaseUrl}/storage/v1/object/${bucketName}/${filePath}`;\n\n // ── Upload via XHR for real-time progress ──────────────────\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhrRef.current = xhr;\n\n // ── Progress handler ───────────────────────────────────\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable) {\n const pct = Math.round((e.loaded / e.total) * 100);\n setProgress(pct);\n }\n });\n\n // ── Success handler ────────────────────────────────────\n xhr.addEventListener(\"load\", () => {\n xhrRef.current = null;\n\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n const response = JSON.parse(xhr.responseText) as {\n Key?: string;\n Id?: string;\n };\n // Supabase returns { Key: \"bucket/path\" }\n const fullPath = response.Key ?? `${bucketName}/${filePath}`;\n setData({ path: filePath, fullPath });\n setProgress(100);\n resolve();\n } catch {\n // Response parsed fine even if body structure differs\n setData({ path: filePath, fullPath: `${bucketName}/${filePath}` });\n setProgress(100);\n resolve();\n }\n } else {\n // Server returned an error status\n try {\n const errBody = JSON.parse(xhr.responseText) as {\n statusCode?: string;\n error?: string;\n message?: string;\n };\n reject(\n new Error(\n errBody.message ?? errBody.error ?? `Upload failed with status ${xhr.status}`,\n ),\n );\n } catch {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n }\n });\n\n // ── Error handler ──────────────────────────────────────\n xhr.addEventListener(\"error\", () => {\n xhrRef.current = null;\n reject(new Error(\"Network error during upload\"));\n });\n\n // ── Abort handler ──────────────────────────────────────\n xhr.addEventListener(\"abort\", () => {\n xhrRef.current = null;\n reject(new Error(\"Upload cancelled\"));\n });\n\n // ── Send the request ───────────────────────────────────\n xhr.open(\"POST\", uploadUrl, true);\n xhr.setRequestHeader(\"Authorization\", `Bearer ${accessToken}`);\n xhr.setRequestHeader(\"apikey\", supabaseAnonKey);\n xhr.setRequestHeader(\n \"Content-Type\",\n options?.contentType ?? (file.type || \"application/octet-stream\"),\n );\n xhr.setRequestHeader(\n \"cache-control\",\n options?.cacheControl ?? \"3600\",\n );\n xhr.setRequestHeader(\n \"x-upsert\",\n String(options?.upsert ?? false),\n );\n\n xhr.send(file);\n });\n } catch (caught: unknown) {\n setError(handleSupaError(caught));\n setProgress(0);\n } finally {\n setIsUploading(false);\n }\n },\n [bucketName],\n );\n\n const cancel = useCallback(() => {\n if (xhrRef.current) {\n xhrRef.current.abort();\n xhrRef.current = null;\n }\n }, []);\n\n const reset = useCallback(() => {\n cancel();\n setIsUploading(false);\n setProgress(0);\n setData(null);\n setError(null);\n }, [cancel]);\n\n return { upload, isUploading, progress, data, error, reset, cancel };\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { createBrowserClient } from \"@supabase/ssr\";\nimport type { RealtimeChannel } from \"@supabase/supabase-js\";\n\nimport type { RealtimeEvent, RealtimePayload } from \"../../types\";\n\n/**\n * React hook that subscribes to Supabase Realtime postgres_changes\n * events and **safely cleans up** on unmount to prevent memory leaks.\n *\n * @param table - The database table to listen to.\n * @param event - The event type: `\"INSERT\"`, `\"UPDATE\"`, `\"DELETE\"`, or `\"*\"` for all.\n * @param callback - Function called with the realtime payload on each event.\n * @param schema - The database schema (defaults to `\"public\"`).\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useSupaRealtime } from \"next-supa-utils/client\";\n *\n * export default function LiveMessages() {\n * const [messages, setMessages] = useState<Message[]>([]);\n *\n * useSupaRealtime(\"messages\", \"INSERT\", (payload) => {\n * setMessages((prev) => [...prev, payload.new as Message]);\n * });\n *\n * return <ul>{messages.map((m) => <li key={m.id}>{m.text}</li>)}</ul>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Listen to all events with a custom schema\n * useSupaRealtime(\"orders\", \"*\", (payload) => {\n * console.log(payload.eventType, payload.new, payload.old);\n * }, \"inventory\");\n * ```\n */\nexport function useSupaRealtime<T extends Record<string, unknown> = Record<string, unknown>>(\n table: string,\n event: RealtimeEvent,\n callback: (payload: RealtimePayload<T>) => void,\n schema: string = \"public\",\n): void {\n // Store callback in a ref so the channel doesn't need to re-subscribe\n // when only the callback identity changes (common with inline arrows).\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n // Store the channel so we can clean it up.\n const channelRef = useRef<RealtimeChannel | null>(null);\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 console.error(\n \"[next-supa-utils] Missing NEXT_PUBLIC_SUPABASE_URL or NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables.\",\n );\n return;\n }\n\n const supabase = createBrowserClient(supabaseUrl, supabaseAnonKey);\n\n // Generate a unique channel name to avoid collisions.\n const channelName = `realtime:${schema}:${table}:${event}:${Date.now()}`;\n\n const channel = supabase\n .channel(channelName)\n .on(\n \"postgres_changes\" as \"postgres_changes\",\n {\n event: event === \"*\" ? \"*\" : event,\n schema,\n table,\n },\n (payload) => {\n callbackRef.current(payload as unknown as RealtimePayload<T>);\n },\n )\n .subscribe();\n\n channelRef.current = channel;\n\n // ── Cleanup: remove the channel on unmount or dep change ──────\n return () => {\n if (channelRef.current) {\n supabase.removeChannel(channelRef.current);\n channelRef.current = null;\n }\n };\n }, [table, event, schema]); // Re-subscribe when table/event/schema changes\n}\n"],"mappings":"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","names":["import_react","import_ssr","import_react","import_ssr","import_react","import_ssr"]}
1
+ {"version":3,"sources":["../../src/client/index.ts","../../src/client/SupaProvider.tsx","../../src/client/hooks/useSupaUser.ts","../../src/shared/utils/error-handler.ts","../../src/client/hooks/useSupaSession.ts","../../src/client/hooks/useSupaUpload.ts","../../src/client/hooks/useSupaRealtime.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 { SupaProvider, useSupaConfig } from \"./SupaProvider\";\nexport { useSupaUser } from \"./hooks/useSupaUser\";\nexport { useSupaSession } from \"./hooks/useSupaSession\";\nexport { useSupaUpload } from \"./hooks/useSupaUpload\";\nexport { useSupaRealtime } from \"./hooks/useSupaRealtime\";\n\n// Re-export types consumers commonly need alongside client helpers.\nexport type {\n UseSupaUserReturn,\n UseSupaSessionReturn,\n UseSupaUploadReturn,\n UploadOptions,\n RealtimeEvent,\n RealtimePayload,\n SupaError,\n} from \"../types\";\n","\"use client\";\n\nimport React, { createContext, useContext, ReactNode } from \"react\";\n\nexport interface SupaContextValue {\n /**\n * Explicit Supabase Project URL.\n * If omitted, the context falls back to `process.env.NEXT_PUBLIC_SUPABASE_URL`.\n */\n supabaseUrl?: string;\n\n /**\n * Explicit Supabase Anon Key.\n * If omitted, the context falls back to `process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY`.\n */\n supabaseAnonKey?: string;\n}\n\nconst SupaContext = createContext<SupaContextValue | undefined>(undefined);\n\n/**\n * `<SupaProvider>` allows you to inject explicit Supabase credentials\n * (URL and Anon Key) into all `next-supa-utils/client` hooks.\n *\n * It is completely **optional** if you are using the standard environment\n * variables (`NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`).\n *\n * @example\n * ```tsx\n * // app/layout.tsx\n * import { SupaProvider } from \"next-supa-utils/client\";\n *\n * export default function RootLayout({ children }) {\n * return (\n * <html>\n * <body>\n * <SupaProvider supabaseUrl=\"https://...\" supabaseAnonKey=\"ey...\">\n * {children}\n * </SupaProvider>\n * </body>\n * </html>\n * );\n * }\n * ```\n */\nexport function SupaProvider({\n children,\n supabaseUrl,\n supabaseAnonKey,\n}: { children: ReactNode } & SupaContextValue) {\n return (\n <SupaContext.Provider value={{ supabaseUrl, supabaseAnonKey }}>\n {children}\n </SupaContext.Provider>\n );\n}\n\n/**\n * Internal hook to retrieve the Supabase configuration from Context or Environment Variables.\n */\nexport function useSupaConfig() {\n const context = useContext(SupaContext);\n\n const url = context?.supabaseUrl ?? process.env.NEXT_PUBLIC_SUPABASE_URL;\n const key = context?.supabaseAnonKey ?? process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;\n\n if (!url || !key) {\n throw new Error(\n \"[next-supa-utils] Missing Supabase configuration. Provide NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY environment variables, or wrap your tree in <SupaProvider supabaseUrl={...} supabaseAnonKey={...}>.\",\n );\n }\n\n return { url, key };\n}\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\";\nimport { useSupaConfig } from \"../SupaProvider\";\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 // Get config from Context or Environment\n const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();\n\n useEffect(() => {\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\";\nimport { useSupaConfig } from \"../SupaProvider\";\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 // Get config from Context or Environment\n const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();\n\n useEffect(() => {\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","\"use client\";\n\nimport { useCallback, useRef, useState } from \"react\";\nimport { createBrowserClient } from \"@supabase/ssr\";\n\nimport type { UseSupaUploadReturn, UploadOptions } from \"../../types\";\nimport { handleSupaError } from \"../../shared/utils/error-handler\";\nimport { useSupaConfig } from \"../SupaProvider\";\n\n/**\n * React hook that simplifies uploading files to Supabase Storage\n * with **real-time progress tracking**.\n *\n * Uses `XMLHttpRequest` against the Supabase Storage REST API so that\n * `progress` updates smoothly from 0 → 100 as bytes are sent, without\n * requiring `tus-js-client` or any extra dependencies.\n *\n * @param bucketName - The name of the Supabase Storage bucket to upload to.\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useSupaUpload } from \"next-supa-utils/client\";\n *\n * export default function AvatarUploader() {\n * const { upload, isUploading, progress, data, error } = useSupaUpload(\"avatars\");\n *\n * const handleChange = async (e: React.ChangeEvent<HTMLInputElement>) => {\n * const file = e.target.files?.[0];\n * if (!file) return;\n * await upload(file, { path: `users/${file.name}`, upsert: true });\n * };\n *\n * return (\n * <div>\n * <input type=\"file\" onChange={handleChange} disabled={isUploading} />\n * {isUploading && <progress value={progress} max={100} />}\n * {isUploading && <p>{progress}%</p>}\n * {error && <p>Error: {error.message}</p>}\n * {data && <p>Uploaded to: {data.path}</p>}\n * </div>\n * );\n * }\n * ```\n */\nexport function useSupaUpload(bucketName: string): UseSupaUploadReturn {\n const [isUploading, setIsUploading] = useState(false);\n const [progress, setProgress] = useState(0);\n const [data, setData] = useState<UseSupaUploadReturn[\"data\"]>(null);\n const [error, setError] = useState<UseSupaUploadReturn[\"error\"]>(null);\n\n // Abort controller reference so we can cancel in-flight uploads.\n const xhrRef = useRef<XMLHttpRequest | null>(null);\n\n // Stable reference to the Supabase client (used only to get the session token).\n const supabaseRef = useRef<ReturnType<typeof createBrowserClient> | null>(null);\n\n // Get config from Context or Environment\n const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();\n\n function getClient() {\n if (supabaseRef.current) return supabaseRef.current;\n supabaseRef.current = createBrowserClient(supabaseUrl, supabaseAnonKey);\n return supabaseRef.current;\n }\n\n const upload = useCallback(\n async (file: File, options?: UploadOptions) => {\n // ── Reset state ──────────────────────────────────────────────\n setIsUploading(true);\n setProgress(0);\n setData(null);\n setError(null);\n\n try {\n const supabase = getClient();\n\n // Retrieve the current session token for authorization.\n const {\n data: { session },\n } = await supabase.auth.getSession();\n\n const accessToken = session?.access_token ?? supabaseAnonKey;\n const filePath = options?.path ?? file.name;\n\n // ── Build the Storage REST API URL ─────────────────────────\n // POST /storage/v1/object/:bucket/:path\n const uploadUrl = `${supabaseUrl}/storage/v1/object/${bucketName}/${filePath}`;\n\n // ── Upload via XHR for real-time progress ──────────────────\n await new Promise<void>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n xhrRef.current = xhr;\n\n // ── Progress handler ───────────────────────────────────\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable) {\n const pct = Math.round((e.loaded / e.total) * 100);\n setProgress(pct);\n }\n });\n\n // ── Success handler ────────────────────────────────────\n xhr.addEventListener(\"load\", () => {\n xhrRef.current = null;\n\n if (xhr.status >= 200 && xhr.status < 300) {\n try {\n const response = JSON.parse(xhr.responseText) as {\n Key?: string;\n Id?: string;\n };\n // Supabase returns { Key: \"bucket/path\" }\n const fullPath = response.Key ?? `${bucketName}/${filePath}`;\n setData({ path: filePath, fullPath });\n setProgress(100);\n resolve();\n } catch {\n // Response parsed fine even if body structure differs\n setData({ path: filePath, fullPath: `${bucketName}/${filePath}` });\n setProgress(100);\n resolve();\n }\n } else {\n // Server returned an error status\n try {\n const errBody = JSON.parse(xhr.responseText) as {\n statusCode?: string;\n error?: string;\n message?: string;\n };\n reject(\n new Error(\n errBody.message ?? errBody.error ?? `Upload failed with status ${xhr.status}`,\n ),\n );\n } catch {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n }\n });\n\n // ── Error handler ──────────────────────────────────────\n xhr.addEventListener(\"error\", () => {\n xhrRef.current = null;\n reject(new Error(\"Network error during upload\"));\n });\n\n // ── Abort handler ──────────────────────────────────────\n xhr.addEventListener(\"abort\", () => {\n xhrRef.current = null;\n reject(new Error(\"Upload cancelled\"));\n });\n\n // ── Send the request ───────────────────────────────────\n xhr.open(\"POST\", uploadUrl, true);\n xhr.setRequestHeader(\"Authorization\", `Bearer ${accessToken}`);\n xhr.setRequestHeader(\"apikey\", supabaseAnonKey);\n xhr.setRequestHeader(\n \"Content-Type\",\n options?.contentType ?? (file.type || \"application/octet-stream\"),\n );\n xhr.setRequestHeader(\n \"cache-control\",\n options?.cacheControl ?? \"3600\",\n );\n xhr.setRequestHeader(\n \"x-upsert\",\n String(options?.upsert ?? false),\n );\n\n xhr.send(file);\n });\n } catch (caught: unknown) {\n setError(handleSupaError(caught));\n setProgress(0);\n } finally {\n setIsUploading(false);\n }\n },\n [bucketName],\n );\n\n const cancel = useCallback(() => {\n if (xhrRef.current) {\n xhrRef.current.abort();\n xhrRef.current = null;\n }\n }, []);\n\n const reset = useCallback(() => {\n cancel();\n setIsUploading(false);\n setProgress(0);\n setData(null);\n setError(null);\n }, [cancel]);\n\n return { upload, isUploading, progress, data, error, reset, cancel };\n}\n","\"use client\";\n\nimport { useEffect, useRef } from \"react\";\nimport { createBrowserClient } from \"@supabase/ssr\";\nimport type { RealtimeChannel } from \"@supabase/supabase-js\";\n\nimport type { RealtimeEvent, RealtimePayload } from \"../../types\";\nimport { useSupaConfig } from \"../SupaProvider\";\n\n/**\n * React hook that subscribes to Supabase Realtime postgres_changes\n * events and **safely cleans up** on unmount to prevent memory leaks.\n *\n * @param table - The database table to listen to.\n * @param event - The event type: `\"INSERT\"`, `\"UPDATE\"`, `\"DELETE\"`, or `\"*\"` for all.\n * @param callback - Function called with the realtime payload on each event.\n * @param schema - The database schema (defaults to `\"public\"`).\n *\n * @example\n * ```tsx\n * \"use client\";\n * import { useSupaRealtime } from \"next-supa-utils/client\";\n *\n * export default function LiveMessages() {\n * const [messages, setMessages] = useState<Message[]>([]);\n *\n * useSupaRealtime(\"messages\", \"INSERT\", (payload) => {\n * setMessages((prev) => [...prev, payload.new as Message]);\n * });\n *\n * return <ul>{messages.map((m) => <li key={m.id}>{m.text}</li>)}</ul>;\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Listen to all events with a custom schema\n * useSupaRealtime(\"orders\", \"*\", (payload) => {\n * console.log(payload.eventType, payload.new, payload.old);\n * }, \"inventory\");\n * ```\n */\nexport function useSupaRealtime<T extends Record<string, unknown> = Record<string, unknown>>(\n table: string,\n event: RealtimeEvent,\n callback: (payload: RealtimePayload<T>) => void,\n schema: string = \"public\",\n): void {\n // Store callback in a ref so the channel doesn't need to re-subscribe\n // when only the callback identity changes (common with inline arrows).\n const callbackRef = useRef(callback);\n callbackRef.current = callback;\n\n // Store the channel so we can clean it up.\n const channelRef = useRef<RealtimeChannel | null>(null);\n\n // Get config from Context or Environment\n const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();\n\n useEffect(() => {\n const supabase = createBrowserClient(supabaseUrl, supabaseAnonKey);\n\n // Generate a unique channel name to avoid collisions.\n const channelName = `realtime:${schema}:${table}:${event}:${Date.now()}`;\n\n const channel = supabase\n .channel(channelName)\n .on(\n \"postgres_changes\" as \"postgres_changes\",\n {\n event: event === \"*\" ? \"*\" : event,\n schema,\n table,\n },\n (payload) => {\n callbackRef.current(payload as unknown as RealtimePayload<T>);\n },\n )\n .subscribe();\n\n channelRef.current = channel;\n\n // ── Cleanup: remove the channel on unmount or dep change ──────\n return () => {\n if (channelRef.current) {\n supabase.removeChannel(channelRef.current);\n channelRef.current = null;\n }\n };\n }, [table, event, schema]); // Re-subscribe when table/event/schema changes\n}\n"],"mappings":"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","names":["import_react","import_react","import_ssr","import_react","import_ssr","import_react","import_ssr"]}
@@ -1,5 +1,55 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
1
3
  import { SupabaseClient } from '@supabase/supabase-js';
2
4
 
5
+ interface SupaContextValue {
6
+ /**
7
+ * Explicit Supabase Project URL.
8
+ * If omitted, the context falls back to `process.env.NEXT_PUBLIC_SUPABASE_URL`.
9
+ */
10
+ supabaseUrl?: string;
11
+ /**
12
+ * Explicit Supabase Anon Key.
13
+ * If omitted, the context falls back to `process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY`.
14
+ */
15
+ supabaseAnonKey?: string;
16
+ }
17
+ /**
18
+ * `<SupaProvider>` allows you to inject explicit Supabase credentials
19
+ * (URL and Anon Key) into all `next-supa-utils/client` hooks.
20
+ *
21
+ * It is completely **optional** if you are using the standard environment
22
+ * variables (`NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`).
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * // app/layout.tsx
27
+ * import { SupaProvider } from "next-supa-utils/client";
28
+ *
29
+ * export default function RootLayout({ children }) {
30
+ * return (
31
+ * <html>
32
+ * <body>
33
+ * <SupaProvider supabaseUrl="https://..." supabaseAnonKey="ey...">
34
+ * {children}
35
+ * </SupaProvider>
36
+ * </body>
37
+ * </html>
38
+ * );
39
+ * }
40
+ * ```
41
+ */
42
+ declare function SupaProvider({ children, supabaseUrl, supabaseAnonKey, }: {
43
+ children: ReactNode;
44
+ } & SupaContextValue): react_jsx_runtime.JSX.Element;
45
+ /**
46
+ * Internal hook to retrieve the Supabase configuration from Context or Environment Variables.
47
+ */
48
+ declare function useSupaConfig(): {
49
+ url: string;
50
+ key: string;
51
+ };
52
+
3
53
  /** Standardized error shape returned by all next-supa-utils helpers. */
4
54
  interface SupaError {
5
55
  message: string;
@@ -204,4 +254,4 @@ declare function useSupaUpload(bucketName: string): UseSupaUploadReturn;
204
254
  */
205
255
  declare function useSupaRealtime<T extends Record<string, unknown> = Record<string, unknown>>(table: string, event: RealtimeEvent, callback: (payload: RealtimePayload<T>) => void, schema?: string): void;
206
256
 
207
- export { type RealtimeEvent, type RealtimePayload, type SupaError, type UploadOptions, type UseSupaSessionReturn, type UseSupaUploadReturn, type UseSupaUserReturn, useSupaRealtime, useSupaSession, useSupaUpload, useSupaUser };
257
+ export { type RealtimeEvent, type RealtimePayload, type SupaError, SupaProvider, type UploadOptions, type UseSupaSessionReturn, type UseSupaUploadReturn, type UseSupaUserReturn, useSupaConfig, useSupaRealtime, useSupaSession, useSupaUpload, useSupaUser };
@@ -1,5 +1,55 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { ReactNode } from 'react';
1
3
  import { SupabaseClient } from '@supabase/supabase-js';
2
4
 
5
+ interface SupaContextValue {
6
+ /**
7
+ * Explicit Supabase Project URL.
8
+ * If omitted, the context falls back to `process.env.NEXT_PUBLIC_SUPABASE_URL`.
9
+ */
10
+ supabaseUrl?: string;
11
+ /**
12
+ * Explicit Supabase Anon Key.
13
+ * If omitted, the context falls back to `process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY`.
14
+ */
15
+ supabaseAnonKey?: string;
16
+ }
17
+ /**
18
+ * `<SupaProvider>` allows you to inject explicit Supabase credentials
19
+ * (URL and Anon Key) into all `next-supa-utils/client` hooks.
20
+ *
21
+ * It is completely **optional** if you are using the standard environment
22
+ * variables (`NEXT_PUBLIC_SUPABASE_URL` and `NEXT_PUBLIC_SUPABASE_ANON_KEY`).
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * // app/layout.tsx
27
+ * import { SupaProvider } from "next-supa-utils/client";
28
+ *
29
+ * export default function RootLayout({ children }) {
30
+ * return (
31
+ * <html>
32
+ * <body>
33
+ * <SupaProvider supabaseUrl="https://..." supabaseAnonKey="ey...">
34
+ * {children}
35
+ * </SupaProvider>
36
+ * </body>
37
+ * </html>
38
+ * );
39
+ * }
40
+ * ```
41
+ */
42
+ declare function SupaProvider({ children, supabaseUrl, supabaseAnonKey, }: {
43
+ children: ReactNode;
44
+ } & SupaContextValue): react_jsx_runtime.JSX.Element;
45
+ /**
46
+ * Internal hook to retrieve the Supabase configuration from Context or Environment Variables.
47
+ */
48
+ declare function useSupaConfig(): {
49
+ url: string;
50
+ key: string;
51
+ };
52
+
3
53
  /** Standardized error shape returned by all next-supa-utils helpers. */
4
54
  interface SupaError {
5
55
  message: string;
@@ -204,4 +254,4 @@ declare function useSupaUpload(bucketName: string): UseSupaUploadReturn;
204
254
  */
205
255
  declare function useSupaRealtime<T extends Record<string, unknown> = Record<string, unknown>>(table: string, event: RealtimeEvent, callback: (payload: RealtimePayload<T>) => void, schema?: string): void;
206
256
 
207
- export { type RealtimeEvent, type RealtimePayload, type SupaError, type UploadOptions, type UseSupaSessionReturn, type UseSupaUploadReturn, type UseSupaUserReturn, useSupaRealtime, useSupaSession, useSupaUpload, useSupaUser };
257
+ export { type RealtimeEvent, type RealtimePayload, type SupaError, SupaProvider, type UploadOptions, type UseSupaSessionReturn, type UseSupaUploadReturn, type UseSupaUserReturn, useSupaConfig, useSupaRealtime, useSupaSession, useSupaUpload, useSupaUser };