{"record":{"id":"52d88a936da8362e","repo":"toeverything/AFFiNE","slug":"failed-to-read-image-size","errorCode":null,"errorMessage":"Failed to read image size","messagePattern":"Failed to read image size","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"blocksuite/affine/blocks/image/src/utils.ts","lineNumber":239,"sourceCode":"\n  if (exceeded) {\n    const size = formatSize(maxFileSize);\n    toast(std.host, `You can only upload files less than ${size}`);\n  }\n\n  return exceeded;\n}\n\nasync function buildPropsWith(std: BlockStdScope, file: File) {\n  const { size } = file;\n  const [imageSize, sourceId] = await Promise.all([\n    readImageSize(file),\n    std.store.blobSync.set(file),\n  ]);\n\n  if (!(imageSize.width * imageSize.height)) {\n    toast(std.host, 'Failed to read image size, please try another image');\n    throw new Error('Failed to read image size');\n  }\n\n  return { size, sourceId, ...imageSize } satisfies Partial<ImageBlockProps>;\n}\n\nexport async function addSiblingImageBlocks(\n  std: BlockStdScope,\n  files: File[],\n  targetModel: BlockModel,\n  placement: 'after' | 'before' = 'after'\n) {\n  files = files.filter(file => file.type.startsWith('image/'));\n  if (!files.length) return [];\n\n  if (hasExceeded(std, files)) return [];\n\n  const flavour = ImageBlockSchema.model.flavour;\n","sourceCodeStart":221,"sourceCodeEnd":257,"githubUrl":"https://github.com/toeverything/AFFiNE/blob/b4c8548c09da21b2898443559a5b846f0ccf5dd8/blocksuite/affine/blocks/image/src/utils.ts#L221-L257","documentation":"Every nested resolve increments a depth counter on the Resolver; reaching depth 100 throws RecursionLimitError. This guards factories that trigger unbounded re-entrant resolution — typically a component whose factory resolves the same identifier again in noCache mode (e.g. createEntity), where each level creates a fresh instance and the circular-dependency check (which keys on identifier already in the stack) is legitimately bypassed because each level is a new, uncached construction.","triggerScenarios":"A factory or constructor that calls provider.createEntity of its own identifier (self-reproducing entity); two factories that mutually createEntity each other; a factory that calls provider.get with noCache:true recursively; a genuinely linear dependency chain deeper than 100 components.","commonSituations":"Recursive structures (trees, graphs) modeled as Entities where each node constructs child nodes in its constructor; accidentally using createEntity instead of get inside a factory; generated/derived dependency graphs exceeding the depth cap.","solutions":["Stop self/looped instantiation: build child entities lazily (on method calls, after construction) instead of inside the factory/constructor","Use provider.get (cached) instead of createEntity/noCache for recursive references so the cache pool breaks the chain","If the chain is legitimately deep, flatten the construction so no single synchronous resolve nests 100+ levels"],"exampleFix":"// before\nclass Node extends Entity<{ id: string }> {\n  children: Node[] = [];\n  constructor(ids: string[], provider: FrameworkProvider) {\n    super();\n    this.children = ids.map(id => provider.createEntity(Node, { id })); // recursion -> limit\n  }\n}\n\n// after\nclass Node extends Entity<{ id: string }> {\n  children: Node[] = [];\n  attach(child: Node) {\n    this.children.push(child); // built imperatively, outside the factory\n  }\n}","handlingStrategy":"try-catch","validationCode":"null","typeGuard":"null","tryCatchPattern":"import { RecursionLimitError } from '.../framework/core/error';\n\ntry {\n  provider.createEntity(TreeNode, { id: 'root' });\n} catch (err) {\n  if (err instanceof RecursionLimitError) {\n    // construction is recursive by design; rebuild lazily instead of retrying\n  } else {\n    throw err;\n  }\n}","preventionTips":["Never call createEntity (or get with noCache) for the same identifier inside a component's own factory/constructor","Build recursive structures imperatively after construction, not during resolution","Prefer cached provider.get for back-references so the cache pool terminates the chain"],"tags":["dependency-injection","recursion","entity","nocache"],"backgroundTag":"di-resolve-recursion-limit","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"}