{"record":{"id":"34fb26e572b6deda","repo":"hcengineering/platform","slug":"invalid-relation-key-key-key","errorCode":null,"errorMessage":"invalid relation key ${key.key}","messagePattern":"invalid relation key (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"plugins/view-resources/src/utils.ts","lineNumber":639,"sourceCode":"          key: '',\n          sortingKey: '',\n          presenter: ErrorPresenter,\n          label: stringKey as IntlString,\n          _class: core.class.TypeString,\n          props: { error: err },\n          collectionAttr: false,\n          isLookup: false\n        }\n        return errorPresenter\n      }\n    })\n  return (await Promise.all(model)).filter((a) => a !== undefined)\n}\n\nasync function getRelationPresenter (client: Client, key: BuildModelKey): Promise<AttributeModel> {\n  const parts = key.key.split('.')\n  if (parts.length < 2) {\n    throw new Error('invalid relation key ' + key.key)\n  }\n\n  // Find the last association segment\n  let lastAssocIndex = -1\n  for (let i = 0; i < parts.length; i++) {\n    if (parts[i] === '$associations' && i + 1 < parts.length) {\n      lastAssocIndex = i\n    }\n  }\n  if (lastAssocIndex === -1) {\n    throw new Error('invalid relation key ' + key.key)\n  }\n\n  const fragments = parts[lastAssocIndex + 1].split('_')\n  const assocId = fragments[0] as Ref<Association>\n  const assoc = client.getModel().findObject(assocId)\n  if (assoc === undefined) {\n    throw new Error('association not found for ' + assocId)","sourceCodeStart":621,"sourceCodeEnd":657,"githubUrl":"https://github.com/hcengineering/platform/blob/63e28dc96483967b2fc21c881b3f1023c1de7718/plugins/view-resources/src/utils.ts#L621-L657","documentation":"getRelationPresenter resolves association-based relation keys (containing $associations). The key must contain a dot-separated path; if the key has no '.' it cannot encode an association and the function throws immediately.","triggerScenarios":"Passing a BuildModelKey to model()/getPresenter whose key contains '$associations' but has no dot, e.g. key: '$associations' or key: 'myKey' routed to relation presenter.","commonSituations":"Hand-written view key strings missing the dotted path; copy-pasted keys from another view with different structure; typos that drop the association id segment.","solutions":["Use a full relation key of the form 'path.$associations.assocId_x', e.g. 'spaces.$associations.space_a'","Verify the key string contains at least one '.' before passing to model()","Split logic: only route keys containing '$associations' to relation presenter"],"exampleFix":"// before\n{ key: '$associations' }\n// after\n{ key: 'spaces.$associations.<assocId>_a' }","handlingStrategy":"validation","validationCode":"function isValidRelationKey(key: string): boolean {\n  return key.includes('.') && key.includes('$associations')\n}\nif (!isValidRelationKey(key)) throw new Error('bad relation key: ' + key)","typeGuard":null,"tryCatchPattern":"try {\n  return await model(client, _class, [{ key }])\n} catch (err) {\n  if (String(err.message).startsWith('invalid relation key')) {\n    console.warn('malformed relation key, skipping:', key)\n    return []\n  }\n  throw err\n}","preventionTips":["Build relation keys programmatically (`${path}.$associations.${assocId}_${side}`) instead of hand-writing strings","Validate view config keys at load time","Reuse existing key constants from the view module"],"tags":["relation-key","associations","view-model","validation"],"backgroundTag":"invalid-relation-key","analyzedSha":"63e28dc96483967b2fc21c881b3f1023c1de7718","analyzedAt":"2026-08-29T15:21:27.377Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}