{"record":{"id":"75ad0d298b24e91c","repo":"perspective-dev/perspective","slug":"tilesourcespec-template-must-contain-placeholde","errorCode":null,"errorMessage":"TileSourceSpec.template must contain \"${placeholder}\"","messagePattern":"TileSourceSpec\\.template must contain \"(.+?)\"","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/viewer-charts/src/ts/map/tile-source.ts","lineNumber":152,"sourceCode":"    const raw = input as Record<string, unknown>;\n    const str = (key: string): string => {\n        const v = raw[key];\n        if (typeof v !== \"string\" || v.length === 0) {\n            throw new TypeError(\n                `TileSourceSpec.${key} must be a non-empty string`,\n            );\n        }\n\n        return v;\n    };\n\n    const id = str(\"id\");\n    const label = str(\"label\");\n    const template = str(\"template\");\n    const attribution = str(\"attribution\");\n    for (const placeholder of [\"{z}\", \"{x}\", \"{y}\"]) {\n        if (!template.includes(placeholder)) {\n            throw new TypeError(\n                `TileSourceSpec.template must contain \"${placeholder}\"`,\n            );\n        }\n    }\n\n    const rawSubdomains = raw[\"subdomains\"] ?? [];\n    if (\n        !Array.isArray(rawSubdomains) ||\n        rawSubdomains.some((s) => typeof s !== \"string\" || s.length === 0)\n    ) {\n        throw new TypeError(\n            \"TileSourceSpec.subdomains must be an array of non-empty strings\",\n        );\n    }\n\n    const subdomains = Object.freeze([...rawSubdomains] as string[]);\n    if (template.includes(\"{s}\") && subdomains.length === 0) {\n        throw new TypeError(","sourceCodeStart":134,"sourceCodeEnd":170,"githubUrl":"https://github.com/perspective-dev/perspective/blob/11c8238c0c9c0b9e490b5a6a2dc277afad1e980a/packages/viewer-charts/src/ts/map/tile-source.ts#L134-L170","documentation":"After validating the template string, parseTileSourceSpec requires the {z}, {x}, {y} placeholders so the tile URL can be filled with zoom/x/y coordinates per tile request. A template missing any of these (e.g. a plain WMS URL or a typo like {Z}) cannot be used by the tile layer.","triggerScenarios":"Thrown at packages/viewer-charts/src/ts/map/tile-source.ts:152 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Include all three placeholders, e.g. https://tile.example.com/{z}/{x}/{y}.png","Fix casing/typos in placeholders ({Z}/{X}/{Y} are not matched)","Use a template from a standard raster XYZ tile provider as reference"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":[],"tags":[],"backgroundTag":null,"analyzedSha":"11c8238c0c9c0b9e490b5a6a2dc277afad1e980a","analyzedAt":"2026-09-09T00:35:19.377Z","contentChangedAt":"2026-09-09T00:35:19.377Z","schemaVersion":2},"datasetVersion":"2026-09-17T15:17:12.973Z"}