@basictech/react 0.7.0-beta.5 → 0.7.0-beta.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/.turbo/turbo-build.log +10 -10
- package/changelog.md +12 -0
- package/dist/index.d.mts +266 -13
- package/dist/index.d.ts +266 -13
- package/dist/index.js +645 -184
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +625 -172
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/readme.md +203 -209
- package/src/AuthContext.tsx +197 -54
- package/src/core/db/RemoteCollection.ts +294 -0
- package/src/core/db/RemoteDB.ts +40 -0
- package/src/core/db/index.ts +7 -0
- package/src/core/db/types.ts +128 -0
- package/src/index.ts +25 -9
- package/src/sync/index.ts +133 -54
- package/src/db.ts +0 -55
package/readme.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @basictech/react
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
React SDK for [Basic](https://basic.tech) - add authentication and real-time database to your React app in minutes.
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -8,298 +8,292 @@ A React package for integrating Basic authentication and database functionality
|
|
|
8
8
|
npm install @basictech/react
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
##
|
|
11
|
+
## Quick Start
|
|
12
12
|
|
|
13
|
-
### 1.
|
|
13
|
+
### 1. Create a Schema
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
Create a `basic.config.ts` file with your project configuration:
|
|
16
16
|
|
|
17
17
|
```typescript
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
tables: {
|
|
22
|
-
todos: {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
},
|
|
28
|
-
title: {
|
|
29
|
-
type: "string",
|
|
30
|
-
indexed: true
|
|
31
|
-
},
|
|
32
|
-
completed: {
|
|
33
|
-
type: "boolean",
|
|
34
|
-
indexed: true
|
|
35
|
-
}
|
|
18
|
+
export const schema = {
|
|
19
|
+
project_id: "YOUR_PROJECT_ID",
|
|
20
|
+
version: 1,
|
|
21
|
+
tables: {
|
|
22
|
+
todos: {
|
|
23
|
+
type: "collection",
|
|
24
|
+
fields: {
|
|
25
|
+
title: { type: "string", indexed: true },
|
|
26
|
+
completed: { type: "boolean", indexed: true }
|
|
36
27
|
}
|
|
37
28
|
}
|
|
38
|
-
}
|
|
29
|
+
}
|
|
39
30
|
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### 2. Add the Provider
|
|
40
34
|
|
|
35
|
+
Wrap your app with `BasicProvider`:
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { BasicProvider } from '@basictech/react'
|
|
39
|
+
import { schema } from './basic.config'
|
|
41
40
|
|
|
42
41
|
function App() {
|
|
43
42
|
return (
|
|
44
|
-
<BasicProvider
|
|
45
|
-
|
|
43
|
+
<BasicProvider schema={schema}>
|
|
44
|
+
<YourApp />
|
|
46
45
|
</BasicProvider>
|
|
47
|
-
)
|
|
46
|
+
)
|
|
48
47
|
}
|
|
49
|
-
|
|
50
|
-
export default App;
|
|
51
48
|
```
|
|
52
49
|
|
|
53
|
-
|
|
50
|
+
### 3. Use the Hook
|
|
54
51
|
|
|
55
|
-
|
|
52
|
+
Access auth and database in any component:
|
|
56
53
|
|
|
57
|
-
|
|
54
|
+
```tsx
|
|
55
|
+
import { useBasic, useQuery } from '@basictech/react'
|
|
58
56
|
|
|
59
|
-
|
|
60
|
-
|
|
57
|
+
function TodoList() {
|
|
58
|
+
const { db, isSignedIn, signIn, signOut, user } = useBasic()
|
|
59
|
+
|
|
60
|
+
// Live query - automatically updates when data changes
|
|
61
|
+
const todos = useQuery(() => db.collection('todos').getAll())
|
|
61
62
|
|
|
62
|
-
|
|
63
|
-
|
|
63
|
+
const addTodo = async () => {
|
|
64
|
+
await db.collection('todos').add({
|
|
65
|
+
title: 'New todo',
|
|
66
|
+
completed: false
|
|
67
|
+
})
|
|
68
|
+
}
|
|
64
69
|
|
|
65
70
|
if (!isSignedIn) {
|
|
66
|
-
return <button onClick={
|
|
71
|
+
return <button onClick={signIn}>Sign In</button>
|
|
67
72
|
}
|
|
68
73
|
|
|
69
74
|
return (
|
|
70
75
|
<div>
|
|
71
|
-
<
|
|
72
|
-
<button onClick={
|
|
76
|
+
<p>Welcome, {user?.email}</p>
|
|
77
|
+
<button onClick={addTodo}>Add Todo</button>
|
|
78
|
+
<ul>
|
|
79
|
+
{todos?.map(todo => (
|
|
80
|
+
<li key={todo.id}>{todo.title}</li>
|
|
81
|
+
))}
|
|
82
|
+
</ul>
|
|
83
|
+
<button onClick={signOut}>Sign Out</button>
|
|
73
84
|
</div>
|
|
74
|
-
)
|
|
85
|
+
)
|
|
75
86
|
}
|
|
76
87
|
```
|
|
77
88
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
For custom OAuth flows (mobile apps, server-side redirects, etc.), you can manually handle authorization codes:
|
|
89
|
+
---
|
|
81
90
|
|
|
82
|
-
|
|
83
|
-
import { useBasic } from '@basictech/react';
|
|
84
|
-
|
|
85
|
-
function CustomAuthComponent() {
|
|
86
|
-
const { signinWithCode } = useBasic();
|
|
87
|
-
|
|
88
|
-
const handleOAuthCode = async (code: string, state?: string) => {
|
|
89
|
-
const result = await signinWithCode(code, state);
|
|
90
|
-
|
|
91
|
-
if (result.success) {
|
|
92
|
-
console.log('Successfully authenticated!');
|
|
93
|
-
// Redirect to authenticated area
|
|
94
|
-
} else {
|
|
95
|
-
console.error('Authentication failed:', result.error);
|
|
96
|
-
// Handle error
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
|
|
100
|
-
// Example: Handle OAuth code from URL parameters
|
|
101
|
-
useEffect(() => {
|
|
102
|
-
const urlParams = new URLSearchParams(window.location.search);
|
|
103
|
-
const code = urlParams.get('code');
|
|
104
|
-
const state = urlParams.get('state');
|
|
105
|
-
|
|
106
|
-
if (code) {
|
|
107
|
-
handleOAuthCode(code, state || undefined);
|
|
108
|
-
}
|
|
109
|
-
}, []);
|
|
91
|
+
## API Reference
|
|
110
92
|
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
```
|
|
93
|
+
### `<BasicProvider>`
|
|
114
94
|
|
|
115
|
-
|
|
95
|
+
Root provider component. Must wrap your entire app.
|
|
116
96
|
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
if (code) {
|
|
125
|
-
signinWithCode(code, state || undefined);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
97
|
+
```tsx
|
|
98
|
+
<BasicProvider
|
|
99
|
+
schema={schema} // Required: Your Basic schema
|
|
100
|
+
debug={false} // Optional: Enable console logging
|
|
101
|
+
dbMode="sync" // Optional: "sync" (default) or "remote"
|
|
102
|
+
/>
|
|
128
103
|
```
|
|
129
104
|
|
|
130
|
-
####
|
|
105
|
+
#### Props
|
|
131
106
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (result.success) {
|
|
138
|
-
// Redirect to authenticated area
|
|
139
|
-
window.location.href = '/dashboard';
|
|
140
|
-
} else {
|
|
141
|
-
// Show error message
|
|
142
|
-
alert(`Authentication failed: ${result.error}`);
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
```
|
|
107
|
+
| Prop | Type | Default | Description |
|
|
108
|
+
|------|------|---------|-------------|
|
|
109
|
+
| `schema` | `object` | required | Schema with `project_id` and `tables` |
|
|
110
|
+
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
111
|
+
| `dbMode` | `"sync" \| "remote"` | `"sync"` | Database mode |
|
|
146
112
|
|
|
147
|
-
|
|
113
|
+
#### Database Modes
|
|
148
114
|
|
|
149
|
-
|
|
115
|
+
- **`sync`** - Local-first with IndexedDB + real-time sync via WebSocket
|
|
116
|
+
- **`remote`** - Direct REST API calls (no local storage)
|
|
150
117
|
|
|
151
|
-
|
|
152
|
-
import { useBasic } from '@basictech/react';
|
|
153
|
-
|
|
154
|
-
function CustomAuthComponent() {
|
|
155
|
-
const { getSignInLink } = useBasic();
|
|
118
|
+
---
|
|
156
119
|
|
|
157
|
-
|
|
158
|
-
// Use a custom redirect URI
|
|
159
|
-
const signInUrl = getSignInLink('https://yourapp.com/auth/callback');
|
|
160
|
-
window.location.href = signInUrl;
|
|
161
|
-
};
|
|
120
|
+
### `useBasic()`
|
|
162
121
|
|
|
163
|
-
|
|
164
|
-
// Use default redirect (current page URL)
|
|
165
|
-
const signInUrl = getSignInLink();
|
|
166
|
-
window.location.href = signInUrl;
|
|
167
|
-
};
|
|
122
|
+
Main hook for accessing auth and database.
|
|
168
123
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
)
|
|
179
|
-
|
|
124
|
+
```tsx
|
|
125
|
+
const {
|
|
126
|
+
// Auth state
|
|
127
|
+
isReady, // boolean - SDK initialized
|
|
128
|
+
isSignedIn, // boolean - User authenticated
|
|
129
|
+
user, // { id, email, ... } | null
|
|
130
|
+
|
|
131
|
+
// Auth methods
|
|
132
|
+
signIn, // () => void - Redirect to login
|
|
133
|
+
signOut, // () => void - Clear session
|
|
134
|
+
signInWithCode, // (code, state?) => Promise - Manual OAuth
|
|
135
|
+
getSignInUrl, // (redirectUri?) => string - Get OAuth URL
|
|
136
|
+
getToken, // () => Promise<string> - Get access token
|
|
137
|
+
|
|
138
|
+
// Database
|
|
139
|
+
db, // Database instance
|
|
140
|
+
dbStatus, // "OFFLINE" | "CONNECTING" | "ONLINE" | "SYNCING"
|
|
141
|
+
dbMode, // "sync" | "remote"
|
|
142
|
+
} = useBasic()
|
|
180
143
|
```
|
|
181
144
|
|
|
182
|
-
|
|
145
|
+
---
|
|
183
146
|
|
|
184
|
-
|
|
185
|
-
- **Multi-Domain**: Redirect to different domains based on context
|
|
186
|
-
- **Testing**: Use test-specific callback URLs
|
|
187
|
-
- **Subdomains**: Redirect to specific subdomains
|
|
147
|
+
### `useQuery()`
|
|
188
148
|
|
|
189
|
-
|
|
149
|
+
Live query hook - automatically re-renders when data changes.
|
|
190
150
|
|
|
151
|
+
```tsx
|
|
152
|
+
import { useQuery } from '@basictech/react'
|
|
191
153
|
|
|
192
|
-
|
|
154
|
+
// Get all items
|
|
155
|
+
const todos = useQuery(() => db.collection('todos').getAll())
|
|
193
156
|
|
|
194
|
-
|
|
157
|
+
// With type safety
|
|
158
|
+
interface Todo {
|
|
159
|
+
id: string
|
|
160
|
+
title: string
|
|
161
|
+
completed: boolean
|
|
162
|
+
}
|
|
163
|
+
const todos = useQuery(() => db.collection<Todo>('todos').getAll())
|
|
164
|
+
```
|
|
195
165
|
|
|
196
|
-
|
|
197
|
-
- `schema` (required): Object - The schema definition for your database.
|
|
198
|
-
- `debug` (optional): Boolean - Enable debug mode for additional logging. Default is `false`.
|
|
199
|
-
- `children` (required): React.ReactNode - The child components to be wrapped by the provider.
|
|
166
|
+
> **Note:** Only works in `sync` mode. In `remote` mode, use manual fetching.
|
|
200
167
|
|
|
168
|
+
---
|
|
201
169
|
|
|
170
|
+
### Database Methods
|
|
202
171
|
|
|
172
|
+
#### `db.collection(name)`
|
|
203
173
|
|
|
204
|
-
|
|
174
|
+
Access a collection by name.
|
|
205
175
|
|
|
206
|
-
|
|
176
|
+
```tsx
|
|
177
|
+
const { db } = useBasic()
|
|
178
|
+
const todos = db.collection('todos')
|
|
179
|
+
```
|
|
207
180
|
|
|
208
|
-
|
|
181
|
+
#### Collection Methods
|
|
209
182
|
|
|
210
|
-
|
|
211
|
-
|
|
183
|
+
| Method | Returns | Description |
|
|
184
|
+
|--------|---------|-------------|
|
|
185
|
+
| `getAll()` | `Promise<T[]>` | Get all records |
|
|
186
|
+
| `get(id)` | `Promise<T \| null>` | Get one record by ID |
|
|
187
|
+
| `add(data)` | `Promise<T>` | Create new record (returns with ID) |
|
|
188
|
+
| `put(data)` | `Promise<T>` | Upsert record (requires ID) |
|
|
189
|
+
| `update(id, data)` | `Promise<T \| null>` | Partial update |
|
|
190
|
+
| `delete(id)` | `Promise<boolean>` | Delete record |
|
|
191
|
+
| `filter(fn)` | `Promise<T[]>` | Filter with predicate |
|
|
212
192
|
|
|
213
|
-
|
|
214
|
-
const data = useQuery(db.collection('data').getAll())
|
|
193
|
+
#### Examples
|
|
215
194
|
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
})
|
|
224
|
-
}
|
|
225
|
-
</div>
|
|
226
|
-
);
|
|
227
|
-
}
|
|
228
|
-
```
|
|
229
|
-
Notes:
|
|
230
|
-
- must pass in a db function, ie `db.collection('todos').getAll()`
|
|
231
|
-
- default will be empty array (this might change in the future)
|
|
195
|
+
```tsx
|
|
196
|
+
// Create
|
|
197
|
+
const todo = await db.collection('todos').add({
|
|
198
|
+
title: 'Buy milk',
|
|
199
|
+
completed: false
|
|
200
|
+
})
|
|
201
|
+
console.log(todo.id) // Auto-generated ID
|
|
232
202
|
|
|
203
|
+
// Read
|
|
204
|
+
const allTodos = await db.collection('todos').getAll()
|
|
205
|
+
const oneTodo = await db.collection('todos').get('some-id')
|
|
233
206
|
|
|
234
|
-
|
|
207
|
+
// Update
|
|
208
|
+
await db.collection('todos').update('some-id', { completed: true })
|
|
235
209
|
|
|
236
|
-
|
|
210
|
+
// Delete
|
|
211
|
+
await db.collection('todos').delete('some-id')
|
|
237
212
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
- `signout()`: Function to sign out the user
|
|
242
|
-
- `signinWithCode(code: string, state?: string)`: Function to manually handle OAuth authorization codes
|
|
243
|
-
- `getSignInLink(redirectUri?: string)`: Function to generate OAuth sign-in URLs
|
|
244
|
-
- `db`: Object for database operations
|
|
213
|
+
// Filter
|
|
214
|
+
const incomplete = await db.collection('todos').filter(t => !t.completed)
|
|
215
|
+
```
|
|
245
216
|
|
|
246
|
-
|
|
247
|
-
- `code` (required): The OAuth authorization code received from the OAuth provider
|
|
248
|
-
- `state` (optional): The state parameter for CSRF protection validation
|
|
217
|
+
---
|
|
249
218
|
|
|
250
|
-
|
|
251
|
-
- `Promise<{ success: boolean, error?: string }>`: Returns success status and optional error message
|
|
219
|
+
## Advanced Usage
|
|
252
220
|
|
|
253
|
-
|
|
254
|
-
- `redirectUri` (optional): Custom redirect URI for OAuth flow. If not provided, defaults to current page URL
|
|
221
|
+
### Manual OAuth Flow
|
|
255
222
|
|
|
256
|
-
|
|
257
|
-
- `string`: Complete OAuth authorization URL
|
|
223
|
+
For custom OAuth handling (mobile apps, popups, etc.):
|
|
258
224
|
|
|
225
|
+
```tsx
|
|
226
|
+
const { signInWithCode, getSignInUrl } = useBasic()
|
|
259
227
|
|
|
228
|
+
// Get OAuth URL with custom redirect
|
|
229
|
+
const url = getSignInUrl('myapp://callback')
|
|
260
230
|
|
|
261
|
-
|
|
231
|
+
// Exchange code for session
|
|
232
|
+
const result = await signInWithCode(code, state)
|
|
233
|
+
if (result.success) {
|
|
234
|
+
console.log('Signed in!')
|
|
235
|
+
}
|
|
236
|
+
```
|
|
262
237
|
|
|
263
|
-
|
|
238
|
+
### Remote Mode
|
|
264
239
|
|
|
240
|
+
For server-rendered apps or when you don't need offline support:
|
|
265
241
|
|
|
266
|
-
|
|
242
|
+
```tsx
|
|
243
|
+
<BasicProvider schema={schema} dbMode="remote">
|
|
244
|
+
<App />
|
|
245
|
+
</BasicProvider>
|
|
246
|
+
```
|
|
267
247
|
|
|
268
|
-
|
|
269
|
-
-
|
|
270
|
-
-
|
|
271
|
-
- `
|
|
272
|
-
-
|
|
273
|
-
- `delete(id: string)`: deletes an item from the collection
|
|
248
|
+
In remote mode:
|
|
249
|
+
- Data is fetched via REST API
|
|
250
|
+
- No IndexedDB storage
|
|
251
|
+
- `useQuery` won't auto-update (use manual refresh)
|
|
252
|
+
- Requires authentication for all operations
|
|
274
253
|
|
|
275
|
-
|
|
254
|
+
### Error Handling
|
|
276
255
|
|
|
277
|
-
|
|
256
|
+
```tsx
|
|
257
|
+
import { NotAuthenticatedError } from '@basictech/react'
|
|
278
258
|
|
|
279
|
-
|
|
280
|
-
|
|
259
|
+
try {
|
|
260
|
+
await db.collection('todos').add({ title: 'Test' })
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (error instanceof NotAuthenticatedError) {
|
|
263
|
+
// User needs to sign in
|
|
264
|
+
signIn()
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
```
|
|
281
268
|
|
|
282
|
-
|
|
283
|
-
const { db } = useBasic();
|
|
269
|
+
---
|
|
284
270
|
|
|
285
|
-
|
|
286
|
-
await db.collection('todos').add({
|
|
287
|
-
title: 'test',
|
|
288
|
-
completed: false
|
|
289
|
-
})
|
|
290
|
-
}
|
|
271
|
+
## TypeScript
|
|
291
272
|
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
273
|
+
Full TypeScript support with generics:
|
|
274
|
+
|
|
275
|
+
```tsx
|
|
276
|
+
interface Todo {
|
|
277
|
+
id: string
|
|
278
|
+
title: string
|
|
279
|
+
completed: boolean
|
|
280
|
+
createdAt: number
|
|
297
281
|
}
|
|
298
282
|
|
|
283
|
+
// Type-safe collection
|
|
284
|
+
const todos = db.collection<Todo>('todos')
|
|
285
|
+
|
|
286
|
+
// All methods are typed
|
|
287
|
+
const todo = await todos.add({
|
|
288
|
+
title: 'Test',
|
|
289
|
+
completed: false,
|
|
290
|
+
createdAt: Date.now()
|
|
291
|
+
})
|
|
292
|
+
// todo is typed as Todo
|
|
299
293
|
```
|
|
300
294
|
|
|
295
|
+
---
|
|
296
|
+
|
|
301
297
|
## License
|
|
302
298
|
|
|
303
299
|
ISC
|
|
304
|
-
|
|
305
|
-
---
|