@basictech/nextjs 0.6.0-beta.6 → 0.6.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/readme.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @basictech/nextjs
2
2
 
3
- A Next.js package for integrating Basic authentication and database functionality into your Next.js applications.
3
+ Next.js SDK for [Basic](https://basic.tech) - add authentication and real-time database to your Next.js app.
4
4
 
5
5
  ## Installation
6
6
 
@@ -8,97 +8,667 @@ A Next.js package for integrating Basic authentication and database functionalit
8
8
  npm install @basictech/nextjs
9
9
  ```
10
10
 
11
- ## Usage
11
+ > **Note:** `@basictech/react` is included as a dependency - no need to install separately.
12
12
 
13
- ### 1. Wrap your application with BasicProvider
13
+ ---
14
14
 
15
- In your `_app.tsx` or `layout.tsx` file, wrap your application with the `BasicProvider`:
15
+ ## Quick Start
16
+
17
+ ### 1. Create a Schema
18
+
19
+ Create a `basic.config.ts` file:
16
20
 
17
21
  ```typescript
18
- import { BasicProvider } from '@basictech/nextjs';
22
+ export const schema = {
23
+ project_id: "YOUR_PROJECT_ID",
24
+ version: 1,
25
+ tables: {
26
+ todos: {
27
+ type: "collection",
28
+ fields: {
29
+ title: { type: "string", indexed: true },
30
+ completed: { type: "boolean", indexed: true }
31
+ }
32
+ }
33
+ }
34
+ }
35
+ ```
36
+
37
+ ### 2. Create a Client Provider
38
+
39
+ Create `app/providers.tsx`:
19
40
 
20
- function MyApp({ Component, pageProps }) {
41
+ ```tsx
42
+ 'use client'
43
+
44
+ import { BasicProvider } from '@basictech/react'
45
+ import { schema } from '../basic.config'
46
+
47
+ export function Providers({ children }: { children: React.ReactNode }) {
21
48
  return (
22
- <BasicProvider project_id="YOUR_PROJECT_ID">
23
- <Component {...pageProps} />
49
+ <BasicProvider schema={schema}>
50
+ {children}
24
51
  </BasicProvider>
25
- );
52
+ )
26
53
  }
54
+ ```
55
+
56
+ ### 3. Add to Layout
57
+
58
+ Update `app/layout.tsx`:
59
+
60
+ ```tsx
61
+ import { Providers } from './providers'
27
62
 
28
- export default MyApp;
63
+ export default function RootLayout({ children }) {
64
+ return (
65
+ <html>
66
+ <body>
67
+ <Providers>{children}</Providers>
68
+ </body>
69
+ </html>
70
+ )
71
+ }
29
72
  ```
30
73
 
31
- Replace `YOUR_PROJECT_ID` with your actual Basic project ID.
74
+ ### 4. Use in Client Components
32
75
 
33
- ### 2. Use the useBasic hook
76
+ ```tsx
77
+ 'use client'
34
78
 
35
- In your components, you can use the `useBasic` hook to access authentication and database functionality:
79
+ import { useBasic, useQuery } from '@basictech/react'
36
80
 
