mirror of
https://github.com/aljazceru/turso.git
synced 2025-12-22 10:44:19 +01:00
fix wasm client
This commit is contained in:
3
bindings/javascript/sync/packages/wasm/cp-entrypoint.sh
Normal file
3
bindings/javascript/sync/packages/wasm/cp-entrypoint.sh
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
sed 's/index-default.js/index-bundle.js/g' promise-default.ts > promise-bundle.ts
|
||||||
|
sed 's/index-default.js/index-turbopack-hack.js/g' promise-default.ts > promise-turbopack-hack.ts
|
||||||
|
sed 's/index-default.js/index-vite-dev-hack.js/g' promise-default.ts > promise-vite-dev-hack.ts
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
||||||
import { DatabasePromise } from "@tursodatabase/database-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 } from "@tursodatabase/sync-common";
|
||||||
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker } from "./index-bundle.js";
|
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from "./index-bundle.js";
|
||||||
|
|
||||||
let BrowserIO: ProtocolIo = {
|
let BrowserIO: ProtocolIo = {
|
||||||
async read(path: string): Promise<Buffer | Uint8Array | null> {
|
async read(path: string): Promise<Buffer | Uint8Array | null> {
|
||||||
@@ -45,6 +45,12 @@ class Database extends DatabasePromise {
|
|||||||
#guards: SyncEngineGuards;
|
#guards: SyncEngineGuards;
|
||||||
#worker: Worker | null;
|
#worker: Worker | null;
|
||||||
constructor(opts: DatabaseOpts) {
|
constructor(opts: DatabaseOpts) {
|
||||||
|
if (opts.url == null) {
|
||||||
|
super(new NativeDatabase(opts.path, { tracing: opts.tracing }) as any);
|
||||||
|
this.#engine = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const engine = new SyncEngine({
|
const engine = new SyncEngine({
|
||||||
path: opts.path,
|
path: opts.path,
|
||||||
clientName: opts.clientName,
|
clientName: opts.clientName,
|
||||||
@@ -52,23 +58,31 @@ class Database extends DatabasePromise {
|
|||||||
protocolVersion: SyncEngineProtocolVersion.V1,
|
protocolVersion: SyncEngineProtocolVersion.V1,
|
||||||
longPollTimeoutMs: opts.longPollTimeoutMs,
|
longPollTimeoutMs: opts.longPollTimeoutMs,
|
||||||
tracing: opts.tracing,
|
tracing: opts.tracing,
|
||||||
|
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||||
|
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||||
});
|
});
|
||||||
super(engine.db() as unknown as any);
|
super(engine.db() as unknown as any);
|
||||||
|
|
||||||
|
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||||
let headers = typeof opts.authToken === "function" ? () => ({
|
if (typeof opts.authToken == "function") {
|
||||||
...(opts.authToken != null && { "Authorization": `Bearer ${(opts.authToken as any)()}` }),
|
const authToken = opts.authToken;
|
||||||
...(opts.encryption != null && {
|
headers = async () => ({
|
||||||
"x-turso-encryption-key": opts.encryption.key,
|
...(opts.authToken != null && { "Authorization": `Bearer ${await authToken()}` }),
|
||||||
"x-turso-encryption-cipher": opts.encryption.cipher,
|
...(opts.remoteEncryption != null && {
|
||||||
|
"x-turso-encryption-key": opts.remoteEncryption.key,
|
||||||
|
"x-turso-encryption-cipher": opts.remoteEncryption.cipher,
|
||||||
})
|
})
|
||||||
}) : {
|
});
|
||||||
...(opts.authToken != null && { "Authorization": `Bearer ${opts.authToken}` }),
|
} else {
|
||||||
...(opts.encryption != null && {
|
const authToken = opts.authToken;
|
||||||
"x-turso-encryption-key": opts.encryption.key,
|
headers = {
|
||||||
"x-turso-encryption-cipher": opts.encryption.cipher,
|
...(opts.authToken != null && { "Authorization": `Bearer ${authToken}` }),
|
||||||
|
...(opts.remoteEncryption != null && {
|
||||||
|
"x-turso-encryption-key": opts.remoteEncryption.key,
|
||||||
|
"x-turso-encryption-cipher": opts.remoteEncryption.cipher,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
}
|
||||||
this.#runOpts = {
|
this.#runOpts = {
|
||||||
url: opts.url,
|
url: opts.url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
@@ -83,7 +97,11 @@ class Database extends DatabasePromise {
|
|||||||
* connect database and initialize it in case of clean start
|
* connect database and initialize it in case of clean start
|
||||||
*/
|
*/
|
||||||
override async connect() {
|
override async connect() {
|
||||||
if (this.connected) { return; }
|
if (this.connected) {
|
||||||
|
return;
|
||||||
|
} else if (this.#engine == null) {
|
||||||
|
await super.connect();
|
||||||
|
} else {
|
||||||
if (!this.memory) {
|
if (!this.memory) {
|
||||||
this.#worker = await init();
|
this.#worker = await init();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -95,6 +113,7 @@ class Database extends DatabasePromise {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||||
|
}
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -103,6 +122,9 @@ class Database extends DatabasePromise {
|
|||||||
* @returns true if new changes were pulled from the remote
|
* @returns true if new changes were pulled from the remote
|
||||||
*/
|
*/
|
||||||
async pull() {
|
async pull() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||||
if (changes.empty()) {
|
if (changes.empty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -115,24 +137,34 @@ class Database extends DatabasePromise {
|
|||||||
* if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
|
* if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
|
||||||
*/
|
*/
|
||||||
async push() {
|
async push() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* checkpoint WAL for local database
|
* checkpoint WAL for local database
|
||||||
*/
|
*/
|
||||||
async checkpoint() {
|
async checkpoint() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @returns statistic of current local database
|
* @returns statistic of current local database
|
||||||
*/
|
*/
|
||||||
async stats(): Promise<DatabaseStats> {
|
async stats(): Promise<DatabaseStats> {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* close the database and relevant files
|
* close the database and relevant files
|
||||||
*/
|
*/
|
||||||
async close() {
|
async close() {
|
||||||
|
if (this.#engine != null) {
|
||||||
if (this.name != null && this.#worker != null) {
|
if (this.name != null && this.#worker != null) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
unregisterFileAtWorker(this.#worker, this.name),
|
unregisterFileAtWorker(this.#worker, this.name),
|
||||||
@@ -142,9 +174,10 @@ class Database extends DatabasePromise {
|
|||||||
unregisterFileAtWorker(this.#worker, `${this.name}-changes`),
|
unregisterFileAtWorker(this.#worker, `${this.name}-changes`),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await super.close();
|
|
||||||
this.#engine.close();
|
this.#engine.close();
|
||||||
}
|
}
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
||||||
import { DatabasePromise } from "@tursodatabase/database-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 } from "@tursodatabase/sync-common";
|
||||||
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker } from "./index-default.js";
|
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from "./index-default.js";
|
||||||
|
|
||||||
let BrowserIO: ProtocolIo = {
|
let BrowserIO: ProtocolIo = {
|
||||||
async read(path: string): Promise<Buffer | Uint8Array | null> {
|
async read(path: string): Promise<Buffer | Uint8Array | null> {
|
||||||
@@ -45,6 +45,12 @@ class Database extends DatabasePromise {
|
|||||||
#guards: SyncEngineGuards;
|
#guards: SyncEngineGuards;
|
||||||
#worker: Worker | null;
|
#worker: Worker | null;
|
||||||
constructor(opts: DatabaseOpts) {
|
constructor(opts: DatabaseOpts) {
|
||||||
|
if (opts.url == null) {
|
||||||
|
super(new NativeDatabase(opts.path, { tracing: opts.tracing }) as any);
|
||||||
|
this.#engine = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const engine = new SyncEngine({
|
const engine = new SyncEngine({
|
||||||
path: opts.path,
|
path: opts.path,
|
||||||
clientName: opts.clientName,
|
clientName: opts.clientName,
|
||||||
@@ -52,23 +58,31 @@ class Database extends DatabasePromise {
|
|||||||
protocolVersion: SyncEngineProtocolVersion.V1,
|
protocolVersion: SyncEngineProtocolVersion.V1,
|
||||||
longPollTimeoutMs: opts.longPollTimeoutMs,
|
longPollTimeoutMs: opts.longPollTimeoutMs,
|
||||||
tracing: opts.tracing,
|
tracing: opts.tracing,
|
||||||
|
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||||
|
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||||
});
|
});
|
||||||
super(engine.db() as unknown as any);
|
super(engine.db() as unknown as any);
|
||||||
|
|
||||||
|
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||||
let headers = typeof opts.authToken === "function" ? () => ({
|
if (typeof opts.authToken == "function") {
|
||||||
...(opts.authToken != null && { "Authorization": `Bearer ${(opts.authToken as any)()}` }),
|
const authToken = opts.authToken;
|
||||||
...(opts.encryption != null && {
|
headers = async () => ({
|
||||||
"x-turso-encryption-key": opts.encryption.key,
|
...(opts.authToken != null && { "Authorization": `Bearer ${await authToken()}` }),
|
||||||
"x-turso-encryption-cipher": opts.encryption.cipher,
|
...(opts.remoteEncryption != null && {
|
||||||
|
"x-turso-encryption-key": opts.remoteEncryption.key,
|
||||||
|
"x-turso-encryption-cipher": opts.remoteEncryption.cipher,
|
||||||
})
|
})
|
||||||
}) : {
|
});
|
||||||
...(opts.authToken != null && { "Authorization": `Bearer ${opts.authToken}` }),
|
} else {
|
||||||
...(opts.encryption != null && {
|
const authToken = opts.authToken;
|
||||||
"x-turso-encryption-key": opts.encryption.key,
|
headers = {
|
||||||
"x-turso-encryption-cipher": opts.encryption.cipher,
|
...(opts.authToken != null && { "Authorization": `Bearer ${authToken}` }),
|
||||||
|
...(opts.remoteEncryption != null && {
|
||||||
|
"x-turso-encryption-key": opts.remoteEncryption.key,
|
||||||
|
"x-turso-encryption-cipher": opts.remoteEncryption.cipher,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
}
|
||||||
this.#runOpts = {
|
this.#runOpts = {
|
||||||
url: opts.url,
|
url: opts.url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
@@ -83,7 +97,11 @@ class Database extends DatabasePromise {
|
|||||||
* connect database and initialize it in case of clean start
|
* connect database and initialize it in case of clean start
|
||||||
*/
|
*/
|
||||||
override async connect() {
|
override async connect() {
|
||||||
if (this.connected) { return; }
|
if (this.connected) {
|
||||||
|
return;
|
||||||
|
} else if (this.#engine == null) {
|
||||||
|
await super.connect();
|
||||||
|
} else {
|
||||||
if (!this.memory) {
|
if (!this.memory) {
|
||||||
this.#worker = await init();
|
this.#worker = await init();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -95,6 +113,7 @@ class Database extends DatabasePromise {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||||
|
}
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -103,6 +122,9 @@ class Database extends DatabasePromise {
|
|||||||
* @returns true if new changes were pulled from the remote
|
* @returns true if new changes were pulled from the remote
|
||||||
*/
|
*/
|
||||||
async pull() {
|
async pull() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||||
if (changes.empty()) {
|
if (changes.empty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -115,24 +137,34 @@ class Database extends DatabasePromise {
|
|||||||
* if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
|
* if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
|
||||||
*/
|
*/
|
||||||
async push() {
|
async push() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* checkpoint WAL for local database
|
* checkpoint WAL for local database
|
||||||
*/
|
*/
|
||||||
async checkpoint() {
|
async checkpoint() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @returns statistic of current local database
|
* @returns statistic of current local database
|
||||||
*/
|
*/
|
||||||
async stats(): Promise<DatabaseStats> {
|
async stats(): Promise<DatabaseStats> {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* close the database and relevant files
|
* close the database and relevant files
|
||||||
*/
|
*/
|
||||||
async close() {
|
async close() {
|
||||||
|
if (this.#engine != null) {
|
||||||
if (this.name != null && this.#worker != null) {
|
if (this.name != null && this.#worker != null) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
unregisterFileAtWorker(this.#worker, this.name),
|
unregisterFileAtWorker(this.#worker, this.name),
|
||||||
@@ -142,9 +174,10 @@ class Database extends DatabasePromise {
|
|||||||
unregisterFileAtWorker(this.#worker, `${this.name}-changes`),
|
unregisterFileAtWorker(this.#worker, `${this.name}-changes`),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await super.close();
|
|
||||||
this.#engine.close();
|
this.#engine.close();
|
||||||
}
|
}
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
||||||
import { DatabasePromise } from "@tursodatabase/database-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 } from "@tursodatabase/sync-common";
|
||||||
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker } from "./index-turbopack-hack.js";
|
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from "./index-turbopack-hack.js";
|
||||||
|
|
||||||
let BrowserIO: ProtocolIo = {
|
let BrowserIO: ProtocolIo = {
|
||||||
async read(path: string): Promise<Buffer | Uint8Array | null> {
|
async read(path: string): Promise<Buffer | Uint8Array | null> {
|
||||||
@@ -45,6 +45,12 @@ class Database extends DatabasePromise {
|
|||||||
#guards: SyncEngineGuards;
|
#guards: SyncEngineGuards;
|
||||||
#worker: Worker | null;
|
#worker: Worker | null;
|
||||||
constructor(opts: DatabaseOpts) {
|
constructor(opts: DatabaseOpts) {
|
||||||
|
if (opts.url == null) {
|
||||||
|
super(new NativeDatabase(opts.path, { tracing: opts.tracing }) as any);
|
||||||
|
this.#engine = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const engine = new SyncEngine({
|
const engine = new SyncEngine({
|
||||||
path: opts.path,
|
path: opts.path,
|
||||||
clientName: opts.clientName,
|
clientName: opts.clientName,
|
||||||
@@ -52,23 +58,31 @@ class Database extends DatabasePromise {
|
|||||||
protocolVersion: SyncEngineProtocolVersion.V1,
|
protocolVersion: SyncEngineProtocolVersion.V1,
|
||||||
longPollTimeoutMs: opts.longPollTimeoutMs,
|
longPollTimeoutMs: opts.longPollTimeoutMs,
|
||||||
tracing: opts.tracing,
|
tracing: opts.tracing,
|
||||||
|
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||||
|
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||||
});
|
});
|
||||||
super(engine.db() as unknown as any);
|
super(engine.db() as unknown as any);
|
||||||
|
|
||||||
|
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||||
let headers = typeof opts.authToken === "function" ? () => ({
|
if (typeof opts.authToken == "function") {
|
||||||
...(opts.authToken != null && { "Authorization": `Bearer ${(opts.authToken as any)()}` }),
|
const authToken = opts.authToken;
|
||||||
...(opts.encryption != null && {
|
headers = async () => ({
|
||||||
"x-turso-encryption-key": opts.encryption.key,
|
...(opts.authToken != null && { "Authorization": `Bearer ${await authToken()}` }),
|
||||||
"x-turso-encryption-cipher": opts.encryption.cipher,
|
...(opts.remoteEncryption != null && {
|
||||||
|
"x-turso-encryption-key": opts.remoteEncryption.key,
|
||||||
|
"x-turso-encryption-cipher": opts.remoteEncryption.cipher,
|
||||||
})
|
})
|
||||||
}) : {
|
});
|
||||||
...(opts.authToken != null && { "Authorization": `Bearer ${opts.authToken}` }),
|
} else {
|
||||||
...(opts.encryption != null && {
|
const authToken = opts.authToken;
|
||||||
"x-turso-encryption-key": opts.encryption.key,
|
headers = {
|
||||||
"x-turso-encryption-cipher": opts.encryption.cipher,
|
...(opts.authToken != null && { "Authorization": `Bearer ${authToken}` }),
|
||||||
|
...(opts.remoteEncryption != null && {
|
||||||
|
"x-turso-encryption-key": opts.remoteEncryption.key,
|
||||||
|
"x-turso-encryption-cipher": opts.remoteEncryption.cipher,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
}
|
||||||
this.#runOpts = {
|
this.#runOpts = {
|
||||||
url: opts.url,
|
url: opts.url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
@@ -83,7 +97,11 @@ class Database extends DatabasePromise {
|
|||||||
* connect database and initialize it in case of clean start
|
* connect database and initialize it in case of clean start
|
||||||
*/
|
*/
|
||||||
override async connect() {
|
override async connect() {
|
||||||
if (this.connected) { return; }
|
if (this.connected) {
|
||||||
|
return;
|
||||||
|
} else if (this.#engine == null) {
|
||||||
|
await super.connect();
|
||||||
|
} else {
|
||||||
if (!this.memory) {
|
if (!this.memory) {
|
||||||
this.#worker = await init();
|
this.#worker = await init();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -95,6 +113,7 @@ class Database extends DatabasePromise {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||||
|
}
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -103,6 +122,9 @@ class Database extends DatabasePromise {
|
|||||||
* @returns true if new changes were pulled from the remote
|
* @returns true if new changes were pulled from the remote
|
||||||
*/
|
*/
|
||||||
async pull() {
|
async pull() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||||
if (changes.empty()) {
|
if (changes.empty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -115,24 +137,34 @@ class Database extends DatabasePromise {
|
|||||||
* if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
|
* if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
|
||||||
*/
|
*/
|
||||||
async push() {
|
async push() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* checkpoint WAL for local database
|
* checkpoint WAL for local database
|
||||||
*/
|
*/
|
||||||
async checkpoint() {
|
async checkpoint() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @returns statistic of current local database
|
* @returns statistic of current local database
|
||||||
*/
|
*/
|
||||||
async stats(): Promise<DatabaseStats> {
|
async stats(): Promise<DatabaseStats> {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* close the database and relevant files
|
* close the database and relevant files
|
||||||
*/
|
*/
|
||||||
async close() {
|
async close() {
|
||||||
|
if (this.#engine != null) {
|
||||||
if (this.name != null && this.#worker != null) {
|
if (this.name != null && this.#worker != null) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
unregisterFileAtWorker(this.#worker, this.name),
|
unregisterFileAtWorker(this.#worker, this.name),
|
||||||
@@ -142,9 +174,10 @@ class Database extends DatabasePromise {
|
|||||||
unregisterFileAtWorker(this.#worker, `${this.name}-changes`),
|
unregisterFileAtWorker(this.#worker, `${this.name}-changes`),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await super.close();
|
|
||||||
this.#engine.close();
|
this.#engine.close();
|
||||||
}
|
}
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
import { registerFileAtWorker, unregisterFileAtWorker } from "@tursodatabase/database-wasm-common"
|
||||||
import { DatabasePromise } from "@tursodatabase/database-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 } from "@tursodatabase/sync-common";
|
||||||
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker } from "./index-vite-dev-hack.js";
|
import { SyncEngine, SyncEngineProtocolVersion, initThreadPool, MainWorker, Database as NativeDatabase } from "./index-vite-dev-hack.js";
|
||||||
|
|
||||||
let BrowserIO: ProtocolIo = {
|
let BrowserIO: ProtocolIo = {
|
||||||
async read(path: string): Promise<Buffer | Uint8Array | null> {
|
async read(path: string): Promise<Buffer | Uint8Array | null> {
|
||||||
@@ -45,6 +45,12 @@ class Database extends DatabasePromise {
|
|||||||
#guards: SyncEngineGuards;
|
#guards: SyncEngineGuards;
|
||||||
#worker: Worker | null;
|
#worker: Worker | null;
|
||||||
constructor(opts: DatabaseOpts) {
|
constructor(opts: DatabaseOpts) {
|
||||||
|
if (opts.url == null) {
|
||||||
|
super(new NativeDatabase(opts.path, { tracing: opts.tracing }) as any);
|
||||||
|
this.#engine = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const engine = new SyncEngine({
|
const engine = new SyncEngine({
|
||||||
path: opts.path,
|
path: opts.path,
|
||||||
clientName: opts.clientName,
|
clientName: opts.clientName,
|
||||||
@@ -52,23 +58,31 @@ class Database extends DatabasePromise {
|
|||||||
protocolVersion: SyncEngineProtocolVersion.V1,
|
protocolVersion: SyncEngineProtocolVersion.V1,
|
||||||
longPollTimeoutMs: opts.longPollTimeoutMs,
|
longPollTimeoutMs: opts.longPollTimeoutMs,
|
||||||
tracing: opts.tracing,
|
tracing: opts.tracing,
|
||||||
|
bootstrapIfEmpty: typeof opts.url != "function" || opts.url() != null,
|
||||||
|
remoteEncryption: opts.remoteEncryption?.cipher,
|
||||||
});
|
});
|
||||||
super(engine.db() as unknown as any);
|
super(engine.db() as unknown as any);
|
||||||
|
|
||||||
|
let headers: { [K: string]: string } | (() => Promise<{ [K: string]: string }>);
|
||||||
let headers = typeof opts.authToken === "function" ? () => ({
|
if (typeof opts.authToken == "function") {
|
||||||
...(opts.authToken != null && { "Authorization": `Bearer ${(opts.authToken as any)()}` }),
|
const authToken = opts.authToken;
|
||||||
...(opts.encryption != null && {
|
headers = async () => ({
|
||||||
"x-turso-encryption-key": opts.encryption.key,
|
...(opts.authToken != null && { "Authorization": `Bearer ${await authToken()}` }),
|
||||||
"x-turso-encryption-cipher": opts.encryption.cipher,
|
...(opts.remoteEncryption != null && {
|
||||||
|
"x-turso-encryption-key": opts.remoteEncryption.key,
|
||||||
|
"x-turso-encryption-cipher": opts.remoteEncryption.cipher,
|
||||||
})
|
})
|
||||||
}) : {
|
});
|
||||||
...(opts.authToken != null && { "Authorization": `Bearer ${opts.authToken}` }),
|
} else {
|
||||||
...(opts.encryption != null && {
|
const authToken = opts.authToken;
|
||||||
"x-turso-encryption-key": opts.encryption.key,
|
headers = {
|
||||||
"x-turso-encryption-cipher": opts.encryption.cipher,
|
...(opts.authToken != null && { "Authorization": `Bearer ${authToken}` }),
|
||||||
|
...(opts.remoteEncryption != null && {
|
||||||
|
"x-turso-encryption-key": opts.remoteEncryption.key,
|
||||||
|
"x-turso-encryption-cipher": opts.remoteEncryption.cipher,
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
|
}
|
||||||
this.#runOpts = {
|
this.#runOpts = {
|
||||||
url: opts.url,
|
url: opts.url,
|
||||||
headers: headers,
|
headers: headers,
|
||||||
@@ -83,7 +97,11 @@ class Database extends DatabasePromise {
|
|||||||
* connect database and initialize it in case of clean start
|
* connect database and initialize it in case of clean start
|
||||||
*/
|
*/
|
||||||
override async connect() {
|
override async connect() {
|
||||||
if (this.connected) { return; }
|
if (this.connected) {
|
||||||
|
return;
|
||||||
|
} else if (this.#engine == null) {
|
||||||
|
await super.connect();
|
||||||
|
} else {
|
||||||
if (!this.memory) {
|
if (!this.memory) {
|
||||||
this.#worker = await init();
|
this.#worker = await init();
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
@@ -95,6 +113,7 @@ class Database extends DatabasePromise {
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
await run(this.#runOpts, this.#io, this.#engine, this.#engine.connect());
|
||||||
|
}
|
||||||
this.connected = true;
|
this.connected = true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -103,6 +122,9 @@ class Database extends DatabasePromise {
|
|||||||
* @returns true if new changes were pulled from the remote
|
* @returns true if new changes were pulled from the remote
|
||||||
*/
|
*/
|
||||||
async pull() {
|
async pull() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.wait()));
|
||||||
if (changes.empty()) {
|
if (changes.empty()) {
|
||||||
return false;
|
return false;
|
||||||
@@ -115,24 +137,34 @@ class Database extends DatabasePromise {
|
|||||||
* if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
|
* if {@link DatabaseOpts.transform} is set - then provided callback will be called for every mutation before sending it to the remote
|
||||||
*/
|
*/
|
||||||
async push() {
|
async push() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.push()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* checkpoint WAL for local database
|
* checkpoint WAL for local database
|
||||||
*/
|
*/
|
||||||
async checkpoint() {
|
async checkpoint() {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.checkpoint()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @returns statistic of current local database
|
* @returns statistic of current local database
|
||||||
*/
|
*/
|
||||||
async stats(): Promise<DatabaseStats> {
|
async stats(): Promise<DatabaseStats> {
|
||||||
|
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.#runOpts, this.#io, this.#engine, this.#engine.stats()));
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* close the database and relevant files
|
* close the database and relevant files
|
||||||
*/
|
*/
|
||||||
async close() {
|
async close() {
|
||||||
|
if (this.#engine != null) {
|
||||||
if (this.name != null && this.#worker != null) {
|
if (this.name != null && this.#worker != null) {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
unregisterFileAtWorker(this.#worker, this.name),
|
unregisterFileAtWorker(this.#worker, this.name),
|
||||||
@@ -142,9 +174,10 @@ class Database extends DatabasePromise {
|
|||||||
unregisterFileAtWorker(this.#worker, `${this.name}-changes`),
|
unregisterFileAtWorker(this.#worker, `${this.name}-changes`),
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
await super.close();
|
|
||||||
this.#engine.close();
|
this.#engine.close();
|
||||||
}
|
}
|
||||||
|
await super.close();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { expect, test } from 'vitest'
|
|||||||
import { Database, connect, DatabaseRowMutation, DatabaseRowTransformResult } from './promise-default.js'
|
import { Database, connect, DatabaseRowMutation, DatabaseRowTransformResult } from './promise-default.js'
|
||||||
|
|
||||||
const localeCompare = (a, b) => a.x.localeCompare(b.x);
|
const localeCompare = (a, b) => a.x.localeCompare(b.x);
|
||||||
|
const intCompare = (a, b) => a.x - b.x;
|
||||||
|
|
||||||
test('implicit connect', async () => {
|
test('implicit connect', async () => {
|
||||||
const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
||||||
@@ -11,6 +12,91 @@ test('implicit connect', async () => {
|
|||||||
expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]);
|
expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]);
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('simple-db', async () => {
|
||||||
|
const db = new Database({ path: ':memory:' });
|
||||||
|
expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }])
|
||||||
|
await db.exec("CREATE TABLE t(x)");
|
||||||
|
await db.exec("INSERT INTO t VALUES (1), (2), (3)");
|
||||||
|
expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }])
|
||||||
|
await expect(async () => await db.pull()).rejects.toThrowError(/sync is disabled as database was opened without sync support/);
|
||||||
|
})
|
||||||
|
|
||||||
|
test('implicit connect', async () => {
|
||||||
|
const db = new Database({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
||||||
|
const defer = db.prepare("SELECT * FROM not_found");
|
||||||
|
await expect(async () => await defer.all()).rejects.toThrowError(/no such table: not_found/);
|
||||||
|
expect(() => db.prepare("SELECT * FROM not_found")).toThrowError(/no such table: not_found/);
|
||||||
|
expect(await db.prepare("SELECT 1 as x").all()).toEqual([{ x: 1 }]);
|
||||||
|
})
|
||||||
|
|
||||||
|
test('defered sync', async () => {
|
||||||
|
{
|
||||||
|
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
||||||
|
await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
|
||||||
|
await db.exec("DELETE FROM t");
|
||||||
|
await db.exec("INSERT INTO t VALUES (100)");
|
||||||
|
await db.push();
|
||||||
|
await db.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
let url = null;
|
||||||
|
const db = new Database({ path: ':memory:', url: () => url });
|
||||||
|
await db.prepare("CREATE TABLE t(x)").run();
|
||||||
|
await db.prepare("INSERT INTO t VALUES (1), (2), (3), (42)").run();
|
||||||
|
expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]);
|
||||||
|
await expect(async () => await db.pull()).rejects.toThrow(/url is empty - sync is paused/);
|
||||||
|
url = process.env.VITE_TURSO_DB_URL;
|
||||||
|
await db.pull();
|
||||||
|
expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }, { x: 42 }]);
|
||||||
|
})
|
||||||
|
|
||||||
|
test('encryption sync', async () => {
|
||||||
|
const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I=';
|
||||||
|
const URL = 'http://encrypted--a--a.localhost:10000';
|
||||||
|
{
|
||||||
|
const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
|
||||||
|
await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
|
||||||
|
await db.exec("DELETE FROM t");
|
||||||
|
await db.push();
|
||||||
|
await db.close();
|
||||||
|
}
|
||||||
|
const db1 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
|
||||||
|
const db2 = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
|
||||||
|
await db1.exec("INSERT INTO t VALUES (1), (2), (3)");
|
||||||
|
await db2.exec("INSERT INTO t VALUES (4), (5), (6)");
|
||||||
|
expect(await db1.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
|
||||||
|
expect(await db2.prepare("SELECT * FROM t").all()).toEqual([{ x: 4 }, { x: 5 }, { x: 6 }]);
|
||||||
|
await Promise.all([db1.push(), db2.push()]);
|
||||||
|
await Promise.all([db1.pull(), db2.pull()]);
|
||||||
|
const expected = [{ x: 1 }, { x: 2 }, { x: 3 }, { x: 4 }, { x: 5 }, { x: 6 }];
|
||||||
|
expect((await db1.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare));
|
||||||
|
expect((await db2.prepare("SELECT * FROM t").all()).sort(intCompare)).toEqual(expected.sort(intCompare));
|
||||||
|
});
|
||||||
|
|
||||||
|
test('defered encryption sync', async () => {
|
||||||
|
const URL = 'http://encrypted--a--a.localhost:10000';
|
||||||
|
const KEY = 'l/FWopMfZisTLgBX4A42AergrCrYKjiO3BfkJUwv83I=';
|
||||||
|
let url = null;
|
||||||
|
{
|
||||||
|
const db = await connect({ path: ':memory:', url: URL, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
|
||||||
|
await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
|
||||||
|
await db.exec("DELETE FROM t");
|
||||||
|
await db.exec("INSERT INTO t VALUES (100)");
|
||||||
|
await db.push();
|
||||||
|
await db.close();
|
||||||
|
}
|
||||||
|
const db = await connect({ path: ':memory:', url: () => url, remoteEncryption: { key: KEY, cipher: 'aes256gcm' } });
|
||||||
|
await db.exec("CREATE TABLE IF NOT EXISTS t(x)");
|
||||||
|
await db.exec("INSERT INTO t VALUES (1), (2), (3)");
|
||||||
|
expect(await db.prepare("SELECT * FROM t").all()).toEqual([{ x: 1 }, { x: 2 }, { x: 3 }]);
|
||||||
|
|
||||||
|
url = URL;
|
||||||
|
await db.pull();
|
||||||
|
|
||||||
|
const expected = [{ x: 100 }, { x: 1 }, { x: 2 }, { x: 3 }];
|
||||||
|
expect((await db.prepare("SELECT * FROM t").all())).toEqual(expected);
|
||||||
|
});
|
||||||
|
|
||||||
test('select-after-push', async () => {
|
test('select-after-push', async () => {
|
||||||
{
|
{
|
||||||
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
const db = await connect({ path: ':memory:', url: process.env.VITE_TURSO_DB_URL });
|
||||||
|
|||||||
Reference in New Issue
Block a user