feat(nbstore): improve nbstore (#9512)

This commit is contained in:
EYHN
2025-01-06 09:38:03 +00:00
parent a2563d2180
commit 46c8c4a408
103 changed files with 3337 additions and 3423 deletions
@@ -1,33 +0,0 @@
import { type BlobRecord, BlobStorageBase, share } from '@affine/nbstore';
import { NativeDBConnection } from './db';
export class SqliteBlobStorage extends BlobStorageBase {
override connection = share(
new NativeDBConnection(this.peer, this.spaceType, this.spaceId)
);
get db() {
return this.connection.inner;
}
override async get(key: string) {
return this.db.getBlob(key);
}
override async set(blob: BlobRecord) {
await this.db.setBlob(blob);
}
override async delete(key: string, permanently: boolean) {
await this.db.deleteBlob(key, permanently);
}
override async release() {
await this.db.releaseBlobs();
}
override async list() {
return this.db.listBlobs();
}
}
@@ -1,46 +0,0 @@
import path from 'node:path';
import { DocStorage as NativeDocStorage } from '@affine/native';
import { AutoReconnectConnection, type SpaceType } from '@affine/nbstore';
import fs from 'fs-extra';
import { logger } from '../logger';
import { getSpaceDBPath } from '../workspace/meta';
export class NativeDBConnection extends AutoReconnectConnection<NativeDocStorage> {
constructor(
private readonly peer: string,
private readonly type: SpaceType,
private readonly id: string
) {
super();
}
async getDBPath() {
return await getSpaceDBPath(this.peer, this.type, this.id);
}
override get shareId(): string {
return `sqlite:${this.peer}:${this.type}:${this.id}`;
}
override async doConnect() {
const dbPath = await this.getDBPath();
await fs.ensureDir(path.dirname(dbPath));
const conn = new NativeDocStorage(dbPath);
await conn.connect();
logger.info('[nbstore] connection established', this.shareId);
return conn;
}
override doDisconnect(conn: NativeDocStorage) {
conn
.close()
.then(() => {
logger.info('[nbstore] connection closed', this.shareId);
})
.catch(err => {
logger.error('[nbstore] connection close failed', this.shareId, err);
});
}
}
@@ -1,83 +0,0 @@
import {
type DocClocks,
type DocRecord,
DocStorageBase,
type DocUpdate,
share,
} from '@affine/nbstore';
import { NativeDBConnection } from './db';
export class SqliteDocStorage extends DocStorageBase {
override connection = share(
new NativeDBConnection(this.peer, this.spaceType, this.spaceId)
);
get db() {
return this.connection.inner;
}
override async pushDocUpdate(update: DocUpdate) {
const timestamp = await this.db.pushUpdate(update.docId, update.bin);
return { docId: update.docId, timestamp };
}
override async deleteDoc(docId: string) {
await this.db.deleteDoc(docId);
}
override async getDocTimestamps(after?: Date) {
const clocks = await this.db.getDocClocks(after);
return clocks.reduce((ret, cur) => {
ret[cur.docId] = cur.timestamp;
return ret;
}, {} as DocClocks);
}
override async getDocTimestamp(docId: string) {
return this.db.getDocClock(docId);
}
protected override async getDocSnapshot(docId: string) {
const snapshot = await this.db.getDocSnapshot(docId);
if (!snapshot) {
return null;
}
return {
docId,
bin: snapshot.data,
timestamp: snapshot.timestamp,
};
}
protected override async setDocSnapshot(
snapshot: DocRecord
): Promise<boolean> {
return this.db.setDocSnapshot({
docId: snapshot.docId,
data: Buffer.from(snapshot.bin),
timestamp: new Date(snapshot.timestamp),
});
}
protected override async getDocUpdates(docId: string) {
return this.db.getDocUpdates(docId).then(updates =>
updates.map(update => ({
docId,
bin: update.data,
timestamp: update.createdAt,
}))
);
}
protected override markUpdatesMerged(docId: string, updates: DocRecord[]) {
return this.db.markUpdatesMerged(
docId,
updates.map(update => update.timestamp)
);
}
}
@@ -1,128 +1,43 @@
import {
type BlobRecord,
type DocClock,
type DocUpdate,
} from '@affine/nbstore';
import path from 'node:path';
import { ensureStorage, getStorage } from './storage';
import { DocStoragePool } from '@affine/native';
import { parseUniversalId } from '@affine/nbstore';
import type { NativeDBApis } from '@affine/nbstore/sqlite';
import fs from 'fs-extra';
export const nbstoreHandlers = {
connect: async (id: string) => {
await ensureStorage(id);
},
close: async (id: string) => {
const store = getStorage(id);
if (store) {
store.disconnect();
// The store may be shared with other tabs, so we don't delete it from cache
// the underlying connection will handle the close correctly
// STORE_CACHE.delete(`${spaceType}:${spaceId}`);
}
},
pushDocUpdate: async (id: string, update: DocUpdate) => {
const store = await ensureStorage(id);
return store.get('doc').pushDocUpdate(update);
},
getDoc: async (id: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('doc').getDoc(docId);
},
deleteDoc: async (id: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('doc').deleteDoc(docId);
},
getDocTimestamps: async (id: string, after?: Date) => {
const store = await ensureStorage(id);
return store.get('doc').getDocTimestamps(after);
},
getDocTimestamp: async (id: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('doc').getDocTimestamp(docId);
},
setBlob: async (id: string, blob: BlobRecord) => {
const store = await ensureStorage(id);
return store.get('blob').set(blob);
},
getBlob: async (id: string, key: string) => {
const store = await ensureStorage(id);
return store.get('blob').get(key);
},
deleteBlob: async (id: string, key: string, permanently: boolean) => {
const store = await ensureStorage(id);
return store.get('blob').delete(key, permanently);
},
listBlobs: async (id: string) => {
const store = await ensureStorage(id);
return store.get('blob').list();
},
releaseBlobs: async (id: string) => {
const store = await ensureStorage(id);
return store.get('blob').release();
},
getPeerRemoteClocks: async (id: string, peer: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerRemoteClocks(peer);
},
getPeerRemoteClock: async (id: string, peer: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerRemoteClock(peer, docId);
},
setPeerRemoteClock: async (id: string, peer: string, clock: DocClock) => {
const store = await ensureStorage(id);
return store.get('sync').setPeerRemoteClock(peer, clock);
},
getPeerPulledRemoteClocks: async (id: string, peer: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerPulledRemoteClocks(peer);
},
getPeerPulledRemoteClock: async (id: string, peer: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerPulledRemoteClock(peer, docId);
},
setPeerPulledRemoteClock: async (
id: string,
peer: string,
clock: DocClock
) => {
const store = await ensureStorage(id);
return store.get('sync').setPeerPulledRemoteClock(peer, clock);
},
getPeerPushedClocks: async (id: string, peer: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerPushedClocks(peer);
},
getPeerPushedClock: async (id: string, peer: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerPushedClock(peer, docId);
},
setPeerPushedClock: async (id: string, peer: string, clock: DocClock) => {
const store = await ensureStorage(id);
return store.get('sync').setPeerPushedClock(peer, clock);
},
clearClocks: async (id: string) => {
const store = await ensureStorage(id);
return store.get('sync').clearClocks();
import { getSpaceDBPath } from '../workspace/meta';
const POOL = new DocStoragePool();
export const nbstoreHandlers: NativeDBApis = {
connect: async (universalId: string) => {
const { peer, type, id } = parseUniversalId(universalId);
const dbPath = await getSpaceDBPath(peer, type, id);
await fs.ensureDir(path.dirname(dbPath));
await POOL.connect(universalId, dbPath);
},
disconnect: POOL.disconnect.bind(POOL),
pushUpdate: POOL.pushUpdate.bind(POOL),
getDocSnapshot: POOL.getDocSnapshot.bind(POOL),
setDocSnapshot: POOL.setDocSnapshot.bind(POOL),
getDocUpdates: POOL.getDocUpdates.bind(POOL),
markUpdatesMerged: POOL.markUpdatesMerged.bind(POOL),
deleteDoc: POOL.deleteDoc.bind(POOL),
getDocClocks: POOL.getDocClocks.bind(POOL),
getDocClock: POOL.getDocClock.bind(POOL),
getBlob: POOL.getBlob.bind(POOL),
setBlob: POOL.setBlob.bind(POOL),
deleteBlob: POOL.deleteBlob.bind(POOL),
releaseBlobs: POOL.releaseBlobs.bind(POOL),
listBlobs: POOL.listBlobs.bind(POOL),
getPeerRemoteClocks: POOL.getPeerRemoteClocks.bind(POOL),
getPeerRemoteClock: POOL.getPeerRemoteClock.bind(POOL),
setPeerRemoteClock: POOL.setPeerRemoteClock.bind(POOL),
getPeerPulledRemoteClocks: POOL.getPeerPulledRemoteClocks.bind(POOL),
getPeerPulledRemoteClock: POOL.getPeerPulledRemoteClock.bind(POOL),
setPeerPulledRemoteClock: POOL.setPeerPulledRemoteClock.bind(POOL),
getPeerPushedClocks: POOL.getPeerPushedClocks.bind(POOL),
getPeerPushedClock: POOL.getPeerPushedClock.bind(POOL),
setPeerPushedClock: POOL.setPeerPushedClock.bind(POOL),
clearClocks: POOL.clearClocks.bind(POOL),
};
@@ -1,4 +1,3 @@
export { nbstoreHandlers } from './handlers';
export * from './storage';
export { dbEvents as dbEventsV1, dbHandlers as dbHandlersV1 } from './v1';
export { universalId } from '@affine/nbstore';
@@ -1,92 +0,0 @@
import { parseUniversalId, SpaceStorage } from '@affine/nbstore';
import { applyUpdate, Doc as YDoc } from 'yjs';
import { logger } from '../logger';
import { SqliteBlobStorage } from './blob';
import { NativeDBConnection } from './db';
import { SqliteDocStorage } from './doc';
import { SqliteSyncStorage } from './sync';
export class SqliteSpaceStorage extends SpaceStorage {
get connection() {
const docStore = this.get('doc');
if (!docStore) {
throw new Error('doc store not found');
}
const connection = docStore.connection;
if (!(connection instanceof NativeDBConnection)) {
throw new Error('doc store connection is not a Sqlite connection');
}
return connection;
}
async getDBPath() {
return this.connection.getDBPath();
}
async getWorkspaceName() {
const docStore = this.tryGet('doc');
if (!docStore) {
return null;
}
const doc = await docStore.getDoc(docStore.spaceId);
if (!doc) {
return null;
}
const ydoc = new YDoc();
applyUpdate(ydoc, doc.bin);
return ydoc.getMap('meta').get('name') as string;
}
async checkpoint() {
await this.connection.inner.checkpoint();
}
}
const STORE_CACHE = new Map<string, SqliteSpaceStorage>();
process.on('beforeExit', () => {
STORE_CACHE.forEach(store => {
store.destroy().catch(err => {
logger.error('[nbstore] destroy store failed', err);
});
});
});
export function getStorage(universalId: string) {
return STORE_CACHE.get(universalId);
}
export async function ensureStorage(universalId: string) {
const { peer, type, id } = parseUniversalId(universalId);
let store = STORE_CACHE.get(universalId);
if (!store) {
const opts = {
peer,
type,
id,
};
store = new SqliteSpaceStorage([
new SqliteDocStorage(opts),
new SqliteBlobStorage(opts),
new SqliteSyncStorage(opts),
]);
store.connect();
await store.waitForConnected();
STORE_CACHE.set(universalId, store);
}
return store;
}
@@ -1,70 +0,0 @@
import {
BasicSyncStorage,
type DocClock,
type DocClocks,
share,
} from '@affine/nbstore';
import { NativeDBConnection } from './db';
export class SqliteSyncStorage extends BasicSyncStorage {
override connection = share(
new NativeDBConnection(this.peer, this.spaceType, this.spaceId)
);
get db() {
return this.connection.inner;
}
override async getPeerRemoteClocks(peer: string) {
const records = await this.db.getPeerRemoteClocks(peer);
return records.reduce((clocks, { docId, timestamp }) => {
clocks[docId] = timestamp;
return clocks;
}, {} as DocClocks);
}
override async getPeerRemoteClock(peer: string, docId: string) {
return this.db.getPeerRemoteClock(peer, docId);
}
override async setPeerRemoteClock(peer: string, clock: DocClock) {
await this.db.setPeerRemoteClock(peer, clock.docId, clock.timestamp);
}
override async getPeerPulledRemoteClock(peer: string, docId: string) {
return this.db.getPeerPulledRemoteClock(peer, docId);
}
override async getPeerPulledRemoteClocks(peer: string) {
const records = await this.db.getPeerPulledRemoteClocks(peer);
return records.reduce((clocks, { docId, timestamp }) => {
clocks[docId] = timestamp;
return clocks;
}, {} as DocClocks);
}
override async setPeerPulledRemoteClock(peer: string, clock: DocClock) {
await this.db.setPeerPulledRemoteClock(peer, clock.docId, clock.timestamp);
}
override async getPeerPushedClocks(peer: string) {
const records = await this.db.getPeerPushedClocks(peer);
return records.reduce((clocks, { docId, timestamp }) => {
clocks[docId] = timestamp;
return clocks;
}, {} as DocClocks);
}
override async getPeerPushedClock(peer: string, docId: string) {
return this.db.getPeerPushedClock(peer, docId);
}
override async setPeerPushedClock(peer: string, clock: DocClock) {
await this.db.setPeerPushedClock(peer, clock.docId, clock.timestamp);
}
override async clearClocks() {
await this.db.clearClocks();
}
}
@@ -13,6 +13,7 @@
50A285D72D112A5E000D5A6D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 50A285D62D112A5E000D5A6D /* Localizable.xcstrings */; };
50A285D82D112A5E000D5A6D /* InfoPlist.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 50A285D52D112A5E000D5A6D /* InfoPlist.xcstrings */; };
50A285DC2D112B24000D5A6D /* Intelligents in Frameworks */ = {isa = PBXBuildFile; productRef = 50A285DB2D112B24000D5A6D /* Intelligents */; };
9D52FC432D26CDBF00105D0A /* JSValueContainerExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D52FC422D26CDB600105D0A /* JSValueContainerExt.swift */; };
9D6A85332CCF6DA700DAB35F /* HashcashPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D6A85322CCF6DA700DAB35F /* HashcashPlugin.swift */; };
9D90BE252CCB9876006677DB /* CookieManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE172CCB9876006677DB /* CookieManager.swift */; };
9D90BE262CCB9876006677DB /* CookiePlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9D90BE182CCB9876006677DB /* CookiePlugin.swift */; };
@@ -23,8 +24,8 @@
9D90BE2B2CCB9876006677DB /* config.xml in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE1F2CCB9876006677DB /* config.xml */; };
9D90BE2D2CCB9876006677DB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE222CCB9876006677DB /* Main.storyboard */; };
9D90BE2E2CCB9876006677DB /* public in Resources */ = {isa = PBXBuildFile; fileRef = 9D90BE232CCB9876006677DB /* public */; };
9DFCD1462D27D1D70028C92B /* libaffine_mobile_native.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9DFCD1452D27D1D70028C92B /* libaffine_mobile_native.a */; };
C4C413792CBE705D00337889 /* Pods_App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */; };
C4C97C6E2D0304D100BC2AD1 /* libaffine_mobile_native.a in Frameworks */ = {isa = PBXBuildFile; fileRef = C4C97C6D2D0304D100BC2AD1 /* libaffine_mobile_native.a */; };
C4C97C7C2D030BE000BC2AD1 /* affine_mobile_native.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4C97C6F2D0307B700BC2AD1 /* affine_mobile_native.swift */; };
C4C97C7D2D030BE000BC2AD1 /* affine_mobile_nativeFFI.h in Sources */ = {isa = PBXBuildFile; fileRef = C4C97C702D0307B700BC2AD1 /* affine_mobile_nativeFFI.h */; };
C4C97C7E2D030BE000BC2AD1 /* affine_mobile_nativeFFI.modulemap in Sources */ = {isa = PBXBuildFile; fileRef = C4C97C712D0307B700BC2AD1 /* affine_mobile_nativeFFI.modulemap */; };
@@ -39,6 +40,7 @@
50802D5E2D112F7D00694021 /* Intelligents */ = {isa = PBXFileReference; lastKnownFileType = wrapper; path = Intelligents; sourceTree = "<group>"; };
50A285D52D112A5E000D5A6D /* InfoPlist.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = InfoPlist.xcstrings; sourceTree = "<group>"; };
50A285D62D112A5E000D5A6D /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
9D52FC422D26CDB600105D0A /* JSValueContainerExt.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSValueContainerExt.swift; sourceTree = "<group>"; };
9D6A85322CCF6DA700DAB35F /* HashcashPlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashcashPlugin.swift; sourceTree = "<group>"; };
9D90BE172CCB9876006677DB /* CookieManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookieManager.swift; sourceTree = "<group>"; };
9D90BE182CCB9876006677DB /* CookiePlugin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CookiePlugin.swift; sourceTree = "<group>"; };
@@ -50,10 +52,10 @@
9D90BE202CCB9876006677DB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
9D90BE212CCB9876006677DB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
9D90BE232CCB9876006677DB /* public */ = {isa = PBXFileReference; lastKnownFileType = folder; path = public; sourceTree = "<group>"; };
9DFCD1452D27D1D70028C92B /* libaffine_mobile_native.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libaffine_mobile_native.a; sourceTree = "<group>"; };
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_App.framework; sourceTree = BUILT_PRODUCTS_DIR; };
AF51FD2D460BCFE21FA515B2 /* Pods-App.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-App.release.xcconfig"; path = "Pods/Target Support Files/Pods-App/Pods-App.release.xcconfig"; sourceTree = "<group>"; };
C4C97C6B2D03027900BC2AD1 /* libaffine_mobile_native.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libaffine_mobile_native.a; path = "../../../../../target/aarch64-apple-ios-sim/release/libaffine_mobile_native.a"; sourceTree = "<group>"; };
C4C97C6D2D0304D100BC2AD1 /* libaffine_mobile_native.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libaffine_mobile_native.a; sourceTree = "<group>"; };
C4C97C6B2D03027900BC2AD1 /* libaffine_mobile_native.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libaffine_mobile_native.a; path = "../../../../../target/aarch64-apple-ios-sim/debug/libaffine_mobile_native.a"; sourceTree = "<group>"; };
C4C97C6F2D0307B700BC2AD1 /* affine_mobile_native.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = affine_mobile_native.swift; sourceTree = "<group>"; };
C4C97C702D0307B700BC2AD1 /* affine_mobile_nativeFFI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = affine_mobile_nativeFFI.h; sourceTree = "<group>"; };
C4C97C712D0307B700BC2AD1 /* affine_mobile_nativeFFI.modulemap */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.module-map"; path = affine_mobile_nativeFFI.modulemap; sourceTree = "<group>"; };
@@ -70,8 +72,8 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
C4C97C6E2D0304D100BC2AD1 /* libaffine_mobile_native.a in Frameworks */,
50A285DC2D112B24000D5A6D /* Intelligents in Frameworks */,
9DFCD1462D27D1D70028C92B /* libaffine_mobile_native.a in Frameworks */,
50802D612D112F8700694021 /* Intelligents in Frameworks */,
C4C413792CBE705D00337889 /* Pods_App.framework in Frameworks */,
);
@@ -84,8 +86,8 @@
isa = PBXGroup;
children = (
C4C97C6B2D03027900BC2AD1 /* libaffine_mobile_native.a */,
C4C97C6D2D0304D100BC2AD1 /* libaffine_mobile_native.a */,
AF277DCFFFF123FFC6DF26C7 /* Pods_App.framework */,
9DFCD1452D27D1D70028C92B /* libaffine_mobile_native.a */,
);
name = Frameworks;
sourceTree = "<group>";
@@ -154,6 +156,7 @@
9D90BE242CCB9876006677DB /* App */ = {
isa = PBXGroup;
children = (
9D52FC422D26CDB600105D0A /* JSValueContainerExt.swift */,
9D90BE1A2CCB9876006677DB /* Plugins */,
9D90BE1C2CCB9876006677DB /* AppDelegate.swift */,
507513692D1924C600AD60C0 /* RootViewController.swift */,
@@ -325,6 +328,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9D52FC432D26CDBF00105D0A /* JSValueContainerExt.swift in Sources */,
5075136E2D1925BC00AD60C0 /* IntelligentsPlugin.swift in Sources */,
5075136A2D1924C600AD60C0 /* RootViewController.swift in Sources */,
C4C97C7C2D030BE000BC2AD1 /* affine_mobile_native.swift in Sources */,
@@ -0,0 +1,55 @@
//
// JSValueContainerExt.swift
// App
//
// Created by EYHN on 2025/1/2.
//
import Capacitor
enum RequestParamError: Error {
case request(key: String)
}
extension JSValueContainer {
public func getStringEnsure(_ key: String) throws -> String {
guard let str = self.getString(key) else {
throw RequestParamError.request(key: key)
}
return str
}
public func getIntEnsure(_ key: String) throws -> Int {
guard let int = self.getInt(key) else {
throw RequestParamError.request(key: key)
}
return int
}
public func getDoubleEnsure(_ key: String) throws -> Double {
guard let doub = self.getDouble(key) else {
throw RequestParamError.request(key: key)
}
return doub
}
public func getBoolEnsure(_ key: String) throws -> Bool {
guard let bool = self.getBool(key) else {
throw RequestParamError.request(key: key)
}
return bool
}
public func getArrayEnsure(_ key: String) throws -> JSArray {
guard let arr = self.getArray(key) else {
throw RequestParamError.request(key: key)
}
return arr
}
public func getArrayEnsure<T>(_ key: String, _ ofType: T.Type) throws -> [T] {
guard let arr = self.getArray(key, ofType) else {
throw RequestParamError.request(key: key)
}
return arr
}
}
@@ -3,400 +3,475 @@ import Foundation
@objc(NbStorePlugin)
public class NbStorePlugin: CAPPlugin, CAPBridgedPlugin {
private let docStoragePool: DocStoragePool = .init(noPointer: DocStoragePool.NoPointer())
public let identifier = "NbStorePlugin"
public let jsName = "NbStoreDocStorage"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "getSpaceDBPath", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "connect", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "close", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "isClosed", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "checkpoint", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "validate", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setSpaceId", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "pushUpdate", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getDocSnapshot", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setDocSnapshot", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getDocUpdates", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "markUpdatesMerged", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "deleteDoc", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getDocClocks", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getDocClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getBlob", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setBlob", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "deleteBlob", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "releaseBlobs", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "listBlobs", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerRemoteClocks", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerRemoteClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setPeerRemoteClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerPulledRemoteClocks", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerPulledRemoteClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setPeerPulledRemoteClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerPushedClocks", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setPeerPushedClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "clearClocks", returnType: CAPPluginReturnPromise),
]
@objc func getSpaceDBPath(_ call: CAPPluginCall) {
let peer = call.getString("peer") ?? ""
let spaceType = call.getString("spaceType") ?? ""
let id = call.getString("id") ?? ""
do {
let path = try getDbPath(peer: peer, spaceType: spaceType, id: id)
call.resolve(["path": path])
} catch {
call.reject("Failed to get space DB path", nil, error)
private let docStoragePool: DocStoragePool = newDocStoragePool()
public let identifier = "NbStorePlugin"
public let jsName = "NbStoreDocStorage"
public let pluginMethods: [CAPPluginMethod] = [
CAPPluginMethod(name: "connect", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "disconnect", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setSpaceId", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "pushUpdate", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getDocSnapshot", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setDocSnapshot", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getDocUpdates", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "markUpdatesMerged", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "deleteDoc", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getDocClocks", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getDocClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getBlob", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setBlob", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "deleteBlob", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "releaseBlobs", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "listBlobs", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerRemoteClocks", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerRemoteClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setPeerRemoteClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerPulledRemoteClocks", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerPulledRemoteClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setPeerPulledRemoteClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "getPeerPushedClocks", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "setPeerPushedClock", returnType: CAPPluginReturnPromise),
CAPPluginMethod(name: "clearClocks", returnType: CAPPluginReturnPromise),
]
@objc func connect(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let spaceId = try call.getStringEnsure("spaceId")
let spaceType = try call.getStringEnsure("spaceType")
let peer = try call.getStringEnsure("peer")
guard let documentDir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
call.reject("Failed to get document file urls")
return
}
let peerDir = documentDir.appending(path: "workspaces")
.appending(path: spaceType)
.appending(path:
peer
.replacing(#/[\/!@#$%^&*()+~`"':;,?<>|]/#, with: "_")
.replacing(/_+/, with: "_")
.replacing(/_+$/, with: ""))
try FileManager.default.createDirectory(atPath: peerDir.path(), withIntermediateDirectories: true)
let db = peerDir.appending(path: spaceId + ".db")
try await docStoragePool.connect(universalId: id, path: db.path())
call.resolve()
} catch {
call.reject("Failed to connect storage, \(error)", nil, error)
}
}
@objc func connect(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
try? await docStoragePool.connect(universalId: id)
}
@objc func disconnect(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
try await docStoragePool.disconnect(universalId: id)
call.resolve()
} catch {
call.reject("Failed to disconnect, \(error)", nil, error)
}
}
@objc func close(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
try? await docStoragePool.close(universalId: id)
}
@objc func setSpaceId(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let spaceId = try call.getStringEnsure("spaceId")
try await docStoragePool.setSpaceId(universalId: id, spaceId: spaceId)
call.resolve()
} catch {
call.reject("Failed to set space id, \(error)", nil, error)
}
}
@objc func isClosed(_ call: CAPPluginCall) {
let id = call.getString("id") ?? ""
call.resolve(["isClosed": docStoragePool.isClosed(universalId: id)])
}
@objc func pushUpdate(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let docId = try call.getStringEnsure("docId")
let data = try call.getStringEnsure("data")
let timestamp = try await docStoragePool.pushUpdate(universalId: id, docId: docId, update: data)
call.resolve(["timestamp": timestamp])
} catch {
call.reject("Failed to push update, \(error)", nil, error)
}
}
@objc func checkpoint(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
try? await docStoragePool.checkpoint(universalId: id)
}
@objc func validate(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let validate = (try? await docStoragePool.validate(universalId: id)) ?? false
call.resolve(["isValidate": validate])
}
@objc func setSpaceId(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let spaceId = call.getString("spaceId") ?? ""
do {
try await docStoragePool.setSpaceId(universalId: id, spaceId: spaceId)
call.resolve()
} catch {
call.reject("Failed to set space id", nil, error)
}
@objc func getDocSnapshot(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let docId = try call.getStringEnsure("docId")
if let record = try await docStoragePool.getDocSnapshot(universalId: id, docId: docId) {
call.resolve([
"docId": record.docId,
"bin": record.bin,
"timestamp": record.timestamp,
])
} else {
call.resolve()
}
} catch {
call.reject("Failed to get doc snapshot, \(error)", nil, error)
}
}
@objc func pushUpdate(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let docId = call.getString("docId") ?? ""
let data = call.getString("data") ?? ""
do {
let timestamp = try await docStoragePool.pushUpdate(universalId: id, docId: docId, update: data)
call.resolve(["timestamp": timestamp.timeIntervalSince1970])
} catch {
call.reject("Failed to push update", nil, error)
}
}
@objc func setDocSnapshot(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let docId = try call.getStringEnsure("docId")
let bin = try call.getStringEnsure("bin")
let timestamp = try call.getIntEnsure("timestamp")
let success = try await docStoragePool.setDocSnapshot(
universalId: id,
snapshot: DocRecord(docId: docId, bin: bin, timestamp: Int64(timestamp))
)
call.resolve(["success": success])
} catch {
call.reject("Failed to set doc snapshot, \(error)", nil, error)
}
}
@objc func getDocSnapshot(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let docId = call.getString("docId") ?? ""
do {
if let record = try await docStoragePool.getDocSnapshot(universalId: id, docId: docId) {
call.resolve([
"docId": record.docId,
"data": record.data,
"timestamp": record.timestamp.timeIntervalSince1970,
])
} else {
call.resolve()
}
} catch {
call.reject("Failed to get doc snapshot", nil, error)
}
}
@objc func getDocUpdates(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let docId = try call.getStringEnsure("docId")
let updates = try await docStoragePool.getDocUpdates(universalId: id, docId: docId)
let mapped = updates.map { [
"docId": $0.docId,
"timestamp": $0.timestamp,
"bin": $0.bin,
] }
call.resolve(["updates": mapped])
} catch {
call.reject("Failed to get doc updates, \(error)", nil, error)
}
}
@objc func setDocSnapshot(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let docId = call.getString("docId") ?? ""
let data = call.getString("data") ?? ""
let timestamp = Date()
do {
let success = try await docStoragePool.setDocSnapshot(
universalId: id,
snapshot: DocRecord(docId: docId, data: data, timestamp: timestamp)
)
call.resolve(["success": success])
} catch {
call.reject("Failed to set doc snapshot", nil, error)
}
}
@objc func markUpdatesMerged(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let docId = try call.getStringEnsure("docId")
let times = try call.getArrayEnsure("timestamps", Int64.self)
let count = try await docStoragePool.markUpdatesMerged(universalId: id, docId: docId, updates: times)
call.resolve(["count": count])
} catch {
call.reject("Failed to mark updates merged, \(error)", nil, error)
}
}
@objc func getDocUpdates(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let docId = call.getString("docId") ?? ""
do {
let updates = try await docStoragePool.getDocUpdates(universalId: id, docId: docId)
let mapped = updates.map { [
"docId": $0.docId,
"createdAt": $0.createdAt.timeIntervalSince1970,
"data": $0.data,
] }
call.resolve(["updates": mapped])
} catch {
call.reject("Failed to get doc updates", nil, error)
}
}
@objc func deleteDoc(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let docId = try call.getStringEnsure("docId")
try await docStoragePool.deleteDoc(universalId: id, docId: docId)
call.resolve()
} catch {
call.reject("Failed to delete doc, \(error)", nil, error)
}
}
@objc func markUpdatesMerged(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let docId = call.getString("docId") ?? ""
let times = call.getArray("timestamps", Double.self) ?? []
let dateArray = times.map { Date(timeIntervalSince1970: $0) }
do {
let count = try await docStoragePool.markUpdatesMerged(universalId: id, docId: docId, updates: dateArray)
call.resolve(["count": count])
} catch {
call.reject("Failed to mark updates merged", nil, error)
}
}
@objc func deleteDoc(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let docId = call.getString("docId") ?? ""
do {
try await docStoragePool.deleteDoc(universalId: id, docId: docId)
call.resolve()
} catch {
call.reject("Failed to delete doc", nil, error)
}
}
@objc func getDocClocks(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
}
@objc func getDocClocks(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let after = call.getInt("after")
do {
let docClocks = try await docStoragePool.getDocClocks(
universalId: id,
after: after != nil ? Date(timeIntervalSince1970: TimeInterval(after!)) : nil
)
let mapped = docClocks.map { [
"docId": $0.docId,
"timestamp": $0.timestamp.timeIntervalSince1970,
] }
call.resolve(["clocks": mapped])
} catch {
call.reject("Failed to get doc clocks", nil, error)
}
let docClocks = try await docStoragePool.getDocClocks(
universalId: id,
after: after != nil ? Int64(after!) : nil
)
let mapped = docClocks.map { [
"docId": $0.docId,
"timestamp": $0.timestamp,
] }
call.resolve(["clocks": mapped])
} catch {
call.reject("Failed to get doc clocks, \(error)", nil, error)
}
}
@objc func getDocClock(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let docId = call.getString("docId") ?? ""
do {
if let docClock = try await docStoragePool.getDocClock(universalId: id, docId: docId) {
call.resolve([
"docId": docClock.docId,
"timestamp": docClock.timestamp.timeIntervalSince1970,
])
} else {
call.resolve()
}
} catch {
call.reject("Failed to get doc clock for docId: \(docId)", nil, error)
}
}
@objc func getBlob(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let key = call.getString("key") ?? ""
if let blob = try? await docStoragePool.getBlob(universalId: id, key: key) {
call.resolve(["blob": blob])
}
@objc func getDocClock(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let docId = try call.getStringEnsure("docId")
if let docClock = try await docStoragePool.getDocClock(universalId: id, docId: docId) {
call.resolve([
"docId": docClock.docId,
"timestamp": docClock.timestamp,
])
} else {
call.resolve()
call.resolve()
}
} catch {
call.reject("Failed to get doc clock, \(error)", nil, error)
}
}
@objc func setBlob(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let key = call.getString("key") ?? ""
let data = call.getString("data") ?? ""
let mime = call.getString("mime") ?? ""
try? await docStoragePool.setBlob(universalId: id, blob: SetBlob(key: key, data: data, mime: mime))
}
@objc func getBlob(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let key = try call.getStringEnsure("key")
if let blob = try await docStoragePool.getBlob(universalId: id, key: key) {
call.resolve(["blob":[
"key": blob.key,
"data": blob.data,
"mime": blob.mime,
"size": blob.size,
"createdAt": blob.createdAt
]])
} else {
call.resolve()
}
} catch {
call.reject("Failed to get blob, \(error)", nil, error)
}
}
@objc func deleteBlob(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let key = call.getString("key") ?? ""
}
@objc func setBlob(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let key = try call.getStringEnsure("key")
let data = try call.getStringEnsure("data")
let mime = try call.getStringEnsure("mime")
try await docStoragePool.setBlob(universalId: id, blob: SetBlob(key: key, data: data, mime: mime))
call.resolve()
} catch {
call.reject("Failed to set blob, \(error)", nil, error)
}
}
}
@objc func deleteBlob(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let key = try call.getStringEnsure("key")
let permanently = call.getBool("permanently") ?? false
try? await docStoragePool.deleteBlob(universalId: id, key: key, permanently: permanently)
try await docStoragePool.deleteBlob(universalId: id, key: key, permanently: permanently)
call.resolve()
} catch {
call.reject("Failed to delete blob, \(error)", nil, error)
}
}
@objc func releaseBlobs(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
try? await docStoragePool.releaseBlobs(universalId: id)
}
@objc func releaseBlobs(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
try await docStoragePool.releaseBlobs(universalId: id)
call.resolve()
} catch {
call.reject("Failed to release blobs, \(error)", nil, error)
}
}
@objc func listBlobs(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
if let blobs = try? await docStoragePool.listBlobs(universalId: id) {
let mapped = blobs.map { [
"key": $0.key,
"size": $0.size,
"mime": $0.mime,
"createdAt": $0.createdAt.timeIntervalSince1970,
] }
call.resolve(["blobs": mapped])
} else {
call.resolve()
}
}
@objc func listBlobs(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let blobs = try await docStoragePool.listBlobs(universalId: id)
let mapped = blobs.map { [
"key": $0.key,
"size": $0.size,
"mime": $0.mime,
"createdAt": $0.createdAt,
] }
call.resolve(["blobs": mapped])
} catch {
call.reject("Failed to list blobs, \(error)", nil, error)
}
}
@objc func getPeerRemoteClocks(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let peer = call.getString("peer") ?? ""
do {
let clocks = try await docStoragePool.getPeerRemoteClocks(universalId: id, peer: peer)
let mapped = clocks.map { [
"docId": $0.docId,
"timestamp": $0.timestamp.timeIntervalSince1970,
] }
call.resolve(["clocks": mapped])
} catch {
call.reject("Failed to get peer remote clocks", nil, error)
}
}
@objc func getPeerRemoteClocks(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let peer = try call.getStringEnsure("peer")
let clocks = try await docStoragePool.getPeerRemoteClocks(universalId: id, peer: peer)
let mapped = clocks.map { [
"docId": $0.docId,
"timestamp": $0.timestamp,
] }
call.resolve(["clocks": mapped])
} catch {
call.reject("Failed to get peer remote clocks, \(error)", nil, error)
}
}
@objc func getPeerRemoteClock(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let peer = call.getString("peer") ?? ""
let docId = call.getString("docId") ?? ""
do {
let clock = try await docStoragePool.getPeerRemoteClock(universalId: id, peer: peer, docId: docId)
call.resolve([
"docId": clock.docId,
"timestamp": clock.timestamp.timeIntervalSince1970,
])
} catch {
call.reject("Failed to get peer remote clock", nil, error)
}
}
@objc func getPeerRemoteClock(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let peer = try call.getStringEnsure("peer")
let docId = try call.getStringEnsure("docId")
let clock = try await docStoragePool.getPeerRemoteClock(universalId: id, peer: peer, docId: docId)
call.resolve([
"docId": clock.docId,
"timestamp": clock.timestamp,
])
} catch {
call.reject("Failed to get peer remote clock, \(error)", nil, error)
}
}
@objc func setPeerRemoteClock(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let peer = call.getString("peer") ?? ""
let docId = call.getString("docId") ?? ""
let timestamp = call.getDouble("timestamp") ?? 0
do {
try await docStoragePool.setPeerRemoteClock(
universalId: id,
peer: peer,
docId: docId,
clock: Date(timeIntervalSince1970: timestamp)
)
call.resolve()
} catch {
call.reject("Failed to set peer remote clock", nil, error)
}
}
@objc func setPeerRemoteClock(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let peer = try call.getStringEnsure("peer")
let docId = try call.getStringEnsure("docId")
let timestamp = try call.getIntEnsure("timestamp")
try await docStoragePool.setPeerRemoteClock(
universalId: id,
peer: peer,
docId: docId,
clock: Int64(timestamp)
)
call.resolve()
} catch {
call.reject("Failed to set peer remote clock, \(error)", nil, error)
}
}
@objc func getPeerPulledRemoteClocks(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let peer = call.getString("peer") ?? ""
do {
let clocks = try await docStoragePool.getPeerPulledRemoteClocks(universalId: id, peer: peer)
let mapped = clocks.map { [
"docId": $0.docId,
"timestamp": $0.timestamp.timeIntervalSince1970,
] }
call.resolve(["clocks": mapped])
} catch {
call.reject("Failed to get peer pulled remote clocks", nil, error)
}
}
@objc func getPeerPulledRemoteClocks(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let peer = try call.getStringEnsure("peer")
let clocks = try await docStoragePool.getPeerPulledRemoteClocks(universalId: id, peer: peer)
let mapped = clocks.map { [
"docId": $0.docId,
"timestamp": $0.timestamp,
] }
call.resolve(["clocks": mapped])
} catch {
call.reject("Failed to get peer pulled remote clocks, \(error)", nil, error)
}
}
@objc func getPeerPulledRemoteClock(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let peer = call.getString("peer") ?? ""
let docId = call.getString("docId") ?? ""
do {
let clock = try await docStoragePool.getPeerPulledRemoteClock(universalId: id, peer: peer, docId: docId)
call.resolve([
"docId": clock.docId,
"timestamp": clock.timestamp.timeIntervalSince1970,
])
} catch {
call.reject("Failed to get peer pulled remote clock", nil, error)
}
}
@objc func getPeerPulledRemoteClock(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let peer = try call.getStringEnsure("peer")
let docId = try call.getStringEnsure("docId")
let clock = try await docStoragePool.getPeerPulledRemoteClock(universalId: id, peer: peer, docId: docId)
call.resolve([
"docId": clock.docId,
"timestamp": clock.timestamp,
])
} catch {
call.reject("Failed to get peer pulled remote clock, \(error)", nil, error)
}
}
@objc func setPeerPulledRemoteClock(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let peer = call.getString("peer") ?? ""
let docId = call.getString("docId") ?? ""
let timestamp = call.getDouble("timestamp") ?? 0
do {
try await docStoragePool.setPeerPulledRemoteClock(
universalId: id,
peer: peer,
docId: docId,
clock: Date(timeIntervalSince1970: timestamp)
)
call.resolve()
} catch {
call.reject("Failed to set peer pulled remote clock", nil, error)
}
}
@objc func setPeerPulledRemoteClock(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let peer = try call.getStringEnsure("peer")
let docId = try call.getStringEnsure("docId")
let timestamp = try call.getIntEnsure("timestamp")
try await docStoragePool.setPeerPulledRemoteClock(
universalId: id,
peer: peer,
docId: docId,
clock: Int64(timestamp)
)
call.resolve()
} catch {
call.reject("Failed to set peer pulled remote clock, \(error)", nil, error)
}
}
@objc func getPeerPushedClocks(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let peer = call.getString("peer") ?? ""
do {
let clocks = try await docStoragePool.getPeerPushedClocks(universalId: id, peer: peer)
let mapped = clocks.map { [
"docId": $0.docId,
"timestamp": $0.timestamp.timeIntervalSince1970,
] }
call.resolve(["clocks": mapped])
} catch {
call.reject("Failed to get peer pushed clocks", nil, error)
}
}
@objc func getPeerPushedClocks(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let peer = try call.getStringEnsure("peer")
let clocks = try await docStoragePool.getPeerPushedClocks(universalId: id, peer: peer)
let mapped = clocks.map { [
"docId": $0.docId,
"timestamp": $0.timestamp,
] }
call.resolve(["clocks": mapped])
} catch {
call.reject("Failed to get peer pushed clocks, \(error)", nil, error)
}
}
@objc func setPeerPushedClock(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
let peer = call.getString("peer") ?? ""
let docId = call.getString("docId") ?? ""
let timestamp = call.getDouble("timestamp") ?? 0
do {
try await docStoragePool.setPeerPushedClock(
universalId: id,
peer: peer,
docId: docId,
clock: Date(timeIntervalSince1970: timestamp)
)
call.resolve()
} catch {
call.reject("Failed to set peer pushed clock", nil, error)
}
}
@objc func setPeerPushedClock(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
let peer = try call.getStringEnsure("peer")
let docId = try call.getStringEnsure("docId")
let timestamp = try call.getIntEnsure("timestamp")
try await docStoragePool.setPeerPushedClock(
universalId: id,
peer: peer,
docId: docId,
clock: Int64(timestamp)
)
call.resolve()
} catch {
call.reject("Failed to set peer pushed clock, \(error)", nil, error)
}
}
@objc func clearClocks(_ call: CAPPluginCall) async {
let id = call.getString("id") ?? ""
do {
try await docStoragePool.clearClocks(universalId: id)
call.resolve()
} catch {
call.reject("Failed to clear clocks", nil, error)
}
}
@objc func clearClocks(_ call: CAPPluginCall) {
Task {
do {
let id = try call.getStringEnsure("id")
try await docStoragePool.clearClocks(universalId: id)
call.resolve()
} catch {
call.reject("Failed to clear clocks, \(error)", nil, error)
}
}
}
}
@@ -492,65 +492,25 @@ private struct FfiConverterString: FfiConverter {
}
}
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
private struct FfiConverterTimestamp: FfiConverterRustBuffer {
typealias SwiftType = Date
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> Date {
let seconds: Int64 = try readInt(&buf)
let nanoseconds: UInt32 = try readInt(&buf)
if seconds >= 0 {
let delta = Double(seconds) + (Double(nanoseconds) / 1.0e9)
return Date(timeIntervalSince1970: delta)
} else {
let delta = Double(seconds) - (Double(nanoseconds) / 1.0e9)
return Date(timeIntervalSince1970: delta)
}
}
public static func write(_ value: Date, into buf: inout [UInt8]) {
var delta = value.timeIntervalSince1970
var sign: Int64 = 1
if delta < 0 {
// The nanoseconds portion of the epoch offset must always be
// positive, to simplify the calculation we will use the absolute
// value of the offset.
sign = -1
delta = -delta
}
if delta.rounded(.down) > Double(Int64.max) {
fatalError("Timestamp overflow, exceeds max bounds supported by Uniffi")
}
let seconds = Int64(delta)
let nanoseconds = UInt32((delta - Double(seconds)) * 1.0e9)
writeInt(&buf, sign * seconds)
writeInt(&buf, nanoseconds)
}
}
public protocol DocStoragePoolProtocol: AnyObject {
func checkpoint(universalId: String) async throws
func clearClocks(universalId: String) async throws
func close(universalId: String) async throws
/**
* Initialize the database and run migrations.
*/
func connect(universalId: String) async throws
func connect(universalId: String, path: String) async throws
func deleteBlob(universalId: String, key: String, permanently: Bool) async throws
func deleteDoc(universalId: String, docId: String) async throws
func disconnect(universalId: String) async throws
func getBlob(universalId: String, key: String) async throws -> Blob?
func getDocClock(universalId: String, docId: String) async throws -> DocClock?
func getDocClocks(universalId: String, after: Date?) async throws -> [DocClock]
func getDocClocks(universalId: String, after: Int64?) async throws -> [DocClock]
func getDocSnapshot(universalId: String, docId: String) async throws -> DocRecord?
@@ -566,13 +526,11 @@ public protocol DocStoragePoolProtocol: AnyObject {
func getPeerRemoteClocks(universalId: String, peer: String) async throws -> [DocClock]
func isClosed(universalId: String) -> Bool
func listBlobs(universalId: String) async throws -> [ListedBlob]
func markUpdatesMerged(universalId: String, docId: String, updates: [Date]) async throws -> UInt32
func markUpdatesMerged(universalId: String, docId: String, updates: [Int64]) async throws -> UInt32
func pushUpdate(universalId: String, docId: String, update: String) async throws -> Date
func pushUpdate(universalId: String, docId: String, update: String) async throws -> Int64
func releaseBlobs(universalId: String) async throws
@@ -580,15 +538,13 @@ public protocol DocStoragePoolProtocol: AnyObject {
func setDocSnapshot(universalId: String, snapshot: DocRecord) async throws -> Bool
func setPeerPulledRemoteClock(universalId: String, peer: String, docId: String, clock: Date) async throws
func setPeerPulledRemoteClock(universalId: String, peer: String, docId: String, clock: Int64) async throws
func setPeerPushedClock(universalId: String, peer: String, docId: String, clock: Date) async throws
func setPeerPushedClock(universalId: String, peer: String, docId: String, clock: Int64) async throws
func setPeerRemoteClock(universalId: String, peer: String, docId: String, clock: Date) async throws
func setPeerRemoteClock(universalId: String, peer: String, docId: String, clock: Int64) async throws
func setSpaceId(universalId: String, spaceId: String) async throws
func validate(universalId: String) async throws -> Bool
}
open class DocStoragePool:
@@ -640,23 +596,6 @@ open class DocStoragePool:
try! rustCall { uniffi_affine_mobile_native_fn_free_docstoragepool(pointer, $0) }
}
open func checkpoint(universalId: String) async throws {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_checkpoint(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_void,
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
)
}
open func clearClocks(universalId: String) async throws {
return
try await uniffiRustCallAsync(
@@ -674,33 +613,16 @@ open class DocStoragePool:
)
}
open func close(universalId: String) async throws {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_close(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_void,
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
)
}
/**
* Initialize the database and run migrations.
*/
open func connect(universalId: String) async throws {
open func connect(universalId: String, path: String) async throws {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_connect(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId)
FfiConverterString.lower(universalId), FfiConverterString.lower(path)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_void,
@@ -745,6 +667,23 @@ open class DocStoragePool:
)
}
open func disconnect(universalId: String) async throws {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_disconnect(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_void,
completeFunc: ffi_affine_mobile_native_rust_future_complete_void,
freeFunc: ffi_affine_mobile_native_rust_future_free_void,
liftFunc: { $0 },
errorHandler: FfiConverterTypeUniffiError.lift
)
}
open func getBlob(universalId: String, key: String) async throws -> Blob? {
return
try await uniffiRustCallAsync(
@@ -779,13 +718,13 @@ open class DocStoragePool:
)
}
open func getDocClocks(universalId: String, after: Date?) async throws -> [DocClock] {
open func getDocClocks(universalId: String, after: Int64?) async throws -> [DocClock] {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_get_doc_clocks(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId), FfiConverterOptionTimestamp.lower(after)
FfiConverterString.lower(universalId), FfiConverterOptionInt64.lower(after)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_rust_buffer,
@@ -915,13 +854,6 @@ open class DocStoragePool:
)
}
open func isClosed(universalId: String) -> Bool {
return try! FfiConverterBool.lift(try! rustCall {
uniffi_affine_mobile_native_fn_method_docstoragepool_is_closed(self.uniffiClonePointer(),
FfiConverterString.lower(universalId), $0)
})
}
open func listBlobs(universalId: String) async throws -> [ListedBlob] {
return
try await uniffiRustCallAsync(
@@ -939,13 +871,13 @@ open class DocStoragePool:
)
}
open func markUpdatesMerged(universalId: String, docId: String, updates: [Date]) async throws -> UInt32 {
open func markUpdatesMerged(universalId: String, docId: String, updates: [Int64]) async throws -> UInt32 {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_mark_updates_merged(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId), FfiConverterString.lower(docId), FfiConverterSequenceTimestamp.lower(updates)
FfiConverterString.lower(universalId), FfiConverterString.lower(docId), FfiConverterSequenceInt64.lower(updates)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_u32,
@@ -956,7 +888,7 @@ open class DocStoragePool:
)
}
open func pushUpdate(universalId: String, docId: String, update: String) async throws -> Date {
open func pushUpdate(universalId: String, docId: String, update: String) async throws -> Int64 {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
@@ -965,10 +897,10 @@ open class DocStoragePool:
FfiConverterString.lower(universalId), FfiConverterString.lower(docId), FfiConverterString.lower(update)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_rust_buffer,
completeFunc: ffi_affine_mobile_native_rust_future_complete_rust_buffer,
freeFunc: ffi_affine_mobile_native_rust_future_free_rust_buffer,
liftFunc: FfiConverterTimestamp.lift,
pollFunc: ffi_affine_mobile_native_rust_future_poll_i64,
completeFunc: ffi_affine_mobile_native_rust_future_complete_i64,
freeFunc: ffi_affine_mobile_native_rust_future_free_i64,
liftFunc: FfiConverterInt64.lift,
errorHandler: FfiConverterTypeUniffiError.lift
)
}
@@ -1024,13 +956,13 @@ open class DocStoragePool:
)
}
open func setPeerPulledRemoteClock(universalId: String, peer: String, docId: String, clock: Date) async throws {
open func setPeerPulledRemoteClock(universalId: String, peer: String, docId: String, clock: Int64) async throws {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_pulled_remote_clock(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId), FfiConverterString.lower(peer), FfiConverterString.lower(docId), FfiConverterTimestamp.lower(clock)
FfiConverterString.lower(universalId), FfiConverterString.lower(peer), FfiConverterString.lower(docId), FfiConverterInt64.lower(clock)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_void,
@@ -1041,13 +973,13 @@ open class DocStoragePool:
)
}
open func setPeerPushedClock(universalId: String, peer: String, docId: String, clock: Date) async throws {
open func setPeerPushedClock(universalId: String, peer: String, docId: String, clock: Int64) async throws {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_pushed_clock(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId), FfiConverterString.lower(peer), FfiConverterString.lower(docId), FfiConverterTimestamp.lower(clock)
FfiConverterString.lower(universalId), FfiConverterString.lower(peer), FfiConverterString.lower(docId), FfiConverterInt64.lower(clock)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_void,
@@ -1058,13 +990,13 @@ open class DocStoragePool:
)
}
open func setPeerRemoteClock(universalId: String, peer: String, docId: String, clock: Date) async throws {
open func setPeerRemoteClock(universalId: String, peer: String, docId: String, clock: Int64) async throws {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_remote_clock(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId), FfiConverterString.lower(peer), FfiConverterString.lower(docId), FfiConverterTimestamp.lower(clock)
FfiConverterString.lower(universalId), FfiConverterString.lower(peer), FfiConverterString.lower(docId), FfiConverterInt64.lower(clock)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_void,
@@ -1091,23 +1023,6 @@ open class DocStoragePool:
errorHandler: FfiConverterTypeUniffiError.lift
)
}
open func validate(universalId: String) async throws -> Bool {
return
try await uniffiRustCallAsync(
rustFutureFunc: {
uniffi_affine_mobile_native_fn_method_docstoragepool_validate(
self.uniffiClonePointer(),
FfiConverterString.lower(universalId)
)
},
pollFunc: ffi_affine_mobile_native_rust_future_poll_i8,
completeFunc: ffi_affine_mobile_native_rust_future_complete_i8,
freeFunc: ffi_affine_mobile_native_rust_future_free_i8,
liftFunc: FfiConverterBool.lift,
errorHandler: FfiConverterTypeUniffiError.lift
)
}
}
#if swift(>=5.8)
@@ -1162,11 +1077,11 @@ public struct Blob {
public var data: String
public var mime: String
public var size: Int64
public var createdAt: Date
public var createdAt: Int64
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(key: String, data: String, mime: String, size: Int64, createdAt: Date) {
public init(key: String, data: String, mime: String, size: Int64, createdAt: Int64) {
self.key = key
self.data = data
self.mime = mime
@@ -1215,7 +1130,7 @@ public struct FfiConverterTypeBlob: FfiConverterRustBuffer {
data: FfiConverterString.read(from: &buf),
mime: FfiConverterString.read(from: &buf),
size: FfiConverterInt64.read(from: &buf),
createdAt: FfiConverterTimestamp.read(from: &buf)
createdAt: FfiConverterInt64.read(from: &buf)
)
}
@@ -1224,7 +1139,7 @@ public struct FfiConverterTypeBlob: FfiConverterRustBuffer {
FfiConverterString.write(value.data, into: &buf)
FfiConverterString.write(value.mime, into: &buf)
FfiConverterInt64.write(value.size, into: &buf)
FfiConverterTimestamp.write(value.createdAt, into: &buf)
FfiConverterInt64.write(value.createdAt, into: &buf)
}
}
@@ -1244,11 +1159,11 @@ public func FfiConverterTypeBlob_lower(_ value: Blob) -> RustBuffer {
public struct DocClock {
public var docId: String
public var timestamp: Date
public var timestamp: Int64
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(docId: String, timestamp: Date) {
public init(docId: String, timestamp: Int64) {
self.docId = docId
self.timestamp = timestamp
}
@@ -1279,13 +1194,13 @@ public struct FfiConverterTypeDocClock: FfiConverterRustBuffer {
return
try DocClock(
docId: FfiConverterString.read(from: &buf),
timestamp: FfiConverterTimestamp.read(from: &buf)
timestamp: FfiConverterInt64.read(from: &buf)
)
}
public static func write(_ value: DocClock, into buf: inout [UInt8]) {
FfiConverterString.write(value.docId, into: &buf)
FfiConverterTimestamp.write(value.timestamp, into: &buf)
FfiConverterInt64.write(value.timestamp, into: &buf)
}
}
@@ -1305,14 +1220,14 @@ public func FfiConverterTypeDocClock_lower(_ value: DocClock) -> RustBuffer {
public struct DocRecord {
public var docId: String
public var data: String
public var timestamp: Date
public var bin: String
public var timestamp: Int64
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(docId: String, data: String, timestamp: Date) {
public init(docId: String, bin: String, timestamp: Int64) {
self.docId = docId
self.data = data
self.bin = bin
self.timestamp = timestamp
}
}
@@ -1322,7 +1237,7 @@ extension DocRecord: Equatable, Hashable {
if lhs.docId != rhs.docId {
return false
}
if lhs.data != rhs.data {
if lhs.bin != rhs.bin {
return false
}
if lhs.timestamp != rhs.timestamp {
@@ -1333,7 +1248,7 @@ extension DocRecord: Equatable, Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(docId)
hasher.combine(data)
hasher.combine(bin)
hasher.combine(timestamp)
}
}
@@ -1346,15 +1261,15 @@ public struct FfiConverterTypeDocRecord: FfiConverterRustBuffer {
return
try DocRecord(
docId: FfiConverterString.read(from: &buf),
data: FfiConverterString.read(from: &buf),
timestamp: FfiConverterTimestamp.read(from: &buf)
bin: FfiConverterString.read(from: &buf),
timestamp: FfiConverterInt64.read(from: &buf)
)
}
public static func write(_ value: DocRecord, into buf: inout [UInt8]) {
FfiConverterString.write(value.docId, into: &buf)
FfiConverterString.write(value.data, into: &buf)
FfiConverterTimestamp.write(value.timestamp, into: &buf)
FfiConverterString.write(value.bin, into: &buf)
FfiConverterInt64.write(value.timestamp, into: &buf)
}
}
@@ -1374,15 +1289,15 @@ public func FfiConverterTypeDocRecord_lower(_ value: DocRecord) -> RustBuffer {
public struct DocUpdate {
public var docId: String
public var createdAt: Date
public var data: String
public var timestamp: Int64
public var bin: String
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(docId: String, createdAt: Date, data: String) {
public init(docId: String, timestamp: Int64, bin: String) {
self.docId = docId
self.createdAt = createdAt
self.data = data
self.timestamp = timestamp
self.bin = bin
}
}
@@ -1391,10 +1306,10 @@ extension DocUpdate: Equatable, Hashable {
if lhs.docId != rhs.docId {
return false
}
if lhs.createdAt != rhs.createdAt {
if lhs.timestamp != rhs.timestamp {
return false
}
if lhs.data != rhs.data {
if lhs.bin != rhs.bin {
return false
}
return true
@@ -1402,8 +1317,8 @@ extension DocUpdate: Equatable, Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(docId)
hasher.combine(createdAt)
hasher.combine(data)
hasher.combine(timestamp)
hasher.combine(bin)
}
}
@@ -1415,15 +1330,15 @@ public struct FfiConverterTypeDocUpdate: FfiConverterRustBuffer {
return
try DocUpdate(
docId: FfiConverterString.read(from: &buf),
createdAt: FfiConverterTimestamp.read(from: &buf),
data: FfiConverterString.read(from: &buf)
timestamp: FfiConverterInt64.read(from: &buf),
bin: FfiConverterString.read(from: &buf)
)
}
public static func write(_ value: DocUpdate, into buf: inout [UInt8]) {
FfiConverterString.write(value.docId, into: &buf)
FfiConverterTimestamp.write(value.createdAt, into: &buf)
FfiConverterString.write(value.data, into: &buf)
FfiConverterInt64.write(value.timestamp, into: &buf)
FfiConverterString.write(value.bin, into: &buf)
}
}
@@ -1445,11 +1360,11 @@ public struct ListedBlob {
public var key: String
public var size: Int64
public var mime: String
public var createdAt: Date
public var createdAt: Int64
// Default memberwise initializers are never public by default, so we
// declare one manually.
public init(key: String, size: Int64, mime: String, createdAt: Date) {
public init(key: String, size: Int64, mime: String, createdAt: Int64) {
self.key = key
self.size = size
self.mime = mime
@@ -1492,7 +1407,7 @@ public struct FfiConverterTypeListedBlob: FfiConverterRustBuffer {
key: FfiConverterString.read(from: &buf),
size: FfiConverterInt64.read(from: &buf),
mime: FfiConverterString.read(from: &buf),
createdAt: FfiConverterTimestamp.read(from: &buf)
createdAt: FfiConverterInt64.read(from: &buf)
)
}
@@ -1500,7 +1415,7 @@ public struct FfiConverterTypeListedBlob: FfiConverterRustBuffer {
FfiConverterString.write(value.key, into: &buf)
FfiConverterInt64.write(value.size, into: &buf)
FfiConverterString.write(value.mime, into: &buf)
FfiConverterTimestamp.write(value.createdAt, into: &buf)
FfiConverterInt64.write(value.createdAt, into: &buf)
}
}
@@ -1588,21 +1503,11 @@ public func FfiConverterTypeSetBlob_lower(_ value: SetBlob) -> RustBuffer {
}
public enum UniffiError {
case GetUserDocumentDirectoryFailed
case CreateAffineDirFailed(String
)
case EmptyDocStoragePath
case EmptySpaceId
case SqlxError(String
case Err(String
)
case Base64DecodingError(String
)
case InvalidUniversalId(String
)
case InvalidSpaceType(String
)
case ConcatSpaceDirFailed(String
)
case TimestampDecodingError
}
#if swift(>=5.8)
@@ -1614,65 +1519,29 @@ public struct FfiConverterTypeUniffiError: FfiConverterRustBuffer {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> UniffiError {
let variant: Int32 = try readInt(&buf)
switch variant {
case 1: return .GetUserDocumentDirectoryFailed
case 2: return try .CreateAffineDirFailed(
case 1: return try .Err(
FfiConverterString.read(from: &buf)
)
case 3: return .EmptyDocStoragePath
case 4: return .EmptySpaceId
case 5: return try .SqlxError(
FfiConverterString.read(from: &buf)
)
case 6: return try .Base64DecodingError(
FfiConverterString.read(from: &buf)
)
case 7: return try .InvalidUniversalId(
FfiConverterString.read(from: &buf)
)
case 8: return try .InvalidSpaceType(
FfiConverterString.read(from: &buf)
)
case 9: return try .ConcatSpaceDirFailed(
case 2: return try .Base64DecodingError(
FfiConverterString.read(from: &buf)
)
case 3: return .TimestampDecodingError
default: throw UniffiInternalError.unexpectedEnumCase
}
}
public static func write(_ value: UniffiError, into buf: inout [UInt8]) {
switch value {
case .GetUserDocumentDirectoryFailed:
case let .Err(v1):
writeInt(&buf, Int32(1))
case let .CreateAffineDirFailed(v1):
writeInt(&buf, Int32(2))
FfiConverterString.write(v1, into: &buf)
case .EmptyDocStoragePath:
writeInt(&buf, Int32(3))
case .EmptySpaceId:
writeInt(&buf, Int32(4))
case let .SqlxError(v1):
writeInt(&buf, Int32(5))
FfiConverterString.write(v1, into: &buf)
case let .Base64DecodingError(v1):
writeInt(&buf, Int32(6))
writeInt(&buf, Int32(2))
FfiConverterString.write(v1, into: &buf)
case let .InvalidUniversalId(v1):
writeInt(&buf, Int32(7))
FfiConverterString.write(v1, into: &buf)
case let .InvalidSpaceType(v1):
writeInt(&buf, Int32(8))
FfiConverterString.write(v1, into: &buf)
case let .ConcatSpaceDirFailed(v1):
writeInt(&buf, Int32(9))
FfiConverterString.write(v1, into: &buf)
case .TimestampDecodingError:
writeInt(&buf, Int32(3))
}
}
}
@@ -1688,8 +1557,8 @@ extension UniffiError: Foundation.LocalizedError {
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
private struct FfiConverterOptionTimestamp: FfiConverterRustBuffer {
typealias SwiftType = Date?
private struct FfiConverterOptionInt64: FfiConverterRustBuffer {
typealias SwiftType = Int64?
public static func write(_ value: SwiftType, into buf: inout [UInt8]) {
guard let value = value else {
@@ -1697,13 +1566,13 @@ private struct FfiConverterOptionTimestamp: FfiConverterRustBuffer {
return
}
writeInt(&buf, Int8(1))
FfiConverterTimestamp.write(value, into: &buf)
FfiConverterInt64.write(value, into: &buf)
}
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> SwiftType {
switch try readInt(&buf) as Int8 {
case 0: return nil
case 1: return try FfiConverterTimestamp.read(from: &buf)
case 1: return try FfiConverterInt64.read(from: &buf)
default: throw UniffiInternalError.unexpectedOptionalTag
}
}
@@ -1784,23 +1653,23 @@ private struct FfiConverterOptionTypeDocRecord: FfiConverterRustBuffer {
#if swift(>=5.8)
@_documentation(visibility: private)
#endif
private struct FfiConverterSequenceTimestamp: FfiConverterRustBuffer {
typealias SwiftType = [Date]
private struct FfiConverterSequenceInt64: FfiConverterRustBuffer {
typealias SwiftType = [Int64]
public static func write(_ value: [Date], into buf: inout [UInt8]) {
public static func write(_ value: [Int64], into buf: inout [UInt8]) {
let len = Int32(value.count)
writeInt(&buf, len)
for item in value {
FfiConverterTimestamp.write(item, into: &buf)
FfiConverterInt64.write(item, into: &buf)
}
}
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [Date] {
public static func read(from buf: inout (data: Data, offset: Data.Index)) throws -> [Int64] {
let len: Int32 = try readInt(&buf)
var seq = [Date]()
var seq = [Int64]()
seq.reserveCapacity(Int(len))
for _ in 0 ..< len {
try seq.append(FfiConverterTimestamp.read(from: &buf))
try seq.append(FfiConverterInt64.read(from: &buf))
}
return seq
}
@@ -1928,16 +1797,6 @@ private func uniffiFutureContinuationCallback(handle: UInt64, pollResult: Int8)
}
}
public func getDbPath(peer: String, spaceType: String, id: String) throws -> String {
return try FfiConverterString.lift(rustCallWithError(FfiConverterTypeUniffiError.lift) {
uniffi_affine_mobile_native_fn_func_get_db_path(
FfiConverterString.lower(peer),
FfiConverterString.lower(spaceType),
FfiConverterString.lower(id), $0
)
})
}
public func hashcashMint(resource: String, bits: UInt32) -> String {
return try! FfiConverterString.lift(try! rustCall {
uniffi_affine_mobile_native_fn_func_hashcash_mint(
@@ -1947,6 +1806,13 @@ public func hashcashMint(resource: String, bits: UInt32) -> String {
})
}
public func newDocStoragePool() -> DocStoragePool {
return try! FfiConverterTypeDocStoragePool.lift(try! rustCall {
uniffi_affine_mobile_native_fn_func_new_doc_storage_pool($0
)
})
}
private enum InitializationResult {
case ok
case contractVersionMismatch
@@ -1963,22 +1829,16 @@ private var initializationResult: InitializationResult = {
if bindings_contract_version != scaffolding_contract_version {
return InitializationResult.contractVersionMismatch
}
if uniffi_affine_mobile_native_checksum_func_get_db_path() != 65350 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_func_hashcash_mint() != 23633 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_checkpoint() != 36299 {
if uniffi_affine_mobile_native_checksum_func_new_doc_storage_pool() != 32882 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_clear_clocks() != 51151 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_close() != 46846 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_connect() != 57961 {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_connect() != 19047 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_delete_blob() != 53695 {
@@ -1987,13 +1847,16 @@ private var initializationResult: InitializationResult = {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_delete_doc() != 4005 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_disconnect() != 20410 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_get_blob() != 56927 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_get_doc_clock() != 48394 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_get_doc_clocks() != 23822 {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_get_doc_clocks() != 46082 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_get_doc_snapshot() != 31220 {
@@ -2017,16 +1880,13 @@ private var initializationResult: InitializationResult = {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_get_peer_remote_clocks() != 14523 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_is_closed() != 40091 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_list_blobs() != 6777 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_mark_updates_merged() != 26982 {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_mark_updates_merged() != 42713 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_push_update() != 54572 {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_push_update() != 20688 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_release_blobs() != 2203 {
@@ -2038,21 +1898,18 @@ private var initializationResult: InitializationResult = {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_set_doc_snapshot() != 5287 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_set_peer_pulled_remote_clock() != 40733 {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_set_peer_pulled_remote_clock() != 33923 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_set_peer_pushed_clock() != 15697 {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_set_peer_pushed_clock() != 16565 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_set_peer_remote_clock() != 57108 {
if uniffi_affine_mobile_native_checksum_method_docstoragepool_set_peer_remote_clock() != 46506 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_set_space_id() != 21955 {
return InitializationResult.apiChecksumMismatch
}
if uniffi_affine_mobile_native_checksum_method_docstoragepool_validate() != 17232 {
return InitializationResult.apiChecksumMismatch
}
return InitializationResult.ok
}()
@@ -261,24 +261,14 @@ void*_Nonnull uniffi_affine_mobile_native_fn_clone_docstoragepool(void*_Nonnull
void uniffi_affine_mobile_native_fn_free_docstoragepool(void*_Nonnull ptr, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_CHECKPOINT
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_CHECKPOINT
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_checkpoint(void*_Nonnull ptr, RustBuffer universal_id
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_CLEAR_CLOCKS
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_CLEAR_CLOCKS
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_clear_clocks(void*_Nonnull ptr, RustBuffer universal_id
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_CLOSE
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_CLOSE
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_close(void*_Nonnull ptr, RustBuffer universal_id
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_CONNECT
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_CONNECT
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_connect(void*_Nonnull ptr, RustBuffer universal_id
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_connect(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer path
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_DELETE_BLOB
@@ -291,6 +281,11 @@ uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_delete_blob(void*_
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_delete_doc(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer doc_id
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_DISCONNECT
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_DISCONNECT
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_disconnect(void*_Nonnull ptr, RustBuffer universal_id
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_GET_BLOB
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_GET_BLOB
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_get_blob(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer key
@@ -341,11 +336,6 @@ uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_get_peer_remote_cl
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_get_peer_remote_clocks(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer peer
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_IS_CLOSED
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_IS_CLOSED
int8_t uniffi_affine_mobile_native_fn_method_docstoragepool_is_closed(void*_Nonnull ptr, RustBuffer universal_id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_LIST_BLOBS
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_LIST_BLOBS
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_list_blobs(void*_Nonnull ptr, RustBuffer universal_id
@@ -378,17 +368,17 @@ uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_doc_snapshot(v
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_SET_PEER_PULLED_REMOTE_CLOCK
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_SET_PEER_PULLED_REMOTE_CLOCK
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_pulled_remote_clock(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer peer, RustBuffer doc_id, RustBuffer clock
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_pulled_remote_clock(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer peer, RustBuffer doc_id, int64_t clock
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_SET_PEER_PUSHED_CLOCK
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_SET_PEER_PUSHED_CLOCK
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_pushed_clock(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer peer, RustBuffer doc_id, RustBuffer clock
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_pushed_clock(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer peer, RustBuffer doc_id, int64_t clock
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_SET_PEER_REMOTE_CLOCK
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_SET_PEER_REMOTE_CLOCK
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_remote_clock(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer peer, RustBuffer doc_id, RustBuffer clock
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_remote_clock(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer peer, RustBuffer doc_id, int64_t clock
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_SET_SPACE_ID
@@ -396,19 +386,15 @@ uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_peer_remote_cl
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_set_space_id(void*_Nonnull ptr, RustBuffer universal_id, RustBuffer space_id
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_VALIDATE
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_METHOD_DOCSTORAGEPOOL_VALIDATE
uint64_t uniffi_affine_mobile_native_fn_method_docstoragepool_validate(void*_Nonnull ptr, RustBuffer universal_id
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_FUNC_GET_DB_PATH
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_FUNC_GET_DB_PATH
RustBuffer uniffi_affine_mobile_native_fn_func_get_db_path(RustBuffer peer, RustBuffer space_type, RustBuffer id, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_FUNC_HASHCASH_MINT
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_FUNC_HASHCASH_MINT
RustBuffer uniffi_affine_mobile_native_fn_func_hashcash_mint(RustBuffer resource, uint32_t bits, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_FUNC_NEW_DOC_STORAGE_POOL
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_FN_FUNC_NEW_DOC_STORAGE_POOL
void*_Nonnull uniffi_affine_mobile_native_fn_func_new_doc_storage_pool(RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_AFFINE_MOBILE_NATIVE_RUSTBUFFER_ALLOC
@@ -689,12 +675,6 @@ void ffi_affine_mobile_native_rust_future_free_void(uint64_t handle
#ifndef UNIFFI_FFIDEF_FFI_AFFINE_MOBILE_NATIVE_RUST_FUTURE_COMPLETE_VOID
#define UNIFFI_FFIDEF_FFI_AFFINE_MOBILE_NATIVE_RUST_FUTURE_COMPLETE_VOID
void ffi_affine_mobile_native_rust_future_complete_void(uint64_t handle, RustCallStatus *_Nonnull out_status
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_FUNC_GET_DB_PATH
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_FUNC_GET_DB_PATH
uint16_t uniffi_affine_mobile_native_checksum_func_get_db_path(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_FUNC_HASHCASH_MINT
@@ -703,9 +683,9 @@ uint16_t uniffi_affine_mobile_native_checksum_func_hashcash_mint(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_CHECKPOINT
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_CHECKPOINT
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_checkpoint(void
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_FUNC_NEW_DOC_STORAGE_POOL
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_FUNC_NEW_DOC_STORAGE_POOL
uint16_t uniffi_affine_mobile_native_checksum_func_new_doc_storage_pool(void
);
#endif
@@ -713,12 +693,6 @@ uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_checkpoint(v
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_CLEAR_CLOCKS
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_clear_clocks(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_CLOSE
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_CLOSE
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_close(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_CONNECT
@@ -737,6 +711,12 @@ uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_delete_blob(
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_DELETE_DOC
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_delete_doc(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_DISCONNECT
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_DISCONNECT
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_disconnect(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_GET_BLOB
@@ -797,12 +777,6 @@ uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_get_peer_rem
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_GET_PEER_REMOTE_CLOCKS
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_get_peer_remote_clocks(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_IS_CLOSED
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_IS_CLOSED
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_is_closed(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_LIST_BLOBS
@@ -863,12 +837,6 @@ uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_set_peer_rem
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_SET_SPACE_ID
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_set_space_id(void
);
#endif
#ifndef UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_VALIDATE
#define UNIFFI_FFIDEF_UNIFFI_AFFINE_MOBILE_NATIVE_CHECKSUM_METHOD_DOCSTORAGEPOOL_VALIDATE
uint16_t uniffi_affine_mobile_native_checksum_method_docstoragepool_validate(void
);
#endif
#ifndef UNIFFI_FFIDEF_FFI_AFFINE_MOBILE_NATIVE_UNIFFI_CONTRACT_VERSION
@@ -1,33 +0,0 @@
import { type BlobRecord, BlobStorageBase, share } from '@affine/nbstore';
import { NativeDBConnection } from './db';
export class SqliteBlobStorage extends BlobStorageBase {
override connection = share(
new NativeDBConnection(this.peer, this.spaceType, this.spaceId)
);
get db() {
return this.connection.inner;
}
override async get(key: string) {
return this.db.getBlob(key);
}
override async set(blob: BlobRecord) {
await this.db.setBlob(blob);
}
override async delete(key: string, permanently: boolean) {
await this.db.deleteBlob(key, permanently);
}
override async release() {
await this.db.releaseBlobs();
}
override async list() {
return this.db.listBlobs();
}
}
@@ -1,60 +0,0 @@
import type { DocStorage } from '@affine/native';
import {
AutoReconnectConnection,
isValidSpaceType,
type SpaceType,
universalId,
} from '@affine/nbstore';
import { NativeDocStorage, NbStoreDocStorage } from './plugin';
export class NativeDBConnection extends AutoReconnectConnection<DocStorage> {
private readonly universalId: string;
constructor(
private readonly peer: string,
private readonly type: SpaceType,
private readonly id: string
) {
super();
if (!isValidSpaceType(type)) {
throw new TypeError(`Invalid space type: ${type}`);
}
this.universalId = universalId({
peer: peer,
type: type,
id: id,
});
}
async getDBPath() {
const { path } = await NbStoreDocStorage.getSpaceDBPath({
peer: this.peer,
spaceType: this.type,
id: this.id,
});
return path;
}
override get shareId(): string {
return `sqlite:${this.peer}:${this.type}:${this.id}`;
}
override async doConnect() {
const conn = new NativeDocStorage(this.universalId);
await conn.connect();
console.info('[nbstore] connection established', this.shareId);
return conn;
}
override doDisconnect(conn: NativeDocStorage) {
conn
.close()
.then(() => {
console.info('[nbstore] connection closed', this.shareId);
})
.catch(err => {
console.error('[nbstore] connection close failed', this.shareId, err);
});
}
}
@@ -27,17 +27,13 @@ export interface DocClock {
}
export interface NbStorePlugin {
getSpaceDBPath: (options: {
peer: string;
spaceType: string;
connect: (options: {
id: string;
}) => Promise<{ path: string }>;
create: (options: { id: string; path: string }) => Promise<void>;
connect: (options: { id: string }) => Promise<void>;
close: (options: { id: string }) => Promise<void>;
isClosed: (options: { id: string }) => Promise<{ isClosed: boolean }>;
checkpoint: (options: { id: string }) => Promise<void>;
validate: (options: { id: string }) => Promise<{ isValidate: boolean }>;
spaceId: string;
spaceType: string;
peer: string;
}) => Promise<void>;
disconnect: (options: { id: string }) => Promise<void>;
setSpaceId: (options: { id: string; spaceId: string }) => Promise<void>;
pushUpdate: (options: {
@@ -49,7 +45,7 @@ export interface NbStorePlugin {
| {
docId: string;
// base64 encoded data
data: string;
bin: string;
timestamp: number;
}
| undefined
@@ -57,23 +53,24 @@ export interface NbStorePlugin {
setDocSnapshot: (options: {
id: string;
docId: string;
data: string;
bin: string;
timestamp: number;
}) => Promise<{ success: boolean }>;
getDocUpdates: (options: { id: string; docId: string }) => Promise<
{
getDocUpdates: (options: { id: string; docId: string }) => Promise<{
updates: {
docId: string;
createdAt: number;
timestamp: number;
// base64 encoded data
data: string;
}[]
>;
bin: string;
}[];
}>;
markUpdatesMerged: (options: {
id: string;
docId: string;
timestamps: number[];
}) => Promise<{ count: number }>;
deleteDoc: (options: { id: string; docId: string }) => Promise<void>;
getDocClocks: (options: { id: string; after: number }) => Promise<
getDocClocks: (options: { id: string; after?: number | null }) => Promise<
{
docId: string;
timestamp: number;
@@ -1,83 +0,0 @@
import {
type DocClocks,
type DocRecord,
DocStorageBase,
type DocUpdate,
share,
} from '@affine/nbstore';
import { NativeDBConnection } from './db';
export class SqliteDocStorage extends DocStorageBase {
override connection = share(
new NativeDBConnection(this.peer, this.spaceType, this.spaceId)
);
get db() {
return this.connection.inner;
}
override async pushDocUpdate(update: DocUpdate) {
const timestamp = await this.db.pushUpdate(update.docId, update.bin);
return { docId: update.docId, timestamp };
}
override async deleteDoc(docId: string) {
await this.db.deleteDoc(docId);
}
override async getDocTimestamps(after?: Date) {
const clocks = await this.db.getDocClocks(after);
return clocks.reduce((ret, cur) => {
ret[cur.docId] = cur.timestamp;
return ret;
}, {} as DocClocks);
}
override async getDocTimestamp(docId: string) {
return this.db.getDocClock(docId);
}
protected override async getDocSnapshot(docId: string) {
const snapshot = await this.db.getDocSnapshot(docId);
if (!snapshot) {
return null;
}
return {
docId,
bin: snapshot.data,
timestamp: snapshot.timestamp,
};
}
protected override async setDocSnapshot(
snapshot: DocRecord
): Promise<boolean> {
return this.db.setDocSnapshot({
docId: snapshot.docId,
data: Buffer.from(snapshot.bin),
timestamp: new Date(snapshot.timestamp),
});
}
protected override async getDocUpdates(docId: string) {
return this.db.getDocUpdates(docId).then(updates =>
updates.map(update => ({
docId,
bin: update.data,
timestamp: update.createdAt,
}))
);
}
protected override markUpdatesMerged(docId: string, updates: DocRecord[]) {
return this.db.markUpdatesMerged(
docId,
updates.map(update => update.timestamp)
);
}
}
@@ -1,128 +0,0 @@
import {
type BlobRecord,
type DocClock,
type DocUpdate,
} from '@affine/nbstore';
import { ensureStorage, getStorage } from './storage';
export const nbstoreHandlers = {
connect: async (id: string) => {
await ensureStorage(id);
},
close: async (id: string) => {
const store = getStorage(id);
if (store) {
store.disconnect();
// The store may be shared with other tabs, so we don't delete it from cache
// the underlying connection will handle the close correctly
// STORE_CACHE.delete(`${spaceType}:${spaceId}`);
}
},
pushDocUpdate: async (id: string, update: DocUpdate) => {
const store = await ensureStorage(id);
return store.get('doc').pushDocUpdate(update);
},
getDoc: async (id: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('doc').getDoc(docId);
},
deleteDoc: async (id: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('doc').deleteDoc(docId);
},
getDocTimestamps: async (id: string, after?: Date) => {
const store = await ensureStorage(id);
return store.get('doc').getDocTimestamps(after);
},
getDocTimestamp: async (id: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('doc').getDocTimestamp(docId);
},
setBlob: async (id: string, blob: BlobRecord) => {
const store = await ensureStorage(id);
return store.get('blob').set(blob);
},
getBlob: async (id: string, key: string) => {
const store = await ensureStorage(id);
return store.get('blob').get(key);
},
deleteBlob: async (id: string, key: string, permanently: boolean) => {
const store = await ensureStorage(id);
return store.get('blob').delete(key, permanently);
},
listBlobs: async (id: string) => {
const store = await ensureStorage(id);
return store.get('blob').list();
},
releaseBlobs: async (id: string) => {
const store = await ensureStorage(id);
return store.get('blob').release();
},
getPeerRemoteClocks: async (id: string, peer: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerRemoteClocks(peer);
},
getPeerRemoteClock: async (id: string, peer: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerRemoteClock(peer, docId);
},
setPeerRemoteClock: async (id: string, peer: string, clock: DocClock) => {
const store = await ensureStorage(id);
return store.get('sync').setPeerRemoteClock(peer, clock);
},
getPeerPulledRemoteClocks: async (id: string, peer: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerPulledRemoteClocks(peer);
},
getPeerPulledRemoteClock: async (id: string, peer: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerPulledRemoteClock(peer, docId);
},
setPeerPulledRemoteClock: async (
id: string,
peer: string,
clock: DocClock
) => {
const store = await ensureStorage(id);
return store.get('sync').setPeerPulledRemoteClock(peer, clock);
},
getPeerPushedClocks: async (id: string, peer: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerPushedClocks(peer);
},
getPeerPushedClock: async (id: string, peer: string, docId: string) => {
const store = await ensureStorage(id);
return store.get('sync').getPeerPushedClock(peer, docId);
},
setPeerPushedClock: async (id: string, peer: string, clock: DocClock) => {
const store = await ensureStorage(id);
return store.get('sync').setPeerPushedClock(peer, clock);
},
clearClocks: async (id: string) => {
const store = await ensureStorage(id);
return store.get('sync').clearClocks();
},
};
@@ -1,5 +1,304 @@
import {
base64ToUint8Array,
uint8ArrayToBase64,
} from '@affine/core/modules/workspace-engine';
import {
type BlobRecord,
type DocClock,
type DocRecord,
type ListedBlobRecord,
parseUniversalId,
} from '@affine/nbstore';
import { type NativeDBApis } from '@affine/nbstore/sqlite';
import { registerPlugin } from '@capacitor/core';
import type { NbStorePlugin } from './definitions';
export * from './definitions';
export { nbstoreHandlers } from './handlers';
export { NbStoreDocStorage } from './plugin';
export * from './storage';
export { universalId } from '@affine/nbstore';
export const NbStore = registerPlugin<NbStorePlugin>('NbStoreDocStorage');
export const NbStoreNativeDBApis: NativeDBApis = {
connect: async function (id: string): Promise<void> {
const { peer, type, id: spaceId } = parseUniversalId(id);
return await NbStore.connect({ id, spaceId, spaceType: type, peer });
},
disconnect: function (id: string): Promise<void> {
return NbStore.disconnect({ id });
},
pushUpdate: async function (
id: string,
docId: string,
update: Uint8Array
): Promise<Date> {
const { timestamp } = await NbStore.pushUpdate({
id,
docId,
data: await uint8ArrayToBase64(update),
});
return new Date(timestamp);
},
getDocSnapshot: async function (
id: string,
docId: string
): Promise<DocRecord | null> {
const snapshot = await NbStore.getDocSnapshot({ id, docId });
return snapshot
? {
bin: base64ToUint8Array(snapshot.bin),
docId: snapshot.docId,
timestamp: new Date(snapshot.timestamp),
}
: null;
},
setDocSnapshot: async function (
id: string,
snapshot: DocRecord
): Promise<boolean> {
const { success } = await NbStore.setDocSnapshot({
id,
docId: snapshot.docId,
bin: await uint8ArrayToBase64(snapshot.bin),
timestamp: snapshot.timestamp.getTime(),
});
return success;
},
getDocUpdates: async function (
id: string,
docId: string
): Promise<DocRecord[]> {
const { updates } = await NbStore.getDocUpdates({ id, docId });
return updates.map(update => ({
bin: base64ToUint8Array(update.bin),
docId: update.docId,
timestamp: new Date(update.timestamp),
}));
},
markUpdatesMerged: async function (
id: string,
docId: string,
updates: Date[]
): Promise<number> {
const { count } = await NbStore.markUpdatesMerged({
id,
docId,
timestamps: updates.map(t => t.getTime()),
});
return count;
},
deleteDoc: async function (id: string, docId: string): Promise<void> {
await NbStore.deleteDoc({
id,
docId,
});
},
getDocClocks: async function (
id: string,
after?: Date | undefined | null
): Promise<DocClock[]> {
const clocks = await NbStore.getDocClocks({
id,
after: after?.getTime(),
});
return clocks.map(c => ({
docId: c.docId,
timestamp: new Date(c.timestamp),
}));
},
getDocClock: async function (
id: string,
docId: string
): Promise<DocClock | null> {
const clock = await NbStore.getDocClock({
id,
docId,
});
return clock
? {
timestamp: new Date(clock.timestamp),
docId: clock.docId,
}
: null;
},
getBlob: async function (
id: string,
key: string
): Promise<BlobRecord | null> {
const record = await NbStore.getBlob({
id,
key,
});
return record
? {
data: base64ToUint8Array(record.data),
key: record.key,
mime: record.mime,
createdAt: new Date(record.createdAt),
}
: null;
},
setBlob: async function (id: string, blob: BlobRecord): Promise<void> {
await NbStore.setBlob({
id,
data: await uint8ArrayToBase64(blob.data),
key: blob.key,
mime: blob.mime,
});
},
deleteBlob: async function (
id: string,
key: string,
permanently: boolean
): Promise<void> {
await NbStore.deleteBlob({
id,
key,
permanently,
});
},
releaseBlobs: async function (id: string): Promise<void> {
await NbStore.releaseBlobs({
id,
});
},
listBlobs: async function (id: string): Promise<ListedBlobRecord[]> {
const listed = await NbStore.listBlobs({
id,
});
return listed.map(b => ({
key: b.key,
mime: b.mime,
size: b.size,
createdAt: new Date(b.createdAt),
}));
},
getPeerRemoteClocks: async function (
id: string,
peer: string
): Promise<DocClock[]> {
const clocks = await NbStore.getPeerRemoteClocks({
id,
peer,
});
return clocks.map(c => ({
docId: c.docId,
timestamp: new Date(c.timestamp),
}));
},
getPeerRemoteClock: async function (
id: string,
peer: string,
docId: string
): Promise<DocClock> {
const clock = await NbStore.getPeerRemoteClock({
id,
peer,
docId,
});
return {
docId: clock.docId,
timestamp: new Date(clock.timestamp),
};
},
setPeerRemoteClock: async function (
id: string,
peer: string,
docId: string,
clock: Date
): Promise<void> {
await NbStore.setPeerRemoteClock({
id,
peer,
docId,
clock: clock.getTime(),
});
},
getPeerPulledRemoteClocks: async function (
id: string,
peer: string
): Promise<DocClock[]> {
const clocks = await NbStore.getPeerPulledRemoteClocks({
id,
peer,
});
return clocks.map(c => ({
docId: c.docId,
timestamp: new Date(c.timestamp),
}));
},
getPeerPulledRemoteClock: async function (
id: string,
peer: string,
docId: string
): Promise<DocClock> {
const clock = await NbStore.getPeerPulledRemoteClock({
id,
peer,
docId,
});
return {
docId: clock.docId,
timestamp: new Date(clock.timestamp),
};
},
setPeerPulledRemoteClock: async function (
id: string,
peer: string,
docId: string,
clock: Date
): Promise<void> {
await NbStore.setPeerPulledRemoteClock({
id,
peer,
docId,
clock: clock.getTime(),
});
},
getPeerPushedClocks: async function (
id: string,
peer: string
): Promise<DocClock[]> {
const clocks = await NbStore.getPeerPushedClocks({
id,
peer,
});
return clocks.map(c => ({
docId: c.docId,
timestamp: new Date(c.timestamp),
}));
},
getPeerPushedClock: async function (
id: string,
peer: string,
docId: string
): Promise<DocClock> {
const clock = await NbStore.getPeerPushedClock({
id,
peer,
docId,
});
return {
docId: clock.docId,
timestamp: new Date(clock.timestamp),
};
},
setPeerPushedClock: async function (
id: string,
peer: string,
docId: string,
clock: Date
): Promise<void> {
await NbStore.setPeerPushedClock({
id,
peer,
docId,
clock: clock.getTime(),
});
},
clearClocks: async function (id: string): Promise<void> {
await NbStore.clearClocks({
id,
});
},
};
@@ -1,312 +0,0 @@
import {
base64ToUint8Array,
uint8ArrayToBase64,
} from '@affine/core/modules/workspace-engine';
import {
type Blob,
type DocClock,
type DocRecord,
type DocStorage,
type DocUpdate,
type ListedBlob,
} from '@affine/native';
import { registerPlugin } from '@capacitor/core';
import type { NbStorePlugin } from './definitions';
export const NbStoreDocStorage =
registerPlugin<NbStorePlugin>('NbStoreDocStorage');
export interface SetBlob {
key: string;
data: Uint8Array;
mime: string;
}
export class NativeDocStorage implements DocStorage {
constructor(private readonly universalId: string) {}
/** Initialize the database and run migrations. */
connect(): Promise<void> {
return NbStoreDocStorage.connect({
id: this.universalId,
});
}
close(): Promise<void> {
return NbStoreDocStorage.close({
id: this.universalId,
});
}
get isClosed(): Promise<boolean> {
return NbStoreDocStorage.isClosed({
id: this.universalId,
}).then(result => result.isClosed);
}
/**
* Flush the WAL file to the database file.
* See https://www.sqlite.org/pragma.html#pragma_wal_checkpoint:~:text=PRAGMA%20schema.wal_checkpoint%3B
*/
checkpoint(): Promise<void> {
return NbStoreDocStorage.checkpoint({
id: this.universalId,
});
}
validate(): Promise<boolean> {
return NbStoreDocStorage.validate({
id: this.universalId,
}).then(result => result.isValidate);
}
setSpaceId(spaceId: string): Promise<void> {
return NbStoreDocStorage.setSpaceId({
id: this.universalId,
spaceId,
});
}
async pushUpdate(docId: string, update: Uint8Array): Promise<Date> {
return NbStoreDocStorage.pushUpdate({
id: this.universalId,
docId,
data: await uint8ArrayToBase64(update),
}).then(result => new Date(result.timestamp));
}
getDocSnapshot(docId: string): Promise<DocRecord | null> {
return NbStoreDocStorage.getDocSnapshot({
id: this.universalId,
docId,
}).then(result => {
if (result) {
return {
...result,
data: base64ToUint8Array(result.data),
timestamp: new Date(result.timestamp),
};
}
return null;
});
}
async setDocSnapshot(snapshot: DocRecord): Promise<boolean> {
return NbStoreDocStorage.setDocSnapshot({
id: this.universalId,
docId: snapshot.docId,
data: await uint8ArrayToBase64(snapshot.data),
}).then(result => result.success);
}
getDocUpdates(docId: string): Promise<Array<DocUpdate>> {
return NbStoreDocStorage.getDocUpdates({
id: this.universalId,
docId,
}).then(result =>
result.map(update => ({
...update,
data: base64ToUint8Array(update.data),
createdAt: new Date(update.createdAt),
}))
);
}
markUpdatesMerged(docId: string, updates: Array<Date>): Promise<number> {
return NbStoreDocStorage.markUpdatesMerged({
id: this.universalId,
docId,
timestamps: updates.map(date => date.getTime()),
}).then(result => result.count);
}
deleteDoc(docId: string): Promise<void> {
return NbStoreDocStorage.deleteDoc({
id: this.universalId,
docId,
});
}
getDocClocks(after: Date): Promise<Array<DocClock>> {
return NbStoreDocStorage.getDocClocks({
id: this.universalId,
after: after.getTime(),
}).then(result =>
result.map(clock => ({
...clock,
timestamp: new Date(clock.timestamp),
}))
);
}
getDocClock(docId: string): Promise<DocClock | null> {
return NbStoreDocStorage.getDocClock({
id: this.universalId,
docId,
}).then(result => {
if (result) {
return {
...result,
timestamp: new Date(result.timestamp),
};
}
return null;
});
}
getBlob(key: string): Promise<Blob | null> {
return NbStoreDocStorage.getBlob({
id: this.universalId,
key,
}).then(result => {
if (result) {
return {
...result,
data: base64ToUint8Array(result.data),
createdAt: new Date(result.createdAt),
};
}
return null;
});
}
async setBlob(blob: SetBlob): Promise<void> {
return NbStoreDocStorage.setBlob({
id: this.universalId,
key: blob.key,
data: await uint8ArrayToBase64(blob.data),
mime: blob.mime,
});
}
deleteBlob(key: string, permanently: boolean): Promise<void> {
return NbStoreDocStorage.deleteBlob({
id: this.universalId,
key,
permanently,
});
}
releaseBlobs(): Promise<void> {
return NbStoreDocStorage.releaseBlobs({
id: this.universalId,
});
}
async listBlobs(): Promise<Array<ListedBlob>> {
return (
await NbStoreDocStorage.listBlobs({
id: this.universalId,
})
).map(blob => ({
...blob,
createdAt: new Date(blob.createdAt),
}));
}
getPeerRemoteClocks(peer: string): Promise<Array<DocClock>> {
return NbStoreDocStorage.getPeerRemoteClocks({
id: this.universalId,
peer,
}).then(result =>
result.map(clock => ({
...clock,
timestamp: new Date(clock.timestamp),
}))
);
}
getPeerRemoteClock(peer: string, docId: string): Promise<DocClock> {
return NbStoreDocStorage.getPeerRemoteClock({
id: this.universalId,
peer,
docId,
}).then(result => ({
...result,
timestamp: new Date(result.timestamp),
}));
}
setPeerRemoteClock(peer: string, docId: string, clock: Date): Promise<void> {
return NbStoreDocStorage.setPeerRemoteClock({
id: this.universalId,
peer,
docId,
clock: clock.getTime(),
});
}
getPeerPulledRemoteClocks(peer: string): Promise<Array<DocClock>> {
return NbStoreDocStorage.getPeerPulledRemoteClocks({
id: this.universalId,
peer,
}).then(result =>
result.map(clock => ({
...clock,
timestamp: new Date(clock.timestamp),
}))
);
}
getPeerPulledRemoteClock(peer: string, docId: string): Promise<DocClock> {
return NbStoreDocStorage.getPeerPulledRemoteClock({
id: this.universalId,
peer,
docId,
}).then(result => ({
...result,
timestamp: new Date(result.timestamp),
}));
}
setPeerPulledRemoteClock(
peer: string,
docId: string,
clock: Date
): Promise<void> {
return NbStoreDocStorage.setPeerPulledRemoteClock({
id: this.universalId,
peer,
docId,
clock: clock.getTime(),
});
}
getPeerPushedClocks(peer: string): Promise<Array<DocClock>> {
return NbStoreDocStorage.getPeerPushedClocks({
id: this.universalId,
peer,
}).then(result =>
result.map(clock => ({
...clock,
timestamp: new Date(clock.timestamp),
}))
);
}
getPeerPushedClock(peer: string, docId: string): Promise<DocClock> {
return NbStoreDocStorage.getPeerPushedClock({
id: this.universalId,
peer,
docId,
}).then(result => ({
...result,
timestamp: new Date(result.timestamp),
}));
}
setPeerPushedClock(peer: string, docId: string, clock: Date): Promise<void> {
return NbStoreDocStorage.setPeerPushedClock({
id: this.universalId,
peer,
docId,
clock: clock.getTime(),
});
}
clearClocks(): Promise<void> {
return NbStoreDocStorage.clearClocks({
id: this.universalId,
});
}
}
@@ -1,83 +0,0 @@
import { parseUniversalId, SpaceStorage } from '@affine/nbstore';
import { applyUpdate, Doc as YDoc } from 'yjs';
import { SqliteBlobStorage } from './blob';
import { NativeDBConnection } from './db';
import { SqliteDocStorage } from './doc';
import { SqliteSyncStorage } from './sync';
export class SqliteSpaceStorage extends SpaceStorage {
get connection() {
const docStore = this.get('doc');
if (!docStore) {
throw new Error('doc store not found');
}
const connection = docStore.connection;
if (!(connection instanceof NativeDBConnection)) {
throw new Error('doc store connection is not a Sqlite connection');
}
return connection;
}
async getDBPath() {
return this.connection.getDBPath();
}
async getWorkspaceName() {
const docStore = this.tryGet('doc');
if (!docStore) {
return null;
}
const doc = await docStore.getDoc(docStore.spaceId);
if (!doc) {
return null;
}
const ydoc = new YDoc();
applyUpdate(ydoc, doc.bin);
return ydoc.getMap('meta').get('name') as string;
}
async checkpoint() {
await this.connection.inner.checkpoint();
}
}
const STORE_CACHE = new Map<string, SqliteSpaceStorage>();
export function getStorage(universalId: string) {
return STORE_CACHE.get(universalId);
}
export async function ensureStorage(universalId: string) {
const { peer, type, id } = parseUniversalId(universalId);
let store = STORE_CACHE.get(universalId);
if (!store) {
const opts = {
peer,
type,
id,
};
store = new SqliteSpaceStorage([
new SqliteDocStorage(opts),
new SqliteBlobStorage(opts),
new SqliteSyncStorage(opts),
]);
store.connect();
await store.waitForConnected();
STORE_CACHE.set(universalId, store);
}
return store;
}
@@ -1,70 +0,0 @@
import {
BasicSyncStorage,
type DocClock,
type DocClocks,
share,
} from '@affine/nbstore';
import { NativeDBConnection } from './db';
export class SqliteSyncStorage extends BasicSyncStorage {
override connection = share(
new NativeDBConnection(this.peer, this.spaceType, this.spaceId)
);
get db() {
return this.connection.inner;
}
override async getPeerRemoteClocks(peer: string) {
const records = await this.db.getPeerRemoteClocks(peer);
return records.reduce((clocks, { docId, timestamp }) => {
clocks[docId] = timestamp;
return clocks;
}, {} as DocClocks);
}
override async getPeerRemoteClock(peer: string, docId: string) {
return this.db.getPeerRemoteClock(peer, docId);
}
override async setPeerRemoteClock(peer: string, clock: DocClock) {
await this.db.setPeerRemoteClock(peer, clock.docId, clock.timestamp);
}
override async getPeerPulledRemoteClock(peer: string, docId: string) {
return this.db.getPeerPulledRemoteClock(peer, docId);
}
override async getPeerPulledRemoteClocks(peer: string) {
const records = await this.db.getPeerPulledRemoteClocks(peer);
return records.reduce((clocks, { docId, timestamp }) => {
clocks[docId] = timestamp;
return clocks;
}, {} as DocClocks);
}
override async setPeerPulledRemoteClock(peer: string, clock: DocClock) {
await this.db.setPeerPulledRemoteClock(peer, clock.docId, clock.timestamp);
}
override async getPeerPushedClocks(peer: string) {
const records = await this.db.getPeerPushedClocks(peer);
return records.reduce((clocks, { docId, timestamp }) => {
clocks[docId] = timestamp;
return clocks;
}, {} as DocClocks);
}
override async getPeerPushedClock(peer: string, docId: string) {
return this.db.getPeerPushedClock(peer, docId);
}
override async setPeerPushedClock(peer: string, clock: DocClock) {
await this.db.setPeerPushedClock(peer, clock.docId, clock.timestamp);
}
override async clearClocks() {
await this.db.clearClocks();
}
}
+1 -1
View File
@@ -20,7 +20,7 @@ chrono = { workspace = true }
dashmap = { workspace = true }
sqlx = { workspace = true }
thiserror = { workspace = true }
uniffi = { workspace = true, features = ["cli"] }
uniffi = { workspace = true, features = ["cli", "tokio"] }
[target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies]
objc2 = { workspace = true }
@@ -1,29 +0,0 @@
use thiserror::Error;
#[derive(uniffi::Error, Error, Debug)]
pub enum UniffiError {
#[error("Get user document directory failed")]
GetUserDocumentDirectoryFailed,
#[error("Create affine dir failed: {0}")]
CreateAffineDirFailed(String),
#[error("Empty doc storage path")]
EmptyDocStoragePath,
#[error("Empty space id")]
EmptySpaceId,
#[error("Sqlx error: {0}")]
SqlxError(String),
#[error("Base64 decoding error: {0}")]
Base64DecodingError(String),
#[error("Invalid universal storage id: {0}. It should be in format of @peer($peer);@type($type);@id($id);")]
InvalidUniversalId(String),
#[error("Invalid space type: {0}")]
InvalidSpaceType(String),
#[error("Concat space dir failed: {0}")]
ConcatSpaceDirFailed(String),
}
impl From<sqlx::Error> for UniffiError {
fn from(err: sqlx::Error) -> Self {
UniffiError::SqlxError(err.to_string())
}
}
+243 -363
View File
@@ -1,15 +1,23 @@
use std::fmt::Display;
use std::str::FromStr;
use std::time::SystemTime;
use affine_common::hashcash::Stamp;
use affine_nbstore::storage;
use dashmap::{mapref::one::RefMut, DashMap, Entry};
use affine_nbstore::pool::SqliteDocStoragePool;
use crate::error::UniffiError;
#[derive(uniffi::Error, thiserror::Error, Debug)]
pub enum UniffiError {
#[error("Error: {0}")]
Err(String),
#[error("Base64 decoding error: {0}")]
Base64DecodingError(String),
#[error("Timestamp decoding error")]
TimestampDecodingError,
}
mod error;
mod utils;
impl From<affine_nbstore::error::Error> for UniffiError {
fn from(err: affine_nbstore::error::Error) -> Self {
Self::Err(err.to_string())
}
}
type Result<T> = std::result::Result<T, UniffiError>;
uniffi::setup_scaffolding!("affine_mobile_native");
@@ -22,16 +30,16 @@ pub fn hashcash_mint(resource: String, bits: u32) -> String {
pub struct DocRecord {
pub doc_id: String,
// base64 encoded data
pub data: String,
pub timestamp: SystemTime,
pub bin: String,
pub timestamp: i64,
}
impl From<affine_nbstore::DocRecord> for DocRecord {
fn from(record: affine_nbstore::DocRecord) -> Self {
Self {
doc_id: record.doc_id,
data: base64_simd::STANDARD.encode_to_string(&record.data),
timestamp: record.timestamp.and_utc().into(),
bin: base64_simd::STANDARD.encode_to_string(&record.bin),
timestamp: record.timestamp.and_utc().timestamp_millis(),
}
}
}
@@ -39,13 +47,15 @@ impl From<affine_nbstore::DocRecord> for DocRecord {
impl TryFrom<DocRecord> for affine_nbstore::DocRecord {
type Error = UniffiError;
fn try_from(record: DocRecord) -> Result<Self, Self::Error> {
fn try_from(record: DocRecord) -> Result<Self> {
Ok(Self {
doc_id: record.doc_id,
data: base64_simd::STANDARD
.decode_to_vec(record.data)
bin: base64_simd::STANDARD
.decode_to_vec(record.bin)
.map_err(|e| UniffiError::Base64DecodingError(e.to_string()))?,
timestamp: chrono::DateTime::<chrono::Utc>::from(record.timestamp).naive_utc(),
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp_millis(record.timestamp)
.ok_or(UniffiError::TimestampDecodingError)?
.naive_utc(),
})
}
}
@@ -53,52 +63,60 @@ impl TryFrom<DocRecord> for affine_nbstore::DocRecord {
#[derive(uniffi::Record)]
pub struct DocUpdate {
pub doc_id: String,
pub created_at: SystemTime,
pub timestamp: i64,
// base64 encoded data
pub data: String,
pub bin: String,
}
impl From<affine_nbstore::DocUpdate> for DocUpdate {
fn from(update: affine_nbstore::DocUpdate) -> Self {
Self {
doc_id: update.doc_id,
created_at: update.created_at.and_utc().into(),
data: base64_simd::STANDARD.encode_to_string(&update.data),
timestamp: update.timestamp.and_utc().timestamp_millis(),
bin: base64_simd::STANDARD.encode_to_string(&update.bin),
}
}
}
impl From<DocUpdate> for affine_nbstore::DocUpdate {
fn from(update: DocUpdate) -> Self {
Self {
impl TryFrom<DocUpdate> for affine_nbstore::DocUpdate {
type Error = UniffiError;
fn try_from(update: DocUpdate) -> Result<Self> {
Ok(Self {
doc_id: update.doc_id,
created_at: chrono::DateTime::<chrono::Utc>::from(update.created_at).naive_utc(),
data: update.data.into(),
}
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp_millis(update.timestamp)
.ok_or(UniffiError::TimestampDecodingError)?
.naive_utc(),
bin: update.bin.into(),
})
}
}
#[derive(uniffi::Record)]
pub struct DocClock {
pub doc_id: String,
pub timestamp: SystemTime,
pub timestamp: i64,
}
impl From<affine_nbstore::DocClock> for DocClock {
fn from(clock: affine_nbstore::DocClock) -> Self {
Self {
doc_id: clock.doc_id,
timestamp: clock.timestamp.and_utc().into(),
timestamp: clock.timestamp.and_utc().timestamp_millis(),
}
}
}
impl From<DocClock> for affine_nbstore::DocClock {
fn from(clock: DocClock) -> Self {
Self {
impl TryFrom<DocClock> for affine_nbstore::DocClock {
type Error = UniffiError;
fn try_from(clock: DocClock) -> Result<Self> {
Ok(Self {
doc_id: clock.doc_id,
timestamp: chrono::DateTime::<chrono::Utc>::from(clock.timestamp).naive_utc(),
}
timestamp: chrono::DateTime::<chrono::Utc>::from_timestamp_millis(clock.timestamp)
.ok_or(UniffiError::TimestampDecodingError)?
.naive_utc(),
})
}
}
@@ -109,7 +127,7 @@ pub struct Blob {
pub data: String,
pub mime: String,
pub size: i64,
pub created_at: SystemTime,
pub created_at: i64,
}
impl From<affine_nbstore::Blob> for Blob {
@@ -119,7 +137,7 @@ impl From<affine_nbstore::Blob> for Blob {
data: base64_simd::STANDARD.encode_to_string(&blob.data),
mime: blob.mime,
size: blob.size,
created_at: blob.created_at.and_utc().into(),
created_at: blob.created_at.and_utc().timestamp_millis(),
}
}
}
@@ -135,7 +153,7 @@ pub struct SetBlob {
impl TryFrom<SetBlob> for affine_nbstore::SetBlob {
type Error = UniffiError;
fn try_from(blob: SetBlob) -> Result<Self, Self::Error> {
fn try_from(blob: SetBlob) -> Result<Self> {
Ok(Self {
key: blob.key,
data: base64_simd::STANDARD
@@ -151,7 +169,7 @@ pub struct ListedBlob {
pub key: String,
pub size: i64,
pub mime: String,
pub created_at: SystemTime,
pub created_at: i64,
}
impl From<affine_nbstore::ListedBlob> for ListedBlob {
@@ -160,76 +178,43 @@ impl From<affine_nbstore::ListedBlob> for ListedBlob {
key: blob.key,
size: blob.size,
mime: blob.mime,
created_at: blob.created_at.and_utc().into(),
created_at: blob.created_at.and_utc().timestamp_millis(),
}
}
}
#[derive(uniffi::Object)]
pub struct DocStoragePool {
inner: DashMap<String, storage::SqliteDocStorage>,
}
impl DocStoragePool {
fn ensure_storage<'a>(
&'a self,
universal_id: &str,
) -> Result<RefMut<'a, String, storage::SqliteDocStorage>, UniffiError> {
let entry = self.inner.entry(universal_id.to_string());
if let Entry::Occupied(storage) = entry {
return Ok(storage.into_ref());
}
let options = parse_universal_id(entry.key())?;
let db_path = utils::get_db_path(&options)?;
if db_path.is_empty() {
return Err(UniffiError::EmptyDocStoragePath);
}
let storage = storage::SqliteDocStorage::new(db_path);
Ok(entry.or_insert(storage))
}
inner: SqliteDocStoragePool,
}
#[uniffi::export]
pub fn new_doc_storage_pool() -> DocStoragePool {
DocStoragePool {
inner: Default::default(),
}
}
#[uniffi::export(async_runtime = "tokio")]
impl DocStoragePool {
/// Initialize the database and run migrations.
pub async fn connect(&self, universal_id: String) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.connect().await?)
pub async fn connect(&self, universal_id: String, path: String) -> Result<()> {
Ok(self.inner.connect(universal_id, path).await?)
}
pub async fn close(&self, universal_id: String) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
storage.close().await;
self.inner.remove(&universal_id);
pub async fn disconnect(&self, universal_id: String) -> Result<()> {
self.inner.disconnect(universal_id).await?;
Ok(())
}
pub fn is_closed(&self, universal_id: String) -> bool {
let storage = self.ensure_storage(&universal_id).unwrap();
storage.is_closed()
}
pub async fn checkpoint(&self, universal_id: String) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.checkpoint().await?)
}
pub async fn validate(&self, universal_id: String) -> Result<bool, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.validate().await?)
}
pub async fn set_space_id(
&self,
universal_id: String,
space_id: String,
) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
if space_id.is_empty() {
return Err(UniffiError::EmptySpaceId);
}
Ok(storage.set_space_id(space_id).await?)
pub async fn set_space_id(&self, universal_id: String, space_id: String) -> Result<()> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.set_space_id(space_id)
.await?,
)
}
pub async fn push_update(
@@ -237,10 +222,11 @@ impl DocStoragePool {
universal_id: String,
doc_id: String,
update: String,
) -> Result<SystemTime, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
) -> Result<i64> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.push_update(
doc_id,
base64_simd::STANDARD
@@ -249,7 +235,7 @@ impl DocStoragePool {
)
.await?
.and_utc()
.into(),
.timestamp_millis(),
)
}
@@ -257,28 +243,36 @@ impl DocStoragePool {
&self,
universal_id: String,
doc_id: String,
) -> Result<Option<DocRecord>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.get_doc_snapshot(doc_id).await?.map(Into::into))
) -> Result<Option<DocRecord>> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.get_doc_snapshot(doc_id)
.await?
.map(Into::into),
)
}
pub async fn set_doc_snapshot(
&self,
universal_id: String,
snapshot: DocRecord,
) -> Result<bool, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.set_doc_snapshot(snapshot.try_into()?).await?)
pub async fn set_doc_snapshot(&self, universal_id: String, snapshot: DocRecord) -> Result<bool> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.set_doc_snapshot(snapshot.try_into()?)
.await?,
)
}
pub async fn get_doc_updates(
&self,
universal_id: String,
doc_id: String,
) -> Result<Vec<DocUpdate>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
) -> Result<Vec<DocUpdate>> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.get_doc_updates(doc_id)
.await?
.into_iter()
@@ -291,36 +285,55 @@ impl DocStoragePool {
&self,
universal_id: String,
doc_id: String,
updates: Vec<SystemTime>,
) -> Result<u32, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
updates: Vec<i64>,
) -> Result<u32> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.mark_updates_merged(
doc_id,
updates
.into_iter()
.map(|t| chrono::DateTime::<chrono::Utc>::from(t).naive_utc())
.collect(),
.map(|t| {
chrono::DateTime::<chrono::Utc>::from_timestamp_millis(t)
.ok_or(UniffiError::TimestampDecodingError)
.map(|t| t.naive_utc())
})
.collect::<Result<Vec<_>>>()?,
)
.await?,
)
}
pub async fn delete_doc(&self, universal_id: String, doc_id: String) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.delete_doc(doc_id).await?)
pub async fn delete_doc(&self, universal_id: String, doc_id: String) -> Result<()> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.delete_doc(doc_id)
.await?,
)
}
pub async fn get_doc_clocks(
&self,
universal_id: String,
after: Option<SystemTime>,
) -> Result<Vec<DocClock>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
after: Option<i64>,
) -> Result<Vec<DocClock>> {
Ok(
storage
.get_doc_clocks(after.map(|t| chrono::DateTime::<chrono::Utc>::from(t).naive_utc()))
self
.inner
.ensure_storage(universal_id)?
.get_doc_clocks(
after
.map(|t| {
chrono::DateTime::<chrono::Utc>::from_timestamp_millis(t)
.ok_or(UniffiError::TimestampDecodingError)
.map(|t| t.naive_utc())
})
.transpose()?,
)
.await?
.into_iter()
.map(Into::into)
@@ -332,23 +345,36 @@ impl DocStoragePool {
&self,
universal_id: String,
doc_id: String,
) -> Result<Option<DocClock>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.get_doc_clock(doc_id).await?.map(Into::into))
) -> Result<Option<DocClock>> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.get_doc_clock(doc_id)
.await?
.map(Into::into),
)
}
pub async fn get_blob(
&self,
universal_id: String,
key: String,
) -> Result<Option<Blob>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.get_blob(key).await?.map(Into::into))
pub async fn get_blob(&self, universal_id: String, key: String) -> Result<Option<Blob>> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.get_blob(key)
.await?
.map(Into::into),
)
}
pub async fn set_blob(&self, universal_id: String, blob: SetBlob) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.set_blob(blob.try_into()?).await?)
pub async fn set_blob(&self, universal_id: String, blob: SetBlob) -> Result<()> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.set_blob(blob.try_into()?)
.await?,
)
}
pub async fn delete_blob(
@@ -356,20 +382,31 @@ impl DocStoragePool {
universal_id: String,
key: String,
permanently: bool,
) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.delete_blob(key, permanently).await?)
}
pub async fn release_blobs(&self, universal_id: String) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.release_blobs().await?)
}
pub async fn list_blobs(&self, universal_id: String) -> Result<Vec<ListedBlob>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
) -> Result<()> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.delete_blob(key, permanently)
.await?,
)
}
pub async fn release_blobs(&self, universal_id: String) -> Result<()> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.release_blobs()
.await?,
)
}
pub async fn list_blobs(&self, universal_id: String) -> Result<Vec<ListedBlob>> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.list_blobs()
.await?
.into_iter()
@@ -382,10 +419,11 @@ impl DocStoragePool {
&self,
universal_id: String,
peer: String,
) -> Result<Vec<DocClock>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
) -> Result<Vec<DocClock>> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.get_peer_remote_clocks(peer)
.await?
.into_iter()
@@ -399,9 +437,15 @@ impl DocStoragePool {
universal_id: String,
peer: String,
doc_id: String,
) -> Result<DocClock, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.get_peer_remote_clock(peer, doc_id).await?.into())
) -> Result<DocClock> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.get_peer_remote_clock(peer, doc_id)
.await?
.into(),
)
}
pub async fn set_peer_remote_clock(
@@ -409,15 +453,18 @@ impl DocStoragePool {
universal_id: String,
peer: String,
doc_id: String,
clock: SystemTime,
) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
clock: i64,
) -> Result<()> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.set_peer_remote_clock(
peer,
doc_id,
chrono::DateTime::<chrono::Utc>::from(clock).naive_utc(),
chrono::DateTime::<chrono::Utc>::from_timestamp_millis(clock)
.ok_or(UniffiError::TimestampDecodingError)?
.naive_utc(),
)
.await?,
)
@@ -427,10 +474,11 @@ impl DocStoragePool {
&self,
universal_id: String,
peer: String,
) -> Result<Vec<DocClock>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
) -> Result<Vec<DocClock>> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.get_peer_pulled_remote_clocks(peer)
.await?
.into_iter()
@@ -444,10 +492,11 @@ impl DocStoragePool {
universal_id: String,
peer: String,
doc_id: String,
) -> Result<DocClock, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
) -> Result<DocClock> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.get_peer_pulled_remote_clock(peer, doc_id)
.await?
.into(),
@@ -459,15 +508,18 @@ impl DocStoragePool {
universal_id: String,
peer: String,
doc_id: String,
clock: SystemTime,
) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
clock: i64,
) -> Result<()> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.set_peer_pulled_remote_clock(
peer,
doc_id,
chrono::DateTime::<chrono::Utc>::from(clock).naive_utc(),
chrono::DateTime::<chrono::Utc>::from_timestamp_millis(clock)
.ok_or(UniffiError::TimestampDecodingError)?
.naive_utc(),
)
.await?,
)
@@ -477,10 +529,11 @@ impl DocStoragePool {
&self,
universal_id: String,
peer: String,
) -> Result<Vec<DocClock>, UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
) -> Result<Vec<DocClock>> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.get_peer_pushed_clocks(peer)
.await?
.into_iter()
@@ -494,203 +547,30 @@ impl DocStoragePool {
universal_id: String,
peer: String,
doc_id: String,
clock: SystemTime,
) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
clock: i64,
) -> Result<()> {
Ok(
storage
self
.inner
.ensure_storage(universal_id)?
.set_peer_pushed_clock(
peer,
doc_id,
chrono::DateTime::<chrono::Utc>::from(clock).naive_utc(),
chrono::DateTime::<chrono::Utc>::from_timestamp_millis(clock)
.ok_or(UniffiError::TimestampDecodingError)?
.naive_utc(),
)
.await?,
)
}
pub async fn clear_clocks(&self, universal_id: String) -> Result<(), UniffiError> {
let storage = self.ensure_storage(&universal_id)?;
Ok(storage.clear_clocks().await?)
}
}
#[uniffi::export]
pub fn get_db_path(peer: String, space_type: String, id: String) -> Result<String, UniffiError> {
let options = StorageOptions {
peer,
space_type: SpaceType::from_str(&space_type)?,
id,
};
utils::get_db_path(&options)
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default)]
pub enum SpaceType {
#[default]
Userspace,
Workspace,
}
impl Display for SpaceType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
SpaceType::Userspace => write!(f, "userspace"),
SpaceType::Workspace => write!(f, "workspace"),
}
}
}
impl FromStr for SpaceType {
type Err = UniffiError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"userspace" => Self::Userspace,
"workspace" => Self::Workspace,
_ => return Err(UniffiError::InvalidSpaceType(s.to_string())),
})
}
}
pub struct StorageOptions {
pub peer: String,
pub space_type: SpaceType,
pub id: String,
}
pub fn parse_universal_id(id: &str) -> Result<StorageOptions, UniffiError> {
let mut result = StorageOptions {
peer: String::new(),
space_type: SpaceType::default(),
id: String::new(),
};
let mut key = String::new();
let mut value = String::new();
let mut is_in_value = false;
let mut chars = id.chars().peekable();
while let Some(ch) = chars.next() {
if is_in_value {
if ch == ')' && chars.peek() == Some(&';') {
// Store the collected value in the appropriate field
match key.as_str() {
"peer" => result.peer = value.clone(),
"type" => result.space_type = SpaceType::from_str(&value)?,
"id" => result.id = value.clone(),
_ => return Err(UniffiError::InvalidUniversalId(id.to_string())),
}
key.clear();
value.clear();
is_in_value = false;
chars.next(); // Skip the semicolon
continue;
}
value.push(ch);
continue;
}
if ch == '@' {
// Find the position of next '('
let mut temp_chars = chars.clone();
let mut found_paren = false;
let mut key_chars = Vec::new();
while let Some(next_ch) = temp_chars.next() {
if next_ch == '(' {
found_paren = true;
break;
}
key_chars.push(next_ch);
}
// Invalid format if no '(' found or it's immediately after '@'
if !found_paren || key_chars.is_empty() {
return Err(UniffiError::InvalidUniversalId(id.to_string()));
}
key = key_chars.into_iter().collect();
// Advance the original iterator to the position after the key
for _ in 0..key.len() + 1 {
chars.next();
}
is_in_value = true;
} else {
return Err(UniffiError::InvalidUniversalId(id.to_string()));
}
}
// Validate the parsed results
if result.peer.is_empty() || result.id.is_empty() {
return Err(UniffiError::InvalidUniversalId(id.to_string()));
}
Ok(result)
}
#[cfg(test)]
mod tests {
use super::*;
// ... existing test functions ...
#[test]
fn test_universal_id() {
let options = StorageOptions {
peer: "123".to_string(),
space_type: SpaceType::Workspace,
id: "456".to_string(),
};
let id = format!(
"@peer({});@type({});@id({});",
options.peer, options.space_type, options.id
);
let result = parse_universal_id(&id).unwrap();
assert_eq!(result.peer, "123");
assert_eq!(result.space_type, SpaceType::Workspace);
assert_eq!(result.id, "456");
}
#[test]
fn test_parse_universal_id_valid_cases() {
let testcases = vec![
"@peer(123);@type(userspace);@id(456);",
"@peer(123);@type(workspace);@id(456);",
"@peer(https://app.affine.pro);@type(userspace);@id(hello:world);",
"@peer(@name);@type(userspace);@id(@id);",
"@peer(@peer(name);@type(userspace);@id(@id);",
];
for id in testcases {
let result = parse_universal_id(id);
assert!(result.is_ok(), "Failed to parse: {}", id);
let parsed = result.unwrap();
assert!(!parsed.peer.is_empty());
assert!(!parsed.id.is_empty());
}
}
#[test]
fn test_parse_universal_id_invalid_cases() {
let testcases = vec![
// invalid space type
"@peer(123);@type(anyspace);@id(456);",
// invalid peer
"@peer(@peer(name););@type(userspace);@id(@id);",
];
for id in testcases {
let result = parse_universal_id(id);
assert!(result.is_err(), "Should have failed to parse: {}", id);
match result {
Err(UniffiError::InvalidUniversalId(_)) => (),
Err(UniffiError::InvalidSpaceType(_)) => (),
_ => panic!("Expected InvalidUniversalId error for: {}", id),
}
}
pub async fn clear_clocks(&self, universal_id: String) -> Result<()> {
Ok(
self
.inner
.ensure_storage(universal_id)?
.clear_clocks()
.await?,
)
}
}
@@ -1,141 +0,0 @@
use std::fs;
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
use homedir::my_home;
#[cfg(any(target_os = "ios", target_os = "macos"))]
use objc2::rc::autoreleasepool;
#[cfg(any(target_os = "ios", target_os = "macos"))]
use objc2_foundation::{NSFileManager, NSSearchPathDirectory, NSSearchPathDomainMask, NSString};
use crate::{error::UniffiError, SpaceType, StorageOptions};
const DB_FILE_NAME: &str = "storage.db";
#[cfg(any(target_os = "ios", target_os = "macos"))]
pub(crate) fn get_db_path(options: &StorageOptions) -> Result<String, UniffiError> {
let file_manager = unsafe { NSFileManager::defaultManager() };
// equivalent to Swift:
// ```swift
// guard let documentsPath = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else {
// return nil
// }
// ```
let urls = unsafe {
file_manager.URLsForDirectory_inDomains(
NSSearchPathDirectory::NSDocumentDirectory,
NSSearchPathDomainMask::NSUserDomainMask,
)
};
let document_directory = urls
.first()
.ok_or(UniffiError::GetUserDocumentDirectoryFailed)?;
let affine_dir = unsafe {
let spaces_dir = match options.space_type {
SpaceType::Userspace => "userspaces",
SpaceType::Workspace => "workspaces",
};
let escaped_peer = escape_filename(&options.peer);
document_directory
.URLByAppendingPathComponent(&NSString::from_str(".affine"))
.and_then(|url| url.URLByAppendingPathComponent(&NSString::from_str(spaces_dir)))
.and_then(|url| url.URLByAppendingPathComponent(&NSString::from_str(&escaped_peer)))
.and_then(|url| url.URLByAppendingPathComponent(&NSString::from_str(&options.id)))
}
.ok_or(UniffiError::ConcatSpaceDirFailed(format!(
"{}:{}:{}",
options.peer, options.space_type, options.id
)))?;
let affine_dir_str = autoreleasepool(|pool| {
Ok::<String, UniffiError>(
unsafe { affine_dir.path() }
.ok_or(UniffiError::GetUserDocumentDirectoryFailed)?
.as_str(pool)
.to_string(),
)
})?;
// Replicate Swift's appending ".affine" subdir, creating it if necessary
fs::create_dir_all(&affine_dir_str)
.map_err(|_| UniffiError::CreateAffineDirFailed(affine_dir_str.clone()))?;
let db_path = autoreleasepool(|pool| {
let db_path =
unsafe { affine_dir.URLByAppendingPathComponent(&NSString::from_str(DB_FILE_NAME)) }.ok_or(
UniffiError::ConcatSpaceDirFailed(format!(
"{}:{}:{}/{DB_FILE_NAME}",
options.peer, options.space_type, options.id
)),
)?;
Ok::<String, UniffiError>(
unsafe { db_path.path() }
.ok_or(UniffiError::GetUserDocumentDirectoryFailed)?
.as_str(pool)
.to_string(),
)
})?;
Ok(db_path)
}
#[cfg(not(any(target_os = "ios", target_os = "macos")))]
pub(crate) fn get_db_path(options: &StorageOptions) -> Result<String, UniffiError> {
let home_dir = my_home()
.map_err(|_| UniffiError::GetUserDocumentDirectoryFailed)?
.ok_or(UniffiError::GetUserDocumentDirectoryFailed)?;
let spaces_dir = match options.space_type {
SpaceType::Userspace => "userspaces",
SpaceType::Workspace => "workspaces",
};
let escaped_peer = escape_filename(&options.peer);
let db_path = home_dir
.join(".affine")
.join(spaces_dir)
.join(&escaped_peer)
.join(&options.id);
fs::create_dir_all(&db_path)
.map_err(|_| UniffiError::CreateAffineDirFailed(db_path.to_string_lossy().to_string()))?;
db_path
.join(DB_FILE_NAME)
.to_str()
.map(|p| p.to_owned())
.ok_or(UniffiError::GetUserDocumentDirectoryFailed)
}
fn escape_filename(name: &str) -> String {
// First replace special chars with '_'
let with_underscores = name.replace(|c: char| "\\/!@#$%^&*()+~`\"':;,?<>|".contains(c), "_");
// Then collapse multiple '_' into single '_'
let mut result = String::with_capacity(with_underscores.len());
let mut last_was_underscore = false;
for c in with_underscores.chars() {
if c == '_' {
if !last_was_underscore {
result.push(c);
}
last_was_underscore = true;
} else {
result.push(c);
last_was_underscore = false;
}
}
// Remove trailing underscore
result.trim_end_matches('_').to_string()
}
#[cfg(all(test, any(target_os = "ios", target_os = "macos")))]
mod tests {
use super::*;
#[test]
fn test_escape_filename() {
assert_eq!(escape_filename("hello@world"), "hello_world");
assert_eq!(escape_filename("test!!file"), "test_file");
assert_eq!(escape_filename("_test_"), "_test"); // Leading underscore preserved
assert_eq!(escape_filename("multi___under"), "multi_under");
assert_eq!(escape_filename("path/to\\file"), "path_to_file");
}
}
+31 -38
View File
@@ -1,41 +1,34 @@
/* auto-generated by NAPI-RS */
/* eslint-disable */
export declare class DocStorage {
constructor(path: string)
export declare class DocStoragePool {
constructor()
/** Initialize the database and run migrations. */
connect(): Promise<void>
close(): Promise<void>
get isClosed(): Promise<boolean>
/**
* Flush the WAL file to the database file.
* See https://www.sqlite.org/pragma.html#pragma_wal_checkpoint:~:text=PRAGMA%20schema.wal_checkpoint%3B
*/
checkpoint(): Promise<void>
validate(): Promise<boolean>
setSpaceId(spaceId: string): Promise<void>
pushUpdate(docId: string, update: Uint8Array): Promise<Date>
getDocSnapshot(docId: string): Promise<DocRecord | null>
setDocSnapshot(snapshot: DocRecord): Promise<boolean>
getDocUpdates(docId: string): Promise<Array<DocUpdate>>
markUpdatesMerged(docId: string, updates: Array<Date>): Promise<number>
deleteDoc(docId: string): Promise<void>
getDocClocks(after?: Date | undefined | null): Promise<Array<DocClock>>
getDocClock(docId: string): Promise<DocClock | null>
getBlob(key: string): Promise<Blob | null>
setBlob(blob: SetBlob): Promise<void>
deleteBlob(key: string, permanently: boolean): Promise<void>
releaseBlobs(): Promise<void>
listBlobs(): Promise<Array<ListedBlob>>
getPeerRemoteClocks(peer: string): Promise<Array<DocClock>>
getPeerRemoteClock(peer: string, docId: string): Promise<DocClock>
setPeerRemoteClock(peer: string, docId: string, clock: Date): Promise<void>
getPeerPulledRemoteClocks(peer: string): Promise<Array<DocClock>>
getPeerPulledRemoteClock(peer: string, docId: string): Promise<DocClock>
setPeerPulledRemoteClock(peer: string, docId: string, clock: Date): Promise<void>
getPeerPushedClocks(peer: string): Promise<Array<DocClock>>
getPeerPushedClock(peer: string, docId: string): Promise<DocClock>
setPeerPushedClock(peer: string, docId: string, clock: Date): Promise<void>
clearClocks(): Promise<void>
connect(universalId: string, path: string): Promise<void>
disconnect(universalId: string): Promise<void>
setSpaceId(universalId: string, spaceId: string): Promise<void>
pushUpdate(universalId: string, docId: string, update: Uint8Array): Promise<Date>
getDocSnapshot(universalId: string, docId: string): Promise<DocRecord | null>
setDocSnapshot(universalId: string, snapshot: DocRecord): Promise<boolean>
getDocUpdates(universalId: string, docId: string): Promise<Array<DocUpdate>>
markUpdatesMerged(universalId: string, docId: string, updates: Array<Date>): Promise<number>
deleteDoc(universalId: string, docId: string): Promise<void>
getDocClocks(universalId: string, after?: Date | undefined | null): Promise<Array<DocClock>>
getDocClock(universalId: string, docId: string): Promise<DocClock | null>
getBlob(universalId: string, key: string): Promise<Blob | null>
setBlob(universalId: string, blob: SetBlob): Promise<void>
deleteBlob(universalId: string, key: string, permanently: boolean): Promise<void>
releaseBlobs(universalId: string): Promise<void>
listBlobs(universalId: string): Promise<Array<ListedBlob>>
getPeerRemoteClocks(universalId: string, peer: string): Promise<Array<DocClock>>
getPeerRemoteClock(universalId: string, peer: string, docId: string): Promise<DocClock>
setPeerRemoteClock(universalId: string, peer: string, docId: string, clock: Date): Promise<void>
getPeerPulledRemoteClocks(universalId: string, peer: string): Promise<Array<DocClock>>
getPeerPulledRemoteClock(universalId: string, peer: string, docId: string): Promise<DocClock>
setPeerPulledRemoteClock(universalId: string, peer: string, docId: string, clock: Date): Promise<void>
getPeerPushedClocks(universalId: string, peer: string): Promise<Array<DocClock>>
getPeerPushedClock(universalId: string, peer: string, docId: string): Promise<DocClock>
setPeerPushedClock(universalId: string, peer: string, docId: string, clock: Date): Promise<void>
clearClocks(universalId: string): Promise<void>
}
export declare class SqliteConnection {
@@ -96,14 +89,14 @@ export interface DocClock {
export interface DocRecord {
docId: string
data: Uint8Array
bin: Uint8Array
timestamp: Date
}
export interface DocUpdate {
docId: string
createdAt: Date
data: Uint8Array
timestamp: Date
bin: Uint8Array
}
export interface InsertRow {
+1 -1
View File
@@ -364,7 +364,7 @@ if (!nativeBinding) {
throw new Error(`Failed to load native binding`)
}
module.exports.DocStorage = nativeBinding.DocStorage
module.exports.DocStoragePool = nativeBinding.DocStoragePool
module.exports.SqliteConnection = nativeBinding.SqliteConnection
module.exports.mintChallengeResponse = nativeBinding.mintChallengeResponse
module.exports.ValidationResult = nativeBinding.ValidationResult
@@ -13,8 +13,10 @@ use-as-lib = ["napi-derive/noop", "napi/noop"]
affine_schema = { path = "../schema" }
anyhow = { workspace = true }
chrono = { workspace = true }
dashmap = { workspace = true }
napi = { workspace = true }
napi-derive = { workspace = true }
thiserror = { workspace = true }
sqlx = { workspace = true, default-features = false, features = ["chrono", "macros", "migrate", "runtime-tokio", "sqlite", "tls-rustls"] }
tokio = { workspace = true, features = ["full"] }
+9 -7
View File
@@ -1,18 +1,18 @@
use std::ops::Deref;
use super::{storage::SqliteDocStorage, Blob, ListedBlob, SetBlob};
type Result<T> = std::result::Result<T, sqlx::Error>;
use super::{error::Result, storage::SqliteDocStorage, Blob, ListedBlob, SetBlob};
impl SqliteDocStorage {
pub async fn get_blob(&self, key: String) -> Result<Option<Blob>> {
sqlx::query_as!(
let result = sqlx::query_as!(
Blob,
"SELECT key, data, size, mime, created_at FROM blobs WHERE key = ? AND deleted_at IS NULL",
key
)
.fetch_optional(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn set_blob(&self, blob: SetBlob) -> Result<()> {
@@ -58,12 +58,14 @@ impl SqliteDocStorage {
}
pub async fn list_blobs(&self) -> Result<Vec<ListedBlob>> {
sqlx::query_as!(
let result = sqlx::query_as!(
ListedBlob,
"SELECT key, size, mime, created_at FROM blobs WHERE deleted_at IS NULL ORDER BY created_at DESC;"
)
.fetch_all(&self.pool)
.await
.await?;
Ok(result)
}
}
+29 -22
View File
@@ -3,8 +3,7 @@ use std::ops::Deref;
use chrono::{DateTime, NaiveDateTime};
use sqlx::{QueryBuilder, Row};
use super::storage::{Result, SqliteDocStorage};
use super::{DocClock, DocRecord, DocUpdate};
use super::{error::Result, storage::SqliteDocStorage, DocClock, DocRecord, DocUpdate};
struct Meta {
space_id: String,
@@ -81,7 +80,7 @@ impl SqliteDocStorage {
Ok(()) => break,
Err(e) => {
if tried > 10 {
return Err(e);
return Err(e.into());
}
// Increment timestamp by 1ms and retry
@@ -126,13 +125,15 @@ impl SqliteDocStorage {
}
pub async fn get_doc_snapshot(&self, doc_id: String) -> Result<Option<DocRecord>> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocRecord,
"SELECT doc_id, data, updated_at as timestamp FROM snapshots WHERE doc_id = ?",
"SELECT doc_id, data as bin, updated_at as timestamp FROM snapshots WHERE doc_id = ?",
doc_id
)
.fetch_optional(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn set_doc_snapshot(&self, snapshot: DocRecord) -> Result<bool> {
@@ -145,7 +146,7 @@ impl SqliteDocStorage {
WHERE updated_at <= $3;"#,
)
.bind(snapshot.doc_id)
.bind(snapshot.data.deref())
.bind(snapshot.bin.deref())
.bind(snapshot.timestamp)
.execute(&self.pool)
.await?;
@@ -154,13 +155,15 @@ impl SqliteDocStorage {
}
pub async fn get_doc_updates(&self, doc_id: String) -> Result<Vec<DocUpdate>> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocUpdate,
"SELECT doc_id, created_at, data FROM updates WHERE doc_id = ?",
"SELECT doc_id, created_at as timestamp, data as bin FROM updates WHERE doc_id = ?",
doc_id
)
.fetch_all(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn mark_updates_merged(
@@ -204,7 +207,9 @@ impl SqliteDocStorage {
.execute(&mut *tx)
.await?;
tx.commit().await
tx.commit().await?;
Ok(())
}
pub async fn get_doc_clocks(&self, after: Option<NaiveDateTime>) -> Result<Vec<DocClock>> {
@@ -228,13 +233,15 @@ impl SqliteDocStorage {
}
pub async fn get_doc_clock(&self, doc_id: String) -> Result<Option<DocClock>> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocClock,
"SELECT doc_id, timestamp FROM clocks WHERE doc_id = ?",
doc_id
)
.fetch_optional(&self.pool)
.await
.await?;
Ok(result)
}
}
@@ -286,7 +293,7 @@ mod tests {
storage
.set_doc_snapshot(DocRecord {
doc_id: "test".to_string(),
data: vec![0, 0].into(),
bin: vec![0, 0].into(),
timestamp: Utc::now().naive_utc(),
})
.await
@@ -350,7 +357,7 @@ mod tests {
assert_eq!(result.len(), 4);
assert_eq!(
result.iter().map(|u| u.data.as_ref()).collect::<Vec<_>>(),
result.iter().map(|u| u.bin.as_ref()).collect::<Vec<_>>(),
updates
);
}
@@ -365,7 +372,7 @@ mod tests {
let snapshot = DocRecord {
doc_id: "test".to_string(),
data: vec![0, 0].into(),
bin: vec![0, 0].into(),
timestamp: Utc::now().naive_utc(),
};
@@ -374,7 +381,7 @@ mod tests {
let result = storage.get_doc_snapshot("test".to_string()).await.unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().data.as_ref(), vec![0, 0]);
assert_eq!(result.unwrap().bin.as_ref(), vec![0, 0]);
}
#[tokio::test]
@@ -383,7 +390,7 @@ mod tests {
let snapshot = DocRecord {
doc_id: "test".to_string(),
data: vec![0, 0].into(),
bin: vec![0, 0].into(),
timestamp: Utc::now().naive_utc(),
};
@@ -392,11 +399,11 @@ mod tests {
let result = storage.get_doc_snapshot("test".to_string()).await.unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().data.as_ref(), vec![0, 0]);
assert_eq!(result.unwrap().bin.as_ref(), vec![0, 0]);
let snapshot = DocRecord {
doc_id: "test".to_string(),
data: vec![0, 1].into(),
bin: vec![0, 1].into(),
timestamp: DateTime::from_timestamp_millis(Utc::now().timestamp_millis() - 1000)
.unwrap()
.naive_utc(),
@@ -408,7 +415,7 @@ mod tests {
let result = storage.get_doc_snapshot("test".to_string()).await.unwrap();
assert!(result.is_some());
assert_eq!(result.unwrap().data.as_ref(), vec![0, 0]);
assert_eq!(result.unwrap().bin.as_ref(), vec![0, 0]);
}
#[tokio::test]
@@ -468,7 +475,7 @@ mod tests {
updates
.iter()
.skip(1)
.map(|u| u.created_at)
.map(|u| u.timestamp)
.collect::<Vec<_>>(),
)
.await
@@ -0,0 +1,11 @@
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error("Sqlite Error: {0}")]
SqlxError(#[from] sqlx::Error),
#[error("Migrate Error: {0}")]
MigrateError(#[from] sqlx::migrate::MigrateError),
#[error("Invalid operation")]
InvalidOperation,
}
+249 -138
View File
@@ -1,11 +1,14 @@
pub mod blob;
pub mod doc;
pub mod error;
pub mod pool;
pub mod storage;
pub mod sync;
use chrono::NaiveDateTime;
use napi::bindgen_prelude::*;
use napi_derive::napi;
use pool::SqliteDocStoragePool;
#[cfg(feature = "use-as-lib")]
type Result<T> = anyhow::Result<T>;
@@ -14,13 +17,10 @@ type Result<T> = anyhow::Result<T>;
type Result<T> = napi::Result<T>;
#[cfg(not(feature = "use-as-lib"))]
fn map_err(err: sqlx::Error) -> Error {
Error::from(anyhow::Error::from(err))
}
#[cfg(feature = "use-as-lib")]
fn map_err(err: sqlx::Error) -> anyhow::Error {
anyhow::Error::from(err)
impl From<error::Error> for napi::Error {
fn from(err: error::Error) -> Self {
napi::Error::new(napi::Status::GenericFailure, err.to_string())
}
}
#[cfg(feature = "use-as-lib")]
@@ -32,16 +32,16 @@ pub type Data = Uint8Array;
#[napi(object)]
pub struct DocUpdate {
pub doc_id: String,
pub created_at: NaiveDateTime,
pub timestamp: NaiveDateTime,
#[napi(ts_type = "Uint8Array")]
pub data: Data,
pub bin: Data,
}
#[napi(object)]
pub struct DocRecord {
pub doc_id: String,
#[napi(ts_type = "Uint8Array")]
pub data: Data,
pub bin: Data,
pub timestamp: NaiveDateTime,
}
@@ -79,243 +79,354 @@ pub struct ListedBlob {
}
#[napi]
pub struct DocStorage {
storage: storage::SqliteDocStorage,
pub struct DocStoragePool {
pool: SqliteDocStoragePool,
}
#[napi]
impl DocStorage {
impl DocStoragePool {
#[napi(constructor, async_runtime)]
pub fn new(path: String) -> Result<Self> {
pub fn new() -> Result<Self> {
Ok(Self {
storage: storage::SqliteDocStorage::new(path),
pool: SqliteDocStoragePool::default(),
})
}
#[napi]
/// Initialize the database and run migrations.
pub async fn connect(&self) -> Result<()> {
self.storage.connect().await.map_err(map_err)
}
#[napi]
pub async fn close(&self) -> Result<()> {
self.storage.close().await;
pub async fn connect(&self, universal_id: String, path: String) -> Result<()> {
self.pool.connect(universal_id, path).await?;
Ok(())
}
#[napi(getter)]
pub async fn is_closed(&self) -> Result<bool> {
Ok(self.storage.is_closed())
}
/**
* Flush the WAL file to the database file.
* See https://www.sqlite.org/pragma.html#pragma_wal_checkpoint:~:text=PRAGMA%20schema.wal_checkpoint%3B
*/
#[napi]
pub async fn checkpoint(&self) -> Result<()> {
self.storage.checkpoint().await.map_err(map_err)
pub async fn disconnect(&self, universal_id: String) -> Result<()> {
self.pool.disconnect(universal_id).await?;
Ok(())
}
#[napi]
pub async fn validate(&self) -> Result<bool> {
self.storage.validate().await.map_err(map_err)
}
#[napi]
pub async fn set_space_id(&self, space_id: String) -> Result<()> {
self.storage.set_space_id(space_id).await.map_err(map_err)
}
#[napi]
pub async fn push_update(&self, doc_id: String, update: Uint8Array) -> Result<NaiveDateTime> {
pub async fn set_space_id(&self, universal_id: String, space_id: String) -> Result<()> {
self
.storage
.push_update(doc_id, update)
.await
.map_err(map_err)
.pool
.ensure_storage(universal_id)?
.set_space_id(space_id)
.await?;
Ok(())
}
#[napi]
pub async fn get_doc_snapshot(&self, doc_id: String) -> Result<Option<DocRecord>> {
self.storage.get_doc_snapshot(doc_id).await.map_err(map_err)
pub async fn push_update(
&self,
universal_id: String,
doc_id: String,
update: Uint8Array,
) -> Result<NaiveDateTime> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.push_update(doc_id, update)
.await?,
)
}
#[napi]
pub async fn set_doc_snapshot(&self, snapshot: DocRecord) -> Result<bool> {
self
.storage
.set_doc_snapshot(snapshot)
.await
.map_err(map_err)
pub async fn get_doc_snapshot(
&self,
universal_id: String,
doc_id: String,
) -> Result<Option<DocRecord>> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_doc_snapshot(doc_id)
.await?,
)
}
#[napi]
pub async fn get_doc_updates(&self, doc_id: String) -> Result<Vec<DocUpdate>> {
self.storage.get_doc_updates(doc_id).await.map_err(map_err)
pub async fn set_doc_snapshot(&self, universal_id: String, snapshot: DocRecord) -> Result<bool> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.set_doc_snapshot(snapshot)
.await?,
)
}
#[napi]
pub async fn get_doc_updates(
&self,
universal_id: String,
doc_id: String,
) -> Result<Vec<DocUpdate>> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_doc_updates(doc_id)
.await?,
)
}
#[napi]
pub async fn mark_updates_merged(
&self,
universal_id: String,
doc_id: String,
updates: Vec<NaiveDateTime>,
) -> Result<u32> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.mark_updates_merged(doc_id, updates)
.await?,
)
}
#[napi]
pub async fn delete_doc(&self, universal_id: String, doc_id: String) -> Result<()> {
self
.storage
.mark_updates_merged(doc_id, updates)
.await
.map_err(map_err)
.pool
.ensure_storage(universal_id)?
.delete_doc(doc_id)
.await?;
Ok(())
}
#[napi]
pub async fn delete_doc(&self, doc_id: String) -> Result<()> {
self.storage.delete_doc(doc_id).await.map_err(map_err)
pub async fn get_doc_clocks(
&self,
universal_id: String,
after: Option<NaiveDateTime>,
) -> Result<Vec<DocClock>> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_doc_clocks(after)
.await?,
)
}
#[napi]
pub async fn get_doc_clocks(&self, after: Option<NaiveDateTime>) -> Result<Vec<DocClock>> {
self.storage.get_doc_clocks(after).await.map_err(map_err)
pub async fn get_doc_clock(
&self,
universal_id: String,
doc_id: String,
) -> Result<Option<DocClock>> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_doc_clock(doc_id)
.await?,
)
}
#[napi]
pub async fn get_doc_clock(&self, doc_id: String) -> Result<Option<DocClock>> {
self.storage.get_doc_clock(doc_id).await.map_err(map_err)
pub async fn get_blob(&self, universal_id: String, key: String) -> Result<Option<Blob>> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_blob(key)
.await?,
)
}
#[napi]
pub async fn get_blob(&self, key: String) -> Result<Option<Blob>> {
self.storage.get_blob(key).await.map_err(map_err)
}
#[napi]
pub async fn set_blob(&self, blob: SetBlob) -> Result<()> {
self.storage.set_blob(blob).await.map_err(map_err)
}
#[napi]
pub async fn delete_blob(&self, key: String, permanently: bool) -> Result<()> {
pub async fn set_blob(&self, universal_id: String, blob: SetBlob) -> Result<()> {
self
.storage
.pool
.ensure_storage(universal_id)?
.set_blob(blob)
.await?;
Ok(())
}
#[napi]
pub async fn delete_blob(
&self,
universal_id: String,
key: String,
permanently: bool,
) -> Result<()> {
self
.pool
.ensure_storage(universal_id)?
.delete_blob(key, permanently)
.await
.map_err(map_err)
.await?;
Ok(())
}
#[napi]
pub async fn release_blobs(&self) -> Result<()> {
self.storage.release_blobs().await.map_err(map_err)
}
#[napi]
pub async fn list_blobs(&self) -> Result<Vec<ListedBlob>> {
self.storage.list_blobs().await.map_err(map_err)
}
#[napi]
pub async fn get_peer_remote_clocks(&self, peer: String) -> Result<Vec<DocClock>> {
pub async fn release_blobs(&self, universal_id: String) -> Result<()> {
self
.storage
.get_peer_remote_clocks(peer)
.await
.map_err(map_err)
.pool
.ensure_storage(universal_id)?
.release_blobs()
.await?;
Ok(())
}
#[napi]
pub async fn get_peer_remote_clock(&self, peer: String, doc_id: String) -> Result<DocClock> {
self
.storage
.get_peer_remote_clock(peer, doc_id)
.await
.map_err(map_err)
pub async fn list_blobs(&self, universal_id: String) -> Result<Vec<ListedBlob>> {
Ok(self.pool.ensure_storage(universal_id)?.list_blobs().await?)
}
#[napi]
pub async fn get_peer_remote_clocks(
&self,
universal_id: String,
peer: String,
) -> Result<Vec<DocClock>> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_peer_remote_clocks(peer)
.await?,
)
}
#[napi]
pub async fn get_peer_remote_clock(
&self,
universal_id: String,
peer: String,
doc_id: String,
) -> Result<DocClock> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_peer_remote_clock(peer, doc_id)
.await?,
)
}
#[napi]
pub async fn set_peer_remote_clock(
&self,
universal_id: String,
peer: String,
doc_id: String,
clock: NaiveDateTime,
) -> Result<()> {
self
.storage
.pool
.ensure_storage(universal_id)?
.set_peer_remote_clock(peer, doc_id, clock)
.await
.map_err(map_err)
.await?;
Ok(())
}
#[napi]
pub async fn get_peer_pulled_remote_clocks(&self, peer: String) -> Result<Vec<DocClock>> {
self
.storage
.get_peer_pulled_remote_clocks(peer)
.await
.map_err(map_err)
pub async fn get_peer_pulled_remote_clocks(
&self,
universal_id: String,
peer: String,
) -> Result<Vec<DocClock>> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_peer_pulled_remote_clocks(peer)
.await?,
)
}
#[napi]
pub async fn get_peer_pulled_remote_clock(
&self,
universal_id: String,
peer: String,
doc_id: String,
) -> Result<DocClock> {
self
.storage
.get_peer_pulled_remote_clock(peer, doc_id)
.await
.map_err(map_err)
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_peer_pulled_remote_clock(peer, doc_id)
.await?,
)
}
#[napi]
pub async fn set_peer_pulled_remote_clock(
&self,
universal_id: String,
peer: String,
doc_id: String,
clock: NaiveDateTime,
) -> Result<()> {
self
.storage
.pool
.ensure_storage(universal_id)?
.set_peer_pulled_remote_clock(peer, doc_id, clock)
.await
.map_err(map_err)
.await?;
Ok(())
}
#[napi]
pub async fn get_peer_pushed_clocks(&self, peer: String) -> Result<Vec<DocClock>> {
self
.storage
.get_peer_pushed_clocks(peer)
.await
.map_err(map_err)
pub async fn get_peer_pushed_clocks(
&self,
universal_id: String,
peer: String,
) -> Result<Vec<DocClock>> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_peer_pushed_clocks(peer)
.await?,
)
}
#[napi]
pub async fn get_peer_pushed_clock(&self, peer: String, doc_id: String) -> Result<DocClock> {
self
.storage
.get_peer_pushed_clock(peer, doc_id)
.await
.map_err(map_err)
pub async fn get_peer_pushed_clock(
&self,
universal_id: String,
peer: String,
doc_id: String,
) -> Result<DocClock> {
Ok(
self
.pool
.ensure_storage(universal_id)?
.get_peer_pushed_clock(peer, doc_id)
.await?,
)
}
#[napi]
pub async fn set_peer_pushed_clock(
&self,
universal_id: String,
peer: String,
doc_id: String,
clock: NaiveDateTime,
) -> Result<()> {
self
.storage
.pool
.ensure_storage(universal_id)?
.set_peer_pushed_clock(peer, doc_id, clock)
.await
.map_err(map_err)
.await?;
Ok(())
}
#[napi]
pub async fn clear_clocks(&self) -> Result<()> {
self.storage.clear_clocks().await.map_err(map_err)
pub async fn clear_clocks(&self, universal_id: String) -> Result<()> {
self
.pool
.ensure_storage(universal_id)?
.clear_clocks()
.await?;
Ok(())
}
}
@@ -0,0 +1,54 @@
use dashmap::{mapref::one::RefMut, DashMap, Entry};
use super::{
error::{Error, Result},
storage::SqliteDocStorage,
};
#[derive(Default)]
pub struct SqliteDocStoragePool {
inner: DashMap<String, SqliteDocStorage>,
}
impl SqliteDocStoragePool {
fn get_or_create_storage<'a>(
&'a self,
universal_id: String,
path: &str,
) -> RefMut<'a, String, SqliteDocStorage> {
let entry = self.inner.entry(universal_id);
if let Entry::Occupied(storage) = entry {
return storage.into_ref();
}
let storage = SqliteDocStorage::new(path.to_string());
entry.or_insert(storage)
}
pub fn ensure_storage<'a>(
&'a self,
universal_id: String,
) -> Result<RefMut<'a, String, SqliteDocStorage>> {
let entry = self.inner.entry(universal_id);
if let Entry::Occupied(storage) = entry {
Ok(storage.into_ref())
} else {
Err(Error::InvalidOperation)
}
}
/// Initialize the database and run migrations.
pub async fn connect(&self, universal_id: String, path: String) -> Result<()> {
let storage = self.get_or_create_storage(universal_id.to_owned(), &path);
storage.connect().await?;
Ok(())
}
pub async fn disconnect(&self, universal_id: String) -> Result<()> {
let storage = self.ensure_storage(universal_id.to_owned())?;
storage.close().await;
self.inner.remove(&universal_id);
Ok(())
}
}
@@ -5,7 +5,7 @@ use sqlx::{
Pool, Row,
};
pub type Result<T> = std::result::Result<T, sqlx::Error>;
use super::error::Result;
pub struct SqliteDocStorage {
pub pool: Pool<Sqlite>,
@@ -52,7 +52,7 @@ impl SqliteDocStorage {
}
pub async fn connect(&self) -> Result<()> {
if !Sqlite::database_exists(&self.path).await.unwrap_or(false) {
if !Sqlite::database_exists(&self.path).await? {
Sqlite::create_database(&self.path).await?;
};
@@ -79,7 +79,6 @@ impl SqliteDocStorage {
///
/// Flush the WAL file to the database file.
/// See https://www.sqlite.org/pragma.html#pragma_wal_checkpoint:~:text=PRAGMA%20schema.wal_checkpoint%3B
///
pub async fn checkpoint(&self) -> Result<()> {
sqlx::query("PRAGMA wal_checkpoint(FULL);")
.execute(&self.pool)
+25 -13
View File
@@ -1,28 +1,32 @@
use chrono::NaiveDateTime;
use super::storage::{Result, SqliteDocStorage};
use super::DocClock;
use super::{error::Result, storage::SqliteDocStorage};
impl SqliteDocStorage {
pub async fn get_peer_remote_clocks(&self, peer: String) -> Result<Vec<DocClock>> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocClock,
"SELECT doc_id, remote_clock as timestamp FROM peer_clocks WHERE peer = ?",
peer
)
.fetch_all(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn get_peer_remote_clock(&self, peer: String, doc_id: String) -> Result<DocClock> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocClock,
"SELECT doc_id, remote_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?",
peer,
doc_id
)
.fetch_one(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn set_peer_remote_clock(
@@ -48,13 +52,15 @@ impl SqliteDocStorage {
}
pub async fn get_peer_pulled_remote_clocks(&self, peer: String) -> Result<Vec<DocClock>> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocClock,
"SELECT doc_id, pulled_remote_clock as timestamp FROM peer_clocks WHERE peer = ?",
peer
)
.fetch_all(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn get_peer_pulled_remote_clock(
@@ -62,14 +68,16 @@ impl SqliteDocStorage {
peer: String,
doc_id: String,
) -> Result<DocClock> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocClock,
"SELECT doc_id, pulled_remote_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?",
peer,
doc_id
)
.fetch_one(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn set_peer_pulled_remote_clock(
@@ -95,24 +103,28 @@ impl SqliteDocStorage {
}
pub async fn get_peer_pushed_clocks(&self, peer: String) -> Result<Vec<DocClock>> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocClock,
"SELECT doc_id, pushed_clock as timestamp FROM peer_clocks WHERE peer = ?",
peer
)
.fetch_all(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn get_peer_pushed_clock(&self, peer: String, doc_id: String) -> Result<DocClock> {
sqlx::query_as!(
let result = sqlx::query_as!(
DocClock,
"SELECT doc_id, pushed_clock as timestamp FROM peer_clocks WHERE peer = ? AND doc_id = ?",
peer,
doc_id
)
.fetch_one(&self.pool)
.await
.await?;
Ok(result)
}
pub async fn set_peer_pushed_clock(