37
- ```typescript
38
- import { useBasic } from '@basictech/nextjs';
81
+ export function TodoList() {
82
+ const { db, isSignedIn, signIn, signOut } = useBasic()
83
+ const todos = useQuery(() => db.collection('todos').getAll())
84
+
85
+ if (!isSignedIn) {
86
+ return <button onClick={signIn}>Sign In</button>
87
+ }
88
+
89
+ return (
90
+ <ul>
91
+ {todos?.map(todo => <li key={todo.id}>{todo.title}</li>)}
92
+ </ul>
93
+ )
94
+ }
95
+ ```
96
+
97
+ ---
98
+
99
+ ## Important: Import Pattern
100
+
101
+ Due to Next.js SSR, **client components must import from `@basictech/react`**:
102
+
103
+ ```tsx
104
+ // ✅ Correct - in client components
105
+ 'use client'
106
+ import { useBasic, BasicProvider } from '@basictech/react'
107
+
108
+ // ✅ Correct - in middleware/server
109
+ import { createBasicMiddleware } from '@basictech/nextjs'
110
+
111
+ // ❌ Wrong - will cause SSR errors
112
+ import { useBasic } from '@basictech/nextjs'
113
+ ```
114
+
115
+ | Import From | Use In |
116
+ |-------------|--------|
117
+ | `@basictech/react` | Client components (`'use client'`) |
118
+ | `@basictech/nextjs` | Middleware, server utilities |
119
+
120
+ ---
121
+
122
+ ## API Reference
123
+
124
+ ### `<BasicProvider>`
125
+
126
+ Root provider component. Must wrap your entire app in a client component.
127
+
128
+ ```tsx
129
+ <BasicProvider
130
+ schema={schema} // Required: Your Basic schema
131
+ debug={false} // Optional: Enable console logging
132
+ dbMode="sync" // Optional: "sync" (default) or "remote"
133
+ />
134
+ ```
135
+
136
+ #### Props
137
+
138
+ | Prop | Type | Default | Description |
139
+ |------|------|---------|-------------|
140
+ | `schema` | `object` | required | Schema with `project_id` and `tables` |
141
+ | `debug` | `boolean` | `false` | Enable debug logging |
142
+ | `dbMode` | `"sync" \| "remote"` | `"sync"` | Database mode |
143
+
144
+ ---
145
+
146
+ ### `useBasic()`
147
+
148
+ Main hook for accessing auth and database. **Must be used in client components.**
149
+
150
+ ```tsx
151
+ 'use client'
152
+
153
+ import { useBasic } from '@basictech/react'
39
154
 
40
155
  function MyComponent() {
41
- const { user, isSignedIn, signin, signout, db } = useBasic();
156
+ const {
157
+ // Auth state
158
+ isReady, // boolean - SDK initialized
159
+ isSignedIn, // boolean - User authenticated
160
+ user, // { id, email, ... } | null
161
+
162
+ // Auth methods
163
+ signIn, // () => void - Redirect to login
164
+ signOut, // () => void - Clear session
165
+ signInWithCode, // (code, state?) => Promise - Manual OAuth
166
+ getSignInUrl, // (redirectUri?) => string - Get OAuth URL
167
+ getToken, // () => Promise<string> - Get access token
168
+
169
+ // Database
170
+ db, // Database instance
171
+ dbStatus, // "OFFLINE" | "CONNECTING" | "ONLINE" | "SYNCING"
172
+ dbMode, // "sync" | "remote"
173
+ } = useBasic()
174
+
175
+ // ... use these values
176
+ }
177
+ ```
42
178
 
