@brandlab-digital/site-core 1.0.9 → 1.0.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/geography.d.ts +15 -7
- package/dist/geography.d.ts.map +1 -1
- package/dist/geography.js +302 -4
- package/dist/geography.js.map +1 -1
- package/dist/locations.d.ts +28 -0
- package/dist/locations.d.ts.map +1 -1
- package/dist/locations.js.map +1 -1
- package/dist/routes.d.ts +14 -6
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +12 -9
- package/dist/routes.js.map +1 -1
- package/package.json +1 -1
package/dist/locations.d.ts
CHANGED
|
@@ -86,6 +86,34 @@ export interface RegistryNonSuburbLocality {
|
|
|
86
86
|
notes?: string;
|
|
87
87
|
}
|
|
88
88
|
export type RegistryOfficialLocality = RegistrySuburb | RegistryNonSuburbLocality;
|
|
89
|
+
export interface WebsiteLocationPolicyExclusionGroup {
|
|
90
|
+
reasonCode: string;
|
|
91
|
+
reason: string;
|
|
92
|
+
slugs: readonly string[];
|
|
93
|
+
}
|
|
94
|
+
export interface WebsiteLocationPolicyExcludedLocality {
|
|
95
|
+
entityType: RegistryNonSuburbEntityType;
|
|
96
|
+
officialUrbanity: 'R';
|
|
97
|
+
reason: string;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Website eligibility policy layered over factual geography.
|
|
101
|
+
*
|
|
102
|
+
* The registry continues to retain every official locality. The policy keeps
|
|
103
|
+
* owner-excluded places out of normal project location catalogs and records
|
|
104
|
+
* how existing public URLs must be retired.
|
|
105
|
+
*/
|
|
106
|
+
export interface WebsiteLocationPolicy {
|
|
107
|
+
version: string;
|
|
108
|
+
generated: string;
|
|
109
|
+
registryVersion: string;
|
|
110
|
+
scope: string;
|
|
111
|
+
defaultCandidateRule: string;
|
|
112
|
+
projectOverrideRule: string;
|
|
113
|
+
sourceSnapshot: string;
|
|
114
|
+
defaultExcludedSuburbGroups: readonly WebsiteLocationPolicyExclusionGroup[];
|
|
115
|
+
excludedLocalities: Readonly<Record<string, WebsiteLocationPolicyExcludedLocality>>;
|
|
116
|
+
}
|
|
89
117
|
export interface GeographyRegistry {
|
|
90
118
|
version: string;
|
|
91
119
|
generated: string;
|
package/dist/locations.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"locations.d.ts","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"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"}
|
package/dist/locations.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"locations.js","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Location publication and geography-registry contracts.\n *\n * Brandlab owns route correctness and shared geography. Projects own their\n * content, evidence and publication decisions.\n */\n\n/**\n * The preferred location publication contract. A project owner explicitly\n * decides whether a route is indexable after checking customer usefulness,\n * geographic correctness and available evidence.\n */\nexport interface BrandlabLocationRouteDecision {\n indexing: 'index' | 'noindex-follow'\n rationale: string\n reviewedAt?: string\n}\n\nexport function defineLocationRouteDecision<const T extends BrandlabLocationRouteDecision>(\n input: T,\n): T {\n if (input.indexing !== 'index' && input.indexing !== 'noindex-follow') {\n throw new Error('Location route indexing must be \"index\" or \"noindex-follow\".')\n }\n if (!input.rationale.trim()) {\n throw new Error('Location route decision requires a concise rationale.')\n }\n if (input.reviewedAt && Number.isNaN(Date.parse(input.reviewedAt))) {\n throw new Error('Location route decision reviewedAt must be a valid date.')\n }\n return input\n}\n\nexport function resolveLocationRouteIndexing(\n decision: BrandlabLocationRouteDecision | null | undefined,\n): 'index' | 'noindex-follow' {\n if (!decision) {\n throw new Error('Location route decision is required.')\n }\n return defineLocationRouteDecision(decision).indexing\n}\n\n// ---------------------------------------------------------------------------\n// Geography registries (standards/registry/*-geography.json)\n// ---------------------------------------------------------------------------\n\nexport interface RegistryRegion {\n slug: string\n name: string\n /** Preferred query-facing region label for titles and primary headings. */\n seoName: string\n /** Recognised search/display variants; these never create additional URLs. */\n alternateNames?: readonly string[]\n /** 'the' → copy reads \"the Eastern Suburbs\"; 'none' → \"Western Sydney\". */\n articleForm: 'the' | 'none'\n councilSlugs: readonly string[]\n}\n\nexport interface RegistrySubRegion {\n slug: string\n name: string\n parentRegionSlug: string\n /** Council-combo sub-region membership. */\n councilSlugs?: readonly string[]\n /** Explicit suburb membership for district-style sub-regions. */\n suburbSlugs?: readonly string[]\n /** Provenance for promoted project districts or audited groupings. */\n source?: string\n}\n\n/**\n * Internal service-coverage partition. Coverage groups have no public name,\n * route, metadata, schema, sitemap, breadcrumb, navigation, or page model.\n */\nexport interface RegistryCoverageGroup {\n slug: string\n /** Maintainer-facing label; never render this value as customer copy. */\n adminLabel: string\n regionSlug: string\n visibility: 'internal'\n}\n\nexport interface RegistryCouncil {\n slug: string\n name: string\n}\n\nexport interface RegistrySuburb {\n name: string\n slug: string\n regionSlug: string\n /** Internal service-coverage partition; never a page parent or URL segment. */\n coverageGroupSlug?: string\n councilSlug: string\n /** Secondary council memberships for split localities. */\n sharedCouncilSlugs?: readonly string[]\n /** Australia Post-matched postcode. */\n postcode: string\n /** Stewardship notes for boundary bindings and corrections. */\n notes?: string\n}\n\nexport type RegistryNonSuburbEntityType =\n | 'rural-locality'\n | 'natural-area'\n | 'protected-area'\n | 'waterbody'\n\n/**\n * Official NSW geography retained for spatial and council completeness but\n * excluded from the normal website suburb catalog.\n */\nexport interface RegistryNonSuburbLocality {\n name: string\n slug: string\n regionSlug: string\n councilSlug: string\n sharedCouncilSlugs?: readonly string[]\n postcode: null\n entityType: RegistryNonSuburbEntityType\n officialUrbanity: 'R'\n defaultWebsiteEligibility: false\n eligibilityReason: string\n notes?: string\n}\n\nexport type RegistryOfficialLocality = RegistrySuburb | RegistryNonSuburbLocality\n\nexport interface GeographyRegistry {\n version: string\n generated: string\n /** Provenance: where the registry data was derived from. */\n sources?: readonly string[]\n /** Stewardship notes for maintainers. */\n notes?: string\n regions: readonly RegistryRegion[]\n subRegions: readonly RegistrySubRegion[]\n /** Backend-only groupings used to select served suburbs. */\n coverageGroups: readonly RegistryCoverageGroup[]\n /** Legacy slug → canonical slug. */\n aliases: Readonly<Record<string, string>>\n councils: readonly RegistryCouncil[]\n /** Postcode-backed candidates only; this does not itself approve pages. */\n suburbs: readonly RegistrySuburb[]\n /** Official non-suburb geography excluded from default website locations. */\n nonSuburbLocalities: readonly RegistryNonSuburbLocality[]\n}\n\nexport type RegistryEntityKind = 'region' | 'subRegion' | 'council' | 'suburb'\n\n/** Canonical Brandlab slugify — identical to the benchmark implementation. */\nexport function toLocationSlug(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n}\n\n/**\n * Resolve a possibly-legacy slug within an explicit registry entity kind.\n *\n * Entity context is required because canonical suburb slugs such as\n * `blacktown`, `hornsby`, `randwick`, and `willoughby` are also legacy council\n * aliases. A canonical slug for the requested kind always wins over an alias.\n */\nexport function resolveRegistryAlias(\n slug: string,\n registry: GeographyRegistry,\n kind: RegistryEntityKind | readonly RegistryEntityKind[],\n): string {\n const kinds = Array.isArray(kind) ? kind : [kind]\n const canonicalSets = kinds.map((entityKind) => getRegistrySlugSet(registry, entityKind))\n\n if (canonicalSets.some((slugSet) => slugSet.has(slug))) return slug\n\n const target = registry.aliases[slug]\n if (!target) return slug\n\n return canonicalSets.some((slugSet) => slugSet.has(target)) ? target : slug\n}\n\nexport interface CatalogRegistryIssue {\n kind:\n | 'unknown-region'\n | 'unknown-coverage-group'\n | 'unknown-council'\n | 'unknown-suburb'\n | 'excluded-website-locality'\n | 'suburb-region-mismatch'\n | 'suburb-coverage-group-mismatch'\n | 'suburb-council-mismatch'\n slug: string\n detail: string\n}\n\nexport interface CatalogForValidation {\n regionSlugs: readonly string[]\n /** Backend coverage selectors; these are never treated as region/page slugs. */\n coverageGroupSlugs?: readonly string[]\n suburbs: readonly {\n slug: string\n regionSlug: string\n coverageGroupSlug?: string\n councilSlug?: string\n }[]\n}\n\n/**\n * Expand internal coverage selectors to canonical suburb slugs.\n *\n * This helper only answers where a business serves. It does not approve pages,\n * static params, sitemap entries, schema nodes, or internal links.\n */\nexport function getCoverageGroupSuburbSlugs(\n coverageGroupSlugs: readonly string[],\n registry: GeographyRegistry,\n): string[] {\n const knownCoverageGroupSlugs = new Set(registry.coverageGroups.map((group) => group.slug))\n const unknownCoverageGroupSlug = coverageGroupSlugs.find(\n (slug) => !knownCoverageGroupSlugs.has(slug),\n )\n if (unknownCoverageGroupSlug) {\n throw new Error(`Unknown registry coverage group \"${unknownCoverageGroupSlug}\".`)\n }\n\n const selected = new Set(coverageGroupSlugs)\n return registry.suburbs\n .filter(\n (suburb) => suburb.coverageGroupSlug && selected.has(suburb.coverageGroupSlug),\n )\n .map((suburb) => suburb.slug)\n}\n\n/**\n * Validate a project catalog subset against the shared registry. Alias slugs\n * are resolved before comparison so migrated repos that kept live URLs can\n * still pass. Returns an empty array when the subset is clean.\n */\nexport function validateCatalogAgainstRegistry(\n catalog: CatalogForValidation,\n registry: GeographyRegistry,\n): CatalogRegistryIssue[] {\n const issues: CatalogRegistryIssue[] = []\n const regionSlugSet = new Set(registry.regions.map((region) => region.slug))\n const subRegionSlugSet = new Set(registry.subRegions.map((subRegion) => subRegion.slug))\n const coverageGroupSlugSet = new Set(registry.coverageGroups.map((group) => group.slug))\n const councilSlugSet = new Set(registry.councils.map((council) => council.slug))\n const suburbBySlug = new Map(registry.suburbs.map((suburb) => [suburb.slug, suburb]))\n const nonSuburbLocalityBySlug = new Map(\n registry.nonSuburbLocalities.map((locality) => [locality.slug, locality]),\n )\n\n for (const rawRegionSlug of catalog.regionSlugs) {\n const regionSlug = resolveRegistryAlias(rawRegionSlug, registry, ['region', 'subRegion'])\n if (!regionSlugSet.has(regionSlug) && !subRegionSlugSet.has(regionSlug)) {\n issues.push({\n kind: 'unknown-region',\n slug: rawRegionSlug,\n detail: `Region slug \"${rawRegionSlug}\" is not a registry region, sub-region, or alias.`,\n })\n }\n }\n\n for (const coverageGroupSlug of catalog.coverageGroupSlugs ?? []) {\n if (!coverageGroupSlugSet.has(coverageGroupSlug)) {\n issues.push({\n kind: 'unknown-coverage-group',\n slug: coverageGroupSlug,\n detail: `Coverage group slug \"${coverageGroupSlug}\" is not an internal registry coverage group.`,\n })\n }\n }\n\n for (const suburb of catalog.suburbs) {\n const resolvedSuburbSlug = suburbBySlug.has(suburb.slug)\n ? suburb.slug\n : resolveRegistryAlias(suburb.slug, registry, 'suburb')\n const registrySuburb = suburbBySlug.get(resolvedSuburbSlug)\n\n if (!registrySuburb) {\n const excludedLocality = nonSuburbLocalityBySlug.get(suburb.slug)\n if (excludedLocality) {\n issues.push({\n kind: 'excluded-website-locality',\n slug: suburb.slug,\n detail: `Official locality \"${suburb.slug}\" is classified as ${excludedLocality.entityType} and excluded from the normal website suburb catalog: ${excludedLocality.eligibilityReason}`,\n })\n continue\n }\n issues.push({\n kind: 'unknown-suburb',\n slug: suburb.slug,\n detail: `Suburb slug \"${suburb.slug}\" is not in the registry (not gazetted, or needs adding to the registry first).`,\n })\n continue\n }\n\n const regionSlug = resolveRegistryAlias(suburb.regionSlug, registry, ['region', 'subRegion'])\n const regionMatches =\n registrySuburb.regionSlug === regionSlug ||\n registry.subRegions.some(\n (subRegion) =>\n subRegion.slug === regionSlug &&\n subRegion.parentRegionSlug === registrySuburb.regionSlug &&\n (subRegion.suburbSlugs?.includes(registrySuburb.slug) ||\n subRegion.councilSlugs?.some(\n (councilSlug) =>\n councilSlug === registrySuburb.councilSlug ||\n registrySuburb.sharedCouncilSlugs?.includes(councilSlug),\n )),\n )\n\n if (!regionMatches) {\n issues.push({\n kind: 'suburb-region-mismatch',\n slug: suburb.slug,\n detail: `Suburb \"${suburb.slug}\" is assigned to \"${suburb.regionSlug}\" but the registry places it in \"${registrySuburb.regionSlug}\".`,\n })\n }\n\n if (suburb.coverageGroupSlug) {\n if (!coverageGroupSlugSet.has(suburb.coverageGroupSlug)) {\n issues.push({\n kind: 'unknown-coverage-group',\n slug: suburb.coverageGroupSlug,\n detail: `Coverage group slug \"${suburb.coverageGroupSlug}\" is not an internal registry coverage group.`,\n })\n } else if (registrySuburb.coverageGroupSlug !== suburb.coverageGroupSlug) {\n issues.push({\n kind: 'suburb-coverage-group-mismatch',\n slug: suburb.slug,\n detail: `Suburb \"${suburb.slug}\" is assigned to coverage group \"${suburb.coverageGroupSlug}\" but the registry places it under \"${registrySuburb.coverageGroupSlug ?? 'none'}\".`,\n })\n }\n }\n\n if (suburb.councilSlug) {\n const councilSlug = resolveRegistryAlias(suburb.councilSlug, registry, 'council')\n if (!councilSlugSet.has(councilSlug)) {\n issues.push({\n kind: 'unknown-council',\n slug: suburb.councilSlug,\n detail: `Council slug \"${suburb.councilSlug}\" is not a registry council or alias.`,\n })\n } else if (\n registrySuburb.councilSlug !== councilSlug &&\n !registrySuburb.sharedCouncilSlugs?.includes(councilSlug)\n ) {\n issues.push({\n kind: 'suburb-council-mismatch',\n slug: suburb.slug,\n detail: `Suburb \"${suburb.slug}\" is assigned to council \"${suburb.councilSlug}\" but the registry places it under \"${registrySuburb.councilSlug}\".`,\n })\n }\n }\n }\n\n return issues\n}\n\nfunction getRegistrySlugSet(\n registry: GeographyRegistry,\n kind: RegistryEntityKind,\n): ReadonlySet<string> {\n switch (kind) {\n case 'region':\n return new Set(registry.regions.map((region) => region.slug))\n case 'subRegion':\n return new Set(registry.subRegions.map((subRegion) => subRegion.slug))\n case 'council':\n return new Set(registry.councils.map((council) => council.slug))\n case 'suburb':\n return new Set(registry.suburbs.map((suburb) => suburb.slug))\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"locations.js","sourceRoot":"","sources":["../src/locations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Location publication and geography-registry contracts.\n *\n * Brandlab owns route correctness and shared geography. Projects own their\n * content, evidence and publication decisions.\n */\n\n/**\n * The preferred location publication contract. A project owner explicitly\n * decides whether a route is indexable after checking customer usefulness,\n * geographic correctness and available evidence.\n */\nexport interface BrandlabLocationRouteDecision {\n indexing: 'index' | 'noindex-follow'\n rationale: string\n reviewedAt?: string\n}\n\nexport function defineLocationRouteDecision<const T extends BrandlabLocationRouteDecision>(\n input: T,\n): T {\n if (input.indexing !== 'index' && input.indexing !== 'noindex-follow') {\n throw new Error('Location route indexing must be \"index\" or \"noindex-follow\".')\n }\n if (!input.rationale.trim()) {\n throw new Error('Location route decision requires a concise rationale.')\n }\n if (input.reviewedAt && Number.isNaN(Date.parse(input.reviewedAt))) {\n throw new Error('Location route decision reviewedAt must be a valid date.')\n }\n return input\n}\n\nexport function resolveLocationRouteIndexing(\n decision: BrandlabLocationRouteDecision | null | undefined,\n): 'index' | 'noindex-follow' {\n if (!decision) {\n throw new Error('Location route decision is required.')\n }\n return defineLocationRouteDecision(decision).indexing\n}\n\n// ---------------------------------------------------------------------------\n// Geography registries (standards/registry/*-geography.json)\n// ---------------------------------------------------------------------------\n\nexport interface RegistryRegion {\n slug: string\n name: string\n /** Preferred query-facing region label for titles and primary headings. */\n seoName: string\n /** Recognised search/display variants; these never create additional URLs. */\n alternateNames?: readonly string[]\n /** 'the' → copy reads \"the Eastern Suburbs\"; 'none' → \"Western Sydney\". */\n articleForm: 'the' | 'none'\n councilSlugs: readonly string[]\n}\n\nexport interface RegistrySubRegion {\n slug: string\n name: string\n parentRegionSlug: string\n /** Council-combo sub-region membership. */\n councilSlugs?: readonly string[]\n /** Explicit suburb membership for district-style sub-regions. */\n suburbSlugs?: readonly string[]\n /** Provenance for promoted project districts or audited groupings. */\n source?: string\n}\n\n/**\n * Internal service-coverage partition. Coverage groups have no public name,\n * route, metadata, schema, sitemap, breadcrumb, navigation, or page model.\n */\nexport interface RegistryCoverageGroup {\n slug: string\n /** Maintainer-facing label; never render this value as customer copy. */\n adminLabel: string\n regionSlug: string\n visibility: 'internal'\n}\n\nexport interface RegistryCouncil {\n slug: string\n name: string\n}\n\nexport interface RegistrySuburb {\n name: string\n slug: string\n regionSlug: string\n /** Internal service-coverage partition; never a page parent or URL segment. */\n coverageGroupSlug?: string\n councilSlug: string\n /** Secondary council memberships for split localities. */\n sharedCouncilSlugs?: readonly string[]\n /** Australia Post-matched postcode. */\n postcode: string\n /** Stewardship notes for boundary bindings and corrections. */\n notes?: string\n}\n\nexport type RegistryNonSuburbEntityType =\n | 'rural-locality'\n | 'natural-area'\n | 'protected-area'\n | 'waterbody'\n\n/**\n * Official NSW geography retained for spatial and council completeness but\n * excluded from the normal website suburb catalog.\n */\nexport interface RegistryNonSuburbLocality {\n name: string\n slug: string\n regionSlug: string\n councilSlug: string\n sharedCouncilSlugs?: readonly string[]\n postcode: null\n entityType: RegistryNonSuburbEntityType\n officialUrbanity: 'R'\n defaultWebsiteEligibility: false\n eligibilityReason: string\n notes?: string\n}\n\nexport type RegistryOfficialLocality = RegistrySuburb | RegistryNonSuburbLocality\n\nexport interface WebsiteLocationPolicyExclusionGroup {\n reasonCode: string\n reason: string\n slugs: readonly string[]\n}\n\nexport interface WebsiteLocationPolicyExcludedLocality {\n entityType: RegistryNonSuburbEntityType\n officialUrbanity: 'R'\n reason: string\n}\n\n/**\n * Website eligibility policy layered over factual geography.\n *\n * The registry continues to retain every official locality. The policy keeps\n * owner-excluded places out of normal project location catalogs and records\n * how existing public URLs must be retired.\n */\nexport interface WebsiteLocationPolicy {\n version: string\n generated: string\n registryVersion: string\n scope: string\n defaultCandidateRule: string\n projectOverrideRule: string\n sourceSnapshot: string\n defaultExcludedSuburbGroups: readonly WebsiteLocationPolicyExclusionGroup[]\n excludedLocalities: Readonly<Record<string, WebsiteLocationPolicyExcludedLocality>>\n}\n\nexport interface GeographyRegistry {\n version: string\n generated: string\n /** Provenance: where the registry data was derived from. */\n sources?: readonly string[]\n /** Stewardship notes for maintainers. */\n notes?: string\n regions: readonly RegistryRegion[]\n subRegions: readonly RegistrySubRegion[]\n /** Backend-only groupings used to select served suburbs. */\n coverageGroups: readonly RegistryCoverageGroup[]\n /** Legacy slug → canonical slug. */\n aliases: Readonly<Record<string, string>>\n councils: readonly RegistryCouncil[]\n /** Postcode-backed candidates only; this does not itself approve pages. */\n suburbs: readonly RegistrySuburb[]\n /** Official non-suburb geography excluded from default website locations. */\n nonSuburbLocalities: readonly RegistryNonSuburbLocality[]\n}\n\nexport type RegistryEntityKind = 'region' | 'subRegion' | 'council' | 'suburb'\n\n/** Canonical Brandlab slugify — identical to the benchmark implementation. */\nexport function toLocationSlug(name: string): string {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/(^-|-$)/g, '')\n}\n\n/**\n * Resolve a possibly-legacy slug within an explicit registry entity kind.\n *\n * Entity context is required because canonical suburb slugs such as\n * `blacktown`, `hornsby`, `randwick`, and `willoughby` are also legacy council\n * aliases. A canonical slug for the requested kind always wins over an alias.\n */\nexport function resolveRegistryAlias(\n slug: string,\n registry: GeographyRegistry,\n kind: RegistryEntityKind | readonly RegistryEntityKind[],\n): string {\n const kinds = Array.isArray(kind) ? kind : [kind]\n const canonicalSets = kinds.map((entityKind) => getRegistrySlugSet(registry, entityKind))\n\n if (canonicalSets.some((slugSet) => slugSet.has(slug))) return slug\n\n const target = registry.aliases[slug]\n if (!target) return slug\n\n return canonicalSets.some((slugSet) => slugSet.has(target)) ? target : slug\n}\n\nexport interface CatalogRegistryIssue {\n kind:\n | 'unknown-region'\n | 'unknown-coverage-group'\n | 'unknown-council'\n | 'unknown-suburb'\n | 'excluded-website-locality'\n | 'suburb-region-mismatch'\n | 'suburb-coverage-group-mismatch'\n | 'suburb-council-mismatch'\n slug: string\n detail: string\n}\n\nexport interface CatalogForValidation {\n regionSlugs: readonly string[]\n /** Backend coverage selectors; these are never treated as region/page slugs. */\n coverageGroupSlugs?: readonly string[]\n suburbs: readonly {\n slug: string\n regionSlug: string\n coverageGroupSlug?: string\n councilSlug?: string\n }[]\n}\n\n/**\n * Expand internal coverage selectors to canonical suburb slugs.\n *\n * This helper only answers where a business serves. It does not approve pages,\n * static params, sitemap entries, schema nodes, or internal links.\n */\nexport function getCoverageGroupSuburbSlugs(\n coverageGroupSlugs: readonly string[],\n registry: GeographyRegistry,\n): string[] {\n const knownCoverageGroupSlugs = new Set(registry.coverageGroups.map((group) => group.slug))\n const unknownCoverageGroupSlug = coverageGroupSlugs.find(\n (slug) => !knownCoverageGroupSlugs.has(slug),\n )\n if (unknownCoverageGroupSlug) {\n throw new Error(`Unknown registry coverage group \"${unknownCoverageGroupSlug}\".`)\n }\n\n const selected = new Set(coverageGroupSlugs)\n return registry.suburbs\n .filter(\n (suburb) => suburb.coverageGroupSlug && selected.has(suburb.coverageGroupSlug),\n )\n .map((suburb) => suburb.slug)\n}\n\n/**\n * Validate a project catalog subset against the shared registry. Alias slugs\n * are resolved before comparison so migrated repos that kept live URLs can\n * still pass. Returns an empty array when the subset is clean.\n */\nexport function validateCatalogAgainstRegistry(\n catalog: CatalogForValidation,\n registry: GeographyRegistry,\n): CatalogRegistryIssue[] {\n const issues: CatalogRegistryIssue[] = []\n const regionSlugSet = new Set(registry.regions.map((region) => region.slug))\n const subRegionSlugSet = new Set(registry.subRegions.map((subRegion) => subRegion.slug))\n const coverageGroupSlugSet = new Set(registry.coverageGroups.map((group) => group.slug))\n const councilSlugSet = new Set(registry.councils.map((council) => council.slug))\n const suburbBySlug = new Map(registry.suburbs.map((suburb) => [suburb.slug, suburb]))\n const nonSuburbLocalityBySlug = new Map(\n registry.nonSuburbLocalities.map((locality) => [locality.slug, locality]),\n )\n\n for (const rawRegionSlug of catalog.regionSlugs) {\n const regionSlug = resolveRegistryAlias(rawRegionSlug, registry, ['region', 'subRegion'])\n if (!regionSlugSet.has(regionSlug) && !subRegionSlugSet.has(regionSlug)) {\n issues.push({\n kind: 'unknown-region',\n slug: rawRegionSlug,\n detail: `Region slug \"${rawRegionSlug}\" is not a registry region, sub-region, or alias.`,\n })\n }\n }\n\n for (const coverageGroupSlug of catalog.coverageGroupSlugs ?? []) {\n if (!coverageGroupSlugSet.has(coverageGroupSlug)) {\n issues.push({\n kind: 'unknown-coverage-group',\n slug: coverageGroupSlug,\n detail: `Coverage group slug \"${coverageGroupSlug}\" is not an internal registry coverage group.`,\n })\n }\n }\n\n for (const suburb of catalog.suburbs) {\n const resolvedSuburbSlug = suburbBySlug.has(suburb.slug)\n ? suburb.slug\n : resolveRegistryAlias(suburb.slug, registry, 'suburb')\n const registrySuburb = suburbBySlug.get(resolvedSuburbSlug)\n\n if (!registrySuburb) {\n const excludedLocality = nonSuburbLocalityBySlug.get(suburb.slug)\n if (excludedLocality) {\n issues.push({\n kind: 'excluded-website-locality',\n slug: suburb.slug,\n detail: `Official locality \"${suburb.slug}\" is classified as ${excludedLocality.entityType} and excluded from the normal website suburb catalog: ${excludedLocality.eligibilityReason}`,\n })\n continue\n }\n issues.push({\n kind: 'unknown-suburb',\n slug: suburb.slug,\n detail: `Suburb slug \"${suburb.slug}\" is not in the registry (not gazetted, or needs adding to the registry first).`,\n })\n continue\n }\n\n const regionSlug = resolveRegistryAlias(suburb.regionSlug, registry, ['region', 'subRegion'])\n const regionMatches =\n registrySuburb.regionSlug === regionSlug ||\n registry.subRegions.some(\n (subRegion) =>\n subRegion.slug === regionSlug &&\n subRegion.parentRegionSlug === registrySuburb.regionSlug &&\n (subRegion.suburbSlugs?.includes(registrySuburb.slug) ||\n subRegion.councilSlugs?.some(\n (councilSlug) =>\n councilSlug === registrySuburb.councilSlug ||\n registrySuburb.sharedCouncilSlugs?.includes(councilSlug),\n )),\n )\n\n if (!regionMatches) {\n issues.push({\n kind: 'suburb-region-mismatch',\n slug: suburb.slug,\n detail: `Suburb \"${suburb.slug}\" is assigned to \"${suburb.regionSlug}\" but the registry places it in \"${registrySuburb.regionSlug}\".`,\n })\n }\n\n if (suburb.coverageGroupSlug) {\n if (!coverageGroupSlugSet.has(suburb.coverageGroupSlug)) {\n issues.push({\n kind: 'unknown-coverage-group',\n slug: suburb.coverageGroupSlug,\n detail: `Coverage group slug \"${suburb.coverageGroupSlug}\" is not an internal registry coverage group.`,\n })\n } else if (registrySuburb.coverageGroupSlug !== suburb.coverageGroupSlug) {\n issues.push({\n kind: 'suburb-coverage-group-mismatch',\n slug: suburb.slug,\n detail: `Suburb \"${suburb.slug}\" is assigned to coverage group \"${suburb.coverageGroupSlug}\" but the registry places it under \"${registrySuburb.coverageGroupSlug ?? 'none'}\".`,\n })\n }\n }\n\n if (suburb.councilSlug) {\n const councilSlug = resolveRegistryAlias(suburb.councilSlug, registry, 'council')\n if (!councilSlugSet.has(councilSlug)) {\n issues.push({\n kind: 'unknown-council',\n slug: suburb.councilSlug,\n detail: `Council slug \"${suburb.councilSlug}\" is not a registry council or alias.`,\n })\n } else if (\n registrySuburb.councilSlug !== councilSlug &&\n !registrySuburb.sharedCouncilSlugs?.includes(councilSlug)\n ) {\n issues.push({\n kind: 'suburb-council-mismatch',\n slug: suburb.slug,\n detail: `Suburb \"${suburb.slug}\" is assigned to council \"${suburb.councilSlug}\" but the registry places it under \"${registrySuburb.councilSlug}\".`,\n })\n }\n }\n }\n\n return issues\n}\n\nfunction getRegistrySlugSet(\n registry: GeographyRegistry,\n kind: RegistryEntityKind,\n): ReadonlySet<string> {\n switch (kind) {\n case 'region':\n return new Set(registry.regions.map((region) => region.slug))\n case 'subRegion':\n return new Set(registry.subRegions.map((subRegion) => subRegion.slug))\n case 'council':\n return new Set(registry.councils.map((council) => council.slug))\n case 'suburb':\n return new Set(registry.suburbs.map((suburb) => suburb.slug))\n }\n}\n"]}
|
package/dist/routes.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { type BrandlabLocationRouteDecision } from './locations.js';
|
|
2
2
|
export type BrandlabIndexingDirective = 'index' | 'noindex-follow' | 'noindex-nofollow';
|
|
3
|
-
export type BrandlabLegalRoutePurpose = 'terms' | 'privacy'
|
|
3
|
+
export type BrandlabLegalRoutePurpose = 'terms' | 'privacy';
|
|
4
4
|
export type BrandlabLegalReference = 'google-maps-platform-terms' | 'google-privacy-policy';
|
|
5
5
|
export declare const BRANDLAB_LEGAL_REFERENCE_URLS: Readonly<Record<BrandlabLegalReference, string>>;
|
|
6
|
-
export type BrandlabRouteKind = 'home' | 'service-index' | 'service' | 'location-index' | 'location' | 'service-location' | 'blog-index' | 'blog-post' | 'project-index' | 'project' | 'contact' | 'legal' | 'thank-you';
|
|
7
|
-
|
|
6
|
+
export type BrandlabRouteKind = 'home' | 'service-index' | 'service' | 'location-index' | 'location' | 'service-location' | 'blog-index' | 'blog-post' | 'project-index' | 'project' | 'content' | 'contact' | 'legal' | 'utility' | 'thank-you';
|
|
7
|
+
interface BrandlabRouteBase {
|
|
8
8
|
id: string;
|
|
9
|
-
kind: BrandlabRouteKind;
|
|
10
9
|
path: string;
|
|
11
10
|
breadcrumbLabel: string;
|
|
12
11
|
parentId?: string;
|
|
@@ -15,10 +14,18 @@ export interface BrandlabRoute {
|
|
|
15
14
|
lastSignificantChange?: string;
|
|
16
15
|
/** Preferred explicit publication decision for location routes. */
|
|
17
16
|
locationDecision?: BrandlabLocationRouteDecision;
|
|
17
|
+
}
|
|
18
|
+
export type BrandlabRoute = (BrandlabRouteBase & {
|
|
19
|
+
kind: 'legal';
|
|
18
20
|
/** Machine-identifiable legal surface used by provider policy binding. */
|
|
19
|
-
legalPurpose
|
|
21
|
+
legalPurpose: BrandlabLegalRoutePurpose;
|
|
20
22
|
legalReferences?: readonly BrandlabLegalReference[];
|
|
21
|
-
}
|
|
23
|
+
}) | (BrandlabRouteBase & {
|
|
24
|
+
kind: Exclude<BrandlabRouteKind, 'legal'>;
|
|
25
|
+
/** Legal policy fields are reserved for legal routes. */
|
|
26
|
+
legalPurpose?: never;
|
|
27
|
+
legalReferences?: never;
|
|
28
|
+
});
|
|
22
29
|
export type BrandlabRouteCatalog = readonly BrandlabRoute[];
|
|
23
30
|
export declare function resolveBrandlabRouteIndexing(route: BrandlabRoute): BrandlabIndexingDirective;
|
|
24
31
|
export declare function normalizeBrandlabRoutePath(path: string): string;
|
|
@@ -31,4 +38,5 @@ export declare function getBrandlabRoutePathIdentity(path: string): string;
|
|
|
31
38
|
export declare function defineRouteCatalog<const T extends BrandlabRouteCatalog>(routes: T): T;
|
|
32
39
|
export declare function getRouteById(routes: BrandlabRouteCatalog, routeId: string): BrandlabRoute | undefined;
|
|
33
40
|
export declare function getRouteByPath(routes: BrandlabRouteCatalog, path: string): BrandlabRoute | undefined;
|
|
41
|
+
export {};
|
|
34
42
|
//# sourceMappingURL=routes.d.ts.map
|
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,gBAAgB,CAAA;AAEvB,MAAM,MAAM,yBAAyB,GACjC,OAAO,GACP,gBAAgB,GAChB,kBAAkB,CAAA;AAEtB,MAAM,MAAM,yBAAyB,GAAG,OAAO,GAAG,SAAS,
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,6BAA6B,EACnC,MAAM,gBAAgB,CAAA;AAEvB,MAAM,MAAM,yBAAyB,GACjC,OAAO,GACP,gBAAgB,GAChB,kBAAkB,CAAA;AAEtB,MAAM,MAAM,yBAAyB,GAAG,OAAO,GAAG,SAAS,CAAA;AAC3D,MAAM,MAAM,sBAAsB,GAC9B,4BAA4B,GAC5B,uBAAuB,CAAA;AAE3B,eAAO,MAAM,6BAA6B,EAAE,QAAQ,CAAC,MAAM,CAAC,sBAAsB,EAAE,MAAM,CAAC,CAGzF,CAAA;AAEF,MAAM,MAAM,iBAAiB,GACzB,MAAM,GACN,eAAe,GACf,SAAS,GACT,gBAAgB,GAChB,UAAU,GACV,kBAAkB,GAClB,YAAY,GACZ,WAAW,GACX,eAAe,GACf,SAAS,GACT,SAAS,GACT,SAAS,GACT,OAAO,GACP,SAAS,GACT,WAAW,CAAA;AA6Bf,UAAU,iBAAiB;IACzB,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,eAAe,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,yBAAyB,CAAA;IACnC,qBAAqB,CAAC,EAAE,MAAM,CAAA;IAC9B,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,6BAA6B,CAAA;CACjD;AAED,MAAM,MAAM,aAAa,GACrB,CAAC,iBAAiB,GAAG;IACnB,IAAI,EAAE,OAAO,CAAA;IACb,0EAA0E;IAC1E,YAAY,EAAE,yBAAyB,CAAA;IACvC,eAAe,CAAC,EAAE,SAAS,sBAAsB,EAAE,CAAA;CACpD,CAAC,GACF,CAAC,iBAAiB,GAAG;IACnB,IAAI,EAAE,OAAO,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAA;IACzC,yDAAyD;IACzD,YAAY,CAAC,EAAE,KAAK,CAAA;IACpB,eAAe,CAAC,EAAE,KAAK,CAAA;CACxB,CAAC,CAAA;AAEN,MAAM,MAAM,oBAAoB,GAAG,SAAS,aAAa,EAAE,CAAA;AAE3D,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,aAAa,GAAG,yBAAyB,CAK5F;AAUD,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,UAyBtD;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAAC,IAAI,EAAE,MAAM,UAOxD;AA0CD,wBAAgB,kBAAkB,CAAC,KAAK,CAAC,CAAC,SAAS,oBAAoB,EAAE,MAAM,EAAE,CAAC,GAAG,CAAC,CA+HrF;AAED,wBAAgB,YAAY,CAAC,MAAM,EAAE,oBAAoB,EAAE,OAAO,EAAE,MAAM,6BAEzE;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,oBAAoB,EAAE,IAAI,EAAE,MAAM,6BAKxE"}
|
package/dist/routes.js
CHANGED
|
@@ -14,8 +14,10 @@ const BRANDLAB_ROUTE_KINDS = new Set([
|
|
|
14
14
|
'blog-post',
|
|
15
15
|
'project-index',
|
|
16
16
|
'project',
|
|
17
|
+
'content',
|
|
17
18
|
'contact',
|
|
18
19
|
'legal',
|
|
20
|
+
'utility',
|
|
19
21
|
'thank-you',
|
|
20
22
|
]);
|
|
21
23
|
const BRANDLAB_INDEXING_DIRECTIVES = new Set([
|
|
@@ -26,7 +28,6 @@ const BRANDLAB_INDEXING_DIRECTIVES = new Set([
|
|
|
26
28
|
const BRANDLAB_LEGAL_PURPOSES = new Set([
|
|
27
29
|
'terms',
|
|
28
30
|
'privacy',
|
|
29
|
-
'other',
|
|
30
31
|
]);
|
|
31
32
|
export function resolveBrandlabRouteIndexing(route) {
|
|
32
33
|
assertRouteRuntimeEnums(route);
|
|
@@ -176,22 +177,24 @@ export function defineRouteCatalog(routes) {
|
|
|
176
177
|
else if (route.locationDecision !== undefined) {
|
|
177
178
|
throw new Error(`Route "${route.id}" cannot declare location publication data for kind "${route.kind}".`);
|
|
178
179
|
}
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
180
|
+
const runtimeLegalRoute = route;
|
|
181
|
+
if (runtimeLegalRoute.kind === 'legal') {
|
|
182
|
+
if (!runtimeLegalRoute.legalPurpose) {
|
|
183
|
+
throw new Error(`Legal route "${runtimeLegalRoute.id}" requires legalPurpose.`);
|
|
182
184
|
}
|
|
183
|
-
const legalReferences =
|
|
185
|
+
const legalReferences = runtimeLegalRoute.legalReferences ?? [];
|
|
184
186
|
const unsupportedReference = legalReferences.find((reference) => !Object.hasOwn(BRANDLAB_LEGAL_REFERENCE_URLS, reference));
|
|
185
187
|
if (unsupportedReference) {
|
|
186
|
-
throw new Error(`Legal route "${
|
|
188
|
+
throw new Error(`Legal route "${runtimeLegalRoute.id}" has unsupported reference "${unsupportedReference}".`);
|
|
187
189
|
}
|
|
188
190
|
const duplicateReference = findDuplicate(legalReferences);
|
|
189
191
|
if (duplicateReference) {
|
|
190
|
-
throw new Error(`Legal route "${
|
|
192
|
+
throw new Error(`Legal route "${runtimeLegalRoute.id}" repeats legal reference "${duplicateReference}".`);
|
|
191
193
|
}
|
|
192
194
|
}
|
|
193
|
-
else if (
|
|
194
|
-
|
|
195
|
+
else if (runtimeLegalRoute.legalPurpose !== undefined ||
|
|
196
|
+
runtimeLegalRoute.legalReferences !== undefined) {
|
|
197
|
+
throw new Error(`Route "${runtimeLegalRoute.id}" cannot declare legal policy fields for kind "${runtimeLegalRoute.kind}".`);
|
|
195
198
|
}
|
|
196
199
|
if (route.parentId && !routeById.has(route.parentId)) {
|
|
197
200
|
throw new Error(`Route "${route.id}" references unknown parent "${route.parentId}".`);
|
package/dist/routes.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveLocationRouteIndexing,\n type BrandlabLocationRouteDecision,\n} from './locations.js'\n\nexport type BrandlabIndexingDirective =\n | 'index'\n | 'noindex-follow'\n | 'noindex-nofollow'\n\nexport type BrandlabLegalRoutePurpose = 'terms' | 'privacy' | 'other'\nexport type BrandlabLegalReference =\n | 'google-maps-platform-terms'\n | 'google-privacy-policy'\n\nexport const BRANDLAB_LEGAL_REFERENCE_URLS: Readonly<Record<BrandlabLegalReference, string>> = Object.freeze({\n 'google-maps-platform-terms': 'https://cloud.google.com/maps-platform/terms',\n 'google-privacy-policy': 'https://policies.google.com/privacy',\n})\n\nexport type BrandlabRouteKind =\n | 'home'\n | 'service-index'\n | 'service'\n | 'location-index'\n | 'location'\n | 'service-location'\n | 'blog-index'\n | 'blog-post'\n | 'project-index'\n | 'project'\n | 'contact'\n | 'legal'\n | 'thank-you'\n\nconst BRANDLAB_ROUTE_KINDS = new Set<BrandlabRouteKind>([\n 'home',\n 'service-index',\n 'service',\n 'location-index',\n 'location',\n 'service-location',\n 'blog-index',\n 'blog-post',\n 'project-index',\n 'project',\n 'contact',\n 'legal',\n 'thank-you',\n])\nconst BRANDLAB_INDEXING_DIRECTIVES = new Set<BrandlabIndexingDirective>([\n 'index',\n 'noindex-follow',\n 'noindex-nofollow',\n])\nconst BRANDLAB_LEGAL_PURPOSES = new Set<BrandlabLegalRoutePurpose>([\n 'terms',\n 'privacy',\n 'other',\n])\n\nexport interface BrandlabRoute {\n id: string\n kind: BrandlabRouteKind\n path: string\n breadcrumbLabel: string\n parentId?: string\n canonicalPath?: string\n indexing: BrandlabIndexingDirective\n lastSignificantChange?: string\n /** Preferred explicit publication decision for location routes. */\n locationDecision?: BrandlabLocationRouteDecision\n /** Machine-identifiable legal surface used by provider policy binding. */\n legalPurpose?: BrandlabLegalRoutePurpose\n legalReferences?: readonly BrandlabLegalReference[]\n}\n\nexport type BrandlabRouteCatalog = readonly BrandlabRoute[]\n\nexport function resolveBrandlabRouteIndexing(route: BrandlabRoute): BrandlabIndexingDirective {\n assertRouteRuntimeEnums(route)\n return route.kind === 'location' || route.kind === 'service-location'\n ? resolveLocationRouteIndexing(route.locationDecision)\n : route.indexing\n}\n\nfunction isUnsafeRouteSegment(segment: string) {\n if (segment === '.' || segment === '..') return true\n if (/%(?:25)*(?:2f|5c)/i.test(segment)) return true\n if (/%(?:25)*(?:0[0-9a-f]|1[0-9a-f]|23|3f|7f)/i.test(segment)) return true\n const decodedDots = segment.replace(/%(?:25)*2e/gi, '.')\n return decodedDots === '.' || decodedDots === '..'\n}\n\nexport function normalizeBrandlabRoutePath(path: string) {\n if (typeof path !== 'string') {\n throw new Error('Route path must be a string.')\n }\n const trimmed = path.trim()\n if (\n trimmed !== path ||\n !trimmed.startsWith('/') ||\n trimmed.startsWith('//') ||\n trimmed.includes('\\\\') ||\n /%(?![0-9a-f]{2})/i.test(trimmed) ||\n /[\\u0000-\\u001f\\u007f]/.test(trimmed) ||\n trimmed.includes('?') ||\n trimmed.includes('#') ||\n trimmed.split('/').some(isUnsafeRouteSegment)\n ) {\n throw new Error(`Route path must be root-relative without a query string or hash: \"${path}\".`)\n }\n\n const normalized = trimmed.replace(/\\/{2,}/g, '/')\n const canonical = normalized === '/' ? '/' : normalized.replace(/\\/+$/, '')\n if (new URL(canonical, 'https://brandlab.invalid').pathname !== canonical) {\n throw new Error(`Route path must use its canonical URL encoding: \"${path}\".`)\n }\n return canonical\n}\n\n/**\n * Browser/server ownership key used only for collision and lookup checks. It\n * preserves the declared canonical spelling while preventing an encoded form\n * of the same path from becoming a second route owner.\n */\nexport function getBrandlabRoutePathIdentity(path: string) {\n const normalized = normalizeBrandlabRoutePath(path)\n try {\n return decodeURIComponent(normalized).normalize('NFC')\n } catch {\n throw new Error(`Route path contains invalid URL encoding: \"${path}\".`)\n }\n}\n\nfunction assertNonEmpty(value: string, field: string) {\n if (!value.trim()) throw new Error(`${field} cannot be empty.`)\n}\n\nfunction findDuplicate(values: readonly string[]) {\n return values.find((value, index) => values.indexOf(value) !== index)\n}\n\nfunction assertRouteRuntimeEnums(route: BrandlabRoute, index?: number) {\n const label = index === undefined ? 'Route' : `routes[${index}]`\n if (!route || typeof route !== 'object' || Array.isArray(route)) {\n throw new Error(`${label} must be a route record.`)\n }\n if (!BRANDLAB_ROUTE_KINDS.has(route.kind)) {\n throw new Error(`${label}.kind must use a supported Brandlab route kind.`)\n }\n if (!BRANDLAB_INDEXING_DIRECTIVES.has(route.indexing)) {\n throw new Error(`${label}.indexing must use an explicit Brandlab indexing directive.`)\n }\n if (\n route.legalPurpose !== undefined &&\n !BRANDLAB_LEGAL_PURPOSES.has(route.legalPurpose)\n ) {\n throw new Error(`${label}.legalPurpose is unsupported.`)\n }\n if (route.legalReferences !== undefined) {\n if (!Array.isArray(route.legalReferences)) {\n throw new Error(`${label}.legalReferences must be an array.`)\n }\n const hasUnsupported = (route.legalReferences as readonly unknown[]).some(\n (reference) =>\n typeof reference !== 'string' ||\n !Object.hasOwn(BRANDLAB_LEGAL_REFERENCE_URLS, reference),\n )\n if (hasUnsupported) {\n throw new Error(`${label}.legalReferences contains an unsupported value.`)\n }\n }\n}\n\nexport function defineRouteCatalog<const T extends BrandlabRouteCatalog>(routes: T): T {\n if (!Array.isArray(routes)) throw new Error('Route catalog must be an array.')\n if (routes.length === 0) throw new Error('Route catalog cannot be empty.')\n routes.forEach((route, index) => assertRouteRuntimeEnums(route, index))\n\n const ids = routes.map((route) => route.id)\n const paths = routes.map((route) => getBrandlabRoutePathIdentity(route.path))\n const effectiveCanonicalPaths = routes.map((route) =>\n getBrandlabRoutePathIdentity(route.canonicalPath ?? route.path),\n )\n const duplicateId = findDuplicate(ids)\n const duplicatePath = findDuplicate(paths)\n const duplicateCanonicalPath = findDuplicate(effectiveCanonicalPaths)\n\n if (duplicateId) throw new Error(`Route catalog contains duplicate ID \"${duplicateId}\".`)\n if (duplicatePath) throw new Error(`Route catalog contains duplicate path \"${duplicatePath}\".`)\n if (duplicateCanonicalPath) {\n throw new Error(`Route catalog contains duplicate canonical path \"${duplicateCanonicalPath}\".`)\n }\n\n const homeRoutes = routes.filter((route) => route.kind === 'home')\n if (homeRoutes.length !== 1) {\n throw new Error('Route catalog must contain exactly one home route.')\n }\n if (normalizeBrandlabRoutePath(homeRoutes[0]!.path) !== '/' || homeRoutes[0]!.parentId) {\n throw new Error('The home route must use path \"/\" and cannot have a parent.')\n }\n\n const routeById = new Map(routes.map((route) => [route.id, route]))\n const protectedLegalPurposes = routes\n .filter((route): route is typeof route & { legalPurpose: 'terms' | 'privacy' } =>\n route.kind === 'legal' &&\n (route.legalPurpose === 'terms' || route.legalPurpose === 'privacy'),\n )\n .map((route) => route.legalPurpose)\n const duplicateLegalPurpose = findDuplicate(protectedLegalPurposes)\n if (duplicateLegalPurpose) {\n throw new Error(`Route catalog contains duplicate legal purpose \"${duplicateLegalPurpose}\".`)\n }\n\n routes.forEach((route, index) => {\n assertNonEmpty(route.id, `routes[${index}].id`)\n assertNonEmpty(route.breadcrumbLabel, `routes[${index}].breadcrumbLabel`)\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(route.id)) {\n throw new Error(`routes[${index}].id must be lowercase kebab-case.`)\n }\n\n if (route.kind !== 'home' && normalizeBrandlabRoutePath(route.path) === '/') {\n throw new Error('Only the home route may use path \"/\".')\n }\n if (route.kind !== 'home' && !route.parentId) {\n throw new Error(\n `Route \"${route.id}\" requires a parentId so its semantic hierarchy reaches Home.`,\n )\n }\n if (route.kind === 'thank-you' && route.indexing === 'index') {\n throw new Error('Thank-you routes must not be indexable.')\n }\n if (route.kind === 'location' || route.kind === 'service-location') {\n if (route.locationDecision === undefined) {\n throw new Error(`Route \"${route.id}\" requires an explicit locationDecision.`)\n }\n const expectedIndexing = resolveBrandlabRouteIndexing(route)\n if (route.indexing !== expectedIndexing) {\n throw new Error(\n `Route \"${route.id}\" indexing must match its location publication decision.`,\n )\n }\n } else if (route.locationDecision !== undefined) {\n throw new Error(\n `Route \"${route.id}\" cannot declare location publication data for kind \"${route.kind}\".`,\n )\n }\n if (route.kind === 'legal') {\n if (!route.legalPurpose) {\n throw new Error(`Legal route \"${route.id}\" requires legalPurpose.`)\n }\n const legalReferences = route.legalReferences ?? []\n const unsupportedReference = legalReferences.find(\n (reference) => !Object.hasOwn(BRANDLAB_LEGAL_REFERENCE_URLS, reference),\n )\n if (unsupportedReference) {\n throw new Error(`Legal route \"${route.id}\" has unsupported reference \"${unsupportedReference}\".`)\n }\n const duplicateReference = findDuplicate(legalReferences)\n if (duplicateReference) {\n throw new Error(`Legal route \"${route.id}\" repeats legal reference \"${duplicateReference}\".`)\n }\n } else if (route.legalPurpose !== undefined || route.legalReferences !== undefined) {\n throw new Error(`Route \"${route.id}\" cannot declare legal policy fields for kind \"${route.kind}\".`)\n }\n\n if (route.parentId && !routeById.has(route.parentId)) {\n throw new Error(`Route \"${route.id}\" references unknown parent \"${route.parentId}\".`)\n }\n if (route.parentId === route.id) {\n throw new Error(`Route \"${route.id}\" cannot be its own parent.`)\n }\n if (\n route.lastSignificantChange &&\n Number.isNaN(Date.parse(route.lastSignificantChange))\n ) {\n throw new Error(`Route \"${route.id}\" has an invalid lastSignificantChange date.`)\n }\n\n const visited = new Set([route.id])\n let parentId = route.parentId\n while (parentId) {\n if (visited.has(parentId)) {\n throw new Error(`Route catalog contains a parent cycle involving \"${route.id}\".`)\n }\n visited.add(parentId)\n parentId = routeById.get(parentId)?.parentId\n }\n })\n\n return routes\n}\n\nexport function getRouteById(routes: BrandlabRouteCatalog, routeId: string) {\n return routes.find((route) => route.id === routeId)\n}\n\nexport function getRouteByPath(routes: BrandlabRouteCatalog, path: string) {\n const identity = getBrandlabRoutePathIdentity(path)\n return routes.find(\n (route) => getBrandlabRoutePathIdentity(route.path) === identity,\n )\n}\n"]}
|
|
1
|
+
{"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n resolveLocationRouteIndexing,\n type BrandlabLocationRouteDecision,\n} from './locations.js'\n\nexport type BrandlabIndexingDirective =\n | 'index'\n | 'noindex-follow'\n | 'noindex-nofollow'\n\nexport type BrandlabLegalRoutePurpose = 'terms' | 'privacy'\nexport type BrandlabLegalReference =\n | 'google-maps-platform-terms'\n | 'google-privacy-policy'\n\nexport const BRANDLAB_LEGAL_REFERENCE_URLS: Readonly<Record<BrandlabLegalReference, string>> = Object.freeze({\n 'google-maps-platform-terms': 'https://cloud.google.com/maps-platform/terms',\n 'google-privacy-policy': 'https://policies.google.com/privacy',\n})\n\nexport type BrandlabRouteKind =\n | 'home'\n | 'service-index'\n | 'service'\n | 'location-index'\n | 'location'\n | 'service-location'\n | 'blog-index'\n | 'blog-post'\n | 'project-index'\n | 'project'\n | 'content'\n | 'contact'\n | 'legal'\n | 'utility'\n | 'thank-you'\n\nconst BRANDLAB_ROUTE_KINDS = new Set<BrandlabRouteKind>([\n 'home',\n 'service-index',\n 'service',\n 'location-index',\n 'location',\n 'service-location',\n 'blog-index',\n 'blog-post',\n 'project-index',\n 'project',\n 'content',\n 'contact',\n 'legal',\n 'utility',\n 'thank-you',\n])\nconst BRANDLAB_INDEXING_DIRECTIVES = new Set<BrandlabIndexingDirective>([\n 'index',\n 'noindex-follow',\n 'noindex-nofollow',\n])\nconst BRANDLAB_LEGAL_PURPOSES = new Set<BrandlabLegalRoutePurpose>([\n 'terms',\n 'privacy',\n])\n\ninterface BrandlabRouteBase {\n id: string\n path: string\n breadcrumbLabel: string\n parentId?: string\n canonicalPath?: string\n indexing: BrandlabIndexingDirective\n lastSignificantChange?: string\n /** Preferred explicit publication decision for location routes. */\n locationDecision?: BrandlabLocationRouteDecision\n}\n\nexport type BrandlabRoute =\n | (BrandlabRouteBase & {\n kind: 'legal'\n /** Machine-identifiable legal surface used by provider policy binding. */\n legalPurpose: BrandlabLegalRoutePurpose\n legalReferences?: readonly BrandlabLegalReference[]\n })\n | (BrandlabRouteBase & {\n kind: Exclude<BrandlabRouteKind, 'legal'>\n /** Legal policy fields are reserved for legal routes. */\n legalPurpose?: never\n legalReferences?: never\n })\n\nexport type BrandlabRouteCatalog = readonly BrandlabRoute[]\n\nexport function resolveBrandlabRouteIndexing(route: BrandlabRoute): BrandlabIndexingDirective {\n assertRouteRuntimeEnums(route)\n return route.kind === 'location' || route.kind === 'service-location'\n ? resolveLocationRouteIndexing(route.locationDecision)\n : route.indexing\n}\n\nfunction isUnsafeRouteSegment(segment: string) {\n if (segment === '.' || segment === '..') return true\n if (/%(?:25)*(?:2f|5c)/i.test(segment)) return true\n if (/%(?:25)*(?:0[0-9a-f]|1[0-9a-f]|23|3f|7f)/i.test(segment)) return true\n const decodedDots = segment.replace(/%(?:25)*2e/gi, '.')\n return decodedDots === '.' || decodedDots === '..'\n}\n\nexport function normalizeBrandlabRoutePath(path: string) {\n if (typeof path !== 'string') {\n throw new Error('Route path must be a string.')\n }\n const trimmed = path.trim()\n if (\n trimmed !== path ||\n !trimmed.startsWith('/') ||\n trimmed.startsWith('//') ||\n trimmed.includes('\\\\') ||\n /%(?![0-9a-f]{2})/i.test(trimmed) ||\n /[\\u0000-\\u001f\\u007f]/.test(trimmed) ||\n trimmed.includes('?') ||\n trimmed.includes('#') ||\n trimmed.split('/').some(isUnsafeRouteSegment)\n ) {\n throw new Error(`Route path must be root-relative without a query string or hash: \"${path}\".`)\n }\n\n const normalized = trimmed.replace(/\\/{2,}/g, '/')\n const canonical = normalized === '/' ? '/' : normalized.replace(/\\/+$/, '')\n if (new URL(canonical, 'https://brandlab.invalid').pathname !== canonical) {\n throw new Error(`Route path must use its canonical URL encoding: \"${path}\".`)\n }\n return canonical\n}\n\n/**\n * Browser/server ownership key used only for collision and lookup checks. It\n * preserves the declared canonical spelling while preventing an encoded form\n * of the same path from becoming a second route owner.\n */\nexport function getBrandlabRoutePathIdentity(path: string) {\n const normalized = normalizeBrandlabRoutePath(path)\n try {\n return decodeURIComponent(normalized).normalize('NFC')\n } catch {\n throw new Error(`Route path contains invalid URL encoding: \"${path}\".`)\n }\n}\n\nfunction assertNonEmpty(value: string, field: string) {\n if (!value.trim()) throw new Error(`${field} cannot be empty.`)\n}\n\nfunction findDuplicate(values: readonly string[]) {\n return values.find((value, index) => values.indexOf(value) !== index)\n}\n\nfunction assertRouteRuntimeEnums(route: BrandlabRoute, index?: number) {\n const label = index === undefined ? 'Route' : `routes[${index}]`\n if (!route || typeof route !== 'object' || Array.isArray(route)) {\n throw new Error(`${label} must be a route record.`)\n }\n if (!BRANDLAB_ROUTE_KINDS.has(route.kind)) {\n throw new Error(`${label}.kind must use a supported Brandlab route kind.`)\n }\n if (!BRANDLAB_INDEXING_DIRECTIVES.has(route.indexing)) {\n throw new Error(`${label}.indexing must use an explicit Brandlab indexing directive.`)\n }\n if (\n route.legalPurpose !== undefined &&\n !BRANDLAB_LEGAL_PURPOSES.has(route.legalPurpose)\n ) {\n throw new Error(`${label}.legalPurpose is unsupported.`)\n }\n if (route.legalReferences !== undefined) {\n if (!Array.isArray(route.legalReferences)) {\n throw new Error(`${label}.legalReferences must be an array.`)\n }\n const hasUnsupported = (route.legalReferences as readonly unknown[]).some(\n (reference) =>\n typeof reference !== 'string' ||\n !Object.hasOwn(BRANDLAB_LEGAL_REFERENCE_URLS, reference),\n )\n if (hasUnsupported) {\n throw new Error(`${label}.legalReferences contains an unsupported value.`)\n }\n }\n}\n\nexport function defineRouteCatalog<const T extends BrandlabRouteCatalog>(routes: T): T {\n if (!Array.isArray(routes)) throw new Error('Route catalog must be an array.')\n if (routes.length === 0) throw new Error('Route catalog cannot be empty.')\n routes.forEach((route, index) => assertRouteRuntimeEnums(route, index))\n\n const ids = routes.map((route) => route.id)\n const paths = routes.map((route) => getBrandlabRoutePathIdentity(route.path))\n const effectiveCanonicalPaths = routes.map((route) =>\n getBrandlabRoutePathIdentity(route.canonicalPath ?? route.path),\n )\n const duplicateId = findDuplicate(ids)\n const duplicatePath = findDuplicate(paths)\n const duplicateCanonicalPath = findDuplicate(effectiveCanonicalPaths)\n\n if (duplicateId) throw new Error(`Route catalog contains duplicate ID \"${duplicateId}\".`)\n if (duplicatePath) throw new Error(`Route catalog contains duplicate path \"${duplicatePath}\".`)\n if (duplicateCanonicalPath) {\n throw new Error(`Route catalog contains duplicate canonical path \"${duplicateCanonicalPath}\".`)\n }\n\n const homeRoutes = routes.filter((route) => route.kind === 'home')\n if (homeRoutes.length !== 1) {\n throw new Error('Route catalog must contain exactly one home route.')\n }\n if (normalizeBrandlabRoutePath(homeRoutes[0]!.path) !== '/' || homeRoutes[0]!.parentId) {\n throw new Error('The home route must use path \"/\" and cannot have a parent.')\n }\n\n const routeById = new Map(routes.map((route) => [route.id, route]))\n const protectedLegalPurposes = routes\n .filter((route): route is typeof route & { legalPurpose: 'terms' | 'privacy' } =>\n route.kind === 'legal' &&\n (route.legalPurpose === 'terms' || route.legalPurpose === 'privacy'),\n )\n .map((route) => route.legalPurpose)\n const duplicateLegalPurpose = findDuplicate(protectedLegalPurposes)\n if (duplicateLegalPurpose) {\n throw new Error(`Route catalog contains duplicate legal purpose \"${duplicateLegalPurpose}\".`)\n }\n\n routes.forEach((route, index) => {\n assertNonEmpty(route.id, `routes[${index}].id`)\n assertNonEmpty(route.breadcrumbLabel, `routes[${index}].breadcrumbLabel`)\n if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(route.id)) {\n throw new Error(`routes[${index}].id must be lowercase kebab-case.`)\n }\n\n if (route.kind !== 'home' && normalizeBrandlabRoutePath(route.path) === '/') {\n throw new Error('Only the home route may use path \"/\".')\n }\n if (route.kind !== 'home' && !route.parentId) {\n throw new Error(\n `Route \"${route.id}\" requires a parentId so its semantic hierarchy reaches Home.`,\n )\n }\n if (route.kind === 'thank-you' && route.indexing === 'index') {\n throw new Error('Thank-you routes must not be indexable.')\n }\n if (route.kind === 'location' || route.kind === 'service-location') {\n if (route.locationDecision === undefined) {\n throw new Error(`Route \"${route.id}\" requires an explicit locationDecision.`)\n }\n const expectedIndexing = resolveBrandlabRouteIndexing(route)\n if (route.indexing !== expectedIndexing) {\n throw new Error(\n `Route \"${route.id}\" indexing must match its location publication decision.`,\n )\n }\n } else if (route.locationDecision !== undefined) {\n throw new Error(\n `Route \"${route.id}\" cannot declare location publication data for kind \"${route.kind}\".`,\n )\n }\n const runtimeLegalRoute = route as BrandlabRouteBase & {\n kind: BrandlabRouteKind\n legalPurpose?: BrandlabLegalRoutePurpose\n legalReferences?: readonly BrandlabLegalReference[]\n }\n if (runtimeLegalRoute.kind === 'legal') {\n if (!runtimeLegalRoute.legalPurpose) {\n throw new Error(`Legal route \"${runtimeLegalRoute.id}\" requires legalPurpose.`)\n }\n const legalReferences = runtimeLegalRoute.legalReferences ?? []\n const unsupportedReference = legalReferences.find(\n (reference) => !Object.hasOwn(BRANDLAB_LEGAL_REFERENCE_URLS, reference),\n )\n if (unsupportedReference) {\n throw new Error(`Legal route \"${runtimeLegalRoute.id}\" has unsupported reference \"${unsupportedReference}\".`)\n }\n const duplicateReference = findDuplicate(legalReferences)\n if (duplicateReference) {\n throw new Error(`Legal route \"${runtimeLegalRoute.id}\" repeats legal reference \"${duplicateReference}\".`)\n }\n } else if (\n runtimeLegalRoute.legalPurpose !== undefined ||\n runtimeLegalRoute.legalReferences !== undefined\n ) {\n throw new Error(\n `Route \"${runtimeLegalRoute.id}\" cannot declare legal policy fields for kind \"${runtimeLegalRoute.kind}\".`,\n )\n }\n\n if (route.parentId && !routeById.has(route.parentId)) {\n throw new Error(`Route \"${route.id}\" references unknown parent \"${route.parentId}\".`)\n }\n if (route.parentId === route.id) {\n throw new Error(`Route \"${route.id}\" cannot be its own parent.`)\n }\n if (\n route.lastSignificantChange &&\n Number.isNaN(Date.parse(route.lastSignificantChange))\n ) {\n throw new Error(`Route \"${route.id}\" has an invalid lastSignificantChange date.`)\n }\n\n const visited = new Set([route.id])\n let parentId = route.parentId\n while (parentId) {\n if (visited.has(parentId)) {\n throw new Error(`Route catalog contains a parent cycle involving \"${route.id}\".`)\n }\n visited.add(parentId)\n parentId = routeById.get(parentId)?.parentId\n }\n })\n\n return routes\n}\n\nexport function getRouteById(routes: BrandlabRouteCatalog, routeId: string) {\n return routes.find((route) => route.id === routeId)\n}\n\nexport function getRouteByPath(routes: BrandlabRouteCatalog, path: string) {\n const identity = getBrandlabRoutePathIdentity(path)\n return routes.find(\n (route) => getBrandlabRoutePathIdentity(route.path) === identity,\n )\n}\n"]}
|