{"record":{"id":"0d8bad5addd4e877","repo":"pydantic/monty","slug":"wrapperkind-id-must-be-a-canonical-uuid-string-got-json","errorCode":null,"errorMessage":"${wrapperKind} id must be a canonical uuid string, got ${JSON.stringify(id)}","messagePattern":"(.+?) id must be a canonical uuid string, got (.+?)","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/monty-js/ts/classInstance.ts","lineNumber":746,"sourceCode":"\n/** Generates a canonical lowercase uuid4 string. `getRandomValues` rather than\n *  `randomUUID` so insecure browser contexts work too. */\nfunction generateUuid(): string {\n  const bytes = crypto.getRandomValues(new Uint8Array(16))\n  bytes[6] = (bytes[6] & 0x0f) | 0x40 // version 4\n  bytes[8] = (bytes[8] & 0x3f) | 0x80 // RFC 4122 variant\n  const hex = [...bytes].map((byte) => byte.toString(16).padStart(2, '0')).join('')\n  return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`\n}\n\n/** Canonical 8-4-4-4-12 hex uuid; case-insensitive since `normalizeId` lowercases. */\nconst UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i\n\n/** Validates a caller-supplied wrapper id and lowercases it, so the id the\n *  sandbox reports back (always lowercase) is the key the store holds. */\nfunction normalizeId(wrapperKind: string, id: string): string {\n  if (typeof id !== 'string' || !UUID_PATTERN.test(id)) {\n    throw new TypeError(`${wrapperKind} id must be a canonical uuid string, got ${JSON.stringify(id)}`)\n  }\n  return id.toLowerCase()\n}\n\n/** Rejects a string policy other than `'all'`: a bare `'greet'` would\n *  otherwise be treated as a character array (`'g'`, `'r'`, ...). */\nfunction validatePolicy(field: string, policy: AttrPolicy | undefined): void {\n  if (typeof policy === 'string' && policy !== 'all') {\n    throw new TypeError(`${field} must be 'all', undefined or a list/Set of names, got '${policy}'`)\n  }\n}\n\n/** Names no policy may expose, `'all'` or explicit: JS object machinery\n *  that would hand the sandbox the class, its prototype, or a call stack. */\nconst DENIED_NAMES: ReadonlySet<string> = new Set(['constructor', '__proto__', 'prototype', 'arguments', 'caller'])\n\n/** Whether `policy` exposes `name`; `'all'` never exposes underscore names,\n *  and [`DENIED_NAMES`] are refused whichever form the policy takes. */","sourceCodeStart":728,"sourceCodeEnd":764,"githubUrl":"https://github.com/pydantic/monty/blob/adc986b362e3961f407868cb118a99fe831b9e61/crates/monty-js/ts/classInstance.ts#L728-L764","documentation":"Caller-supplied wrapper ids (ClassType or ClassInstance registration) must match the canonical uuid pattern; normalizeId rejects anything else and lowercases the accepted id so the id the sandbox reports back (always lowercase) matches the store key. This TypeError names the wrapper kind and shows the offending value as JSON.","triggerScenarios":"Calling ClassType or ClassInstance with an id that is a non-uuid string, has uppercase-and-odd formatting beyond hex-uuid, contains extra characters, or is a generated short id (e.g. nanoid/cuid) instead of a canonical uuid.","commonSituations":"Generating ids with non-uuid libraries, copying ids with surrounding whitespace or braces, using a database key that is not a uuid.","solutions":["Omit the id and let the library generate a canonical uuid","Pass a canonical uuid (crypto.randomUUID() output is valid)","Trim whitespace and strip braces/quotes, or normalize with crypto.randomUUID() before passing"],"exampleFix":"// before\nconst wrapper = ClassInstance(obj, type, { id: 'obj-123' });\n// after\nconst wrapper = ClassInstance(obj, type, { id: crypto.randomUUID() });","handlingStrategy":"validation","validationCode":"const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\nif (id !== undefined && !(typeof id === 'string' && UUID_RE.test(id))) {\n  throw new Error(`id must be a canonical uuid, got ${JSON.stringify(id)}`);\n}","typeGuard":"function isCanonicalUuid(v) {\n  return typeof v === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(v);\n}","tryCatchPattern":"try {\n  const wrapper = ClassInstance(obj, type, { id });\n} catch (e) {\n  if (e instanceof TypeError && e.message.includes('canonical uuid string')) {\n    throw new Error('supply crypto.randomUUID() or omit the id to auto-generate');\n  }\n  throw e;\n}","preventionTips":["Generate ids with crypto.randomUUID() or omit the id entirely","Never use short-id libraries (nanoid, cuid) for wrapper ids","Trim and strip formatting from ids sourced from external systems before passing them"],"tags":["typescript","uuid","identifier-validation"],"backgroundTag":"invalid-identifier-format","analyzedSha":"adc986b362e3961f407868cb118a99fe831b9e61","analyzedAt":"2026-09-13T19:19:18.698Z","contentChangedAt":"2026-09-13T19:19:18.698Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}