toeverything/AFFiNE · error · BlockSuiteError

ReactiveProxyError

ReactiveProxyError

Error message

flatY2Native does not support Y.Map as value of Y.Map

What it means

Thrown by initializeData in the flat-native-y reactive layer when a block's Yjs map contains a value that is itself a Y.Map. The flat-data model flattens nested objects into dotted keys (e.g. 'prop:style.color') and only supports scalars, YArray, YText, and Boxed as leaf values; a nested Y.Map has no flat representation, so initialization refuses it.

Source

Thrown at blocksuite/framework/store/src/reactive/flat-native-y/initialize.ts:37

}: InitializeDataOptions): UnRecord => {
  const root: UnRecord = {};
  Array.from(yMap.entries()).forEach(([key, value]) => {
    if (key.startsWith('sys')) {
      return;
    }
    const keys = keyWithoutPrefix(key).split('.');
    const firstKey = keys[0];

    let finalData = value;
    if (Boxed.is(value)) {
      finalData = transform(firstKey, new Boxed(value), value);
    } else if (value instanceof YArray) {
      finalData = transform(firstKey, value.toArray(), value);
    } else if (value instanceof YText) {
      const next = new Text(value);
      finalData = transform(firstKey, next, value);
    } else if (value instanceof YMap) {
      throw new BlockSuiteError(
        BlockSuiteError.ErrorCode.ReactiveProxyError,
        'flatY2Native does not support Y.Map as value of Y.Map'
      );
    } else {
      finalData = transform(firstKey, value, value);
    }
    const allLength = keys.length;
    void keys.reduce((acc: UnRecord, key, index) => {
      if (!acc[key] && index !== allLength - 1) {
        const path = keys.slice(0, index + 1).join('.');
        const data = getProxy({} as UnRecord, root, path);
        acc[key] = data;
      }
      if (index === allLength - 1) {
        acc[key] = finalData;
      }
      return acc[key] as UnRecord;
    }, root);

View on GitHub (pinned to 26c515e050)

Solutions

  1. For flat-data blocks, store nested objects as plain JS objects (the flat encoder will flatten them to dotted keys) instead of pre-wrapping in Y.Map.
  2. If you need a nested map, wrap it in Boxed (schema model supports Boxed values) rather than a raw Y.Map.
  3. Migrate existing stored Y.Map values to dotted-flat or Boxed form before opening the doc.
  4. Confirm the block schema's isFlatData flag matches how its props are written.

Example fix

// before: storing a raw nested Y.Map on a flat-data block
block.props.style = new Y.Map(); // triggers the error on load

// after: store a plain object (flattened to prop:style.*) or use Boxed
block.props.style = { color: '#000', bold: false };
// or, for an opaque nested map:
block.props.advanced = new Boxed(new Y.Map());
Defensive patterns

Strategy: validation

Validate before calling

import * as Y from 'yjs';

export function assertNoNestedYMap(values: Iterable<unknown>): void {
  for (const v of values) {
    if (v instanceof Y.Map) {
      throw new Error('flat-data block cannot store a nested Y.Map; use a plain object or Boxed');
    }
  }
}

// before assigning into a flat-data block prop
assertNoNestedYMap(Object.values(props));
block.props = { ...props };

Type guard

import * as Y from 'yjs';
export const hasNoNestedYMap = (value: unknown): boolean => {
  if (value instanceof Y.Map) return false;
  if (value && typeof value === 'object') {
    return Object.values(value).every(hasNoNestedYMap);
  }
  return true;
};

Try / catch

try {
  // trigger flat initialization (e.g. by reading the block proxy)
  void block.props;
} catch (e) {
  if (e instanceof BlockSuiteError && e.code === ErrorCode.ReactiveProxyError && /Y.Map as value of Y.Map/.test(e.message)) {
    // migrate offending prop to plain object or Boxed, then reload
    migrateFlatProp(yBlock);
  } else throw e;
}

Prevention

When it happens

Trigger: Using a block whose schema declares isFlatData:true and storing a prop whose value is a Y.Map (e.g. directly assigning a Y.Map, or a nested object that the flat encoder couldn't flatten because it was already a Y.Map instance).

Common situations: Mixing flat-data blocks with code that calls native2Y/Y.Map directly on a nested field; importing data from a non-flat block into a flat one; a transformer writing a nested Y.Map into prop keys; upgrading a block to isFlatData without migrating its stored values.

Related errors


AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12). Data as JSON: /api/errors/96a1011cb5a8c8cc. Report an issue: GitHub.