@framers/sql-storage-adapter 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/LICENSE +21 -0
- package/README.md +363 -0
- package/dist/adapters/baseStorageAdapter.d.ts +204 -0
- package/dist/adapters/baseStorageAdapter.d.ts.map +1 -0
- package/dist/adapters/baseStorageAdapter.js +364 -0
- package/dist/adapters/baseStorageAdapter.js.map +1 -0
- package/dist/adapters/betterSqliteAdapter.d.ts +64 -0
- package/dist/adapters/betterSqliteAdapter.d.ts.map +1 -0
- package/dist/adapters/betterSqliteAdapter.js +206 -0
- package/dist/adapters/betterSqliteAdapter.js.map +1 -0
- package/dist/adapters/capacitorSqliteAdapter.d.ts +33 -0
- package/dist/adapters/capacitorSqliteAdapter.d.ts.map +1 -0
- package/dist/adapters/capacitorSqliteAdapter.js +95 -0
- package/dist/adapters/capacitorSqliteAdapter.js.map +1 -0
- package/dist/adapters/postgresAdapter.d.ts +180 -0
- package/dist/adapters/postgresAdapter.d.ts.map +1 -0
- package/dist/adapters/postgresAdapter.js +271 -0
- package/dist/adapters/postgresAdapter.js.map +1 -0
- package/dist/adapters/sqlJsAdapter.d.ts +28 -0
- package/dist/adapters/sqlJsAdapter.d.ts.map +1 -0
- package/dist/adapters/sqlJsAdapter.js +136 -0
- package/dist/adapters/sqlJsAdapter.js.map +1 -0
- package/dist/adapters/supabase.d.ts +58 -0
- package/dist/adapters/supabase.d.ts.map +1 -0
- package/dist/adapters/supabase.js +385 -0
- package/dist/adapters/supabase.js.map +1 -0
- package/dist/database.d.ts +124 -0
- package/dist/database.d.ts.map +1 -0
- package/dist/database.js +136 -0
- package/dist/database.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/resolver.d.ts +24 -0
- package/dist/resolver.d.ts.map +1 -0
- package/dist/resolver.js +91 -0
- package/dist/resolver.js.map +1 -0
- package/dist/types/context.d.ts +221 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/context.js +9 -0
- package/dist/types/context.js.map +1 -0
- package/dist/types/events.d.ts +225 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +8 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/extensions.d.ts +73 -0
- package/dist/types/extensions.d.ts.map +1 -0
- package/dist/types/extensions.js +7 -0
- package/dist/types/extensions.js.map +1 -0
- package/dist/types/limitations.d.ts +46 -0
- package/dist/types/limitations.d.ts.map +1 -0
- package/dist/types/limitations.js +154 -0
- package/dist/types/limitations.js.map +1 -0
- package/dist/types.d.ts +235 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +18 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/cloudBackup.d.ts +219 -0
- package/dist/utils/cloudBackup.d.ts.map +1 -0
- package/dist/utils/cloudBackup.js +289 -0
- package/dist/utils/cloudBackup.js.map +1 -0
- package/dist/utils/dataExport.d.ts +77 -0
- package/dist/utils/dataExport.d.ts.map +1 -0
- package/dist/utils/dataExport.js +212 -0
- package/dist/utils/dataExport.js.map +1 -0
- package/dist/utils/dataImport.d.ts +54 -0
- package/dist/utils/dataImport.d.ts.map +1 -0
- package/dist/utils/dataImport.js +324 -0
- package/dist/utils/dataImport.js.map +1 -0
- package/dist/utils/migration.d.ts +89 -0
- package/dist/utils/migration.d.ts.map +1 -0
- package/dist/utils/migration.js +184 -0
- package/dist/utils/migration.js.map +1 -0
- package/dist/utils/parameterUtils.d.ts +9 -0
- package/dist/utils/parameterUtils.d.ts.map +1 -0
- package/dist/utils/parameterUtils.js +17 -0
- package/dist/utils/parameterUtils.js.map +1 -0
- package/dist/utils/syncManager.d.ts +342 -0
- package/dist/utils/syncManager.d.ts.map +1 -0
- package/dist/utils/syncManager.js +533 -0
- package/dist/utils/syncManager.js.map +1 -0
- package/package.json +108 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Framers
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
# SQL Storage Adapter
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@framers/sql-storage-adapter)
|
|
4
|
+
[](https://github.com/wearetheframers/sql-storage-adapter/actions/workflows/ci.yml)
|
|
5
|
+
[](https://codecov.io/gh/wearetheframers/sql-storage-adapter)
|
|
6
|
+
[](https://www.typescriptlang.org/)
|
|
7
|
+
|
|
8
|
+
> One SQL interface for Node.js, browsers, and mobile apps. Write your database code once, run it anywhere.
|
|
9
|
+
|
|
10
|
+
**[Documentation](https://wearetheframers.github.io/sql-storage-adapter/)** | **[GitHub](https://github.com/wearetheframers/sql-storage-adapter)** | **[NPM](https://www.npmjs.com/package/@framers/sql-storage-adapter)**
|
|
11
|
+
|
|
12
|
+
## Why?
|
|
13
|
+
|
|
14
|
+
Build apps that work across platforms without rewriting database code:
|
|
15
|
+
- 🖥️ **Desktop** (Electron) - fast local storage with better-sqlite3
|
|
16
|
+
- 🌐 **Web** - in-browser SQL with sql.js (WebAssembly)
|
|
17
|
+
- 📱 **Mobile** (Capacitor/React Native) - native SQLite
|
|
18
|
+
- ☁️ **Server** - PostgreSQL or SQLite
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { createDatabase } from '@framers/sql-storage-adapter';
|
|
22
|
+
|
|
23
|
+
// Auto-picks the best adapter for your environment
|
|
24
|
+
const db = await createDatabase();
|
|
25
|
+
|
|
26
|
+
// Same code everywhere
|
|
27
|
+
await db.run('INSERT INTO users (name) VALUES (?)', ['Alice']);
|
|
28
|
+
const user = await db.get('SELECT * FROM users WHERE id = ?', [1]);
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
npm install @framers/sql-storage-adapter
|
|
35
|
+
|
|
36
|
+
# Install adapters you need:
|
|
37
|
+
npm install better-sqlite3 # Desktop (Node.js/Electron)
|
|
38
|
+
npm install pg # PostgreSQL (servers)
|
|
39
|
+
npm install @capacitor-community/sqlite # Mobile (Capacitor)
|
|
40
|
+
# sql.js included - no extra install needed
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Quick Start
|
|
44
|
+
|
|
45
|
+
```typescript
|
|
46
|
+
import { createDatabase } from '@framers/sql-storage-adapter';
|
|
47
|
+
|
|
48
|
+
// Create database (auto-detects best option)
|
|
49
|
+
const db = await createDatabase();
|
|
50
|
+
|
|
51
|
+
// Create table
|
|
52
|
+
await db.exec(`
|
|
53
|
+
CREATE TABLE IF NOT EXISTS todos (
|
|
54
|
+
id INTEGER PRIMARY KEY,
|
|
55
|
+
task TEXT NOT NULL,
|
|
56
|
+
done INTEGER DEFAULT 0
|
|
57
|
+
)
|
|
58
|
+
`);
|
|
59
|
+
|
|
60
|
+
// Insert data
|
|
61
|
+
await db.run('INSERT INTO todos (task) VALUES (?)', ['Buy groceries']);
|
|
62
|
+
|
|
63
|
+
// Query data
|
|
64
|
+
const todos = await db.all('SELECT * FROM todos WHERE done = 0');
|
|
65
|
+
|
|
66
|
+
// Clean up
|
|
67
|
+
await db.close();
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Core API
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
// Execute SQL (no results)
|
|
74
|
+
await db.exec('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
|
|
75
|
+
|
|
76
|
+
// Run mutation (INSERT/UPDATE/DELETE)
|
|
77
|
+
const result = await db.run('INSERT INTO users (name) VALUES (?)', ['Alice']);
|
|
78
|
+
console.log(result.lastInsertRowid); // Auto-increment ID
|
|
79
|
+
|
|
80
|
+
// Get single row
|
|
81
|
+
const user = await db.get<User>('SELECT * FROM users WHERE id = ?', [1]);
|
|
82
|
+
|
|
83
|
+
// Get all rows
|
|
84
|
+
const users = await db.all<User>('SELECT * FROM users');
|
|
85
|
+
|
|
86
|
+
// Transactions (automatic commit/rollback)
|
|
87
|
+
await db.transaction(async (tx) => {
|
|
88
|
+
await tx.run('INSERT INTO users (name) VALUES (?)', ['Bob']);
|
|
89
|
+
await tx.run('INSERT INTO posts (user_id, title) VALUES (?, ?)', [1, 'Hello']);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Batch operations (if supported)
|
|
93
|
+
if (db.capabilities.has('batch')) {
|
|
94
|
+
await db.batch([
|
|
95
|
+
{ statement: 'INSERT INTO users (name) VALUES (?)', parameters: ['Alice'] },
|
|
96
|
+
{ statement: 'INSERT INTO users (name) VALUES (?)', parameters: ['Bob'] }
|
|
97
|
+
]);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Close connection
|
|
101
|
+
await db.close();
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Adapter Selection
|
|
105
|
+
|
|
106
|
+
Auto-detection order:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
🖥️ Node.js: better-sqlite3 → sql.js
|
|
110
|
+
🌐 Browser: sql.js
|
|
111
|
+
📱 Mobile: capacitor-sqlite → sql.js
|
|
112
|
+
☁️ Server: postgres → better-sqlite3 → sql.js
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Override with specific adapter:
|
|
116
|
+
|
|
117
|
+
```typescript
|
|
118
|
+
// Use PostgreSQL explicitly
|
|
119
|
+
const db = await createDatabase({
|
|
120
|
+
url: 'postgresql://localhost/mydb'
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// Use SQLite explicitly
|
|
124
|
+
const db = await createDatabase({
|
|
125
|
+
file: './app.db'
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Custom priority
|
|
129
|
+
const db = await createDatabase({
|
|
130
|
+
priority: ['postgres', 'better-sqlite3'],
|
|
131
|
+
postgres: { connectionString: process.env.DATABASE_URL },
|
|
132
|
+
filePath: './fallback.db'
|
|
133
|
+
});
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Auto-Sync & Offline Support
|
|
137
|
+
|
|
138
|
+
Build offline-first apps with automatic cloud sync:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { createSyncManager } from '@framers/sql-storage-adapter';
|
|
142
|
+
|
|
143
|
+
const manager = await createSyncManager({
|
|
144
|
+
primary: './app.db', // Local SQLite
|
|
145
|
+
remote: process.env.DATABASE_URL, // Cloud PostgreSQL
|
|
146
|
+
sync: {
|
|
147
|
+
mode: 'auto', // Auto-sync after writes
|
|
148
|
+
conflictStrategy: 'last-write-wins'
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Work offline
|
|
153
|
+
await manager.db.run('INSERT INTO tasks (title) VALUES (?)', ['Buy milk']);
|
|
154
|
+
|
|
155
|
+
// Sync manually when needed
|
|
156
|
+
const result = await manager.sync();
|
|
157
|
+
console.log(`Synced ${result.recordsSynced} records`);
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
**Sync Modes:**
|
|
161
|
+
- `manual` - Call `sync()` explicitly (default)
|
|
162
|
+
- `auto` - Syncs after writes (debounced)
|
|
163
|
+
- `periodic` - Syncs every N seconds
|
|
164
|
+
- `realtime` - Syncs immediately on every write
|
|
165
|
+
- `on-reconnect` - Syncs when network returns
|
|
166
|
+
|
|
167
|
+
**Conflict Strategies:**
|
|
168
|
+
- `last-write-wins` - Newest timestamp wins (default)
|
|
169
|
+
- `local-wins` - Local always wins
|
|
170
|
+
- `remote-wins` - Server is authority
|
|
171
|
+
- `merge` - Custom merge function
|
|
172
|
+
- `keep-both` - Duplicate records
|
|
173
|
+
|
|
174
|
+
📖 **[Complete Offline Sync Guide](./guides/OFFLINE_SYNC.md)** - 8 real-world examples with mobile optimization
|
|
175
|
+
|
|
176
|
+
## Cloud Backups
|
|
177
|
+
|
|
178
|
+
Automatic S3-compatible backups (AWS S3, Cloudflare R2, MinIO):
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
182
|
+
import { createCloudBackupManager } from '@framers/sql-storage-adapter';
|
|
183
|
+
|
|
184
|
+
const s3 = new S3Client({
|
|
185
|
+
region: 'us-east-1',
|
|
186
|
+
credentials: {
|
|
187
|
+
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
|
|
188
|
+
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
const manager = createCloudBackupManager(db, s3, 'my-backups', {
|
|
193
|
+
interval: 3600000, // Hourly backups
|
|
194
|
+
maxBackups: 24, // Keep 24 backups
|
|
195
|
+
options: { compression: 'gzip' }
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
manager.start(); // Auto-backup every hour
|
|
199
|
+
|
|
200
|
+
// Manual backup
|
|
201
|
+
await manager.backupNow();
|
|
202
|
+
|
|
203
|
+
// Restore
|
|
204
|
+
await manager.restore('backups/my-database-2024-01-15.json.gz');
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
## Data Migration
|
|
208
|
+
|
|
209
|
+
Export, import, and migrate between adapters:
|
|
210
|
+
|
|
211
|
+
```typescript
|
|
212
|
+
import {
|
|
213
|
+
migrateLocalToSupabase,
|
|
214
|
+
exportAsJSON,
|
|
215
|
+
importFromJSON
|
|
216
|
+
} from '@framers/sql-storage-adapter';
|
|
217
|
+
|
|
218
|
+
// Export to JSON
|
|
219
|
+
const backup = await exportAsJSON(db, {
|
|
220
|
+
tables: ['users', 'posts'],
|
|
221
|
+
includeSchema: true
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// Import from JSON
|
|
225
|
+
await importFromJSON(db, backup, {
|
|
226
|
+
dropTables: true,
|
|
227
|
+
batchSize: 100
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
// Migrate SQLite → PostgreSQL
|
|
231
|
+
const result = await migrateLocalToSupabase(sqliteDb, postgresDb, {
|
|
232
|
+
verify: true,
|
|
233
|
+
onConflict: 'replace'
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
console.log(`Migrated ${result.rowsImported} rows in ${result.duration}ms`);
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
## PostgreSQL Remote Connections
|
|
240
|
+
|
|
241
|
+
Connect to hosted databases (AWS RDS, Heroku, Supabase, etc.):
|
|
242
|
+
|
|
243
|
+
```typescript
|
|
244
|
+
import { connectDatabase } from '@framers/sql-storage-adapter';
|
|
245
|
+
|
|
246
|
+
// Auto-detect from DATABASE_URL
|
|
247
|
+
const db = await connectDatabase(process.env.DATABASE_URL);
|
|
248
|
+
|
|
249
|
+
// Or configure explicitly
|
|
250
|
+
const db = await connectDatabase({
|
|
251
|
+
host: 'db.example.com',
|
|
252
|
+
database: 'myapp',
|
|
253
|
+
user: 'dbuser',
|
|
254
|
+
password: process.env.DB_PASSWORD,
|
|
255
|
+
ssl: true,
|
|
256
|
+
max: 20 // Connection pool size
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Same API as SQLite!
|
|
260
|
+
const users = await db.all('SELECT * FROM users');
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
📖 **[PostgreSQL Remote Guide](./guides/POSTGRES_REMOTE_CONNECTION.md)** - Cloud provider examples & SSL config
|
|
264
|
+
|
|
265
|
+
## Adapter Comparison
|
|
266
|
+
|
|
267
|
+
| Adapter | Best For | Speed | Size | Concurrent |
|
|
268
|
+
|---------|----------|-------|------|------------|
|
|
269
|
+
| **PostgreSQL** | Production servers | ⚡⚡⚡ | N/A | ✅ Yes |
|
|
270
|
+
| **better-sqlite3** | Desktop apps | ⚡⚡⚡ | 6 MB | ❌ No |
|
|
271
|
+
| **SQL.js** | Browsers | ⚡ | 2.3 MB | ❌ No |
|
|
272
|
+
| **Capacitor** | Mobile apps | ⚡⚡⚡ | ~1 MB | ❌ No |
|
|
273
|
+
|
|
274
|
+
## TypeScript Support
|
|
275
|
+
|
|
276
|
+
Full type safety with generics:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
interface User {
|
|
280
|
+
id: number;
|
|
281
|
+
name: string;
|
|
282
|
+
email: string;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Type-safe queries
|
|
286
|
+
const user = await db.get<User>('SELECT * FROM users WHERE id = ?', [1]);
|
|
287
|
+
// user: User | null
|
|
288
|
+
|
|
289
|
+
const users = await db.all<User>('SELECT * FROM users');
|
|
290
|
+
// users: User[]
|
|
291
|
+
|
|
292
|
+
// Runtime introspection
|
|
293
|
+
const context = db.context;
|
|
294
|
+
console.log('Adapter:', db.kind);
|
|
295
|
+
console.log('Capabilities:', Array.from(db.capabilities));
|
|
296
|
+
console.log('Max connections:', context.getLimitations().maxConnections);
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
## Event Monitoring
|
|
300
|
+
|
|
301
|
+
Track queries and performance:
|
|
302
|
+
|
|
303
|
+
```typescript
|
|
304
|
+
db.events.on('query:error', (event) => {
|
|
305
|
+
console.error('Query failed:', event.statement, event.error);
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
db.events.on('performance:slow-query', (event) => {
|
|
309
|
+
if (event.duration > 1000) {
|
|
310
|
+
console.warn(`Slow query (${event.duration}ms):`, event.statement);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
db.events.on('transaction:rollback', (event) => {
|
|
315
|
+
console.warn('Transaction rolled back:', event.error);
|
|
316
|
+
});
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
## Examples
|
|
320
|
+
|
|
321
|
+
Real-world usage in `examples/`:
|
|
322
|
+
- `basic-usage.ts` - Getting started
|
|
323
|
+
- `remote-postgres.ts` - Cloud database connections
|
|
324
|
+
- `electron-app/` - Desktop app with better-sqlite3
|
|
325
|
+
- `browser-extension/` - Chrome extension with SQL.js
|
|
326
|
+
- `full-stack/` - Shared code frontend/backend
|
|
327
|
+
- `offline-sync.ts` - Auto-sync patterns
|
|
328
|
+
- `testing/` - Unit testing strategies
|
|
329
|
+
|
|
330
|
+
## FAQ
|
|
331
|
+
|
|
332
|
+
**Q: Which adapter should I use?**
|
|
333
|
+
Use `createDatabase()` - it auto-picks the best adapter for your environment.
|
|
334
|
+
|
|
335
|
+
**Q: Can I switch adapters later?**
|
|
336
|
+
Yes. Use migration functions to move data between adapters.
|
|
337
|
+
|
|
338
|
+
**Q: Is this production-ready?**
|
|
339
|
+
Yes. Built on battle-tested libraries (pg, better-sqlite3, sql.js). The adapter layer adds <1% overhead.
|
|
340
|
+
|
|
341
|
+
**Q: What about schema migrations?**
|
|
342
|
+
Use any migration tool: [`node-pg-migrate`](https://github.com/salsita/node-pg-migrate), [`prisma`](https://www.prisma.io/), or raw SQL.
|
|
343
|
+
|
|
344
|
+
**Q: How do I handle conflicts in sync?**
|
|
345
|
+
Use `conflictStrategy`: `last-write-wins` (default), `local-wins`, `remote-wins`, `merge`, or `keep-both`.
|
|
346
|
+
|
|
347
|
+
**Q: Can I sync only on WiFi?**
|
|
348
|
+
Yes. Use `mode: 'manual'` and call `sync()` when `isOnWiFi === true`. See [Offline Sync Guide](./guides/OFFLINE_SYNC.md#1-mobile-app-with-wifi-only-sync).
|
|
349
|
+
|
|
350
|
+
## Documentation
|
|
351
|
+
|
|
352
|
+
- **[API Documentation](https://wearetheframers.github.io/sql-storage-adapter/)** - Complete TypeDoc reference
|
|
353
|
+
- **[Offline Sync Guide](./guides/OFFLINE_SYNC.md)** - Comprehensive sync patterns
|
|
354
|
+
- **[PostgreSQL Guide](./guides/POSTGRES_REMOTE_CONNECTION.md)** - Remote database setup
|
|
355
|
+
- **[Architecture](./ARCHITECTURE.md)** - Design decisions
|
|
356
|
+
|
|
357
|
+
## Contributing
|
|
358
|
+
|
|
359
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md)
|
|
360
|
+
|
|
361
|
+
## License
|
|
362
|
+
|
|
363
|
+
MIT © [The Framers](https://frame.dev)
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract base class for SQL storage adapters.
|
|
3
|
+
*
|
|
4
|
+
* Provides common functionality and enforces consistent behavior across all adapters.
|
|
5
|
+
* Follows the Template Method pattern - subclasses implement adapter-specific logic
|
|
6
|
+
* while the base class handles cross-cutting concerns.
|
|
7
|
+
*
|
|
8
|
+
* ## Responsibilities
|
|
9
|
+
* - Parameter validation and sanitization
|
|
10
|
+
* - Error handling and standardization
|
|
11
|
+
* - Lifecycle management (open/close state tracking)
|
|
12
|
+
* - Performance monitoring
|
|
13
|
+
* - Logging and diagnostics
|
|
14
|
+
*
|
|
15
|
+
* @example Implementing a new adapter
|
|
16
|
+
* ```typescript
|
|
17
|
+
* export class MyAdapter extends BaseStorageAdapter {
|
|
18
|
+
* protected async doOpen(options?: StorageOpenOptions): Promise<void> {
|
|
19
|
+
* // Adapter-specific connection logic
|
|
20
|
+
* }
|
|
21
|
+
*
|
|
22
|
+
* protected async doRun(statement: string, parameters?: StorageParameters): Promise<StorageRunResult> {
|
|
23
|
+
* // Adapter-specific mutation logic
|
|
24
|
+
* }
|
|
25
|
+
*
|
|
26
|
+
* // ... implement other abstract methods
|
|
27
|
+
* }
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
import type { StorageAdapter, StorageCapability, StorageOpenOptions, StorageParameters, StorageRunResult, BatchOperation, BatchResult, PreparedStatement } from '../types';
|
|
31
|
+
/**
|
|
32
|
+
* Options for BaseStorageAdapter configuration.
|
|
33
|
+
*/
|
|
34
|
+
export interface BaseAdapterOptions {
|
|
35
|
+
/** Enable detailed logging (default: false) */
|
|
36
|
+
verbose?: boolean;
|
|
37
|
+
/** Validate SQL statements before execution (default: true) */
|
|
38
|
+
validateSQL?: boolean;
|
|
39
|
+
/** Track performance metrics (default: true) */
|
|
40
|
+
trackPerformance?: boolean;
|
|
41
|
+
/** Max retry attempts for transient errors (default: 3) */
|
|
42
|
+
maxRetries?: number;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Performance metrics tracked by the base adapter.
|
|
46
|
+
*/
|
|
47
|
+
export interface AdapterMetrics {
|
|
48
|
+
/** Total number of queries executed */
|
|
49
|
+
totalQueries: number;
|
|
50
|
+
/** Total number of mutations (INSERT/UPDATE/DELETE) */
|
|
51
|
+
totalMutations: number;
|
|
52
|
+
/** Total number of transactions */
|
|
53
|
+
totalTransactions: number;
|
|
54
|
+
/** Total number of errors */
|
|
55
|
+
totalErrors: number;
|
|
56
|
+
/** Average query duration in milliseconds */
|
|
57
|
+
averageQueryDuration: number;
|
|
58
|
+
/** Time when adapter was opened */
|
|
59
|
+
openedAt: Date | null;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Abstract base class for SQL storage adapters.
|
|
63
|
+
*
|
|
64
|
+
* Implements common functionality shared by all adapters:
|
|
65
|
+
* - State management (open/close tracking)
|
|
66
|
+
* - Parameter validation
|
|
67
|
+
* - Error handling and wrapping
|
|
68
|
+
* - Performance metrics
|
|
69
|
+
* - Logging and diagnostics
|
|
70
|
+
*/
|
|
71
|
+
export declare abstract class BaseStorageAdapter implements StorageAdapter {
|
|
72
|
+
abstract readonly kind: string;
|
|
73
|
+
abstract readonly capabilities: ReadonlySet<StorageCapability>;
|
|
74
|
+
private state;
|
|
75
|
+
protected readonly options: Required<BaseAdapterOptions>;
|
|
76
|
+
private metrics;
|
|
77
|
+
private queryDurations;
|
|
78
|
+
private readonly MAX_DURATION_SAMPLES;
|
|
79
|
+
/**
|
|
80
|
+
* Creates a new adapter instance.
|
|
81
|
+
*
|
|
82
|
+
* @param options - Configuration options for the adapter
|
|
83
|
+
*/
|
|
84
|
+
constructor(options?: BaseAdapterOptions);
|
|
85
|
+
/**
|
|
86
|
+
* Opens the adapter connection.
|
|
87
|
+
* Handles state management and delegates to subclass implementation.
|
|
88
|
+
*/
|
|
89
|
+
open(options?: StorageOpenOptions): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* Executes a mutation statement (INSERT, UPDATE, DELETE).
|
|
92
|
+
*/
|
|
93
|
+
run(statement: string, parameters?: StorageParameters): Promise<StorageRunResult>;
|
|
94
|
+
/**
|
|
95
|
+
* Retrieves a single row.
|
|
96
|
+
*/
|
|
97
|
+
get<T = unknown>(statement: string, parameters?: StorageParameters): Promise<T | null>;
|
|
98
|
+
/**
|
|
99
|
+
* Retrieves all rows.
|
|
100
|
+
*/
|
|
101
|
+
all<T = unknown>(statement: string, parameters?: StorageParameters): Promise<T[]>;
|
|
102
|
+
/**
|
|
103
|
+
* Executes a SQL script.
|
|
104
|
+
*/
|
|
105
|
+
exec(script: string): Promise<void>;
|
|
106
|
+
/**
|
|
107
|
+
* Executes a transaction.
|
|
108
|
+
*/
|
|
109
|
+
transaction<T>(fn: (trx: StorageAdapter) => Promise<T>): Promise<T>;
|
|
110
|
+
/**
|
|
111
|
+
* Closes the adapter connection.
|
|
112
|
+
*/
|
|
113
|
+
close(): Promise<void>;
|
|
114
|
+
/**
|
|
115
|
+
* Executes a batch of operations (optional).
|
|
116
|
+
*/
|
|
117
|
+
batch(operations: BatchOperation[]): Promise<BatchResult>;
|
|
118
|
+
/**
|
|
119
|
+
* Creates a prepared statement (optional).
|
|
120
|
+
*/
|
|
121
|
+
prepare<T = unknown>(statement: string): PreparedStatement<T>;
|
|
122
|
+
/**
|
|
123
|
+
* Adapter-specific open logic.
|
|
124
|
+
* Called by base class after state validation.
|
|
125
|
+
*/
|
|
126
|
+
protected abstract performOpen(options?: StorageOpenOptions): Promise<void>;
|
|
127
|
+
/**
|
|
128
|
+
* Adapter-specific mutation logic.
|
|
129
|
+
*/
|
|
130
|
+
protected abstract performRun(statement: string, parameters?: StorageParameters): Promise<StorageRunResult>;
|
|
131
|
+
/**
|
|
132
|
+
* Adapter-specific single-row query logic.
|
|
133
|
+
*/
|
|
134
|
+
protected abstract performGet<T>(statement: string, parameters?: StorageParameters): Promise<T | null>;
|
|
135
|
+
/**
|
|
136
|
+
* Adapter-specific multi-row query logic.
|
|
137
|
+
*/
|
|
138
|
+
protected abstract performAll<T>(statement: string, parameters?: StorageParameters): Promise<T[]>;
|
|
139
|
+
/**
|
|
140
|
+
* Adapter-specific script execution logic.
|
|
141
|
+
*/
|
|
142
|
+
protected abstract performExec(script: string): Promise<void>;
|
|
143
|
+
/**
|
|
144
|
+
* Adapter-specific transaction logic.
|
|
145
|
+
*/
|
|
146
|
+
protected abstract performTransaction<T>(fn: (trx: StorageAdapter) => Promise<T>): Promise<T>;
|
|
147
|
+
/**
|
|
148
|
+
* Adapter-specific close logic.
|
|
149
|
+
*/
|
|
150
|
+
protected abstract performClose(): Promise<void>;
|
|
151
|
+
/**
|
|
152
|
+
* Adapter-specific batch logic (optional).
|
|
153
|
+
* Only called if adapter declares 'batch' capability.
|
|
154
|
+
*/
|
|
155
|
+
protected performBatch?(operations: BatchOperation[]): Promise<BatchResult>;
|
|
156
|
+
/**
|
|
157
|
+
* Adapter-specific prepared statement logic (optional).
|
|
158
|
+
* Only called if adapter declares 'prepared' capability.
|
|
159
|
+
*/
|
|
160
|
+
protected performPrepare?<T>(statement: string): PreparedStatement<T>;
|
|
161
|
+
/**
|
|
162
|
+
* Asserts that adapter is in open state.
|
|
163
|
+
* @throws {Error} If adapter is not open
|
|
164
|
+
*/
|
|
165
|
+
protected assertOpen(): void;
|
|
166
|
+
/**
|
|
167
|
+
* Validates SQL statement.
|
|
168
|
+
* @throws {Error} If statement is invalid
|
|
169
|
+
*/
|
|
170
|
+
protected validateStatement(statement: string): void;
|
|
171
|
+
/**
|
|
172
|
+
* Wraps an error with adapter context.
|
|
173
|
+
*/
|
|
174
|
+
protected wrapError(message: string, error: unknown): Error;
|
|
175
|
+
/**
|
|
176
|
+
* Logs a message if verbose mode is enabled.
|
|
177
|
+
*/
|
|
178
|
+
protected log(message: string): void;
|
|
179
|
+
/**
|
|
180
|
+
* Tracks query duration for performance metrics.
|
|
181
|
+
*/
|
|
182
|
+
private trackDuration;
|
|
183
|
+
/**
|
|
184
|
+
* Gets current adapter state.
|
|
185
|
+
*/
|
|
186
|
+
getState(): string;
|
|
187
|
+
/**
|
|
188
|
+
* Gets performance metrics.
|
|
189
|
+
*/
|
|
190
|
+
getMetrics(): Readonly<AdapterMetrics>;
|
|
191
|
+
/**
|
|
192
|
+
* Checks if adapter is open.
|
|
193
|
+
*/
|
|
194
|
+
isOpen(): boolean;
|
|
195
|
+
/**
|
|
196
|
+
* Checks if adapter is closed.
|
|
197
|
+
*/
|
|
198
|
+
isClosed(): boolean;
|
|
199
|
+
/**
|
|
200
|
+
* Gets uptime in milliseconds.
|
|
201
|
+
*/
|
|
202
|
+
getUptime(): number;
|
|
203
|
+
}
|
|
204
|
+
//# sourceMappingURL=baseStorageAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseStorageAdapter.d.ts","sourceRoot":"","sources":["../../src/adapters/baseStorageAdapter.ts"],"names":[],"mappings":"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"}
|