toeverything/AFFiNE · error · BlockSuiteError

ErrorCode.SelectionError

ErrorCode.SelectionError

Error message

You must override this method

What it means

BaseSelection.fromJSON() is the base stub that every concrete selection class must override, because each selection type needs its own deserialization logic. If a registered selection subclass ships without a static fromJSON override, the first deserialization (undo/redo restoring selection state, remote selection updates, persisted selection) dispatches to the stub and throws SelectionError 'You must override this method'.

Solutions

  1. Implement static fromJSON(json) on the selection subclass: parse the fields, return new MySelection({ blockId, ... }).
  2. Mirror the fields you serialize in equals/onChange so round-trips are lossless.
  3. Register the class via SelectionExtension(MySelection) so deserialization uses your constructor.
  4. If you never persist/remote-sync this selection type, reconsider whether a custom class is needed at all.

Example fix

// before
class MySelection extends BaseSelection {
  static override type = 'my';
  // no fromJSON -> throws when deserialized
}

// after
class MySelection extends BaseSelection {
  static override type = 'my-selection';
  static override fromJSON(json: Record<string, unknown>) {
    return new MySelection({ blockId: json.blockId as string });
  }
  constructor(opts: BaseSelectionOptions) { super({ blockId: opts.blockId }); }
  override equals(other: BaseSelection) { return other instanceof MySelection && other.blockId === this.blockId; }
}
Defensive patterns

Strategy: type-guard

Type guard

import { BaseSelection } from '@blocksuite/store';
function implementsFromJSON(
  ctor: typeof BaseSelection
): boolean {
  return ctor.fromJSON !== BaseSelection.fromJSON;
}
// guard before registration:
if (!implementsFromJSON(MySelection)) throw new Error('MySelection must implement static fromJSON');

Prevention

When it happens

Trigger: Defining a custom selection class extending BaseSelection without implementing static fromJSON(json), then triggering selection (de)serialization: undoManager pop restoring 'selection-state' from stack item meta, or selectionManager.set / fromJSON on remote updates.

Common situations: Writing custom block selection types for new flavours; copying a selection class skeleton from docs/examples that omits fromJSON; minor-version upgrades where fromJSON became required/typed differently.

Understand the failure class

Background: "NotImplementedError: Subclasses should override this method" / "must be implemented" — abstract method errors explained — this error's family across 40 libraries.

Related errors


AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18). Data as JSON: /api/errors/3a9fe509b1f82162. Report an issue: GitHub.

Appendix: source

Thrown at blocksuite/framework/store/src/extension/selection/base.ts:31

  static readonly recoverable: boolean = false;

  readonly blockId: string;

  get group(): string {
    return (this.constructor as SelectionConstructor).group;
  }

  get type(): string {
    return (this.constructor as SelectionConstructor).type as string;
  }

  constructor({ blockId }: BaseSelectionOptions) {
    this.blockId = blockId;
  }

  static fromJSON(_: Record<string, unknown>): BaseSelection {
    throw new BlockSuiteError(
      ErrorCode.SelectionError,
      'You must override this method'
    );
  }

  abstract equals(other: BaseSelection): boolean;

  is<T extends SelectionConstructor>(
    type: T
  ): this is T extends SelectionConstructor<infer U> ? U : never {
    return this.type === type.type;
  }

  abstract toJSON(): Record<string, unknown>;
}

View on GitHub (pinned to b4c8548c09)