@ereo/db-surrealdb 0.1.6

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 ADDED
@@ -0,0 +1,1094 @@
1
+ # @ereo/db-surrealdb
2
+
3
+ SurrealDB adapter for the EreoJS database abstraction layer. This package provides seamless integration between EreoJS applications and SurrealDB, a multi-model database that supports SQL-like queries, graph relationships, and real-time subscriptions.
4
+
5
+ ## Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Installation](#installation)
9
+ - [Quick Start](#quick-start)
10
+ - [API Reference](#api-reference)
11
+ - [Adapter Factory](#adapter-factory)
12
+ - [Configuration Helpers](#configuration-helpers)
13
+ - [Authentication Helpers](#authentication-helpers)
14
+ - [URL Helpers](#url-helpers)
15
+ - [Preset Configurations](#preset-configurations)
16
+ - [Query Helpers](#query-helpers)
17
+ - [Validation](#validation)
18
+ - [Configuration Options](#configuration-options)
19
+ - [SurrealDB-Specific Features](#surrealdb-specific-features)
20
+ - [SurrealQL Queries](#surrealql-queries)
21
+ - [Record IDs](#record-ids)
22
+ - [Transactions](#transactions)
23
+ - [Connection Protocols](#connection-protocols)
24
+ - [Use Cases with Examples](#use-cases-with-examples)
25
+ - [Integration with Core db Package](#integration-with-core-db-package)
26
+ - [TypeScript Types Reference](#typescript-types-reference)
27
+ - [Troubleshooting / FAQ](#troubleshooting--faq)
28
+
29
+ ## Overview
30
+
31
+ `@ereo/db-surrealdb` implements the `DatabaseAdapter` interface from `@ereo/db`, providing:
32
+
33
+ - Full SurrealDB client integration via the official `surrealdb` package
34
+ - Support for multiple authentication methods (root, namespace, database, record access, scope)
35
+ - HTTP, HTTPS, WebSocket, and secure WebSocket connection protocols
36
+ - Request-scoped query deduplication for optimal performance
37
+ - Transaction support with automatic commit/rollback
38
+ - Health check capabilities
39
+ - Edge runtime compatibility
40
+
41
+ ## Installation
42
+
43
+ ```bash
44
+ # Using bun
45
+ bun add @ereo/db-surrealdb surrealdb
46
+
47
+ # Using npm
48
+ npm install @ereo/db-surrealdb surrealdb
49
+
50
+ # Using pnpm
51
+ pnpm add @ereo/db-surrealdb surrealdb
52
+ ```
53
+
54
+ **Peer Dependencies:**
55
+ - `surrealdb` (^1.0.0) - Required
56
+
57
+ ## Quick Start
58
+
59
+ ### Basic Setup
60
+
61
+ ```typescript
62
+ // ereo.config.ts
63
+ import { defineConfig } from '@ereo/core';
64
+ import { createSurrealAdapter, defineSurrealConfig, createDatabasePlugin } from '@ereo/db-surrealdb';
65
+
66
+ const adapter = createSurrealAdapter(defineSurrealConfig({
67
+ url: 'http://localhost:8000',
68
+ namespace: 'test',
69
+ database: 'test',
70
+ auth: {
71
+ username: 'root',
72
+ password: 'root',
73
+ },
74
+ }));
75
+
76
+ export default defineConfig({
77
+ plugins: [createDatabasePlugin(adapter)],
78
+ });
79
+ ```
80
+
81
+ ### Using in Routes
82
+
83
+ ```typescript
84
+ // routes/users.ts
85
+ import { createLoader } from '@ereo/core';
86
+ import { useDb } from '@ereo/db-surrealdb';
87
+
88
+ export const loader = createLoader({
89
+ load: async ({ context }) => {
90
+ const db = useDb(context);
91
+
92
+ // Use SurrealQL
93
+ const result = await db.client.query('SELECT * FROM users WHERE active = true');
94
+
95
+ // Or use convenience methods
96
+ const users = await db.client.select('users');
97
+
98
+ return users;
99
+ },
100
+ });
101
+ ```
102
+
103
+ ### Environment-Based Configuration
104
+
105
+ ```typescript
106
+ import { createSurrealAdapter, envConfig } from '@ereo/db-surrealdb';
107
+
108
+ // Reads from environment variables:
109
+ // - SURREAL_URL (required)
110
+ // - SURREAL_NAMESPACE (required)
111
+ // - SURREAL_DATABASE (required)
112
+ // - SURREAL_USERNAME (optional)
113
+ // - SURREAL_PASSWORD (optional)
114
+ // - SURREAL_DEBUG (optional, 'true' to enable)
115
+ const adapter = createSurrealAdapter(envConfig());
116
+ ```
117
+
118
+ ## API Reference
119
+
120
+ ### Adapter Factory
121
+
122
+ #### `createSurrealAdapter(config: SurrealDBConfig): DatabaseAdapter<SurrealClient>`
123
+
124
+ Creates a SurrealDB database adapter instance.
125
+
126
+ ```typescript
127
+ import { createSurrealAdapter } from '@ereo/db-surrealdb';
128
+
129
+ const adapter = createSurrealAdapter({
130
+ url: 'http://localhost:8000',
131
+ namespace: 'test',
132
+ database: 'test',
133
+ auth: {
134
+ username: 'root',
135
+ password: 'root',
136
+ },
137
+ });
138
+ ```
139
+
140
+ **Returns:** A `DatabaseAdapter<SurrealClient>` instance implementing:
141
+ - `name`: `'surrealdb'`
142
+ - `edgeCompatible`: `true`
143
+ - `getClient()`: Returns the SurrealDB client
144
+ - `getRequestClient(context)`: Returns a request-scoped client with deduplication
145
+ - `query(sql, params?)`: Execute a SELECT query
146
+ - `execute(sql, params?)`: Execute a mutation (INSERT/UPDATE/DELETE)
147
+ - `transaction(fn, options?)`: Run operations in a transaction
148
+ - `beginTransaction(options?)`: Start a manual transaction
149
+ - `healthCheck()`: Check database connectivity
150
+ - `disconnect()`: Close the connection
151
+
152
+ ### Configuration Helpers
153
+
154
+ #### `defineSurrealConfig(config: SurrealDBConfig): SurrealDBConfig`
155
+
156
+ Type-safe configuration builder with sensible defaults.
157
+
158
+ ```typescript
159
+ import { defineSurrealConfig } from '@ereo/db-surrealdb';
160
+
161
+ const config = defineSurrealConfig({
162
+ url: 'http://localhost:8000',
163
+ namespace: 'test',
164
+ database: 'test',
165
+ auth: {
166
+ username: 'root',
167
+ password: 'root',
168
+ },
169
+ });
170
+
171
+ // Defaults applied:
172
+ // - timeout: 30000
173
+ // - debug: false
174
+ ```
175
+
176
+ ### Authentication Helpers
177
+
178
+ #### `rootAuth(username: string, password: string): RootAuth`
179
+
180
+ Create root-level authentication credentials.
181
+
182
+ ```typescript
183
+ import { rootAuth } from '@ereo/db-surrealdb';
184
+
185
+ const auth = rootAuth('root', 'surrealdb');
186
+ // => { username: 'root', password: 'surrealdb' }
187
+ ```
188
+
189
+ #### `namespaceAuth(namespace: string, username: string, password: string): NamespaceAuth`
190
+
191
+ Create namespace-level authentication credentials.
192
+
193
+ ```typescript
194
+ import { namespaceAuth } from '@ereo/db-surrealdb';
195
+
196
+ const auth = namespaceAuth('myns', 'admin', 'password');
197
+ // => { namespace: 'myns', username: 'admin', password: 'password' }
198
+ ```
199
+
200
+ #### `databaseAuth(namespace: string, database: string, username: string, password: string): DatabaseAuth`
201
+
202
+ Create database-level authentication credentials.
203
+
204
+ ```typescript
205
+ import { databaseAuth } from '@ereo/db-surrealdb';
206
+
207
+ const auth = databaseAuth('myns', 'mydb', 'admin', 'password');
208
+ // => { namespace: 'myns', database: 'mydb', username: 'admin', password: 'password' }
209
+ ```
210
+
211
+ #### `recordAccessAuth(namespace: string, database: string, access: string, variables?: Record<string, unknown>): RecordAccessAuth`
212
+
213
+ Create record access authentication (SurrealDB 2.x+).
214
+
215
+ ```typescript
216
+ import { recordAccessAuth } from '@ereo/db-surrealdb';
217
+
218
+ const auth = recordAccessAuth('myns', 'mydb', 'user', {
219
+ email: 'user@example.com',
220
+ password: 'secret',
221
+ });
222
+ // => { namespace: 'myns', database: 'mydb', access: 'user', variables: { email: '...', password: '...' } }
223
+ ```
224
+
225
+ ### URL Helpers
226
+
227
+ #### `buildSurrealUrl(host: string, port?: number, protocol?: ConnectionProtocol): string`
228
+
229
+ Build a SurrealDB connection URL.
230
+
231
+ ```typescript
232
+ import { buildSurrealUrl } from '@ereo/db-surrealdb';
233
+
234
+ buildSurrealUrl('localhost', 8000, 'http');
235
+ // => 'http://localhost:8000'
236
+
237
+ buildSurrealUrl('cloud.surrealdb.com', 443, 'https');
238
+ // => 'https://cloud.surrealdb.com:443'
239
+
240
+ buildSurrealUrl('localhost', 8000, 'ws');
241
+ // => 'ws://localhost:8000'
242
+
243
+ // With defaults (port: 8000, protocol: 'http')
244
+ buildSurrealUrl('localhost');
245
+ // => 'http://localhost:8000'
246
+ ```
247
+
248
+ #### `parseSurrealUrl(url: string): { protocol: string; host: string; port: number; path: string }`
249
+
250
+ Parse a SurrealDB connection URL into components.
251
+
252
+ ```typescript
253
+ import { parseSurrealUrl } from '@ereo/db-surrealdb';
254
+
255
+ parseSurrealUrl('http://localhost:8000');
256
+ // => { protocol: 'http', host: 'localhost', port: 8000, path: '/' }
257
+
258
+ parseSurrealUrl('https://cloud.surrealdb.com');
259
+ // => { protocol: 'https', host: 'cloud.surrealdb.com', port: 443, path: '/' }
260
+
261
+ parseSurrealUrl('wss://cloud.surrealdb.com:8080/custom');
262
+ // => { protocol: 'wss', host: 'cloud.surrealdb.com', port: 8080, path: '/custom' }
263
+ ```
264
+
265
+ ### Preset Configurations
266
+
267
+ #### `localConfig(namespace: string, database: string, options?: Partial<SurrealDBConfig>): SurrealDBConfig`
268
+
269
+ Create a local development configuration.
270
+
271
+ ```typescript
272
+ import { localConfig, rootAuth } from '@ereo/db-surrealdb';
273
+
274
+ const config = localConfig('test', 'test');
275
+ // => { url: 'http://127.0.0.1:8000', namespace: 'test', database: 'test', debug: true }
276
+
277
+ // With authentication
278
+ const configWithAuth = localConfig('test', 'test', {
279
+ auth: rootAuth('root', 'root'),
280
+ debug: false,
281
+ });
282
+ ```
283
+
284
+ #### `cloudConfig(url: string, namespace: string, database: string, auth: SurrealAuth, options?: Partial<SurrealDBConfig>): SurrealDBConfig`
285
+
286
+ Create a cloud/production configuration.
287
+
288
+ ```typescript
289
+ import { cloudConfig, rootAuth } from '@ereo/db-surrealdb';
290
+
291
+ const config = cloudConfig(
292
+ 'https://cloud.surrealdb.com',
293
+ 'production',
294
+ 'mydb',
295
+ rootAuth('user', 'password')
296
+ );
297
+ // => { url: 'https://...', namespace: 'production', database: 'mydb', auth: {...}, debug: false }
298
+ ```
299
+
300
+ #### `envConfig(env?: Record<string, string | undefined>): SurrealDBConfig`
301
+
302
+ Create configuration from environment variables.
303
+
304
+ ```typescript
305
+ import { envConfig } from '@ereo/db-surrealdb';
306
+
307
+ // Uses process.env by default
308
+ const config = envConfig();
309
+
310
+ // Or provide custom env object (useful for testing)
311
+ const config = envConfig({
312
+ SURREAL_URL: 'http://localhost:8000',
313
+ SURREAL_NAMESPACE: 'test',
314
+ SURREAL_DATABASE: 'test',
315
+ SURREAL_USERNAME: 'root',
316
+ SURREAL_PASSWORD: 'root',
317
+ SURREAL_DEBUG: 'true',
318
+ });
319
+ ```
320
+
321
+ **Expected Environment Variables:**
322
+ | Variable | Required | Description |
323
+ |----------|----------|-------------|
324
+ | `SURREAL_URL` | Yes | SurrealDB connection URL |
325
+ | `SURREAL_NAMESPACE` | Yes | Target namespace |
326
+ | `SURREAL_DATABASE` | Yes | Target database |
327
+ | `SURREAL_USERNAME` | No | Authentication username |
328
+ | `SURREAL_PASSWORD` | No | Authentication password |
329
+ | `SURREAL_DEBUG` | No | Enable debug logging (`'true'`) |
330
+
331
+ ### Query Helpers
332
+
333
+ #### `select(table: string, options?: SelectOptions): string`
334
+
335
+ Create a SurrealQL SELECT query string.
336
+
337
+ ```typescript
338
+ import { select } from '@ereo/db-surrealdb';
339
+
340
+ select('users');
341
+ // => 'SELECT * FROM users'
342
+
343
+ select('users', { where: 'active = true' });
344
+ // => 'SELECT * FROM users WHERE active = true'
345
+
346
+ select('users', { orderBy: 'name ASC' });
347
+ // => 'SELECT * FROM users ORDER BY name ASC'
348
+
349
+ select('users', { limit: 10 });
350
+ // => 'SELECT * FROM users LIMIT 10'
351
+
352
+ select('users', { start: 20 });
353
+ // => 'SELECT * FROM users START 20'
354
+
355
+ // Combine all options
356
+ select('users', {
357
+ where: 'active = true',
358
+ orderBy: 'created_at DESC',
359
+ limit: 10,
360
+ start: 0,
361
+ });
362
+ // => 'SELECT * FROM users WHERE active = true ORDER BY created_at DESC LIMIT 10 START 0'
363
+ ```
364
+
365
+ #### `create(table: string, id?: string): string`
366
+
367
+ Create a SurrealQL CREATE query string.
368
+
369
+ ```typescript
370
+ import { create } from '@ereo/db-surrealdb';
371
+
372
+ create('users');
373
+ // => 'CREATE users'
374
+
375
+ create('users', 'john');
376
+ // => 'CREATE users:john'
377
+ ```
378
+
379
+ #### `update(table: string, id?: string): string`
380
+
381
+ Create a SurrealQL UPDATE query string.
382
+
383
+ ```typescript
384
+ import { update } from '@ereo/db-surrealdb';
385
+
386
+ update('users');
387
+ // => 'UPDATE users'
388
+
389
+ update('users', 'john');
390
+ // => 'UPDATE users:john'
391
+ ```
392
+
393
+ #### `deleteFrom(table: string, id?: string): string`
394
+
395
+ Create a SurrealQL DELETE query string.
396
+
397
+ ```typescript
398
+ import { deleteFrom } from '@ereo/db-surrealdb';
399
+
400
+ deleteFrom('users');
401
+ // => 'DELETE users'
402
+
403
+ deleteFrom('users', 'john');
404
+ // => 'DELETE users:john'
405
+ ```
406
+
407
+ ### Validation
408
+
409
+ #### `validateConfig(config: SurrealDBConfig): void`
410
+
411
+ Validate a SurrealDB configuration. Throws an error if invalid.
412
+
413
+ ```typescript
414
+ import { validateConfig } from '@ereo/db-surrealdb';
415
+
416
+ // Valid configurations
417
+ validateConfig({
418
+ url: 'http://localhost:8000',
419
+ namespace: 'test',
420
+ database: 'test',
421
+ }); // OK
422
+
423
+ validateConfig({
424
+ url: 'mem://',
425
+ namespace: 'test',
426
+ database: 'test',
427
+ }); // OK (in-memory)
428
+
429
+ validateConfig({
430
+ url: 'surrealkv://./data',
431
+ namespace: 'test',
432
+ database: 'test',
433
+ }); // OK (persistent)
434
+
435
+ // Invalid configurations throw errors
436
+ validateConfig({ url: '', namespace: 'test', database: 'test' });
437
+ // Throws: 'SurrealDB url is required'
438
+
439
+ validateConfig({ url: 'http://localhost:8000', namespace: '', database: 'test' });
440
+ // Throws: 'SurrealDB namespace is required'
441
+
442
+ validateConfig({ url: 'not-a-valid-url', namespace: 'test', database: 'test' });
443
+ // Throws: 'Invalid SurrealDB URL: not-a-valid-url'
444
+ ```
445
+
446
+ ## Configuration Options
447
+
448
+ The `SurrealDBConfig` interface extends `AdapterConfig` and includes:
449
+
450
+ | Option | Type | Required | Default | Description |
451
+ |--------|------|----------|---------|-------------|
452
+ | `url` | `string` | Yes | - | SurrealDB connection URL |
453
+ | `namespace` | `string` | Yes | - | Target namespace |
454
+ | `database` | `string` | Yes | - | Target database within the namespace |
455
+ | `auth` | `SurrealAuth` | No | - | Authentication credentials |
456
+ | `timeout` | `number` | No | `30000` | Connection timeout in milliseconds |
457
+ | `debug` | `boolean` | No | `false` | Enable debug logging |
458
+
459
+ **Supported URL Formats:**
460
+ - `http://127.0.0.1:8000` - HTTP connection
461
+ - `https://cloud.surrealdb.com` - HTTPS connection
462
+ - `ws://127.0.0.1:8000` - WebSocket connection
463
+ - `wss://cloud.surrealdb.com` - Secure WebSocket connection
464
+ - `mem://` - In-memory database (requires `@surrealdb/node`)
465
+ - `surrealkv://path/to/db` - Persistent storage (requires `@surrealdb/node`)
466
+
467
+ ## SurrealDB-Specific Features
468
+
469
+ ### SurrealQL Queries
470
+
471
+ Access the full power of SurrealQL through the client:
472
+
473
+ ```typescript
474
+ import { useDb } from '@ereo/db-surrealdb';
475
+
476
+ export const loader = createLoader({
477
+ load: async ({ context }) => {
478
+ const db = useDb(context);
479
+
480
+ // Raw SurrealQL queries
481
+ const users = await db.client.query(`
482
+ SELECT *, ->follows->user AS following
483
+ FROM users
484
+ WHERE active = true
485
+ ORDER BY created_at DESC
486
+ LIMIT 10
487
+ `);
488
+
489
+ // Parameterized queries (parameters use $0, $1, etc.)
490
+ const result = await db.client.query(
491
+ 'SELECT * FROM users WHERE email = $0',
492
+ ['user@example.com']
493
+ );
494
+
495
+ return users;
496
+ },
497
+ });
498
+ ```
499
+
500
+ ### Record IDs
501
+
502
+ SurrealDB uses compound record IDs in the format `table:id`:
503
+
504
+ ```typescript
505
+ import { useDb } from '@ereo/db-surrealdb';
506
+
507
+ export const loader = createLoader({
508
+ load: async ({ context }) => {
509
+ const db = useDb(context);
510
+
511
+ // Select a specific record
512
+ const user = await db.client.select('users:john');
513
+
514
+ // Create with specific ID
515
+ const newUser = await db.client.create('users:jane', {
516
+ name: 'Jane Doe',
517
+ email: 'jane@example.com',
518
+ });
519
+
520
+ // Update specific record
521
+ await db.client.update('users:john', {
522
+ name: 'John Smith',
523
+ });
524
+
525
+ // Delete specific record
526
+ await db.client.delete('users:john');
527
+
528
+ return user;
529
+ },
530
+ });
531
+ ```
532
+
533
+ ### Transactions
534
+
535
+ The adapter supports both automatic and manual transaction handling:
536
+
537
+ ```typescript
538
+ import { useDb, withTransaction } from '@ereo/db-surrealdb';
539
+
540
+ // Automatic transaction (recommended)
541
+ export const action = createAction({
542
+ action: async ({ context }) => {
543
+ const db = useDb(context);
544
+
545
+ const result = await withTransaction(context, async (tx) => {
546
+ // All operations here are in a transaction
547
+ const user = await tx.create('users', { name: 'Alice' });
548
+ await tx.create('profiles', { user: user.id, bio: 'Hello!' });
549
+ return user;
550
+ });
551
+
552
+ return result;
553
+ },
554
+ });
555
+
556
+ // Manual transaction control
557
+ export const action = createAction({
558
+ action: async ({ context }) => {
559
+ const adapter = useAdapter(context);
560
+ const tx = await adapter.beginTransaction();
561
+
562
+ try {
563
+ await tx.client.query('CREATE users SET name = "Bob"');
564
+ await tx.client.query('CREATE profiles SET user = users:bob');
565
+ await tx.commit();
566
+ } catch (error) {
567
+ await tx.rollback();
568
+ throw error;
569
+ }
570
+ },
571
+ });
572
+ ```
573
+
574
+ ### Connection Protocols
575
+
576
+ The adapter automatically appends `/rpc` to HTTP/HTTPS URLs for compatibility with the SurrealDB RPC endpoint:
577
+
578
+ ```typescript
579
+ // Input: 'http://localhost:8000'
580
+ // Connection URL: 'http://localhost:8000/rpc'
581
+
582
+ // Input: 'http://localhost:8000/'
583
+ // Connection URL: 'http://localhost:8000/rpc'
584
+
585
+ // Input: 'ws://localhost:8000'
586
+ // Connection URL: 'ws://localhost:8000' (unchanged for WebSocket)
587
+ ```
588
+
589
+ ## Use Cases with Examples
590
+
591
+ ### Basic CRUD Operations
592
+
593
+ ```typescript
594
+ import { createLoader, createAction } from '@ereo/core';
595
+ import { useDb } from '@ereo/db-surrealdb';
596
+
597
+ // Read all users
598
+ export const usersLoader = createLoader({
599
+ load: async ({ context }) => {
600
+ const db = useDb(context);
601
+ return db.client.select('users');
602
+ },
603
+ });
604
+
605
+ // Read single user
606
+ export const userLoader = createLoader({
607
+ load: async ({ context, params }) => {
608
+ const db = useDb(context);
609
+ return db.client.select(`users:${params.id}`);
610
+ },
611
+ });
612
+
613
+ // Create user
614
+ export const createUserAction = createAction({
615
+ action: async ({ context, request }) => {
616
+ const db = useDb(context);
617
+ const data = await request.json();
618
+ return db.client.create('users', data);
619
+ },
620
+ });
621
+
622
+ // Update user
623
+ export const updateUserAction = createAction({
624
+ action: async ({ context, params, request }) => {
625
+ const db = useDb(context);
626
+ const data = await request.json();
627
+ return db.client.merge(`users:${params.id}`, data);
628
+ },
629
+ });
630
+
631
+ // Delete user
632
+ export const deleteUserAction = createAction({
633
+ action: async ({ context, params }) => {
634
+ const db = useDb(context);
635
+ return db.client.delete(`users:${params.id}`);
636
+ },
637
+ });
638
+ ```
639
+
640
+ ### Graph Relationships
641
+
642
+ ```typescript
643
+ import { useDb } from '@ereo/db-surrealdb';
644
+
645
+ export const loader = createLoader({
646
+ load: async ({ context, params }) => {
647
+ const db = useDb(context);
648
+
649
+ // Create a relationship
650
+ await db.client.query(`
651
+ RELATE users:${params.userId}->follows->users:${params.targetId}
652
+ SET created_at = time::now()
653
+ `);
654
+
655
+ // Query relationships
656
+ const following = await db.client.query(`
657
+ SELECT ->follows->user.* AS following
658
+ FROM users:${params.userId}
659
+ `);
660
+
661
+ const followers = await db.client.query(`
662
+ SELECT <-follows<-user.* AS followers
663
+ FROM users:${params.userId}
664
+ `);
665
+
666
+ // Mutual friends
667
+ const mutuals = await db.client.query(`
668
+ SELECT ->follows->user<-follows<-user AS mutuals
669
+ FROM users:${params.userId}
670
+ WHERE mutuals != users:${params.userId}
671
+ `);
672
+
673
+ return { following, followers, mutuals };
674
+ },
675
+ });
676
+ ```
677
+
678
+ ### Query Deduplication
679
+
680
+ The adapter automatically deduplicates identical queries within a request:
681
+
682
+ ```typescript
683
+ import { useDb } from '@ereo/db-surrealdb';
684
+
685
+ export const loader = createLoader({
686
+ load: async ({ context }) => {
687
+ const db = useDb(context);
688
+
689
+ // These identical queries will only execute once
690
+ const [users1, users2, users3] = await Promise.all([
691
+ db.query('SELECT * FROM users'),
692
+ db.query('SELECT * FROM users'),
693
+ db.query('SELECT * FROM users'),
694
+ ]);
695
+
696
+ // Check deduplication stats
697
+ const stats = db.getDedupStats();
698
+ console.log(stats);
699
+ // => { total: 3, deduplicated: 2, unique: 1, hitRate: 0.67 }
700
+
701
+ // Clear cache after mutations
702
+ await db.client.create('users', { name: 'New User' });
703
+ db.invalidate(['users']); // or db.clearDedup() to clear all
704
+
705
+ return users1;
706
+ },
707
+ });
708
+ ```
709
+
710
+ ### Health Checks
711
+
712
+ ```typescript
713
+ import { createSurrealAdapter } from '@ereo/db-surrealdb';
714
+
715
+ const adapter = createSurrealAdapter(config);
716
+
717
+ // Check database health
718
+ const health = await adapter.healthCheck();
719
+
720
+ if (health.healthy) {
721
+ console.log(`Database is healthy (latency: ${health.latencyMs}ms)`);
722
+ console.log(`Connected to: ${health.metadata?.namespace}/${health.metadata?.database}`);
723
+ } else {
724
+ console.error(`Database unhealthy: ${health.error}`);
725
+ }
726
+ ```
727
+
728
+ ### Multi-Tenant Configuration
729
+
730
+ ```typescript
731
+ import { createSurrealAdapter, defineSurrealConfig, databaseAuth } from '@ereo/db-surrealdb';
732
+
733
+ // Create tenant-specific adapter
734
+ function createTenantAdapter(tenantId: string) {
735
+ return createSurrealAdapter(defineSurrealConfig({
736
+ url: process.env.SURREAL_URL!,
737
+ namespace: 'production',
738
+ database: `tenant_${tenantId}`,
739
+ auth: databaseAuth(
740
+ 'production',
741
+ `tenant_${tenantId}`,
742
+ process.env.TENANT_USER!,
743
+ process.env.TENANT_PASSWORD!
744
+ ),
745
+ }));
746
+ }
747
+ ```
748
+
749
+ ## Integration with Core db Package
750
+
751
+ This package re-exports commonly used items from `@ereo/db` for convenience:
752
+
753
+ ```typescript
754
+ import {
755
+ // Plugin factory
756
+ createDatabasePlugin,
757
+
758
+ // Context helpers
759
+ useDb,
760
+ useAdapter,
761
+ getDb,
762
+
763
+ // Transaction helper
764
+ withTransaction,
765
+
766
+ // Types
767
+ type DatabaseAdapter,
768
+ type RequestScopedClient,
769
+ type QueryResult,
770
+ type MutationResult,
771
+ type DedupResult,
772
+ type DedupStats,
773
+ type TransactionOptions,
774
+ } from '@ereo/db-surrealdb';
775
+ ```
776
+
777
+ ## TypeScript Types Reference
778
+
779
+ ### Authentication Types
780
+
781
+ ```typescript
782
+ // Root user authentication
783
+ interface RootAuth {
784
+ username: string;
785
+ password: string;
786
+ }
787
+
788
+ // Namespace-level authentication
789
+ interface NamespaceAuth {
790
+ namespace: string;
791
+ username: string;
792
+ password: string;
793
+ }
794
+
795
+ // Database-level authentication
796
+ interface DatabaseAuth {
797
+ namespace: string;
798
+ database: string;
799
+ username: string;
800
+ password: string;
801
+ }
802
+
803
+ // Record access authentication (SurrealDB 2.x+)
804
+ interface RecordAccessAuth {
805
+ namespace: string;
806
+ database: string;
807
+ access: string;
808
+ variables?: Record<string, unknown>;
809
+ }
810
+
811
+ // Scope-based authentication (SurrealDB 1.x)
812
+ interface ScopeAuth {
813
+ namespace: string;
814
+ database: string;
815
+ scope: string;
816
+ [key: string]: unknown;
817
+ }
818
+
819
+ // Union of all authentication types
820
+ type SurrealAuth = RootAuth | NamespaceAuth | DatabaseAuth | RecordAccessAuth | ScopeAuth;
821
+ ```
822
+
823
+ ### Configuration Types
824
+
825
+ ```typescript
826
+ // Connection protocol
827
+ type ConnectionProtocol = 'http' | 'https' | 'ws' | 'wss';
828
+
829
+ // Engine type
830
+ type SurrealEngine = 'remote' | 'memory' | 'surrealkv';
831
+
832
+ // Main configuration
833
+ interface SurrealDBConfig extends AdapterConfig {
834
+ url: string;
835
+ namespace: string;
836
+ database: string;
837
+ auth?: SurrealAuth;
838
+ timeout?: number;
839
+ debug?: boolean;
840
+ }
841
+ ```
842
+
843
+ ### Query Result Types
844
+
845
+ ```typescript
846
+ // SurrealDB query result
847
+ interface SurrealQueryResult<T = unknown> {
848
+ result: T;
849
+ status: 'OK' | 'ERR';
850
+ time: string;
851
+ }
852
+
853
+ // Raw RPC response
854
+ interface SurrealRawResponse<T = unknown> {
855
+ result: T;
856
+ status: string;
857
+ time: string;
858
+ }
859
+ ```
860
+
861
+ ### Record Types
862
+
863
+ ```typescript
864
+ // SurrealDB Record ID (format: "table:id")
865
+ interface RecordId<T extends string = string> {
866
+ tb: T;
867
+ id: string | number | object;
868
+ }
869
+
870
+ // Base record with ID
871
+ interface SurrealRecord {
872
+ id: RecordId | string;
873
+ }
874
+ ```
875
+
876
+ ### Live Query Types
877
+
878
+ ```typescript
879
+ // Live query action types
880
+ type LiveAction = 'CREATE' | 'UPDATE' | 'DELETE';
881
+
882
+ // Live query notification
883
+ interface LiveNotification<T = unknown> {
884
+ action: LiveAction;
885
+ result: T;
886
+ }
887
+ ```
888
+
889
+ ### Type Guards
890
+
891
+ ```typescript
892
+ import {
893
+ isRootAuth,
894
+ isNamespaceAuth,
895
+ isDatabaseAuth,
896
+ isRecordAccessAuth,
897
+ isScopeAuth,
898
+ } from '@ereo/db-surrealdb';
899
+
900
+ // Example usage
901
+ function handleAuth(auth: SurrealAuth) {
902
+ if (isRootAuth(auth)) {
903
+ console.log('Root auth:', auth.username);
904
+ } else if (isNamespaceAuth(auth)) {
905
+ console.log('Namespace auth:', auth.namespace, auth.username);
906
+ } else if (isDatabaseAuth(auth)) {
907
+ console.log('Database auth:', auth.namespace, auth.database, auth.username);
908
+ } else if (isRecordAccessAuth(auth)) {
909
+ console.log('Record access auth:', auth.access);
910
+ } else if (isScopeAuth(auth)) {
911
+ console.log('Scope auth:', auth.scope);
912
+ }
913
+ }
914
+ ```
915
+
916
+ ### SurrealClient Interface
917
+
918
+ ```typescript
919
+ interface SurrealClient {
920
+ connect(url: string): Promise<void>;
921
+ close(): Promise<void>;
922
+ use(params: { namespace: string; database: string }): Promise<void>;
923
+ signin(credentials: Record<string, unknown>): Promise<string>;
924
+ query<T = unknown>(sql: string, vars?: Record<string, unknown>): Promise<T[]>;
925
+ select<T = unknown>(thing: string): Promise<T[]>;
926
+ create<T = unknown>(thing: string, data?: Record<string, unknown>): Promise<T>;
927
+ insert<T = unknown>(thing: string, data?: Record<string, unknown> | Record<string, unknown>[]): Promise<T[]>;
928
+ update<T = unknown>(thing: string, data?: Record<string, unknown>): Promise<T>;
929
+ merge<T = unknown>(thing: string, data?: Record<string, unknown>): Promise<T>;
930
+ patch<T = unknown>(thing: string, data?: unknown[]): Promise<T>;
931
+ delete<T = unknown>(thing: string): Promise<T>;
932
+ }
933
+ ```
934
+
935
+ ## Troubleshooting / FAQ
936
+
937
+ ### Connection Issues
938
+
939
+ **Q: I get "Failed to connect to SurrealDB" error**
940
+
941
+ A: Check the following:
942
+ 1. Ensure SurrealDB is running and accessible at the configured URL
943
+ 2. Verify the URL format is correct (include protocol: `http://`, `https://`, `ws://`, or `wss://`)
944
+ 3. Check that namespace and database exist in SurrealDB
945
+ 4. Verify authentication credentials are correct
946
+
947
+ ```bash
948
+ # Test SurrealDB connectivity
949
+ curl -X POST http://localhost:8000/health
950
+ ```
951
+
952
+ **Q: WebSocket connection keeps disconnecting**
953
+
954
+ A: This may be due to timeout settings. Increase the timeout:
955
+
956
+ ```typescript
957
+ const config = defineSurrealConfig({
958
+ url: 'ws://localhost:8000',
959
+ namespace: 'test',
960
+ database: 'test',
961
+ timeout: 60000, // 60 seconds
962
+ });
963
+ ```
964
+
965
+ ### Authentication Issues
966
+
967
+ **Q: What authentication method should I use?**
968
+
969
+ A: It depends on your security requirements:
970
+ - **Root auth**: Full access, use for admin tasks or development
971
+ - **Namespace auth**: Access to all databases in a namespace
972
+ - **Database auth**: Access to a specific database
973
+ - **Record access auth** (2.x): Fine-grained access control per record
974
+ - **Scope auth** (1.x): User-level authentication with custom scopes
975
+
976
+ **Q: How do I authenticate users in my application?**
977
+
978
+ A: Use record access authentication (SurrealDB 2.x) or scope authentication (SurrealDB 1.x):
979
+
980
+ ```typescript
981
+ // SurrealDB 2.x
982
+ const auth = recordAccessAuth('myns', 'mydb', 'user', {
983
+ email: userEmail,
984
+ password: userPassword,
985
+ });
986
+
987
+ // SurrealDB 1.x
988
+ const auth: ScopeAuth = {
989
+ namespace: 'myns',
990
+ database: 'mydb',
991
+ scope: 'user',
992
+ email: userEmail,
993
+ password: userPassword,
994
+ };
995
+ ```
996
+
997
+ ### Query Issues
998
+
999
+ **Q: How do I pass parameters to queries?**
1000
+
1001
+ A: Use numbered parameters ($0, $1, etc.):
1002
+
1003
+ ```typescript
1004
+ const db = useDb(context);
1005
+
1006
+ // Parameters are converted to $0, $1, etc.
1007
+ const result = await db.client.query(
1008
+ 'SELECT * FROM users WHERE email = $0 AND active = $1',
1009
+ ['user@example.com', true]
1010
+ );
1011
+ ```
1012
+
1013
+ **Q: Query deduplication is not working**
1014
+
1015
+ A: Ensure you're using the request-scoped client:
1016
+
1017
+ ```typescript
1018
+ // Correct - uses deduplication
1019
+ const db = useDb(context);
1020
+ const result = await db.query('SELECT * FROM users');
1021
+
1022
+ // Direct client access - no deduplication
1023
+ const adapter = useAdapter(context);
1024
+ const client = adapter.getClient();
1025
+ const result = await client.query('SELECT * FROM users');
1026
+ ```
1027
+
1028
+ ### Transaction Issues
1029
+
1030
+ **Q: My transaction is not rolling back on error**
1031
+
1032
+ A: Ensure you're using the transaction helper or properly handling errors:
1033
+
1034
+ ```typescript
1035
+ // Recommended approach - automatic rollback
1036
+ const result = await withTransaction(context, async (tx) => {
1037
+ // If any operation throws, the transaction is rolled back
1038
+ await tx.create('users', { name: 'Alice' });
1039
+ throw new Error('Something went wrong'); // Transaction will rollback
1040
+ });
1041
+
1042
+ // Manual approach - must handle rollback explicitly
1043
+ const tx = await adapter.beginTransaction();
1044
+ try {
1045
+ await tx.client.query('...');
1046
+ await tx.commit();
1047
+ } catch (error) {
1048
+ await tx.rollback(); // Don't forget this!
1049
+ throw error;
1050
+ }
1051
+ ```
1052
+
1053
+ ### Debug Mode
1054
+
1055
+ Enable debug logging to troubleshoot issues:
1056
+
1057
+ ```typescript
1058
+ const config = defineSurrealConfig({
1059
+ url: 'http://localhost:8000',
1060
+ namespace: 'test',
1061
+ database: 'test',
1062
+ debug: true, // Logs connection events and queries
1063
+ });
1064
+ ```
1065
+
1066
+ Debug output includes:
1067
+ - Connection attempts: `[surrealdb] Connecting to http://localhost:8000/rpc...`
1068
+ - Authentication: `[surrealdb] Signing in...`
1069
+ - Successful connection: `[surrealdb] Connected to test/test`
1070
+ - Query execution: `[surrealdb] Query: SELECT * FROM users {...}`
1071
+ - Disconnection: `[surrealdb] Disconnected`
1072
+
1073
+ ### Error Classes
1074
+
1075
+ The adapter uses typed errors from `@ereo/db`:
1076
+
1077
+ ```typescript
1078
+ import { ConnectionError, QueryError, TransactionError } from '@ereo/db-surrealdb';
1079
+
1080
+ try {
1081
+ const db = useDb(context);
1082
+ await db.client.query('INVALID SQL');
1083
+ } catch (error) {
1084
+ if (error instanceof ConnectionError) {
1085
+ console.error('Connection failed:', error.message);
1086
+ } else if (error instanceof QueryError) {
1087
+ console.error('Query failed:', error.message);
1088
+ console.error('SQL:', error.query);
1089
+ console.error('Params:', error.params);
1090
+ } else if (error instanceof TransactionError) {
1091
+ console.error('Transaction failed:', error.message);
1092
+ }
1093
+ }
1094
+ ```