toeverything/AFFiNE · critical · BlockSuiteError

ErrorCode.ModelCRUDError

ErrorCode.ModelCRUDError

Error message

schema for flavour: ${this.flavour} not found

What it means

When a flat-data-model block is instantiated from its Yjs map, FlatSyncController resolves the block's sys:flavour in Schema.flavourSchemaMap before building the model. If no schema is registered under that flavour, the store cannot create the model and throws this ModelCRUDError during block hydration.

Solutions

  1. Register every flavour the doc can contain via schema.register([...schemas]) before the doc is loaded
  2. Inspect the doc's blocks Y.Map and collect all sys:flavour values to find which one is missing from doc.schema.flavourSchemaMap
  3. If a flavour was renamed, add a migration that rewrites sys:flavour on old blocks or register a legacy schema
  4. As a last resort for disposable data, delete the offending block from the snapshot and let the doc load

Example fix

// before
const schema = new Schema();
const doc = workspace.createSession({ id: 'doc1', schema });
// doc1 contains 'my:flat' blocks -> schema for flavour: my:flat not found

// after
const schema = new Schema();
schema.register([MyFlatBlock]); // defineBlockSchema({ flavour: 'my:flat', isFlatData: true, ... })
const doc = workspace.createSession({ id: 'doc1', schema });
Defensive patterns

Strategy: validation

Validate before calling

for (const [, yBlock] of doc.getBlockMap()) {
  const flavour = yBlock.get('sys:flavour') as string;
  if (!doc.schema.flavourSchemaMap.has(flavour)) {
    console.warn('unregistered flavour in doc:', flavour);
  }
}

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) {
    // log flavour registry and doc contents, register missing schemas, retry load
  }
  throw e;
}

Prevention

When it happens

Trigger: Instantiating a Block for a Yjs block map whose sys:flavour is not registered on the Schema instance passed to the Store — e.g. a doc written by another build containing 'my:custom' flavours, or a flavour string that was renamed.

Common situations: Forgetting schema.register([...]) before loading persisted docs; opening documents produced by a different editor configuration; renaming or splitting a block flavour without migrating old docs; schemas lazy-loaded after the doc has already mounted.

Related errors


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

Appendix: source

Thrown at blocksuite/framework/store/src/model/block/flat-sync-controller.ts:43

    readonly schema: Schema,
    readonly yBlock: YBlock,
    readonly doc?: Store,
    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);
  }

  private _createModel(props: Set<string>) {
    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>();
    const defaultProps = schema.model.props?.(internalPrimitives);
    model.schema = schema;

    model.id = this.id;
    model.keys = Array.from(props);
    model.yBlock = this.yBlock;
    const reactive = new ReactiveFlatYMap(
      this.yBlock,
      model.deleted,
      this.onChange,
      defaultProps
    );

View on GitHub (pinned to b4c8548c09)