diff --git a/bindings/javascript/sync/packages/common/index.ts b/bindings/javascript/sync/packages/common/index.ts index 7e9af0bea..03db7e853 100644 --- a/bindings/javascript/sync/packages/common/index.ts +++ b/bindings/javascript/sync/packages/common/index.ts @@ -1,5 +1,15 @@ import { run, memoryIO, SyncEngineGuards } from "./run.js" -import { SyncOpts, ProtocolIo, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, SyncEngineStats } from "./types.js" +import { DatabaseOpts, ProtocolIo, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, DatabaseChangeType } from "./types.js" export { run, memoryIO, SyncEngineGuards } -export type { SyncOpts, ProtocolIo, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, SyncEngineStats } \ No newline at end of file +export type { + DatabaseStats, + DatabaseOpts, + DatabaseChangeType, + DatabaseRowMutation, + DatabaseRowStatement, + DatabaseRowTransformResult, + + ProtocolIo, + RunOpts, +} \ No newline at end of file diff --git a/bindings/javascript/sync/packages/common/run.ts b/bindings/javascript/sync/packages/common/run.ts index f80606db8..4d1d1c969 100644 --- a/bindings/javascript/sync/packages/common/run.ts +++ b/bindings/javascript/sync/packages/common/run.ts @@ -3,9 +3,6 @@ import { GeneratorResponse, ProtocolIo, RunOpts } from "./types.js"; import { AsyncLock } from "@tursodatabase/database-common"; -const GENERATOR_RESUME_IO = 0; -const GENERATOR_RESUME_DONE = 1; - interface TrackPromise { promise: Promise, finished: boolean @@ -26,7 +23,7 @@ async function process(opts: RunOpts, io: ProtocolIo, request: any) { const completion = request.completion(); if (requestType.type == 'Http') { try { - let headers = opts.headers; + let headers = typeof opts.headers === "function" ? opts.headers() : opts.headers; if (requestType.headers != null && requestType.headers.length > 0) { headers = { ...opts.headers }; for (let header of requestType.headers) { @@ -128,11 +125,8 @@ export async function run(opts: RunOpts, io: ProtocolIo, engine: any, generator: tasks = tasks.filter(t => !t.finished); } - return generator.take(); } - - export class SyncEngineGuards { waitLock: AsyncLock; pushLock: AsyncLock; diff --git a/bindings/javascript/sync/packages/common/types.ts b/bindings/javascript/sync/packages/common/types.ts index 8391825cf..3a8ddb02a 100644 --- a/bindings/javascript/sync/packages/common/types.ts +++ b/bindings/javascript/sync/packages/common/types.ts @@ -1,25 +1,136 @@ export declare const enum DatabaseChangeType { - Insert = 0, - Update = 1, - Delete = 2 + Insert = 'insert', + Update = 'update', + Delete = 'delete' } +export interface DatabaseRowStatement { + /** + * SQL statements with positional placeholders (?) + */ + sql: string + /** + * values to substitute placeholders + */ + values: Array +} + + +/** + * transformation result: + * - skip: ignore the mutation completely and do not apply it + * - rewrite: replace mutation with the provided statement + * - null: do not change mutation and keep it as is + */ +export type DatabaseRowTransformResult = { operation: 'skip' } | { operation: 'rewrite', stmt: DatabaseRowStatement } | null; + export interface DatabaseRowMutation { + /** + * unix seconds timestamp of the change + */ changeTime: number + /** + * table name of the change + */ tableName: string + /** + * rowid of the change + */ id: number + /** + * type of the change (insert/delete/update) + */ changeType: DatabaseChangeType + /** + * columns of the row before the change + */ before?: Record + /** + * columns of the row after the change + */ after?: Record + /** + * only updated columns of the row after the change + */ updates?: Record } -export type DatabaseRowTransformResult = { operation: 'skip' } | { operation: 'rewrite', stmt: DatabaseRowStatement } | null; export type Transform = (arg: DatabaseRowMutation) => DatabaseRowTransformResult; +export interface DatabaseOpts { + /** + * local path where to store all synced database files (e.g. local.db) + * note, that synced database will write several files with that prefix + * (e.g. local.db-info, local.db-wal, etc) + * */ + path: string; + /** + * optional url of the remote database (e.g. libsql://db-org.turso.io) + * (if omitted - local-only database will be created) + */ + url?: string; + /** + * auth token for the remote database + * (can be either static string or function which will provide short-lived credentials for every new request) + */ + authToken?: string | (() => string); + /** + * arbitrary client name which can be used to distinguish clients internally + * the library will gurantee uniquiness of the clientId by appending unique suffix to the clientName + */ + clientName?: string; + /** + * optional key if cloud database were encrypted by default + */ + encryptionKey?: string; + /** + * optional callback which will be called for every mutation before sending it to the remote + * this callback can transform the update in order to support complex conflict resolution strategy + */ + transform?: Transform, + /** + * optional long-polling timeout for pull operation + * if not set - no timeout is applied + */ + longPollTimeoutMs?: number, + /** + * optional parameter to enable internal logging for the database + */ + tracing?: 'error' | 'warn' | 'info' | 'debug' | 'trace', +} +export interface DatabaseStats { + /** + * amount of local changes not sent to the remote + */ + operations: number; + /** + * size of the main WAL file in bytes + */ + mainWal: number; + /** + * size of the revert WAL file in bytes + */ + revertWal: number; + /** + * unix timestamp of last successful pull time + */ + lastPullUnixTime: number; + /** + * unix timestamp of last successful push time + */ + lastPushUnixTime: number | null; + /** + * opaque revision of the changes pulled locally from remote + * (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; +} + +/* internal types used in the native/browser packages */ + export interface RunOpts { preemptionMs: number, url: string, - headers: { [K: string]: string } + headers: { [K: string]: string } | (() => { [K: string]: string }) transform?: Transform, } @@ -27,31 +138,4 @@ export interface ProtocolIo { read(path: string): Promise; write(path: string, content: Buffer | Uint8Array): Promise; } - -export interface SyncOpts { - path: string; - clientName?: string; - url: string; - authToken?: string; - encryptionKey?: string; - tablesIgnore?: string[], - transform?: Transform, - longPollTimeoutMs?: number, - tracing?: string, -} - -export interface DatabaseRowStatement { - sql: string - values: Array -} - -export interface SyncEngineStats { - operations: number; - mainWal: number; - revertWal: number; - lastPullUnixTime: number; - lastPushUnixTime: number | null; - revision: string | null; -} - -export type GeneratorResponse = { type: 'IO' } | { type: 'Done' } | ({ type: 'SyncEngineStats' } & SyncEngineStats) | { type: 'SyncEngineChanges', changes: any } \ No newline at end of file +export type GeneratorResponse = { type: 'IO' } | { type: 'Done' } | ({ type: 'SyncEngineStats' } & DatabaseStats) | { type: 'SyncEngineChanges', changes: any } \ No newline at end of file diff --git a/bindings/javascript/sync/packages/native/index.d.ts b/bindings/javascript/sync/packages/native/index.d.ts index af73101f3..bc3560c19 100644 --- a/bindings/javascript/sync/packages/native/index.d.ts +++ b/bindings/javascript/sync/packages/native/index.d.ts @@ -9,6 +9,18 @@ export declare class Database { * * `path` - The path to the database file. */ constructor(path: string, opts?: DatabaseOpts | undefined | null) + /** + * Connect the database synchronously + * This method is idempotent and can be called multiple times safely until the database will be closed + */ + connectSync(): void + /** + * Connect the database asynchronously + * This method is idempotent and can be called multiple times safely until the database will be closed + */ + connectAsync(): Promise + /** Returns whether the database is in readonly-only mode. */ + get readonly(): boolean /** Returns whether the database is in memory-only mode. */ get memory(): boolean /** Returns whether the database is in memory-only mode. */ @@ -101,11 +113,6 @@ export declare class Statement { * 1 = Row available, 2 = Done, 3 = I/O needed */ stepSync(): number - /** - * Step the statement and return result code (executed on the background thread): - * 1 = Row available, 2 = Done, 3 = I/O needed - */ - stepAsync(): Promise /** Get the current row data according to the presentation mode */ row(): unknown /** Sets the presentation mode to raw. */ @@ -126,7 +133,14 @@ export declare class Statement { finalize(): void } +/** + * Most of the options are aligned with better-sqlite API + * (see https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#new-databasepath-options) + */ export interface DatabaseOpts { + readonly?: boolean + timeout?: number + fileMustExist?: boolean tracing?: string } export declare class GeneratorHolder { @@ -153,7 +167,7 @@ export declare class JsProtocolRequestBytes { export declare class SyncEngine { constructor(opts: SyncEngineOpts) - init(): GeneratorHolder + connect(): GeneratorHolder ioLoopSync(): void /** Runs the I/O loop asynchronously, returning a Promise. */ ioLoopAsync(): Promise @@ -163,22 +177,18 @@ export declare class SyncEngine { wait(): GeneratorHolder apply(changes: SyncEngineChanges): GeneratorHolder checkpoint(): GeneratorHolder - open(): Database + db(): Database close(): void } export declare class SyncEngineChanges { - + empty(): boolean } export declare const enum DatabaseChangeTypeJs { - Insert = 0, - Update = 1, - Delete = 2 -} - -export interface DatabaseOpts { - path: string + Insert = 'insert', + Update = 'update', + Delete = 'delete' } export interface DatabaseRowMutationJs { @@ -225,6 +235,6 @@ export interface SyncEngineOpts { } export declare const enum SyncEngineProtocolVersion { - Legacy = 0, - V1 = 1 + Legacy = 'legacy', + V1 = 'v1' } diff --git a/bindings/javascript/sync/packages/native/index.js b/bindings/javascript/sync/packages/native/index.js index cd543a959..9887adb36 100644 --- a/bindings/javascript/sync/packages/native/index.js +++ b/bindings/javascript/sync/packages/native/index.js @@ -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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 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.2.0-pre.3' && 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.2.0-pre.3 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) + if (bindingPackageVersion !== '0.2.0-pre.7' && 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.2.0-pre.7 but got ${bindingPackageVersion}. You can reinstall dependencies to fix this issue.`) } return binding } catch (e) { diff --git a/bindings/javascript/sync/packages/native/promise.test.ts b/bindings/javascript/sync/packages/native/promise.test.ts index fea253fef..503c4934d 100644 --- a/bindings/javascript/sync/packages/native/promise.test.ts +++ b/bindings/javascript/sync/packages/native/promise.test.ts @@ -1,6 +1,6 @@ import { unlinkSync } from "node:fs"; import { expect, test } from 'vitest' -import { connect, DatabaseRowMutation, DatabaseRowTransformResult } from './promise.js' +import { connect, Database, DatabaseRowMutation, DatabaseRowTransformResult } from './promise.js' const localeCompare = (a, b) => a.x.localeCompare(b.x); @@ -12,6 +12,13 @@ function cleanup(path) { try { unlinkSync(`${path}-wal-revert`) } catch (e) { } } +test('explicit connect', async () => { + const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); + expect(() => db.prepare("SELECT 1")).toThrowError(/database must be connected/g); + await db.connect(); + expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]); +}) + test('select-after-push', async () => { { const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL }); diff --git a/bindings/javascript/sync/packages/native/promise.ts b/bindings/javascript/sync/packages/native/promise.ts index f00656b03..f6d0af579 100644 --- a/bindings/javascript/sync/packages/native/promise.ts +++ b/bindings/javascript/sync/packages/native/promise.ts @@ -1,6 +1,6 @@ -import { DatabasePromise, DatabaseOpts, NativeDatabase } from "@tursodatabase/database-common" -import { ProtocolIo, run, SyncOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, SyncEngineStats, SyncEngineGuards } from "@tursodatabase/sync-common"; -import { Database as NativeDB, SyncEngine } from "#index"; +import { DatabasePromise } from "@tursodatabase/database-common" +import { ProtocolIo, run, DatabaseOpts as SyncDatabaseOpts, RunOpts, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult, DatabaseStats, SyncEngineGuards } from "@tursodatabase/sync-common"; +import { SyncEngine, SyncEngineProtocolVersion } from "#index"; import { promises } from "node:fs"; let NodeIO: ProtocolIo = { @@ -40,70 +40,95 @@ function memoryIO(): ProtocolIo { } }; class Database extends DatabasePromise { - runOpts: RunOpts; - engine: any; - io: ProtocolIo; - guards: SyncEngineGuards - constructor(db: NativeDatabase, io: ProtocolIo, runOpts: RunOpts, engine: any, opts: DatabaseOpts = {}) { - super(db, opts) - this.runOpts = runOpts; - this.engine = engine; - this.io = io; - this.guards = new SyncEngineGuards(); + #runOpts: RunOpts; + #engine: any; + #io: ProtocolIo; + #guards: SyncEngineGuards + constructor(opts: SyncDatabaseOpts) { + const engine = new SyncEngine({ + path: opts.path, + clientName: opts.clientName, + useTransform: opts.transform != null, + protocolVersion: SyncEngineProtocolVersion.V1, + longPollTimeoutMs: opts.longPollTimeoutMs, + tracing: opts.tracing, + }); + super(engine.db() as unknown as any); + + + let headers = typeof opts.authToken === "function" ? () => ({ + ...(opts.authToken != null && { "Authorization": `Bearer ${(opts.authToken as any)()}` }), + ...(opts.encryptionKey != null && { "x-turso-encryption-key": opts.encryptionKey }) + }) : { + ...(opts.authToken != null && { "Authorization": `Bearer ${opts.authToken}` }), + ...(opts.encryptionKey != null && { "x-turso-encryption-key": opts.encryptionKey }) + }; + this.#runOpts = { + url: opts.url, + headers: headers, + preemptionMs: 1, + transform: opts.transform, + }; + this.#engine = engine; + this.#io = this.memory ? memoryIO() : NodeIO; + this.#guards = new SyncEngineGuards(); } - async sync() { - await this.push(); - await this.pull(); + /** + * connect database and initialize it in case of clean start + */ + override async connect() { + await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect()); } + /** + * pull new changes from the remote database + * if {@link SyncDatabaseOpts.longPollTimeoutMs} is set - then server will hold the connection open until either new changes will appear in the database or timeout occurs. + * @returns true if new changes were pulled from the remote + */ async pull() { - const changes = await this.guards.wait(async () => await run(this.runOpts, this.io, this.engine, this.engine.wait())); - await this.guards.apply(async () => await run(this.runOpts, this.io, this.engine, this.engine.apply(changes))); + const changes = await this.#guards.wait(async () => await run(this.#runOpts, this.#io, this.#engine, 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))); + return true; } + /** + * push new local changes to the remote database + * if {@link SyncDatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote + */ async push() { - await this.guards.push(async () => await run(this.runOpts, this.io, this.engine, this.engine.push())); + await this.#guards.push(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.push())); } + /** + * checkpoint WAL for local database + */ async checkpoint() { - await this.guards.checkpoint(async () => await run(this.runOpts, this.io, this.engine, this.engine.checkpoint())); + await this.#guards.checkpoint(async () => await run(this.#runOpts, this.#io, this.#engine, this.#engine.checkpoint())); } - async stats(): Promise { - return (await run(this.runOpts, this.io, this.engine, this.engine.stats())); + /** + * @returns statistic of current local database + */ + async stats(): Promise { + return (await run(this.#runOpts, this.#io, this.#engine, this.#engine.stats())); } + /** + * close the database + */ override async close(): Promise { - this.engine.close(); + this.#engine.close(); } } /** * Creates a new database connection asynchronously. * - * @param {string} path - Path to the database file. * @param {Object} opts - Options for database behavior. * @returns {Promise} - A promise that resolves to a Database instance. */ -async function connect(opts: SyncOpts): Promise { - const engine = new SyncEngine({ - path: opts.path, - clientName: opts.clientName, - tablesIgnore: opts.tablesIgnore, - useTransform: opts.transform != null, - tracing: opts.tracing, - protocolVersion: 1, - longPollTimeoutMs: opts.longPollTimeoutMs, - }); - const runOpts: RunOpts = { - url: opts.url, - headers: { - ...(opts.authToken != null && { "Authorization": `Bearer ${opts.authToken}` }), - ...(opts.encryptionKey != null && { "x-turso-encryption-key": opts.encryptionKey }) - }, - preemptionMs: 1, - transform: opts.transform, - }; - let io = opts.path == ':memory:' ? memoryIO() : NodeIO; - await run(runOpts, io, engine, engine.init()); - - const nativeDb = engine.open(); - return new Database(nativeDb as any, io, runOpts, engine, {}); +async function connect(opts: SyncDatabaseOpts): Promise { + const db = new Database(opts); + await db.connect(); + return db; } export { connect, Database, DatabaseRowMutation, DatabaseRowStatement, DatabaseRowTransformResult }