next-supa-utils 0.1.4 → 0.1.6
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 +21 -0
- package/README.md +145 -3
- package/dist/client/index.cjs +187 -32
- package/dist/client/index.cjs.map +1 -1
- package/dist/client/index.d.cts +190 -1
- package/dist/client/index.d.ts +190 -1
- package/dist/client/index.js +177 -26
- package/dist/client/index.js.map +1 -1
- package/dist/server/index.cjs +92 -7
- package/dist/server/index.cjs.map +1 -1
- package/dist/server/index.d.cts +97 -2
- package/dist/server/index.d.ts +97 -2
- package/dist/server/index.js +91 -7
- package/dist/server/index.js.map +1 -1
- package/dist/shared/index.d.cts +10 -0
- package/dist/shared/index.d.ts +10 -0
- package/package.json +1 -1
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
|
|
@@ -102,6 +129,30 @@ export default function Avatar() {
|
|
|
102
129
|
}
|
|
103
130
|
```
|
|
104
131
|
|
|
132
|
+
### 4. Route Handlers (API Routes)
|
|
133
|
+
|
|
134
|
+
Wrap your Next.js API endpoints to automatically handle Supabase errors and enforce authentication.
|
|
135
|
+
|
|
136
|
+
```ts
|
|
137
|
+
// app/api/posts/route.ts
|
|
138
|
+
import { routeWrapper } from "next-supa-utils/server";
|
|
139
|
+
import { NextResponse } from "next/server";
|
|
140
|
+
|
|
141
|
+
export const POST = routeWrapper(
|
|
142
|
+
async (request, { supabase, user }) => {
|
|
143
|
+
const body = await request.json();
|
|
144
|
+
const { data, error } = await supabase
|
|
145
|
+
.from("posts")
|
|
146
|
+
.insert({ ...body, user_id: user!.id })
|
|
147
|
+
.single();
|
|
148
|
+
|
|
149
|
+
if (error) throw error; // Auto-caught and returned as 500
|
|
150
|
+
return NextResponse.json({ data });
|
|
151
|
+
},
|
|
152
|
+
{ requireAuth: true } // Auto-returns 401 if not logged in
|
|
153
|
+
);
|
|
154
|
+
```
|
|
155
|
+
|
|
105
156
|
---
|
|
106
157
|
|
|
107
158
|
## API Reference
|
|
@@ -120,6 +171,8 @@ Creates a Next.js middleware function that handles session refresh and route pro
|
|
|
120
171
|
| `redirectTo` | `string` | — | `"/login"` | Where to redirect unauthenticated users |
|
|
121
172
|
| `publicRoutes` | `string[]` | — | `[]` | Routes that are always public, even if matching a protected prefix |
|
|
122
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) |
|
|
123
176
|
|
|
124
177
|
**Returns:** `(request: NextRequest) => Promise<NextResponse>`
|
|
125
178
|
|
|
@@ -161,10 +214,47 @@ function createAction<TArgs extends unknown[], TResult>(
|
|
|
161
214
|
|
|
162
215
|
---
|
|
163
216
|
|
|
217
|
+
#### `routeWrapper(handler, options?)`
|
|
218
|
+
|
|
219
|
+
Wraps a Next.js App Router Route Handler (e.g., `GET`, `POST`) with automatic try-catch, error normalization, and optional authentication gating.
|
|
220
|
+
|
|
221
|
+
**Signature:**
|
|
222
|
+
|
|
223
|
+
```ts
|
|
224
|
+
function routeWrapper<TContext>(
|
|
225
|
+
handler: (request: NextRequest, context: RouteHandlerContext<TContext>) => Promise<NextResponse | Response>,
|
|
226
|
+
options?: RouteWrapperOptions
|
|
227
|
+
): (request: NextRequest, context: NextRouteContext) => Promise<NextResponse>
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**Options (`RouteWrapperOptions`):**
|
|
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.
|
|
234
|
+
|
|
235
|
+
**Context (`RouteHandlerContext`):**
|
|
236
|
+
- `params`: Auto-resolved dynamic route params (e.g., `{ id: "123" }`).
|
|
237
|
+
- `supabase`: An initialized Supabase server client (always available).
|
|
238
|
+
- `user`: The authenticated user (guaranteed non-null if `requireAuth: true`).
|
|
239
|
+
|
|
240
|
+
---
|
|
241
|
+
|
|
164
242
|
### Client — `next-supa-utils/client`
|
|
165
243
|
|
|
166
244
|
> ⚠️ All client exports include the `"use client"` directive. They must be used inside Client Components only.
|
|
167
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
|
+
|
|
168
258
|
#### `useSupaUser()`
|
|
169
259
|
|
|
170
260
|
React hook that provides the current authenticated user and subscribes to auth state changes.
|
|
@@ -199,6 +289,45 @@ React hook that provides the current session (access token, refresh token, expir
|
|
|
199
289
|
|
|
200
290
|
---
|
|
201
291
|
|
|
292
|
+
#### `useSupaUpload(bucketName)`
|
|
293
|
+
|
|
294
|
+
React hook that simplifies uploading files to Supabase Storage with **real-time progress tracking** (using direct XHR to bypass the JS SDK's lack of progress events).
|
|
295
|
+
|
|
296
|
+
**Returns:** `UseSupaUploadReturn`
|
|
297
|
+
|
|
298
|
+
| Property | Type | Description |
|
|
299
|
+
|---|---|---|
|
|
300
|
+
| `upload` | `(file: File, options?: UploadOptions) => Promise<void>` | Upload function. Options admit `path`, `upsert`, `cacheControl`, `contentType`. |
|
|
301
|
+
| `isUploading` | `boolean` | `true` while the upload is in progress |
|
|
302
|
+
| `progress` | `number` | Upload progress percentage (0–100) updated in real-time |
|
|
303
|
+
| `data` | `{ path: string; fullPath: string } \| null` | Successful upload result |
|
|
304
|
+
| `error` | `SupaError \| null` | Error details if upload failed |
|
|
305
|
+
| `cancel` | `() => void` | Aborts the in-flight upload |
|
|
306
|
+
| `reset` | `() => void` | Resets the hook state |
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
#### `useSupaRealtime(table, event, callback, schema?)`
|
|
311
|
+
|
|
312
|
+
React hook that subscribes to Supabase Realtime `postgres_changes` events. **Safely cleans up** the subscription on unmount to prevent memory leaks and duplicate listeners.
|
|
313
|
+
|
|
314
|
+
**Parameters:**
|
|
315
|
+
|
|
316
|
+
- `table` (`string`): The database table to listen to.
|
|
317
|
+
- `event` (`"INSERT" \| "UPDATE" \| "DELETE" \| "*"`): The event type.
|
|
318
|
+
- `callback` (`(payload: RealtimePayload) => void`): Function invoked on each event.
|
|
319
|
+
- `schema` (`string`, default `"public"`): The database schema.
|
|
320
|
+
|
|
321
|
+
**Example:**
|
|
322
|
+
|
|
323
|
+
```tsx
|
|
324
|
+
useSupaRealtime("messages", "INSERT", (payload) => {
|
|
325
|
+
console.log("New message:", payload.new);
|
|
326
|
+
});
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
---
|
|
330
|
+
|
|
202
331
|
### Shared — `next-supa-utils`
|
|
203
332
|
|
|
204
333
|
#### `handleSupaError(error)`
|
|
@@ -268,12 +397,25 @@ src/
|
|
|
268
397
|
|
|
269
398
|
| Import | Environment | Contains |
|
|
270
399
|
|---|---|---|
|
|
271
|
-
| `next-supa-utils/client` | Client Components | `useSupaUser`, `useSupaSession` |
|
|
272
|
-
| `next-supa-utils/server` | Server Components, Middleware, Server Actions | `withSupaAuth`, `createAction` |
|
|
400
|
+
| `next-supa-utils/client` | Client Components | `useSupaUser`, `useSupaSession`, `useSupaUpload`, `useSupaRealtime` |
|
|
401
|
+
| `next-supa-utils/server` | Server Components, Middleware, Server Actions, Route Handlers | `withSupaAuth`, `createAction`, `routeWrapper` |
|
|
273
402
|
| `next-supa-utils` | Anywhere | `handleSupaError`, all types |
|
|
274
403
|
|
|
275
404
|
## Changelog
|
|
276
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
|
+
|
|
412
|
+
### v0.1.5
|
|
413
|
+
|
|
414
|
+
- **🚀 New Features:**
|
|
415
|
+
- `useSupaUpload` — React hook for uploading files to Supabase Storage with **real-time progress tracking** (using direct XHR to REST API, no extra dependencies) and abort capabilities.
|
|
416
|
+
- `useSupaRealtime` — React hook to subscribe to Supabase Realtime `postgres_changes` events with **safe auto-cleanup** (prevents memory leaks on unmount).
|
|
417
|
+
- `routeWrapper` — Higher-order function for Next.js Route Handlers (API routes) with auto try-catch, standardized error responses, and optional auth gating (`requireAuth: true`).
|
|
418
|
+
|
|
277
419
|
### v0.1.3
|
|
278
420
|
|
|
279
421
|
- **🔐 RBAC Support** — `withSupaAuth` now supports **role-based access control**.
|
package/dist/client/index.cjs
CHANGED
|
@@ -22,13 +22,40 @@ 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,
|
|
27
|
+
useSupaRealtime: () => useSupaRealtime,
|
|
25
28
|
useSupaSession: () => useSupaSession,
|
|
29
|
+
useSupaUpload: () => useSupaUpload,
|
|
26
30
|
useSupaUser: () => useSupaUser
|
|
27
31
|
});
|
|
28
32
|
module.exports = __toCommonJS(client_exports);
|
|
29
33
|
|
|
30
|
-
// src/client/
|
|
34
|
+
// src/client/SupaProvider.tsx
|
|
31
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");
|
|
32
59
|
var import_ssr = require("@supabase/ssr");
|
|
33
60
|
|
|
34
61
|
// src/shared/utils/error-handler.ts
|
|
@@ -57,25 +84,13 @@ function handleSupaError(error) {
|
|
|
57
84
|
|
|
58
85
|
// src/client/hooks/useSupaUser.ts
|
|
59
86
|
function useSupaUser() {
|
|
60
|
-
const [state, setState] = (0,
|
|
87
|
+
const [state, setState] = (0, import_react2.useState)({
|
|
61
88
|
user: null,
|
|
62
89
|
loading: true,
|
|
63
90
|
error: null
|
|
64
91
|
});
|
|
65
|
-
|
|
66
|
-
|
|
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
|
-
}
|
|
92
|
+
const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();
|
|
93
|
+
(0, import_react2.useEffect)(() => {
|
|
79
94
|
const supabase = (0, import_ssr.createBrowserClient)(supabaseUrl, supabaseAnonKey);
|
|
80
95
|
supabase.auth.getUser().then(({ data, error }) => {
|
|
81
96
|
setState({
|
|
@@ -101,28 +116,16 @@ function useSupaUser() {
|
|
|
101
116
|
}
|
|
102
117
|
|
|
103
118
|
// src/client/hooks/useSupaSession.ts
|
|
104
|
-
var
|
|
119
|
+
var import_react3 = require("react");
|
|
105
120
|
var import_ssr2 = require("@supabase/ssr");
|
|
106
121
|
function useSupaSession() {
|
|
107
|
-
const [state, setState] = (0,
|
|
122
|
+
const [state, setState] = (0, import_react3.useState)({
|
|
108
123
|
session: null,
|
|
109
124
|
loading: true,
|
|
110
125
|
error: null
|
|
111
126
|
});
|
|
112
|
-
|
|
113
|
-
|
|
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
|
-
}
|
|
127
|
+
const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();
|
|
128
|
+
(0, import_react3.useEffect)(() => {
|
|
126
129
|
const supabase = (0, import_ssr2.createBrowserClient)(supabaseUrl, supabaseAnonKey);
|
|
127
130
|
supabase.auth.getSession().then(({ data, error }) => {
|
|
128
131
|
setState({
|
|
@@ -146,9 +149,161 @@ function useSupaSession() {
|
|
|
146
149
|
}, []);
|
|
147
150
|
return state;
|
|
148
151
|
}
|
|
152
|
+
|
|
153
|
+
// src/client/hooks/useSupaUpload.ts
|
|
154
|
+
var import_react4 = require("react");
|
|
155
|
+
var import_ssr3 = require("@supabase/ssr");
|
|
156
|
+
function useSupaUpload(bucketName) {
|
|
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();
|
|
164
|
+
function getClient() {
|
|
165
|
+
if (supabaseRef.current) return supabaseRef.current;
|
|
166
|
+
supabaseRef.current = (0, import_ssr3.createBrowserClient)(supabaseUrl, supabaseAnonKey);
|
|
167
|
+
return supabaseRef.current;
|
|
168
|
+
}
|
|
169
|
+
const upload = (0, import_react4.useCallback)(
|
|
170
|
+
async (file, options) => {
|
|
171
|
+
setIsUploading(true);
|
|
172
|
+
setProgress(0);
|
|
173
|
+
setData(null);
|
|
174
|
+
setError(null);
|
|
175
|
+
try {
|
|
176
|
+
const supabase = getClient();
|
|
177
|
+
const {
|
|
178
|
+
data: { session }
|
|
179
|
+
} = await supabase.auth.getSession();
|
|
180
|
+
const accessToken = session?.access_token ?? supabaseAnonKey;
|
|
181
|
+
const filePath = options?.path ?? file.name;
|
|
182
|
+
const uploadUrl = `${supabaseUrl}/storage/v1/object/${bucketName}/${filePath}`;
|
|
183
|
+
await new Promise((resolve, reject) => {
|
|
184
|
+
const xhr = new XMLHttpRequest();
|
|
185
|
+
xhrRef.current = xhr;
|
|
186
|
+
xhr.upload.addEventListener("progress", (e) => {
|
|
187
|
+
if (e.lengthComputable) {
|
|
188
|
+
const pct = Math.round(e.loaded / e.total * 100);
|
|
189
|
+
setProgress(pct);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
xhr.addEventListener("load", () => {
|
|
193
|
+
xhrRef.current = null;
|
|
194
|
+
if (xhr.status >= 200 && xhr.status < 300) {
|
|
195
|
+
try {
|
|
196
|
+
const response = JSON.parse(xhr.responseText);
|
|
197
|
+
const fullPath = response.Key ?? `${bucketName}/${filePath}`;
|
|
198
|
+
setData({ path: filePath, fullPath });
|
|
199
|
+
setProgress(100);
|
|
200
|
+
resolve();
|
|
201
|
+
} catch {
|
|
202
|
+
setData({ path: filePath, fullPath: `${bucketName}/${filePath}` });
|
|
203
|
+
setProgress(100);
|
|
204
|
+
resolve();
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
try {
|
|
208
|
+
const errBody = JSON.parse(xhr.responseText);
|
|
209
|
+
reject(
|
|
210
|
+
new Error(
|
|
211
|
+
errBody.message ?? errBody.error ?? `Upload failed with status ${xhr.status}`
|
|
212
|
+
)
|
|
213
|
+
);
|
|
214
|
+
} catch {
|
|
215
|
+
reject(new Error(`Upload failed with status ${xhr.status}`));
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
xhr.addEventListener("error", () => {
|
|
220
|
+
xhrRef.current = null;
|
|
221
|
+
reject(new Error("Network error during upload"));
|
|
222
|
+
});
|
|
223
|
+
xhr.addEventListener("abort", () => {
|
|
224
|
+
xhrRef.current = null;
|
|
225
|
+
reject(new Error("Upload cancelled"));
|
|
226
|
+
});
|
|
227
|
+
xhr.open("POST", uploadUrl, true);
|
|
228
|
+
xhr.setRequestHeader("Authorization", `Bearer ${accessToken}`);
|
|
229
|
+
xhr.setRequestHeader("apikey", supabaseAnonKey);
|
|
230
|
+
xhr.setRequestHeader(
|
|
231
|
+
"Content-Type",
|
|
232
|
+
options?.contentType ?? (file.type || "application/octet-stream")
|
|
233
|
+
);
|
|
234
|
+
xhr.setRequestHeader(
|
|
235
|
+
"cache-control",
|
|
236
|
+
options?.cacheControl ?? "3600"
|
|
237
|
+
);
|
|
238
|
+
xhr.setRequestHeader(
|
|
239
|
+
"x-upsert",
|
|
240
|
+
String(options?.upsert ?? false)
|
|
241
|
+
);
|
|
242
|
+
xhr.send(file);
|
|
243
|
+
});
|
|
244
|
+
} catch (caught) {
|
|
245
|
+
setError(handleSupaError(caught));
|
|
246
|
+
setProgress(0);
|
|
247
|
+
} finally {
|
|
248
|
+
setIsUploading(false);
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
[bucketName]
|
|
252
|
+
);
|
|
253
|
+
const cancel = (0, import_react4.useCallback)(() => {
|
|
254
|
+
if (xhrRef.current) {
|
|
255
|
+
xhrRef.current.abort();
|
|
256
|
+
xhrRef.current = null;
|
|
257
|
+
}
|
|
258
|
+
}, []);
|
|
259
|
+
const reset = (0, import_react4.useCallback)(() => {
|
|
260
|
+
cancel();
|
|
261
|
+
setIsUploading(false);
|
|
262
|
+
setProgress(0);
|
|
263
|
+
setData(null);
|
|
264
|
+
setError(null);
|
|
265
|
+
}, [cancel]);
|
|
266
|
+
return { upload, isUploading, progress, data, error, reset, cancel };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/client/hooks/useSupaRealtime.ts
|
|
270
|
+
var import_react5 = require("react");
|
|
271
|
+
var import_ssr4 = require("@supabase/ssr");
|
|
272
|
+
function useSupaRealtime(table, event, callback, schema = "public") {
|
|
273
|
+
const callbackRef = (0, import_react5.useRef)(callback);
|
|
274
|
+
callbackRef.current = callback;
|
|
275
|
+
const channelRef = (0, import_react5.useRef)(null);
|
|
276
|
+
const { url: supabaseUrl, key: supabaseAnonKey } = useSupaConfig();
|
|
277
|
+
(0, import_react5.useEffect)(() => {
|
|
278
|
+
const supabase = (0, import_ssr4.createBrowserClient)(supabaseUrl, supabaseAnonKey);
|
|
279
|
+
const channelName = `realtime:${schema}:${table}:${event}:${Date.now()}`;
|
|
280
|
+
const channel = supabase.channel(channelName).on(
|
|
281
|
+
"postgres_changes",
|
|
282
|
+
{
|
|
283
|
+
event: event === "*" ? "*" : event,
|
|
284
|
+
schema,
|
|
285
|
+
table
|
|
286
|
+
},
|
|
287
|
+
(payload) => {
|
|
288
|
+
callbackRef.current(payload);
|
|
289
|
+
}
|
|
290
|
+
).subscribe();
|
|
291
|
+
channelRef.current = channel;
|
|
292
|
+
return () => {
|
|
293
|
+
if (channelRef.current) {
|
|
294
|
+
supabase.removeChannel(channelRef.current);
|
|
295
|
+
channelRef.current = null;
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
}, [table, event, schema]);
|
|
299
|
+
}
|
|
149
300
|
// Annotate the CommonJS export names for ESM import in node:
|
|
150
301
|
0 && (module.exports = {
|
|
302
|
+
SupaProvider,
|
|
303
|
+
useSupaConfig,
|
|
304
|
+
useSupaRealtime,
|
|
151
305
|
useSupaSession,
|
|
306
|
+
useSupaUpload,
|
|
152
307
|
useSupaUser
|
|
153
308
|
});
|
|
154
309
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -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"],"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":";;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEA,mBAAoC;AACpC,iBAAoC;;;ACQ7B,SAAS,gBAAgB,OAA2B;AAEzD,MAAI,iBAAiB,OAAO;AAC1B,UAAM,SAAS;AACf,WAAO;AAAA,MACL,SAAS,MAAM;AAAA,MACf,MAAM,OAAO,OAAO,SAAS,WAAW,OAAO,OAAO;AAAA,MACtD,QAAQ,OAAO,OAAO,WAAW,WAAW,OAAO,SAAS;AAAA,IAC9D;AAAA,EACF;AAGA,MACE,OAAO,UAAU,YACjB,UAAU,QACV,aAAa,SACb,OAAQ,MAAkC,YAAY,UACtD;AACA,UAAM,MAAM;AACZ,WAAO;AAAA,MACL,SAAS,IAAI;AAAA,MACb,MAAM,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAAA,MAChD,QAAQ,OAAO,IAAI,WAAW,WAAW,IAAI,SAAS;AAAA,IACxD;AAAA,EACF;AAGA,MAAI,OAAO,UAAU,UAAU;AAC7B,WAAO,EAAE,SAAS,MAAM;AAAA,EAC1B;AAGA,SAAO,EAAE,SAAS,4BAA4B;AAChD;;;ADdO,SAAS,cAAiC;AAC/C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAA4B;AAAA,IACpD,MAAM;AAAA,IACN,SAAS;AAAA,IACT,OAAO;AAAA,EACT,CAAC;AAED,8BAAU,MAAM;AACd,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,kBAAkB,QAAQ,IAAI;AAEpC,QAAI,CAAC,eAAe,CAAC,iBAAiB;AACpC,eAAS;AAAA,QACP,MAAM;AAAA,QACN,SAAS;AAAA,QACT,OAAO;AAAA,UACL,SACE;AAAA,UACF,MAAM;AAAA,QACR;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAEA,UAAM,eAAW,gCAAoB,aAAa,eAAe;AAGjE,aAAS,KAAK,QAAQ,EAAE,KAAK,CAAC,EAAE,MAAM,MAAM,MAAM;AAChD,eAAS;AAAA,QACP,MAAM,KAAK;AAAA,QACX,SAAS;AAAA,QACT,OAAO,QAAQ,gBAAgB,KAAK,IAAI;AAAA,MAC1C,CAAC;AAAA,IACH,CAAC;AAGD,UAAM;AAAA,MACJ,MAAM,EAAE,aAAa;AAAA,IACvB,IAAI,SAAS,KAAK,kBAAkB,CAAC,QAAQ,YAAY;AACvD,eAAS,CAAC,UAAU;AAAA,QAClB,GAAG;AAAA,QACH,MAAM,SAAS,QAAQ;AAAA,QACvB,SAAS;AAAA,MACX,EAAE;AAAA,IACJ,CAAC;AAED,WAAO,MAAM;AACX,mBAAa,YAAY;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;;;AEhFA,IAAAA,gBAAoC;AACpC,IAAAC,cAAoC;AA4B7B,SAAS,iBAAuC;AACrD,QAAM,CAAC,OAAO,QAAQ,QAAI,wBAA+B;AAAA,IACvD,SAAS;AAAA,IACT,SAAS;AAAA,IACT,OAAO;AAAA,EACT,CAAC;AAED,+BAAU,MAAM;AACd,UAAM,cAAc,QAAQ,IAAI;AAChC,UAAM,kBAAkB,QAAQ,IAAI;AAEpC,QAAI,CAAC,eAAe,CAAC,iBAAiB;AACpC,eAAS;AAAA,QACP,SAAS;AAAA,QACT,SAAS;AAAA,QACT,OAAO;AAAA,UACL,SACE;AAAA,UACF,MAAM;AAAA,QACR;AAAA,MACF,CAAC;AACD;AAAA,IACF;AAEA,UAAM,eAAW,iCAAoB,aAAa,eAAe;AAGjE,aAAS,KAAK,WAAW,EAAE,KAAK,CAAC,EAAE,MAAM,MAAM,MAAM;AACnD,eAAS;AAAA,QACP,SAAS,KAAK;AAAA,QACd,SAAS;AAAA,QACT,OAAO,QAAQ,gBAAgB,KAAK,IAAI;AAAA,MAC1C,CAAC;AAAA,IACH,CAAC;AAGD,UAAM;AAAA,MACJ,MAAM,EAAE,aAAa;AAAA,IACvB,IAAI,SAAS,KAAK,kBAAkB,CAAC,QAAQ,YAA4B;AACvE,eAAS,CAAC,UAAU;AAAA,QAClB,GAAG;AAAA,QACH;AAAA,QACA,SAAS;AAAA,MACX,EAAE;AAAA,IACJ,CAAC;AAED,WAAO,MAAM;AACX,mBAAa,YAAY;AAAA,IAC3B;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AACT;","names":["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"]}
|