diff --git a/packages/frontend/core/src/modules/workspace-engine/impls/engine/doc-sqlite.ts b/packages/frontend/core/src/modules/workspace-engine/impls/engine/doc-sqlite.ts index cb3a6585ee..a16791f38f 100644 --- a/packages/frontend/core/src/modules/workspace-engine/impls/engine/doc-sqlite.ts +++ b/packages/frontend/core/src/modules/workspace-engine/impls/engine/doc-sqlite.ts @@ -37,10 +37,7 @@ class Doc implements DocType { if (!apis?.db) { throw new Error('sqlite datasource is not available'); } - const update = await apis.db.getDocAsUpdates( - this.workspaceId, - this.workspaceId === docId ? undefined : docId - ); + const update = await apis.db.getDocAsUpdates(this.workspaceId, docId); if (update) { if ( @@ -60,19 +57,18 @@ class Doc implements DocType { if (!apis?.db) { throw new Error('sqlite datasource is not available'); } - await apis.db.applyDocUpdate( - this.workspaceId, - data, - this.workspaceId === docId ? undefined : docId - ); + await apis.db.applyDocUpdate(this.workspaceId, data, docId); } clear(): void | Promise { return; } - del(): void | Promise { - return; + async del(docId: string) { + if (!apis?.db) { + throw new Error('sqlite datasource is not available'); + } + await apis.db.deleteDoc(this.workspaceId, docId); } } diff --git a/packages/frontend/electron/src/helper/db/base-db-adapter.ts b/packages/frontend/electron/src/helper/db/db-adapter.ts similarity index 74% rename from packages/frontend/electron/src/helper/db/base-db-adapter.ts rename to packages/frontend/electron/src/helper/db/db-adapter.ts index a9e0aa1573..7caeeeae70 100644 --- a/packages/frontend/electron/src/helper/db/base-db-adapter.ts +++ b/packages/frontend/electron/src/helper/db/db-adapter.ts @@ -2,16 +2,14 @@ import type { InsertRow } from '@affine/native'; import { SqliteConnection, ValidationResult } from '@affine/native'; import { WorkspaceVersion } from '@toeverything/infra/blocksuite'; -import { applyGuidCompatibilityFix, migrateToLatest } from '../db/migration'; import { logger } from '../logger'; +import { applyGuidCompatibilityFix, migrateToLatest } from './migration'; /** * A base class for SQLite DB adapter that provides basic methods around updates & blobs */ -export abstract class BaseSQLiteAdapter { +export class SQLiteAdapter { db: SqliteConnection | null = null; - abstract role: string; - constructor(public readonly path: string) {} async connectIfNeeded() { @@ -27,7 +25,7 @@ export abstract class BaseSQLiteAdapter { await migrateToLatest(this.path, WorkspaceVersion.Surface); } await applyGuidCompatibilityFix(this.db); - logger.info(`[SQLiteAdapter:${this.role}]`, 'connected:', this.path); + logger.info(`[SQLiteAdapter]`, 'connected:', this.path); } return this.db; } @@ -36,7 +34,7 @@ export abstract class BaseSQLiteAdapter { const { db } = this; this.db = null; // log after close will sometimes crash the app when quitting - logger.info(`[SQLiteAdapter:${this.role}]`, 'destroyed:', this.path); + logger.info(`[SQLiteAdapter]`, 'destroyed:', this.path); await db?.close(); } @@ -128,7 +126,7 @@ export abstract class BaseSQLiteAdapter { const start = performance.now(); await this.db.insertUpdates(updates); logger.debug( - `[SQLiteAdapter][${this.role}] addUpdateToSQLite`, + `[SQLiteAdapter] addUpdateToSQLite`, 'length:', updates.length, 'docids', @@ -140,4 +138,41 @@ export abstract class BaseSQLiteAdapter { logger.error('addUpdateToSQLite', this.path, error); } } + + async deleteUpdates(docId?: string) { + try { + if (!this.db) { + logger.warn(`${this.path} is not connected`); + return; + } + await this.db.deleteUpdates(docId); + } catch (error) { + logger.error('deleteUpdates', error); + } + } + + async getUpdatesCount(docId?: string) { + try { + if (!this.db) { + logger.warn(`${this.path} is not connected`); + return 0; + } + return await this.db.getUpdatesCount(docId); + } catch (error) { + logger.error('getUpdatesCount', error); + return 0; + } + } + + async replaceUpdates(docId: string | null | undefined, updates: InsertRow[]) { + try { + if (!this.db) { + logger.warn(`${this.path} is not connected`); + return; + } + await this.db.replaceUpdates(docId, updates); + } catch (error) { + logger.error('replaceUpdates', error); + } + } } diff --git a/packages/frontend/electron/src/helper/db/index.ts b/packages/frontend/electron/src/helper/db/index.ts index fbe7ee83ef..b71fb7302e 100644 --- a/packages/frontend/electron/src/helper/db/index.ts +++ b/packages/frontend/electron/src/helper/db/index.ts @@ -5,22 +5,21 @@ import { ensureSQLiteDB } from './ensure-db'; export * from './ensure-db'; export const dbHandlers = { - getDocAsUpdates: async (workspaceId: string, subdocId?: string) => { + getDocAsUpdates: async (workspaceId: string, subdocId: string) => { const workspaceDB = await ensureSQLiteDB(workspaceId); return workspaceDB.getDocAsUpdates(subdocId); }, applyDocUpdate: async ( workspaceId: string, update: Uint8Array, - subdocId?: string + subdocId: string ) => { const workspaceDB = await ensureSQLiteDB(workspaceId); - return workspaceDB.addUpdateToSQLite([ - { - data: update, - docId: subdocId, - }, - ]); + return workspaceDB.addUpdateToSQLite(update, subdocId); + }, + deleteDoc: async (workspaceId: string, subdocId: string) => { + const workspaceDB = await ensureSQLiteDB(workspaceId); + return workspaceDB.deleteUpdate(subdocId); }, addBlob: async (workspaceId: string, key: string, data: Uint8Array) => { const workspaceDB = await ensureSQLiteDB(workspaceId); diff --git a/packages/frontend/electron/src/helper/db/workspace-db-adapter.ts b/packages/frontend/electron/src/helper/db/workspace-db-adapter.ts index 24d0faf921..9406e2e7e8 100644 --- a/packages/frontend/electron/src/helper/db/workspace-db-adapter.ts +++ b/packages/frontend/electron/src/helper/db/workspace-db-adapter.ts @@ -1,36 +1,43 @@ -import type { InsertRow } from '@affine/native'; +import { AsyncLock } from '@toeverything/infra'; import { Subject } from 'rxjs'; import { applyUpdate, Doc as YDoc } from 'yjs'; import { logger } from '../logger'; import { getWorkspaceMeta } from '../workspace/meta'; -import { BaseSQLiteAdapter } from './base-db-adapter'; +import { SQLiteAdapter } from './db-adapter'; import { mergeUpdate } from './merge-update'; const TRIM_SIZE = 500; -export class WorkspaceSQLiteDB extends BaseSQLiteAdapter { - role = 'primary'; - +export class WorkspaceSQLiteDB { + lock = new AsyncLock(); update$ = new Subject(); + adapter = new SQLiteAdapter(this.path); constructor( - public override path: string, + public path: string, public workspaceId: string - ) { - super(path); + ) {} + + async transaction(cb: () => Promise): Promise { + using _lock = await this.lock.acquire(); + return await cb(); } - override async destroy() { - await super.destroy(); + async destroy() { + await this.adapter.destroy(); // when db is closed, we can safely remove it from ensure-db list this.update$.complete(); } + toDBDocId = (docId: string) => { + return this.workspaceId === docId ? undefined : docId; + }; + getWorkspaceName = async () => { const ydoc = new YDoc(); - const updates = await this.getUpdates(); + const updates = await this.adapter.getUpdates(); updates.forEach(update => { applyUpdate(ydoc, update.data); }); @@ -38,44 +45,75 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter { }; async init() { - const db = await super.connectIfNeeded(); + const db = await this.adapter.connectIfNeeded(); await this.tryTrim(); return db; } + async get(docId: string) { + return this.adapter.getUpdates(docId); + } + // getUpdates then encode - getDocAsUpdates = async (docId?: string) => { - const updates = await this.getUpdates(docId); - return mergeUpdate(updates.map(row => row.data)); + getDocAsUpdates = async (docId: string) => { + const dbID = this.toDBDocId(docId); + const update = await this.tryTrim(dbID); + if (update) { + return update; + } else { + const updates = await this.adapter.getUpdates(dbID); + return mergeUpdate(updates.map(row => row.data)); + } }; - override async addBlob(key: string, value: Uint8Array) { + async addBlob(key: string, value: Uint8Array) { this.update$.next(); - const res = await super.addBlob(key, value); + const res = await this.adapter.addBlob(key, value); return res; } - override async deleteBlob(key: string) { - this.update$.next(); - await super.deleteBlob(key); + async getBlob(key: string) { + return this.adapter.getBlob(key); } - override async addUpdateToSQLite(data: InsertRow[]) { - this.update$.next(); - await super.addUpdateToSQLite(data); + async getBlobKeys() { + return this.adapter.getBlobKeys(); } - private readonly tryTrim = async (docId?: string) => { - const count = (await this.db?.getUpdatesCount(docId)) ?? 0; + async deleteBlob(key: string) { + this.update$.next(); + await this.adapter.deleteBlob(key); + } + + async addUpdateToSQLite(update: Uint8Array, subdocId: string) { + this.update$.next(); + await this.adapter.addUpdateToSQLite([ + { + data: update, + docId: this.toDBDocId(subdocId), + }, + ]); + } + + async deleteUpdate(subdocId: string) { + this.update$.next(); + await this.adapter.deleteUpdates(this.toDBDocId(subdocId)); + } + + private readonly tryTrim = async (dbID?: string) => { + const count = (await this.adapter?.getUpdatesCount(dbID)) ?? 0; if (count > TRIM_SIZE) { - logger.debug(`trim ${this.workspaceId}:${docId} ${count}`); - const update = await this.getDocAsUpdates(docId); - if (update) { - const insertRows = [{ data: update, docId }]; - await this.db?.replaceUpdates(docId, insertRows); - logger.debug(`trim ${this.workspaceId}:${docId} successfully`); - } + return await this.transaction(async () => { + logger.debug(`trim ${this.workspaceId}:${dbID} ${count}`); + const updates = await this.adapter.getUpdates(dbID); + const update = mergeUpdate(updates.map(row => row.data)); + const insertRows = [{ data: update, dbID }]; + await this.adapter?.replaceUpdates(dbID, insertRows); + logger.debug(`trim ${this.workspaceId}:${dbID} successfully`); + return update; + }); } + return null; }; } diff --git a/packages/frontend/electron/test/db/ensure-db.spec.ts b/packages/frontend/electron/test/db/ensure-db.spec.ts index e081a99e56..9ec70fd816 100644 --- a/packages/frontend/electron/test/db/ensure-db.spec.ts +++ b/packages/frontend/electron/test/db/ensure-db.spec.ts @@ -77,16 +77,16 @@ test('db should be destroyed when app quits', async () => { const db0 = await ensureSQLiteDB(workspaceId); const db1 = await ensureSQLiteDB(v4()); - expect(db0.db).not.toBeNull(); - expect(db1.db).not.toBeNull(); + expect(db0.adapter).not.toBeNull(); + expect(db1.adapter).not.toBeNull(); existProcess(); // wait the async `db.destroy()` to be called await setTimeout(100); - expect(db0.db).toBeNull(); - expect(db1.db).toBeNull(); + expect(db0.adapter.db).toBeNull(); + expect(db1.adapter.db).toBeNull(); }); test('db should be removed in db$Map after destroyed', async () => { diff --git a/packages/frontend/electron/test/db/workspace-db-adapter.spec.ts b/packages/frontend/electron/test/db/workspace-db-adapter.spec.ts index 349316be7c..c5b5c8f642 100644 --- a/packages/frontend/electron/test/db/workspace-db-adapter.spec.ts +++ b/packages/frontend/electron/test/db/workspace-db-adapter.spec.ts @@ -51,6 +51,6 @@ test('on destroy, check if resources have been released', async () => { }; db.update$ = updateSub as any; await db.destroy(); - expect(db.db).toBe(null); + expect(db.adapter.db).toBe(null); expect(updateSub.complete).toHaveBeenCalled(); }); diff --git a/packages/frontend/electron/tsconfig.json b/packages/frontend/electron/tsconfig.json index 1a98e1e52b..ff29aa8d6e 100644 --- a/packages/frontend/electron/tsconfig.json +++ b/packages/frontend/electron/tsconfig.json @@ -9,7 +9,7 @@ "experimentalDecorators": true, "types": ["node", "affine__env"], "outDir": "lib", - "moduleResolution": "node", + "moduleResolution": "Bundler", "resolveJsonModule": true, "noImplicitOverride": true, "paths": { diff --git a/packages/frontend/native/index.d.ts b/packages/frontend/native/index.d.ts index 3446100ebc..a82709e90f 100644 --- a/packages/frontend/native/index.d.ts +++ b/packages/frontend/native/index.d.ts @@ -1,6 +1,5 @@ /* auto-generated by NAPI-RS */ /* eslint-disable */ - export class SqliteConnection { constructor(path: string) connect(): Promise @@ -9,6 +8,7 @@ export class SqliteConnection { deleteBlob(key: string): Promise getBlobKeys(): Promise> getUpdates(docId?: string | undefined | null): Promise> + deleteUpdates(docId?: string | undefined | null): Promise getUpdatesCount(docId?: string | undefined | null): Promise getAllUpdates(): Promise> insertUpdates(updates: Array): Promise diff --git a/packages/frontend/native/index.js b/packages/frontend/native/index.js index 2aae7d108d..c8426d8a88 100644 --- a/packages/frontend/native/index.js +++ b/packages/frontend/native/index.js @@ -2,14 +2,10 @@ /* eslint-disable */ /* auto-generated by NAPI-RS */ -const { existsSync, readFileSync } = require('fs') -const { join } = require('path') - -const { platform, arch } = process +const { readFileSync } = require('fs') let nativeBinding = null -let localFileExisted = false -let loadError = null +const loadErrors = [] const isMusl = () => { let musl = false @@ -60,281 +56,281 @@ const isMuslFromChildProcess = () => { } } -switch (platform) { - case 'android': - switch (arch) { - case 'arm64': - localFileExisted = existsSync(join(__dirname, 'affine.android-arm64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./affine.android-arm64.node') - } else { - nativeBinding = require('@affine/native-android-arm64') - } - } catch (e) { - loadError = e - } - break - case 'arm': - localFileExisted = existsSync(join(__dirname, 'affine.android-arm-eabi.node')) - try { - if (localFileExisted) { - nativeBinding = require('./affine.android-arm-eabi.node') - } else { - nativeBinding = require('@affine/native-android-arm-eabi') - } - } catch (e) { - loadError = e - } - break - default: - loadError = new Error(`Unsupported architecture on Android ${arch}`) - } - break - case 'win32': - switch (arch) { - case 'x64': - localFileExisted = existsSync( - join(__dirname, 'affine.win32-x64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.win32-x64-msvc.node') - } else { - nativeBinding = require('@affine/native-win32-x64-msvc') - } - } catch (e) { - loadError = e - } - break - case 'ia32': - localFileExisted = existsSync( - join(__dirname, 'affine.win32-ia32-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.win32-ia32-msvc.node') - } else { - nativeBinding = require('@affine/native-win32-ia32-msvc') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'affine.win32-arm64-msvc.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.win32-arm64-msvc.node') - } else { - nativeBinding = require('@affine/native-win32-arm64-msvc') - } - } catch (e) { - loadError = e - } - break - default: - loadError = new Error(`Unsupported architecture on Windows: ${arch}`) - } - break - case 'darwin': - localFileExisted = existsSync(join(__dirname, 'affine.darwin-universal.node')) - try { - if (localFileExisted) { - nativeBinding = require('./affine.darwin-universal.node') - } else { - nativeBinding = require('@affine/native-darwin-universal') +function requireNative() { + if (process.platform === 'android') { + if (process.arch === 'arm64') { + try { + return require('./affine.android-arm64.node') + } catch (e) { + loadErrors.push(e) } - break - } catch {} - switch (arch) { - case 'x64': - localFileExisted = existsSync(join(__dirname, 'affine.darwin-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./affine.darwin-x64.node') - } else { - nativeBinding = require('@affine/native-darwin-x64') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync( - join(__dirname, 'affine.darwin-arm64.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.darwin-arm64.node') - } else { - nativeBinding = require('@affine/native-darwin-arm64') - } - } catch (e) { - loadError = e - } - break - default: - loadError = new Error(`Unsupported architecture on macOS: ${arch}`) + try { + return require('@affine/native-android-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm') { + try { + return require('./affine.android-arm-eabi.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-android-arm-eabi') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`)) } - break - case 'freebsd': - switch (arch) { - case 'x64': - localFileExisted = existsSync(join(__dirname, 'affine.freebsd-x64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./affine.freebsd-x64.node') - } else { - nativeBinding = require('@affine/native-freebsd-x64') - } - } catch (e) { - loadError = e - } - break - case 'arm64': - localFileExisted = existsSync(join(__dirname, 'affine.freebsd-arm64.node')) - try { - if (localFileExisted) { - nativeBinding = require('./affine.freebsd-arm64.node') - } else { - nativeBinding = require('@affine/native-freebsd-arm64') - } - } catch (e) { - loadError = e - } - break - default: - loadError = new Error(`Unsupported architecture on FreeBSD: ${arch}`) + } else if (process.platform === 'win32') { + if (process.arch === 'x64') { + try { + return require('./affine.win32-x64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-win32-x64-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'ia32') { + try { + return require('./affine.win32-ia32-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-win32-ia32-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./affine.win32-arm64-msvc.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-win32-arm64-msvc') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Windows: ${process.arch}`)) } - break - case 'linux': - switch (arch) { - case 'x64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'affine.linux-x64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.linux-x64-musl.node') - } else { - nativeBinding = require('@affine/native-linux-x64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'affine.linux-x64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.linux-x64-gnu.node') - } else { - nativeBinding = require('@affine/native-linux-x64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'affine.linux-arm64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.linux-arm64-musl.node') - } else { - nativeBinding = require('@affine/native-linux-arm64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'affine.linux-arm64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.linux-arm64-gnu.node') - } else { - nativeBinding = require('@affine/native-linux-arm64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 'arm': - localFileExisted = existsSync( - join(__dirname, 'affine.linux-arm-gnueabihf.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.linux-arm-gnueabihf.node') - } else { - nativeBinding = require('@affine/native-linux-arm-gnueabihf') - } - } catch (e) { - loadError = e - } - break - case 'riscv64': - if (isMusl()) { - localFileExisted = existsSync( - join(__dirname, 'affine.linux-riscv64-musl.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.linux-riscv64-musl.node') - } else { - nativeBinding = require('@affine/native-linux-riscv64-musl') - } - } catch (e) { - loadError = e - } - } else { - localFileExisted = existsSync( - join(__dirname, 'affine.linux-riscv64-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.linux-riscv64-gnu.node') - } else { - nativeBinding = require('@affine/native-linux-riscv64-gnu') - } - } catch (e) { - loadError = e - } - } - break - case 's390x': - localFileExisted = existsSync( - join(__dirname, 'affine.linux-s390x-gnu.node') - ) - try { - if (localFileExisted) { - nativeBinding = require('./affine.linux-s390x-gnu.node') - } else { - nativeBinding = require('@affine/native-linux-s390x-gnu') - } - } catch (e) { - loadError = e - } - break - default: - loadError = new Error(`Unsupported architecture on Linux: ${arch}`) + } else if (process.platform === 'darwin') { + try { + return require('./affine.darwin-universal.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-darwin-universal') + } catch (e) { + loadErrors.push(e) + } + + if (process.arch === 'x64') { + try { + return require('./affine.darwin-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-darwin-x64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./affine.darwin-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-darwin-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`)) } - break - default: - loadError = new Error(`Unsupported OS: ${platform}, architecture: ${arch}`) + } else if (process.platform === 'freebsd') { + if (process.arch === 'x64') { + try { + return require('./affine.freebsd-x64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-freebsd-x64') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 'arm64') { + try { + return require('./affine.freebsd-arm64.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-freebsd-arm64') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on FreeBSD: ${process.arch}`)) + } + } else if (process.platform === 'linux') { + if (process.arch === 'x64') { + if (isMusl()) { + try { + return require('./affine.linux-x64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-x64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./affine.linux-x64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-x64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'arm64') { + if (isMusl()) { + try { + return require('./affine.linux-arm64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-arm64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./affine.linux-arm64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-arm64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'arm') { + if (isMusl()) { + try { + return require('./affine.linux-arm-musleabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-arm-musleabihf') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./affine.linux-arm-gnueabihf.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-arm-gnueabihf') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'riscv64') { + if (isMusl()) { + try { + return require('./affine.linux-riscv64-musl.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-riscv64-musl') + } catch (e) { + loadErrors.push(e) + } + + } else { + try { + return require('./affine.linux-riscv64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-riscv64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } + } else if (process.arch === 'ppc64') { + try { + return require('./affine.linux-ppc64-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-ppc64-gnu') + } catch (e) { + loadErrors.push(e) + } + + } else if (process.arch === 's390x') { + try { + return require('./affine.linux-s390x-gnu.node') + } catch (e) { + loadErrors.push(e) + } + try { + return require('@affine/native-linux-s390x-gnu') + } catch (e) { + loadErrors.push(e) + } + + } else { + loadErrors.push(new Error(`Unsupported architecture on Linux: ${process.arch}`)) + } + } else { + loadErrors.push(new Error(`Unsupported OS: ${process.platform}, architecture: ${process.arch}`)) + } } +nativeBinding = requireNative() + if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { try { nativeBinding = require('./affine.wasi.cjs') @@ -355,8 +351,12 @@ if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) { } if (!nativeBinding) { - if (loadError) { - throw loadError + if (loadErrors.length > 0) { + // TODO Link to documentation with potential fixes + // - The package owner could build/publish bindings for this arch + // - The user may need to bundle the correct files + // - The user may need to re-install node_modules to get new packages + throw new Error('Failed to load native binding', { cause: loadErrors }) } throw new Error(`Failed to load native binding`) } diff --git a/packages/frontend/native/src/sqlite/mod.rs b/packages/frontend/native/src/sqlite/mod.rs index 7738721167..e400407f8f 100644 --- a/packages/frontend/native/src/sqlite/mod.rs +++ b/packages/frontend/native/src/sqlite/mod.rs @@ -73,6 +73,7 @@ impl SqliteConnection { .await .map_err(anyhow::Error::from)?; self.migrate_add_doc_id().await?; + self.migrate_add_doc_id_index().await?; connection.detach(); Ok(()) } @@ -145,6 +146,25 @@ impl SqliteConnection { Ok(updates) } + #[napi] + pub async fn delete_updates(&self, doc_id: Option) -> napi::Result<()> { + match doc_id { + Some(doc_id) => { + sqlx::query!("DELETE FROM updates WHERE doc_id = ?", doc_id) + .execute(&self.pool) + .await + .map_err(anyhow::Error::from)?; + } + None => { + sqlx::query!("DELETE FROM updates WHERE doc_id is NULL") + .execute(&self.pool) + .await + .map_err(anyhow::Error::from)?; + } + }; + Ok(()) + } + #[napi] pub async fn get_updates_count(&self, doc_id: Option) -> napi::Result { let count = match doc_id { @@ -361,4 +381,17 @@ impl SqliteConnection { } } } + + pub async fn migrate_add_doc_id_index(&self) -> napi::Result<()> { + // ignore errors + match sqlx::query("CREATE INDEX IF NOT EXISTS idx_doc_id ON updates(doc_id);") + .execute(&self.pool) + .await + { + Ok(_) => Ok(()), + Err(err) => { + Err(anyhow::Error::from(err).into()) // Propagate other errors + } + } + } }