feat: Data Replication Plugin Pull external data into edge SQLite replica#134
Open
suletetes wants to merge 12 commits intoouterbase:mainfrom
Open
feat: Data Replication Plugin Pull external data into edge SQLite replica#134suletetes wants to merge 12 commits intoouterbase:mainfrom
suletetes wants to merge 12 commits intoouterbase:mainfrom
Conversation
…nc engine, schema introspection, and alarm scheduling
…ence and examples
…, CRUD, sync logic, and error handling
…overing 11 correctness properties
… FROM entries, and subquery AST recursion
…ch actual SQL output
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Overview
Implements a Data Replication Plugin for StarbaseDB that pulls data from external databases (PostgreSQL, MySQL, SQLite/Turso/D1/Starbase) into the internal Durable Object SQLite store. This creates a close-to-edge replica that serves queries locally, eliminating round-trips to the external source.
Two sync modes are supported:
id,created_at) and only fetches rows newer than the last sync. UsesINSERT OR REPLACEfor deduplication.Key capabilities:
/replicationfor managing replication configsBonus: Also fixes 4 pre-existing RLS test failures caused by schema-qualified table name mismatches, a shared test state mutation bug, and broken subquery recursion.
Completed Tasks
plugins/data-replication/index.tswithDataReplicationPluginclass extendingStarbasePluginplugins/data-replication/meta.jsonfollowing existing plugin patternsplugins/data-replication/README.mdwith usage documentationregister()method with middleware and all route handlers (POST/GET/PUT/DELETE configs, GET status, POST sync, POST callback)mapToSQLiteType()andintrospectSchema()methods with dialect-specific queries (PostgreSQL, MySQL, SQLite)ensureTargetTable()for automatic target table creationfetchExternalRows()with optional cursor-based WHERE clause filteringinsertRows()supporting both full replacement and incremental modesupdateSyncState()for persisting sync metadatasyncConfig()orchestrating the full sync flowscheduleNextAlarm()computing earliest due time across all enabled configsfinallyblock for alarm chain resiliencesrc/index.tsto instantiate and registerDataReplicationPluginsrc/do.tsalarm handler to POST to/replication/callbackrpc.executeQuery()type assertionsplugins/data-replication/index.test.tsplugins/data-replication/index.property.test.ts(fast-check, 100 iterations each, 11 correctness properties)src/rls/index.tsfromItem.expr.astsrc/rls/index.test.tsHow to test / use it
1. Register the plugin (already wired in
src/index.ts)2. Create a replication config
3. Manually trigger a sync
curl -X POST https://your-endpoint/replication/sync/1 \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN"4. Check sync status
curl https://your-endpoint/replication/status \ -H "Authorization: Bearer YOUR_ADMIN_TOKEN"5. Run the tests
How it works (technical)
Architecture
Sync algorithm
/replication/callbackfinallyblockSchema introspection
SELECT column_name, data_type FROM information_schema.columnsPRAGMA table_info(table_name)INTEGER, float types →REAL, binary types →BLOB, boolean →INTEGER, everything else →TEXTDO alarm integration
The
alarm()handler insrc/do.tswas extended to also querytmp_replication_configsfor acallback_hostand POST to/replication/callback, alongside the existing cron callback. Wrapped in try/catch to avoid disrupting the cron alarm chain.Testing & edge cases covered
Test suite: 222 tests, 0 failures
I have tested all the changes all 222 tests across 21 test files pass at 100%. Screenshot below.
plugins/data-replication/index.test.tsplugins/data-replication/index.property.test.tssrc/rls/index.test.tsEdge cases handled
source_table→ 400 errorinterval_seconds→ 400 errortarget_tabledefaults tosource_tablewhen omittedcolumnsdefaults to*(all columns) when omittedenabled: false) are skipped during sync cyclesfinallyblockvarchar(255)) are normalized before mappingRLS fixes (bonus)
schema.tableformat (e.g.public.users) now correctly match unqualified table names in queries (users)mockConfig, preventing downstream test pollutionapplyRLSToAstto recurse intofromItem.expr.ast(where node-sql-parser puts subquery ASTs) instead offromItem.exprfromItem.tableexistence before callingnormalizeIdentifierto handle subquery FROM entriesFiles changed
New files
plugins/data-replication/index.tsPlugin implementation (CRUD API, sync engine, schema introspection, alarm scheduling)plugins/data-replication/index.test.ts47 unit testsplugins/data-replication/index.property.test.ts20 property-based tests (fast-check)plugins/data-replication/meta.jsonPlugin metadataplugins/data-replication/README.mdUsage documentationModified files
src/index.tsImport and registerDataReplicationPluginsrc/do.tsExtend alarm handler to POST to/replication/callbacksrc/rls/index.tsFix schema-qualified table matching, null guard, subquery recursionsrc/rls/index.test.tsFix test state mutation, update assertions to match actual SQL outputpackage.jsonAddfast-checkdev dependencypnpm-lock.yamlUpdated lockfile/claim #72