@brandlab-digital/site-core 1.0.9 → 1.0.10
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/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/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"]}
|