@spooky-sync/client-solid 0.0.0-canary.1

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.
@@ -0,0 +1,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(kubectl get:*)",
5
+ "Bash(helm list:*)",
6
+ "Bash(kubectl describe:*)"
7
+ ],
8
+ "deny": [],
9
+ "ask": []
10
+ }
11
+ }
package/QUICK_START.md ADDED
@@ -0,0 +1,415 @@
1
+ # Quick Start Guide
2
+
3
+ ## Installation
4
+
5
+ ```bash
6
+ pnpm add db-solid
7
+ ```
8
+
9
+ ## Basic Setup
10
+
11
+ ### 1. Configure Database
12
+
13
+ ```typescript
14
+ // db.ts
15
+ import { SyncedDb, type SyncedDbConfig } from 'db-solid';
16
+ import { type Schema, SURQL_SCHEMA } from './schema.gen';
17
+
18
+ export const dbConfig: SyncedDbConfig<Schema> = {
19
+ schema: SURQL_SCHEMA,
20
+ localDbName: 'my-app-local',
21
+ internalDbName: 'syncdb-int',
22
+ storageStrategy: 'indexeddb',
23
+ namespace: 'main',
24
+ database: 'my_db',
25
+ remoteUrl: 'http://localhost:8000', // Your SurrealDB server
26
+ tables: ['user', 'thread', 'comment'],
27
+ };
28
+
29
+ export const db = new SyncedDb<Schema>(dbConfig);
30
+
31
+ export async function initDatabase(): Promise<void> {
32
+ await db.init();
33
+ console.log('Database initialized');
34
+ }
35
+ ```
36
+
37
+ ### 2. Initialize in Your App
38
+
39
+ ```typescript
40
+ // App.tsx
41
+ import { initDatabase } from "./db";
42
+
43
+ function App() {
44
+ onMount(async () => {
45
+ await initDatabase();
46
+ });
47
+
48
+ return <Router>{/* your routes */}</Router>;
49
+ }
50
+ ```
51
+
52
+ ## Usage Examples
53
+
54
+ ### Querying Data with Live Updates
55
+
56
+ ```typescript
57
+ import { db } from "./db";
58
+ import { createSignal, createEffect, onMount, onCleanup } from "solid-js";
59
+
60
+ function ThreadList() {
61
+ const [threads, setThreads] = createSignal([]);
62
+
63
+ onMount(async () => {
64
+ // Create a live query that automatically updates
65
+ const liveQuery = await db.query.thread
66
+ .find({})
67
+ .orderBy("created_at", "desc")
68
+ .query();
69
+
70
+ // React to changes
71
+ createEffect(() => {
72
+ setThreads([...liveQuery.data]);
73
+ });
74
+
75
+ // Cleanup when component unmounts
76
+ onCleanup(() => {
77
+ liveQuery.kill();
78
+ });
79
+ });
80
+
81
+ return (
82
+ <For each={threads()}>
83
+ {(thread) => <ThreadCard thread={thread} />}
84
+ </For>
85
+ );
86
+ }
87
+ ```
88
+
89
+ ### Filtered Queries
90
+
91
+ ```typescript
92
+ // Find threads by specific criteria
93
+ const liveQuery = await db.query.thread
94
+ .find({ status: 'active', author: currentUserId })
95
+ .orderBy('created_at', 'desc')
96
+ .limit(20)
97
+ .query();
98
+
99
+ // React to changes
100
+ createEffect(() => {
101
+ setThreads([...liveQuery.data]);
102
+ });
103
+ ```
104
+
105
+ ### Creating Records
106
+
107
+ ```typescript
108
+ // Create a new thread on the remote server
109
+ // This will automatically trigger live query updates
110
+ async function createThread(title: string, content: string) {
111
+ const [thread] = await db.query.thread.createRemote({
112
+ title,
113
+ content,
114
+ author: currentUserId,
115
+ created_at: new Date(),
116
+ });
117
+
118
+ return thread;
119
+ }
120
+ ```
121
+
122
+ ### Updating Records
123
+
124
+ ```typescript
125
+ // Update a record on the remote server
126
+ async function updateThread(threadId: RecordId, updates: Partial<Thread>) {
127
+ await db.query.thread.updateRemote(threadId, updates);
128
+ }
129
+ ```
130
+
131
+ ### Deleting Records
132
+
133
+ ```typescript
134
+ // Delete a record on the remote server
135
+ async function deleteThread(threadId: RecordId) {
136
+ await db.query.thread.deleteRemote(threadId);
137
+ }
138
+ ```
139
+
140
+ ## API Cheat Sheet
141
+
142
+ ### Query Builder
143
+
144
+ ```typescript
145
+ db.query.tableName
146
+ .find(whereConditions) // Optional: filter records
147
+ .select('field1', 'field2') // Optional: select specific fields
148
+ .orderBy('field', 'asc') // Optional: sort results
149
+ .limit(50) // Optional: limit results
150
+ .offset(10) // Optional: pagination
151
+ .query(); // Execute and return ReactiveQueryResult
152
+ ```
153
+
154
+ ### CRUD Operations
155
+
156
+ ```typescript
157
+ // Create
158
+ await db.query.tableName.createRemote(data);
159
+ await db.query.tableName.createRemote([data1, data2]); // Batch
160
+
161
+ // Read (live)
162
+ const liveQuery = await db.query.tableName.find().query();
163
+
164
+ // Update
165
+ await db.query.tableName.updateRemote(recordId, updates);
166
+
167
+ // Delete
168
+ await db.query.tableName.deleteRemote(recordId);
169
+ ```
170
+
171
+ ### Cleanup
172
+
173
+ ```typescript
174
+ // Always cleanup live queries when component unmounts
175
+ onCleanup(() => {
176
+ liveQuery.kill();
177
+ });
178
+ ```
179
+
180
+ ## How It Works
181
+
182
+ 1. **Query Creation**: When you call `.query()`, a live query is created on the remote server
183
+ 2. **Initial Data**: Data is fetched from the local cache and displayed immediately
184
+ 3. **Live Updates**: When data changes on the remote server:
185
+ - The syncer receives the change event
186
+ - Local cache is updated automatically
187
+ - Query re-hydrates from local cache
188
+ - UI updates reactively
189
+
190
+ ```
191
+ User Action (createRemote)
192
+
193
+ Remote Server Updates
194
+
195
+ Live Query Event
196
+
197
+ Syncer Updates Local Cache
198
+
199
+ Query Re-hydrates
200
+
201
+ UI Updates (via createEffect)
202
+ ```
203
+
204
+ ## Key Concepts
205
+
206
+ ### Remote vs Local Operations
207
+
208
+ - **`createRemote()`**: Creates on remote server, automatically syncs to local cache
209
+ - **`createLocal()`**: Creates only in local cache (rarely used)
210
+ - **Recommendation**: Always use `Remote` methods for real-time sync
211
+
212
+ ### Query Deduplication
213
+
214
+ Multiple components with identical queries share the same remote subscription:
215
+
216
+ ```typescript
217
+ // Component A
218
+ const query1 = await db.query.thread.find({ status: 'active' }).query();
219
+
220
+ // Component B (different location)
221
+ const query2 = await db.query.thread.find({ status: 'active' }).query();
222
+
223
+ // Result: Only ONE remote live query is created!
224
+ // Both components update simultaneously when data changes
225
+ ```
226
+
227
+ ### Reactive Updates
228
+
229
+ Use `createEffect` to reactively update when query data changes:
230
+
231
+ ```typescript
232
+ const liveQuery = await db.query.thread.find().query();
233
+
234
+ // ✅ Good: Reactively updates
235
+ createEffect(() => {
236
+ setThreads([...liveQuery.data]); // Spread to trigger reactivity
237
+ });
238
+
239
+ // ❌ Bad: Won't update
240
+ const threads = liveQuery.data; // Static reference
241
+ ```
242
+
243
+ ## Common Patterns
244
+
245
+ ### List View
246
+
247
+ ```typescript
248
+ function ListView() {
249
+ const [items, setItems] = createSignal([]);
250
+
251
+ onMount(async () => {
252
+ const liveQuery = await db.query.item
253
+ .find()
254
+ .orderBy("created_at", "desc")
255
+ .query();
256
+
257
+ createEffect(() => setItems([...liveQuery.data]));
258
+ onCleanup(() => liveQuery.kill());
259
+ });
260
+
261
+ return <For each={items()}>{(item) => <ItemCard item={item} />}</For>;
262
+ }
263
+ ```
264
+
265
+ ### Detail View
266
+
267
+ ```typescript
268
+ function DetailView(props: { id: string }) {
269
+ const [item, setItem] = createSignal(null);
270
+
271
+ onMount(async () => {
272
+ const liveQuery = await db.query.item
273
+ .find({ id: new RecordId("item", props.id) })
274
+ .query();
275
+
276
+ createEffect(() => setItem(liveQuery.data[0] || null));
277
+ onCleanup(() => liveQuery.kill());
278
+ });
279
+
280
+ return <Show when={item()}>{(data) => <ItemDetail item={data()} />}</Show>;
281
+ }
282
+ ```
283
+
284
+ ### Form Submission
285
+
286
+ ```typescript
287
+ async function handleSubmit(formData: FormData) {
288
+ try {
289
+ const [newItem] = await db.query.item.createRemote({
290
+ title: formData.title,
291
+ content: formData.content,
292
+ created_at: new Date(),
293
+ });
294
+
295
+ // Navigate to detail page or show success
296
+ navigate(`/item/${newItem.id}`);
297
+ } catch (error) {
298
+ console.error('Failed to create item:', error);
299
+ }
300
+ }
301
+ ```
302
+
303
+ ## Troubleshooting
304
+
305
+ ### Data Not Updating
306
+
307
+ **Problem**: UI doesn't update when data changes
308
+
309
+ **Solution**: Make sure you're using `createEffect` and spreading the array
310
+
311
+ ```typescript
312
+ // ✅ Correct
313
+ createEffect(() => {
314
+ setData([...liveQuery.data]); // Spread creates new reference
315
+ });
316
+
317
+ // ❌ Wrong
318
+ setData(liveQuery.data); // Same reference won't trigger updates
319
+ ```
320
+
321
+ ### Memory Leaks
322
+
323
+ **Problem**: Application gets slower over time
324
+
325
+ **Solution**: Always cleanup live queries
326
+
327
+ ```typescript
328
+ onMount(async () => {
329
+ const liveQuery = await db.query.item.find().query();
330
+
331
+ onCleanup(() => {
332
+ liveQuery.kill(); // Essential!
333
+ });
334
+ });
335
+ ```
336
+
337
+ ### "No syncer available" Warning
338
+
339
+ **Problem**: Warning in console about missing syncer
340
+
341
+ **Solution**: Ensure `remoteUrl` is configured in your `SyncedDbConfig`
342
+
343
+ ```typescript
344
+ export const dbConfig: SyncedDbConfig<Schema> = {
345
+ // ... other config
346
+ remoteUrl: 'http://localhost:8000', // Add this!
347
+ };
348
+ ```
349
+
350
+ ### TypeScript Errors
351
+
352
+ **Problem**: Type errors when accessing query results
353
+
354
+ **Solution**: Make sure your schema types are properly generated
355
+
356
+ ```bash
357
+ # Generate schema types
358
+ pnpm run generate-schema
359
+ ```
360
+
361
+ ## Best Practices
362
+
363
+ 1. **Always use `createRemote()`** for CRUD operations
364
+ 2. **Always cleanup** with `liveQuery.kill()` in `onCleanup()`
365
+ 3. **Use `createEffect()`** to reactively update signals
366
+ 4. **Spread arrays** when setting state: `[...liveQuery.data]`
367
+ 5. **Use specific queries** to benefit from deduplication
368
+ 6. **Paginate large lists** with `.limit()` and `.offset()`
369
+ 7. **Handle errors** in async operations
370
+
371
+ ## Advanced Usage
372
+
373
+ ### Custom Queries
374
+
375
+ ```typescript
376
+ // Use custom SQL for complex queries
377
+ const result = await db.queryRemote(
378
+ `SELECT *, author.* FROM thread WHERE created_at > $timestamp`,
379
+ { timestamp: new Date('2024-01-01') }
380
+ );
381
+
382
+ const [threads] = await result.collect();
383
+ ```
384
+
385
+ ### Authentication
386
+
387
+ ```typescript
388
+ // Authenticate with the database
389
+ await db.authenticate(jwtToken);
390
+ ```
391
+
392
+ ### Multiple Conditions
393
+
394
+ ```typescript
395
+ const liveQuery = await db.query.thread
396
+ .find({ status: 'active' })
397
+ .where({ category: 'tech' }) // Add more conditions
398
+ .orderBy('votes', 'desc')
399
+ .limit(50)
400
+ .query();
401
+ ```
402
+
403
+ ## Next Steps
404
+
405
+ - Read the [Full Architecture Documentation](./LIVE_QUERY_ARCHITECTURE.md)
406
+ - Explore the example app in `/example/app-solid`
407
+ - Check out the SurrealDB documentation: https://surrealdb.com/docs
408
+
409
+ ## Support
410
+
411
+ For issues or questions:
412
+
413
+ - Open an issue on GitHub
414
+ - Check the architecture documentation for detailed explanations
415
+ - Review the example app for implementation patterns