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

  1. Register the missing block schema(s) before the doc hydrates
  2. Diff the doc's flavour set against doc.schema.flavourSchemaMap.keys() to identify exactly what is missing
  3. Register a legacy schema (old flavour string) alongside the new one for backward compatibility
  4. 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

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


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)