|
| 1 | +# @objectql/driver-sqlite-wasm |
| 2 | + |
| 3 | +> Browser-native SQLite driver for ObjectQL using WebAssembly and OPFS persistence |
| 4 | +
|
| 5 | +[](https://www.npmjs.com/package/@objectql/driver-sqlite-wasm) |
| 6 | +[](https://opensource.org/licenses/MIT) |
| 7 | + |
| 8 | +## Overview |
| 9 | + |
| 10 | +`@objectql/driver-sqlite-wasm` brings full-featured SQL database capabilities to browser environments through WebAssembly and the Origin Private File System (OPFS). This driver allows you to run ObjectQL applications entirely client-side with persistent storage. |
| 11 | + |
| 12 | +**Key Features:** |
| 13 | + |
| 14 | +- ✅ **Zero Server Dependencies** — Complete SQL database in the browser |
| 15 | +- ✅ **Persistent Storage** — Data survives page refresh via OPFS |
| 16 | +- ✅ **Full SQL Support** — Leverages SQLite 3.x features (CTEs, window functions, JSON operators) |
| 17 | +- ✅ **Reuses SQL Pipeline** — Composes `@objectql/driver-sql` for proven Knex-based query building |
| 18 | +- ✅ **~300KB Bundle** — Optimized WASM binary size |
| 19 | +- ✅ **Library-Agnostic API** — Public API references "SQLite WASM", underlying implementation (wa-sqlite) is swappable |
| 20 | + |
| 21 | +## Architecture |
| 22 | + |
| 23 | +``` |
| 24 | +QueryAST → SqlDriver (Knex) → SQL string → wa-sqlite WASM → OPFS/Memory |
| 25 | +``` |
| 26 | + |
| 27 | +This driver uses **composition over inheritance**: |
| 28 | +- Wraps `SqlDriver` from `@objectql/driver-sql` internally |
| 29 | +- Custom Knex client adapter bridges wa-sqlite WASM API |
| 30 | +- All query compilation logic delegated to `SqlDriver` |
| 31 | +- No code duplication, only WASM integration layer |
| 32 | + |
| 33 | +## Installation |
| 34 | + |
| 35 | +```bash |
| 36 | +pnpm add @objectql/driver-sqlite-wasm |
| 37 | +``` |
| 38 | + |
| 39 | +## Usage |
| 40 | + |
| 41 | +### Basic Example |
| 42 | + |
| 43 | +```typescript |
| 44 | +import { SqliteWasmDriver } from '@objectql/driver-sqlite-wasm'; |
| 45 | + |
| 46 | +// Create driver with OPFS persistence (default) |
| 47 | +const driver = new SqliteWasmDriver({ |
| 48 | + storage: 'opfs', // 'opfs' | 'memory' |
| 49 | + filename: 'myapp.db', // Database filename in OPFS |
| 50 | + walMode: true, // Enable WAL mode for concurrency |
| 51 | + pageSize: 4096 // SQLite page size |
| 52 | +}); |
| 53 | + |
| 54 | +// Initialize schema |
| 55 | +await driver.init([ |
| 56 | + { |
| 57 | + name: 'tasks', |
| 58 | + fields: { |
| 59 | + id: { type: 'text', primary: true }, |
| 60 | + title: { type: 'text' }, |
| 61 | + completed: { type: 'boolean', default: false } |
| 62 | + } |
| 63 | + } |
| 64 | +]); |
| 65 | + |
| 66 | +// CRUD operations |
| 67 | +const task = await driver.create('tasks', { |
| 68 | + title: 'Learn ObjectQL', |
| 69 | + completed: false |
| 70 | +}); |
| 71 | + |
| 72 | +const tasks = await driver.find('tasks', { |
| 73 | + where: { completed: false }, |
| 74 | + orderBy: [{ field: 'title', order: 'asc' }] |
| 75 | +}); |
| 76 | + |
| 77 | +await driver.update('tasks', task.id, { completed: true }); |
| 78 | + |
| 79 | +await driver.delete('tasks', task.id); |
| 80 | +``` |
| 81 | + |
| 82 | +### With ObjectStack Kernel |
| 83 | + |
| 84 | +```typescript |
| 85 | +import { ObjectStackKernel } from '@objectstack/runtime'; |
| 86 | +import { SqliteWasmDriver } from '@objectql/driver-sqlite-wasm'; |
| 87 | +import { HonoServerPlugin } from '@objectstack/plugin-hono-server'; |
| 88 | +import MyAppManifest from './objectstack.config'; |
| 89 | + |
| 90 | +const kernel = new ObjectStackKernel([ |
| 91 | + MyAppManifest, |
| 92 | + new SqliteWasmDriver({ storage: 'opfs' }), |
| 93 | + new HonoServerPlugin({ port: 3000 }) |
| 94 | +]); |
| 95 | + |
| 96 | +await kernel.start(); |
| 97 | +``` |
| 98 | + |
| 99 | +### In-Memory Mode (Testing/SSR) |
| 100 | + |
| 101 | +```typescript |
| 102 | +// Ephemeral storage - data lost on page refresh |
| 103 | +const driver = new SqliteWasmDriver({ storage: 'memory' }); |
| 104 | +``` |
| 105 | + |
| 106 | +## Configuration |
| 107 | + |
| 108 | +```typescript |
| 109 | +export interface SqliteWasmDriverConfig { |
| 110 | + /** Storage backend. Default: 'opfs' */ |
| 111 | + storage?: 'opfs' | 'memory'; |
| 112 | + |
| 113 | + /** Database filename in OPFS. Default: 'objectql.db' */ |
| 114 | + filename?: string; |
| 115 | + |
| 116 | + /** Enable WAL mode for better read concurrency. Default: true */ |
| 117 | + walMode?: boolean; |
| 118 | + |
| 119 | + /** Page size in bytes. Default: 4096 */ |
| 120 | + pageSize?: number; |
| 121 | +} |
| 122 | +``` |
| 123 | + |
| 124 | +### Storage Modes |
| 125 | + |
| 126 | +| Mode | Persistence | Use Case | Browser Support | |
| 127 | +|------|-------------|----------|-----------------| |
| 128 | +| **opfs** | Persistent (survives refresh) | Production PWAs, offline apps | Chrome 102+, Edge 102+, Safari 15.2+ | |
| 129 | +| **memory** | Ephemeral (lost on refresh) | Testing, SSR, demos | All browsers with WASM support | |
| 130 | + |
| 131 | +**Auto-fallback:** If `storage: 'opfs'` is specified but OPFS is unavailable, the driver automatically falls back to `'memory'` with a console warning. |
| 132 | + |
| 133 | +## Environment Requirements |
| 134 | + |
| 135 | +| Requirement | Minimum Version | |
| 136 | +|-------------|-----------------| |
| 137 | +| **WebAssembly** | Required (95%+ browser coverage) | |
| 138 | +| **OPFS** | Optional (for persistence) | |
| 139 | +| **Browser Support** | Chrome 102+, Firefox 110+, Safari 15.2+, Edge 102+ | |
| 140 | + |
| 141 | +**Environment Detection:** |
| 142 | + |
| 143 | +```typescript |
| 144 | +import { checkWebAssembly, checkOPFS, detectStorageBackend } from '@objectql/driver-sqlite-wasm'; |
| 145 | + |
| 146 | +// Throws ObjectQLError({ code: 'ENVIRONMENT_ERROR' }) if WASM unavailable |
| 147 | +checkWebAssembly(); |
| 148 | + |
| 149 | +// Returns true if OPFS is supported |
| 150 | +const hasOPFS = await checkOPFS(); |
| 151 | + |
| 152 | +// Auto-detect best storage backend |
| 153 | +const storage = await detectStorageBackend(); // 'opfs' | 'memory' |
| 154 | + |
| 155 | +const driver = new SqliteWasmDriver({ storage }); |
| 156 | +``` |
| 157 | + |
| 158 | +## Driver Capabilities |
| 159 | + |
| 160 | +```typescript |
| 161 | +driver.supports = { |
| 162 | + create: true, |
| 163 | + read: true, |
| 164 | + update: true, |
| 165 | + delete: true, |
| 166 | + bulkCreate: true, |
| 167 | + bulkUpdate: true, |
| 168 | + bulkDelete: true, |
| 169 | + transactions: false, // Single connection limitation |
| 170 | + savepoints: false, |
| 171 | + queryFilters: true, |
| 172 | + queryAggregations: true, |
| 173 | + querySorting: true, |
| 174 | + queryPagination: true, |
| 175 | + queryWindowFunctions: true, |
| 176 | + querySubqueries: true, |
| 177 | + queryCTE: true, |
| 178 | + joins: true, |
| 179 | + fullTextSearch: true, |
| 180 | + jsonFields: true, |
| 181 | + arrayFields: false, |
| 182 | + streaming: false, |
| 183 | + schemaSync: true, |
| 184 | + migrations: true, |
| 185 | + indexes: true, |
| 186 | + connectionPooling: false, // Single connection |
| 187 | + preparedStatements: true, |
| 188 | + queryCache: false |
| 189 | +}; |
| 190 | +``` |
| 191 | + |
| 192 | +## API Reference |
| 193 | + |
| 194 | +### Driver Methods |
| 195 | + |
| 196 | +All standard `Driver` interface methods are supported: |
| 197 | + |
| 198 | +| Method | Description | |
| 199 | +|--------|-------------| |
| 200 | +| `connect()` | Initialize WASM module and database | |
| 201 | +| `disconnect()` | Close database connection | |
| 202 | +| `checkHealth()` | Verify database is responsive | |
| 203 | +| `find(object, query, options?)` | Query records | |
| 204 | +| `findOne(object, id, query?, options?)` | Get single record by ID | |
| 205 | +| `create(object, data, options?)` | Insert record | |
| 206 | +| `update(object, id, data, options?)` | Update record | |
| 207 | +| `delete(object, id, options?)` | Delete record | |
| 208 | +| `count(object, filters, options?)` | Count records | |
| 209 | +| `bulkCreate(object, data[], options?)` | Insert multiple records | |
| 210 | +| `bulkUpdate(object, updates[], options?)` | Update multiple records | |
| 211 | +| `bulkDelete(object, ids[], options?)` | Delete multiple records | |
| 212 | +| `aggregate(object, query, options?)` | Aggregate query | |
| 213 | +| `distinct(object, field, filters?, options?)` | Get distinct values | |
| 214 | +| `init(objects[])` | Initialize schema from metadata | |
| 215 | +| `introspectSchema()` | Discover existing schema | |
| 216 | +| `executeQuery(ast, options?)` | Execute QueryAST (DriverInterface v4.0) | |
| 217 | +| `executeCommand(command, options?)` | Execute Command (DriverInterface v4.0) | |
| 218 | + |
| 219 | +### Query Syntax |
| 220 | + |
| 221 | +Supports both legacy filter syntax and modern QueryAST: |
| 222 | + |
| 223 | +```typescript |
| 224 | +// MongoDB-style filters |
| 225 | +await driver.find('tasks', { |
| 226 | + where: { |
| 227 | + $or: [ |
| 228 | + { completed: true }, |
| 229 | + { priority: { $gte: 5 } } |
| 230 | + ] |
| 231 | + }, |
| 232 | + orderBy: [{ field: 'createdAt', order: 'desc' }], |
| 233 | + limit: 10, |
| 234 | + skip: 0 |
| 235 | +}); |
| 236 | + |
| 237 | +// Simple object filters |
| 238 | +await driver.find('tasks', { |
| 239 | + where: { completed: false, assignee: 'alice@example.com' } |
| 240 | +}); |
| 241 | +``` |
| 242 | + |
| 243 | +## Performance Considerations |
| 244 | + |
| 245 | +### OPFS Storage |
| 246 | + |
| 247 | +- **Quota:** Browsers allocate significant storage (10GB+ on desktop) |
| 248 | +- **Performance:** Near-native I/O speed (~80% of native SQLite) |
| 249 | +- **Concurrency:** Single connection per database (no cross-tab locking yet) |
| 250 | + |
| 251 | +### WAL Mode |
| 252 | + |
| 253 | +Enabled by default (`walMode: true`): |
| 254 | +- Better read concurrency |
| 255 | +- Faster writes (batched to disk) |
| 256 | +- Small WAL file overhead |
| 257 | + |
| 258 | +### Bundle Size |
| 259 | + |
| 260 | +| Component | Size (gzip) | |
| 261 | +|-----------|-------------| |
| 262 | +| Driver code | ~5KB | |
| 263 | +| wa-sqlite WASM | ~295KB | |
| 264 | +| **Total** | **~300KB** | |
| 265 | + |
| 266 | +**Optimization Tips:** |
| 267 | +- Use dynamic imports to lazy-load the driver |
| 268 | +- Consider code-splitting for apps with multiple drivers |
| 269 | +- WASM binary is cacheable (set long `Cache-Control` headers) |
| 270 | + |
| 271 | +## Migration from LocalStorage Driver |
| 272 | + |
| 273 | +If you previously used `@objectql/driver-localstorage` (now deprecated): |
| 274 | + |
| 275 | +```typescript |
| 276 | +// OLD (deprecated) |
| 277 | +import { LocalStorageDriver } from '@objectql/driver-localstorage'; |
| 278 | +const driver = new LocalStorageDriver(); |
| 279 | + |
| 280 | +// NEW (recommended) |
| 281 | +import { SqliteWasmDriver } from '@objectql/driver-sqlite-wasm'; |
| 282 | +const driver = new SqliteWasmDriver({ storage: 'opfs' }); |
| 283 | +``` |
| 284 | + |
| 285 | +**Benefits of Migration:** |
| 286 | +- No 5MB localStorage limit |
| 287 | +- Full SQL query support (joins, aggregations, subqueries) |
| 288 | +- Better performance for large datasets |
| 289 | +- Data persistence across sessions |
| 290 | + |
| 291 | +## Troubleshooting |
| 292 | + |
| 293 | +### ENVIRONMENT_ERROR: WebAssembly not supported |
| 294 | + |
| 295 | +**Cause:** Browser does not support WebAssembly. |
| 296 | + |
| 297 | +**Solution:** Check browser compatibility. All modern browsers (Chrome 57+, Firefox 52+, Safari 11+) support WASM. |
| 298 | + |
| 299 | +### OPFS not available warning |
| 300 | + |
| 301 | +**Cause:** Browser does not support OPFS or it's disabled. |
| 302 | + |
| 303 | +**Solution:** Driver auto-falls back to memory storage. To enable OPFS: |
| 304 | +- Ensure browser version supports OPFS (Chrome 102+, Safari 15.2+) |
| 305 | +- Check if OPFS is disabled in browser settings |
| 306 | +- Verify site is served over HTTPS (required for OPFS) |
| 307 | + |
| 308 | +### Data lost after page refresh |
| 309 | + |
| 310 | +**Cause:** Driver is using memory storage instead of OPFS. |
| 311 | + |
| 312 | +**Solution:** |
| 313 | +```typescript |
| 314 | +// Explicitly check OPFS availability |
| 315 | +import { checkOPFS } from '@objectql/driver-sqlite-wasm'; |
| 316 | + |
| 317 | +if (await checkOPFS()) { |
| 318 | + const driver = new SqliteWasmDriver({ storage: 'opfs' }); |
| 319 | +} else { |
| 320 | + console.error('OPFS not available - data will not persist'); |
| 321 | +} |
| 322 | +``` |
| 323 | + |
| 324 | +### Cross-origin isolation errors |
| 325 | + |
| 326 | +**Cause:** SharedArrayBuffer requires cross-origin isolation headers. |
| 327 | + |
| 328 | +**Solution:** Set these HTTP headers: |
| 329 | +``` |
| 330 | +Cross-Origin-Embedder-Policy: require-corp |
| 331 | +Cross-Origin-Opener-Policy: same-origin |
| 332 | +``` |
| 333 | + |
| 334 | +## Limitations |
| 335 | + |
| 336 | +- **No Transactions:** Single connection architecture prevents full ACID transactions |
| 337 | +- **No Cross-Tab Sync:** Database changes not visible across tabs/windows (yet) |
| 338 | +- **No Streaming:** Result sets fully materialized in memory |
| 339 | +- **No Array Fields:** SQLite JSON fields supported, but not native arrays |
| 340 | + |
| 341 | +## Roadmap |
| 342 | + |
| 343 | +- [ ] Cross-tab synchronization via BroadcastChannel |
| 344 | +- [ ] Transaction support via async locking |
| 345 | +- [ ] Streaming query results |
| 346 | +- [ ] Incremental vacuum on disconnect |
| 347 | +- [ ] IndexedDB fallback for older browsers |
| 348 | +- [ ] Encryption at rest (SQLite extension) |
| 349 | + |
| 350 | +## License |
| 351 | + |
| 352 | +MIT © ObjectStack Inc. |
| 353 | + |
| 354 | +## Related Packages |
| 355 | + |
| 356 | +- [`@objectql/driver-sql`](../sql) — Server-side SQL driver (PostgreSQL, MySQL, SQLite) |
| 357 | +- [`@objectql/driver-pg-wasm`](../pg-wasm) — PostgreSQL WASM driver (coming in Q1 P1) |
| 358 | +- [`@objectql/driver-memory`](../memory) — In-memory driver for testing |
| 359 | +- [`@objectql/driver-sdk`](../sdk) — Remote HTTP driver |
| 360 | + |
| 361 | +## Contributing |
| 362 | + |
| 363 | +See [CONTRIBUTING.md](../../../CONTRIBUTING.md) for development guidelines. |
| 364 | + |
| 365 | +## Support |
| 366 | + |
| 367 | +- [Documentation](https://objectql.dev/drivers/sqlite-wasm) |
| 368 | +- [GitHub Issues](https://github.com/objectstack-ai/objectql/issues) |
| 369 | +- [Discord Community](https://discord.gg/objectql) |
0 commit comments