43
- if (!isSignedIn) {
44
- return <button onClick={signin}>Sign In</button>;
179
+ #### Auth State
180
+
181
+ | Property | Type | Description |
182
+ |----------|------|-------------|
183
+ | `isReady` | `boolean` | `true` when SDK is fully initialized |
184
+ | `isSignedIn` | `boolean` | `true` when user is authenticated |
185
+ | `user` | `object \| null` | User object with `id`, `email`, etc. |
186
+
187
+ #### Auth Methods
188
+
189
+ | Method | Parameters | Returns | Description |
190
+ |--------|------------|---------|-------------|
191
+ | `signIn()` | none | `void` | Redirects to Basic login page |
192
+ | `signOut()` | none | `void` | Clears session and signs out |
193
+ | `signInWithCode()` | `code: string, state?: string` | `Promise<{success, error?}>` | Exchange OAuth code for session |
194
+ | `getSignInUrl()` | `redirectUri?: string` | `string` | Get OAuth URL for custom flows |
195
+ | `getToken()` | none | `Promise<string>` | Get current access token |
196
+
197
+ #### Database
198
+
199
+ | Property | Type | Description |
200
+ |----------|------|-------------|
201
+ | `db` | `object` | Database instance for CRUD operations |
202
+ | `dbStatus` | `string` | Connection status |
203
+ | `dbMode` | `"sync" \| "remote"` | Current database mode |
204
+
205
+ ---
206
+
207
+ ### `useQuery()`
208
+
209
+ Live query hook - automatically re-renders when data changes. **Only works in sync mode.**
210
+
211
+ ```tsx
212
+ 'use client'
213
+
214
+ import { useBasic, useQuery } from '@basictech/react'
215
+
216
+ function TodoList() {
217
+ const { db } = useBasic()
218
+
219
+ // Get all items - auto-updates when data changes
220
+ const todos = useQuery(() => db.collection('todos').getAll())
221
+
222
+ // With type safety
223
+ interface Todo { id: string; title: string; completed: boolean }
224
+ const typedTodos = useQuery(() => db.collection<Todo>('todos').getAll())
225
+
226
+ return (
227
+ <ul>
228
+ {todos?.map(todo => <li key={todo.id}>{todo.title}</li>)}
229
+ </ul>
230
+ )
231
+ }
232
+ ```
233
+
234
+ > **Note:** In `remote` mode, `useQuery` won't auto-update. Fetch data manually instead.
235
+
236
+ ---
237
+
238
+ ### Database Methods
239
+
240
+ #### `db.collection(name)`
241
+
242
+ Access a collection by name.
243
+
244
+ ```tsx
245
+ const { db } = useBasic()
246
+ const todosCollection = db.collection('todos')
247
+
248
+ // With TypeScript generics
249
+ interface Todo { id: string; title: string; completed: boolean }
250
+ const typedCollection = db.collection<Todo>('todos')
251
+ ```
252
+
253
+ #### Collection Methods
254
+
255
+ | Method | Returns | Description |
256
+ |--------|---------|-------------|
257
+ | `getAll()` | `Promise<T[]>` | Get all records |
258
+ | `get(id)` | `Promise<T \| null>` | Get one record by ID |
259
+ | `add(data)` | `Promise<T>` | Create new record (returns with generated ID) |
260
+ | `put(data)` | `Promise<T>` | Upsert record (requires ID in data) |
261
+ | `update(id, data)` | `Promise<T \| null>` | Partial update by ID |
262
+ | `delete(id)` | `Promise<boolean>` | Delete record by ID |
263
+ | `filter(fn)` | `Promise<T[]>` | Filter with predicate function |
264
+
265
+ #### Examples
266
+
267
+ ```tsx
268
+ 'use client'
269
+
270
+ import { useBasic } from '@basictech/react'
271
+
272
+ function TodoActions() {
273
+ const { db } = useBasic()
274
+
275
+ // CREATE - add new record
276
+ const addTodo = async () => {
277
+ const todo = await db.collection('todos').add({
278
+ title: 'Buy milk',
279
+ completed: false
280
+ })
281
+ console.log('Created:', todo.id) // Auto-generated ID
282
+ }
283
+
284
+ // READ - get all records
285
+ const getAllTodos = async () => {
286
+ const todos = await db.collection('todos').getAll()
287
+ console.log('All todos:', todos)
288
+ }
289
+
290
+ // READ - get single record
291
+ const getTodo = async (id: string) => {
292
+ const todo = await db.collection('todos').get(id)
293
+ if (todo) {
294
+ console.log('Found:', todo)
295
+ } else {
296
+ console.log('Not found')
297
+ }
298
+ }
299
+
300
+ // UPDATE - partial update
301
+ const completeTodo = async (id: string) => {
302
+ const updated = await db.collection('todos').update(id, {
303
+ completed: true
304
+ })
305
+ console.log('Updated:', updated)
306
+ }
307
+
308
+ // DELETE - remove record
309
+ const deleteTodo = async (id: string) => {
310
+ const deleted = await db.collection('todos').delete(id)
311
+ console.log('Deleted:', deleted) // true or false
312
+ }
313
+
314
+ // FILTER - query with predicate
315
+ const getIncompleteTodos = async () => {
316
+ const incomplete = await db.collection('todos').filter(
317
+ todo => !todo.completed
318
+ )
319
+ console.log('Incomplete:', incomplete)
45
320
  }
46
321
 
47
322
  return (
48
323
  <div>
49
- <h1>Welcome, {user.name}!</h1>
50
- <button onClick={signout}>Sign Out</button>
324
+ <button onClick={addTodo}>Add Todo</button>
325
+ <button onClick={getAllTodos}>Get All</button>
326
+ {/* ... */}
51
327
  </div>
52
- );
328
+ )
53
329
  }
54
330
  ```
55
331
 
56
- ### 3. Database Operations
332
+ ---
333
+
334
+ ## Middleware (Optional)
57
335
 
58
- You can perform database operations using the `db` object:
336
+ Protect routes with authentication middleware.
337
+
338
+ ### Setup
339
+
340
+ Create `middleware.ts` in your project root:
59
341
 
60
342
  ```typescript
61
- const { db } = useBasic();
343
+ import { createBasicMiddleware } from '@basictech/nextjs'
344
+
345
+ export const middleware = createBasicMiddleware({
346
+ publicRoutes: ['/', '/about', '/login'],
347
+ loginRoute: '/login',
348
+ })
62
349
 
63
- // Get data
64
- const getData = async () => {
65
- const data = await db.table('myTable').get();
66
- console.log(data);
67
- };
350
+ export const config = {
351
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
352
+ }
353
+ ```
68
354
 
69
- // Add data
70
- const addData = async () => {
71
- const result = await db.table('myTable').add({ key: 'value' });
72
- console.log(result);
73
- };
355
+ ### Configuration Options
74
356
 
75
- // Update data
76
- const updateData = async () => {
77
- const result = await db.table('myTable').update('itemId', { key: 'newValue' });
78
- console.log(result);
79
- };
357
+ ```typescript
358
+ createBasicMiddleware({
359
+ // Routes that don't require auth
360
+ publicRoutes: ['/'],
361
+
362
+ // Where to redirect unauthenticated users
363
+ loginRoute: '/login',
364
+
365
+ // Custom auth check (optional)
366
+ isAuthenticated: (request) => {
367
+ return !!request.cookies.get('basic_refresh_token')
368
+ }
369
+ })
80
370
  ```
81
371
 
82
- ## API Reference
372
+ ### Utility Functions
373
+
374
+ ```typescript
375
+ import {
376
+ getAuthFromRequest,
377
+ getReturnUrl,
378
+ withBasicAuth
379
+ } from '@basictech/nextjs'
83
380
 
84
- ### useBasic()
381
+ // Get auth state from request
382
+ const auth = getAuthFromRequest(request)
383
+ if (auth.isAuthenticated) {
384
+ console.log('User is logged in')
385
+ }
85
386
 
86
- Returns an object with the following properties and methods:
387
+ // Get URL to return to after login
388
+ const returnUrl = getReturnUrl(request)
389
+ ```
390
+
391
+ ---
392
+
393
+ ## Database Modes
394
+
395
+ ### Sync Mode (Default)
396
+
397
+ Local-first with IndexedDB + real-time sync:
398
+
399
+ ```tsx
400
+ <BasicProvider schema={schema} dbMode="sync">
401
+ ```
402
+
403
+ - ✅ Works offline
404
+ - ✅ Real-time updates via WebSocket
405
+ - ✅ `useQuery` auto-refreshes
406
+ - ✅ Fast reads from local DB
407
+
408
+ ### Remote Mode
409
+
410
+ Direct API calls (no local storage):
411
+
412
+ ```tsx
413
+ <BasicProvider schema={schema} dbMode="remote">
414
+ ```
415
+
416
+ - ✅ No IndexedDB dependencies
417
+ - ✅ Better for SSR-heavy apps
418
+ - ❌ Requires authentication for all operations
419
+ - ❌ No offline support
420
+ - ❌ `useQuery` won't auto-update
421
+
422
+ ---
423
+
424
+ ## Error Handling
425
+
426
+ ### NotAuthenticatedError
427
+
428
+ Thrown when attempting write operations without being signed in (remote mode):
429
+
430
+ ```tsx
431
+ import { NotAuthenticatedError } from '@basictech/react'
432
+
433
+ const addTodo = async () => {
434
+ try {
435
+ await db.collection('todos').add({ title: 'Test' })
436
+ } catch (error) {
437
+ if (error instanceof NotAuthenticatedError) {
438
+ // User needs to sign in
439
+ signIn()
440
+ }
441
+ }
442
+ }
443
+ ```
444
+
445
+ ### Graceful Degradation
446
+
447
+ In remote mode, read operations gracefully handle unauthenticated state:
448
+
449
+ | Operation | Behavior When Not Signed In |
450
+ |-----------|----------------------------|
451
+ | `getAll()` | Returns `[]` |
452
+ | `get(id)` | Returns `null` |
453
+ | `filter()` | Returns `[]` |
454
+ | `add()` | Throws `NotAuthenticatedError` |
455
+ | `update()` | Throws `NotAuthenticatedError` |
456
+ | `delete()` | Throws `NotAuthenticatedError` |
457
+
458
+ ---
459
+
460
+ ## TypeScript
461
+
462
+ Full TypeScript support with generics:
463
+
464
+ ```tsx
465
+ interface Todo {
466
+ id: string
467
+ title: string
468
+ completed: boolean
469
+ createdAt: number
470
+ }
471
+
472
+ // Type-safe collection
473
+ const todos = db.collection<Todo>('todos')
474
+
475
+ // All methods are typed
476
+ const todo = await todos.add({
477
+ title: 'Test',
478
+ completed: false,
479
+ createdAt: Date.now()
480
+ })
481
+ // todo is typed as Todo
482
+
483
+ // Type-safe queries
484
+ const incomplete = await todos.filter(t => !t.completed)
485
+ // incomplete is typed as Todo[]
486
+ ```
487
+
488
+ ---
489
+
490
+ ## Full Example
491
+
492
+ ### File Structure
493
+
494
+ ```
495
+ app/
496
+ ├── layout.tsx # Root layout with Providers
497
+ ├── providers.tsx # Client-side BasicProvider
498
+ ├── page.tsx # Home page (server component)
499
+ ├── dashboard/
500
+ │ └── page.tsx # Protected page
501
+ └── components/
502
+ └── TodoList.tsx # Client component
503
+ middleware.ts # Route protection
504
+ basic.config.ts # Schema
505
+ ```
506
+
507
+ ### `basic.config.ts`
508
+
509
+ ```typescript
510
+ export const schema = {
511
+ project_id: "YOUR_PROJECT_ID",
512
+ version: 1,
513
+ tables: {
514
+ todos: {
515
+ type: "collection",
516
+ fields: {
517
+ title: { type: "string", indexed: true },
518
+ completed: { type: "boolean", indexed: true }
519
+ }
520
+ }
521
+ }
522
+ }
523
+ ```
524
+
525
+ ### `app/providers.tsx`
526
+
527
+ ```tsx
528
+ 'use client'
529
+
530
+ import { BasicProvider } from '@basictech/react'
531
+ import { schema } from '../basic.config'
532
+
533
+ export function Providers({ children }: { children: React.ReactNode }) {
534
+ return (
535
+ <BasicProvider schema={schema} debug>
536
+ {children}
537
+ </BasicProvider>
538
+ )
539
+ }
540
+ ```
541
+
542
+ ### `app/layout.tsx`
543
+
544
+ ```tsx
545
+ import { Providers } from './providers'
546
+
547
+ export default function RootLayout({ children }) {
548
+ return (
549
+ <html lang="en">
550
+ <body>
551
+ <Providers>{children}</Providers>
552
+ </body>
553
+ </html>
554
+ )
555
+ }
556
+ ```
557
+
558
+ ### `app/components/TodoList.tsx`
559
+
560
+ ```tsx
561
+ 'use client'
562
+
563
+ import { useBasic, useQuery } from '@basictech/react'
564
+
565
+ interface Todo {
566
+ id: string
567
+ title: string
568
+ completed: boolean
569
+ }
570
+
571
+ export function TodoList() {
572
+ const { db, isSignedIn, signIn } = useBasic()
573
+ const todos = useQuery(() => db.collection<Todo>('todos').getAll())
574
+
575
+ const addTodo = async () => {
576
+ await db.collection<Todo>('todos').add({
577
+ title: 'New todo',
578
+ completed: false
579
+ })
580
+ }
581
+
582
+ const toggleTodo = async (id: string, completed: boolean) => {
583
+ await db.collection('todos').update(id, { completed: !completed })
584
+ }
585
+
586
+ const deleteTodo = async (id: string) => {
587
+ await db.collection('todos').delete(id)
588
+ }
589
+
590
+ if (!isSignedIn) {
591
+ return <button onClick={signIn}>Sign In to view todos</button>
592
+ }
593
+
594
+ return (
595
+ <div>
596
+ <button onClick={addTodo}>Add Todo</button>
597
+ <ul>
598
+ {todos?.map(todo => (
599
+ <li key={todo.id}>
600
+ <span
601
+ style={{ textDecoration: todo.completed ? 'line-through' : 'none' }}
602
+ onClick={() => toggleTodo(todo.id, todo.completed)}
603
+ >
604
+ {todo.title}
605
+ </span>
606
+ <button onClick={() => deleteTodo(todo.id)}>Delete</button>
607
+ </li>
608
+ ))}
609
+ </ul>
610
+ </div>
611
+ )
612
+ }
613
+ ```
614
+
615
+ ### `middleware.ts`
616
+
617
+ ```typescript
618
+ import { createBasicMiddleware } from '@basictech/nextjs'
619
+
620
+ export const middleware = createBasicMiddleware({
621
+ publicRoutes: ['/', '/login'],
622
+ loginRoute: '/login',
623
+ })
624
+
625
+ export const config = {
626
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
627
+ }
628
+ ```
629
+
630
+ ---
631
+
632
+ ## Troubleshooting
633
+
634
+ ### "self is not defined" Error
635
+
636
+ This happens when importing client code on the server. Make sure:
637
+
638
+ 1. Client components have `'use client'` directive at the top
639
+ 2. Import from `@basictech/react`, not `@basictech/nextjs`
640
+ 3. `BasicProvider` is wrapped in a client component
641
+
642
+ ### Hydration Mismatch
643
+
644
+ Wrap provider initialization in a `mounted` check:
645
+
646
+ ```tsx
647
+ 'use client'
648
+
649
+ import { useState, useEffect } from 'react'
650
+ import { BasicProvider } from '@basictech/react'
651
+
652
+ export function Providers({ children }) {
653
+ const [mounted, setMounted] = useState(false)
654
+
655
+ useEffect(() => setMounted(true), [])
656
+
657
+ if (!mounted) return null
658
+
659
+ return <BasicProvider schema={schema}>{children}</BasicProvider>
660
+ }
661
+ ```
87
662
 
88
- - `user`: The current user object
89
- - `isSignedIn`: Boolean indicating if the user is signed in
90
- - `signin()`: Function to initiate the sign-in process
91
- - `signout()`: Function to sign out the user
92
- - `db`: Object for database operations
663
+ ### Remote Mode Returns Empty Data
93
664
 
94
- ### db
665
+ In remote mode, you must be signed in to fetch data. Check:
95
666
 
96
- The `db` object provides the following methods:
667
+ 1. `isSignedIn` is `true` before fetching
668
+ 2. User has access to the project
669
+ 3. Network requests aren't being blocked
97
670
 
98
- - `table(tableName)`: Selects a table for operations
99
- - `get()`: Retrieves all items from the table
100
- - `add(value)`: Adds a new item to the table
101
- - `update(id, value)`: Updates an item in the table
671
+ ---
102
672
 
103
673
  ## License
104
674