{"record":{"id":"a83bcc589adff374","repo":"toeverything/AFFiNE","slug":"invalid-key-for-key","errorCode":null,"errorMessage":"Invalid key for: ${key}","messagePattern":"Invalid key for: (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"blocksuite/framework/store/src/reactive/flat-native-y/utils.ts","lineNumber":41,"sourceCode":"\nexport function isEmptyObject(obj: UnRecord): boolean {\n  return Object.keys(obj).length === 0;\n}\n\nexport function deleteEmptyObject(\n  obj: UnRecord,\n  key: string,\n  parent: UnRecord\n): void {\n  if (isEmptyObject(obj)) {\n    delete parent[key];\n  }\n}\n\nexport function getFirstKey(key: string): string {\n  const result = key.split('.').at(0);\n  if (!result) {\n    throw new Error(`Invalid key for: ${key}`);\n  }\n  return result;\n}\n\nexport function bindOnChangeIfNeed(value: unknown, onChange: () => void): void {\n  if (value instanceof Text || Boxed.is(value)) {\n    value.bind(onChange);\n  }\n}\n","sourceCodeStart":23,"sourceCodeEnd":51,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/blocksuite/framework/store/src/reactive/flat-native-y/utils.ts#L23-L51","documentation":"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.","triggerScenarios":"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:'.","commonSituations":"Programmatic prop-path builders that skip empty segments; importing data with blank prop names; edge-case Y updates from peers with malformed keys.","solutions":["Validate generated keys before prop access: non-empty, no leading/trailing dots, no '..' sequences","Audit template-literal key construction (e.g. `${prefix}.${name}`) for empty prefix/name and use a join helper that filters empties","Repair malformed 'prop:'-prefixed entries in the Y map by deleting or renaming them"],"exampleFix":"// before\nconst path = `${base}.${name}`; // base is '' -> path '.title' -> Invalid key for: .title\nmodel._props[path] = value;\n\n// after\nconst path = [base, name].filter(Boolean).join('.'); // 'title'\nif (path) {\n  model._props[path] = value;\n}","handlingStrategy":"validation","validationCode":"function isValidPropPath(key: string): boolean {\n  return key.length > 0 && !key.startsWith('.') && !key.endsWith('.') && !key.includes('..');\n}\n\nconst path = [base, name].filter(Boolean).join('.');\nif (isValidPropPath(path)) {\n  model._props[path] = value;\n}","typeGuard":"const isValidPropPath = (key: string): boolean =>\n  key.length > 0 && !key.startsWith('.') && !key.endsWith('.') && !key.includes('..');","tryCatchPattern":"try {\n  model._props[path] = value;\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('Invalid key for:')) {\n    // rebuild the path from non-empty segments and retry\n  } else {\n    throw e;\n  }\n}","preventionTips":["Build dotted prop paths with a helper that filters out empty segments","Validate computed keys (non-empty, no leading/trailing dot) before prop access","Sanitize imported prop names: reject blank and dot-padded keys at the boundary"],"tags":["blocksuite","flat-data-model","prop-key","validation","reactive"],"backgroundTag":"invalid-key-format","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}