refactor(core): move workspace implementation to affine (#9504)

This commit is contained in:
Saul-Mirone
2025-01-03 08:13:57 +00:00
parent 897c7d4284
commit cfd64f1fa5
18 changed files with 284 additions and 48 deletions
+7 -2
View File
@@ -18,7 +18,8 @@
"@blocksuite/global": "workspace:*", "@blocksuite/global": "workspace:*",
"@blocksuite/inline": "workspace:*", "@blocksuite/inline": "workspace:*",
"@blocksuite/presets": "workspace:*", "@blocksuite/presets": "workspace:*",
"@blocksuite/store": "workspace:*" "@blocksuite/store": "workspace:*",
"@blocksuite/sync": "workspace:*"
}, },
"exports": { "exports": {
".": "./src/index.ts", ".": "./src/index.ts",
@@ -37,7 +38,8 @@
"./inline/types": "./src/inline/types.ts", "./inline/types": "./src/inline/types.ts",
"./presets": "./src/presets/index.ts", "./presets": "./src/presets/index.ts",
"./blocks": "./src/blocks/index.ts", "./blocks": "./src/blocks/index.ts",
"./blocks/schemas": "./src/blocks/schemas.ts" "./blocks/schemas": "./src/blocks/schemas.ts",
"./sync": "./src/sync/index.ts"
}, },
"typesVersions": { "typesVersions": {
"*": { "*": {
@@ -88,6 +90,9 @@
], ],
"blocks/schemas": [ "blocks/schemas": [
"dist/blocks/schemas.d.ts" "dist/blocks/schemas.d.ts"
],
"sync": [
"dist/sync/index.d.ts"
] ]
} }
}, },
+1
View File
@@ -0,0 +1 @@
export * from '@blocksuite/sync';
@@ -70,10 +70,6 @@ const FLAGS_PRESET = {
readonly: {}, readonly: {},
} satisfies BlockSuiteFlags; } satisfies BlockSuiteFlags;
export interface StackItem {
meta: Map<'cursor-location' | 'selection-state', unknown>;
}
export class DocCollection implements Workspace { export class DocCollection implements Workspace {
protected readonly _schema: Schema; protected readonly _schema: Schema;
@@ -3,5 +3,5 @@ export { DocCollection } from './collection.js';
export type * from './doc/block-collection.js'; export type * from './doc/block-collection.js';
export * from './doc/index.js'; export * from './doc/index.js';
export * from './id.js'; export * from './id.js';
export type * from './meta.js'; export * from './meta.js';
export * from './workspace.js'; export * from './workspace.js';
@@ -84,3 +84,7 @@ export interface Workspace {
dispose(): void; dispose(): void;
} }
export interface StackItem {
meta: Map<'cursor-location' | 'selection-state', unknown>;
}
+3
View File
@@ -13,6 +13,9 @@
}, },
{ {
"path": "./store" "path": "./store"
},
{
"path": "./sync"
} }
] ]
} }
@@ -1,3 +1,4 @@
import { WorkspaceImpl } from '@affine/core/modules/workspace/impl/workspace';
import type { import type {
EditorHost, EditorHost,
TextRangePoint, TextRangePoint,
@@ -14,7 +15,7 @@ import {
} from '@blocksuite/affine/blocks'; } from '@blocksuite/affine/blocks';
import type { ServiceProvider } from '@blocksuite/affine/global/di'; import type { ServiceProvider } from '@blocksuite/affine/global/di';
import type { JobMiddleware, Schema } from '@blocksuite/affine/store'; import type { JobMiddleware, Schema } from '@blocksuite/affine/store';
import { DocCollection, Job } from '@blocksuite/affine/store'; import { Job } from '@blocksuite/affine/store';
import { assertExists } from '@blocksuite/global/utils'; import { assertExists } from '@blocksuite/global/utils';
import type { import type {
BlockModel, BlockModel,
@@ -207,7 +208,7 @@ export async function markDownToDoc(
additionalMiddlewares?: JobMiddleware[] additionalMiddlewares?: JobMiddleware[]
) { ) {
// Should not create a new doc in the original collection // Should not create a new doc in the original collection
const collection = new DocCollection({ const collection = new WorkspaceImpl({
schema, schema,
}); });
collection.meta.initialize(); collection.meta.initialize();
@@ -1,3 +1,4 @@
import { WorkspaceImpl } from '@affine/core/modules/workspace/impl/workspace';
import { BlockStdScope, type EditorHost } from '@blocksuite/affine/block-std'; import { BlockStdScope, type EditorHost } from '@blocksuite/affine/block-std';
import { import {
type AffineAIPanelWidgetConfig, type AffineAIPanelWidgetConfig,
@@ -6,7 +7,7 @@ import {
import { AffineSchemas } from '@blocksuite/affine/blocks/schemas'; import { AffineSchemas } from '@blocksuite/affine/blocks/schemas';
import { WithDisposable } from '@blocksuite/affine/global/utils'; import { WithDisposable } from '@blocksuite/affine/global/utils';
import type { Doc } from '@blocksuite/affine/store'; import type { Doc } from '@blocksuite/affine/store';
import { DocCollection, Schema } from '@blocksuite/affine/store'; import { Schema } from '@blocksuite/affine/store';
import { css, html, LitElement, nothing } from 'lit'; import { css, html, LitElement, nothing } from 'lit';
import { property, query } from 'lit/decorators.js'; import { property, query } from 'lit/decorators.js';
import { createRef, type Ref, ref } from 'lit/directives/ref.js'; import { createRef, type Ref, ref } from 'lit/directives/ref.js';
@@ -54,7 +55,7 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
private _doc!: Doc; private _doc!: Doc;
private _docCollection: DocCollection | null = null; private _docCollection: WorkspaceImpl | null = null;
@query('editor-host') @query('editor-host')
private accessor _editorHost!: EditorHost; private accessor _editorHost!: EditorHost;
@@ -220,7 +221,7 @@ export class AISlidesRenderer extends WithDisposable(LitElement) {
super.connectedCallback(); super.connectedCallback();
const schema = new Schema().register(AffineSchemas); const schema = new Schema().register(AffineSchemas);
const collection = new DocCollection({ const collection = new WorkspaceImpl({
schema, schema,
id: 'SLIDES_PREVIEW', id: 'SLIDES_PREVIEW',
}); });
@@ -1,3 +1,4 @@
import { WorkspaceImpl } from '@affine/core/modules/workspace/impl/workspace.js';
import { BlockStdScope, type EditorHost } from '@blocksuite/affine/block-std'; import { BlockStdScope, type EditorHost } from '@blocksuite/affine/block-std';
import { import {
MarkdownAdapter, MarkdownAdapter,
@@ -13,7 +14,6 @@ import type { ServiceProvider } from '@blocksuite/affine/global/di';
import { WithDisposable } from '@blocksuite/affine/global/utils'; import { WithDisposable } from '@blocksuite/affine/global/utils';
import { import {
type Doc, type Doc,
DocCollection,
type DocCollectionOptions, type DocCollectionOptions,
IdGeneratorType, IdGeneratorType,
Job, Job,
@@ -109,7 +109,7 @@ export class MiniMindmapPreview extends WithDisposable(LitElement) {
awarenessSources: [], awarenessSources: [],
}; };
const collection = new DocCollection(options); const collection = new WorkspaceImpl(options);
collection.meta.initialize(); collection.meta.initialize();
collection.start(); collection.start();
@@ -2,12 +2,13 @@ import { useDocMetaHelper } from '@affine/core/components/hooks/use-block-suite-
import { useDocCollectionPage } from '@affine/core/components/hooks/use-block-suite-workspace-page'; import { useDocCollectionPage } from '@affine/core/components/hooks/use-block-suite-workspace-page';
import { FetchService, GraphQLService } from '@affine/core/modules/cloud'; import { FetchService, GraphQLService } from '@affine/core/modules/cloud';
import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace'; import { getAFFiNEWorkspaceSchema } from '@affine/core/modules/workspace';
import { WorkspaceImpl } from '@affine/core/modules/workspace/impl/workspace';
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import type { ListHistoryQuery } from '@affine/graphql'; import type { ListHistoryQuery } from '@affine/graphql';
import { listHistoryQuery, recoverDocMutation } from '@affine/graphql'; import { listHistoryQuery, recoverDocMutation } from '@affine/graphql';
import { i18nTime } from '@affine/i18n'; import { i18nTime } from '@affine/i18n';
import { assertEquals } from '@blocksuite/affine/global/utils'; import { assertEquals } from '@blocksuite/affine/global/utils';
import { DocCollection, type Workspace } from '@blocksuite/affine/store'; import type { Workspace } from '@blocksuite/affine/store';
import { useService } from '@toeverything/infra'; import { useService } from '@toeverything/infra';
import { useEffect, useMemo } from 'react'; import { useEffect, useMemo } from 'react';
import useSWRImmutable from 'swr/immutable'; import useSWRImmutable from 'swr/immutable';
@@ -114,11 +115,9 @@ const getOrCreateShellWorkspace = (
fetchService, fetchService,
graphQLService graphQLService
); );
docCollection = new DocCollection({ docCollection = new WorkspaceImpl({
id: workspaceId, id: workspaceId,
blobSources: { blobSource: blobStorage,
main: blobStorage,
},
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
}); });
docCollectionMap.set(workspaceId, docCollection); docCollectionMap.set(workspaceId, docCollection);
@@ -1,16 +1,17 @@
import { WorkspaceImpl } from '@affine/core/modules/workspace/impl/workspace';
import { AffineSchemas } from '@blocksuite/affine/blocks'; import { AffineSchemas } from '@blocksuite/affine/blocks';
import type { Doc, DocSnapshot } from '@blocksuite/affine/store'; import type { Doc, DocSnapshot } from '@blocksuite/affine/store';
import { DocCollection, Job, Schema } from '@blocksuite/affine/store'; import { Job, Schema } from '@blocksuite/affine/store';
const getCollection = (() => { const getCollection = (() => {
let collection: DocCollection | null = null; let collection: WorkspaceImpl | null = null;
return async function () { return async function () {
if (collection) { if (collection) {
return collection; return collection;
} }
const schema = new Schema(); const schema = new Schema();
schema.register(AffineSchemas); schema.register(AffineSchemas);
collection = new DocCollection({ schema }); collection = new WorkspaceImpl({ schema });
collection.meta.initialize(); collection.meta.initialize();
return collection; return collection;
}; };
@@ -14,7 +14,6 @@ import {
import { Container } from '@blocksuite/affine/global/di'; import { Container } from '@blocksuite/affine/global/di';
import { import {
createYProxy, createYProxy,
DocCollection,
type DraftModel, type DraftModel,
Job, Job,
type JobMiddleware, type JobMiddleware,
@@ -35,6 +34,7 @@ import {
} from 'yjs'; } from 'yjs';
import { getAFFiNEWorkspaceSchema } from '../../workspace/global-schema'; import { getAFFiNEWorkspaceSchema } from '../../workspace/global-schema';
import { WorkspaceImpl } from '../../workspace/impl/workspace';
import type { BlockIndexSchema, DocIndexSchema } from '../schema'; import type { BlockIndexSchema, DocIndexSchema } from '../schema';
import type { import type {
WorkerIngoingMessage, WorkerIngoingMessage,
@@ -118,7 +118,7 @@ const bookmarkFlavours = new Set([
'affine:embed-loom', 'affine:embed-loom',
]); ]);
const markdownPreviewDocCollection = new DocCollection({ const markdownPreviewDocCollection = new WorkspaceImpl({
id: 'indexer', id: 'indexer',
schema: blocksuiteSchema, schema: blocksuiteSchema,
}); });
@@ -5,7 +5,6 @@ import {
getWorkspaceInfoQuery, getWorkspaceInfoQuery,
getWorkspacesQuery, getWorkspacesQuery,
} from '@affine/graphql'; } from '@affine/graphql';
import { DocCollection } from '@blocksuite/affine/store';
import { import {
type BlobStorage, type BlobStorage,
catchErrorInto, catchErrorInto,
@@ -20,7 +19,6 @@ import {
Service, Service,
} from '@toeverything/infra'; } from '@toeverything/infra';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { nanoid } from 'nanoid';
import { EMPTY, map, mergeMap, Observable, switchMap } from 'rxjs'; import { EMPTY, map, mergeMap, Observable, switchMap } from 'rxjs';
import { encodeStateAsUpdate } from 'yjs'; import { encodeStateAsUpdate } from 'yjs';
@@ -43,6 +41,7 @@ import {
type WorkspaceMetadata, type WorkspaceMetadata,
type WorkspaceProfileInfo, type WorkspaceProfileInfo,
} from '../../workspace'; } from '../../workspace';
import { WorkspaceImpl } from '../../workspace/impl/workspace';
import type { WorkspaceEngineStorageProvider } from '../providers/engine'; import type { WorkspaceEngineStorageProvider } from '../providers/engine';
import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel'; import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel';
import { CloudAwarenessConnection } from './engine/awareness-cloud'; import { CloudAwarenessConnection } from './engine/awareness-cloud';
@@ -101,7 +100,7 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
async createWorkspace( async createWorkspace(
initial: ( initial: (
docCollection: DocCollection, docCollection: WorkspaceImpl,
blobStorage: BlobStorage, blobStorage: BlobStorage,
docStorage: DocStorage docStorage: DocStorage
) => Promise<void> ) => Promise<void>
@@ -117,13 +116,10 @@ class CloudWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
const blobStorage = this.storageProvider.getBlobStorage(workspaceId); const blobStorage = this.storageProvider.getBlobStorage(workspaceId);
const docStorage = this.storageProvider.getDocStorage(workspaceId); const docStorage = this.storageProvider.getDocStorage(workspaceId);
const docCollection = new DocCollection({ const docCollection = new WorkspaceImpl({
id: workspaceId, id: workspaceId,
idGenerator: () => nanoid(),
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
blobSources: { blobSource: blobStorage,
main: blobStorage,
},
}); });
try { try {
@@ -1,5 +1,4 @@
import { DebugLogger } from '@affine/debug'; import { DebugLogger } from '@affine/debug';
import { DocCollection } from '@blocksuite/affine/store';
import type { import type {
BlobStorage, BlobStorage,
DocStorage, DocStorage,
@@ -20,6 +19,7 @@ import {
type WorkspaceMetadata, type WorkspaceMetadata,
type WorkspaceProfileInfo, type WorkspaceProfileInfo,
} from '../../workspace'; } from '../../workspace';
import { WorkspaceImpl } from '../../workspace/impl/workspace';
import type { WorkspaceEngineStorageProvider } from '../providers/engine'; import type { WorkspaceEngineStorageProvider } from '../providers/engine';
import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel'; import { BroadcastChannelAwarenessConnection } from './engine/awareness-broadcast-channel';
import { StaticBlobStorage } from './engine/blob-static'; import { StaticBlobStorage } from './engine/blob-static';
@@ -79,7 +79,7 @@ class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
} }
async createWorkspace( async createWorkspace(
initial: ( initial: (
docCollection: DocCollection, docCollection: WorkspaceImpl,
blobStorage: BlobStorage, blobStorage: BlobStorage,
docStorage: DocStorage docStorage: DocStorage
) => Promise<void> ) => Promise<void>
@@ -90,11 +90,10 @@ class LocalWorkspaceFlavourProvider implements WorkspaceFlavourProvider {
const blobStorage = this.storageProvider.getBlobStorage(id); const blobStorage = this.storageProvider.getBlobStorage(id);
const docStorage = this.storageProvider.getDocStorage(id); const docStorage = this.storageProvider.getDocStorage(id);
const docCollection = new DocCollection({ const docCollection = new WorkspaceImpl({
id: id, id: id,
idGenerator: () => nanoid(),
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
blobSources: { main: blobStorage }, blobSource: blobStorage,
}); });
try { try {
@@ -1,14 +1,11 @@
import { import type { Workspace as WorkspaceInterface } from '@blocksuite/affine/store';
DocCollection,
type Workspace as BSWorkspace,
} from '@blocksuite/affine/store';
import { Entity, LiveData } from '@toeverything/infra'; import { Entity, LiveData } from '@toeverything/infra';
import { nanoid } from 'nanoid';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import type { Awareness } from 'y-protocols/awareness.js'; import type { Awareness } from 'y-protocols/awareness.js';
import { WorkspaceDBService } from '../../db'; import { WorkspaceDBService } from '../../db';
import { getAFFiNEWorkspaceSchema } from '../global-schema'; import { getAFFiNEWorkspaceSchema } from '../global-schema';
import { WorkspaceImpl } from '../impl/workspace';
import type { WorkspaceScope } from '../scopes/workspace'; import type { WorkspaceScope } from '../scopes/workspace';
import { WorkspaceEngineService } from '../services/engine'; import { WorkspaceEngineService } from '../services/engine';
@@ -25,16 +22,13 @@ export class Workspace extends Entity {
readonly flavour = this.meta.flavour; readonly flavour = this.meta.flavour;
_docCollection: BSWorkspace | null = null; _docCollection: WorkspaceInterface | null = null;
get docCollection() { get docCollection() {
if (!this._docCollection) { if (!this._docCollection) {
this._docCollection = new DocCollection({ this._docCollection = new WorkspaceImpl({
id: this.openOptions.metadata.id, id: this.openOptions.metadata.id,
blobSources: { blobSource: this.engine.blob,
main: this.engine.blob,
},
idGenerator: () => nanoid(),
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
}); });
this._docCollection.slots.docCreated.on(id => { this._docCollection.slots.docCreated.on(id => {
@@ -0,0 +1,234 @@
import {
BlockSuiteError,
ErrorCode,
} from '@blocksuite/affine/global/exceptions';
import type { BlockSuiteFlags } from '@blocksuite/affine/global/types';
import { NoopLogger, Slot } from '@blocksuite/affine/global/utils';
import {
AwarenessStore,
BlockCollection,
BlockSuiteDoc,
type CreateDocOptions,
type Doc,
DocCollectionMeta,
type GetDocOptions,
type IdGenerator,
nanoid,
type Schema,
type Workspace,
} from '@blocksuite/affine/store';
import {
AwarenessEngine,
BlobEngine,
type BlobSource,
DocEngine,
MemoryBlobSource,
NoopDocSource,
} from '@blocksuite/affine/sync';
import { Awareness } from 'y-protocols/awareness.js';
type WorkspaceOptions = {
id?: string;
schema: Schema;
blobSource?: BlobSource;
};
const FLAGS_PRESET = {
enable_synced_doc_block: false,
enable_pie_menu: false,
enable_database_number_formatting: false,
enable_database_attachment_note: false,
enable_database_full_width: false,
enable_block_query: false,
enable_lasso_tool: false,
enable_edgeless_text: true,
enable_ai_onboarding: false,
enable_ai_chat_block: false,
enable_color_picker: false,
enable_mind_map_import: false,
enable_advanced_block_visibility: false,
enable_shape_shadow_blur: false,
enable_mobile_keyboard_toolbar: false,
enable_mobile_linked_doc_menu: false,
readonly: {},
} satisfies BlockSuiteFlags;
export class WorkspaceImpl implements Workspace {
protected readonly _schema: Schema;
readonly awarenessStore: AwarenessStore;
readonly awarenessSync: AwarenessEngine;
readonly blobSync: BlobEngine;
readonly blockCollections = new Map<string, BlockCollection>();
readonly doc: BlockSuiteDoc;
readonly docSync: DocEngine;
readonly id: string;
readonly idGenerator: IdGenerator;
meta: DocCollectionMeta;
slots = {
docListUpdated: new Slot(),
docRemoved: new Slot<string>(),
docCreated: new Slot<string>(),
};
get docs() {
return this.blockCollections;
}
get schema() {
return this._schema;
}
constructor({ id, schema, blobSource }: WorkspaceOptions) {
this._schema = schema;
this.id = id || '';
this.doc = new BlockSuiteDoc({ guid: id });
this.awarenessStore = new AwarenessStore(new Awareness(this.doc), {
...FLAGS_PRESET,
readonly: {},
});
blobSource = blobSource ?? new MemoryBlobSource();
const docSource = new NoopDocSource();
const logger = new NoopLogger();
this.awarenessSync = new AwarenessEngine(this.awarenessStore.awareness, []);
this.docSync = new DocEngine(this.doc, docSource, [], logger);
this.blobSync = new BlobEngine(blobSource, [], logger);
this.idGenerator = nanoid;
this.meta = new DocCollectionMeta(this.doc);
this._bindDocMetaEvents();
}
private _bindDocMetaEvents() {
this.meta.docMetaAdded.on(docId => {
const doc = new BlockCollection({
id: docId,
collection: this,
doc: this.doc,
awarenessStore: this.awarenessStore,
idGenerator: this.idGenerator,
});
this.blockCollections.set(doc.id, doc);
});
this.meta.docMetaUpdated.on(() => this.slots.docListUpdated.emit());
this.meta.docMetaRemoved.on(id => {
const space = this.getBlockCollection(id);
if (!space) return;
this.blockCollections.delete(id);
space.remove();
this.slots.docRemoved.emit(id);
});
}
private _hasDoc(docId: string) {
return this.docs.has(docId);
}
/**
* Verify that all data has been successfully saved to the primary storage.
* Return true if the data transfer is complete and it is secure to terminate the synchronization operation.
*/
canGracefulStop() {
this.docSync.canGracefulStop();
}
/**
* By default, only an empty doc will be created.
* If the `init` parameter is passed, a `surface`, `note`, and `paragraph` block
* will be created in the doc simultaneously.
*/
createDoc(options: CreateDocOptions = {}) {
const { id: docId = this.idGenerator(), query, readonly } = options;
if (this._hasDoc(docId)) {
throw new BlockSuiteError(
ErrorCode.DocCollectionError,
'doc already exists'
);
}
this.meta.addDocMeta({
id: docId,
title: '',
createDate: Date.now(),
tags: [],
});
this.slots.docCreated.emit(docId);
return this.getDoc(docId, { query, readonly }) as Doc;
}
dispose() {
this.awarenessStore.destroy();
}
/**
* Terminate the data sync process forcefully, which may cause data loss.
* It is advised to invoke `canGracefulStop` before calling this method.
*/
forceStop() {
this.docSync.forceStop();
this.blobSync.stop();
this.awarenessSync.disconnect();
}
getBlockCollection(docId: string): BlockCollection | null {
const space = this.docs.get(docId) as BlockCollection | undefined;
return space ?? null;
}
getDoc(docId: string, options?: GetDocOptions): Doc | null {
const collection = this.getBlockCollection(docId);
return collection?.getDoc(options) ?? null;
}
removeDoc(docId: string) {
const docMeta = this.meta.getDocMeta(docId);
if (!docMeta) {
throw new BlockSuiteError(
ErrorCode.DocCollectionError,
`doc meta not found: ${docId}`
);
}
const blockCollection = this.getBlockCollection(docId);
if (!blockCollection) return;
blockCollection.dispose();
this.meta.removeDocMeta(docId);
this.blockCollections.delete(docId);
}
/**
* Start the data sync process
*/
start() {
this.docSync.start();
this.blobSync.start();
this.awarenessSync.connect();
}
/**
* Wait for all data has been successfully saved to the primary storage.
*/
waitForGracefulStop(abort?: AbortSignal) {
return this.docSync.waitForGracefulStop(abort);
}
waitForSynced() {
return this.docSync.waitForSynced();
}
}
+1
View File
@@ -11,6 +11,7 @@ export const PackageList = [
'blocksuite/framework/inline', 'blocksuite/framework/inline',
'blocksuite/presets', 'blocksuite/presets',
'blocksuite/framework/store', 'blocksuite/framework/store',
'blocksuite/framework/sync',
], ],
}, },
{ {
+1
View File
@@ -3804,6 +3804,7 @@ __metadata:
"@blocksuite/inline": "workspace:*" "@blocksuite/inline": "workspace:*"
"@blocksuite/presets": "workspace:*" "@blocksuite/presets": "workspace:*"
"@blocksuite/store": "workspace:*" "@blocksuite/store": "workspace:*"
"@blocksuite/sync": "workspace:*"
languageName: unknown languageName: unknown
linkType: soft linkType: soft