@weirdscience/based-client 0.1.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +69 -1
- package/dist/client.d.ts.map +1 -1
- package/dist/hooks/use-query.d.ts.map +1 -1
- package/dist/hooks/use-record.d.ts +3 -0
- package/dist/hooks/use-record.d.ts.map +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +181 -33
- package/dist/types.d.ts +48 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -60,6 +60,41 @@ const { data, total, isLoading } = useQuery("posts", {
|
|
|
60
60
|
});
|
|
61
61
|
```
|
|
62
62
|
|
|
63
|
+
Gate the query behind auth or any boolean with `enabled`:
|
|
64
|
+
|
|
65
|
+
```tsx
|
|
66
|
+
const { user } = useUser();
|
|
67
|
+
const { data } = useQuery("notes", { enabled: !!user });
|
|
68
|
+
// Won't fire until `user` is truthy — avoids a 403 when signed out.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### `useRecord(table, id, options?)`
|
|
72
|
+
|
|
73
|
+
Fetch a single record by id via `GET /api/:table/:id`. Returns `{ data, isLoading, error, refetch }` — `data` is a single row, not an array.
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
import { useRecord } from "@weirdscience/based-client";
|
|
77
|
+
|
|
78
|
+
function Post({ id }: { id: string }) {
|
|
79
|
+
const { data: post, isLoading } = useRecord("posts", id);
|
|
80
|
+
if (isLoading) return <Spinner />;
|
|
81
|
+
if (!post) return <NotFound />;
|
|
82
|
+
return <h1>{post.title}</h1>;
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Pass `null`/`undefined` as the id to skip the query (same effect as `enabled: false`):
|
|
87
|
+
|
|
88
|
+
```tsx
|
|
89
|
+
const { data } = useRecord("posts", selectedId); // no fetch until selectedId is set
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Perfect for deterministic keys like `${userId}:${key}`:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
const { data: prefs } = useRecord("preferences", `${user.id}:theme`);
|
|
96
|
+
```
|
|
97
|
+
|
|
63
98
|
### `useMutation(table, operation)`
|
|
64
99
|
|
|
65
100
|
Write rows. `operation` is `"create" | "update" | "delete"`. Returns `{ mutate, isLoading, error }`.
|
|
@@ -124,7 +159,40 @@ Methods on `client.auth`:
|
|
|
124
159
|
- `signOut()` → invalidates the session
|
|
125
160
|
- `refreshSession()` → manually refresh (happens automatically on 401)
|
|
126
161
|
|
|
127
|
-
Access tokens auto-refresh on `401`.
|
|
162
|
+
Access tokens auto-refresh on `401`.
|
|
163
|
+
|
|
164
|
+
## Session persistence
|
|
165
|
+
|
|
166
|
+
Sessions persist across page reloads via `localStorage` by default. On mount, the client restores the saved session and validates it by calling `/auth/me`.
|
|
167
|
+
|
|
168
|
+
Use `client.ready()` or `useUser().isLoading` to avoid flashing a logged-out UI during hydration:
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
const { user, isLoading } = useUser();
|
|
172
|
+
if (isLoading) return <Spinner />;
|
|
173
|
+
if (!user) return <LoginForm />;
|
|
174
|
+
return <Dashboard user={user} />;
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Opt out or use a custom storage adapter:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
// Disable entirely (in-memory only)
|
|
181
|
+
createClient({ url, anonKey, storage: false });
|
|
182
|
+
|
|
183
|
+
// Custom storage (cookies, IndexedDB, React Native AsyncStorage, etc.)
|
|
184
|
+
createClient({
|
|
185
|
+
url,
|
|
186
|
+
anonKey,
|
|
187
|
+
storage: {
|
|
188
|
+
getItem: (k) => AsyncStorage.getItem(k),
|
|
189
|
+
setItem: (k, v) => AsyncStorage.setItem(k, v),
|
|
190
|
+
removeItem: (k) => AsyncStorage.removeItem(k),
|
|
191
|
+
},
|
|
192
|
+
});
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Storage adapters can return promises — the client awaits them.
|
|
128
196
|
|
|
129
197
|
## Type safety
|
|
130
198
|
|
package/dist/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,kBAAkB,EAGlB,WAAW,EAEZ,MAAM,SAAS,CAAC;AAqBjB,wBAAgB,YAAY,CAAC,OAAO,EAAE,kBAAkB,GAAG,WAAW,CAyPrE"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-query.d.ts","sourceRoot":"","sources":["../../src/hooks/use-query.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzE,wBAAgB,QAAQ,CACtB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,aAAa,EACjE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,EAE7C,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC3B,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,
|
|
1
|
+
{"version":3,"file":"use-query.d.ts","sourceRoot":"","sources":["../../src/hooks/use-query.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzE,wBAAgB,QAAQ,CACtB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,aAAa,EACjE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,EAE7C,KAAK,EAAE,CAAC,EACR,OAAO,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAC3B,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAyDnB"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { RecordOptions, RecordResult, DefaultTables } from "../types";
|
|
2
|
+
export declare function useRecord<T extends Record<string, Record<string, unknown>> = DefaultTables, K extends string & keyof T = string & keyof T>(table: K, id: string | null | undefined, options?: RecordOptions): RecordResult<T[K]>;
|
|
3
|
+
//# sourceMappingURL=use-record.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-record.d.ts","sourceRoot":"","sources":["../../src/hooks/use-record.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAE3E,wBAAgB,SAAS,CACvB,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,GAAG,aAAa,EACjE,CAAC,SAAS,MAAM,GAAG,MAAM,CAAC,GAAG,MAAM,GAAG,MAAM,CAAC,EAE7C,KAAK,EAAE,CAAC,EACR,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EAC7B,OAAO,CAAC,EAAE,aAAa,GACtB,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CA8CpB"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ export { createClient } from "./client";
|
|
|
2
2
|
export { BasedProvider, useBasedClient } from "./provider";
|
|
3
3
|
export { useUser } from "./hooks/use-user";
|
|
4
4
|
export { useQuery } from "./hooks/use-query";
|
|
5
|
+
export { useRecord } from "./hooks/use-record";
|
|
5
6
|
export { useMutation } from "./hooks/use-mutation";
|
|
6
|
-
export type { BasedClient, BasedClientOptions, AuthUser, AuthState, QueryOptions, QueryResult, MutationResult, DefaultTables, } from "./types";
|
|
7
|
+
export type { BasedClient, BasedClientOptions, StorageAdapter, AuthUser, AuthState, QueryOptions, QueryResult, RecordOptions, RecordResult, MutationResult, DefaultTables, } from "./types";
|
|
7
8
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,cAAc,EACd,aAAa,GACd,MAAM,SAAS,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAC3C,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAEnD,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,cAAc,EACd,QAAQ,EACR,SAAS,EACT,YAAY,EACZ,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,aAAa,GACd,MAAM,SAAS,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
// src/client.ts
|
|
2
|
+
var DEFAULT_STORAGE_KEY = "based.session";
|
|
3
|
+
function defaultStorage() {
|
|
4
|
+
if (typeof globalThis === "undefined")
|
|
5
|
+
return null;
|
|
6
|
+
const ls = globalThis.localStorage;
|
|
7
|
+
if (!ls || typeof ls.getItem !== "function")
|
|
8
|
+
return null;
|
|
9
|
+
return {
|
|
10
|
+
getItem: (k) => ls.getItem(k),
|
|
11
|
+
setItem: (k, v) => ls.setItem(k, v),
|
|
12
|
+
removeItem: (k) => ls.removeItem(k)
|
|
13
|
+
};
|
|
14
|
+
}
|
|
2
15
|
function createClient(options) {
|
|
3
16
|
const { url, anonKey } = options;
|
|
4
17
|
const baseUrl = url.replace(/\/$/, "");
|
|
18
|
+
const storage = options.storage === false ? null : options.storage ?? defaultStorage();
|
|
19
|
+
const storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
5
20
|
let state = {
|
|
6
21
|
user: null,
|
|
7
22
|
accessToken: null,
|
|
8
|
-
refreshToken: null
|
|
23
|
+
refreshToken: null,
|
|
24
|
+
isLoading: !!storage
|
|
9
25
|
};
|
|
10
26
|
const listeners = new Set;
|
|
11
27
|
function notify() {
|
|
@@ -19,6 +35,66 @@ function createClient(options) {
|
|
|
19
35
|
function getState() {
|
|
20
36
|
return state;
|
|
21
37
|
}
|
|
38
|
+
async function persist() {
|
|
39
|
+
if (!storage)
|
|
40
|
+
return;
|
|
41
|
+
const snapshot = {
|
|
42
|
+
user: state.user,
|
|
43
|
+
accessToken: state.accessToken,
|
|
44
|
+
refreshToken: state.refreshToken
|
|
45
|
+
};
|
|
46
|
+
if (!snapshot.refreshToken) {
|
|
47
|
+
await storage.removeItem(storageKey);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
await storage.setItem(storageKey, JSON.stringify(snapshot));
|
|
51
|
+
}
|
|
52
|
+
function setState(next) {
|
|
53
|
+
state = { ...state, ...next };
|
|
54
|
+
notify();
|
|
55
|
+
persist().catch(() => {});
|
|
56
|
+
}
|
|
57
|
+
const readyPromise = (async () => {
|
|
58
|
+
if (!storage) {
|
|
59
|
+
setState({ isLoading: false });
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const raw = await storage.getItem(storageKey);
|
|
64
|
+
if (!raw) {
|
|
65
|
+
setState({ isLoading: false });
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const stored = JSON.parse(raw);
|
|
69
|
+
if (!stored.refreshToken) {
|
|
70
|
+
setState({ isLoading: false });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
state = {
|
|
74
|
+
user: stored.user,
|
|
75
|
+
accessToken: stored.accessToken,
|
|
76
|
+
refreshToken: stored.refreshToken,
|
|
77
|
+
isLoading: true
|
|
78
|
+
};
|
|
79
|
+
notify();
|
|
80
|
+
const user = await getUser();
|
|
81
|
+
if (!user) {
|
|
82
|
+
const refreshed = await refreshSession();
|
|
83
|
+
if (refreshed) {
|
|
84
|
+
await getUser();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
} catch {
|
|
88
|
+
try {
|
|
89
|
+
await storage.removeItem(storageKey);
|
|
90
|
+
} catch {}
|
|
91
|
+
} finally {
|
|
92
|
+
setState({ isLoading: false });
|
|
93
|
+
}
|
|
94
|
+
})();
|
|
95
|
+
function ready() {
|
|
96
|
+
return readyPromise;
|
|
97
|
+
}
|
|
22
98
|
async function fetchWithAuth(path, init = {}) {
|
|
23
99
|
const headers = new Headers(init.headers);
|
|
24
100
|
if (state.accessToken) {
|
|
@@ -46,16 +122,16 @@ function createClient(options) {
|
|
|
46
122
|
body: JSON.stringify({ email, password })
|
|
47
123
|
});
|
|
48
124
|
if (!res.ok) {
|
|
49
|
-
const json = await res.json();
|
|
50
|
-
throw new Error(json
|
|
125
|
+
const json = await res.json().catch(() => null);
|
|
126
|
+
throw new Error(json?.error?.message || "Sign up failed");
|
|
51
127
|
}
|
|
52
128
|
const { data } = await res.json();
|
|
53
|
-
|
|
129
|
+
setState({
|
|
54
130
|
user: data.user,
|
|
55
131
|
accessToken: data.accessToken,
|
|
56
|
-
refreshToken: data.refreshToken
|
|
57
|
-
|
|
58
|
-
|
|
132
|
+
refreshToken: data.refreshToken,
|
|
133
|
+
isLoading: false
|
|
134
|
+
});
|
|
59
135
|
return data.user;
|
|
60
136
|
}
|
|
61
137
|
async function signIn(email, password) {
|
|
@@ -65,16 +141,16 @@ function createClient(options) {
|
|
|
65
141
|
body: JSON.stringify({ email, password })
|
|
66
142
|
});
|
|
67
143
|
if (!res.ok) {
|
|
68
|
-
const json = await res.json();
|
|
69
|
-
throw new Error(json
|
|
144
|
+
const json = await res.json().catch(() => null);
|
|
145
|
+
throw new Error(json?.error?.message || "Sign in failed");
|
|
70
146
|
}
|
|
71
147
|
const { data } = await res.json();
|
|
72
|
-
|
|
148
|
+
setState({
|
|
73
149
|
user: data.user,
|
|
74
150
|
accessToken: data.accessToken,
|
|
75
|
-
refreshToken: data.refreshToken
|
|
76
|
-
|
|
77
|
-
|
|
151
|
+
refreshToken: data.refreshToken,
|
|
152
|
+
isLoading: false
|
|
153
|
+
});
|
|
78
154
|
return data.user;
|
|
79
155
|
}
|
|
80
156
|
async function signOut() {
|
|
@@ -86,8 +162,7 @@ function createClient(options) {
|
|
|
86
162
|
});
|
|
87
163
|
} catch {}
|
|
88
164
|
}
|
|
89
|
-
|
|
90
|
-
notify();
|
|
165
|
+
setState({ user: null, accessToken: null, refreshToken: null, isLoading: false });
|
|
91
166
|
}
|
|
92
167
|
async function refreshSession() {
|
|
93
168
|
if (!state.refreshToken)
|
|
@@ -99,27 +174,46 @@ function createClient(options) {
|
|
|
99
174
|
body: JSON.stringify({ refreshToken: state.refreshToken })
|
|
100
175
|
});
|
|
101
176
|
if (!res.ok) {
|
|
102
|
-
|
|
103
|
-
notify();
|
|
177
|
+
setState({ user: null, accessToken: null, refreshToken: null, isLoading: false });
|
|
104
178
|
return false;
|
|
105
179
|
}
|
|
106
180
|
const { data } = await res.json();
|
|
107
|
-
|
|
108
|
-
...state,
|
|
181
|
+
setState({
|
|
109
182
|
accessToken: data.accessToken,
|
|
110
183
|
refreshToken: data.refreshToken
|
|
111
|
-
};
|
|
112
|
-
notify();
|
|
184
|
+
});
|
|
113
185
|
return true;
|
|
114
186
|
} catch {
|
|
115
187
|
return false;
|
|
116
188
|
}
|
|
117
189
|
}
|
|
190
|
+
async function getUser() {
|
|
191
|
+
if (!state.accessToken)
|
|
192
|
+
return null;
|
|
193
|
+
try {
|
|
194
|
+
const res = await fetch(`${baseUrl}/auth/me`, {
|
|
195
|
+
headers: { Authorization: `Bearer ${state.accessToken}` }
|
|
196
|
+
});
|
|
197
|
+
if (!res.ok) {
|
|
198
|
+
if (res.status === 401) {
|
|
199
|
+
setState({ user: null });
|
|
200
|
+
}
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
const { data } = await res.json();
|
|
204
|
+
const user = { id: data.id, email: data.email };
|
|
205
|
+
setState({ user });
|
|
206
|
+
return user;
|
|
207
|
+
} catch {
|
|
208
|
+
return null;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
118
211
|
return {
|
|
119
|
-
auth: { signUp, signIn, signOut, refreshSession },
|
|
212
|
+
auth: { signUp, signIn, signOut, refreshSession, getUser },
|
|
120
213
|
fetch: fetchWithAuth,
|
|
121
214
|
subscribe,
|
|
122
|
-
getState
|
|
215
|
+
getState,
|
|
216
|
+
ready
|
|
123
217
|
};
|
|
124
218
|
}
|
|
125
219
|
// src/provider.tsx
|
|
@@ -149,18 +243,19 @@ function useUser() {
|
|
|
149
243
|
const state = useSyncExternalStore(client.subscribe, () => client.getState(), () => client.getState());
|
|
150
244
|
return {
|
|
151
245
|
user: state.user,
|
|
152
|
-
isLoading:
|
|
246
|
+
isLoading: state.isLoading,
|
|
153
247
|
error: null
|
|
154
248
|
};
|
|
155
249
|
}
|
|
156
250
|
// src/hooks/use-query.ts
|
|
157
|
-
import { useState
|
|
251
|
+
import { useState, useEffect, useCallback } from "react";
|
|
158
252
|
function useQuery(table, options) {
|
|
159
253
|
const client = useBasedClient();
|
|
160
|
-
const
|
|
161
|
-
const [
|
|
162
|
-
const [
|
|
163
|
-
const [
|
|
254
|
+
const enabled = options?.enabled !== false;
|
|
255
|
+
const [data, setData] = useState(null);
|
|
256
|
+
const [total, setTotal] = useState(0);
|
|
257
|
+
const [isLoading, setIsLoading] = useState(enabled);
|
|
258
|
+
const [error, setError] = useState(null);
|
|
164
259
|
const fetchData = useCallback(async () => {
|
|
165
260
|
setIsLoading(true);
|
|
166
261
|
setError(null);
|
|
@@ -196,18 +291,70 @@ function useQuery(table, options) {
|
|
|
196
291
|
setIsLoading(false);
|
|
197
292
|
}
|
|
198
293
|
}, [client, table, JSON.stringify(options)]);
|
|
199
|
-
|
|
294
|
+
useEffect(() => {
|
|
295
|
+
if (!enabled) {
|
|
296
|
+
setIsLoading(false);
|
|
297
|
+
setData(null);
|
|
298
|
+
setTotal(0);
|
|
299
|
+
setError(null);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
200
302
|
fetchData();
|
|
201
|
-
}, [fetchData]);
|
|
303
|
+
}, [fetchData, enabled]);
|
|
202
304
|
return { data, total, isLoading, error, refetch: fetchData };
|
|
203
305
|
}
|
|
306
|
+
// src/hooks/use-record.ts
|
|
307
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
|
|
308
|
+
function useRecord(table, id, options) {
|
|
309
|
+
const client = useBasedClient();
|
|
310
|
+
const enabled = options?.enabled !== false && !!id;
|
|
311
|
+
const [data, setData] = useState2(null);
|
|
312
|
+
const [isLoading, setIsLoading] = useState2(enabled);
|
|
313
|
+
const [error, setError] = useState2(null);
|
|
314
|
+
const fetchData = useCallback2(async () => {
|
|
315
|
+
if (!id)
|
|
316
|
+
return;
|
|
317
|
+
setIsLoading(true);
|
|
318
|
+
setError(null);
|
|
319
|
+
try {
|
|
320
|
+
const res = await client.fetch(`/api/${table}/${encodeURIComponent(id)}`);
|
|
321
|
+
const json = await res.json();
|
|
322
|
+
if (!res.ok) {
|
|
323
|
+
if (res.status === 404) {
|
|
324
|
+
setData(null);
|
|
325
|
+
setError(null);
|
|
326
|
+
} else {
|
|
327
|
+
setError(json.error || { code: "UNKNOWN", message: "Request failed" });
|
|
328
|
+
setData(null);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
setData(json.data);
|
|
332
|
+
}
|
|
333
|
+
} catch (err) {
|
|
334
|
+
setError({ code: "NETWORK_ERROR", message: err.message });
|
|
335
|
+
setData(null);
|
|
336
|
+
} finally {
|
|
337
|
+
setIsLoading(false);
|
|
338
|
+
}
|
|
339
|
+
}, [client, table, id]);
|
|
340
|
+
useEffect2(() => {
|
|
341
|
+
if (!enabled) {
|
|
342
|
+
setIsLoading(false);
|
|
343
|
+
setData(null);
|
|
344
|
+
setError(null);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
fetchData();
|
|
348
|
+
}, [fetchData, enabled]);
|
|
349
|
+
return { data, isLoading, error, refetch: fetchData };
|
|
350
|
+
}
|
|
204
351
|
// src/hooks/use-mutation.ts
|
|
205
|
-
import { useState as useState3, useCallback as
|
|
352
|
+
import { useState as useState3, useCallback as useCallback3 } from "react";
|
|
206
353
|
function useMutation(table, operation) {
|
|
207
354
|
const client = useBasedClient();
|
|
208
355
|
const [isLoading, setIsLoading] = useState3(false);
|
|
209
356
|
const [error, setError] = useState3(null);
|
|
210
|
-
const mutate =
|
|
357
|
+
const mutate = useCallback3(async (data) => {
|
|
211
358
|
setIsLoading(true);
|
|
212
359
|
setError(null);
|
|
213
360
|
try {
|
|
@@ -256,6 +403,7 @@ function useMutation(table, operation) {
|
|
|
256
403
|
}
|
|
257
404
|
export {
|
|
258
405
|
useUser,
|
|
406
|
+
useRecord,
|
|
259
407
|
useQuery,
|
|
260
408
|
useMutation,
|
|
261
409
|
useBasedClient,
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
|
+
export interface StorageAdapter {
|
|
2
|
+
getItem(key: string): string | null | Promise<string | null>;
|
|
3
|
+
setItem(key: string, value: string): void | Promise<void>;
|
|
4
|
+
removeItem(key: string): void | Promise<void>;
|
|
5
|
+
}
|
|
1
6
|
export interface BasedClientOptions {
|
|
2
7
|
url: string;
|
|
3
8
|
anonKey: string;
|
|
9
|
+
/**
|
|
10
|
+
* Where to persist the session. Defaults to `localStorage` in browsers,
|
|
11
|
+
* in-memory elsewhere. Pass `false` to disable persistence entirely.
|
|
12
|
+
*/
|
|
13
|
+
storage?: StorageAdapter | false;
|
|
14
|
+
/**
|
|
15
|
+
* Key used for storage. Defaults to `based.session`.
|
|
16
|
+
*/
|
|
17
|
+
storageKey?: string;
|
|
4
18
|
}
|
|
5
19
|
export interface AuthUser {
|
|
6
20
|
id: string;
|
|
@@ -10,6 +24,11 @@ export interface AuthState {
|
|
|
10
24
|
user: AuthUser | null;
|
|
11
25
|
accessToken: string | null;
|
|
12
26
|
refreshToken: string | null;
|
|
27
|
+
/**
|
|
28
|
+
* True during the initial session rehydration. Use this to avoid flashing
|
|
29
|
+
* a "logged out" UI before the stored session has been validated.
|
|
30
|
+
*/
|
|
31
|
+
isLoading: boolean;
|
|
13
32
|
}
|
|
14
33
|
export interface BasedClient {
|
|
15
34
|
auth: {
|
|
@@ -17,15 +36,29 @@ export interface BasedClient {
|
|
|
17
36
|
signIn(email: string, password: string): Promise<AuthUser>;
|
|
18
37
|
signOut(): Promise<void>;
|
|
19
38
|
refreshSession(): Promise<boolean>;
|
|
39
|
+
/**
|
|
40
|
+
* Returns the current authenticated user by calling `/auth/me`.
|
|
41
|
+
*/
|
|
42
|
+
getUser(): Promise<AuthUser | null>;
|
|
20
43
|
};
|
|
21
44
|
fetch(path: string, init?: RequestInit): Promise<Response>;
|
|
22
45
|
subscribe(listener: () => void): () => void;
|
|
23
46
|
getState(): AuthState;
|
|
47
|
+
/**
|
|
48
|
+
* Resolves when the client finishes restoring a stored session (if any).
|
|
49
|
+
* Safe to await multiple times.
|
|
50
|
+
*/
|
|
51
|
+
ready(): Promise<void>;
|
|
24
52
|
}
|
|
25
53
|
export interface QueryOptions<T = Record<string, unknown>> {
|
|
26
54
|
filter?: Partial<Record<keyof T, string | number | boolean>>;
|
|
27
55
|
limit?: number;
|
|
28
56
|
offset?: number;
|
|
57
|
+
/**
|
|
58
|
+
* When false, the query won't fire. Useful for gating queries behind auth:
|
|
59
|
+
* `useQuery("notes", { enabled: !!user })`. Defaults to true.
|
|
60
|
+
*/
|
|
61
|
+
enabled?: boolean;
|
|
29
62
|
}
|
|
30
63
|
export interface QueryResult<T = unknown> {
|
|
31
64
|
data: T[] | null;
|
|
@@ -37,6 +70,21 @@ export interface QueryResult<T = unknown> {
|
|
|
37
70
|
} | null;
|
|
38
71
|
refetch: () => void;
|
|
39
72
|
}
|
|
73
|
+
export interface RecordOptions {
|
|
74
|
+
/**
|
|
75
|
+
* When false, the query won't fire.
|
|
76
|
+
*/
|
|
77
|
+
enabled?: boolean;
|
|
78
|
+
}
|
|
79
|
+
export interface RecordResult<T = unknown> {
|
|
80
|
+
data: T | null;
|
|
81
|
+
isLoading: boolean;
|
|
82
|
+
error: {
|
|
83
|
+
code: string;
|
|
84
|
+
message: string;
|
|
85
|
+
} | null;
|
|
86
|
+
refetch: () => void;
|
|
87
|
+
}
|
|
40
88
|
export interface MutationResult<T = unknown> {
|
|
41
89
|
mutate: (data: Partial<T> & Record<string, unknown>) => Promise<T>;
|
|
42
90
|
isLoading: boolean;
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|