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
- 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.
- If you need a nested map, wrap it in Boxed (schema model supports Boxed values) rather than a raw Y.Map.
- Migrate existing stored Y.Map values to dotted-flat or Boxed form before opening the doc.
- 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
- For flat-data blocks, store nested data as plain JS objects; let the encoder flatten them.
- If you need an opaque nested map, wrap it in Boxed, not a raw Y.Map.
- Audit transformers/importers writing into flat-data block prop keys.
- Confirm isFlatData on the schema matches how props are written.
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
- ErrorCode.ModelCRUDError
- Invalid key for: ${key}
- ReactiveProxyError
- ReactiveProxyError
- ErrorCode.ModelCRUDError
AI-assisted analysis of toeverything/AFFiNE@26c515e050 (2026-08-12).
Data as JSON: /api/errors/96a1011cb5a8c8cc.
Report an issue: GitHub.