@blocksuite/store
Advanced tools
Comparing version
@@ -31,3 +31,2 @@ import { Signal } from '../utils/signal.js'; | ||
private readonly _workspace; | ||
private readonly _uploading; | ||
private readonly _onUploadFinished; | ||
@@ -37,5 +36,8 @@ private _pipeline; | ||
private _uploadingIds; | ||
private _onUploadStateChanged; | ||
constructor(workspace: string, prefixUrl: string, db: IDBInstance, onUploadStateChanged: Signal<boolean>, onUploadFinished: Signal<BlobId>); | ||
private _handleTaskRetry; | ||
private _taskRunner; | ||
private _addUploadId; | ||
private _removeUploadId; | ||
get(id: BlobId): Promise<BlobURL | null>; | ||
@@ -42,0 +44,0 @@ addTask(id: BlobId, type: 'add' | 'delete'): Promise<void>; |
@@ -10,3 +10,4 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
import { Signal } from '../utils/signal.js'; | ||
import { getDatabase, sha, sleep } from './utils.js'; | ||
import { getDatabase, sha } from './utils.js'; | ||
import { sleep } from '@blocksuite/global/utils'; | ||
const RETRY_TIMEOUT = 500; | ||
@@ -16,8 +17,2 @@ function staticImplements() { | ||
} | ||
function isSetAddFunction(value) { | ||
return typeof value === 'function'; | ||
} | ||
function isSetDeleteFunction(value) { | ||
return typeof value === 'function'; | ||
} | ||
let IndexedDBBlobProvider = IndexedDBBlobProvider_1 = class IndexedDBBlobProvider { | ||
@@ -108,3 +103,5 @@ onUploadStateChange(callback, sync = true) { | ||
this.initialized = false; | ||
this._uploadingIds = new Set(); | ||
this._onUploadFinished = onUploadFinished; | ||
this._onUploadStateChanged = onUploadStateChanged; | ||
this._fetcher = ky.create({ | ||
@@ -115,32 +112,4 @@ 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; | ||
@@ -154,2 +123,5 @@ this._pending.keys().then(async (keys) => { | ||
} | ||
if (blob && type === 'upload') { | ||
this.addTask(id, 'add'); | ||
} | ||
return undefined; | ||
@@ -162,17 +134,7 @@ }))).filter((v) => !!v); | ||
}); | ||
// 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); | ||
this._removeUploadId(task.id); | ||
if (status?.exists) { | ||
await this._pending.delete(task.id); | ||
await this._uploading.set(task.id, true); | ||
this._onUploadFinished.emit(task.id); | ||
@@ -198,4 +160,7 @@ } | ||
if (resp.status === 404) { | ||
await this._uploading.set(task.id, false); | ||
this._uploadingIds.add(task.id); | ||
await this._pending.set(task.id, { | ||
type: 'upload', | ||
retry: task.retry, | ||
}); | ||
this._addUploadId(task.id); | ||
const status = await this._fetcher | ||
@@ -216,2 +181,10 @@ .put(`${this._workspace}/blob`, { body: task.blob, retry: 3 }) | ||
} | ||
_addUploadId(id) { | ||
this._uploadingIds.add(id); | ||
this._onUploadStateChanged.emit(!!this._uploadingIds.size); | ||
} | ||
_removeUploadId(id) { | ||
this._uploadingIds.delete(id); | ||
this._onUploadStateChanged.emit(!!this._uploadingIds.size); | ||
} | ||
async get(id) { | ||
@@ -218,0 +191,0 @@ const api = `${this._workspace}/blob/${id}`; |
import type { IDBInstance } from './types.js'; | ||
export declare function sha(input: ArrayBuffer): Promise<string>; | ||
export declare function getDatabase<T = ArrayBufferLike>(type: string, database: string): IDBInstance<T>; | ||
export declare function sleep(ms: number): Promise<void>; | ||
//# sourceMappingURL=utils.d.ts.map |
@@ -19,5 +19,2 @@ import { createStore, del, get, keys, set, clear } from 'idb-keyval'; | ||
} | ||
export async function sleep(ms) { | ||
return new Promise(resolve => setTimeout(resolve, ms)); | ||
} | ||
//# sourceMappingURL=utils.js.map |
@@ -0,1 +1,7 @@ | ||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { | ||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; | ||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); | ||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; | ||
return c > 3 && r && Object.defineProperty(target, key, r), r; | ||
}; | ||
import * as Y from 'yjs'; | ||
@@ -10,2 +16,3 @@ import { uuidv4 } from 'lib0/random.js'; | ||
import { assertExists, matchFlavours } from '@blocksuite/global/utils'; | ||
import { debug } from '@blocksuite/global/debug'; | ||
const isWeb = typeof window !== 'undefined'; | ||
@@ -655,2 +662,14 @@ function createChildMap(yChildIds) { | ||
} | ||
__decorate([ | ||
debug('CRUD') | ||
], Page.prototype, "addBlockByFlavour", null); | ||
__decorate([ | ||
debug('CRUD') | ||
], Page.prototype, "moveBlock", null); | ||
__decorate([ | ||
debug('CRUD') | ||
], Page.prototype, "updateBlock", null); | ||
__decorate([ | ||
debug('CRUD') | ||
], Page.prototype, "deleteBlock", null); | ||
//# sourceMappingURL=page.js.map |
{ | ||
"name": "@blocksuite/store", | ||
"version": "0.4.0-20230117022540-0a09287", | ||
"version": "0.4.0-20230117135824-d571db4", | ||
"description": "BlockSuite data store built for general purpose state management.", | ||
@@ -11,3 +11,3 @@ "main": "dist/index.js", | ||
"dependencies": { | ||
"@blocksuite/global": "0.4.0-20230117022540-0a09287", | ||
"@blocksuite/global": "0.4.0-20230117135824-d571db4", | ||
"@types/flexsearch": "^0.7.3", | ||
@@ -42,2 +42,3 @@ "buffer": "^6.0.3", | ||
"test:unit": "vitest --run", | ||
"test:unit:ui": "vitest --ui", | ||
"test:e2e": "playwright test", | ||
@@ -44,0 +45,0 @@ "test": "pnpm test:unit && pnpm test:e2e" |
@@ -45,3 +45,3 @@ /* eslint-disable @typescript-eslint/no-restricted-imports */ | ||
async function createRoot(page: Page) { | ||
queueMicrotask(() => page.addBlock({ flavour: 'affine:page' })); | ||
queueMicrotask(() => page.addBlockByFlavour('affine:page')); | ||
const root = await waitOnce(page.signals.rootAdded); | ||
@@ -99,3 +99,3 @@ return root; | ||
const page = await createTestPage(); | ||
page.addBlock({ flavour: 'affine:page' }); | ||
page.addBlockByFlavour('affine:page'); | ||
@@ -115,3 +115,3 @@ assert.deepEqual(serialize(page)[spaceId], { | ||
const page = await createTestPage(); | ||
page.addBlock({ flavour: 'affine:page', title: 'hello' }); | ||
page.addBlockByFlavour('affine:page', { title: 'hello' }); | ||
@@ -132,4 +132,4 @@ assert.deepEqual(serialize(page)[spaceId], { | ||
const page = await createTestPage(); | ||
page.addBlock({ flavour: 'affine:page' }); | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlockByFlavour('affine:page'); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
@@ -157,3 +157,3 @@ assert.deepEqual(serialize(page)[spaceId], { | ||
queueMicrotask(() => page.addBlock({ flavour: 'affine:page' })); | ||
queueMicrotask(() => page.addBlockByFlavour('affine:page')); | ||
const block = await waitOnce(page.signals.rootAdded); | ||
@@ -166,7 +166,8 @@ assert.ok(block instanceof BlockSchema['affine:page']); | ||
queueMicrotask(() => page.addBlock({ flavour: 'affine:page' })); | ||
const root = await waitOnce(page.signals.rootAdded); | ||
queueMicrotask(() => page.addBlockByFlavour('affine:page')); | ||
const roots = await waitOnce(page.signals.rootAdded); | ||
const root = Array.isArray(roots) ? roots[0] : roots; | ||
assert.ok(root instanceof BlockSchema['affine:page']); | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
assert.ok(root.children[0] instanceof BlockSchema['affine:paragraph']); | ||
@@ -189,3 +190,3 @@ assert.equal(root.childMap.get('1'), 0); | ||
page0.addBlock({ flavour: 'affine:page' }); | ||
page0.addBlockByFlavour('affine:page'); | ||
workspace.removePage(page0.id); | ||
@@ -261,3 +262,3 @@ | ||
page.addBlock({ flavour: 'affine:page' }); | ||
page.addBlockByFlavour('affine:page'); | ||
assert.deepEqual(serialize(page)[spaceId], { | ||
@@ -279,5 +280,6 @@ '0': { | ||
const page = await createTestPage(); | ||
const root = await createRoot(page); | ||
const roots = await createRoot(page); | ||
const root = Array.isArray(roots) ? roots[0] : roots; | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
@@ -321,6 +323,7 @@ // before delete | ||
const page = await createTestPage(); | ||
const root = await createRoot(page); | ||
const roots = await createRoot(page); | ||
const root = Array.isArray(roots) ? roots[0] : roots; | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
@@ -337,6 +340,7 @@ const text = page.getBlockById('2') as BaseBlockModel; | ||
const page = await createTestPage(); | ||
const root = await createRoot(page); | ||
const roots = await createRoot(page); | ||
const root = Array.isArray(roots) ? roots[0] : roots; | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
@@ -352,6 +356,7 @@ const result = page.getParent(root.children[1]) as BaseBlockModel; | ||
const page = await createTestPage(); | ||
const root = await createRoot(page); | ||
const roots = await createRoot(page); | ||
const root = Array.isArray(roots) ? roots[0] : roots; | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
@@ -373,3 +378,3 @@ const result = page.getPreviousSibling(root.children[1]) as BaseBlockModel; | ||
page.addBlock({ flavour: 'affine:page', title: 'hello' }); | ||
page.addBlockByFlavour('affine:page', { title: 'hello' }); | ||
@@ -396,5 +401,5 @@ expect(workspace.exportJSX()).toMatchInlineSnapshot(` | ||
page.addBlock({ flavour: 'affine:page' }); | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlock({ flavour: 'affine:paragraph' }); | ||
page.addBlockByFlavour('affine:page'); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
page.addBlockByFlavour('affine:paragraph'); | ||
@@ -420,6 +425,5 @@ expect(workspace.exportJSX()).toMatchInlineSnapshot(/* xml */ ` | ||
page.addBlock({ flavour: 'affine:page', title: 'hello' }); | ||
page.addBlockByFlavour('affine:page', { title: 'hello' }); | ||
page.addBlock({ | ||
flavour: 'affine:paragraph', | ||
page.addBlockByFlavour('affine:paragraph', { | ||
text: new page.Text( | ||
@@ -431,4 +435,3 @@ page, | ||
page.addBlock({ | ||
flavour: 'affine:paragraph', | ||
page.addBlockByFlavour('affine:paragraph', { | ||
text: new page.Text( | ||
@@ -435,0 +438,0 @@ page, |
@@ -5,3 +5,4 @@ import ky from 'ky'; | ||
import type { BlobId, BlobProvider, BlobURL, IDBInstance } from './types.js'; | ||
import { getDatabase, sha, sleep } from './utils.js'; | ||
import { getDatabase, sha } from './utils.js'; | ||
import { sleep } from '@blocksuite/global/utils'; | ||
@@ -18,10 +19,2 @@ const RETRY_TIMEOUT = 500; | ||
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>() | ||
@@ -147,7 +140,6 @@ export class IndexedDBBlobProvider implements BlobProvider { | ||
retry: number; | ||
type: 'add' | 'delete'; | ||
type: 'add' | 'delete' | 'upload'; | ||
}>; | ||
private readonly _pendingPipeline: SyncTask[] = []; | ||
private readonly _workspace: string; | ||
private readonly _uploading: IDBInstance<boolean>; | ||
private readonly _onUploadFinished: Signal<string>; | ||
@@ -157,3 +149,4 @@ | ||
private initialized = false; | ||
private _uploadingIds: Set<string>; | ||
private _uploadingIds: Set<string> = new Set(); | ||
private _onUploadStateChanged: Signal<boolean>; | ||
@@ -168,2 +161,3 @@ constructor( | ||
this._onUploadFinished = onUploadFinished; | ||
this._onUploadStateChanged = onUploadStateChanged; | ||
this._fetcher = ky.create({ | ||
@@ -175,34 +169,4 @@ 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; | ||
@@ -219,2 +183,5 @@ | ||
} | ||
if (blob && type === 'upload') { | ||
this.addTask(id, 'add'); | ||
} | ||
return undefined; | ||
@@ -231,19 +198,8 @@ }) | ||
}); | ||
// 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); | ||
this._removeUploadId(task.id); | ||
if (status?.exists) { | ||
await this._pending.delete(task.id); | ||
await this._uploading.set(task.id, true); | ||
this._onUploadFinished.emit(task.id); | ||
@@ -274,4 +230,7 @@ } else { | ||
if (resp.status === 404) { | ||
await this._uploading.set(task.id, false); | ||
this._uploadingIds.add(task.id); | ||
await this._pending.set(task.id, { | ||
type: 'upload', | ||
retry: task.retry, | ||
}); | ||
this._addUploadId(task.id); | ||
const status = await this._fetcher | ||
@@ -293,2 +252,12 @@ .put(`${this._workspace}/blob`, { body: task.blob, retry: 3 }) | ||
private _addUploadId(id: BlobId) { | ||
this._uploadingIds.add(id); | ||
this._onUploadStateChanged.emit(!!this._uploadingIds.size); | ||
} | ||
private _removeUploadId(id: BlobId) { | ||
this._uploadingIds.delete(id); | ||
this._onUploadStateChanged.emit(!!this._uploadingIds.size); | ||
} | ||
async get(id: BlobId): Promise<BlobURL | null> { | ||
@@ -295,0 +264,0 @@ const api = `${this._workspace}/blob/${id}`; |
@@ -26,5 +26,1 @@ import { createStore, del, get, keys, set, clear } from 'idb-keyval'; | ||
} | ||
export async function sleep(ms: number): Promise<void> { | ||
return new Promise(resolve => setTimeout(resolve, ms)); | ||
} |
@@ -26,2 +26,3 @@ import * as Y from 'yjs'; | ||
import { assertExists, matchFlavours } from '@blocksuite/global/utils'; | ||
import { debug } from '@blocksuite/global/debug'; | ||
import BlockTag = BlockSuiteInternal.BlockTag; | ||
@@ -311,2 +312,3 @@ import TagSchema = BlockSuiteInternal.TagSchema; | ||
@debug('CRUD') | ||
public addBlockByFlavour< | ||
@@ -393,2 +395,3 @@ // eslint-disable-next-line @typescript-eslint/no-explicit-any | ||
@debug('CRUD') | ||
moveBlock(model: BaseBlockModel, targetModel: BaseBlockModel, top = true) { | ||
@@ -424,2 +427,3 @@ if (this.awareness.isReadonly()) { | ||
@debug('CRUD') | ||
updateBlock<T extends Partial<BlockProps>>(model: BaseBlockModel, props: T) { | ||
@@ -466,2 +470,3 @@ if (this.awareness.isReadonly()) { | ||
@debug('CRUD') | ||
deleteBlock( | ||
@@ -468,0 +473,0 @@ model: BaseBlockModel, |
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
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
Sorry, the diff of this file is not supported yet
499906
-0.36%8283
-0.4%+ Added
+ Added
- Removed