@basictech/react 0.8.0-beta.3 → 0.8.0-beta.4
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/changelog.md +6 -0
- package/dist/index.d.mts +31 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +537 -141
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +545 -142
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/readme.md +99 -97
- package/src/AuthContext.tsx +513 -415
- package/src/context.tsx +19 -1
- package/src/core/auth/AuthManager.ts +1235 -746
- package/src/sync/syncProtocol.js +23 -7
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -23,11 +23,11 @@ export const schema = {
|
|
|
23
23
|
type: "collection",
|
|
24
24
|
fields: {
|
|
25
25
|
title: { type: "string", indexed: true },
|
|
26
|
-
completed: { type: "boolean", indexed: true }
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
26
|
+
completed: { type: "boolean", indexed: true },
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
31
|
```
|
|
32
32
|
|
|
33
33
|
### 2. Add the Provider
|
|
@@ -35,15 +35,15 @@ export const schema = {
|
|
|
35
35
|
Wrap your app with `BasicProvider`:
|
|
36
36
|
|
|
37
37
|
```tsx
|
|
38
|
-
import { BasicProvider } from
|
|
39
|
-
import { schema } from
|
|
38
|
+
import { BasicProvider } from "@basictech/react";
|
|
39
|
+
import { schema } from "./basic.config";
|
|
40
40
|
|
|
41
41
|
function App() {
|
|
42
42
|
return (
|
|
43
43
|
<BasicProvider schema={schema}>
|
|
44
44
|
<YourApp />
|
|
45
45
|
</BasicProvider>
|
|
46
|
-
)
|
|
46
|
+
);
|
|
47
47
|
}
|
|
48
48
|
```
|
|
49
49
|
|
|
@@ -52,23 +52,23 @@ function App() {
|
|
|
52
52
|
Access auth and database in any component:
|
|
53
53
|
|
|
54
54
|
```tsx
|
|
55
|
-
import { useBasic, useQuery } from
|
|
55
|
+
import { useBasic, useQuery } from "@basictech/react";
|
|
56
56
|
|
|
57
57
|
function TodoList() {
|
|
58
|
-
const { db, isSignedIn, signIn, signOut, user } = useBasic()
|
|
59
|
-
|
|
58
|
+
const { db, isSignedIn, signIn, signOut, user } = useBasic();
|
|
59
|
+
|
|
60
60
|
// Live query - automatically updates when data changes
|
|
61
|
-
const todos = useQuery(() => db.collection(
|
|
61
|
+
const todos = useQuery(() => db.collection("todos").getAll());
|
|
62
62
|
|
|
63
63
|
const addTodo = async () => {
|
|
64
|
-
await db.collection(
|
|
65
|
-
title:
|
|
66
|
-
completed: false
|
|
67
|
-
})
|
|
68
|
-
}
|
|
64
|
+
await db.collection("todos").add({
|
|
65
|
+
title: "New todo",
|
|
66
|
+
completed: false,
|
|
67
|
+
});
|
|
68
|
+
};
|
|
69
69
|
|
|
70
70
|
if (!isSignedIn) {
|
|
71
|
-
return <button onClick={signIn}>Sign In</button
|
|
71
|
+
return <button onClick={signIn}>Sign In</button>;
|
|
72
72
|
}
|
|
73
73
|
|
|
74
74
|
return (
|
|
@@ -76,13 +76,13 @@ function TodoList() {
|
|
|
76
76
|
<p>Welcome, {user?.email}</p>
|
|
77
77
|
<button onClick={addTodo}>Add Todo</button>
|
|
78
78
|
<ul>
|
|
79
|
-
{todos?.map(todo => (
|
|
79
|
+
{todos?.map((todo) => (
|
|
80
80
|
<li key={todo.id}>{todo.title}</li>
|
|
81
81
|
))}
|
|
82
82
|
</ul>
|
|
83
83
|
<button onClick={signOut}>Sign Out</button>
|
|
84
84
|
</div>
|
|
85
|
-
)
|
|
85
|
+
);
|
|
86
86
|
}
|
|
87
87
|
```
|
|
88
88
|
|
|
@@ -96,21 +96,21 @@ Root provider component. Must wrap your entire app.
|
|
|
96
96
|
|
|
97
97
|
```tsx
|
|
98
98
|
<BasicProvider
|
|
99
|
-
schema={schema}
|
|
100
|
-
debug={false}
|
|
101
|
-
dbMode="sync"
|
|
102
|
-
devToolbar={false}
|
|
99
|
+
schema={schema} // Required: Your Basic schema
|
|
100
|
+
debug={false} // Optional: Enable console logging
|
|
101
|
+
dbMode="sync" // Optional: "sync" (default) or "remote"
|
|
102
|
+
devToolbar={false} // Optional: Floating dev status bar (localhost / dev / debug)
|
|
103
103
|
/>
|
|
104
104
|
```
|
|
105
105
|
|
|
106
106
|
#### Props
|
|
107
107
|
|
|
108
|
-
| Prop
|
|
109
|
-
|
|
110
|
-
| `schema`
|
|
111
|
-
| `debug`
|
|
112
|
-
| `dbMode`
|
|
113
|
-
| `devToolbar` | `boolean`
|
|
108
|
+
| Prop | Type | Default | Description |
|
|
109
|
+
| ------------ | -------------------- | -------- | --------------------------------------------------------------------------------------------------- |
|
|
110
|
+
| `schema` | `object` | required | Schema with `project_id` and `tables` |
|
|
111
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
112
|
+
| `dbMode` | `"sync" \| "remote"` | `"sync"` | Database mode |
|
|
113
|
+
| `devToolbar` | `boolean` | `false` | Show the Basic dev toolbar (only when `localhost`, `NODE_ENV === "development"`, or `debug={true}`) |
|
|
114
114
|
|
|
115
115
|
#### Database Modes
|
|
116
116
|
|
|
@@ -126,40 +126,40 @@ Main hook for accessing auth and database.
|
|
|
126
126
|
```tsx
|
|
127
127
|
const {
|
|
128
128
|
// Auth state
|
|
129
|
-
isReady,
|
|
130
|
-
isSignedIn,
|
|
131
|
-
user,
|
|
132
|
-
|
|
129
|
+
isReady, // boolean - SDK initialized
|
|
130
|
+
isSignedIn, // boolean - Session is valid
|
|
131
|
+
user, // { id, email, ... } | null (may be null briefly during profile refresh/recovery)
|
|
132
|
+
|
|
133
133
|
// Auth methods
|
|
134
|
-
signIn,
|
|
135
|
-
signOut,
|
|
134
|
+
signIn, // () => void - Redirect to login
|
|
135
|
+
signOut, // () => void - Clear session
|
|
136
136
|
signInWithCode, // (code, state?) => Promise - Manual OAuth
|
|
137
|
-
getSignInUrl,
|
|
138
|
-
getToken,
|
|
139
|
-
|
|
137
|
+
getSignInUrl, // (redirectUri?) => string - Get OAuth URL
|
|
138
|
+
getToken, // () => Promise<string> - Get access token
|
|
139
|
+
|
|
140
140
|
// Database
|
|
141
|
-
db,
|
|
142
|
-
dbStatus,
|
|
143
|
-
dbMode,
|
|
141
|
+
db, // Database instance
|
|
142
|
+
dbStatus, // DBStatus - see below
|
|
143
|
+
dbMode, // "sync" | "remote"
|
|
144
144
|
|
|
145
145
|
// Dev / schema snapshot (for custom tooling)
|
|
146
|
-
devInfo,
|
|
147
|
-
refreshSchemaStatus,
|
|
148
|
-
} = useBasic()
|
|
146
|
+
devInfo, // BasicSchemaDevInfo | null — local vs remote schema status
|
|
147
|
+
refreshSchemaStatus, // () => Promise<void> — re-fetch schema status from API
|
|
148
|
+
} = useBasic();
|
|
149
149
|
```
|
|
150
150
|
|
|
151
151
|
#### `DBStatus` (sync connection state)
|
|
152
152
|
|
|
153
153
|
When `dbMode === "sync"`, `dbStatus` is one of:
|
|
154
154
|
|
|
155
|
-
| Value
|
|
156
|
-
|
|
157
|
-
| `LOADING`
|
|
158
|
-
| `OFFLINE`
|
|
159
|
-
| `CONNECTING`
|
|
160
|
-
| `ONLINE`
|
|
161
|
-
| `SYNCING`
|
|
162
|
-
| `ERROR`
|
|
155
|
+
| Value | Description |
|
|
156
|
+
| ------------------ | ----------------------------------------------------------------------------------------------------------------- |
|
|
157
|
+
| `LOADING` | SDK initializing |
|
|
158
|
+
| `OFFLINE` | Not connected |
|
|
159
|
+
| `CONNECTING` | Connecting to sync server |
|
|
160
|
+
| `ONLINE` | Connected and idle |
|
|
161
|
+
| `SYNCING` | Syncing data |
|
|
162
|
+
| `ERROR` | Sync error |
|
|
163
163
|
| `ERROR_WILL_RETRY` | Sync error but client will retry (e.g. expired token). Use this to show "Reconnecting…" or trigger token refresh. |
|
|
164
164
|
|
|
165
165
|
Import the enum for comparisons: `import { useBasic, DBStatus } from '@basictech/react'`.
|
|
@@ -181,12 +181,12 @@ A small floating bar (similar in spirit to Next.js dev indicators) shows **auth*
|
|
|
181
181
|
**Option B — place the component yourself** (must be under `BasicProvider`; respects the same visibility rules, or pass `debug` to force):
|
|
182
182
|
|
|
183
183
|
```tsx
|
|
184
|
-
import { BasicDevToolbar } from
|
|
184
|
+
import { BasicDevToolbar } from "@basictech/react";
|
|
185
185
|
|
|
186
186
|
<BasicProvider schema={schema}>
|
|
187
187
|
<App />
|
|
188
188
|
<BasicDevToolbar />
|
|
189
|
-
</BasicProvider
|
|
189
|
+
</BasicProvider>;
|
|
190
190
|
```
|
|
191
191
|
|
|
192
192
|
The expanded panel includes **Refresh schema** (re-runs the remote schema check) and **Copy debug info** (JSON snapshot **without** raw access tokens). You can also read `devInfo` and call `refreshSchemaStatus()` from `useBasic()` for your own UI.
|
|
@@ -198,18 +198,18 @@ The expanded panel includes **Refresh schema** (re-runs the remote schema check)
|
|
|
198
198
|
Live query hook - automatically re-renders when data changes.
|
|
199
199
|
|
|
200
200
|
```tsx
|
|
201
|
-
import { useQuery } from
|
|
201
|
+
import { useQuery } from "@basictech/react";
|
|
202
202
|
|
|
203
203
|
// Get all items
|
|
204
|
-
const todos = useQuery(() => db.collection(
|
|
204
|
+
const todos = useQuery(() => db.collection("todos").getAll());
|
|
205
205
|
|
|
206
206
|
// With type safety
|
|
207
207
|
interface Todo {
|
|
208
|
-
id: string
|
|
209
|
-
title: string
|
|
210
|
-
completed: boolean
|
|
208
|
+
id: string;
|
|
209
|
+
title: string;
|
|
210
|
+
completed: boolean;
|
|
211
211
|
}
|
|
212
|
-
const todos = useQuery(() => db.collection<Todo>(
|
|
212
|
+
const todos = useQuery(() => db.collection<Todo>("todos").getAll());
|
|
213
213
|
```
|
|
214
214
|
|
|
215
215
|
> **Note:** Only works in `sync` mode. In `remote` mode, use manual fetching.
|
|
@@ -223,44 +223,44 @@ const todos = useQuery(() => db.collection<Todo>('todos').getAll())
|
|
|
223
223
|
Access a collection by name.
|
|
224
224
|
|
|
225
225
|
```tsx
|
|
226
|
-
const { db } = useBasic()
|
|
227
|
-
const todos = db.collection(
|
|
226
|
+
const { db } = useBasic();
|
|
227
|
+
const todos = db.collection("todos");
|
|
228
228
|
```
|
|
229
229
|
|
|
230
230
|
#### Collection Methods
|
|
231
231
|
|
|
232
|
-
| Method
|
|
233
|
-
|
|
234
|
-
| `getAll()`
|
|
235
|
-
| `get(id)`
|
|
236
|
-
| `add(data)`
|
|
237
|
-
| `put(data)`
|
|
238
|
-
| `update(id, data)` | `Promise<T \| null>` | Partial update
|
|
239
|
-
| `delete(id)`
|
|
240
|
-
| `filter(fn)`
|
|
232
|
+
| Method | Returns | Description |
|
|
233
|
+
| ------------------ | -------------------- | ----------------------------------- |
|
|
234
|
+
| `getAll()` | `Promise<T[]>` | Get all records |
|
|
235
|
+
| `get(id)` | `Promise<T \| null>` | Get one record by ID |
|
|
236
|
+
| `add(data)` | `Promise<T>` | Create new record (returns with ID) |
|
|
237
|
+
| `put(data)` | `Promise<T>` | Upsert record (requires ID) |
|
|
238
|
+
| `update(id, data)` | `Promise<T \| null>` | Partial update |
|
|
239
|
+
| `delete(id)` | `Promise<boolean>` | Delete record |
|
|
240
|
+
| `filter(fn)` | `Promise<T[]>` | Filter with predicate |
|
|
241
241
|
|
|
242
242
|
#### Examples
|
|
243
243
|
|
|
244
244
|
```tsx
|
|
245
245
|
// Create
|
|
246
|
-
const todo = await db.collection(
|
|
247
|
-
title:
|
|
248
|
-
completed: false
|
|
249
|
-
})
|
|
250
|
-
console.log(todo.id) // Auto-generated ID
|
|
246
|
+
const todo = await db.collection("todos").add({
|
|
247
|
+
title: "Buy milk",
|
|
248
|
+
completed: false,
|
|
249
|
+
});
|
|
250
|
+
console.log(todo.id); // Auto-generated ID
|
|
251
251
|
|
|
252
252
|
// Read
|
|
253
|
-
const allTodos = await db.collection(
|
|
254
|
-
const oneTodo = await db.collection(
|
|
253
|
+
const allTodos = await db.collection("todos").getAll();
|
|
254
|
+
const oneTodo = await db.collection("todos").get("some-id");
|
|
255
255
|
|
|
256
256
|
// Update
|
|
257
|
-
await db.collection(
|
|
257
|
+
await db.collection("todos").update("some-id", { completed: true });
|
|
258
258
|
|
|
259
259
|
// Delete
|
|
260
|
-
await db.collection(
|
|
260
|
+
await db.collection("todos").delete("some-id");
|
|
261
261
|
|
|
262
262
|
// Filter
|
|
263
|
-
const incomplete = await db.collection(
|
|
263
|
+
const incomplete = await db.collection("todos").filter((t) => !t.completed);
|
|
264
264
|
```
|
|
265
265
|
|
|
266
266
|
---
|
|
@@ -272,15 +272,15 @@ const incomplete = await db.collection('todos').filter(t => !t.completed)
|
|
|
272
272
|
For custom OAuth handling (mobile apps, popups, etc.):
|
|
273
273
|
|
|
274
274
|
```tsx
|
|
275
|
-
const { signInWithCode, getSignInUrl } = useBasic()
|
|
275
|
+
const { signInWithCode, getSignInUrl } = useBasic();
|
|
276
276
|
|
|
277
277
|
// Get OAuth URL with custom redirect
|
|
278
|
-
const url = getSignInUrl(
|
|
278
|
+
const url = getSignInUrl("myapp://callback");
|
|
279
279
|
|
|
280
280
|
// Exchange code for session
|
|
281
|
-
const result = await signInWithCode(code, state)
|
|
281
|
+
const result = await signInWithCode(code, state);
|
|
282
282
|
if (result.success) {
|
|
283
|
-
console.log(
|
|
283
|
+
console.log("Signed in!");
|
|
284
284
|
}
|
|
285
285
|
```
|
|
286
286
|
|
|
@@ -295,22 +295,24 @@ For server-rendered apps or when you don't need offline support:
|
|
|
295
295
|
```
|
|
296
296
|
|
|
297
297
|
In remote mode:
|
|
298
|
+
|
|
298
299
|
- Data is fetched via REST API
|
|
299
300
|
- No IndexedDB storage
|
|
300
301
|
- `useQuery` won't auto-update (use manual refresh)
|
|
301
302
|
- Requires authentication for all operations
|
|
303
|
+
- Auth request failures are surfaced to the caller; recoverable 401s do not automatically sign the user out
|
|
302
304
|
|
|
303
305
|
### Error Handling
|
|
304
306
|
|
|
305
307
|
```tsx
|
|
306
|
-
import { NotAuthenticatedError } from
|
|
308
|
+
import { NotAuthenticatedError } from "@basictech/react";
|
|
307
309
|
|
|
308
310
|
try {
|
|
309
|
-
await db.collection(
|
|
311
|
+
await db.collection("todos").add({ title: "Test" });
|
|
310
312
|
} catch (error) {
|
|
311
313
|
if (error instanceof NotAuthenticatedError) {
|
|
312
314
|
// User needs to sign in
|
|
313
|
-
signIn()
|
|
315
|
+
signIn();
|
|
314
316
|
}
|
|
315
317
|
}
|
|
316
318
|
```
|
|
@@ -323,21 +325,21 @@ Full TypeScript support with generics:
|
|
|
323
325
|
|
|
324
326
|
```tsx
|
|
325
327
|
interface Todo {
|
|
326
|
-
id: string
|
|
327
|
-
title: string
|
|
328
|
-
completed: boolean
|
|
329
|
-
createdAt: number
|
|
328
|
+
id: string;
|
|
329
|
+
title: string;
|
|
330
|
+
completed: boolean;
|
|
331
|
+
createdAt: number;
|
|
330
332
|
}
|
|
331
333
|
|
|
332
334
|
// Type-safe collection
|
|
333
|
-
const todos = db.collection<Todo>(
|
|
335
|
+
const todos = db.collection<Todo>("todos");
|
|
334
336
|
|
|
335
337
|
// All methods are typed
|
|
336
338
|
const todo = await todos.add({
|
|
337
|
-
title:
|
|
339
|
+
title: "Test",
|
|
338
340
|
completed: false,
|
|
339
|
-
createdAt: Date.now()
|
|
340
|
-
})
|
|
341
|
+
createdAt: Date.now(),
|
|
342
|
+
});
|
|
341
343
|
// todo is typed as Todo
|
|
342
344
|
```
|
|
343
345
|
|