@powerhousedao/academy 3.3.0-dev.2 → 3.3.0-dev.3
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
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
## 3.3.0-dev.3 (2025-07-08)
|
|
2
|
+
|
|
3
|
+
### 🚀 Features
|
|
4
|
+
|
|
5
|
+
- added operational hooks and utils in reactor-browser ([216f7d03d](https://github.com/powerhouse-inc/powerhouse/commit/216f7d03d))
|
|
6
|
+
|
|
7
|
+
### ❤️ Thank You
|
|
8
|
+
|
|
9
|
+
- acaldas
|
|
10
|
+
|
|
1
11
|
## 3.3.0-dev.2 (2025-07-05)
|
|
2
12
|
|
|
3
13
|
### 🩹 Fixes
|
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
# Operational Database
|
|
2
|
+
|
|
3
|
+
This page covers the operational database tools available in Powerhouse applications, providing type-safe database operations with real-time updates through PGlite integration.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The operational database layer gives you powerful tools to work with data in your Powerhouse applications. You get type-safe queries, real-time updates, and a simple API that feels familiar to React developers.
|
|
8
|
+
|
|
9
|
+
**Key Benefits:**
|
|
10
|
+
- 🔒 **Type-safe queries** with full TypeScript support
|
|
11
|
+
- 🔄 **Live query capabilities** with real-time updates
|
|
12
|
+
- ⚡ **Automatic optimization** to prevent infinite re-renders
|
|
13
|
+
- 🎯 **Simple API** that abstracts away complexity
|
|
14
|
+
- 🧠 **Smart memoization** for parameters and queries
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
<details>
|
|
19
|
+
<summary>Setting up your first operational database query</summary>
|
|
20
|
+
|
|
21
|
+
### Step 1: Define your database schema
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
type MyDatabase = {
|
|
25
|
+
users: {
|
|
26
|
+
id: number;
|
|
27
|
+
name: string;
|
|
28
|
+
email: string;
|
|
29
|
+
};
|
|
30
|
+
posts: {
|
|
31
|
+
id: number;
|
|
32
|
+
title: string;
|
|
33
|
+
content: string;
|
|
34
|
+
author_id: number;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Step 2: Create a typed query hook
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { createTypedQuery } from '@powerhousedao/reactor-browser/operational';
|
|
43
|
+
|
|
44
|
+
const useTypedQuery = createTypedQuery<MyDatabase>();
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Step 3: Use it in your component
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// Simple query - no parameters needed
|
|
51
|
+
export function useUserList() {
|
|
52
|
+
return useTypedQuery(db => {
|
|
53
|
+
return db.selectFrom('users').selectAll().compile();
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Query with parameters
|
|
58
|
+
export function useUserById(userId: number) {
|
|
59
|
+
return useTypedQuery(
|
|
60
|
+
(db, params) => {
|
|
61
|
+
return db
|
|
62
|
+
.selectFrom('users')
|
|
63
|
+
.selectAll()
|
|
64
|
+
.where('id', '=', params.userId)
|
|
65
|
+
.compile();
|
|
66
|
+
},
|
|
67
|
+
{ userId }
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Step 4: Use in your React component
|
|
73
|
+
|
|
74
|
+
```typescript
|
|
75
|
+
function UserList() {
|
|
76
|
+
const { isLoading, error, result } = useUserList();
|
|
77
|
+
|
|
78
|
+
if (isLoading) return <div>Loading...</div>;
|
|
79
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
80
|
+
if (!result) return <div>No data</div>;
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<ul>
|
|
84
|
+
{result.rows.map(user => (
|
|
85
|
+
<li key={user.id}>
|
|
86
|
+
{user.name} - {user.email}
|
|
87
|
+
</li>
|
|
88
|
+
))}
|
|
89
|
+
</ul>
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
</details>
|
|
95
|
+
|
|
96
|
+
## Core Hooks
|
|
97
|
+
|
|
98
|
+
### 1. createTypedQuery()
|
|
99
|
+
|
|
100
|
+
<details>
|
|
101
|
+
<summary>`createTypedQuery<Schema>()`: Creates a typed query hook for your database schema</summary>
|
|
102
|
+
|
|
103
|
+
### Hook Name and Signature
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
function createTypedQuery<Schema>(): TypedQueryHook<Schema>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Description
|
|
110
|
+
|
|
111
|
+
Creates a typed query hook that provides type-safe database operations with live query capabilities. This is the main hook you'll use for most operational database operations in your components.
|
|
112
|
+
|
|
113
|
+
### Usage Example
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import { createTypedQuery } from '@powerhousedao/reactor-browser/operational';
|
|
117
|
+
|
|
118
|
+
type AppDatabase = {
|
|
119
|
+
users: { id: number; name: string; email: string };
|
|
120
|
+
posts: { id: number; title: string; author_id: number };
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const useTypedQuery = createTypedQuery<AppDatabase>();
|
|
124
|
+
|
|
125
|
+
// Static query (no parameters)
|
|
126
|
+
function useAllUsers() {
|
|
127
|
+
return useTypedQuery(db => {
|
|
128
|
+
return db.selectFrom('users').selectAll().compile();
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Dynamic query with parameters
|
|
133
|
+
function useUsersByStatus(status: string) {
|
|
134
|
+
return useTypedQuery(
|
|
135
|
+
(db, params) => {
|
|
136
|
+
return db
|
|
137
|
+
.selectFrom('users')
|
|
138
|
+
.selectAll()
|
|
139
|
+
.where('status', '=', params.status)
|
|
140
|
+
.compile();
|
|
141
|
+
},
|
|
142
|
+
{ status }
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Parameters
|
|
148
|
+
|
|
149
|
+
The returned hook has two overloads:
|
|
150
|
+
|
|
151
|
+
**Static queries (no parameters):**
|
|
152
|
+
- `queryCallback: (db: EnhancedKysely<Schema>) => QueryCallbackReturnType` - Function that receives the database instance and returns a query
|
|
153
|
+
|
|
154
|
+
**Parameterized queries:**
|
|
155
|
+
- `queryCallback: (db: EnhancedKysely<Schema>, parameters: TParams) => QueryCallbackReturnType` - Function that receives the database instance and parameters
|
|
156
|
+
- `parameters: TParams` - Parameters for the query (automatically memoized)
|
|
157
|
+
|
|
158
|
+
### Return Value
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
{
|
|
162
|
+
isLoading: boolean; // True while query is loading
|
|
163
|
+
error: Error | null; // Any error that occurred
|
|
164
|
+
result: LiveQueryResults<T> | null; // Query results with real-time updates
|
|
165
|
+
}
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Notes / Caveats
|
|
169
|
+
|
|
170
|
+
- Parameters are automatically memoized using deep comparison
|
|
171
|
+
- Queries update in real-time when the database changes
|
|
172
|
+
- The callback must return an object with `sql` and optional `parameters` properties
|
|
173
|
+
- Use `.compile()` on Kysely queries to get the required format
|
|
174
|
+
|
|
175
|
+
### Related Hooks
|
|
176
|
+
|
|
177
|
+
- [`useOperationalStore`](#useoperationalstore) - For direct database access
|
|
178
|
+
- [`useOperationalQuery`](#useoperationalquery) - Lower-level query hook
|
|
179
|
+
|
|
180
|
+
</details>
|
|
181
|
+
|
|
182
|
+
### 2. useOperationalStore()
|
|
183
|
+
|
|
184
|
+
<details>
|
|
185
|
+
<summary>`useOperationalStore<Schema>()`: Access the enhanced database instance directly</summary>
|
|
186
|
+
|
|
187
|
+
### Hook Name and Signature
|
|
188
|
+
|
|
189
|
+
```typescript
|
|
190
|
+
function useOperationalStore<Schema>(): IOperationalStore<Schema>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Description
|
|
194
|
+
|
|
195
|
+
Provides direct access to the enhanced Kysely database instance with live query capabilities. Use this when you need to perform operational database operations outside of the typical query patterns.
|
|
196
|
+
|
|
197
|
+
### Usage Example
|
|
198
|
+
|
|
199
|
+
```typescript
|
|
200
|
+
import { useOperationalStore } from '@powerhousedao/reactor-browser/operational';
|
|
201
|
+
|
|
202
|
+
function DatabaseOperations() {
|
|
203
|
+
const { db, isLoading, error } = useOperationalStore<MyDatabase>();
|
|
204
|
+
|
|
205
|
+
const createUser = async (name: string, email: string) => {
|
|
206
|
+
if (!db) return;
|
|
207
|
+
|
|
208
|
+
// Direct database operations
|
|
209
|
+
await db
|
|
210
|
+
.insertInto('users')
|
|
211
|
+
.values({ name, email })
|
|
212
|
+
.execute();
|
|
213
|
+
};
|
|
214
|
+
|
|
215
|
+
if (isLoading) return <div>Database initializing...</div>;
|
|
216
|
+
if (error) return <div>Database error: {error.message}</div>;
|
|
217
|
+
|
|
218
|
+
return (
|
|
219
|
+
<button onClick={() => createUser('John', 'john@example.com')}>
|
|
220
|
+
Create User
|
|
221
|
+
</button>
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Parameters
|
|
227
|
+
|
|
228
|
+
- `Schema` - TypeScript type defining your database schema
|
|
229
|
+
|
|
230
|
+
### Return Value
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
{
|
|
234
|
+
db: EnhancedKysely<Schema> | null; // Enhanced Kysely instance with live capabilities
|
|
235
|
+
isLoading: boolean; // True while database is initializing
|
|
236
|
+
error: Error | null; // Any initialization error
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
### Notes / Caveats
|
|
241
|
+
|
|
242
|
+
- Always check if `db` is not null before using it
|
|
243
|
+
- The database instance includes both Kysely methods and live query capabilities
|
|
244
|
+
- Use this for direct database operations like inserts, updates, and deletes
|
|
245
|
+
- For queries, prefer `createTypedQuery()` which provides better optimization
|
|
246
|
+
|
|
247
|
+
### Related Hooks
|
|
248
|
+
|
|
249
|
+
- [`createTypedQuery`](#createtypedquery) - For optimized queries
|
|
250
|
+
- [`useOperationalQuery`](#useoperationalquery) - For manual query control
|
|
251
|
+
|
|
252
|
+
</details>
|
|
253
|
+
|
|
254
|
+
### 3. useOperationalQuery()
|
|
255
|
+
|
|
256
|
+
<details>
|
|
257
|
+
<summary>`useOperationalQuery<Schema, T, TParams>()`: Lower-level hook for manual query control</summary>
|
|
258
|
+
|
|
259
|
+
### Hook Name and Signature
|
|
260
|
+
|
|
261
|
+
```typescript
|
|
262
|
+
function useOperationalQuery<Schema, T, TParams>(
|
|
263
|
+
queryCallback: (db: EnhancedKysely<Schema>, parameters?: TParams) => QueryCallbackReturnType,
|
|
264
|
+
parameters?: TParams
|
|
265
|
+
): QueryResult<T>
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Description
|
|
269
|
+
|
|
270
|
+
Lower-level hook for creating live queries with manual control over the query callback and parameters. Most developers should use `createTypedQuery()` instead, but this hook is useful for advanced use cases.
|
|
271
|
+
|
|
272
|
+
### Usage Example
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
import { useOperationalQuery } from '@powerhousedao/reactor-browser/operational';
|
|
276
|
+
|
|
277
|
+
function UserCount() {
|
|
278
|
+
const { result, isLoading, error } = useOperationalQuery<MyDatabase, { count: number }>(
|
|
279
|
+
(db) => {
|
|
280
|
+
return db
|
|
281
|
+
.selectFrom('users')
|
|
282
|
+
.select(db.fn.count('id').as('count'))
|
|
283
|
+
.compile();
|
|
284
|
+
}
|
|
285
|
+
);
|
|
286
|
+
|
|
287
|
+
if (isLoading) return <div>Loading...</div>;
|
|
288
|
+
if (error) return <div>Error: {error.message}</div>;
|
|
289
|
+
|
|
290
|
+
return <div>User count: {result?.rows[0]?.count ?? 0}</div>;
|
|
291
|
+
}
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
### Parameters
|
|
295
|
+
|
|
296
|
+
- `queryCallback` - Function that receives the database instance and optional parameters
|
|
297
|
+
- `parameters` - Optional parameters for the query
|
|
298
|
+
|
|
299
|
+
### Return Value
|
|
300
|
+
|
|
301
|
+
```typescript
|
|
302
|
+
{
|
|
303
|
+
result: LiveQueryResults<T> | null; // Live query results
|
|
304
|
+
isLoading: boolean; // Combined loading state
|
|
305
|
+
error: Error | null; // Any error that occurred
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
### Notes / Caveats
|
|
310
|
+
|
|
311
|
+
- This hook doesn't include automatic parameter memoization
|
|
312
|
+
- Use `createTypedQuery()` for better developer experience and optimization
|
|
313
|
+
- Useful for cases where you need manual control over the query lifecycle
|
|
314
|
+
|
|
315
|
+
### Related Hooks
|
|
316
|
+
|
|
317
|
+
- [`createTypedQuery`](#createtypedquery) - Recommended higher-level API
|
|
318
|
+
- [`useOperationalStore`](#useoperationalstore) - For direct database access
|
|
319
|
+
|
|
320
|
+
</details>
|
|
321
|
+
|
|
322
|
+
## Advanced Patterns
|
|
323
|
+
|
|
324
|
+
### Working with Dynamic Parameters
|
|
325
|
+
|
|
326
|
+
<details>
|
|
327
|
+
<summary>How to handle parameters that change over time</summary>
|
|
328
|
+
|
|
329
|
+
### Problem
|
|
330
|
+
|
|
331
|
+
You need to create queries that update automatically when search terms, filters, or other parameters change.
|
|
332
|
+
|
|
333
|
+
### Solution
|
|
334
|
+
|
|
335
|
+
The `createTypedQuery` hook automatically handles parameter changes and memoizes them using deep comparison:
|
|
336
|
+
|
|
337
|
+
```typescript
|
|
338
|
+
function useSearchResults() {
|
|
339
|
+
const [searchTerm, setSearchTerm] = useState('');
|
|
340
|
+
const [category, setCategory] = useState('all');
|
|
341
|
+
|
|
342
|
+
// Query automatically updates when searchTerm or category changes
|
|
343
|
+
const result = useTypedQuery(
|
|
344
|
+
(db, params) => {
|
|
345
|
+
let query = db.selectFrom('products').selectAll();
|
|
346
|
+
|
|
347
|
+
if (params.searchTerm) {
|
|
348
|
+
query = query.where('name', 'like', `%${params.searchTerm}%`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (params.category !== 'all') {
|
|
352
|
+
query = query.where('category', '=', params.category);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return query.compile();
|
|
356
|
+
},
|
|
357
|
+
{ searchTerm, category }
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
return { result, setSearchTerm, setCategory };
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Key Points
|
|
365
|
+
|
|
366
|
+
- Parameters are automatically memoized using deep comparison
|
|
367
|
+
- No need to wrap parameters in `useMemo`
|
|
368
|
+
- Query re-runs only when parameter values actually change
|
|
369
|
+
- Works with complex nested objects
|
|
370
|
+
|
|
371
|
+
</details>
|
|
372
|
+
|
|
373
|
+
### Custom SQL Queries
|
|
374
|
+
|
|
375
|
+
<details>
|
|
376
|
+
<summary>Using raw SQL instead of Kysely query builder</summary>
|
|
377
|
+
|
|
378
|
+
### Problem
|
|
379
|
+
|
|
380
|
+
You need to write complex SQL queries that are easier to express in raw SQL than using the Kysely query builder.
|
|
381
|
+
|
|
382
|
+
### Solution
|
|
383
|
+
|
|
384
|
+
You can return raw SQL queries from your callback:
|
|
385
|
+
|
|
386
|
+
```typescript
|
|
387
|
+
function useCustomUserStats() {
|
|
388
|
+
return useTypedQuery(() => {
|
|
389
|
+
return {
|
|
390
|
+
sql: `
|
|
391
|
+
SELECT
|
|
392
|
+
u.name,
|
|
393
|
+
COUNT(p.id) as post_count,
|
|
394
|
+
MAX(p.created_at) as last_post_date
|
|
395
|
+
FROM users u
|
|
396
|
+
LEFT JOIN posts p ON u.id = p.author_id
|
|
397
|
+
GROUP BY u.id, u.name
|
|
398
|
+
ORDER BY post_count DESC
|
|
399
|
+
`
|
|
400
|
+
};
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
// With parameters
|
|
405
|
+
function useUserPostsByDateRange(startDate: string, endDate: string) {
|
|
406
|
+
return useTypedQuery(
|
|
407
|
+
(db, params) => {
|
|
408
|
+
return {
|
|
409
|
+
sql: `
|
|
410
|
+
SELECT p.*, u.name as author_name
|
|
411
|
+
FROM posts p
|
|
412
|
+
JOIN users u ON p.author_id = u.id
|
|
413
|
+
WHERE p.created_at BETWEEN $1 AND $2
|
|
414
|
+
ORDER BY p.created_at DESC
|
|
415
|
+
`,
|
|
416
|
+
parameters: [params.startDate, params.endDate]
|
|
417
|
+
};
|
|
418
|
+
},
|
|
419
|
+
{ startDate, endDate }
|
|
420
|
+
);
|
|
421
|
+
}
|
|
422
|
+
```
|
|
423
|
+
|
|
424
|
+
### Key Points
|
|
425
|
+
|
|
426
|
+
- Return an object with `sql` and optional `parameters` properties
|
|
427
|
+
- Use parameterized queries ($1, $2, etc.) for dynamic values
|
|
428
|
+
- You can mix Kysely and raw SQL approaches in the same application
|
|
429
|
+
|
|
430
|
+
</details>
|
|
431
|
+
|
|
432
|
+
### Complex Joins and Relationships
|
|
433
|
+
|
|
434
|
+
<details>
|
|
435
|
+
<summary>Working with related data across multiple tables</summary>
|
|
436
|
+
|
|
437
|
+
### Problem
|
|
438
|
+
|
|
439
|
+
You need to fetch related data from multiple tables with complex relationships.
|
|
440
|
+
|
|
441
|
+
### Solution
|
|
442
|
+
|
|
443
|
+
Use Kysely's join capabilities within your query callbacks:
|
|
444
|
+
|
|
445
|
+
```typescript
|
|
446
|
+
function useUsersWithPosts() {
|
|
447
|
+
return useTypedQuery(db => {
|
|
448
|
+
return db
|
|
449
|
+
.selectFrom('users')
|
|
450
|
+
.leftJoin('posts', 'users.id', 'posts.author_id')
|
|
451
|
+
.select([
|
|
452
|
+
'users.id',
|
|
453
|
+
'users.name',
|
|
454
|
+
'users.email',
|
|
455
|
+
'posts.title as post_title',
|
|
456
|
+
'posts.content as post_content'
|
|
457
|
+
])
|
|
458
|
+
.compile();
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// More complex example with multiple joins and aggregations
|
|
463
|
+
function useUserDashboardData(userId: number) {
|
|
464
|
+
return useTypedQuery(
|
|
465
|
+
(db, params) => {
|
|
466
|
+
return db
|
|
467
|
+
.selectFrom('users')
|
|
468
|
+
.leftJoin('posts', 'users.id', 'posts.author_id')
|
|
469
|
+
.leftJoin('comments', 'posts.id', 'comments.post_id')
|
|
470
|
+
.select([
|
|
471
|
+
'users.id',
|
|
472
|
+
'users.name',
|
|
473
|
+
'users.email',
|
|
474
|
+
db.fn.count('posts.id').as('post_count'),
|
|
475
|
+
db.fn.count('comments.id').as('comment_count')
|
|
476
|
+
])
|
|
477
|
+
.where('users.id', '=', params.userId)
|
|
478
|
+
.groupBy(['users.id', 'users.name', 'users.email'])
|
|
479
|
+
.compile();
|
|
480
|
+
},
|
|
481
|
+
{ userId }
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Key Points
|
|
487
|
+
|
|
488
|
+
- Use Kysely's join methods for related data
|
|
489
|
+
- Leverage aggregation functions for counts and calculations
|
|
490
|
+
- Type safety is maintained throughout complex queries
|
|
491
|
+
|
|
492
|
+
</details>
|
|
493
|
+
|
|
494
|
+
## Best Practices
|
|
495
|
+
|
|
496
|
+
### 1. Schema Definition
|
|
497
|
+
|
|
498
|
+
<details>
|
|
499
|
+
<summary>How to properly define your database schema types</summary>
|
|
500
|
+
|
|
501
|
+
Always define clear TypeScript interfaces for your database schema:
|
|
502
|
+
|
|
503
|
+
```typescript
|
|
504
|
+
// ✅ Good - Clear, typed schema
|
|
505
|
+
type AppDatabase = {
|
|
506
|
+
users: {
|
|
507
|
+
id: number;
|
|
508
|
+
name: string;
|
|
509
|
+
email: string;
|
|
510
|
+
created_at: Date;
|
|
511
|
+
updated_at: Date;
|
|
512
|
+
};
|
|
513
|
+
posts: {
|
|
514
|
+
id: number;
|
|
515
|
+
title: string;
|
|
516
|
+
content: string;
|
|
517
|
+
author_id: number;
|
|
518
|
+
published: boolean;
|
|
519
|
+
created_at: Date;
|
|
520
|
+
};
|
|
521
|
+
};
|
|
522
|
+
|
|
523
|
+
// ❌ Avoid - Vague or missing types
|
|
524
|
+
type BadDatabase = {
|
|
525
|
+
users: any;
|
|
526
|
+
posts: Record<string, unknown>;
|
|
527
|
+
};
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
</details>
|
|
531
|
+
|
|
532
|
+
### 2. Hook Organization
|
|
533
|
+
|
|
534
|
+
<details>
|
|
535
|
+
<summary>How to organize your database hooks</summary>
|
|
536
|
+
|
|
537
|
+
Create focused, reusable hooks for different data access patterns:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
// ✅ Good - Focused, reusable hooks
|
|
541
|
+
export function useUsers() {
|
|
542
|
+
return useTypedQuery(db =>
|
|
543
|
+
db.selectFrom('users').selectAll().compile()
|
|
544
|
+
);
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
export function useUserById(id: number) {
|
|
548
|
+
return useTypedQuery(
|
|
549
|
+
(db, params) => db
|
|
550
|
+
.selectFrom('users')
|
|
551
|
+
.selectAll()
|
|
552
|
+
.where('id', '=', params.id)
|
|
553
|
+
.compile(),
|
|
554
|
+
{ id }
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
export function useActiveUsers() {
|
|
559
|
+
return useTypedQuery(db =>
|
|
560
|
+
db.selectFrom('users')
|
|
561
|
+
.selectAll()
|
|
562
|
+
.where('active', '=', true)
|
|
563
|
+
.compile()
|
|
564
|
+
);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ❌ Avoid - Too generic or complex
|
|
568
|
+
export function useEverything() {
|
|
569
|
+
return useTypedQuery(db =>
|
|
570
|
+
db.selectFrom('users')
|
|
571
|
+
.leftJoin('posts', 'users.id', 'posts.author_id')
|
|
572
|
+
.leftJoin('comments', 'posts.id', 'comments.post_id')
|
|
573
|
+
.selectAll() // Too much data
|
|
574
|
+
.compile()
|
|
575
|
+
);
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
</details>
|
|
580
|
+
|
|
581
|
+
### 3. Error Handling
|
|
582
|
+
|
|
583
|
+
<details>
|
|
584
|
+
<summary>How to handle loading states and errors</summary>
|
|
585
|
+
|
|
586
|
+
Always handle loading and error states in your components:
|
|
587
|
+
|
|
588
|
+
```typescript
|
|
589
|
+
function UserList() {
|
|
590
|
+
const { isLoading, error, result } = useUsers();
|
|
591
|
+
|
|
592
|
+
// ✅ Good - Handle all states
|
|
593
|
+
if (isLoading) return <LoadingSpinner />;
|
|
594
|
+
if (error) return <ErrorMessage error={error} />;
|
|
595
|
+
if (!result) return <NoDataMessage />;
|
|
596
|
+
|
|
597
|
+
return (
|
|
598
|
+
<ul>
|
|
599
|
+
{result.rows.map(user => (
|
|
600
|
+
<li key={user.id}>{user.name}</li>
|
|
601
|
+
))}
|
|
602
|
+
</ul>
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// ❌ Avoid - Missing error handling
|
|
607
|
+
function BadUserList() {
|
|
608
|
+
const { result } = useUsers();
|
|
609
|
+
|
|
610
|
+
return (
|
|
611
|
+
<ul>
|
|
612
|
+
{result?.rows.map(user => (
|
|
613
|
+
<li key={user.id}>{user.name}</li>
|
|
614
|
+
))}
|
|
615
|
+
</ul>
|
|
616
|
+
);
|
|
617
|
+
}
|
|
618
|
+
```
|
|
619
|
+
|
|
620
|
+
</details>
|
|
621
|
+
|
|
622
|
+
### 4. Performance Optimization
|
|
623
|
+
|
|
624
|
+
<details>
|
|
625
|
+
<summary>Tips for optimal query performance</summary>
|
|
626
|
+
|
|
627
|
+
- **Keep queries focused**: Don't select unnecessary columns or join too many tables
|
|
628
|
+
- **Use parameters wisely**: The automatic memoization handles most cases, but avoid creating new objects unnecessarily
|
|
629
|
+
- **Consider query frequency**: For data that changes rarely, consider caching strategies
|
|
630
|
+
|
|
631
|
+
```typescript
|
|
632
|
+
// ✅ Good - Focused query
|
|
633
|
+
function useUserNames() {
|
|
634
|
+
return useTypedQuery(db =>
|
|
635
|
+
db.selectFrom('users')
|
|
636
|
+
.select(['id', 'name']) // Only what you need
|
|
637
|
+
.compile()
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// ✅ Good - Stable parameters
|
|
642
|
+
function useUsersByStatus(status: string) {
|
|
643
|
+
return useTypedQuery(
|
|
644
|
+
(db, params) => db
|
|
645
|
+
.selectFrom('users')
|
|
646
|
+
.selectAll()
|
|
647
|
+
.where('status', '=', params.status)
|
|
648
|
+
.compile(),
|
|
649
|
+
{ status } // Simple, stable parameter
|
|
650
|
+
);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
// ❌ Avoid - Unnecessary data
|
|
654
|
+
function useEverythingAboutUsers() {
|
|
655
|
+
return useTypedQuery(db =>
|
|
656
|
+
db.selectFrom('users')
|
|
657
|
+
.leftJoin('posts', 'users.id', 'posts.author_id')
|
|
658
|
+
.selectAll() // Too much data
|
|
659
|
+
.compile()
|
|
660
|
+
);
|
|
661
|
+
}
|
|
662
|
+
```
|
|
663
|
+
|
|
664
|
+
</details>
|
|
665
|
+
|
|
666
|
+
## Common Issues and Solutions
|
|
667
|
+
|
|
668
|
+
### Query Not Updating
|
|
669
|
+
|
|
670
|
+
<details>
|
|
671
|
+
<summary>My query results aren't updating when I expect them to</summary>
|
|
672
|
+
|
|
673
|
+
### Problem
|
|
674
|
+
Your query results don't update when you expect them to, even though you've changed parameters.
|
|
675
|
+
|
|
676
|
+
### Solution
|
|
677
|
+
Check that your parameters are actually changing in content, not just reference:
|
|
678
|
+
|
|
679
|
+
```typescript
|
|
680
|
+
// ✅ Good - Parameters change in content
|
|
681
|
+
const [userId, setUserId] = useState(1);
|
|
682
|
+
const result = useUserById(userId); // Updates when userId changes
|
|
683
|
+
|
|
684
|
+
// ❌ Common mistake - Same content, different objects
|
|
685
|
+
const result = useTypedQuery(
|
|
686
|
+
(db, params) => /* query */,
|
|
687
|
+
{ userId: user.id } // New object every render, but same content
|
|
688
|
+
);
|
|
689
|
+
|
|
690
|
+
// ✅ Better - Extract stable values
|
|
691
|
+
const userId = user.id;
|
|
692
|
+
const result = useTypedQuery(
|
|
693
|
+
(db, params) => /* query */,
|
|
694
|
+
{ userId } // Stable parameter
|
|
695
|
+
);
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
### Debugging Tips
|
|
699
|
+
- Log your parameters to see if they're actually changing
|
|
700
|
+
- Check the `isLoading` state to see if queries are re-running
|
|
701
|
+
- Use React DevTools to inspect hook state changes
|
|
702
|
+
|
|
703
|
+
</details>
|
|
704
|
+
|
|
705
|
+
### Type Errors
|
|
706
|
+
|
|
707
|
+
<details>
|
|
708
|
+
<summary>Getting TypeScript errors with my queries</summary>
|
|
709
|
+
|
|
710
|
+
### Problem
|
|
711
|
+
TypeScript is showing errors about query return types or database schema.
|
|
712
|
+
|
|
713
|
+
### Solution
|
|
714
|
+
Make sure your callback returns the correct type:
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
// ✅ Good - Returns QueryCallbackReturnType
|
|
718
|
+
const result = useTypedQuery(db => {
|
|
719
|
+
return db.selectFrom('users').selectAll().compile(); // Has sql property
|
|
720
|
+
});
|
|
721
|
+
|
|
722
|
+
// ❌ Error - Missing .compile()
|
|
723
|
+
const result = useTypedQuery(db => {
|
|
724
|
+
return db.selectFrom('users').selectAll(); // No sql property
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// ✅ Good - Raw SQL format
|
|
728
|
+
const result = useTypedQuery(() => {
|
|
729
|
+
return {
|
|
730
|
+
sql: 'SELECT * FROM users',
|
|
731
|
+
parameters: []
|
|
732
|
+
};
|
|
733
|
+
});
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
</details>
|
|
737
|
+
|
|
738
|
+
### Performance Issues
|
|
739
|
+
|
|
740
|
+
<details>
|
|
741
|
+
<summary>My queries are running too frequently or causing lag</summary>
|
|
742
|
+
|
|
743
|
+
### Problem
|
|
744
|
+
Your queries are running more often than expected, causing performance issues.
|
|
745
|
+
|
|
746
|
+
### Solution
|
|
747
|
+
Check for unstable parameters or overly complex queries:
|
|
748
|
+
|
|
749
|
+
```typescript
|
|
750
|
+
// ❌ Problem - New object every render
|
|
751
|
+
function BadComponent({ user }) {
|
|
752
|
+
const result = useTypedQuery(
|
|
753
|
+
(db, params) => /* query */,
|
|
754
|
+
{
|
|
755
|
+
filter: { status: 'active', dept: user.department } // New object each render
|
|
756
|
+
}
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// ✅ Solution - Stable parameters
|
|
761
|
+
function GoodComponent({ user }) {
|
|
762
|
+
const filter = useMemo(() => ({
|
|
763
|
+
status: 'active',
|
|
764
|
+
dept: user.department
|
|
765
|
+
}), [user.department]);
|
|
766
|
+
|
|
767
|
+
const result = useTypedQuery(
|
|
768
|
+
(db, params) => /* query */,
|
|
769
|
+
{ filter }
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
// ✅ Even better - Direct values
|
|
774
|
+
function BetterComponent({ user }) {
|
|
775
|
+
const result = useTypedQuery(
|
|
776
|
+
(db, params) => /* query */,
|
|
777
|
+
{
|
|
778
|
+
status: 'active',
|
|
779
|
+
dept: user.department
|
|
780
|
+
}
|
|
781
|
+
);
|
|
782
|
+
}
|
|
783
|
+
```
|
|
784
|
+
|
|
785
|
+
</details>
|
|
786
|
+
|
|
787
|
+
## Further Reading
|
|
788
|
+
|
|
789
|
+
- [Kysely Documentation](https://kysely.dev/) - Learn more about the query builder
|
|
790
|
+
- [PGlite Documentation](https://pglite.dev/) - Understanding the underlying database
|
|
791
|
+
- [React Hooks](/academy/APIReferences/ReactHooks) - Other available hooks in Powerhouse
|
|
792
|
+
- [Component Library](/academy/ComponentLibrary/DocumentEngineering) - Building UI components
|
|
793
|
+
|
|
794
|
+
## Related Hooks
|
|
795
|
+
|
|
796
|
+
- [`useDocuments`](/academy/APIReferences/ReactHooks#usedocuments) - Working with Powerhouse documents
|
|
797
|
+
- [`useDrives`](/academy/APIReferences/ReactHooks#usedrives) - Managing document drives
|
|
798
|
+
- [`useSelectedDocument`](/academy/APIReferences/ReactHooks#useselecteddocument) - Document selection state
|