@blocksuite/store
Advanced tools
Comparing version
@@ -6,2 +6,3 @@ import { Signal } from '../utils/signal.js'; | ||
private readonly _cloud?; | ||
private _uploading; | ||
readonly blobs: Set<string>; | ||
@@ -11,3 +12,7 @@ readonly signals: { | ||
blobDeleted: Signal<string>; | ||
uploadStateChanged: Signal<boolean>; | ||
uploadFinished: Signal<string>; | ||
}; | ||
onUploadStateChange(callback: (uploading: boolean) => void, sync?: boolean): void; | ||
onUploadFinished(callback: (id: BlobId) => void): void; | ||
static init(workspace: string, cloudApi?: string): Promise<IndexedDBBlobProvider>; | ||
@@ -21,3 +26,3 @@ private _initBlobs; | ||
} | ||
export declare class BlobCloudSync { | ||
export declare class CloudSyncManager { | ||
private readonly _abortController; | ||
@@ -29,5 +34,8 @@ private readonly _fetcher; | ||
private readonly _workspace; | ||
private readonly _uploading; | ||
private readonly _onUploadFinished; | ||
private _pipeline; | ||
private initialized; | ||
constructor(workspace: string, prefixUrl: string, db: IDBInstance); | ||
private _uploadingIds; | ||
constructor(workspace: string, prefixUrl: string, db: IDBInstance, onUploadStateChanged: Signal<boolean>, onUploadFinished: Signal<BlobId>); | ||
private _handleTaskRetry; | ||
@@ -34,0 +42,0 @@ private _taskRunner; |
@@ -15,3 +15,17 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
} | ||
function isSetAddFunction(value) { | ||
return typeof value === 'function'; | ||
} | ||
function isSetDeleteFunction(value) { | ||
return typeof value === 'function'; | ||
} | ||
let IndexedDBBlobProvider = IndexedDBBlobProvider_1 = class IndexedDBBlobProvider { | ||
onUploadStateChange(callback, sync = true) { | ||
if (sync) | ||
callback(this._uploading); | ||
this.signals.uploadStateChanged.on(callback); | ||
} | ||
onUploadFinished(callback) { | ||
this.signals.uploadFinished.on(callback); | ||
} | ||
static async init(workspace, cloudApi) { | ||
@@ -31,2 +45,3 @@ const provider = new IndexedDBBlobProvider_1(workspace, cloudApi); | ||
constructor(workspace, cloudApi) { | ||
this._uploading = false; | ||
this.blobs = new Set(); | ||
@@ -36,6 +51,11 @@ this.signals = { | ||
blobDeleted: new Signal(), | ||
uploadStateChanged: new Signal(), | ||
uploadFinished: new Signal(), | ||
}; | ||
this._database = getDatabase('blob', workspace); | ||
if (cloudApi) { | ||
this._cloud = new BlobCloudSync(workspace, cloudApi, this._database); | ||
this.signals.uploadStateChanged.on(uploading => { | ||
this._uploading = uploading; | ||
}); | ||
this._cloud = new CloudSyncManager(workspace, cloudApi, this._database, this.signals.uploadStateChanged, this.signals.uploadFinished); | ||
} | ||
@@ -83,4 +103,4 @@ } | ||
export { IndexedDBBlobProvider }; | ||
export class BlobCloudSync { | ||
constructor(workspace, prefixUrl, db) { | ||
export class CloudSyncManager { | ||
constructor(workspace, prefixUrl, db, onUploadStateChanged, onUploadFinished) { | ||
this._abortController = new AbortController(); | ||
@@ -90,2 +110,3 @@ this._pendingPipeline = []; | ||
this.initialized = false; | ||
this._onUploadFinished = onUploadFinished; | ||
this._fetcher = ky.create({ | ||
@@ -96,4 +117,32 @@ prefixUrl, | ||
}); | ||
const uploadingProxy = new Proxy(new Set(), { | ||
get(target, prop, receiver) { | ||
const val = target[prop]; | ||
if (typeof val === 'function' && typeof prop === 'string') { | ||
let resultFunction; | ||
if (prop === 'add' && isSetAddFunction(val)) { | ||
resultFunction = (...args) => { | ||
const result = val.apply(target, args); | ||
onUploadStateChanged.emit(!!target.size); | ||
return result; | ||
}; | ||
} | ||
if (prop === 'delete' && isSetDeleteFunction(val)) { | ||
resultFunction = (...args) => { | ||
const result = val.apply(target, args); | ||
onUploadStateChanged.emit(!!target.size); | ||
return result; | ||
}; | ||
} | ||
if (resultFunction) { | ||
return resultFunction; | ||
} | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
}, | ||
}); | ||
this._uploadingIds = uploadingProxy; | ||
this._database = db; | ||
this._pending = getDatabase('pending', workspace); | ||
this._uploading = getDatabase('uploading', workspace); | ||
this._workspace = workspace; | ||
@@ -114,6 +163,18 @@ this._pending.keys().then(async (keys) => { | ||
}); | ||
// resume pending blobs when reconnected | ||
this._uploading.keys().then(async (keys) => { | ||
for (const key of keys) { | ||
const uploaded = await this._uploading.get(key); | ||
if (!uploaded) { | ||
this.addTask(key, 'add'); | ||
} | ||
} | ||
}); | ||
} | ||
async _handleTaskRetry(task, status) { | ||
this._uploadingIds.delete(task.id); | ||
if (status?.exists) { | ||
await this._pending.delete(task.id); | ||
await this._uploading.set(task.id, true); | ||
this._onUploadFinished.emit(task.id); | ||
} | ||
@@ -138,2 +199,4 @@ else { | ||
if (resp.status === 404) { | ||
await this._uploading.set(task.id, false); | ||
this._uploadingIds.add(task.id); | ||
const status = await this._fetcher | ||
@@ -152,3 +215,3 @@ .put(`${this._workspace}/blob`, { body: task.blob, retry: 3 }) | ||
} | ||
console.error('BlobCloudSync taskRunner exited'); | ||
console.error('CloudSyncManager taskRunner exited'); | ||
} | ||
@@ -155,0 +218,0 @@ async get(id) { |
{ | ||
"name": "@blocksuite/store", | ||
"version": "0.4.0-20230116190618-0dc5fd3", | ||
"version": "0.4.0-20230117022540-0a09287", | ||
"description": "BlockSuite data store built for general purpose state management.", | ||
@@ -11,3 +11,3 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@blocksuite/global": "0.4.0-20230116190618-0dc5fd3", | ||
"@blocksuite/global": "0.4.0-20230117022540-0a09287", | ||
"@types/flexsearch": "^0.7.3", | ||
@@ -14,0 +14,0 @@ "buffer": "^6.0.3", |
@@ -17,6 +17,15 @@ import ky from 'ky'; | ||
function isSetAddFunction<T>(value: unknown): value is Set<T>['add'] { | ||
return typeof value === 'function'; | ||
} | ||
function isSetDeleteFunction<T>(value: unknown): value is Set<T>['delete'] { | ||
return typeof value === 'function'; | ||
} | ||
@staticImplements<BlobProviderStatic>() | ||
export class IndexedDBBlobProvider implements BlobProvider { | ||
private readonly _database: IDBInstance; | ||
private readonly _cloud?: BlobCloudSync; | ||
private readonly _cloud?: CloudSyncManager; | ||
private _uploading = false; | ||
@@ -27,4 +36,15 @@ readonly blobs: Set<string> = new Set(); | ||
blobDeleted: new Signal<BlobId>(), | ||
uploadStateChanged: new Signal<boolean>(), | ||
uploadFinished: new Signal<BlobId>(), | ||
}; | ||
onUploadStateChange(callback: (uploading: boolean) => void, sync = true) { | ||
if (sync) callback(this._uploading); | ||
this.signals.uploadStateChanged.on(callback); | ||
} | ||
onUploadFinished(callback: (id: BlobId) => void) { | ||
this.signals.uploadFinished.on(callback); | ||
} | ||
static async init( | ||
@@ -51,3 +71,12 @@ workspace: string, | ||
if (cloudApi) { | ||
this._cloud = new BlobCloudSync(workspace, cloudApi, this._database); | ||
this.signals.uploadStateChanged.on(uploading => { | ||
this._uploading = uploading; | ||
}); | ||
this._cloud = new CloudSyncManager( | ||
workspace, | ||
cloudApi, | ||
this._database, | ||
this.signals.uploadStateChanged, | ||
this.signals.uploadFinished | ||
); | ||
} | ||
@@ -113,3 +142,3 @@ } | ||
export class BlobCloudSync { | ||
export class CloudSyncManager { | ||
private readonly _abortController = new AbortController(); | ||
@@ -124,7 +153,17 @@ private readonly _fetcher: typeof ky; | ||
private readonly _workspace: string; | ||
private readonly _uploading: IDBInstance<boolean>; | ||
private readonly _onUploadFinished: Signal<string>; | ||
private _pipeline: SyncTask[] = []; | ||
private initialized = false; | ||
private _uploadingIds: Set<string>; | ||
constructor(workspace: string, prefixUrl: string, db: IDBInstance) { | ||
constructor( | ||
workspace: string, | ||
prefixUrl: string, | ||
db: IDBInstance, | ||
onUploadStateChanged: Signal<boolean>, | ||
onUploadFinished: Signal<BlobId> | ||
) { | ||
this._onUploadFinished = onUploadFinished; | ||
this._fetcher = ky.create({ | ||
@@ -136,4 +175,34 @@ prefixUrl, | ||
const uploadingProxy = new Proxy(new Set<string>(), { | ||
get(target, prop, receiver) { | ||
const val = target[prop as keyof typeof Set.prototype]; | ||
if (typeof val === 'function' && typeof prop === 'string') { | ||
let resultFunction; | ||
if (prop === 'add' && isSetAddFunction<string>(val)) { | ||
resultFunction = (...args: Parameters<typeof val>) => { | ||
const result = val.apply(target, args); | ||
onUploadStateChanged.emit(!!target.size); | ||
return result; | ||
}; | ||
} | ||
if (prop === 'delete' && isSetDeleteFunction<string>(val)) { | ||
resultFunction = (...args: Parameters<typeof val>) => { | ||
const result = val.apply(target, args); | ||
onUploadStateChanged.emit(!!target.size); | ||
return result; | ||
}; | ||
} | ||
if (resultFunction) { | ||
return resultFunction; | ||
} | ||
} | ||
return Reflect.get(target, prop, receiver); | ||
}, | ||
}); | ||
this._uploadingIds = uploadingProxy; | ||
this._database = db; | ||
this._pending = getDatabase('pending', workspace); | ||
this._uploading = getDatabase('uploading', workspace); | ||
this._workspace = workspace; | ||
@@ -161,7 +230,20 @@ | ||
}); | ||
// resume pending blobs when reconnected | ||
this._uploading.keys().then(async keys => { | ||
for (const key of keys) { | ||
const uploaded = await this._uploading.get(key); | ||
if (!uploaded) { | ||
this.addTask(key, 'add'); | ||
} | ||
} | ||
}); | ||
} | ||
private async _handleTaskRetry(task: SyncTask, status?: BlobStatus) { | ||
this._uploadingIds.delete(task.id); | ||
if (status?.exists) { | ||
await this._pending.delete(task.id); | ||
await this._uploading.set(task.id, true); | ||
this._onUploadFinished.emit(task.id); | ||
} else { | ||
@@ -191,2 +273,4 @@ await this._pending.set(task.id, { | ||
if (resp.status === 404) { | ||
await this._uploading.set(task.id, false); | ||
this._uploadingIds.add(task.id); | ||
const status = await this._fetcher | ||
@@ -205,3 +289,3 @@ .put(`${this._workspace}/blob`, { body: task.blob, retry: 3 }) | ||
console.error('BlobCloudSync taskRunner exited'); | ||
console.error('CloudSyncManager taskRunner exited'); | ||
} | ||
@@ -208,0 +292,0 @@ |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
501737
1.76%8316
1.81%+ Added
- Removed