feat(editor): feature flag store extension builder (#12235)

<!-- This is an auto-generated comment: release notes by coderabbit.ai -->

## Summary by CodeRabbit

- **New Features**
  - Introduced feature flag synchronization for enhanced control over feature availability.
  - Added new configuration options for store management, allowing initialization and feature flag setup.

- **Improvements**
  - Updated how store extensions are accessed throughout the app for more robust initialization and configuration.
  - Enhanced workspace entities to support feature flag services, improving flexibility for workspace-specific features.
  - Centralized configuration of storage implementations for Electron environments.

- **Refactor**
  - Simplified editor module by removing direct feature flag synchronization logic.
  - Streamlined imports and configuration for storage modules, especially in Electron-based apps.

- **Bug Fixes**
  - Ensured consistent retrieval of store extensions across various modules and platforms.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Saul-Mirone
2025-05-12 09:29:14 +00:00
parent ef3775e8a9
commit bc00a58ae1
23 changed files with 178 additions and 68 deletions

View File

@@ -252,7 +252,8 @@ framework.scope(ServerScope).override(AuthProvider, resolver => {
const container = new Container(); const container = new Container();
getStoreManager() getStoreManager()
.get('store') .config.init()
.value.get('store')
.forEach(ext => { .forEach(ext => {
ext.setup(container); ext.setup(container);
}); });

View File

@@ -1,3 +1,4 @@
import { configureElectronStateStorageImpls } from '@affine/core/desktop/storage';
import { configureCommonModules } from '@affine/core/modules'; import { configureCommonModules } from '@affine/core/modules';
import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header'; import { configureAppTabsHeaderModule } from '@affine/core/modules/app-tabs-header';
import { configureDesktopBackupModule } from '@affine/core/modules/backup'; import { configureDesktopBackupModule } from '@affine/core/modules/backup';
@@ -11,7 +12,6 @@ import {
configureTraySettingModule, configureTraySettingModule,
} from '@affine/core/modules/editor-setting'; } from '@affine/core/modules/editor-setting';
import { configureFindInPageModule } from '@affine/core/modules/find-in-page'; import { configureFindInPageModule } from '@affine/core/modules/find-in-page';
import { configureElectronStateStorageImpls } from '@affine/core/modules/storage';
import { import {
ClientSchemeProvider, ClientSchemeProvider,
PopupWindowProvider, PopupWindowProvider,

View File

@@ -1,10 +1,8 @@
import { ThemeProvider } from '@affine/core/components/theme-provider'; import { ThemeProvider } from '@affine/core/components/theme-provider';
import { configureElectronStateStorageImpls } from '@affine/core/desktop/storage';
import { configureDesktopApiModule } from '@affine/core/modules/desktop-api'; import { configureDesktopApiModule } from '@affine/core/modules/desktop-api';
import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n'; import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n';
import { import { configureStorageModule } from '@affine/core/modules/storage';
configureElectronStateStorageImpls,
configureStorageModule,
} from '@affine/core/modules/storage';
import { configureEssentialThemeModule } from '@affine/core/modules/theme'; import { configureEssentialThemeModule } from '@affine/core/modules/theme';
import { appInfo } from '@affine/electron-api'; import { appInfo } from '@affine/electron-api';
import { Framework, FrameworkRoot } from '@toeverything/infra'; import { Framework, FrameworkRoot } from '@toeverything/infra';

View File

@@ -1,6 +1,7 @@
import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper'; import { useAppSettingHelper } from '@affine/core/components/hooks/affine/use-app-setting-helper';
import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls'; import { WindowsAppControls } from '@affine/core/components/pure/header/windows-app-controls';
import { ThemeProvider } from '@affine/core/components/theme-provider'; import { ThemeProvider } from '@affine/core/components/theme-provider';
import { configureElectronStateStorageImpls } from '@affine/core/desktop/storage';
import { configureAppSidebarModule } from '@affine/core/modules/app-sidebar'; import { configureAppSidebarModule } from '@affine/core/modules/app-sidebar';
import { ShellAppSidebarFallback } from '@affine/core/modules/app-sidebar/views'; import { ShellAppSidebarFallback } from '@affine/core/modules/app-sidebar/views';
import { import {
@@ -9,10 +10,7 @@ import {
} from '@affine/core/modules/app-tabs-header'; } from '@affine/core/modules/app-tabs-header';
import { configureDesktopApiModule } from '@affine/core/modules/desktop-api'; import { configureDesktopApiModule } from '@affine/core/modules/desktop-api';
import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n'; import { configureI18nModule, I18nProvider } from '@affine/core/modules/i18n';
import { import { configureStorageModule } from '@affine/core/modules/storage';
configureElectronStateStorageImpls,
configureStorageModule,
} from '@affine/core/modules/storage';
import { configureAppThemeModule } from '@affine/core/modules/theme'; import { configureAppThemeModule } from '@affine/core/modules/theme';
import { Framework, FrameworkRoot } from '@toeverything/infra'; import { Framework, FrameworkRoot } from '@toeverything/infra';

View File

@@ -264,7 +264,8 @@ const frameworkProvider = framework.provider();
const container = new Container(); const container = new Container();
getStoreManager() getStoreManager()
.get('store') .config.init()
.value.get('store')
.forEach(ext => { .forEach(ext => {
ext.setup(container); ext.setup(container);
}); });
@@ -309,7 +310,7 @@ const frameworkProvider = framework.provider();
collection: workspace.docCollection, collection: workspace.docCollection,
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
markdown, markdown,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
const docsService = workspace.scope.get(DocsService); const docsService = workspace.scope.get(DocsService);
if (docId) { if (docId) {

View File

@@ -252,7 +252,8 @@ export class TextRenderer extends WithDisposable(ShadowlessElement) {
} else { } else {
const container = new Container(); const container = new Container();
getStoreManager() getStoreManager()
.get('store') .config.init()
.value.get('store')
.forEach(ext => { .forEach(ext => {
ext.setup(container); ext.setup(container);
}); });

View File

@@ -7,7 +7,7 @@ import { markdownToMindmap } from '../mindmap-preview.js';
const container = new Container(); const container = new Container();
getStoreManager() getStoreManager()
.get('store') .value.get('store')
.forEach(ext => { .forEach(ext => {
ext.setup(container); ext.setup(container);
}); });

View File

@@ -99,7 +99,7 @@ export const usePageHelper = (docCollection: Workspace) => {
showImportModal({ showImportModal({
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
onSuccess, onSuccess,
onFail: message => { onFail: message => {
reject(new Error(message)); reject(new Error(message));

View File

@@ -0,0 +1,29 @@
import {
AFFINE_FLAGS,
type FeatureFlagService,
} from '@affine/core/modules/feature-flag';
import { FeatureFlagService as BSFeatureFlagService } from '@blocksuite/affine/shared/services';
import { type ExtensionType, StoreExtension } from '@blocksuite/affine/store';
export function getFeatureFlagSyncer(
featureFlagService: FeatureFlagService
): ExtensionType {
class FeatureFlagSyncer extends StoreExtension {
static override key = 'feature-flag-syncer';
override loaded() {
const bsFeatureFlagService = this.store.get(BSFeatureFlagService);
Object.entries(AFFINE_FLAGS).forEach(([key, flag]) => {
if (flag.category === 'blocksuite') {
const value =
featureFlagService.flags[key as keyof AFFINE_FLAGS].value;
if (value !== undefined) {
bsFeatureFlagService.setFlag(flag.bsFlag, value);
}
}
});
}
}
return FeatureFlagSyncer;
}

View File

@@ -0,0 +1,29 @@
import { getFeatureFlagSyncer } from '@affine/core/blocksuite/extensions/feature-flag/feature-flag-syncer';
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
import {
type StoreExtensionContext,
StoreExtensionProvider,
} from '@blocksuite/affine/ext-loader';
import { z } from 'zod';
const optionsSchema = z.object({
featureFlagService: z.instanceof(FeatureFlagService).optional(),
});
export class FeatureFlagStoreExtension extends StoreExtensionProvider {
override name = 'feature-flag-store-extension';
override schema = optionsSchema;
override setup(
context: StoreExtensionContext,
options?: z.infer<typeof optionsSchema>
) {
super.setup(context, options);
const featureFlagService = options?.featureFlagService;
if (!featureFlagService) {
return;
}
context.register(getFeatureFlagSyncer(featureFlagService));
}
}

View File

@@ -1,3 +1,5 @@
import { FeatureFlagStoreExtension } from '@affine/core/blocksuite/extensions/feature-flag';
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import { import {
type StoreExtensionContext, type StoreExtensionContext,
StoreExtensionManager, StoreExtensionManager,
@@ -18,11 +20,57 @@ class MigratingAffineStoreExtension extends StoreExtensionProvider {
} }
} }
const manager = new StoreExtensionManager([ interface Configure {
...getInternalStoreExtensions(), init: () => Configure;
MigratingAffineStoreExtension, featureFlag: (featureFlagService?: FeatureFlagService) => Configure;
]); value: StoreExtensionManager;
}
class StoreProvider {
static instance: StoreProvider | null = null;
static getInstance() {
if (!StoreProvider.instance) {
StoreProvider.instance = new StoreProvider();
}
return StoreProvider.instance;
}
private readonly _manager: StoreExtensionManager;
constructor() {
this._manager = new StoreExtensionManager([
...getInternalStoreExtensions(),
MigratingAffineStoreExtension,
FeatureFlagStoreExtension,
]);
}
get config(): Configure {
return {
init: this._initDefaultConfig,
featureFlag: this._configureFeatureFlag,
value: this._manager,
};
}
get value(): StoreExtensionManager {
return this._manager;
}
private readonly _initDefaultConfig = () => {
this.config.featureFlag();
return this.config;
};
private readonly _configureFeatureFlag = (
featureFlagService?: FeatureFlagService
) => {
this._manager.configure(FeatureFlagStoreExtension, { featureFlagService });
return this.config;
};
}
export function getStoreManager() { export function getStoreManager() {
return manager; return StoreProvider.getInstance();
} }

View File

@@ -13,7 +13,7 @@ import { beforeEach, describe, expect, test, vi } from 'vitest';
import { useBlockSuitePagePreview } from '../use-block-suite-page-preview'; import { useBlockSuitePagePreview } from '../use-block-suite-page-preview';
let docCollection: TestWorkspace; let docCollection: TestWorkspace;
const extensions = getStoreManager().get('store'); const extensions = getStoreManager().config.init().value.get('store');
beforeEach(async () => { beforeEach(async () => {
vi.useFakeTimers({ toFake: ['requestIdleCallback'] }); vi.useFakeTimers({ toFake: ['requestIdleCallback'] });

View File

@@ -146,7 +146,7 @@ const importConfigs: Record<ImportType, ImportConfig> = {
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
markdown: text, markdown: text,
fileName, fileName,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
if (docId) docIds.push(docId); if (docId) docIds.push(docId);
} }
@@ -165,7 +165,7 @@ const importConfigs: Record<ImportType, ImportConfig> = {
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
imported: file, imported: file,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
return { return {
docIds, docIds,
@@ -185,7 +185,7 @@ const importConfigs: Record<ImportType, ImportConfig> = {
const docId = await HtmlTransformer.importHTMLToDoc({ const docId = await HtmlTransformer.importHTMLToDoc({
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
html: text, html: text,
fileName, fileName,
}); });
@@ -207,7 +207,7 @@ const importConfigs: Record<ImportType, ImportConfig> = {
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
imported: file, imported: file,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
return { return {
docIds: pageIds, docIds: pageIds,

View File

@@ -0,0 +1,18 @@
import { DesktopApiService } from '@affine/core/modules/desktop-api';
import {
CacheStorage,
GlobalCache,
GlobalState,
} from '@affine/core/modules/storage';
import {
ElectronGlobalCache,
ElectronGlobalState,
} from '@affine/core/modules/storage/impls/electron';
import { IDBGlobalState } from '@affine/core/modules/storage/impls/storage';
import type { Framework } from '@toeverything/infra';
export function configureElectronStateStorageImpls(framework: Framework) {
framework.impl(GlobalCache, ElectronGlobalCache, [DesktopApiService]);
framework.impl(GlobalState, ElectronGlobalState, [DesktopApiService]);
framework.impl(CacheStorage, IDBGlobalState);
}

View File

@@ -5,10 +5,7 @@ import { DefaultTool } from '@blocksuite/affine/blocks/surface';
import type { DocTitle } from '@blocksuite/affine/fragments/doc-title'; import type { DocTitle } from '@blocksuite/affine/fragments/doc-title';
import type { DocMode, ReferenceParams } from '@blocksuite/affine/model'; import type { DocMode, ReferenceParams } from '@blocksuite/affine/model';
import { HighlightSelection } from '@blocksuite/affine/shared/selection'; import { HighlightSelection } from '@blocksuite/affine/shared/selection';
import { import { DocModeProvider } from '@blocksuite/affine/shared/services';
DocModeProvider,
FeatureFlagService as BSFeatureFlagService,
} from '@blocksuite/affine/shared/services';
import { GfxControllerIdentifier } from '@blocksuite/affine/std/gfx'; import { GfxControllerIdentifier } from '@blocksuite/affine/std/gfx';
import type { InlineEditor } from '@blocksuite/std/inline'; import type { InlineEditor } from '@blocksuite/std/inline';
import { effect } from '@preact/signals-core'; import { effect } from '@preact/signals-core';
@@ -17,7 +14,6 @@ import { defaults, isEqual, omit } from 'lodash-es';
import { skip } from 'rxjs'; import { skip } from 'rxjs';
import type { DocService } from '../../doc'; import type { DocService } from '../../doc';
import { AFFINE_FLAGS, type FeatureFlagService } from '../../feature-flag';
import { paramsParseOptions, preprocessParams } from '../../navigation/utils'; import { paramsParseOptions, preprocessParams } from '../../navigation/utils';
import type { WorkbenchView } from '../../workbench'; import type { WorkbenchView } from '../../workbench';
import type { WorkspaceService } from '../../workspace'; import type { WorkspaceService } from '../../workspace';
@@ -196,7 +192,6 @@ export class Editor extends Entity {
throw new Error('already bound'); throw new Error('already bound');
} }
this._setupBlocksuiteEditorFlags(editorContainer);
this.editorContainer$.next(editorContainer); this.editorContainer$.next(editorContainer);
const unsubs: (() => void)[] = []; const unsubs: (() => void)[] = [];
@@ -325,24 +320,9 @@ export class Editor extends Entity {
}; };
} }
private _setupBlocksuiteEditorFlags(editorContainer: AffineEditorContainer) {
const affineFeatureFlagService = this.featureFlagService;
const bsFeatureFlagService = editorContainer.doc.get(BSFeatureFlagService);
Object.entries(AFFINE_FLAGS).forEach(([key, flag]) => {
if (flag.category === 'blocksuite') {
const value =
affineFeatureFlagService.flags[key as keyof AFFINE_FLAGS].value;
if (value !== undefined) {
bsFeatureFlagService.setFlag(flag.bsFlag, value);
}
}
});
}
constructor( constructor(
private readonly docService: DocService, private readonly docService: DocService,
private readonly workspaceService: WorkspaceService, private readonly workspaceService: WorkspaceService
private readonly featureFlagService: FeatureFlagService
) { ) {
super(); super();
} }

View File

@@ -1,7 +1,6 @@
import { type Framework } from '@toeverything/infra'; import { type Framework } from '@toeverything/infra';
import { DocScope, DocService } from '../doc'; import { DocScope, DocService } from '../doc';
import { FeatureFlagService } from '../feature-flag';
import { WorkspaceScope, WorkspaceService } from '../workspace'; import { WorkspaceScope, WorkspaceService } from '../workspace';
import { Editor } from './entities/editor'; import { Editor } from './entities/editor';
import { EditorScope } from './scopes/editor'; import { EditorScope } from './scopes/editor';
@@ -19,7 +18,7 @@ export function configureEditorModule(framework: Framework) {
.scope(WorkspaceScope) .scope(WorkspaceScope)
.scope(DocScope) .scope(DocScope)
.service(EditorsService) .service(EditorsService)
.entity(Editor, [DocService, WorkspaceService, FeatureFlagService]) .entity(Editor, [DocService, WorkspaceService])
.scope(EditorScope) .scope(EditorScope)
.service(EditorService, [EditorScope]); .service(EditorService, [EditorScope]);
} }

View File

@@ -35,7 +35,7 @@ export class ImportClipperService extends Service {
collection: workspace.docCollection, collection: workspace.docCollection,
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
markdown: clipperInput.contentMarkdown, markdown: clipperInput.contentMarkdown,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
const docsService = workspace.scope.get(DocsService); const docsService = workspace.scope.get(DocsService);
if (docId) { if (docId) {
@@ -69,7 +69,7 @@ export class ImportClipperService extends Service {
collection: docCollection, collection: docCollection,
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
markdown: clipperInput.contentMarkdown, markdown: clipperInput.contentMarkdown,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
} }
); );

View File

@@ -61,7 +61,7 @@ export class IntegrationWriter extends Entity {
schema: getAFFiNEWorkspaceSchema(), schema: getAFFiNEWorkspaceSchema(),
markdown, markdown,
fileName: title, fileName: title,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
if (!newDocId) throw new Error('Failed to create a new doc'); if (!newDocId) throw new Error('Failed to create a new doc');
@@ -85,7 +85,7 @@ export class IntegrationWriter extends Entity {
doc, doc,
blockId: noteBlockId, blockId: noteBlockId,
markdown, markdown,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
} else if (updateStrategy === 'append') { } else if (updateStrategy === 'append') {
const pageBlockId = doc.getBlocksByFlavour('affine:page')[0]?.id; const pageBlockId = doc.getBlocksByFlavour('affine:page')[0]?.id;
@@ -94,7 +94,7 @@ export class IntegrationWriter extends Entity {
doc, doc,
blockId, blockId,
markdown: `---\n${markdown}`, markdown: `---\n${markdown}`,
extensions: getStoreManager().get('store'), extensions: getStoreManager().config.init().value.get('store'),
}); });
} else { } else {
throw new Error('Invalid update strategy'); throw new Error('Invalid update strategy');

View File

@@ -14,8 +14,6 @@ export { NbstoreService } from './services/nbstore';
import { type Framework } from '@toeverything/infra'; import { type Framework } from '@toeverything/infra';
import { DesktopApiService } from '../desktop-api';
import { ElectronGlobalCache, ElectronGlobalState } from './impls/electron';
import { import {
IDBGlobalState, IDBGlobalState,
LocalStorageGlobalCache, LocalStorageGlobalCache,
@@ -49,12 +47,6 @@ export function configureLocalStorageStateStorageImpls(framework: Framework) {
framework.impl(CacheStorage, IDBGlobalState); framework.impl(CacheStorage, IDBGlobalState);
} }
export function configureElectronStateStorageImpls(framework: Framework) {
framework.impl(GlobalCache, ElectronGlobalCache, [DesktopApiService]);
framework.impl(GlobalState, ElectronGlobalState, [DesktopApiService]);
framework.impl(CacheStorage, IDBGlobalState);
}
export function configureCommonGlobalStorageImpls(framework: Framework) { export function configureCommonGlobalStorageImpls(framework: Framework) {
framework.impl(GlobalSessionState, SessionStorageGlobalSessionState); framework.impl(GlobalSessionState, SessionStorageGlobalSessionState);
} }

View File

@@ -1,3 +1,4 @@
import type { FeatureFlagService } from '@affine/core/modules/feature-flag';
import type { Workspace as WorkspaceInterface } from '@blocksuite/affine/store'; import type { Workspace as WorkspaceInterface } from '@blocksuite/affine/store';
import { Entity, LiveData, yjsGetPath } from '@toeverything/infra'; import { Entity, LiveData, yjsGetPath } from '@toeverything/infra';
import type { Observable } from 'rxjs'; import type { Observable } from 'rxjs';
@@ -9,7 +10,10 @@ import type { WorkspaceScope } from '../scopes/workspace';
import { WorkspaceEngineService } from '../services/engine'; import { WorkspaceEngineService } from '../services/engine';
export class Workspace extends Entity { export class Workspace extends Entity {
constructor(public readonly scope: WorkspaceScope) { constructor(
public readonly scope: WorkspaceScope,
public readonly featureFlagService: FeatureFlagService
) {
super(); super();
} }
@@ -30,6 +34,7 @@ export class Workspace extends Entity {
this._docCollection = new WorkspaceImpl({ this._docCollection = new WorkspaceImpl({
id: this.openOptions.metadata.id, id: this.openOptions.metadata.id,
rootDoc: this.rootYDoc, rootDoc: this.rootYDoc,
featureFlagService: this.featureFlagService,
blobSource: { blobSource: {
get: async key => { get: async key => {
const record = await this.engine.blob.get(key); const record = await this.engine.blob.get(key);

View File

@@ -5,20 +5,21 @@ import {
type ExtensionType, type ExtensionType,
type GetStoreOptions, type GetStoreOptions,
StoreContainer, StoreContainer,
type Workspace,
type YBlock, type YBlock,
} from '@blocksuite/affine/store'; } from '@blocksuite/affine/store';
import { Awareness } from 'y-protocols/awareness.js'; import { Awareness } from 'y-protocols/awareness.js';
import * as Y from 'yjs'; import * as Y from 'yjs';
import type { WorkspaceImpl } from './workspace';
type DocOptions = { type DocOptions = {
id: string; id: string;
collection: Workspace; collection: WorkspaceImpl;
doc: Y.Doc; doc: Y.Doc;
}; };
export class DocImpl implements Doc { export class DocImpl implements Doc {
private readonly _collection: Workspace; private readonly _collection: WorkspaceImpl;
private readonly _storeContainer: StoreContainer; private readonly _storeContainer: StoreContainer;
@@ -136,7 +137,10 @@ export class DocImpl implements Doc {
extensions, extensions,
id, id,
}: GetStoreOptions = {}) { }: GetStoreOptions = {}) {
const storeExtensions = getStoreManager().get('store'); const storeExtensions = getStoreManager()
.config.init()
.featureFlag(this.workspace.featureFlagService)
.value.get('store');
const exts = storeExtensions const exts = storeExtensions
.concat(extensions ?? []) .concat(extensions ?? [])
.concat(this.storeExtensions); .concat(this.storeExtensions);

View File

@@ -19,6 +19,7 @@ import { Subject } from 'rxjs';
import type { Awareness } from 'y-protocols/awareness.js'; import type { Awareness } from 'y-protocols/awareness.js';
import type { Doc as YDoc } from 'yjs'; import type { Doc as YDoc } from 'yjs';
import type { FeatureFlagService } from '../../feature-flag';
import { DocImpl } from './doc'; import { DocImpl } from './doc';
import { WorkspaceMetaImpl } from './meta'; import { WorkspaceMetaImpl } from './meta';
@@ -29,6 +30,7 @@ type WorkspaceOptions = {
onLoadDoc?: (doc: YDoc) => void; onLoadDoc?: (doc: YDoc) => void;
onLoadAwareness?: (awareness: Awareness) => void; onLoadAwareness?: (awareness: Awareness) => void;
onCreateDoc?: (docId?: string) => string; onCreateDoc?: (docId?: string) => string;
featureFlagService?: FeatureFlagService;
}; };
export class WorkspaceImpl implements Workspace { export class WorkspaceImpl implements Workspace {
@@ -57,6 +59,7 @@ export class WorkspaceImpl implements Workspace {
readonly onLoadDoc?: (doc: YDoc) => void; readonly onLoadDoc?: (doc: YDoc) => void;
readonly onLoadAwareness?: (awareness: Awareness) => void; readonly onLoadAwareness?: (awareness: Awareness) => void;
readonly onCreateDoc?: (docId?: string) => string; readonly onCreateDoc?: (docId?: string) => string;
readonly featureFlagService?: FeatureFlagService;
constructor({ constructor({
id, id,
@@ -65,8 +68,10 @@ export class WorkspaceImpl implements Workspace {
onLoadDoc, onLoadDoc,
onLoadAwareness, onLoadAwareness,
onCreateDoc, onCreateDoc,
featureFlagService,
}: WorkspaceOptions) { }: WorkspaceOptions) {
this.id = id || ''; this.id = id || '';
this.featureFlagService = featureFlagService;
this.doc = rootDoc; this.doc = rootDoc;
this.onLoadDoc = onLoadDoc; this.onLoadDoc = onLoadDoc;
this.onLoadDoc?.(this.doc); this.onLoadDoc?.(this.doc);

View File

@@ -1,3 +1,5 @@
import { FeatureFlagService } from '@affine/core/modules/feature-flag';
export type { WorkspaceProfileInfo } from './entities/profile'; export type { WorkspaceProfileInfo } from './entities/profile';
export { Workspace } from './entities/workspace'; export { Workspace } from './entities/workspace';
export { WorkspaceEngineBeforeStart, WorkspaceInitialized } from './events'; export { WorkspaceEngineBeforeStart, WorkspaceInitialized } from './events';
@@ -70,7 +72,7 @@ export function configureWorkspaceModule(framework: Framework) {
]) ])
.scope(WorkspaceScope) .scope(WorkspaceScope)
.service(WorkspaceService) .service(WorkspaceService)
.entity(Workspace, [WorkspaceScope]) .entity(Workspace, [WorkspaceScope, FeatureFlagService])
.service(WorkspaceEngineService, [WorkspaceScope]) .service(WorkspaceEngineService, [WorkspaceScope])
.entity(WorkspaceEngine, [WorkspaceService, NbstoreService]) .entity(WorkspaceEngine, [WorkspaceService, NbstoreService])
.impl(WorkspaceLocalState, WorkspaceLocalStateImpl, [ .impl(WorkspaceLocalState, WorkspaceLocalStateImpl, [