toeverything/AFFiNE · critical · BlockSuiteError
ErrorCode.ModelCRUDError
ErrorCode.ModelCRUDError
Error message
schema for flavour: ${this.flavour} not found What it means
This is the tree-data-model counterpart of the FlatSyncController error: when SyncController builds a BlockModel for a regular block, it resolves schema.flavourSchemaMap.get(this.flavour) and throws ModelCRUDError if the flavour has no registered schema, because the model cannot be constructed without its schema.
Solutions
- Register the missing block schema(s) before the doc hydrates
- Diff the doc's flavour set against doc.schema.flavourSchemaMap.keys() to identify exactly what is missing
- Register a legacy schema (old flavour string) alongside the new one for backward compatibility
- Migrate sys:flavour values in old snapshots
Example fix
// before
const schema = new Schema();
const doc = workspace.createSession({ id: 'doc1', schema });
// -> schema for flavour: my:block not found
// after
import { MyBlock } from './my-block.js';
schema.register([MyBlock]);
const doc = workspace.createSession({ id: 'doc1', schema }); Defensive patterns
Strategy: validation
Validate before calling
const flavours = new Set<string>();
for (const [, yBlock] of doc.getBlockMap()) {
flavours.add(yBlock.get('sys:flavour') as string);
}
const unregistered = [...flavours].filter(f => !schema.flavourSchemaMap.has(f));
if (unregistered.length === 0) {
/* safe to hydrate */
} Type guard
const isFlavourRegistered = (schema: Schema, flavour: string): boolean => schema.flavourSchemaMap.has(flavour);
Try / catch
try {
const doc = workspace.createSession({ id, schema });
} catch (e) {
if (e instanceof BlockSuiteError && e.code === ErrorCode.ModelCRUDError) {
// message names the flavour: register it or its legacy alias, retry
}
throw e;
} Prevention
- Centralize schema registration so every build registers the same block set
- Add a startup assertion that all flavours referenced by stored docs are registered
- Never share docs between apps with different block registries without legacy schemas
When it happens
Trigger: Hydrating or observing a standard (non-flat) block whose sys:flavour is absent from the Schema — e.g. a paragraph-like block from an unregistered or renamed flavour.
Common situations: Missing schema.register call in a custom editor build; docs shared between apps with different block sets; upgrades that renamed flavours without migrating stored docs.
Related errors
- ErrorCode.ModelCRUDError
- ErrorCode.ModelCRUDError
- Block cannot have parent
- Schema not found. The block flavour may not be registered.
- ErrorCode.MissingViewModelError
AI-assisted analysis of toeverything/AFFiNE@b4c8548c09 (2026-08-18).
Data as JSON: /api/errors/d4e630593996c609.
Report an issue: GitHub.
Appendix: source
Thrown at blocksuite/framework/store/src/model/block/sync-controller.ts:133
readonly onChange?: (key: string, isLocal: boolean) => void
) {
const { id, flavour, version, yChildren, props } = this._parseYBlock();
this.id = id;
this.flavour = flavour;
this.yChildren = yChildren;
this.version = version;
this.model = this._createModel(props);
this._observeYBlockChanges();
}
private _createModel(props: UnRecord) {
const _mutex = this._mutex;
const schema = this.schema.flavourSchemaMap.get(this.flavour);
if (!schema) {
throw new BlockSuiteError(
ErrorCode.ModelCRUDError,
`schema for flavour: ${this.flavour} not found`
);
}
const model = schema.model.toModel?.() ?? new BlockModel<object>();
model.schema = schema;
const signalWithProps = Object.entries(props).reduce(
(acc, [key, value]) => {
const data = signal(value);
const dispose = effect(() => {
const value = data.value;
if (!this.model) return;
_mutex(() => {
// @ts-expect-error allow magic props
this.model.props[key] = value;
});
});View on GitHub (pinned to b4c8548c09)