mirror of
https://github.com/aljazceru/turso.git
synced 2026-01-14 13:44:21 +01:00
Merge 'Partial sync basic' from Nikita Sivukhin
This PR implements basic support for partial sync. Right now the scope is limited to only `:memory:` IO and later will be properly expanded to the file based IO later. The main addition is `PartialDatabaseStorage` which make request to the remote server for missing local pages on demand. The main change is that now tursodatabase JS bindings accept optional "external" IO event loop which in case of sync will drive `ProtocolIo` internal work associated with remote page fetching tasks. Closes #3931
This commit is contained in:
@@ -1,7 +1,17 @@
|
||||
import { run, memoryIO, SyncEngineGuards } from "./run.js"
|
||||
import { DatabaseOpts, ProtocolIo, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, DatabaseChangeType, EncryptionOpts } from "./types.js"
|
||||
import { run, memoryIO, runner, SyncEngineGuards, Runner } from "./run.js"
|
||||
import {
|
||||
DatabaseOpts,
|
||||
ProtocolIo,
|
||||
RunOpts,
|
||||
DatabaseRowMutation,
|
||||
DatabaseRowStatement,
|
||||
DatabaseRowTransformResult,
|
||||
DatabaseStats,
|
||||
DatabaseChangeType,
|
||||
EncryptionOpts,
|
||||
} from "./types.js"
|
||||
|
||||
export { run, memoryIO, SyncEngineGuards }
|
||||
export { run, memoryIO, runner, SyncEngineGuards, Runner }
|
||||
export type {
|
||||
DatabaseStats,
|
||||
DatabaseOpts,
|
||||
|
||||
@@ -116,9 +116,28 @@ export function memoryIO(): ProtocolIo {
|
||||
}
|
||||
};
|
||||
|
||||
export interface Runner {
|
||||
wait(): Promise<void>;
|
||||
}
|
||||
|
||||
export async function run(opts: RunOpts, io: ProtocolIo, engine: any, generator: any): Promise<any> {
|
||||
export function runner(opts: RunOpts, io: ProtocolIo, engine: any): Runner {
|
||||
let tasks = [];
|
||||
return {
|
||||
async wait() {
|
||||
for (let request = engine.protocolIo(); request != null; request = engine.protocolIo()) {
|
||||
tasks.push(trackPromise(process(opts, io, request)));
|
||||
}
|
||||
const tasksRace = tasks.length == 0 ? Promise.resolve() : Promise.race([timeoutMs(opts.preemptionMs), ...tasks.map(t => t.promise)]);
|
||||
await Promise.all([engine.ioLoopAsync(), tasksRace]);
|
||||
|
||||
tasks = tasks.filter(t => !t.finished);
|
||||
|
||||
engine.protocolIoStep();
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
export async function run(runner: Runner, generator: any): Promise<any> {
|
||||
while (true) {
|
||||
const { type, ...rest }: GeneratorResponse = await generator.resumeAsync(null);
|
||||
if (type == 'Done') {
|
||||
@@ -131,14 +150,7 @@ export async function run(opts: RunOpts, io: ProtocolIo, engine: any, generator:
|
||||
//@ts-ignore
|
||||
return rest.changes;
|
||||
}
|
||||
for (let request = engine.protocolIo(); request != null; request = engine.protocolIo()) {
|
||||
tasks.push(trackPromise(process(opts, io, request)));
|
||||
}
|
||||
|
||||
const tasksRace = tasks.length == 0 ? Promise.resolve() : Promise.race([timeoutMs(opts.preemptionMs), ...tasks.map(t => t.promise)]);
|
||||
await Promise.all([engine.ioLoopAsync(), tasksRace]);
|
||||
|
||||
tasks = tasks.filter(t => !t.finished);
|
||||
await runner.wait();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -107,6 +107,10 @@ export interface DatabaseOpts {
|
||||
* optional parameter to enable internal logging for the database
|
||||
*/
|
||||
tracing?: 'error' | 'warn' | 'info' | 'debug' | 'trace',
|
||||
/**
|
||||
* optional parameter to enable partial sync for the database
|
||||
*/
|
||||
partialBootstrapStrategy?: { kind: 'prefix', length: number } | { kind: 'query', query: string };
|
||||
}
|
||||
export interface DatabaseStats {
|
||||
/**
|
||||
@@ -134,6 +138,14 @@ export interface DatabaseStats {
|
||||
* (can be used as e-tag, but string must not be interpreted in any way and must be used as opaque value)
|
||||
*/
|
||||
revision: string | null;
|
||||
/**
|
||||
* total amount of sent bytes over the network
|
||||
*/
|
||||
networkSentBytes: number;
|
||||
/**
|
||||
* total amount of received bytes over the network
|
||||
*/
|
||||
networkReceivedBytes: number;
|
||||
}
|
||||
|
||||
/* internal types used in the native/browser packages */
|
||||
|
||||
@@ -163,7 +163,7 @@ export declare class JsDataCompletion {
|
||||
}
|
||||
|
||||
export declare class JsProtocolIo {
|
||||
takeRequest(): JsProtocolRequestBytes | null
|
||||
|
||||
}
|
||||
|
||||
export declare class JsProtocolRequestBytes {
|
||||
@@ -178,6 +178,7 @@ export declare class SyncEngine {
|
||||
/** Runs the I/O loop asynchronously, returning a Promise. */
|
||||
ioLoopAsync(): Promise<void>
|
||||
protocolIo(): JsProtocolRequestBytes | null
|
||||
protocolIoStep(): void
|
||||
push(): GeneratorHolder
|
||||
stats(): GeneratorHolder
|
||||
wait(): GeneratorHolder
|
||||
@@ -220,9 +221,13 @@ export type DatabaseRowTransformResultJs =
|
||||
export type GeneratorResponse =
|
||||
| { type: 'IO' }
|
||||
| { type: 'Done' }
|
||||
| { type: 'SyncEngineStats', operations: number, mainWal: number, revertWal: number, lastPullUnixTime?: number, lastPushUnixTime?: number, revision?: string }
|
||||
| { type: 'SyncEngineStats', operations: number, mainWal: number, revertWal: number, lastPullUnixTime?: number, lastPushUnixTime?: number, revision?: string, networkSentBytes: number, networkReceivedBytes: number }
|
||||
| { type: 'SyncEngineChanges', changes: SyncEngineChanges }
|
||||
|
||||
export type JsPartialBootstrapStrategy =
|
||||
| { type: 'Prefix', length: number }
|
||||
| { type: 'Query', query: string }
|
||||
|
||||
export type JsProtocolRequest =
|
||||
| { type: 'Http', method: string, path: string, body?: Array<number>, headers: Array<[string, string]> }
|
||||
| { type: 'FullRead', path: string }
|
||||
@@ -240,6 +245,7 @@ export interface SyncEngineOpts {
|
||||
protocolVersion?: SyncEngineProtocolVersion
|
||||
bootstrapIfEmpty: boolean
|
||||
remoteEncryption?: string
|
||||
partialBoostrapStrategy?: JsPartialBootstrapStrategy
|
||||
}
|
||||
|
||||
export declare const enum SyncEngineProtocolVersion {
|
||||
|
||||
@@ -81,8 +81,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-android-arm64')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-android-arm64/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -97,8 +97,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-android-arm-eabi')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-android-arm-eabi/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -117,8 +117,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-win32-x64-msvc')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-win32-x64-msvc/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -133,8 +133,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-win32-ia32-msvc')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-win32-ia32-msvc/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -149,8 +149,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-win32-arm64-msvc')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-win32-arm64-msvc/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -168,8 +168,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-darwin-universal')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-darwin-universal/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -184,8 +184,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-darwin-x64')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-darwin-x64/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -200,8 +200,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-darwin-arm64')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-darwin-arm64/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -220,8 +220,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-freebsd-x64')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-freebsd-x64/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -236,8 +236,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-freebsd-arm64')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-freebsd-arm64/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -257,8 +257,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-x64-musl')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-x64-musl/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -273,8 +273,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-x64-gnu')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-x64-gnu/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -291,8 +291,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-arm64-musl')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-arm64-musl/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -307,8 +307,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-arm64-gnu')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-arm64-gnu/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -325,8 +325,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-arm-musleabihf')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-arm-musleabihf/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -341,8 +341,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-arm-gnueabihf')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-arm-gnueabihf/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -359,8 +359,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-riscv64-musl')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-riscv64-musl/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -375,8 +375,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-riscv64-gnu')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-riscv64-gnu/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -392,8 +392,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-ppc64-gnu')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-ppc64-gnu/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -408,8 +408,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-linux-s390x-gnu')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-linux-s390x-gnu/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -428,8 +428,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-openharmony-arm64')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-openharmony-arm64/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -444,8 +444,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-openharmony-x64')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-openharmony-x64/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
@@ -460,8 +460,8 @@ function requireNative() {
|
||||
try {
|
||||
const binding = require('@tursodatabase/sync-openharmony-arm')
|
||||
const bindingPackageVersion = require('@tursodatabase/sync-openharmony-arm/package.json').version
|
||||
if (bindingPackageVersion !== '0.3.0-pre.4' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.3.0-pre.4 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
if (bindingPackageVersion !== '0.4.0-pre.1' && process.env.NAPI_RS_ENFORCE_VERSION_CHECK && process.env.NAPI_RS_ENFORCE_VERSION_CHECK !== '0') {
|
||||
throw new Error(`Native binding package version mismatch, expected 0.4.0-pre.1 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`)
|
||||
}
|
||||
return binding
|
||||
} catch (e) {
|
||||
|
||||
@@ -13,6 +13,77 @@ function cleanup(path) {
|
||||
try { unlinkSync(`${path}-wal-revert`) } catch (e) { }
|
||||
}
|
||||
|
||||
test('partial sync (prefix bootstrap strategy)', async () => {
|
||||
{
|
||||
const db = await connect({
|
||||
path: ':memory:',
|
||||
url: process.env.VITE_TURSO_DB_URL,
|
||||
longPollTimeoutMs: 100,
|
||||
});
|
||||
await db.exec("CREATE TABLE IF NOT EXISTS partial(value BLOB)");
|
||||
await db.exec("DELETE FROM partial");
|
||||
await db.exec("INSERT INTO partial SELECT randomblob(1024) FROM generate_series(1, 2000)");
|
||||
await db.push();
|
||||
await db.close();
|
||||
}
|
||||
|
||||
const db = await connect({
|
||||
path: ':memory:',
|
||||
url: process.env.VITE_TURSO_DB_URL,
|
||||
longPollTimeoutMs: 100,
|
||||
partialBootstrapStrategy: { kind: 'prefix', length: 128 * 1024 },
|
||||
});
|
||||
|
||||
// 128 pages plus some overhead (very rough estimation)
|
||||
expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (4096 + 128));
|
||||
|
||||
// select of one record shouldn't increase amount of received data
|
||||
expect(await db.prepare("SELECT length(value) as length FROM partial LIMIT 1").all()).toEqual([{ length: 1024 }]);
|
||||
expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(128 * (4096 + 128));
|
||||
|
||||
await db.prepare("INSERT INTO partial VALUES (-1)").run();
|
||||
|
||||
expect(await db.prepare("SELECT COUNT(*) as cnt FROM partial").all()).toEqual([{ cnt: 2001 }]);
|
||||
expect((await db.stats()).networkReceivedBytes).toBeGreaterThanOrEqual(2000 * 1024);
|
||||
})
|
||||
|
||||
test('partial sync (query bootstrap strategy)', async () => {
|
||||
{
|
||||
const db = await connect({
|
||||
path: ':memory:',
|
||||
url: process.env.VITE_TURSO_DB_URL,
|
||||
longPollTimeoutMs: 100,
|
||||
});
|
||||
await db.exec("CREATE TABLE IF NOT EXISTS partial_keyed(key INTEGER PRIMARY KEY, value BLOB)");
|
||||
await db.exec("DELETE FROM partial_keyed");
|
||||
await db.exec("INSERT INTO partial_keyed SELECT value, randomblob(1024) FROM generate_series(1, 2000)");
|
||||
await db.push();
|
||||
await db.close();
|
||||
}
|
||||
|
||||
const db = await connect({
|
||||
path: ':memory:',
|
||||
url: process.env.VITE_TURSO_DB_URL,
|
||||
longPollTimeoutMs: 100,
|
||||
partialBootstrapStrategy: { kind: 'query', query: 'SELECT * FROM partial_keyed WHERE key = 1000' },
|
||||
});
|
||||
|
||||
// we must sync only few pages
|
||||
expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(10 * (4096 + 128));
|
||||
|
||||
// select of one record shouldn't increase amount of received data by a lot
|
||||
expect(await db.prepare("SELECT length(value) as length FROM partial_keyed LIMIT 1").all()).toEqual([{ length: 1024 }]);
|
||||
expect((await db.stats()).networkReceivedBytes).toBeLessThanOrEqual(10 * (4096 + 128));
|
||||
|
||||
await db.prepare("INSERT INTO partial_keyed VALUES (-1, -1)").run();
|
||||
const n1 = await db.stats();
|
||||
|
||||
// same as bootstrap query - we shouldn't bring any more pages
|
||||
expect(await db.prepare("SELECT length(value) as length FROM partial_keyed WHERE key = 1000").all()).toEqual([{ length: 1024 }]);
|
||||
const n2 = await db.stats();
|
||||
expect(n1.networkReceivedBytes).toEqual(n2.networkReceivedBytes);
|
||||
})
|
||||
|
||||
test('concurrent-actions-consistency', async () => {
|
||||
{
|
||||
const db = await connect({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { DatabasePromise } from "@tursodatabase/database-common"
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards } from "@tursodatabase/sync-common";
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner } from "@tursodatabase/sync-common";
|
||||
import { SyncEngine, SyncEngineProtocolVersion, Database as NativeDatabase } from "#index";
|
||||
import { promises } from "node:fs";
|
||||
|
||||
@@ -40,10 +40,9 @@ function memoryIO(): ProtocolIo {
|
||||
}
|
||||
};
|
||||
class Database extends DatabasePromise {
|
||||
#runOpts: RunOpts;
|
||||
#engine: any;
|
||||
#io: ProtocolIo;
|
||||
#guards: SyncEngineGuards
|
||||
#guards: SyncEngineGuards;
|
||||
#runner: Runner;
|
||||
constructor(opts: DatabaseOpts) {
|
||||
if (opts.url == null) {
|
||||
super(new NativeDatabase(opts.path, { tracing: opts.tracing }) as any);
|
||||
@@ -51,6 +50,17 @@ class Database extends DatabasePromise {
|
||||
return;
|
||||
}
|
||||
|
||||
let partialBoostrapStrategy = undefined;
|
||||
if (opts.partialBootstrapStrategy != null) {
|
||||
switch (opts.partialBootstrapStrategy.kind) {
|
||||
case "prefix":
|
||||
partialBoostrapStrategy = { type: "Prefix", length: opts.partialBootstrapStrategy.length };
|
||||
break;
|
||||
case "query":
|
||||
partialBoostrapStrategy = { type: "Query", query: opts.partialBootstrapStrategy.query };
|
||||
break;
|
||||
}
|
||||
}
|
||||
const engine = new SyncEngine({
|
||||
path: opts.path,
|
||||
clientName: opts.clientName,
|
||||
@@ -60,8 +70,8 @@ class Database extends DatabasePromise {
|
||||
tracing: opts.tracing,
|
||||
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||
partialBoostrapStrategy: partialBoostrapStrategy
|
||||
});
|
||||
super(engine.db() as unknown as any);
|
||||
|
||||
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||
if (typeof opts.authToken == "function") {
|
||||
@@ -83,14 +93,21 @@ class Database extends DatabasePromise {
|
||||
})
|
||||
};
|
||||
}
|
||||
this.#runOpts = {
|
||||
const runOpts = {
|
||||
url: opts.url,
|
||||
headers: headers,
|
||||
preemptionMs: 1,
|
||||
transform: opts.transform,
|
||||
};
|
||||
const db = engine.db() as unknown as any;
|
||||
const memory = db.memory;
|
||||
const io = memory ? memoryIO() : NodeIO;
|
||||
const run = runner(runOpts, io, engine);
|
||||
|
||||
super(engine.db() as unknown as any, () => run.wait());
|
||||
|
||||
this.#runner = run;
|
||||
this.#engine = engine;
|
||||
this.#io = this.memory ? memoryIO() : NodeIO;
|
||||
this.#guards = new SyncEngineGuards();
|
||||
}
|
||||
/**
|
||||
@@ -102,7 +119,7 @@ class Database extends DatabasePromise {
|
||||
} else if (this.#engine == null) {
|
||||
await super.connect();
|
||||
} else {
|
||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||
await run(this.#runner, this.#engine.connect());
|
||||
}
|
||||
this.connected = true;
|
||||
}
|
||||
@@ -115,11 +132,11 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));
|
||||
if (changes.empty()) {
|
||||
return false;
|
||||
}
|
||||
await this.#guards.apply(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.apply(changes)));
|
||||
await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
@@ -130,7 +147,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.push(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||
await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));
|
||||
}
|
||||
/**
|
||||
* checkpoint WAL for local database
|
||||
@@ -139,7 +156,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.checkpoint(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||
await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));
|
||||
}
|
||||
/**
|
||||
* @returns statistic of current local database
|
||||
@@ -148,7 +165,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
return (await run(this.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||
return (await run(this.#runner, this.#engine.stats()));
|
||||
}
|
||||
/**
|
||||
* close the database
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
||||
import { DatabasePromise } from "@tursodatabase/database-common"
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards } from "@tursodatabase/sync-common";
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner } from "@tursodatabase/sync-common";
|
||||
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from "./index-bundle.js";
|
||||
|
||||
let BrowserIO: ProtocolIo = {
|
||||
@@ -39,7 +39,7 @@ async function init(): Promise<Worker> {
|
||||
}
|
||||
|
||||
class Database extends DatabasePromise {
|
||||
#runOpts: RunOpts;
|
||||
#runner: Runner;
|
||||
#engine: any;
|
||||
#io: ProtocolIo;
|
||||
#guards: SyncEngineGuards;
|
||||
@@ -61,7 +61,6 @@ class Database extends DatabasePromise {
|
||||
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||
});
|
||||
super(engine.db() as unknown as any);
|
||||
|
||||
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||
if (typeof opts.authToken == "function") {
|
||||
@@ -83,14 +82,21 @@ class Database extends DatabasePromise {
|
||||
})
|
||||
};
|
||||
}
|
||||
this.#runOpts = {
|
||||
const runOpts = {
|
||||
url: opts.url,
|
||||
headers: headers,
|
||||
preemptionMs: 1,
|
||||
transform: opts.transform,
|
||||
};
|
||||
const db = engine.db() as unknown as any;
|
||||
const memory = db.memory;
|
||||
const io = memory ? memoryIO() : BrowserIO;
|
||||
const run = runner(runOpts, io, engine);
|
||||
super(engine.db() as unknown as any, () => run.wait());
|
||||
|
||||
this.#runner = run;
|
||||
this.#engine = engine;
|
||||
this.#io = this.memory ? memoryIO() : BrowserIO;
|
||||
this.#io = io;
|
||||
this.#guards = new SyncEngineGuards();
|
||||
}
|
||||
/**
|
||||
@@ -112,7 +118,7 @@ class Database extends DatabasePromise {
|
||||
registerFileAtWorker(this.#worker, `${this.name}-changes`),
|
||||
]);
|
||||
}
|
||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||
await run(this.#runner, this.#engine.connect());
|
||||
}
|
||||
this.connected = true;
|
||||
}
|
||||
@@ -125,11 +131,11 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));
|
||||
if (changes.empty()) {
|
||||
return false;
|
||||
}
|
||||
await this.#guards.apply(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.apply(changes)));
|
||||
await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
@@ -140,7 +146,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.push(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||
await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));
|
||||
}
|
||||
/**
|
||||
* checkpoint WAL for local database
|
||||
@@ -149,7 +155,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.checkpoint(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||
await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));
|
||||
}
|
||||
/**
|
||||
* @returns statistic of current local database
|
||||
@@ -158,7 +164,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
return (await run(this.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||
return (await run(this.#runner, this.#engine.stats()));
|
||||
}
|
||||
/**
|
||||
* close the database and relevant files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
||||
import { DatabasePromise } from "@tursodatabase/database-common"
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards } from "@tursodatabase/sync-common";
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner } from "@tursodatabase/sync-common";
|
||||
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from "./index-default.js";
|
||||
|
||||
let BrowserIO: ProtocolIo = {
|
||||
@@ -39,7 +39,7 @@ async function init(): Promise<Worker> {
|
||||
}
|
||||
|
||||
class Database extends DatabasePromise {
|
||||
#runOpts: RunOpts;
|
||||
#runner: Runner;
|
||||
#engine: any;
|
||||
#io: ProtocolIo;
|
||||
#guards: SyncEngineGuards;
|
||||
@@ -61,7 +61,6 @@ class Database extends DatabasePromise {
|
||||
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||
});
|
||||
super(engine.db() as unknown as any);
|
||||
|
||||
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||
if (typeof opts.authToken == "function") {
|
||||
@@ -83,14 +82,21 @@ class Database extends DatabasePromise {
|
||||
})
|
||||
};
|
||||
}
|
||||
this.#runOpts = {
|
||||
const runOpts = {
|
||||
url: opts.url,
|
||||
headers: headers,
|
||||
preemptionMs: 1,
|
||||
transform: opts.transform,
|
||||
};
|
||||
const db = engine.db() as unknown as any;
|
||||
const memory = db.memory;
|
||||
const io = memory ? memoryIO() : BrowserIO;
|
||||
const run = runner(runOpts, io, engine);
|
||||
super(engine.db() as unknown as any, () => run.wait());
|
||||
|
||||
this.#runner = run;
|
||||
this.#engine = engine;
|
||||
this.#io = this.memory ? memoryIO() : BrowserIO;
|
||||
this.#io = io;
|
||||
this.#guards = new SyncEngineGuards();
|
||||
}
|
||||
/**
|
||||
@@ -112,7 +118,7 @@ class Database extends DatabasePromise {
|
||||
registerFileAtWorker(this.#worker, `${this.name}-changes`),
|
||||
]);
|
||||
}
|
||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||
await run(this.#runner, this.#engine.connect());
|
||||
}
|
||||
this.connected = true;
|
||||
}
|
||||
@@ -125,11 +131,11 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));
|
||||
if (changes.empty()) {
|
||||
return false;
|
||||
}
|
||||
await this.#guards.apply(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.apply(changes)));
|
||||
await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
@@ -140,7 +146,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.push(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||
await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));
|
||||
}
|
||||
/**
|
||||
* checkpoint WAL for local database
|
||||
@@ -149,7 +155,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.checkpoint(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||
await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));
|
||||
}
|
||||
/**
|
||||
* @returns statistic of current local database
|
||||
@@ -158,7 +164,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
return (await run(this.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||
return (await run(this.#runner, this.#engine.stats()));
|
||||
}
|
||||
/**
|
||||
* close the database and relevant files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
||||
import { DatabasePromise } from "@tursodatabase/database-common"
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards } from "@tursodatabase/sync-common";
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner } from "@tursodatabase/sync-common";
|
||||
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from "./index-turbopack-hack.js";
|
||||
|
||||
let BrowserIO: ProtocolIo = {
|
||||
@@ -39,7 +39,7 @@ async function init(): Promise<Worker> {
|
||||
}
|
||||
|
||||
class Database extends DatabasePromise {
|
||||
#runOpts: RunOpts;
|
||||
#runner: Runner;
|
||||
#engine: any;
|
||||
#io: ProtocolIo;
|
||||
#guards: SyncEngineGuards;
|
||||
@@ -61,7 +61,6 @@ class Database extends DatabasePromise {
|
||||
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||
});
|
||||
super(engine.db() as unknown as any);
|
||||
|
||||
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||
if (typeof opts.authToken == "function") {
|
||||
@@ -83,14 +82,21 @@ class Database extends DatabasePromise {
|
||||
})
|
||||
};
|
||||
}
|
||||
this.#runOpts = {
|
||||
const runOpts = {
|
||||
url: opts.url,
|
||||
headers: headers,
|
||||
preemptionMs: 1,
|
||||
transform: opts.transform,
|
||||
};
|
||||
const db = engine.db() as unknown as any;
|
||||
const memory = db.memory;
|
||||
const io = memory ? memoryIO() : BrowserIO;
|
||||
const run = runner(runOpts, io, engine);
|
||||
super(engine.db() as unknown as any, () => run.wait());
|
||||
|
||||
this.#runner = run;
|
||||
this.#engine = engine;
|
||||
this.#io = this.memory ? memoryIO() : BrowserIO;
|
||||
this.#io = io;
|
||||
this.#guards = new SyncEngineGuards();
|
||||
}
|
||||
/**
|
||||
@@ -112,7 +118,7 @@ class Database extends DatabasePromise {
|
||||
registerFileAtWorker(this.#worker, `${this.name}-changes`),
|
||||
]);
|
||||
}
|
||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||
await run(this.#runner, this.#engine.connect());
|
||||
}
|
||||
this.connected = true;
|
||||
}
|
||||
@@ -125,11 +131,11 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));
|
||||
if (changes.empty()) {
|
||||
return false;
|
||||
}
|
||||
await this.#guards.apply(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.apply(changes)));
|
||||
await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
@@ -140,7 +146,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.push(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||
await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));
|
||||
}
|
||||
/**
|
||||
* checkpoint WAL for local database
|
||||
@@ -149,7 +155,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.checkpoint(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||
await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));
|
||||
}
|
||||
/**
|
||||
* @returns statistic of current local database
|
||||
@@ -158,7 +164,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
return (await run(this.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||
return (await run(this.#runner, this.#engine.stats()));
|
||||
}
|
||||
/**
|
||||
* close the database and relevant files
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
||||
import { DatabasePromise } from "@tursodatabase/database-common"
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards } from "@tursodatabase/sync-common";
|
||||
import { ProtocolIo, run, DatabaseOpts, EncryptionOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards, Runner, runner } from "@tursodatabase/sync-common";
|
||||
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from "./index-vite-dev-hack.js";
|
||||
|
||||
let BrowserIO: ProtocolIo = {
|
||||
@@ -39,7 +39,7 @@ async function init(): Promise<Worker> {
|
||||
}
|
||||
|
||||
class Database extends DatabasePromise {
|
||||
#runOpts: RunOpts;
|
||||
#runner: Runner;
|
||||
#engine: any;
|
||||
#io: ProtocolIo;
|
||||
#guards: SyncEngineGuards;
|
||||
@@ -61,7 +61,6 @@ class Database extends DatabasePromise {
|
||||
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||
});
|
||||
super(engine.db() as unknown as any);
|
||||
|
||||
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||
if (typeof opts.authToken == "function") {
|
||||
@@ -83,14 +82,21 @@ class Database extends DatabasePromise {
|
||||
})
|
||||
};
|
||||
}
|
||||
this.#runOpts = {
|
||||
const runOpts = {
|
||||
url: opts.url,
|
||||
headers: headers,
|
||||
preemptionMs: 1,
|
||||
transform: opts.transform,
|
||||
};
|
||||
const db = engine.db() as unknown as any;
|
||||
const memory = db.memory;
|
||||
const io = memory ? memoryIO() : BrowserIO;
|
||||
const run = runner(runOpts, io, engine);
|
||||
super(engine.db() as unknown as any, () => run.wait());
|
||||
|
||||
this.#runner = run;
|
||||
this.#engine = engine;
|
||||
this.#io = this.memory ? memoryIO() : BrowserIO;
|
||||
this.#io = io;
|
||||
this.#guards = new SyncEngineGuards();
|
||||
}
|
||||
/**
|
||||
@@ -112,7 +118,7 @@ class Database extends DatabasePromise {
|
||||
registerFileAtWorker(this.#worker, `${this.name}-changes`),
|
||||
]);
|
||||
}
|
||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||
await run(this.#runner, this.#engine.connect());
|
||||
}
|
||||
this.connected = true;
|
||||
}
|
||||
@@ -125,11 +131,11 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||
const changes = await this.#guards.wait(async () => await run(this.#runner, this.#engine.wait()));
|
||||
if (changes.empty()) {
|
||||
return false;
|
||||
}
|
||||
await this.#guards.apply(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.apply(changes)));
|
||||
await this.#guards.apply(async () => await run(this.#runner, this.#engine.apply(changes)));
|
||||
return true;
|
||||
}
|
||||
/**
|
||||
@@ -140,7 +146,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.push(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||
await this.#guards.push(async () => await run(this.#runner, this.#engine.push()));
|
||||
}
|
||||
/**
|
||||
* checkpoint WAL for local database
|
||||
@@ -149,7 +155,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
await this.#guards.checkpoint(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||
await this.#guards.checkpoint(async () => await run(this.#runner, this.#engine.checkpoint()));
|
||||
}
|
||||
/**
|
||||
* @returns statistic of current local database
|
||||
@@ -158,7 +164,7 @@ class Database extends DatabasePromise {
|
||||
if (this.#engine == null) {
|
||||
throw new Error("sync is disabled as database was opened without sync support")
|
||||
}
|
||||
return (await run(this.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||
return (await run(this.#runner, this.#engine.stats()));
|
||||
}
|
||||
/**
|
||||
* close the database and relevant files
|
||||
|
||||
@@ -48,6 +48,8 @@ pub enum GeneratorResponse {
|
||||
last_pull_unix_time: Option<i64>,
|
||||
last_push_unix_time: Option<i64>,
|
||||
revision: Option<String>,
|
||||
network_sent_bytes: i64,
|
||||
network_received_bytes: i64,
|
||||
},
|
||||
SyncEngineChanges {
|
||||
changes: SyncEngineChanges,
|
||||
|
||||
@@ -241,24 +241,48 @@ impl ProtocolIO for JsProtocolIo {
|
||||
.collect(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn add_work(&self, callback: Box<dyn FnMut() -> bool + Send>) {
|
||||
let mut work = self.work.lock().unwrap();
|
||||
work.push_back(callback);
|
||||
}
|
||||
|
||||
fn step_work(&self) {
|
||||
let mut items = {
|
||||
let mut work = self.work.lock().unwrap();
|
||||
work.drain(..).collect::<VecDeque<_>>()
|
||||
};
|
||||
let length = items.len();
|
||||
for _ in 0..length {
|
||||
let mut item = items.pop_front().unwrap();
|
||||
if item() {
|
||||
continue;
|
||||
}
|
||||
items.push_back(item);
|
||||
}
|
||||
{
|
||||
let mut work = self.work.lock().unwrap();
|
||||
work.extend(items);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub struct JsProtocolIo {
|
||||
requests: Mutex<Vec<JsProtocolRequestBytes>>,
|
||||
work: Mutex<VecDeque<Box<dyn FnMut() -> bool + Send>>>,
|
||||
}
|
||||
|
||||
impl Default for JsProtocolIo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
requests: Mutex::new(Vec::new()),
|
||||
work: Mutex::new(VecDeque::new()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl JsProtocolIo {
|
||||
#[napi]
|
||||
pub fn take_request(&self) -> Option<JsProtocolRequestBytes> {
|
||||
self.requests.lock().unwrap().pop()
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ use napi::bindgen_prelude::{AsyncTask, Either5, Null};
|
||||
use napi_derive::napi;
|
||||
use turso_node::{DatabaseOpts, IoLoopTask};
|
||||
use turso_sync_engine::{
|
||||
database_sync_engine::{DatabaseSyncEngine, DatabaseSyncEngineOpts},
|
||||
database_sync_engine::{DatabaseSyncEngine, DatabaseSyncEngineOpts, PartialBootstrapStrategy},
|
||||
protocol_io::ProtocolIO,
|
||||
types::{Coro, DatabaseChangeType, DatabaseSyncEngineProtocolVersion},
|
||||
};
|
||||
|
||||
@@ -107,6 +108,13 @@ pub enum DatabaseRowTransformResultJs {
|
||||
Rewrite { stmt: DatabaseRowStatementJs },
|
||||
}
|
||||
|
||||
#[napi(discriminant = "type")]
|
||||
#[derive(Debug)]
|
||||
pub enum JsPartialBootstrapStrategy {
|
||||
Prefix { length: i64 },
|
||||
Query { query: String },
|
||||
}
|
||||
|
||||
#[napi(object, object_to_js = false)]
|
||||
pub struct SyncEngineOpts {
|
||||
pub path: String,
|
||||
@@ -119,6 +127,7 @@ pub struct SyncEngineOpts {
|
||||
pub protocol_version: Option<SyncEngineProtocolVersion>,
|
||||
pub bootstrap_if_empty: bool,
|
||||
pub remote_encryption: Option<String>,
|
||||
pub partial_boostrap_strategy: Option<JsPartialBootstrapStrategy>,
|
||||
}
|
||||
|
||||
struct SyncEngineOptsFilled {
|
||||
@@ -131,6 +140,7 @@ struct SyncEngineOptsFilled {
|
||||
pub protocol_version: DatabaseSyncEngineProtocolVersion,
|
||||
pub bootstrap_if_empty: bool,
|
||||
pub remote_encryption: Option<CipherMode>,
|
||||
pub partial_boostrap_strategy: Option<PartialBootstrapStrategy>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -170,12 +180,32 @@ impl SyncEngine {
|
||||
let io: Arc<dyn turso_core::IO> = if is_memory {
|
||||
Arc::new(turso_core::MemoryIO::new())
|
||||
} else {
|
||||
#[cfg(not(feature = "browser"))]
|
||||
#[cfg(all(target_os = "linux", not(feature = "browser")))]
|
||||
{
|
||||
if opts.partial_boostrap_strategy.is_none() {
|
||||
Arc::new(turso_core::PlatformIO::new().map_err(|e| {
|
||||
napi::Error::new(
|
||||
napi::Status::GenericFailure,
|
||||
format!("Failed to create platform IO: {e}"),
|
||||
)
|
||||
})?)
|
||||
} else {
|
||||
use turso_sync_engine::sparse_io::SparseLinuxIo;
|
||||
|
||||
Arc::new(SparseLinuxIo::new().map_err(|e| {
|
||||
napi::Error::new(
|
||||
napi::Status::GenericFailure,
|
||||
format!("Failed to create sparse IO: {e}"),
|
||||
)
|
||||
})?)
|
||||
}
|
||||
}
|
||||
#[cfg(all(not(target_os = "linux"), not(feature = "browser")))]
|
||||
{
|
||||
Arc::new(turso_core::PlatformIO::new().map_err(|e| {
|
||||
napi::Error::new(
|
||||
napi::Status::GenericFailure,
|
||||
format!("Failed to create IO: {e}"),
|
||||
format!("Failed to create platform IO: {e}"),
|
||||
)
|
||||
})?)
|
||||
}
|
||||
@@ -224,6 +254,14 @@ impl SyncEngine {
|
||||
))
|
||||
}
|
||||
},
|
||||
partial_boostrap_strategy: opts.partial_boostrap_strategy.map(|s| match s {
|
||||
JsPartialBootstrapStrategy::Prefix { length } => PartialBootstrapStrategy::Prefix {
|
||||
length: length as usize,
|
||||
},
|
||||
JsPartialBootstrapStrategy::Query { query } => {
|
||||
PartialBootstrapStrategy::Query { query }
|
||||
}
|
||||
}),
|
||||
};
|
||||
Ok(SyncEngine {
|
||||
opts: opts_filled,
|
||||
@@ -251,6 +289,7 @@ impl SyncEngine {
|
||||
.remote_encryption
|
||||
.map(|x| x.required_metadata_size())
|
||||
.unwrap_or(0),
|
||||
partial_bootstrap_strategy: self.opts.partial_boostrap_strategy.clone(),
|
||||
};
|
||||
|
||||
let io = self.io()?;
|
||||
@@ -300,6 +339,12 @@ impl SyncEngine {
|
||||
Ok(self.protocol()?.take_request())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn protocol_io_step(&self) -> napi::Result<()> {
|
||||
self.protocol()?.step_work();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn push(&self) -> GeneratorHolder {
|
||||
self.run(async move |coro, guard| {
|
||||
@@ -323,6 +368,8 @@ impl SyncEngine {
|
||||
last_pull_unix_time: stats.last_pull_unix_time,
|
||||
last_push_unix_time: stats.last_push_unix_time,
|
||||
revision: stats.revision,
|
||||
network_sent_bytes: stats.network_sent_bytes as i64,
|
||||
network_received_bytes: stats.network_received_bytes as i64,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user