perf(electron): add index for updates (#6951)
 Above image shows the performance on querying a 20k rows of updates table, which is super slow at 150+ms. After adding index for doc_id the performance should be greatly improved. After:  fix TOV-866
This commit is contained in:
@@ -37,10 +37,7 @@ class Doc implements DocType {
|
|||||||
if (!apis?.db) {
|
if (!apis?.db) {
|
||||||
throw new Error('sqlite datasource is not available');
|
throw new Error('sqlite datasource is not available');
|
||||||
}
|
}
|
||||||
const update = await apis.db.getDocAsUpdates(
|
const update = await apis.db.getDocAsUpdates(this.workspaceId, docId);
|
||||||
this.workspaceId,
|
|
||||||
this.workspaceId === docId ? undefined : docId
|
|
||||||
);
|
|
||||||
|
|
||||||
if (update) {
|
if (update) {
|
||||||
if (
|
if (
|
||||||
@@ -60,19 +57,18 @@ class Doc implements DocType {
|
|||||||
if (!apis?.db) {
|
if (!apis?.db) {
|
||||||
throw new Error('sqlite datasource is not available');
|
throw new Error('sqlite datasource is not available');
|
||||||
}
|
}
|
||||||
await apis.db.applyDocUpdate(
|
await apis.db.applyDocUpdate(this.workspaceId, data, docId);
|
||||||
this.workspaceId,
|
|
||||||
data,
|
|
||||||
this.workspaceId === docId ? undefined : docId
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
clear(): void | Promise<void> {
|
clear(): void | Promise<void> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
del(): void | Promise<void> {
|
async del(docId: string) {
|
||||||
return;
|
if (!apis?.db) {
|
||||||
|
throw new Error('sqlite datasource is not available');
|
||||||
|
}
|
||||||
|
await apis.db.deleteDoc(this.workspaceId, docId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,14 @@ import type { InsertRow } from '@affine/native';
|
|||||||
import { SqliteConnection, ValidationResult } from '@affine/native';
|
import { SqliteConnection, ValidationResult } from '@affine/native';
|
||||||
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
import { WorkspaceVersion } from '@toeverything/infra/blocksuite';
|
||||||
|
|
||||||
import { applyGuidCompatibilityFix, migrateToLatest } from '../db/migration';
|
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
|
import { applyGuidCompatibilityFix, migrateToLatest } from './migration';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A base class for SQLite DB adapter that provides basic methods around updates & blobs
|
* 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;
|
db: SqliteConnection | null = null;
|
||||||
abstract role: string;
|
|
||||||
|
|
||||||
constructor(public readonly path: string) {}
|
constructor(public readonly path: string) {}
|
||||||
|
|
||||||
async connectIfNeeded() {
|
async connectIfNeeded() {
|
||||||
@@ -27,7 +25,7 @@ export abstract class BaseSQLiteAdapter {
|
|||||||
await migrateToLatest(this.path, WorkspaceVersion.Surface);
|
await migrateToLatest(this.path, WorkspaceVersion.Surface);
|
||||||
}
|
}
|
||||||
await applyGuidCompatibilityFix(this.db);
|
await applyGuidCompatibilityFix(this.db);
|
||||||
logger.info(`[SQLiteAdapter:${this.role}]`, 'connected:', this.path);
|
logger.info(`[SQLiteAdapter]`, 'connected:', this.path);
|
||||||
}
|
}
|
||||||
return this.db;
|
return this.db;
|
||||||
}
|
}
|
||||||
@@ -36,7 +34,7 @@ export abstract class BaseSQLiteAdapter {
|
|||||||
const { db } = this;
|
const { db } = this;
|
||||||
this.db = null;
|
this.db = null;
|
||||||
// log after close will sometimes crash the app when quitting
|
// 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();
|
await db?.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +126,7 @@ export abstract class BaseSQLiteAdapter {
|
|||||||
const start = performance.now();
|
const start = performance.now();
|
||||||
await this.db.insertUpdates(updates);
|
await this.db.insertUpdates(updates);
|
||||||
logger.debug(
|
logger.debug(
|
||||||
`[SQLiteAdapter][${this.role}] addUpdateToSQLite`,
|
`[SQLiteAdapter] addUpdateToSQLite`,
|
||||||
'length:',
|
'length:',
|
||||||
updates.length,
|
updates.length,
|
||||||
'docids',
|
'docids',
|
||||||
@@ -140,4 +138,41 @@ export abstract class BaseSQLiteAdapter {
|
|||||||
logger.error('addUpdateToSQLite', this.path, error);
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -5,22 +5,21 @@ import { ensureSQLiteDB } from './ensure-db';
|
|||||||
export * from './ensure-db';
|
export * from './ensure-db';
|
||||||
|
|
||||||
export const dbHandlers = {
|
export const dbHandlers = {
|
||||||
getDocAsUpdates: async (workspaceId: string, subdocId?: string) => {
|
getDocAsUpdates: async (workspaceId: string, subdocId: string) => {
|
||||||
const workspaceDB = await ensureSQLiteDB(workspaceId);
|
const workspaceDB = await ensureSQLiteDB(workspaceId);
|
||||||
return workspaceDB.getDocAsUpdates(subdocId);
|
return workspaceDB.getDocAsUpdates(subdocId);
|
||||||
},
|
},
|
||||||
applyDocUpdate: async (
|
applyDocUpdate: async (
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
update: Uint8Array,
|
update: Uint8Array,
|
||||||
subdocId?: string
|
subdocId: string
|
||||||
) => {
|
) => {
|
||||||
const workspaceDB = await ensureSQLiteDB(workspaceId);
|
const workspaceDB = await ensureSQLiteDB(workspaceId);
|
||||||
return workspaceDB.addUpdateToSQLite([
|
return workspaceDB.addUpdateToSQLite(update, subdocId);
|
||||||
{
|
},
|
||||||
data: update,
|
deleteDoc: async (workspaceId: string, subdocId: string) => {
|
||||||
docId: subdocId,
|
const workspaceDB = await ensureSQLiteDB(workspaceId);
|
||||||
},
|
return workspaceDB.deleteUpdate(subdocId);
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
addBlob: async (workspaceId: string, key: string, data: Uint8Array) => {
|
addBlob: async (workspaceId: string, key: string, data: Uint8Array) => {
|
||||||
const workspaceDB = await ensureSQLiteDB(workspaceId);
|
const workspaceDB = await ensureSQLiteDB(workspaceId);
|
||||||
|
|||||||
@@ -1,36 +1,43 @@
|
|||||||
import type { InsertRow } from '@affine/native';
|
import { AsyncLock } from '@toeverything/infra';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { applyUpdate, Doc as YDoc } from 'yjs';
|
import { applyUpdate, Doc as YDoc } from 'yjs';
|
||||||
|
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import { getWorkspaceMeta } from '../workspace/meta';
|
import { getWorkspaceMeta } from '../workspace/meta';
|
||||||
import { BaseSQLiteAdapter } from './base-db-adapter';
|
import { SQLiteAdapter } from './db-adapter';
|
||||||
import { mergeUpdate } from './merge-update';
|
import { mergeUpdate } from './merge-update';
|
||||||
|
|
||||||
const TRIM_SIZE = 500;
|
const TRIM_SIZE = 500;
|
||||||
|
|
||||||
export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
export class WorkspaceSQLiteDB {
|
||||||
role = 'primary';
|
lock = new AsyncLock();
|
||||||
|
|
||||||
update$ = new Subject<void>();
|
update$ = new Subject<void>();
|
||||||
|
adapter = new SQLiteAdapter(this.path);
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
public override path: string,
|
public path: string,
|
||||||
public workspaceId: string
|
public workspaceId: string
|
||||||
) {
|
) {}
|
||||||
super(path);
|
|
||||||
|
async transaction<T>(cb: () => Promise<T>): Promise<T> {
|
||||||
|
using _lock = await this.lock.acquire();
|
||||||
|
return await cb();
|
||||||
}
|
}
|
||||||
|
|
||||||
override async destroy() {
|
async destroy() {
|
||||||
await super.destroy();
|
await this.adapter.destroy();
|
||||||
|
|
||||||
// when db is closed, we can safely remove it from ensure-db list
|
// when db is closed, we can safely remove it from ensure-db list
|
||||||
this.update$.complete();
|
this.update$.complete();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
toDBDocId = (docId: string) => {
|
||||||
|
return this.workspaceId === docId ? undefined : docId;
|
||||||
|
};
|
||||||
|
|
||||||
getWorkspaceName = async () => {
|
getWorkspaceName = async () => {
|
||||||
const ydoc = new YDoc();
|
const ydoc = new YDoc();
|
||||||
const updates = await this.getUpdates();
|
const updates = await this.adapter.getUpdates();
|
||||||
updates.forEach(update => {
|
updates.forEach(update => {
|
||||||
applyUpdate(ydoc, update.data);
|
applyUpdate(ydoc, update.data);
|
||||||
});
|
});
|
||||||
@@ -38,44 +45,75 @@ export class WorkspaceSQLiteDB extends BaseSQLiteAdapter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
const db = await super.connectIfNeeded();
|
const db = await this.adapter.connectIfNeeded();
|
||||||
await this.tryTrim();
|
await this.tryTrim();
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async get(docId: string) {
|
||||||
|
return this.adapter.getUpdates(docId);
|
||||||
|
}
|
||||||
|
|
||||||
// getUpdates then encode
|
// getUpdates then encode
|
||||||
getDocAsUpdates = async (docId?: string) => {
|
getDocAsUpdates = async (docId: string) => {
|
||||||
const updates = await this.getUpdates(docId);
|
const dbID = this.toDBDocId(docId);
|
||||||
return mergeUpdate(updates.map(row => row.data));
|
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();
|
this.update$.next();
|
||||||
const res = await super.addBlob(key, value);
|
const res = await this.adapter.addBlob(key, value);
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
override async deleteBlob(key: string) {
|
async getBlob(key: string) {
|
||||||
this.update$.next();
|
return this.adapter.getBlob(key);
|
||||||
await super.deleteBlob(key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override async addUpdateToSQLite(data: InsertRow[]) {
|
async getBlobKeys() {
|
||||||
this.update$.next();
|
return this.adapter.getBlobKeys();
|
||||||
await super.addUpdateToSQLite(data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private readonly tryTrim = async (docId?: string) => {
|
async deleteBlob(key: string) {
|
||||||
const count = (await this.db?.getUpdatesCount(docId)) ?? 0;
|
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) {
|
if (count > TRIM_SIZE) {
|
||||||
logger.debug(`trim ${this.workspaceId}:${docId} ${count}`);
|
return await this.transaction(async () => {
|
||||||
const update = await this.getDocAsUpdates(docId);
|
logger.debug(`trim ${this.workspaceId}:${dbID} ${count}`);
|
||||||
if (update) {
|
const updates = await this.adapter.getUpdates(dbID);
|
||||||
const insertRows = [{ data: update, docId }];
|
const update = mergeUpdate(updates.map(row => row.data));
|
||||||
await this.db?.replaceUpdates(docId, insertRows);
|
const insertRows = [{ data: update, dbID }];
|
||||||
logger.debug(`trim ${this.workspaceId}:${docId} successfully`);
|
await this.adapter?.replaceUpdates(dbID, insertRows);
|
||||||
}
|
logger.debug(`trim ${this.workspaceId}:${dbID} successfully`);
|
||||||
|
return update;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -77,16 +77,16 @@ test('db should be destroyed when app quits', async () => {
|
|||||||
const db0 = await ensureSQLiteDB(workspaceId);
|
const db0 = await ensureSQLiteDB(workspaceId);
|
||||||
const db1 = await ensureSQLiteDB(v4());
|
const db1 = await ensureSQLiteDB(v4());
|
||||||
|
|
||||||
expect(db0.db).not.toBeNull();
|
expect(db0.adapter).not.toBeNull();
|
||||||
expect(db1.db).not.toBeNull();
|
expect(db1.adapter).not.toBeNull();
|
||||||
|
|
||||||
existProcess();
|
existProcess();
|
||||||
|
|
||||||
// wait the async `db.destroy()` to be called
|
// wait the async `db.destroy()` to be called
|
||||||
await setTimeout(100);
|
await setTimeout(100);
|
||||||
|
|
||||||
expect(db0.db).toBeNull();
|
expect(db0.adapter.db).toBeNull();
|
||||||
expect(db1.db).toBeNull();
|
expect(db1.adapter.db).toBeNull();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('db should be removed in db$Map after destroyed', async () => {
|
test('db should be removed in db$Map after destroyed', async () => {
|
||||||
|
|||||||
@@ -51,6 +51,6 @@ test('on destroy, check if resources have been released', async () => {
|
|||||||
};
|
};
|
||||||
db.update$ = updateSub as any;
|
db.update$ = updateSub as any;
|
||||||
await db.destroy();
|
await db.destroy();
|
||||||
expect(db.db).toBe(null);
|
expect(db.adapter.db).toBe(null);
|
||||||
expect(updateSub.complete).toHaveBeenCalled();
|
expect(updateSub.complete).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"experimentalDecorators": true,
|
"experimentalDecorators": true,
|
||||||
"types": ["node", "affine__env"],
|
"types": ["node", "affine__env"],
|
||||||
"outDir": "lib",
|
"outDir": "lib",
|
||||||
"moduleResolution": "node",
|
"moduleResolution": "Bundler",
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"noImplicitOverride": true,
|
"noImplicitOverride": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
|
|||||||
2
packages/frontend/native/index.d.ts
vendored
2
packages/frontend/native/index.d.ts
vendored
@@ -1,6 +1,5 @@
|
|||||||
/* auto-generated by NAPI-RS */
|
/* auto-generated by NAPI-RS */
|
||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
|
|
||||||
export class SqliteConnection {
|
export class SqliteConnection {
|
||||||
constructor(path: string)
|
constructor(path: string)
|
||||||
connect(): Promise<void>
|
connect(): Promise<void>
|
||||||
@@ -9,6 +8,7 @@ export class SqliteConnection {
|
|||||||
deleteBlob(key: string): Promise<void>
|
deleteBlob(key: string): Promise<void>
|
||||||
getBlobKeys(): Promise<Array<string>>
|
getBlobKeys(): Promise<Array<string>>
|
||||||
getUpdates(docId?: string | undefined | null): Promise<Array<UpdateRow>>
|
getUpdates(docId?: string | undefined | null): Promise<Array<UpdateRow>>
|
||||||
|
deleteUpdates(docId?: string | undefined | null): Promise<void>
|
||||||
getUpdatesCount(docId?: string | undefined | null): Promise<number>
|
getUpdatesCount(docId?: string | undefined | null): Promise<number>
|
||||||
getAllUpdates(): Promise<Array<UpdateRow>>
|
getAllUpdates(): Promise<Array<UpdateRow>>
|
||||||
insertUpdates(updates: Array<InsertRow>): Promise<void>
|
insertUpdates(updates: Array<InsertRow>): Promise<void>
|
||||||
|
|||||||
@@ -2,14 +2,10 @@
|
|||||||
/* eslint-disable */
|
/* eslint-disable */
|
||||||
/* auto-generated by NAPI-RS */
|
/* auto-generated by NAPI-RS */
|
||||||
|
|
||||||
const { existsSync, readFileSync } = require('fs')
|
const { readFileSync } = require('fs')
|
||||||
const { join } = require('path')
|
|
||||||
|
|
||||||
const { platform, arch } = process
|
|
||||||
|
|
||||||
let nativeBinding = null
|
let nativeBinding = null
|
||||||
let localFileExisted = false
|
const loadErrors = []
|
||||||
let loadError = null
|
|
||||||
|
|
||||||
const isMusl = () => {
|
const isMusl = () => {
|
||||||
let musl = false
|
let musl = false
|
||||||
@@ -60,281 +56,281 @@ const isMuslFromChildProcess = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (platform) {
|
function requireNative() {
|
||||||
case 'android':
|
if (process.platform === 'android') {
|
||||||
switch (arch) {
|
if (process.arch === 'arm64') {
|
||||||
case 'arm64':
|
try {
|
||||||
localFileExisted = existsSync(join(__dirname, 'affine.android-arm64.node'))
|
return require('./affine.android-arm64.node')
|
||||||
try {
|
} catch (e) {
|
||||||
if (localFileExisted) {
|
loadErrors.push(e)
|
||||||
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')
|
|
||||||
}
|
}
|
||||||
break
|
try {
|
||||||
} catch {}
|
return require('@affine/native-android-arm64')
|
||||||
switch (arch) {
|
} catch (e) {
|
||||||
case 'x64':
|
loadErrors.push(e)
|
||||||
localFileExisted = existsSync(join(__dirname, 'affine.darwin-x64.node'))
|
}
|
||||||
try {
|
|
||||||
if (localFileExisted) {
|
} else if (process.arch === 'arm') {
|
||||||
nativeBinding = require('./affine.darwin-x64.node')
|
try {
|
||||||
} else {
|
return require('./affine.android-arm-eabi.node')
|
||||||
nativeBinding = require('@affine/native-darwin-x64')
|
} catch (e) {
|
||||||
}
|
loadErrors.push(e)
|
||||||
} catch (e) {
|
}
|
||||||
loadError = e
|
try {
|
||||||
}
|
return require('@affine/native-android-arm-eabi')
|
||||||
break
|
} catch (e) {
|
||||||
case 'arm64':
|
loadErrors.push(e)
|
||||||
localFileExisted = existsSync(
|
}
|
||||||
join(__dirname, 'affine.darwin-arm64.node')
|
|
||||||
)
|
} else {
|
||||||
try {
|
loadErrors.push(new Error(`Unsupported architecture on Android ${process.arch}`))
|
||||||
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}`)
|
|
||||||
}
|
}
|
||||||
break
|
} else if (process.platform === 'win32') {
|
||||||
case 'freebsd':
|
if (process.arch === 'x64') {
|
||||||
switch (arch) {
|
try {
|
||||||
case 'x64':
|
return require('./affine.win32-x64-msvc.node')
|
||||||
localFileExisted = existsSync(join(__dirname, 'affine.freebsd-x64.node'))
|
} catch (e) {
|
||||||
try {
|
loadErrors.push(e)
|
||||||
if (localFileExisted) {
|
}
|
||||||
nativeBinding = require('./affine.freebsd-x64.node')
|
try {
|
||||||
} else {
|
return require('@affine/native-win32-x64-msvc')
|
||||||
nativeBinding = require('@affine/native-freebsd-x64')
|
} catch (e) {
|
||||||
}
|
loadErrors.push(e)
|
||||||
} catch (e) {
|
}
|
||||||
loadError = e
|
|
||||||
}
|
} else if (process.arch === 'ia32') {
|
||||||
break
|
try {
|
||||||
case 'arm64':
|
return require('./affine.win32-ia32-msvc.node')
|
||||||
localFileExisted = existsSync(join(__dirname, 'affine.freebsd-arm64.node'))
|
} catch (e) {
|
||||||
try {
|
loadErrors.push(e)
|
||||||
if (localFileExisted) {
|
}
|
||||||
nativeBinding = require('./affine.freebsd-arm64.node')
|
try {
|
||||||
} else {
|
return require('@affine/native-win32-ia32-msvc')
|
||||||
nativeBinding = require('@affine/native-freebsd-arm64')
|
} catch (e) {
|
||||||
}
|
loadErrors.push(e)
|
||||||
} catch (e) {
|
}
|
||||||
loadError = e
|
|
||||||
}
|
} else if (process.arch === 'arm64') {
|
||||||
break
|
try {
|
||||||
default:
|
return require('./affine.win32-arm64-msvc.node')
|
||||||
loadError = new Error(`Unsupported architecture on FreeBSD: ${arch}`)
|
} 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
|
} else if (process.platform === 'darwin') {
|
||||||
case 'linux':
|
try {
|
||||||
switch (arch) {
|
return require('./affine.darwin-universal.node')
|
||||||
case 'x64':
|
} catch (e) {
|
||||||
if (isMusl()) {
|
loadErrors.push(e)
|
||||||
localFileExisted = existsSync(
|
}
|
||||||
join(__dirname, 'affine.linux-x64-musl.node')
|
try {
|
||||||
)
|
return require('@affine/native-darwin-universal')
|
||||||
try {
|
} catch (e) {
|
||||||
if (localFileExisted) {
|
loadErrors.push(e)
|
||||||
nativeBinding = require('./affine.linux-x64-musl.node')
|
}
|
||||||
} else {
|
|
||||||
nativeBinding = require('@affine/native-linux-x64-musl')
|
if (process.arch === 'x64') {
|
||||||
}
|
try {
|
||||||
} catch (e) {
|
return require('./affine.darwin-x64.node')
|
||||||
loadError = e
|
} catch (e) {
|
||||||
}
|
loadErrors.push(e)
|
||||||
} else {
|
}
|
||||||
localFileExisted = existsSync(
|
try {
|
||||||
join(__dirname, 'affine.linux-x64-gnu.node')
|
return require('@affine/native-darwin-x64')
|
||||||
)
|
} catch (e) {
|
||||||
try {
|
loadErrors.push(e)
|
||||||
if (localFileExisted) {
|
}
|
||||||
nativeBinding = require('./affine.linux-x64-gnu.node')
|
|
||||||
} else {
|
} else if (process.arch === 'arm64') {
|
||||||
nativeBinding = require('@affine/native-linux-x64-gnu')
|
try {
|
||||||
}
|
return require('./affine.darwin-arm64.node')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
loadError = e
|
loadErrors.push(e)
|
||||||
}
|
}
|
||||||
}
|
try {
|
||||||
break
|
return require('@affine/native-darwin-arm64')
|
||||||
case 'arm64':
|
} catch (e) {
|
||||||
if (isMusl()) {
|
loadErrors.push(e)
|
||||||
localFileExisted = existsSync(
|
}
|
||||||
join(__dirname, 'affine.linux-arm64-musl.node')
|
|
||||||
)
|
} else {
|
||||||
try {
|
loadErrors.push(new Error(`Unsupported architecture on macOS: ${process.arch}`))
|
||||||
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}`)
|
|
||||||
}
|
}
|
||||||
break
|
} else if (process.platform === 'freebsd') {
|
||||||
default:
|
if (process.arch === 'x64') {
|
||||||
loadError = new Error(`Unsupported OS: ${platform}, architecture: ${arch}`)
|
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) {
|
if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
|
||||||
try {
|
try {
|
||||||
nativeBinding = require('./affine.wasi.cjs')
|
nativeBinding = require('./affine.wasi.cjs')
|
||||||
@@ -355,8 +351,12 @@ if (!nativeBinding || process.env.NAPI_RS_FORCE_WASI) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!nativeBinding) {
|
if (!nativeBinding) {
|
||||||
if (loadError) {
|
if (loadErrors.length > 0) {
|
||||||
throw loadError
|
// 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`)
|
throw new Error(`Failed to load native binding`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ impl SqliteConnection {
|
|||||||
.await
|
.await
|
||||||
.map_err(anyhow::Error::from)?;
|
.map_err(anyhow::Error::from)?;
|
||||||
self.migrate_add_doc_id().await?;
|
self.migrate_add_doc_id().await?;
|
||||||
|
self.migrate_add_doc_id_index().await?;
|
||||||
connection.detach();
|
connection.detach();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -145,6 +146,25 @@ impl SqliteConnection {
|
|||||||
Ok(updates)
|
Ok(updates)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
pub async fn delete_updates(&self, doc_id: Option<String>) -> 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]
|
#[napi]
|
||||||
pub async fn get_updates_count(&self, doc_id: Option<String>) -> napi::Result<i32> {
|
pub async fn get_updates_count(&self, doc_id: Option<String>) -> napi::Result<i32> {
|
||||||
let count = match doc_id {
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user