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
- Register the selection class via its extension so it appears in `SelectionIdentifier` (add the extension to the store's extensions array).
- 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.
- 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
- Register every custom selection type via its extension in the store's extensions list.
- Route remote selection JSON through the framework's try/catch path, not _jsonToSelection directly.
- Ship a migration when renaming a selection type.
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.