feat(editor): affine extension provider and manager (#11822)

Closes: BS-3186

# @blocksuite/affine-ext-loader

Blocksuite extension loader system for AFFiNE, providing a structured way to manage and load extensions in different contexts.

## Usage

### Basic Extension Provider

```typescript
import { BaseExtensionProvider } from '@blocksuite/affine-ext-loader';
import { z } from 'zod';

// Create a custom provider with options
class MyProvider extends BaseExtensionProvider<'my-scope', { enabled: boolean }> {
  name = 'MyProvider';

  schema = z.object({
    enabled: z.boolean(),
  });

  setup(context: Context<'my-scope'>, options?: { enabled: boolean }) {
    super.setup(context, options);
    // Custom setup logic
  }
}
```

### Store Extensions

```typescript
import { StoreExtensionProvider, StoreExtensionManager } from '@blocksuite/affine-ext-loader';
import { z } from 'zod';

// Create a store provider with custom options
class MyStoreProvider extends StoreExtensionProvider<{ cacheSize: number }> {
  override name = 'MyStoreProvider';

  override schema = z.object({
    cacheSize: z.number().min(0),
  });

  override setup(context: StoreExtensionContext, options?: { cacheSize: number }) {
    super.setup(context, options);
    context.register([Ext1, Ext2, Ext3]);
  }
}

// Create and use the store extension manager
const manager = new StoreExtensionManager([MyStoreProvider]);
manager.configure(MyStoreProvider, { cacheSize: 100 });
const extensions = manager.get('store');
```

### View Extensions

```typescript
import { ViewExtensionProvider, ViewExtensionManager } from '@blocksuite/affine-ext-loader';
import { z } from 'zod';

// Create a view provider with custom options
class MyViewProvider extends ViewExtensionProvider<{ theme: string }> {
  override name = 'MyViewProvider';

  override schema = z.object({
    theme: z.enum(['light', 'dark']),
  });

  override setup(context: ViewExtensionContext, options?: { theme: string }) {
    super.setup(context, options);

    context.register([CommonExt]);
    if (context.scope === 'page') {
      context.register([PageExt]);
    } else if (context.scope === 'edgeless') {
      context.register([EdgelessExt]);
    }
    if (options?.theme === 'dark') {
      context.register([DarkModeExt]);
    }
  }

  // Override effect to run one-time initialization logic
  override effect() {
    // This will only run once per provider class
    console.log('Initializing MyViewProvider');
    // Register lit elements
    this.registerLitElements();
  }
}

// Create and use the view extension manager
const manager = new ViewExtensionManager([MyViewProvider]);
manager.configure(MyViewProvider, { theme: 'dark' });

// Get extensions for different view scopes
const pageExtensions = manager.get('page');
const edgelessExtensions = manager.get('edgeless');
```

### One-time Initialization with Effect

View extensions support one-time initialization through the `effect` method. This method is called automatically during setup, but only once per provider class. It's useful for:

- Initializing global state
- Registering lit elements
- Setting up shared resources

```typescript
class MyViewProvider extends ViewExtensionProvider {
  override effect() {
    // This will only run once, even if multiple instances are created
    initializeGlobalState();
    registerLitElements();
    setupGlobalEventListeners();
  }
}
```

### Available View Scopes

The view extension system supports the following scopes:

- `page` - Standard page view
- `edgeless` - Edgeless (whiteboard) view
- `preview-page` - Page preview view
- `preview-edgeless` - Edgeless preview view
- `mobile-page` - Mobile page view
- `mobile-edgeless` - Mobile edgeless view

### Extension Configuration

Extensions can be configured using the `configure` method:

```typescript
// Set configuration directly
manager.configure(MyProvider, { enabled: true });

// Update configuration using a function
manager.configure(MyProvider, prev => {
  if (!prev) return prev;
  return {
    ...prev,
    enabled: !prev.enabled,
  };
});

// Remove configuration
manager.configure(MyProvider, undefined);
```

### Dependency Injection

Both store and view extension managers support dependency injection:

```typescript
// Access the manager through the di container
const viewManager = std.get(ViewExtensionManagerIdentifier);
const pagePreviewExtension = viewManager.get('preview-page');
```
This commit is contained in:
Saul-Mirone
2025-04-21 04:27:18 +00:00
parent 535b5ff7c2
commit 70ddae15ef
15 changed files with 800 additions and 0 deletions
+132
View File
@@ -0,0 +1,132 @@
# @blocksuite/affine-ext-loader
Blocksuite extension loader system for AFFiNE, providing a structured way to manage and load extensions in different contexts.
## Usage
### Basic Extension Provider
```typescript
import { BaseExtensionProvider } from '@blocksuite/affine-ext-loader';
import { z } from 'zod';
// Create a custom provider with options
class MyProvider extends BaseExtensionProvider<'my-scope', { enabled: boolean }> {
name = 'MyProvider';
schema = z.object({
enabled: z.boolean(),
});
setup(context: Context<'my-scope'>, options?: { enabled: boolean }) {
super.setup(context, options);
// Custom setup logic
}
}
```
### Store Extensions
```typescript
import { StoreExtensionProvider, StoreExtensionManager } from '@blocksuite/affine-ext-loader';
import { z } from 'zod';
// Create a store provider with custom options
class MyStoreProvider extends StoreExtensionProvider<{ cacheSize: number }> {
override name = 'MyStoreProvider';
override schema = z.object({
cacheSize: z.number().min(0),
});
override setup(context: StoreExtensionContext, options?: { cacheSize: number }) {
super.setup(context, options);
context.register([Ext1, Ext2, Ext3]);
}
}
// Create and use the store extension manager
const manager = new StoreExtensionManager([MyStoreProvider]);
manager.configure(MyStoreProvider, { cacheSize: 100 });
const extensions = manager.get('store');
```
### View Extensions
```typescript
import { ViewExtensionProvider, ViewExtensionManager } from '@blocksuite/affine-ext-loader';
import { z } from 'zod';
// Create a view provider with custom options
class MyViewProvider extends ViewExtensionProvider<{ theme: string }> {
override name = 'MyViewProvider';
override schema = z.object({
theme: z.enum(['light', 'dark']),
});
override setup(context: ViewExtensionContext, options?: { theme: string }) {
super.setup(context, options);
context.register([CommonExt]);
if (context.scope === 'page') {
context.register([PageExt]);
} else if (context.scope === 'edgeless') {
context.register([EdgelessExt]);
}
if (options?.theme === 'dark') {
context.register([DarkModeExt]);
}
}
}
// Create and use the view extension manager
const manager = new ViewExtensionManager([MyViewProvider]);
manager.configure(MyViewProvider, { theme: 'dark' });
// Get extensions for different view scopes
const pageExtensions = manager.get('page');
const edgelessExtensions = manager.get('edgeless');
```
### Available View Scopes
The view extension system supports the following scopes:
- `page` - Standard page view
- `edgeless` - Edgeless (whiteboard) view
- `preview-page` - Page preview view
- `preview-edgeless` - Edgeless preview view
- `mobile-page` - Mobile page view
- `mobile-edgeless` - Mobile edgeless view
### Extension Configuration
Extensions can be configured using the `configure` method:
```typescript
// Set configuration directly
manager.configure(MyProvider, { enabled: true });
// Update configuration using a function
manager.configure(MyProvider, prev => {
if (!prev) return prev;
return {
...prev,
enabled: !prev.enabled,
};
});
// Remove configuration
manager.configure(MyProvider, undefined);
```
### Dependency Injection
Both store and view extension managers support dependency injection:
```typescript
// Access the manager through the di container
const viewManager = std.get(ViewExtensionManagerIdentifier);
const pagePreviewExtension = viewManager.get('preview-page');
```
+30
View File
@@ -0,0 +1,30 @@
{
"name": "@blocksuite/affine-ext-loader",
"description": "Extension loader for affine",
"type": "module",
"scripts": {
"build": "tsc"
},
"sideEffects": false,
"keywords": [],
"author": "toeverything",
"license": "MIT",
"dependencies": {
"@blocksuite/global": "workspace:*",
"@blocksuite/store": "workspace:*",
"zod": "^3.23.8"
},
"devDependencies": {
"vitest": "3.1.1"
},
"exports": {
".": "./src/index.ts"
},
"files": [
"src",
"dist",
"!src/__tests__",
"!dist/__tests__"
],
"version": "0.21.0"
}
@@ -0,0 +1,167 @@
import { Container } from '@blocksuite/global/di';
import { type ExtensionType } from '@blocksuite/store';
import { describe, expect, it, vi } from 'vitest';
import { z } from 'zod';
import { ExtensionManager } from '../manager';
import {
StoreExtensionManager,
StoreExtensionManagerIdentifier,
} from '../store-manager';
import {
type StoreExtensionContext,
StoreExtensionProvider,
} from '../store-provider';
import {
type ViewExtensionContext,
ViewExtensionProvider,
} from '../view-provider';
export const Ext1: ExtensionType = {
setup: () => {},
};
export const Ext2: ExtensionType = {
setup: () => {},
};
export const Ext3: ExtensionType = {
setup: () => {},
};
export const Ext4: ExtensionType = {
setup: () => {},
};
export const Ext5: ExtensionType = {
setup: () => {},
};
it('should be able to load extensions', () => {
class StoreExt1 extends StoreExtensionProvider {
override name = 'StoreExt1';
override setup(context: StoreExtensionContext) {
super.setup(context);
context.register(Ext1);
}
}
const manager = new ExtensionManager([StoreExt1]);
const storeExtensions = manager.get('store');
expect(storeExtensions).toEqual([Ext1]);
});
describe('multiple scopes', () => {
const setup1 = vi.fn();
const setup2 = vi.fn();
class ViewExt1 extends ViewExtensionProvider {
override name = 'ViewExt1';
override setup(context: ViewExtensionContext) {
super.setup(context);
if (context.scope === 'page') {
setup1();
context.register(Ext2);
}
if (context.scope === 'edgeless') {
setup2();
context.register(Ext3);
}
}
}
class ViewExt2 extends ViewExtensionProvider {
override name = 'ViewExt2';
override setup(context: ViewExtensionContext) {
super.setup(context);
if (context.scope === 'page') {
context.register(Ext4);
}
if (context.scope === 'edgeless') {
context.register(Ext5);
}
}
}
const manager = new ExtensionManager([ViewExt1, ViewExt2]);
const pageExtensions = manager.get('page');
const edgelessExtensions = manager.get('edgeless');
it('should be able to load extensions from different scopes', () => {
expect(pageExtensions).toEqual([Ext2, Ext4]);
expect(edgelessExtensions).toEqual([Ext3, Ext5]);
});
it('should setup be cached', () => {
manager.get('page');
manager.get('edgeless');
expect(setup1).toHaveBeenCalledTimes(1);
expect(setup2).toHaveBeenCalledTimes(1);
manager.get('page');
manager.get('edgeless');
expect(setup1).toHaveBeenCalledTimes(1);
expect(setup2).toHaveBeenCalledTimes(1);
});
});
it('should be able to validate schema', () => {
type Option = { foo: number; bar: string };
const setupOption = vi.fn();
class ViewExt1 extends ViewExtensionProvider<Option> {
override name = 'ViewExt1';
override schema = z.object({
foo: z.number(),
bar: z.string(),
});
override setup(context: ViewExtensionContext, option?: Option) {
super.setup(context, option);
if (context.scope === 'page') {
setupOption(option);
context.register(Ext1);
}
if (context.scope === 'edgeless') {
setupOption(option);
context.register(Ext2);
}
}
}
const manager = new ExtensionManager([ViewExt1]);
manager.configure(ViewExt1, { foo: 1, bar: '2' });
manager.configure(ViewExt1, prev => {
if (!prev) {
return prev;
}
return {
...prev,
foo: prev.foo + 1,
};
});
let viewExtensions = manager.get('page');
expect(viewExtensions).toEqual([Ext1]);
expect(setupOption).toHaveBeenCalledWith({ foo: 2, bar: '2' });
setupOption.mockClear();
manager.configure(ViewExt1, undefined);
viewExtensions = manager.get('edgeless');
expect(viewExtensions).toEqual([Ext2]);
expect(setupOption).toHaveBeenCalledWith(undefined);
viewExtensions = manager.get('page');
expect(setupOption).toHaveBeenCalledWith(undefined);
});
it('should extension manager be able to be injected', () => {
class StoreExt1 extends StoreExtensionProvider {
override name = 'StoreExt1';
override setup(context: StoreExtensionContext) {
super.setup(context);
context.register(Ext1);
}
}
const manager = new StoreExtensionManager([StoreExt1]);
const extensions = manager.get('store');
const container = new Container();
extensions.forEach(ext => {
ext.setup(container);
});
const provider = container.provider();
expect(provider.get(StoreExtensionManagerIdentifier)).toBe(manager);
});
@@ -0,0 +1,69 @@
import type { ExtensionType } from '@blocksuite/store';
import { z, type ZodSchema } from 'zod';
/**
* An empty object type used as a default for extension provider options
* when no specific options are needed.
*/
export type Empty = {};
/**
* Context object provided to extension providers during setup.
* Contains the scope information and a registration function for extensions.
*
* @typeParam Scope - The type of scope identifiers used for categorizing extensions
*/
export type Context<Scope extends string> = {
/** The scope this context is associated with */
scope: Scope;
/** Function to register one or more extensions */
register(extensions: ExtensionType[] | ExtensionType): void;
};
/**
* Base class for all extension providers.
* Provides common functionality for managing extensions and validating options.
*
* @typeParam Scope - The type of scope identifiers used for categorizing extensions
* @typeParam Options - The type of configuration options for the provider
*
* @example
* ```ts
* class MyProvider extends BaseExtensionProvider<'my-scope', { enabled: boolean }> {
* name = 'MyProvider';
*
* schema = z.object({
* enabled: z.boolean()
* });
*
* setup(context: Context<'my-scope'>, options?: { enabled: boolean }) {
* super.setup(context, options);
* // Custom setup logic
* }
* }
* ```
*/
export class BaseExtensionProvider<
Scope extends string,
Options extends object = Empty,
> {
/** The name of the provider */
name = 'BaseExtension';
/** Zod schema for validating provider options */
schema: ZodSchema = z.object({});
/**
* Sets up the provider with the given context and options.
* Validates the options against the schema if provided.
*
* @param context - The context object containing scope and registration function
* @param option - Optional configuration options for the provider
*/
setup(context: Context<Scope>, option?: Options) {
if (option) {
this.schema.parse(option);
}
context;
}
}
@@ -0,0 +1,4 @@
export * from './store-manager';
export * from './store-provider';
export * from './view-manager';
export * from './view-provider';
+137
View File
@@ -0,0 +1,137 @@
import { BlockSuiteError } from '@blocksuite/global/exceptions';
import type { ExtensionType } from '@blocksuite/store';
import type { BaseExtensionProvider, Context, Empty } from './base-provider';
/**
* A manager class that handles the registration and configuration of extensions
* for different scopes. It manages extension providers and their instances,
* allowing for dynamic configuration and extension loading.
*
* @typeParam Scope - The type of scope identifiers used for categorizing extensions
*/
export class ExtensionManager<Scope extends string> {
/** @internal */
protected _extensions: Map<string, Set<ExtensionType>> = new Map();
/** @internal */
private readonly _providers: Set<typeof BaseExtensionProvider<Scope>>;
/** @internal */
private readonly _providerOptions: Map<
typeof BaseExtensionProvider<Scope>,
object
> = new Map();
/** @internal */
private readonly _providerInstances: Map<
typeof BaseExtensionProvider<Scope>,
BaseExtensionProvider<Scope>
> = new Map();
/**
* Creates a new ExtensionManager instance with the specified providers.
*
* @param providers - Array of extension provider classes to be managed
*/
constructor(providers: Array<typeof BaseExtensionProvider<Scope>>) {
this._providers = new Set(providers);
}
/** @internal */
private readonly _build = (scope: Scope) => {
const context = this._getContextByScope(scope);
this._providers.forEach(Provider => {
let instance: BaseExtensionProvider<Scope>;
if (this._providerInstances.has(Provider)) {
instance = this._providerInstances.get(Provider)!;
} else {
instance = new Provider();
this._providerInstances.set(Provider, instance);
}
instance.setup(context, this._providerOptions.get(Provider));
});
};
/** @internal */
private readonly _registerToScope = (
scope: Scope,
extensions: ExtensionType[] | ExtensionType
) => {
let extSet: Set<ExtensionType>;
if (!this._extensions.has(scope)) {
extSet = new Set();
} else {
extSet = this._extensions.get(scope)!;
}
const extensionsArray = Array.isArray(extensions)
? extensions
: [extensions];
extensionsArray.forEach(extension => {
extSet.add(extension);
});
this._extensions.set(scope, extSet);
};
/** @internal */
private readonly _getContextByScope = (scope: Scope): Context<Scope> => {
return {
scope,
register: (extensions: ExtensionType[] | ExtensionType) =>
this._registerToScope(scope, extensions),
};
};
/**
* Retrieves all extensions registered for a specific scope.
* If the scope hasn't been built yet, it triggers the build process.
*
* @param scope - The scope to retrieve extensions for
* @returns An array of extensions registered for the specified scope
* @throws {BlockSuiteError} If the scope is not found
*/
get(scope: Scope) {
if (!this._extensions.has(scope)) {
this._build(scope);
}
const extensionSet = this._extensions.get(scope);
if (!extensionSet) {
throw new BlockSuiteError(
BlockSuiteError.ErrorCode.ValueNotExists,
`Extension scope ${scope} not found`
);
}
return Array.from(extensionSet);
}
/**
* Configures a specific provider with new options.
* Can update existing configuration or remove it entirely.
* Triggers a rebuild of the provider instance when configuration changes.
*
* @typeParam T - The type of configuration options for the provider
* @param provider - The provider class to configure
* @param options - New configuration options or a function to update existing options
*/
configure<T extends Empty>(
provider: typeof BaseExtensionProvider<Scope, T>,
options: ((prev: T | undefined) => T | undefined) | T | undefined
) {
let config: T | undefined;
if (typeof options === 'function') {
const prev = this._providerOptions.get(provider);
config = (options as (prev: unknown) => T)(prev);
} else {
config = options;
}
if (config === undefined) {
this._providerOptions.delete(provider);
} else {
this._providerOptions.set(provider, config);
}
// If the config is changed, we need to rebuild the extension
this._providerInstances.delete(provider);
}
}
@@ -0,0 +1,47 @@
import { createIdentifier } from '@blocksuite/global/di';
import type { ExtensionType } from '@blocksuite/store';
import { ExtensionManager } from './manager';
/**
* Identifier for the StoreExtensionManager that can be used for dependency injection.
*/
export const StoreExtensionManagerIdentifier =
createIdentifier<StoreExtensionManager>('StoreExtensionManager');
/**
* A specialized extension manager for store-related extensions.
* Extends the base ExtensionManager to provide store-specific functionality.
*
* This manager is responsible for handling store-related extensions and ensuring
* proper dependency injection setup for store components.
*
* @example
* ```ts
* // Create a store extension manager with providers
* const manager = new StoreExtensionManager([MyStoreProvider]);
*
* // Configure provider options
* manager.configure(MyStoreProvider, { option1: 'value' });
*
* // Get store extensions
* const extensions = manager.get('store');
* ```
*/
export class StoreExtensionManager extends ExtensionManager<'store'> {
/**
* Retrieves store extensions and adds self-registration functionality.
*
* @param scope - The scope of extensions to retrieve, must be 'store'
* @returns An array of extensions including the self-registration extension
*/
override get(scope: 'store') {
const extensions = super.get(scope);
const selfExtension: ExtensionType = {
setup: di => {
di.addImpl(StoreExtensionManagerIdentifier, () => this);
},
};
return extensions.concat(selfExtension);
}
}
@@ -0,0 +1,41 @@
import {
BaseExtensionProvider,
type Context,
type Empty,
} from './base-provider';
/**
* A specialized extension provider for store-related functionality.
* Extends the base provider with store-specific scope and configuration.
*
* @typeParam Options - The type of configuration options for the store provider
*
* @example
* ```ts
* // Create a store provider with custom options
* class MyStoreProvider extends StoreExtensionProvider<{ cacheSize: number }> {
* override name = 'MyStoreProvider';
*
* override schema = z.object({
* cacheSize: z.number().min(0)
* });
*
* override setup(context: StoreExtensionContext, options?: { cacheSize: number }) {
* super.setup(context, options);
* context.register([Ext1, Ext2, Ext3]);
* }
* }
* ```
*/
export class StoreExtensionProvider<
Options extends object = Empty,
> extends BaseExtensionProvider<'store', Options> {
/** The name of the store extension provider */
override name = 'StoreExtension';
}
/**
* Context type specifically for store-related extensions.
* Provides type safety for store extension registration and setup.
*/
export type StoreExtensionContext = Context<'store'>;
@@ -0,0 +1,49 @@
import { createIdentifier } from '@blocksuite/global/di';
import type { ExtensionType } from '@blocksuite/store';
import { ExtensionManager } from './manager';
import type { ViewScope } from './view-provider';
/**
* Identifier for the ViewExtensionManager that can be used for dependency injection.
*/
export const ViewExtensionManagerIdentifier =
createIdentifier<ViewExtensionManager>('ViewExtensionManager');
/**
* A specialized extension manager for view-related extensions.
* Extends the base ExtensionManager to provide view-specific functionality.
*
* This manager is responsible for handling view-related extensions and ensuring
* proper dependency injection setup for view components.
*
* @example
* ```ts
* // Create a view extension manager with providers
* const manager = new ViewExtensionManager([MyViewProvider]);
*
* // Configure provider options
* manager.configure(MyViewProvider, { option1: 'value' });
*
* // Get view extensions for a specific scope
* const pageExtensions = manager.get('page');
* const edgelessExtensions = manager.get('edgeless');
* ```
*/
export class ViewExtensionManager extends ExtensionManager<ViewScope> {
/**
* Retrieves view extensions and adds self-registration functionality.
*
* @param scope - The scope of extensions to retrieve
* @returns An array of extensions including the self-registration extension
*/
override get(scope: ViewScope) {
const extensions = super.get(scope);
const selfExtension: ExtensionType = {
setup: di => {
di.addImpl(ViewExtensionManagerIdentifier, () => this);
},
};
return extensions.concat(selfExtension);
}
}
@@ -0,0 +1,62 @@
import {
BaseExtensionProvider,
type Context,
type Empty,
} from './base-provider';
/**
* Available view scopes for view-related extensions.
* Defines the different types of views where extensions can be applied.
*/
export type ViewScope =
| 'page' // Standard page view
| 'edgeless' // Edgeless (whiteboard) view
| 'preview-page' // Page preview view
| 'preview-edgeless' // Edgeless preview view
| 'mobile-page' // Mobile page view
| 'mobile-edgeless'; // Mobile edgeless view
/**
* A specialized extension provider for view-related functionality.
* Extends the base provider with view-specific scope and configuration.
*
* @typeParam Options - The type of configuration options for the view provider
*
* @example
* ```ts
* // Create a view provider with custom options
* class MyViewProvider extends ViewExtensionProvider<{ theme: string }> {
* override name = 'MyViewProvider';
*
* override schema = z.object({
* theme: z.enum(['light', 'dark'])
* });
*
* override setup(context: ViewExtensionContext, options?: { theme: string }) {
* super.setup(context, options);
*
* context.register([CommonExt]);
* if (context.scope === 'page') {
* context.register([PageExt]);
* } else if (context.scope === 'edgeless') {
* context.register([EdgelessExt]);
* }
* if (options?.theme === 'dark') {
* context.register([DarkModeExt]);
* }
* }
* }
* ```
*/
export class ViewExtensionProvider<
Options extends object = Empty,
> extends BaseExtensionProvider<ViewScope, Options> {
/** The name of the view extension provider */
override name = 'ViewExtension';
}
/**
* Context type specifically for view-related extensions.
* Provides type safety for view extension registration and setup.
*/
export type ViewExtensionContext = Context<ViewScope>;
@@ -0,0 +1,13 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist",
"tsBuildInfoFile": "./dist/tsconfig.tsbuildinfo"
},
"include": ["./src"],
"references": [
{ "path": "../../framework/global" },
{ "path": "../../framework/store" }
]
}
@@ -0,0 +1,28 @@
import { defineConfig } from 'vitest/config';
export default defineConfig({
esbuild: {
target: 'es2018',
},
test: {
include: ['src/__tests__/**/*.unit.spec.ts'],
testTimeout: 500,
coverage: {
provider: 'istanbul', // or 'c8'
reporter: ['lcov'],
reportsDirectory: '../../../.coverage/ext-loader',
},
/**
* Custom handler for console.log in tests.
*
* Return `false` to ignore the log.
*/
onConsoleLog(log, type) {
if (log.includes('https://lit.dev/msg/dev-mode')) {
return false;
}
console.warn(`Unexpected ${type} log`, log);
throw new Error(log);
},
},
});
+9
View File
@@ -419,6 +419,14 @@ export const PackageList = [
'blocksuite/framework/store',
],
},
{
location: 'blocksuite/affine/ext-loader',
name: '@blocksuite/affine-ext-loader',
workspaceDependencies: [
'blocksuite/framework/global',
'blocksuite/framework/store',
],
},
{
location: 'blocksuite/affine/fragments/doc-title',
name: '@blocksuite/affine-fragment-doc-title',
@@ -1249,6 +1257,7 @@ export type PackageName =
| '@blocksuite/affine-block-table'
| '@blocksuite/affine-components'
| '@blocksuite/data-view'
| '@blocksuite/affine-ext-loader'
| '@blocksuite/affine-fragment-doc-title'
| '@blocksuite/affine-fragment-frame-panel'
| '@blocksuite/affine-fragment-outline'
+1
View File
@@ -72,6 +72,7 @@
{ "path": "./blocksuite/affine/blocks/table" },
{ "path": "./blocksuite/affine/components" },
{ "path": "./blocksuite/affine/data-view" },
{ "path": "./blocksuite/affine/ext-loader" },
{ "path": "./blocksuite/affine/fragments/doc-title" },
{ "path": "./blocksuite/affine/fragments/frame-panel" },
{ "path": "./blocksuite/affine/fragments/outline" },
+11
View File
@@ -2969,6 +2969,17 @@ __metadata:
languageName: unknown
linkType: soft
"@blocksuite/affine-ext-loader@workspace:blocksuite/affine/ext-loader":
version: 0.0.0-use.local
resolution: "@blocksuite/affine-ext-loader@workspace:blocksuite/affine/ext-loader"
dependencies:
"@blocksuite/global": "workspace:*"
"@blocksuite/store": "workspace:*"
vitest: "npm:3.1.1"
zod: "npm:^3.23.8"
languageName: unknown
linkType: soft
"@blocksuite/affine-fragment-doc-title@workspace:*, @blocksuite/affine-fragment-doc-title@workspace:blocksuite/affine/fragments/doc-title":
version: 0.0.0-use.local
resolution: "@blocksuite/affine-fragment-doc-title@workspace:blocksuite/affine/fragments/doc-title"