toeverything/AFFiNE · error · BlockSuiteError

ErrorCode.SelectionError

ErrorCode.SelectionError

Error message

Unknown selection type: ${json.type}

What it means

Thrown by `StoreSelectionExtension._jsonToSelection` when a selection JSON's `type` has no entry in `_selectionConstructors`. The constructors are populated in `loaded()` from everything registered under the `SelectionIdentifier` DI token; an unknown type means no selection class was registered for it.

Source

Thrown at blocksuite/framework/store/src/extension/selection/selection-extension.ts:36

  private readonly _remoteSelections = signal<Map<number, BaseSelection[]>>(
    new Map()
  );

  private readonly _itemAdded = (event: { stackItem: StackItem }) => {
    event.stackItem.meta.set('selection-state', this._selections.value);
  };

  private readonly _itemPopped = (event: { stackItem: StackItem }) => {
    const selection = event.stackItem.meta.get('selection-state');
    if (selection) {
      this.set(selection as BaseSelection[]);
    }
  };

  private readonly _jsonToSelection = (json: Record<string, unknown>) => {
    const ctor = this._selectionConstructors[json.type as string];
    if (!ctor) {
      throw new BlockSuiteError(
        ErrorCode.SelectionError,
        `Unknown selection type: ${json.type}`
      );
    }
    return ctor.fromJSON(json);
  };

  slots = {
    changed: new Subject<BaseSelection[]>(),
    remoteChanged: new Subject<Map<number, BaseSelection[]>>(),
  };

  override loaded() {
    this.store.provider.getAll(SelectionIdentifier).forEach(ctor => {
      [ctor].flat().forEach(ctor => {
        this._selectionConstructors[ctor.type] = ctor;
      });
    });

View on GitHub (pinned to 26c515e050)

Solutions

  1. Register the selection class via its extension so it appears in `SelectionIdentifier` (add the extension to the store's extensions array).
  2. On the receiving side, the existing `try/catch` in `_jsonToSelection` callers already logs and skips unknown remote types — ensure your code routes remote JSON through that path rather than calling `_jsonToSelection` directly.
  3. If you renamed a type, ship a migration that rewrites old `type` values in stored selections.

Example fix

// before: custom selection type used but never registered
class MySelection extends BaseSelection { static type = 'my:selection'; /* ... */ }
selection.set([instance]); // remote peers throw Unknown selection type

// after: register the selection extension so the constructor is known
const store = createStore({ extensions: [MySelectionExtension] });
// where MySelectionExtension provides SelectionIdentifier -> MySelection
Defensive patterns

Strategy: validation

Validate before calling

// check that the selection type is registered before instantiating
function isKnownSelectionType(
  knownTypes: string[],
  type: string
): boolean {
  return knownTypes.includes(type);
}

// route remote JSON through the existing try/catch in selection-extension;
// for local fromJSON, validate first:
if (!isKnownSelectionType(Object.keys(registeredCtors), json.type)) {
  console.warn('Skipping unknown selection type:', json.type);
  return null;
}

Type guard

function isRegisteredSelectionType(type: string, ctors: Record<string, unknown>): type is keyof typeof ctors {
  return type in ctors;
}

Try / catch

import { BlockSuiteError, ErrorCode } from '@blocksuite/global/exceptions';

try {
  return ctor.fromJSON(json);
} catch (e) {
  if (e instanceof BlockSuiteError && e.code === ErrorCode.SelectionError) {
    console.warn('Skipping unknown remote selection:', json.type);
    return null;
  }
  throw e;
}

Prevention

When it happens

Trigger: A remote peer (via awareness) or a persisted selection snapshot sends a selection of a type the local doc has no constructor for. Also triggered when calling `selection.fromJSON({ type: 'foo', ... })` for a `foo` selection class that was never registered as a `SelectionIdentifier` provider.

Common situations: Custom selection subclass defined but its extension not passed to the store's extensions list; version skew where one client knows a new selection type and another does not; renaming a selection `type` without a migration; selection snapshot from a third-party plugin.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/ee2e68f2221afe59. Report an issue: GitHub.