{"record":{"id":"7e9b50cff7cfef73","repo":"toeverything/AFFiNE","slug":"table-tablename-field-name-is-reserved-7e9b50","errorCode":null,"errorMessage":"[Table(${tableName})]: Field '${name}' is reserved keyword and can't be set.","messagePattern":"\\[Table\\((.+?)\\)\\]: Field '(.+?)' is reserved keyword and can't be set\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/common/infra/src/orm/core/validators/yjs.ts","lineNumber":28,"sourceCode":"  UsePreservedFields: {\n    validate(tableName, table) {\n      for (const name in table) {\n        if (PRESERVED_FIELDS.includes(name)) {\n          throw new Error(\n            `[Table(${tableName})]: Field '${name}' is reserved keyword and can't be used.`\n          );\n        }\n      }\n    },\n  },\n};\n\nexport const yjsDataValidators: Record<string, DataValidator> = {\n  SetPreservedFields: {\n    validate(tableName, data) {\n      for (const name of PRESERVED_FIELDS) {\n        if (data[name] !== undefined) {\n          throw new Error(\n            `[Table(${tableName})]: Field '${name}' is reserved keyword and can't be set.`\n          );\n        }\n      }\n    },\n  },\n};\n","sourceCodeStart":10,"sourceCodeEnd":36,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/packages/common/infra/src/orm/core/validators/yjs.ts#L10-L36","documentation":"YJS data validator SetPreservedFields throws when an entity payload tries to set the reserved '$$DELETED' key (any value other than undefined). Unlike error 708 (schema definition), this fires at write time: the tombstone flag is managed exclusively by the framework's delete flow and cannot be written through the normal data API.","triggerScenarios":"Passing { ..., '$$DELETED': true } to create/set on a YJS-backed table; spreading a previously-read raw row (which includes the tombstone) back into an update; tests fabricating row objects with the internal flag.","commonSituations":"Round-tripping internal row objects through user code; copying fixtures that captured the storage-level representation including tombstones.","solutions":["Remove '$$DELETED' from the payload before writing (strip it or use a pick of real schema fields).","Use the table's delete API to soft-delete rows instead of setting the flag by hand.","If you read raw rows, map them to your entity type and drop internal keys before writing back."],"exampleFix":"// before\nawait db.doc.set(id, { ...rawRow, title: 'new' }); // rawRow contains $$DELETED\n\n// after\nconst { ['$$DELETED']: _omit, ...entity } = rawRow;\nawait db.doc.set(id, { ...entity, title: 'new' });","handlingStrategy":"validation","validationCode":"function stripReserved(data: Record<string, unknown>) {\n  const clone = { ...data };\n  delete clone['$$DELETED'];\n  return clone;\n}","typeGuard":"type Entity<T> = Omit<T, '$$DELETED'>;\n// Omit the tombstone key when typing round-tripped rows","tryCatchPattern":"try { await table.set(id, payload); } catch (e) { if (e instanceof Error && e.message.includes(\"reserved keyword and can't be set\")) { delete payload['$$DELETED']; await table.set(id, payload); } else throw e; }","preventionTips":["Use the framework delete API for soft deletes","Map raw storage rows to clean entity types before writing back","Never fabricate rows with internal flags in fixtures"],"tags":["orm","yjs","reserved-name","affine"],"backgroundTag":"reserved-field-name","analyzedSha":"b4c8548c09da21b2898443559a5b846f0ccf5dd8","analyzedAt":"2026-08-18T21:16:52.546Z","contentChangedAt":"2026-08-18T21:16:52.546Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}