{"record":{"id":"b83ddb4bcdc4fe27","repo":"perspective-dev/perspective","slug":"tilesourcespec-template-uses-s-but-no-subdomai","errorCode":null,"errorMessage":"TileSourceSpec.template uses \"{s}\" but no subdomains were given","messagePattern":"TileSourceSpec\\.template uses \"(.+?)\" but no subdomains were given","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/viewer-charts/src/ts/map/tile-source.ts","lineNumber":170,"sourceCode":"            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(\n            'TileSourceSpec.template uses \"{s}\" but no subdomains were given',\n        );\n    }\n\n    const num = (key: string, dflt: number): number => {\n        const v = raw[key];\n        if (v === undefined) {\n            return dflt;\n        }\n\n        if (typeof v !== \"number\" || !Number.isFinite(v) || v <= 0) {\n            throw new TypeError(\n                `TileSourceSpec.${key} must be a positive number`,\n            );\n        }\n\n        return v;\n    };","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/perspective-dev/perspective/blob/11c8238c0c9c0b9e490b5a6a2dc277afad1e980a/packages/viewer-charts/src/ts/map/tile-source.ts#L152-L188","documentation":"Cross-field consistency check in parseTileSourceSpec: a template containing the {s} placeholder needs at least one entry in `subdomains` to substitute for it. The spec references subdomain cycling but supplies none, so at runtime the URL would contain a literal {s}; the validator rejects it before any tile is requested.","triggerScenarios":"Thrown at packages/viewer-charts/src/ts/map/tile-source.ts:170 when the library encounters an invalid state.","commonSituations":"See trigger scenarios.","solutions":["Provide subdomains: [\"a\",\"b\",\"c\"] alongside the {s} template","Remove {s} from the template URL if the provider serves from a single host","Use a full explicit template like https://a.tile.example.com/{z}/{x}/{y}.png instead of {s}"],"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"}