{"record":{"id":"1fce072ad13264f6","repo":"PaddlePaddle/PaddleOCR","slug":"asset-assetname-must-define-url","errorCode":null,"errorMessage":"Asset \"${assetName}\" must define url.","messagePattern":"Asset \"(.+?)\" must define url\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"paddleocr-js/packages/core/src/resources/model-asset.ts","lineNumber":73,"sourceCode":"function isObject(value: unknown): value is Record<string, unknown> {\n  return Boolean(value && typeof value === \"object\" && !Array.isArray(value));\n}\n\nexport function normalizeModelAsset(assetName: string, asset: unknown): ModelAsset {\n  if (isNonEmptyString(asset)) {\n    const resolvedAsset = DEFAULT_MODEL_ASSETS[asset];\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- runtime guard for missing Record key\n    if (!resolvedAsset) {\n      throw new Error(`Asset \"${assetName}\" references unknown model asset \"${asset}\".`);\n    }\n    return { url: resolvedAsset.url };\n  }\n\n  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)])","sourceCodeStart":55,"sourceCodeEnd":91,"githubUrl":"https://github.com/PaddlePaddle/PaddleOCR/blob/2661c7c0ef5c613e8f93c6e93b2e052399f0f854/paddleocr-js/packages/core/src/resources/model-asset.ts#L55-L91","documentation":"Thrown by normalizeModelAsset() when the asset entry is an object but its url field is missing, empty, or not a non-empty string. The object form of an asset exists solely to carry a URL to a tar model bundle, so the url must be a non-empty string. This fires before any fetch, catching malformed config early.","triggerScenarios":"Passing { det: {} }, { det: { url: \"\" } }, or { det: { url: undefined } }; building the URL from a variable that is undefined at runtime; trailing-comma or key-typo objects like { uri: \"...\" }.","commonSituations":"Template-literal URLs where an env var is unset producing \"undefined\" or empty string; key renamed during a refactor (src -> url); config merging that drops the url field.","solutions":["Set a non-empty url string: assets: { det: { url: \"https://...\" } }","Check for typos in the key name (must be exactly url)","Assert the URL variable is a non-empty string before building the config"],"exampleFix":"// before\nconst ocr = await PaddleOCR.create({ assets: { det: { url: process.env.DET_URL } } }); // env unset -> throws\n\n// after\nif (!process.env.DET_URL) throw new Error(\"DET_URL is required\");\nconst ocr = await PaddleOCR.create({ assets: { det: { url: process.env.DET_URL } } });","handlingStrategy":"validation","validationCode":"function hasNonEmptyUrl(asset: unknown): boolean {\n  return (\n    typeof asset === \"object\" && asset !== null &&\n    typeof (asset as Record<string, unknown>).url === \"string\" &&\n    ((asset as Record<string, unknown>).url as string).length > 0\n  );\n}","typeGuard":"function isUrlAsset(v: unknown): v is { url: string } {\n  return typeof (v as { url?: unknown })?.url === \"string\" && (v as { url: string }).url.length > 0;\n}","tryCatchPattern":null,"preventionTips":["Assert env-derived URLs are non-empty strings before building config","Use exact key 'url' (not uri/src/path)","Fail fast in app bootstrap when required URL env vars are missing"],"tags":["config","validation","url","model-assets"],"backgroundTag":null,"analyzedSha":"2661c7c0ef5c613e8f93c6e93b2e052399f0f854","analyzedAt":"2026-08-14T20:17:30.180Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}