@agelum/backend 0.1.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 +401 -0
- package/dist/client/hooks.d.ts +65 -0
- package/dist/client/hooks.d.ts.map +1 -0
- package/dist/client/hooks.js +339 -0
- package/dist/client/hooks.js.map +1 -0
- package/dist/client/index.d.ts +10 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +37 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/manager.d.ts +137 -0
- package/dist/client/manager.d.ts.map +1 -0
- package/dist/client/manager.js +292 -0
- package/dist/client/manager.js.map +1 -0
- package/dist/client/provider.d.ts +25 -0
- package/dist/client/provider.d.ts.map +1 -0
- package/dist/client/provider.js +121 -0
- package/dist/client/provider.js.map +1 -0
- package/dist/client/revalidation.d.ts +101 -0
- package/dist/client/revalidation.d.ts.map +1 -0
- package/dist/client/revalidation.js +313 -0
- package/dist/client/revalidation.js.map +1 -0
- package/dist/client/session.d.ts +84 -0
- package/dist/client/session.d.ts.map +1 -0
- package/dist/client/session.js +186 -0
- package/dist/client/session.js.map +1 -0
- package/dist/client/sse-client.d.ts +81 -0
- package/dist/client/sse-client.d.ts.map +1 -0
- package/dist/client/sse-client.js +221 -0
- package/dist/client/sse-client.js.map +1 -0
- package/dist/client/storage.d.ts +124 -0
- package/dist/client/storage.d.ts.map +1 -0
- package/dist/client/storage.js +441 -0
- package/dist/client/storage.js.map +1 -0
- package/dist/client/trpc.d.ts +12 -0
- package/dist/client/trpc.d.ts.map +1 -0
- package/dist/client/trpc.js +36 -0
- package/dist/client/trpc.js.map +1 -0
- package/dist/client/types.d.ts +10 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +3 -0
- package/dist/client/types.js.map +1 -0
- package/dist/client.d.ts +12 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +26 -0
- package/dist/client.js.map +1 -0
- package/dist/config/schema.d.ts +261 -0
- package/dist/config/schema.d.ts.map +1 -0
- package/dist/config/schema.js +69 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/core/analyzer.d.ts +15 -0
- package/dist/core/analyzer.d.ts.map +1 -0
- package/dist/core/analyzer.js +217 -0
- package/dist/core/analyzer.js.map +1 -0
- package/dist/core/driver.d.ts +7 -0
- package/dist/core/driver.d.ts.map +1 -0
- package/dist/core/driver.js +261 -0
- package/dist/core/driver.js.map +1 -0
- package/dist/core/function.d.ts +97 -0
- package/dist/core/function.d.ts.map +1 -0
- package/dist/core/function.js +252 -0
- package/dist/core/function.js.map +1 -0
- package/dist/core/sse.d.ts +98 -0
- package/dist/core/sse.d.ts.map +1 -0
- package/dist/core/sse.js +331 -0
- package/dist/core/sse.js.map +1 -0
- package/dist/core/types.d.ts +179 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +3 -0
- package/dist/core/types.js.map +1 -0
- package/dist/examples/teamhub-integration.d.ts +56 -0
- package/dist/examples/teamhub-integration.d.ts.map +1 -0
- package/dist/examples/teamhub-integration.js +188 -0
- package/dist/examples/teamhub-integration.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/localStorage.d.ts +13 -0
- package/dist/providers/localStorage.d.ts.map +1 -0
- package/dist/providers/localStorage.js +64 -0
- package/dist/providers/localStorage.js.map +1 -0
- package/dist/providers/memory.d.ts +13 -0
- package/dist/providers/memory.d.ts.map +1 -0
- package/dist/providers/memory.js +40 -0
- package/dist/providers/memory.js.map +1 -0
- package/dist/providers/redis.d.ts +14 -0
- package/dist/providers/redis.d.ts.map +1 -0
- package/dist/providers/redis.js +36 -0
- package/dist/providers/redis.js.map +1 -0
- package/dist/server.d.ts +17 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +34 -0
- package/dist/server.js.map +1 -0
- package/dist/trpc/hooks.d.ts +82 -0
- package/dist/trpc/hooks.d.ts.map +1 -0
- package/dist/trpc/hooks.js +282 -0
- package/dist/trpc/hooks.js.map +1 -0
- package/dist/trpc/router.d.ts +75 -0
- package/dist/trpc/router.d.ts.map +1 -0
- package/dist/trpc/router.js +160 -0
- package/dist/trpc/router.js.map +1 -0
- package/dist/trpc/types.d.ts +105 -0
- package/dist/trpc/types.d.ts.map +1 -0
- package/dist/trpc/types.js +6 -0
- package/dist/trpc/types.js.map +1 -0
- package/package.json +87 -0
package/README.md
ADDED
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
# @drizzle/reactive
|
|
2
|
+
|
|
3
|
+
**Zero configuration, maximum intelligence. Reactive everywhere with no boilerplate.**
|
|
4
|
+
|
|
5
|
+
A reactive database library that transforms any Drizzle + tRPC setup into a reactive, real-time system with minimal configuration and zero boilerplate code changes.
|
|
6
|
+
|
|
7
|
+
## ✨ Features
|
|
8
|
+
|
|
9
|
+
- **🚀 Zero Configuration**: Single config file with just table relations
|
|
10
|
+
- **⚡ Instant Cache**: Shows cached data immediately, revalidates smartly
|
|
11
|
+
- **🔄 Real-time Sync**: Built-in Server-Sent Events for cache invalidation
|
|
12
|
+
- **🎯 Smart Invalidation**: Only invalidates relevant queries based on relations
|
|
13
|
+
- **📱 Offline Ready**: Handles page refresh and session gaps gracefully
|
|
14
|
+
- **🔒 Type Safe**: 100% automatic type safety with tRPC integration
|
|
15
|
+
- **☁️ Vercel Compatible**: Works perfectly with serverless deployment
|
|
16
|
+
- **🧠 Intelligent**: Prioritizes active hooks for better UX
|
|
17
|
+
|
|
18
|
+
## 🚀 Quick Start
|
|
19
|
+
|
|
20
|
+
### Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pnpm add @drizzle/reactive drizzle-orm @trpc/server @trpc/client zod
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 📖 Core Usage Patterns
|
|
27
|
+
|
|
28
|
+
### 1. Define Reactive Functions
|
|
29
|
+
|
|
30
|
+
**Key Concept**: Reactive functions work both standalone (server-side) AND via tRPC. The `name` property is crucial for cache keys and tRPC procedures. Use `db.db` to access the underlying Drizzle instance inside handlers.
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
// server/functions/users.ts
|
|
34
|
+
import { defineReactiveFunction } from '@drizzle/reactive/server'
|
|
35
|
+
import { z } from 'zod'
|
|
36
|
+
|
|
37
|
+
// 1. Define a reactive function with explicit name
|
|
38
|
+
export const getUsers = defineReactiveFunction({
|
|
39
|
+
name: 'users.getAll', // 🔑 This becomes the cache key and tRPC procedure name
|
|
40
|
+
|
|
41
|
+
input: z.object({
|
|
42
|
+
companyId: z.string(), // Generic, not hardcoded organizationId
|
|
43
|
+
limit: z.number().optional().default(50),
|
|
44
|
+
}),
|
|
45
|
+
|
|
46
|
+
dependencies: ['user'], // What tables this function reads from
|
|
47
|
+
|
|
48
|
+
handler: async (input, db) => {
|
|
49
|
+
// Clean signature: (input, db)
|
|
50
|
+
return db.db.query.users.findMany({
|
|
51
|
+
where: (users, { eq }) => eq(users.companyId, input.companyId),
|
|
52
|
+
limit: input.limit,
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
export const createUser = defineReactiveFunction({
|
|
58
|
+
name: 'users.create',
|
|
59
|
+
|
|
60
|
+
input: z.object({
|
|
61
|
+
name: z.string(),
|
|
62
|
+
email: z.string().email(),
|
|
63
|
+
companyId: z.string(),
|
|
64
|
+
}),
|
|
65
|
+
|
|
66
|
+
dependencies: ['user'],
|
|
67
|
+
|
|
68
|
+
handler: async (input, db) => {
|
|
69
|
+
return db.db.insert(users).values(input).returning()
|
|
70
|
+
},
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
export const getUserProfile = defineReactiveFunction({
|
|
74
|
+
name: 'users.profile.getDetailed', // 🏷️ Nested names work perfectly
|
|
75
|
+
|
|
76
|
+
input: z.object({
|
|
77
|
+
userId: z.string(),
|
|
78
|
+
}),
|
|
79
|
+
|
|
80
|
+
dependencies: ['user', 'profile', 'preferences'],
|
|
81
|
+
|
|
82
|
+
handler: async (input, db) => {
|
|
83
|
+
const user = await db.db.query.users.findFirst({
|
|
84
|
+
where: (users, { eq }) => eq(users.id, input.userId),
|
|
85
|
+
with: {
|
|
86
|
+
profile: true,
|
|
87
|
+
preferences: true,
|
|
88
|
+
},
|
|
89
|
+
})
|
|
90
|
+
return user
|
|
91
|
+
},
|
|
92
|
+
})
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 2. Server-Side Execution (Without tRPC)
|
|
96
|
+
|
|
97
|
+
**Use Case**: Background jobs, API routes, server actions, webhooks, etc.
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
// server/api/users/route.ts - Next.js API route
|
|
101
|
+
import { getUsers, createUser } from '../functions/users'
|
|
102
|
+
import { db } from '../db'
|
|
103
|
+
|
|
104
|
+
export async function GET(request: Request) {
|
|
105
|
+
const { searchParams } = new URL(request.url)
|
|
106
|
+
const companyId = searchParams.get('companyId')!
|
|
107
|
+
|
|
108
|
+
// ✅ Execute reactive function directly on server
|
|
109
|
+
const users = await getUsers.execute(
|
|
110
|
+
{ companyId, limit: 20 },
|
|
111
|
+
db // Your reactive database instance
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return Response.json({ users })
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export async function POST(request: Request) {
|
|
118
|
+
const body = await request.json()
|
|
119
|
+
|
|
120
|
+
// ✅ Execute reactive function directly on server
|
|
121
|
+
const newUser = await createUser.execute(body, db)
|
|
122
|
+
|
|
123
|
+
return Response.json({ user: newUser })
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
// server/jobs/daily-stats.ts - Background job
|
|
129
|
+
import { getUsers } from '../functions/users'
|
|
130
|
+
import { db } from '../db'
|
|
131
|
+
|
|
132
|
+
export async function generateDailyStats() {
|
|
133
|
+
const companies = await db.db.query.companies.findMany()
|
|
134
|
+
|
|
135
|
+
for (const company of companies) {
|
|
136
|
+
// ✅ Execute reactive function in background job
|
|
137
|
+
const users = await getUsers.execute({ companyId: company.id }, db)
|
|
138
|
+
|
|
139
|
+
// Process stats...
|
|
140
|
+
console.log(`Company ${company.name} has ${users.length} users`)
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3. tRPC Integration (Auto-Generated)
|
|
146
|
+
|
|
147
|
+
**Key Feature**: The tRPC router automatically uses the function `name` as the procedure name. Call `.build()` to get the final tRPC router instance.
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
// server/trpc/router.ts
|
|
151
|
+
import { createReactiveRouter } from '@drizzle/reactive/server'
|
|
152
|
+
import { getUsers, createUser, getUserProfile } from '../functions/users'
|
|
153
|
+
import { db } from '../db'
|
|
154
|
+
|
|
155
|
+
export const appRouter = createReactiveRouter({ db })
|
|
156
|
+
.addQuery(getUsers) // 🔄 Creates procedure: users.getAll
|
|
157
|
+
.addMutation(createUser) // 🔄 Creates procedure: users.create
|
|
158
|
+
.addQuery(getUserProfile) // 🔄 Creates procedure: users.profile.getDetailed
|
|
159
|
+
.build()
|
|
160
|
+
|
|
161
|
+
// ✅ Auto-generated procedures from function names:
|
|
162
|
+
// - users.getAll (query)
|
|
163
|
+
// - users.create (mutation)
|
|
164
|
+
// - users.profile.getDetailed (query)
|
|
165
|
+
|
|
166
|
+
export type AppRouter = typeof appRouter
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 4. Client-Side Usage (React Hooks)
|
|
170
|
+
|
|
171
|
+
**Zero Configuration**: Just use the tRPC procedure names (which match function names).
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// client/components/UserList.tsx
|
|
175
|
+
import { useReactive } from '@drizzle/reactive/client'
|
|
176
|
+
|
|
177
|
+
function UserList({ companyId }: { companyId: string }) {
|
|
178
|
+
// ✅ Uses the function name automatically: 'users.getAll'
|
|
179
|
+
const {
|
|
180
|
+
data: users,
|
|
181
|
+
isStale,
|
|
182
|
+
isLoading,
|
|
183
|
+
} = useReactive('users.getAll', {
|
|
184
|
+
companyId,
|
|
185
|
+
limit: 20,
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
if (isLoading) return <div>Loading...</div>
|
|
189
|
+
|
|
190
|
+
return (
|
|
191
|
+
<div>
|
|
192
|
+
{isStale && <div className="text-orange-500">Syncing...</div>}
|
|
193
|
+
|
|
194
|
+
{users?.map((user) => (
|
|
195
|
+
<UserCard key={user.id} user={user} />
|
|
196
|
+
))}
|
|
197
|
+
</div>
|
|
198
|
+
)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function UserProfile({ userId }: { userId: string }) {
|
|
202
|
+
// ✅ Nested function names work perfectly
|
|
203
|
+
const { data: profile } = useReactive('users.profile.getDetailed', {
|
|
204
|
+
userId,
|
|
205
|
+
})
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div>
|
|
209
|
+
<h1>{profile?.name}</h1>
|
|
210
|
+
<p>{profile?.email}</p>
|
|
211
|
+
{/* Profile details... */}
|
|
212
|
+
</div>
|
|
213
|
+
)
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### 5. Mutations with Real-time Updates
|
|
218
|
+
|
|
219
|
+
```typescript
|
|
220
|
+
// client/components/CreateUserForm.tsx
|
|
221
|
+
import { useMutation } from '@trpc/react-query'
|
|
222
|
+
import { trpc } from '../trpc'
|
|
223
|
+
|
|
224
|
+
function CreateUserForm({ companyId }: { companyId: string }) {
|
|
225
|
+
const createUserMutation = trpc.users.create.useMutation({
|
|
226
|
+
onSuccess: () => {
|
|
227
|
+
// ✅ Automatic cache invalidation happens via SSE
|
|
228
|
+
// No manual invalidation needed!
|
|
229
|
+
},
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
const handleSubmit = (data: FormData) => {
|
|
233
|
+
createUserMutation.mutate({
|
|
234
|
+
name: data.get('name') as string,
|
|
235
|
+
email: data.get('email') as string,
|
|
236
|
+
companyId,
|
|
237
|
+
})
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return <form onSubmit={handleSubmit}>{/* Form fields... */}</form>
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
## 🏗️ Setup
|
|
245
|
+
|
|
246
|
+
### 1. Database Configuration
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// server/db.ts
|
|
250
|
+
import { createReactiveDb } from '@drizzle/reactive/server'
|
|
251
|
+
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
252
|
+
|
|
253
|
+
const config = {
|
|
254
|
+
relations: {
|
|
255
|
+
// Relations are table names (not column paths)
|
|
256
|
+
// When user table changes, invalidate these queries
|
|
257
|
+
user: ['profile', 'preferences'],
|
|
258
|
+
|
|
259
|
+
// When profile table changes, invalidate these queries
|
|
260
|
+
profile: ['user'],
|
|
261
|
+
|
|
262
|
+
// When preferences table changes, invalidate these queries
|
|
263
|
+
preferences: ['user'],
|
|
264
|
+
},
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
export const db = createReactiveDb(drizzle(pool), config)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
### 2. SSE Endpoint (Next.js)
|
|
271
|
+
|
|
272
|
+
```typescript
|
|
273
|
+
// app/api/events/route.ts
|
|
274
|
+
import { createSSEStream } from '@drizzle/reactive/server'
|
|
275
|
+
|
|
276
|
+
export async function GET(request: Request) {
|
|
277
|
+
const { searchParams } = new URL(request.url)
|
|
278
|
+
const organizationId = searchParams.get('organizationId')!
|
|
279
|
+
|
|
280
|
+
return createSSEStream(organizationId)
|
|
281
|
+
}
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// app/api/events/ack/route.ts
|
|
286
|
+
// Required for reliable delivery: client acks invalidation events
|
|
287
|
+
import { acknowledgeEvent } from '@drizzle/reactive/server'
|
|
288
|
+
|
|
289
|
+
export async function POST(request: Request) {
|
|
290
|
+
const { eventId } = await request.json()
|
|
291
|
+
acknowledgeEvent(eventId)
|
|
292
|
+
return Response.json({ ok: true })
|
|
293
|
+
}
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### 3. Client Setup
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// client/providers/ReactiveProvider.tsx
|
|
300
|
+
// Recommended: use the built-in TrpcReactiveProvider to wire revalidation generically
|
|
301
|
+
'use client'
|
|
302
|
+
import { TrpcReactiveProvider } from '@drizzle/reactive/client'
|
|
303
|
+
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client'
|
|
304
|
+
import type { AppRouter } from '../server/trpc'
|
|
305
|
+
import { reactiveRelations } from '@your-db-package/reactive-config'
|
|
306
|
+
|
|
307
|
+
const trpcClient = createTRPCProxyClient<AppRouter>({
|
|
308
|
+
links: [httpBatchLink({ url: '/api/trpc' })],
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
export function AppProviders({ children }: { children: React.ReactNode }) {
|
|
312
|
+
const organizationId = 'your-organization-id'
|
|
313
|
+
return (
|
|
314
|
+
<TrpcReactiveProvider
|
|
315
|
+
organizationId={organizationId}
|
|
316
|
+
relations={reactiveRelations}
|
|
317
|
+
trpcClient={trpcClient}
|
|
318
|
+
>
|
|
319
|
+
{children}
|
|
320
|
+
</TrpcReactiveProvider>
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Alternatively, you can create your own revalidateFn with createTrpcRevalidateFn
|
|
325
|
+
// and pass it to ReactiveProvider if you need custom behavior.
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
If you use `ReactiveProvider` directly, make sure to pass a `revalidateFn` for production; the default revalidator returns mock data.
|
|
329
|
+
|
|
330
|
+
### 4.1 Client Storage & Revalidation Details
|
|
331
|
+
|
|
332
|
+
- The hook composes cache keys as `name::JSON(input)`.
|
|
333
|
+
- LocalStorage is sharded per query to avoid large single entries:
|
|
334
|
+
- Index per organization: `reactive_registry_<orgId>` stores metadata (last revalidated, last server change, connection status).
|
|
335
|
+
- Per-query entry key: `@drizzle/reactive:entry:<orgId>:<hash>` stores `{ name, input, queryKey, data }`.
|
|
336
|
+
- On initial render, cached data (if present) is shown immediately; background revalidation respects a minimum time window to avoid thrashing on quick navigations/refreshes.
|
|
337
|
+
- Errors during revalidation do not overwrite existing cache (no-write-on-error), keeping previously known-good data.
|
|
338
|
+
- Real-time invalidation uses SSE with client acknowledgments and retry; no heartbeats are sent.
|
|
339
|
+
|
|
340
|
+
### 4.2 Multi-tenant Tips (Optional)
|
|
341
|
+
|
|
342
|
+
- Resolve tenant databases via a main database lookup (e.g., `organization.databaseName`), not by using IDs directly as database names.
|
|
343
|
+
- Read paths should not create databases; handle missing DB (`3D000`) by propagating the error or returning empty based on product policy.
|
|
344
|
+
- Provisioning (create DB/schemas) belongs to explicit setup flows.
|
|
345
|
+
|
|
346
|
+
## 🎯 Key Benefits Over Manual Approach
|
|
347
|
+
|
|
348
|
+
| Feature | Manual tRPC | @drizzle/reactive |
|
|
349
|
+
| ----------------------- | ---------------------------------- | ------------------------------- |
|
|
350
|
+
| **Function Definition** | Separate function + tRPC procedure | Single `defineReactiveFunction` |
|
|
351
|
+
| **Cache Keys** | Manual generation | Auto from function name |
|
|
352
|
+
| **Invalidation** | Manual `invalidateQueries` | Automatic via relations |
|
|
353
|
+
| **Real-time** | Manual WebSocket setup | Built-in SSE |
|
|
354
|
+
| **Server Execution** | Separate function needed | Same function works everywhere |
|
|
355
|
+
| **Type Safety** | Manual type wiring | 100% automatic |
|
|
356
|
+
|
|
357
|
+
## 📈 Advanced Usage
|
|
358
|
+
|
|
359
|
+
### Custom tRPC Procedure Names
|
|
360
|
+
|
|
361
|
+
```typescript
|
|
362
|
+
// If you need different tRPC names than function names
|
|
363
|
+
const router = createReactiveRouter({ db })
|
|
364
|
+
.addQueryWithName(getUsers, 'getAllUsers') // Custom name
|
|
365
|
+
.addQuery(getUserProfile) // Uses function name: 'users.profile.getDetailed'
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
### Background Revalidation
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
// client/hooks.ts
|
|
372
|
+
function MyComponent() {
|
|
373
|
+
useReactivePriorities([
|
|
374
|
+
'users.getAll', // High priority (visible)
|
|
375
|
+
'users.profile.getDetailed', // Medium priority (likely next)
|
|
376
|
+
])
|
|
377
|
+
|
|
378
|
+
// Component content...
|
|
379
|
+
}
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
## 🔧 How It Works
|
|
383
|
+
|
|
384
|
+
1. **Function Definition**: `defineReactiveFunction` creates functions that work both server-side and via tRPC
|
|
385
|
+
2. **Name-Based Mapping**: The `name` property becomes both the cache key and tRPC procedure name
|
|
386
|
+
3. **Auto-Generated Router**: `createReactiveRouter` automatically creates tRPC procedures from functions
|
|
387
|
+
4. **Smart Caching**: Cache keys are generated from function names and inputs. The React hook composes a key as `name::JSON(input)` internally to uniquely cache and revalidate by input.
|
|
388
|
+
5. **Real-time Updates**: SSE automatically invalidates affected queries when data changes. No heartbeats are sent; reliability is ensured via client acknowledgments and retry.
|
|
389
|
+
6. **Session Recovery**: Smart revalidation on page load handles offline scenarios and avoids thrashing with a minimum revalidation window.
|
|
390
|
+
|
|
391
|
+
## 🤝 Contributing
|
|
392
|
+
|
|
393
|
+
We welcome contributions! Please see our [Contributing Guide](./CONTRIBUTING.md) for details.
|
|
394
|
+
|
|
395
|
+
## 📄 License
|
|
396
|
+
|
|
397
|
+
MIT License - see [LICENSE](./LICENSE) for details.
|
|
398
|
+
|
|
399
|
+
---
|
|
400
|
+
|
|
401
|
+
**Made with ❤️ by the TeamHub team**
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hooks for @drizzle/reactive
|
|
3
|
+
* Provides reactive data access with automatic caching and real-time updates
|
|
4
|
+
*/
|
|
5
|
+
import type { ReactiveConfig, InvalidationEvent } from '../core/types';
|
|
6
|
+
/**
|
|
7
|
+
* Initialize the reactive client
|
|
8
|
+
* Call this once at app startup
|
|
9
|
+
*/
|
|
10
|
+
export declare function initializeReactiveClient(organizationId: string, config: ReactiveConfig, revalidateFn: (queryKey: string) => Promise<any>): void;
|
|
11
|
+
/**
|
|
12
|
+
* Main hook for reactive data access
|
|
13
|
+
* Automatically handles caching, real-time updates, and revalidation
|
|
14
|
+
*/
|
|
15
|
+
export declare function useReactive<T = any>(queryKey: string, input?: any, options?: {
|
|
16
|
+
enabled?: boolean;
|
|
17
|
+
}): {
|
|
18
|
+
data: T | undefined;
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
isStale: boolean;
|
|
21
|
+
error: Error | null;
|
|
22
|
+
refetch: () => void;
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Optional page-level priority hints for better UX
|
|
26
|
+
*/
|
|
27
|
+
export declare function useReactivePriorities(priorities: string[]): void;
|
|
28
|
+
/**
|
|
29
|
+
* Hook to get current session statistics
|
|
30
|
+
*/
|
|
31
|
+
export declare function useReactiveStats(): any;
|
|
32
|
+
/**
|
|
33
|
+
* Hook to manually trigger cache refresh
|
|
34
|
+
*/
|
|
35
|
+
export declare function useReactiveRefresh(): () => Promise<void>;
|
|
36
|
+
/**
|
|
37
|
+
* Hook to get revalidation statistics and performance metrics
|
|
38
|
+
*/
|
|
39
|
+
export declare function useRevalidationStats(): any;
|
|
40
|
+
/**
|
|
41
|
+
* Hook to handle invalidation events
|
|
42
|
+
*/
|
|
43
|
+
export declare function useReactiveInvalidation(callback: (event: InvalidationEvent) => void): void;
|
|
44
|
+
/**
|
|
45
|
+
* Hook for manual queries with dynamic parameters
|
|
46
|
+
* Unlike useReactive, this hook doesn't auto-fetch and allows changing parameters on each call
|
|
47
|
+
*/
|
|
48
|
+
export declare function useReactiveQuery<TData = unknown, TVariables = unknown>(queryKey: string): {
|
|
49
|
+
data: TData | undefined;
|
|
50
|
+
isLoading: boolean;
|
|
51
|
+
error: Error | null;
|
|
52
|
+
refetch: (variables?: TVariables) => Promise<TData>;
|
|
53
|
+
run: (variables: TVariables) => Promise<TData>;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Hook to get connection status
|
|
57
|
+
*/
|
|
58
|
+
export declare function useReactiveConnection(): {
|
|
59
|
+
status: "connected" | "connecting" | "disconnected";
|
|
60
|
+
isConnected: boolean;
|
|
61
|
+
isConnecting: boolean;
|
|
62
|
+
isDisconnected: boolean;
|
|
63
|
+
reconnect: () => void;
|
|
64
|
+
};
|
|
65
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../src/client/hooks.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAA;AAiBtE;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,cAAc,EAAE,MAAM,EACtB,MAAM,EAAE,cAAc,EACtB,YAAY,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,CAAC,GAC/C,IAAI,CA+BN;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,GAAG,EACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,CAAC,EAAE,GAAG,EACX,OAAO,CAAC,EAAE;IACR,OAAO,CAAC,EAAE,OAAO,CAAA;CAClB,GACA;IACD,IAAI,EAAE,CAAC,GAAG,SAAS,CAAA;IACnB,SAAS,EAAE,OAAO,CAAA;IAClB,OAAO,EAAE,OAAO,CAAA;IAChB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,MAAM,IAAI,CAAA;CACpB,CA2JA;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI,CAYhE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,QAmB/B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,wBAiBjC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,QAiBnC;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,QAAQ,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,IAAI,GAC3C,IAAI,CAWN;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,GAAG,OAAO,EAAE,UAAU,GAAG,OAAO,EACpE,QAAQ,EAAE,MAAM,GACf;IACD,IAAI,EAAE,KAAK,GAAG,SAAS,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;IAClB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAA;IACnB,OAAO,EAAE,CAAC,SAAS,CAAC,EAAE,UAAU,KAAK,OAAO,CAAC,KAAK,CAAC,CAAA;IACnD,GAAG,EAAE,CAAC,SAAS,EAAE,UAAU,KAAK,OAAO,CAAC,KAAK,CAAC,CAAA;CAC/C,CAgEA;AAED;;GAEG;AACH,wBAAgB,qBAAqB;;;;;;EAwBpC"}
|