@@ -397,6 +397,66 @@ await db.executeTransaction([
397397 * Attachments are connection-scoped and don't persist across queries
398398 * Main database is always accessible without a schema prefix
399399
400+ ### Change Notifications
401+
402+ Subscribe to real-time change notifications when rows are inserted, updated, or
403+ deleted. Changes are only published after transactions commit — you never see
404+ partial or rolled-back data.
405+
406+ ``` typescript
407+ // 1. Enable observation for specific tables
408+ await db .observe ([' users' , ' posts' ]);
409+
410+ // 2. Subscribe to changes
411+ const subscription = await db .subscribe ([' users' ], (event ) => {
412+ if (event .event === ' change' ) {
413+ const { table, operation, primaryKey, newValues, oldValues } = event .data ;
414+
415+ console .info (` ${operation } on ${table }, row key: ` , primaryKey );
416+
417+ if (operation === ' insert' || operation === ' update' ) {
418+ console .info (' New values:' , newValues );
419+ }
420+ if (operation === ' update' || operation === ' delete' ) {
421+ console .info (' Old values:' , oldValues );
422+ }
423+ } else if (event .event === ' lagged' ) {
424+ // Consumer fell behind — some notifications were missed
425+ console .warn (` Missed ${event .data .count } notifications ` );
426+ }
427+ });
428+
429+ // 3. Changes are now streamed to the callback
430+ await db .execute (' INSERT INTO users (name) VALUES ($1)' , [' Alice' ]);
431+ // callback fires: { event: 'change', data: { table: 'users', operation: 'insert', ... } }
432+
433+ // 4. Unsubscribe when done
434+ await subscription .unsubscribe ();
435+
436+ // 5. Disable observation entirely (also aborts all active subscriptions)
437+ await db .unobserve ();
438+ ```
439+
440+ ** Configuration:**
441+
442+ ``` typescript
443+ await db .observe ([' users' ], {
444+ channelCapacity: 512 , // default: 256 — at least the number of writes in your largest transaction
445+ captureValues: false , // default: true — disable to reduce memory per notification
446+ });
447+ ```
448+
449+ ** Important:**
450+
451+ * Call ` observe() ` before ` subscribe() ` — subscribing without observation returns
452+ an error
453+ * Multiple subscriptions can be active on the same database, each filtering by
454+ different tables
455+ * ` lagged ` events indicate the broadcast channel filled up before the
456+ subscriber could read — increase ` channelCapacity `
457+ * Column values (` oldValues ` , ` newValues ` ) are typed as ` ColumnValue ` — a tagged
458+ union of ` null ` , ` integer ` , ` real ` , ` text ` , or ` blob ` (base64-encoded)
459+
400460### Error Handling
401461
402462``` typescript
@@ -419,6 +479,8 @@ Common error codes:
419479 * ` IO_ERROR ` - File system error
420480 * ` MIGRATION_ERROR ` - Migration failed
421481 * ` MULTIPLE_ROWS_RETURNED ` - ` fetchOne() ` returned multiple rows
482+ * ` OBSERVATION_NOT_ENABLED ` - Called ` subscribe() ` before ` observe() `
483+ * ` OBSERVER_ERROR ` - Error from the observer subsystem
422484
423485### Closing and Removing
424486
@@ -449,6 +511,9 @@ await db.remove(); // Close and DELETE database file(s) - irreversible
449511| ` fetchOne<T>(query, values?) ` | Execute SELECT, return single row or ` undefined ` |
450512| ` close() ` | Close connection, returns ` true ` if was loaded |
451513| ` remove() ` | Close and delete database file(s), returns ` true ` if was loaded |
514+ | ` observe(tables, config?) ` | Enable change observation for tables |
515+ | ` subscribe(tables, onEvent) ` | Subscribe to change notifications, returns ` Subscription ` |
516+ | ` unobserve() ` | Disable observation and abort all subscriptions |
452517
453518### Builder Methods
454519
@@ -469,6 +534,12 @@ return builders that are directly awaitable and support method chaining:
469534| ` commit() ` | Commit transaction and release write lock |
470535| ` rollback() ` | Rollback transaction and release write lock |
471536
537+ ### Subscription Methods
538+
539+ | Method | Description |
540+ | ------ | ----------- |
541+ | ` unsubscribe() ` | Stop receiving change notifications, returns ` true ` if was active |
542+
472543### Types
473544
474545``` typescript
@@ -492,6 +563,33 @@ interface SqliteError {
492563 code: string ;
493564 message: string ;
494565}
566+
567+ interface ObserverConfig {
568+ channelCapacity? : number ; // default: 256
569+ captureValues? : boolean ; // default: true
570+ }
571+
572+ type ChangeOperation = ' insert' | ' update' | ' delete' ;
573+
574+ type ColumnValue =
575+ | { type: ' null' }
576+ | { type: ' integer' ; value: number }
577+ | { type: ' real' ; value: number }
578+ | { type: ' text' ; value: string }
579+ | { type: ' blob' ; value: string }; // base64-encoded
580+
581+ interface TableChange {
582+ table: string ;
583+ operation? : ChangeOperation ;
584+ rowid? : number ;
585+ primaryKey: ColumnValue [];
586+ oldValues? : ColumnValue []; // update, delete
587+ newValues? : ColumnValue []; // insert, update
588+ }
589+
590+ type TableChangeEvent =
591+ | { event: ' change' ; data: TableChange }
592+ | { event: ' lagged' ; data: { count: number } };
495593```
496594
497595## Rust-Only API
0 commit comments