toeverything/AFFiNE · error · Error

Invalid key for

Error message

Invalid key for: ${key}

What it means

getFirstKey is used throughout the flat reactive layer (proxy set/delete paths and Y event handling) to reduce a dotted prop path like 'a.b.c' to its first segment. An empty path ('') or one with a leading dot ('.b') splits to an empty first segment, which is falsy, so the util throws a plain Error — the key was built wrong before it reached the flat model.

Solutions

  1. Validate generated keys before prop access: non-empty, no leading/trailing dots, no '..' sequences
  2. Audit template-literal key construction (e.g. `${prefix}.${name}`) for empty prefix/name and use a join helper that filters empties
  3. Repair malformed 'prop:'-prefixed entries in the Y map by deleting or renaming them

Example fix

// before
const path = `${base}.${name}`; // base is '' -> path '.title' -> Invalid key for: .title
model._props[path] = value;

// after
const path = [base, name].filter(Boolean).join('.'); // 'title'
if (path) {
  model._props[path] = value;
}
Defensive patterns

Strategy: validation

Validate before calling

function isValidPropPath(key: string): boolean {
  return key.length > 0 && !key.startsWith('.') && !key.endsWith('.') && !key.includes('..');
}

const path = [base, name].filter(Boolean).join('.');
if (isValidPropPath(path)) {
  model._props[path] = value;
}

Type guard

const isValidPropPath = (key: string): boolean =>
  key.length > 0 && !key.startsWith('.') && !key.endsWith('.') && !key.includes('..');

Try / catch

try {
  model._props[path] = value;
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Invalid key for:')) {
    // rebuild the path from non-empty segments and retry
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: Setting, deleting, or remotely updating a flat-model prop whose computed key is '' or starts/ends with '.' — e.g. template strings concatenating an undefined segment (`prop:${base}.x` with base empty), or a Y event arriving for a key literally named 'prop:'.

Common situations: Programmatic prop-path builders that skip empty segments; importing data with blank prop names; edge-case Y updates from peers with malformed keys.

Related errors


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

Appendix: source

Thrown at blocksuite/framework/store/src/reactive/flat-native-y/utils.ts:41

export function isEmptyObject(obj: UnRecord): boolean {
  return Object.keys(obj).length === 0;
}

export function deleteEmptyObject(
  obj: UnRecord,
  key: string,
  parent: UnRecord
): void {
  if (isEmptyObject(obj)) {
    delete parent[key];
  }
}

export function getFirstKey(key: string): string {
  const result = key.split('.').at(0);
  if (!result) {
    throw new Error(`Invalid key for: ${key}`);
  }
  return result;
}

export function bindOnChangeIfNeed(value: unknown, onChange: () => void): void {
  if (value instanceof Text || Boxed.is(value)) {
    value.bind(onChange);
  }
}

View on GitHub (pinned to b4c8548c09)