{"record":{"id":"5bc72c5891879c8f","repo":"PaddlePaddle/PaddleOCR","slug":"assets-must-define-at-least-one-asset","errorCode":null,"errorMessage":"Assets must define at least one asset.","messagePattern":"Assets must define at least one asset\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"paddleocr-js/packages/core/src/resources/model-asset.ts","lineNumber":87,"sourceCode":"  if (!isObject(asset)) {\n    throw new Error(`Asset \"${assetName}\" must be an object.`);\n  }\n  if (!isNonEmptyString(asset.url)) {\n    throw new Error(`Asset \"${assetName}\" must define url.`);\n  }\n\n  return {\n    url: asset.url\n  };\n}\n\nexport function normalizeAssets(\n  assets: Record<string, unknown> | undefined\n): Record<string, ModelAsset> {\n  const assetEntries = Object.entries(assets || {});\n\n  if (assetEntries.length === 0) {\n    throw new Error(\"Assets must define at least one asset.\");\n  }\n\n  return Object.fromEntries(\n    assetEntries.map(([assetName, asset]) => [assetName, normalizeModelAsset(assetName, asset)])\n  );\n}\n\n// --- Model loading ---\n\nexport function getModelEntryPath(slot: string): string | null {\n  return MODEL_ENTRY_PATHS[slot] || null;\n}\n\nexport function assertModelResourceSlot(kind: string, slot: string, value: unknown): void {\n  if (slot === \"model\") {\n    if (!(value instanceof Uint8Array) || value.byteLength === 0) {\n      throw new Error(`${kind} model requires a non-empty ${MODEL_ENTRY_PATHS.model} resource.`);\n    }","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/paddleocr-js/packages/core/src/resources/model-asset.ts#L69-L105","documentation":"Thrown by normalizeAssets() when the assets object, after resolving defaults, contains zero entries. The OCR pipeline needs at least one model asset to run, so an explicitly empty assets mapping ({}) that overrides built-ins is rejected. Note Object.entries(assets || {}) also treats non-object input the same way, so undefined/empty both land here when no defaults merge in.","triggerScenarios":"Passing assets: {} explicitly (overriding default assets); a config spread that results in an empty object; passing assets: undefined in a code path where defaults are not applied.","commonSituations":"Apps intending to disable a subset of models by passing an empty object instead of omitting the key; dynamic config assembly that filters out every entry.","solutions":["Omit the assets option entirely to use built-in defaults","Provide at least one entry: assets: { det: \"...\" } or { det: { url } }","Log the assembled config before create() to catch accidental empty objects"],"exampleFix":"// before\nconst ocr = await PaddleOCR.create({ assets: {} }); // throws: at least one asset required\n\n// after\nconst ocr = await PaddleOCR.create({}); // use built-in default assets","handlingStrategy":"validation","validationCode":"const assets = maybeAssets && Object.keys(maybeAssets).length > 0 ? maybeAssets : undefined;\n// pass undefined (not {}) to use defaults","typeGuard":"function hasAssets(v: unknown): v is Record<string, unknown> {\n  return typeof v === \"object\" && v !== null && Object.keys(v).length > 0;\n}","tryCatchPattern":null,"preventionTips":["Omit the assets key instead of passing {}","Log the final config object before create() during development"],"tags":["config","validation","model-assets"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}