pagegraph 0.14.0 → 0.15.0

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.
Files changed (55) hide show
  1. package/README.md +169 -10
  2. package/dist/audit.d.ts +1 -1
  3. package/dist/audit.js +1 -1
  4. package/dist/{checks-BS-aDXB_.js → checks-BEhJPNzN.js} +12 -1
  5. package/dist/checks-BEhJPNzN.js.map +1 -0
  6. package/dist/{claims-Bch7Lxlo.d.ts → claims-CCf2_Jox.d.ts} +2 -2
  7. package/dist/{claims-Bv7voeEV.js → claims-CQlzze1K.js} +2 -2
  8. package/dist/{claims-Bv7voeEV.js.map → claims-CQlzze1K.js.map} +1 -1
  9. package/dist/claims.d.ts +2 -2
  10. package/dist/claims.js +1 -1
  11. package/dist/cli.js +1355 -616
  12. package/dist/cli.js.map +1 -1
  13. package/dist/config.d.ts +7 -3
  14. package/dist/config.js +1 -1
  15. package/dist/config.js.map +1 -1
  16. package/dist/{content-CHd0X4s6.js → content-D5jZprMq.js} +2 -2
  17. package/dist/{content-CHd0X4s6.js.map → content-D5jZprMq.js.map} +1 -1
  18. package/dist/{content-DgNr7apS.d.ts → content-DerWgQy9.d.ts} +11 -3
  19. package/dist/{dist-B6zg_Z3Z.js → dist-Br2oOB2Q.js} +2 -2
  20. package/dist/{dist-B6zg_Z3Z.js.map → dist-Br2oOB2Q.js.map} +1 -1
  21. package/dist/{document-CztEu3Vd.d.ts → document-D9P8yY5g.d.ts} +4 -4
  22. package/dist/{graph-D1PIndsC.d.ts → graph-ChrvU-6X.d.ts} +27 -4
  23. package/dist/{graph-DMNOLXLu.js → graph-UONEaqIX.js} +43 -5
  24. package/dist/graph-UONEaqIX.js.map +1 -0
  25. package/dist/{index-C5w9ripz.d.ts → index-DzjUmbHY.d.ts} +5 -5
  26. package/dist/index.d.ts +6 -6
  27. package/dist/index.js +5 -6
  28. package/dist/index.js.map +1 -1
  29. package/dist/{page-heads-BfjWQvau.d.ts → page-heads-DDz_2_hq.d.ts} +3 -2
  30. package/dist/{page-heads-BgbaIjj9.js → page-heads-YL8nyYjF.js} +4 -3
  31. package/dist/page-heads-YL8nyYjF.js.map +1 -0
  32. package/dist/{pi-Bvw6J0gh.js → pi-Bz4BgZdC.js} +207 -167
  33. package/dist/pi-Bz4BgZdC.js.map +1 -0
  34. package/dist/{projections-Bk-qOOsR.d.ts → projections-Dqo_ih-J.d.ts} +2 -2
  35. package/dist/react.d.ts +5 -3
  36. package/dist/react.js +43 -19
  37. package/dist/react.js.map +1 -1
  38. package/dist/{state-BuJP_3Lr.js → state-2ikjo7uI.js} +638 -25
  39. package/dist/state-2ikjo7uI.js.map +1 -0
  40. package/dist/tanstack-start/index.d.ts +1 -1
  41. package/dist/tanstack-start/index.js +6 -4
  42. package/dist/tanstack-start/index.js.map +1 -1
  43. package/dist/tanstack-start/react.d.ts +1 -1
  44. package/dist/tanstack-start/server.d.ts +2 -2
  45. package/dist/tanstack-start/server.js +1 -1
  46. package/dist/tanstack-start/server.js.map +1 -1
  47. package/dist/{vite-graph-loader-DZCwejuJ.d.ts → vite-graph-loader-BtLgkN0V.d.ts} +7 -5
  48. package/package.json +2 -2
  49. package/dist/checks-BS-aDXB_.js.map +0 -1
  50. package/dist/declare-D5o7hwmv.js +0 -9
  51. package/dist/declare-D5o7hwmv.js.map +0 -1
  52. package/dist/graph-DMNOLXLu.js.map +0 -1
  53. package/dist/page-heads-BgbaIjj9.js.map +0 -1
  54. package/dist/pi-Bvw6J0gh.js.map +0 -1
  55. package/dist/state-BuJP_3Lr.js.map +0 -1
@@ -1,4 +1,4 @@
1
- import { a as SeoGraph, r as SeoEdge, s as SeoNode } from "./graph-D1PIndsC.js";
1
+ import { a as SeoEdge, l as SeoNode, s as SeoGraph } from "./graph-ChrvU-6X.js";
2
2
  //#region src/core/projections.d.ts
3
3
  interface ProjectionConfig {
4
4
  origin: string;
@@ -75,4 +75,4 @@ declare function renderRobots(_graph: SeoGraph, cfg: RobotsConfig): string;
75
75
  declare function inspectNode(graph: SeoGraph, path: string): NodeReport | undefined;
76
76
  //#endregion
77
77
  export { inspectNode as a, renderRobots as c, contentSignal as i, renderSitemap as l, ProjectionConfig as n, isSitemapEligible as o, RobotsConfig as r, lastModified as s, NodeReport as t };
78
- //# sourceMappingURL=projections-Bk-qOOsR.d.ts.map
78
+ //# sourceMappingURL=projections-Dqo_ih-J.d.ts.map
package/dist/react.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { A as WithContext, C as FAQPage, D as Service, E as Organization, O as Thing, S as BreadcrumbList, T as ItemList, f as JsonLdDocument, g as SeoFaq, j as IdReference, k as WebSite, v as SeoPageHead, w as Graph, x as Article } from "./graph-D1PIndsC.js";
2
- import { a as Fact, c as ForHumans, d as Title, f as Visual, g as useCapturePage, h as messageText, i as DocumentProvider, l as Section, m as finishDocument, n as CaptureRequest, o as FactId, p as createCollector, r as Collector, s as ForAgents, t as CaptureAnchor, u as T } from "./document-CztEu3Vd.js";
1
+ import { A as Organization, D as FAQPage, E as BreadcrumbList, F as IdReference, M as Thing, N as WebSite, O as Graph, P as WithContext, S as SeoPageHead, T as Article, b as SeoImage, h as JsonLdDocument, j as Service, k as ItemList, n as OgImageNode, r as OgImageResolver, y as SeoFaq } from "./graph-ChrvU-6X.js";
2
+ import { a as Fact, c as ForHumans, d as Title, f as Visual, g as useCapturePage, h as messageText, i as DocumentProvider, l as Section, m as finishDocument, n as CaptureRequest, o as FactId, p as createCollector, r as Collector, s as ForAgents, t as CaptureAnchor, u as T } from "./document-D9P8yY5g.js";
3
3
  import { AnyRouteMatch } from "@tanstack/react-router";
4
4
  //#region src/react/json-ld-composition.d.ts
5
5
  /** Any schema.org entity accepted by `schema-dts`. */
@@ -118,6 +118,8 @@ interface SeoConfig {
118
118
  organization: SeoOrganization;
119
119
  website: SeoWebsite;
120
120
  jsonLd?: SeoJsonLdConfig | undefined;
121
+ /** Shared with pagegraph({ ogImage }) so rendered tags and graph heads agree. */
122
+ ogImage?: OgImageResolver | undefined;
121
123
  /**
122
124
  * Last-mile override for every head `seoHead` renders, e.g. to append
123
125
  * site-specific meta. Runs after the canonical, title template, and JSON-LD.
@@ -224,5 +226,5 @@ declare function Breadcrumbs({ className }: {
224
226
  className?: string | undefined;
225
227
  }): import("react").JSX.Element | null;
226
228
  //#endregion
227
- export { type ArticleParams, type BreadcrumbItem, Breadcrumbs, CaptureAnchor, CaptureRequest, type Collector, type CrumbTrailItem, DocumentProvider, Fact, type FactId, ForAgents, ForHumans, type ItemListEntry, type JsonLd, type JsonLdDocument, type JsonLdEntityIds, type JsonLdEntry, type JsonLdNode, type JsonLdTransform, type JsonLdTransformContext, type JsonLdTransformResult, Section, type Seo, type SeoConfig, type SeoFaq, type SeoHead, type SeoHeadCtx, type SeoJsonLdConfig, type SeoOrganization, type SeoPageHead, type SeoPostalAddress, type SeoSite, type SeoWebsite, type ServiceParams, T, Title, Visual, composeJsonLd, createCollector, createSeo, defineJsonLd, extendJsonLd, finishDocument, jsonLdGraph, jsonLdRef, messageText, resolveCrumbTrail, useCapturePage };
229
+ export { type ArticleParams, type BreadcrumbItem, Breadcrumbs, CaptureAnchor, CaptureRequest, type Collector, type CrumbTrailItem, DocumentProvider, Fact, type FactId, ForAgents, ForHumans, type ItemListEntry, type JsonLd, type JsonLdDocument, type JsonLdEntityIds, type JsonLdEntry, type JsonLdNode, type JsonLdTransform, type JsonLdTransformContext, type JsonLdTransformResult, type OgImageNode, type OgImageResolver, Section, type Seo, type SeoConfig, type SeoFaq, type SeoHead, type SeoHeadCtx, type SeoImage, type SeoJsonLdConfig, type SeoOrganization, type SeoPageHead, type SeoPostalAddress, type SeoSite, type SeoWebsite, type ServiceParams, T, Title, Visual, composeJsonLd, createCollector, createSeo, defineJsonLd, extendJsonLd, finishDocument, jsonLdGraph, jsonLdRef, messageText, resolveCrumbTrail, useCapturePage };
228
230
  //# sourceMappingURL=react.d.ts.map
package/dist/react.js CHANGED
@@ -1,4 +1,4 @@
1
- import { t as applyTitleTemplate } from "./declare-D5o7hwmv.js";
1
+ import { n as resolveOgImage, r as applyTitleTemplate } from "./graph-UONEaqIX.js";
2
2
  import { a as ForAgents, c as T, d as createCollector, f as finishDocument, i as Fact, l as Title, m as useCapturePage, n as CaptureRequest, o as ForHumans, p as messageText, r as DocumentProvider, s as Section, t as CaptureAnchor, u as Visual } from "./document-Dvemcv7V.js";
3
3
  import { Link, useMatches } from "@tanstack/react-router";
4
4
  import { Fragment } from "react";
@@ -50,12 +50,6 @@ function Breadcrumbs({ className }) {
50
50
  });
51
51
  }
52
52
  //#endregion
53
- //#region src/react/site.ts
54
- /** Absolute URL for an origin-relative path; an already-absolute URL passes through. */
55
- function absoluteUrl(origin, pathOrUrl) {
56
- return pathOrUrl.startsWith("http") ? pathOrUrl : `${origin}${pathOrUrl}`;
57
- }
58
- //#endregion
59
53
  //#region src/react/json-ld-composition.ts
60
54
  /** Add the schema.org context to an entity while preserving its concrete type. */
61
55
  function defineJsonLd(node) {
@@ -97,6 +91,12 @@ function composeJsonLd(entries, context, transform) {
97
91
  });
98
92
  }
99
93
  //#endregion
94
+ //#region src/react/site.ts
95
+ /** Absolute URL for an origin-relative path; an already-absolute URL passes through. */
96
+ function absoluteUrl(origin, pathOrUrl) {
97
+ return pathOrUrl.startsWith("http") ? pathOrUrl : `${origin}${pathOrUrl}`;
98
+ }
99
+ //#endregion
100
100
  //#region src/react/json-ld.ts
101
101
  function createJsonLd(config) {
102
102
  const { origin, site, organization, website } = config;
@@ -280,6 +280,19 @@ function createSeo(config) {
280
280
  const resolvedOgDescription = instance.ogDescription ?? instance.description;
281
281
  const routeSeo = ctx.match.staticData.seo;
282
282
  const { article } = instance;
283
+ const image = resolveOgImage({
284
+ path: instance.canonicalPath ?? normalizePathname(ctx.match.pathname),
285
+ kind: routeSeo?.kind ?? "page",
286
+ head: {
287
+ title,
288
+ description: instance.description,
289
+ faqs: instance.faqs?.map(({ question, answer }) => ({
290
+ question,
291
+ answer
292
+ })),
293
+ image: instance.image ?? (article?.image === void 0 ? void 0 : { url: article.image })
294
+ }
295
+ }, origin, config.ogImage);
283
296
  const jsonLdEntries = [];
284
297
  const meta = [
285
298
  { title },
@@ -305,7 +318,7 @@ function createSeo(config) {
305
318
  },
306
319
  {
307
320
  name: "twitter:card",
308
- content: "summary_large_image"
321
+ content: image === void 0 ? "summary" : "summary_large_image"
309
322
  },
310
323
  {
311
324
  name: "twitter:title",
@@ -333,16 +346,27 @@ function createSeo(config) {
333
346
  property: "article:modified_time",
334
347
  content: article.modifiedAt ?? article.publishedAt
335
348
  });
336
- if (article.image !== void 0) {
337
- const imageUrl = absoluteUrl(origin, article.image);
338
- meta.push({
339
- property: "og:image",
340
- content: imageUrl
341
- }, {
342
- name: "twitter:image",
343
- content: imageUrl
344
- });
345
- }
349
+ }
350
+ if (image !== void 0) {
351
+ meta.push({
352
+ property: "og:image",
353
+ content: image.url
354
+ }, {
355
+ name: "twitter:image",
356
+ content: image.url
357
+ });
358
+ if (image.width !== void 0) meta.push({
359
+ property: "og:image:width",
360
+ content: String(image.width)
361
+ });
362
+ if (image.height !== void 0) meta.push({
363
+ property: "og:image:height",
364
+ content: String(image.height)
365
+ });
366
+ if (image.alt !== void 0) meta.push({
367
+ property: "og:image:alt",
368
+ content: image.alt
369
+ });
346
370
  }
347
371
  if (article) jsonLdEntries.push({
348
372
  kind: "article",
@@ -350,7 +374,7 @@ function createSeo(config) {
350
374
  document: jsonLd.generateArticleSchema({
351
375
  headline: resolvedOgTitle,
352
376
  description: instance.description,
353
- image: article.image,
377
+ image: image?.url,
354
378
  datePublished: article.publishedAt,
355
379
  dateModified: article.modifiedAt ?? article.publishedAt,
356
380
  author: article.author ?? site.defaultAuthor,
package/dist/react.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"react.js","names":["composeJsonLd","composeEntries"],"sources":["../src/react/breadcrumbs.tsx","../src/react/site.ts","../src/react/json-ld-composition.ts","../src/react/json-ld.ts","../src/react/create-seo.ts"],"sourcesContent":["import type { AnyRouteMatch } from \"@tanstack/react-router\";\nimport { Link, useMatches } from \"@tanstack/react-router\";\nimport { Fragment } from \"react\";\n\nexport interface CrumbTrailItem {\n name: string;\n path: string;\n}\n\n/**\n * Walk the match chain in order, keeping matches that declare `staticData.seo.crumb`,\n * resolving string | fn form. Shared by <Breadcrumbs/> and seoHead's BreadcrumbList.\n */\nexport function resolveCrumbTrail(matches: ReadonlyArray<AnyRouteMatch>): Array<CrumbTrailItem> {\n const trail: Array<CrumbTrailItem> = [];\n for (const match of matches) {\n const crumb = match.staticData.seo?.crumb;\n if (crumb === undefined) continue;\n const name = typeof crumb === \"function\" ? crumb(match) : crumb;\n trail.push({ name, path: match.pathname });\n }\n return trail;\n}\n\nexport function Breadcrumbs({ className }: { className?: string | undefined }) {\n const trail = resolveCrumbTrail(useMatches());\n if (trail.length < 2) return null;\n\n const lastIndex = trail.length - 1;\n return (\n <nav aria-label=\"Breadcrumb\" className={className}>\n <ol className=\"text-muted-foreground flex items-center gap-2 font-mono text-[11px] tracking-wide uppercase\">\n {trail.map((item, index) => {\n const isLast = index === lastIndex;\n return (\n <Fragment key={item.path}>\n {index > 0 ? (\n <li aria-hidden=\"true\" className=\"text-muted-foreground/50\">\n /\n </li>\n ) : null}\n <li\n aria-current={isLast ? \"page\" : undefined}\n className={isLast ? \"text-foreground truncate\" : undefined}\n >\n {isLast ? (\n item.name\n ) : (\n <Link to={item.path} className=\"hover:text-foreground transition-colors\">\n {item.name}\n </Link>\n )}\n </li>\n </Fragment>\n );\n })}\n </ol>\n </nav>\n );\n}\n","/**\n * Site identity — the values a package cannot know about its host.\n *\n * Bound once through {@link createSeo}, so the ~30 route call sites that render\n * a head never thread an origin or a brand name through their arguments.\n * Everything here is a *value*; the schema construction it feeds lives in\n * `json-ld.ts`.\n */\n\nimport type { SeoHead } from \"./create-seo\";\nimport type { SeoJsonLdConfig } from \"./json-ld-composition\";\n\n/** Absolute URL for an origin-relative path; an already-absolute URL passes through. */\nexport function absoluteUrl(origin: string, pathOrUrl: string): string {\n return pathOrUrl.startsWith(\"http\") ? pathOrUrl : `${origin}${pathOrUrl}`;\n}\n\nexport interface SeoSite {\n /** Brand name: Organization/WebSite `name`, Article publisher, Service provider default. */\n name: string;\n /** Organization `logo`. Origin-relative path or absolute URL. */\n logo: string;\n /** Article `publisher.logo` (Google wants an ImageObject distinct from the brand mark). */\n publisherLogo: string;\n /** Article `image` when the instance declares none. */\n defaultImage: string;\n /** Article `author` when the instance declares none. */\n defaultAuthor: { name: string; url?: string | undefined };\n}\n\n/** Public mailing address rendered as a schema.org PostalAddress. */\nexport interface SeoPostalAddress {\n streetAddress: string;\n addressLocality: string;\n addressRegion: string;\n postalCode: string;\n addressCountry: string;\n}\n\n/** The site-wide Organization entity (Knowledge Graph). */\nexport interface SeoOrganization {\n legalName?: string | undefined;\n description: string;\n sameAs: ReadonlyArray<string>;\n contactPoint: { contactType: string; email: string };\n address?: SeoPostalAddress | undefined;\n}\n\n/** The site-wide WebSite entity (sitelinks search box). */\nexport interface SeoWebsite {\n /** Origin-relative search target carrying the `{search_term_string}` placeholder. */\n searchPath: string;\n}\n\nexport interface SeoConfig {\n /** Canonical origin, no trailing slash — e.g. `https://example.com`. */\n origin: string;\n site: SeoSite;\n organization: SeoOrganization;\n website: SeoWebsite;\n jsonLd?: SeoJsonLdConfig | undefined;\n /**\n * Last-mile override for every head `seoHead` renders, e.g. to append\n * site-specific meta. Runs after the canonical, title template, and JSON-LD.\n */\n transformHead?: ((head: SeoHead) => SeoHead) | undefined;\n}\n","import type {\n Article,\n BreadcrumbList,\n FAQPage,\n Graph,\n IdReference,\n ItemList,\n Organization,\n Service,\n Thing,\n WebSite,\n WithContext,\n} from \"schema-dts\";\n\n/** Any schema.org entity accepted by `schema-dts`. */\nexport type JsonLdNode = Extract<Thing, object>;\n\nimport type { JsonLdDocument } from \"../core/declare\";\n\nexport type { JsonLdDocument };\n\nexport interface JsonLdEntityIds {\n organization: string;\n website: string;\n}\n\nexport type JsonLdEntry =\n | {\n kind: \"organization\";\n source: \"generated\";\n document: WithContext<Organization>;\n }\n | { kind: \"website\"; source: \"generated\"; document: WithContext<WebSite> }\n | { kind: \"article\"; source: \"generated\"; document: WithContext<Article> }\n | { kind: \"service\"; source: \"generated\"; document: WithContext<Service> }\n | { kind: \"faq\"; source: \"generated\"; document: WithContext<FAQPage> }\n | {\n kind: \"breadcrumb\";\n source: \"generated\";\n document: WithContext<BreadcrumbList>;\n }\n | { kind: \"item-list\"; source: \"generated\"; document: WithContext<ItemList> }\n | { kind: \"custom\"; source: \"site\" | \"page\"; document: JsonLdDocument };\n\nexport interface JsonLdTransformContext {\n /** Canonical site origin configured through `createSeo`. */\n origin: string;\n /** Stable identifiers for the plugin's generated site entities. */\n entityIds: JsonLdEntityIds;\n /** Canonical page URL when composition happens for a route. */\n canonical?: string | undefined;\n}\n\n/** Return no document to suppress an entry, one to replace it, or an array to expand it. */\nexport type JsonLdTransformResult =\n | JsonLdDocument\n | ReadonlyArray<JsonLdDocument>\n | false\n | null\n | undefined;\n\nexport type JsonLdTransform = (\n entry: JsonLdEntry,\n context: JsonLdTransformContext,\n) => JsonLdTransformResult;\n\nexport interface SeoJsonLdConfig {\n /** Custom entities included by `generateSiteGraphSchema`. */\n site?:\n | ReadonlyArray<JsonLdDocument>\n | ((entityIds: JsonLdEntityIds) => ReadonlyArray<JsonLdDocument>)\n | undefined;\n /** Open composition hook for generated and custom entries. */\n transform?: JsonLdTransform | undefined;\n}\n\n/** Add the schema.org context to an entity while preserving its concrete type. */\nexport function defineJsonLd<const Node extends JsonLdNode & object>(\n node: Node,\n): Node & { \"@context\": \"https://schema.org\" } {\n return { \"@context\": \"https://schema.org\", ...node };\n}\n\n/** Reference a canonical entity declared elsewhere in the same graph or page. */\nexport function jsonLdRef(id: string): IdReference {\n return { \"@id\": id };\n}\n\n/** Extend a generated or custom document without rebuilding its existing properties. */\nexport function extendJsonLd<\n const Document extends JsonLdDocument,\n const Extension extends object,\n>(document: Document, extension: Extension): Document & Extension {\n return { ...(document as unknown as object), ...extension } as Document &\n Extension;\n}\n\nconst documentNodes = (document: JsonLdDocument): ReadonlyArray<JsonLdNode> => {\n if (\"@graph\" in document) {\n return document[\"@graph\"] as ReadonlyArray<JsonLdNode>;\n }\n const { \"@context\": _context, ...node } = document as unknown as Record<\n string,\n unknown\n >;\n return [node as JsonLdNode];\n};\n\n/** Compose related schema.org documents into one graph without nested `@context` values. */\nexport function jsonLdGraph(documents: ReadonlyArray<JsonLdDocument>): Graph {\n return {\n \"@context\": \"https://schema.org\",\n \"@graph\": documents.flatMap(documentNodes),\n };\n}\n\n/** Apply an optional zero/one/many transform while preserving entry order. */\nexport function composeJsonLd(\n entries: ReadonlyArray<JsonLdEntry>,\n context: JsonLdTransformContext,\n transform?: JsonLdTransform,\n): Array<JsonLdDocument> {\n if (transform === undefined) {\n return entries.map((entry) => entry.document as JsonLdDocument);\n }\n\n return entries.flatMap((entry) => {\n const result = transform(entry, context);\n if (result === undefined || result === null || result === false) return [];\n return Array.isArray(result) ? [...result] : [result as JsonLdDocument];\n });\n}\n","/**\n * JSON-LD structured-data generators.\n *\n * schema.org payloads typed against `schema-dts`, which is a types-only devDep —\n * it erases at build, so this module keeps the entry's zero-runtime-dep rule.\n * The brand values (name, logo, sameAs, the Organization description) are the\n * host's, and arrive through {@link SeoConfig}; the schema construction is ours.\n *\n * @see https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data\n * @see https://schema.org/\n */\n\nimport type {\n Article,\n BreadcrumbList,\n FAQPage,\n Graph,\n ItemList,\n Organization,\n Person,\n Question,\n Service,\n WebSite,\n WithContext,\n} from \"schema-dts\";\n\nimport {\n composeJsonLd as composeEntries,\n jsonLdGraph,\n jsonLdRef,\n type JsonLdDocument,\n type JsonLdEntityIds,\n type JsonLdEntry,\n} from \"./json-ld-composition\";\nimport type { SeoFaq } from \"../core/declare\";\nimport { absoluteUrl, type SeoConfig } from \"./site\";\n\nexport interface ServiceParams {\n name: string;\n description: string;\n serviceType: string;\n provider?: string | undefined;\n areaServed?: string | undefined;\n}\n\n\nexport interface BreadcrumbItem {\n name: string;\n url: string;\n}\n\nexport interface ItemListEntry {\n name: string;\n url: string;\n}\n\nexport interface ArticleParams {\n headline: string;\n description: string;\n image?: string | undefined;\n datePublished: string;\n dateModified?: string | undefined;\n author: {\n name: string;\n url?: string | undefined;\n };\n url: string;\n}\n\n/** The JSON-LD generators, bound to one site identity. */\nexport interface JsonLd {\n readonly entityIds: JsonLdEntityIds;\n /** Brand entity for the Knowledge Graph. Render site-wide (root layout). */\n generateOrganizationSchema: () => WithContext<Organization>;\n /** WebSite entity with a sitelinks search box. Render on the homepage. */\n generateWebsiteSchema: () => WithContext<WebSite>;\n generateServiceSchema: (params: ServiceParams) => WithContext<Service>;\n generateFAQPageSchema: (faqs: SeoFaq[]) => WithContext<FAQPage>;\n generateBreadcrumbSchema: (\n items: BreadcrumbItem[],\n ) => WithContext<BreadcrumbList>;\n /** Describe a visibly rendered, ordered collection of canonical pages. */\n generateItemListSchema: (\n name: string,\n items: ReadonlyArray<ItemListEntry>,\n ) => WithContext<ItemList>;\n generateArticleSchema: (params: ArticleParams) => WithContext<Article>;\n /** Compose the Organization, WebSite, and configured site entities into one graph. */\n generateSiteGraphSchema: () => Graph | undefined;\n /** Apply the configured transform to generated or custom entries. */\n composeJsonLd: (\n entries: ReadonlyArray<JsonLdEntry>,\n context?: { canonical?: string | undefined },\n ) => Array<JsonLdDocument>;\n}\n\nexport function createJsonLd(config: SeoConfig): JsonLd {\n const { origin, site, organization, website } = config;\n const organizationId = `${origin}/#organization`;\n const websiteId = `${origin}/#website`;\n const entityIds = { organization: organizationId, website: websiteId };\n\n const generateOrganizationSchema = (): WithContext<Organization> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"Organization\",\n \"@id\": organizationId,\n name: site.name,\n ...(organization.legalName === undefined\n ? {}\n : { legalName: organization.legalName }),\n url: origin,\n logo: absoluteUrl(origin, site.logo),\n description: organization.description,\n sameAs: [...organization.sameAs],\n contactPoint: {\n \"@type\": \"ContactPoint\",\n contactType: organization.contactPoint.contactType,\n email: organization.contactPoint.email,\n },\n ...(organization.address === undefined\n ? {}\n : {\n address: {\n \"@type\": \"PostalAddress\" as const,\n ...organization.address,\n },\n }),\n });\n\n const generateWebsiteSchema = (): WithContext<WebSite> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"WebSite\",\n \"@id\": websiteId,\n name: site.name,\n url: origin,\n publisher: jsonLdRef(organizationId),\n potentialAction: {\n \"@type\": \"SearchAction\",\n target: {\n \"@type\": \"EntryPoint\",\n urlTemplate: absoluteUrl(origin, website.searchPath),\n },\n query: \"required name=search_term_string\",\n },\n });\n\n const generateServiceSchema = (\n params: ServiceParams,\n ): WithContext<Service> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"Service\",\n name: params.name,\n description: params.description,\n serviceType: params.serviceType,\n provider: {\n \"@type\": \"Organization\",\n ...(params.provider === undefined\n ? { \"@id\": organizationId, url: origin }\n : {}),\n name: params.provider ?? site.name,\n },\n areaServed: params.areaServed ?? \"Worldwide\",\n });\n\n const generateFAQPageSchema = (faqs: SeoFaq[]): WithContext<FAQPage> => {\n const questions: Question[] = faqs.map((faq) => ({\n \"@type\": \"Question\",\n name: faq.question,\n acceptedAnswer: {\n \"@type\": \"Answer\",\n text: faq.answer,\n },\n }));\n\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"FAQPage\",\n mainEntity: questions,\n };\n };\n\n const generateBreadcrumbSchema = (\n items: BreadcrumbItem[],\n ): WithContext<BreadcrumbList> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"BreadcrumbList\",\n itemListElement: items.map((item, index) => ({\n \"@type\": \"ListItem\",\n position: index + 1,\n name: item.name,\n item: `${origin}${item.url}`,\n })),\n });\n\n const generateItemListSchema = (\n name: string,\n items: ReadonlyArray<ItemListEntry>,\n ): WithContext<ItemList> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"ItemList\",\n name,\n numberOfItems: items.length,\n itemListElement: items.map((item, index) => ({\n \"@type\": \"ListItem\",\n position: index + 1,\n name: item.name,\n url: `${origin}${item.url}`,\n })),\n });\n\n const generateArticleSchema = (\n params: ArticleParams,\n ): WithContext<Article> => {\n const authorSchema: Person = {\n \"@type\": \"Person\",\n name: params.author.name,\n ...(params.author.url && { url: params.author.url }),\n };\n\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"Article\",\n headline: params.headline,\n description: params.description,\n // Deliberately NOT absolutized: a declared instance image lands verbatim\n // (og:image is where the absolute form is required, and seoHead does that).\n image: params.image ?? absoluteUrl(origin, site.defaultImage),\n datePublished: params.datePublished,\n dateModified: params.dateModified ?? params.datePublished,\n author: authorSchema,\n publisher: {\n \"@type\": \"Organization\",\n \"@id\": organizationId,\n name: site.name,\n logo: {\n \"@type\": \"ImageObject\",\n url: absoluteUrl(origin, site.publisherLogo),\n },\n },\n mainEntityOfPage: {\n \"@type\": \"WebPage\",\n \"@id\": params.url,\n },\n };\n };\n\n const composeJsonLd: JsonLd[\"composeJsonLd\"] = (entries, context = {}) =>\n composeEntries(\n entries,\n {\n origin,\n entityIds,\n ...(context.canonical === undefined\n ? {}\n : { canonical: context.canonical }),\n },\n config.jsonLd?.transform,\n );\n\n const generateSiteGraphSchema = (): Graph | undefined => {\n const configuredSite =\n typeof config.jsonLd?.site === \"function\"\n ? config.jsonLd.site(entityIds)\n : (config.jsonLd?.site ?? []);\n const entries: Array<JsonLdEntry> = [\n {\n kind: \"organization\",\n source: \"generated\",\n document: generateOrganizationSchema(),\n },\n {\n kind: \"website\",\n source: \"generated\",\n document: generateWebsiteSchema(),\n },\n ...configuredSite.map(\n (document): JsonLdEntry => ({\n kind: \"custom\",\n source: \"site\",\n document,\n }),\n ),\n ];\n const documents = composeJsonLd(entries);\n return documents.length === 0 ? undefined : jsonLdGraph(documents);\n };\n\n return {\n entityIds,\n generateOrganizationSchema,\n generateWebsiteSchema,\n generateServiceSchema,\n generateFAQPageSchema,\n generateBreadcrumbSchema,\n generateItemListSchema,\n generateArticleSchema,\n generateSiteGraphSchema,\n composeJsonLd,\n };\n}\n","/**\n * The render layer: one route's SEO declaration plus its per-instance content,\n * composed into the meta/link descriptors TanStack Router renders into <head>,\n * with the JSON-LD that page warrants.\n *\n * {@link createSeo} binds the site identity once (see `site.ts`) and returns\n * the bound API, so a route file calls `seoHead(ctx, instance)` and never sees\n * an origin or a brand name.\n */\n\nimport type { AnyRouteMatch, MetaDescriptor } from \"@tanstack/react-router\";\n\nimport { applyTitleTemplate, type SeoPageHead } from \"../core/declare\";\n\nimport { resolveCrumbTrail } from \"./breadcrumbs\";\nimport type { JsonLdDocument, JsonLdEntry } from \"./json-ld-composition\";\nimport { absoluteUrl, type SeoConfig } from \"./site\";\nimport { createJsonLd, type JsonLd } from \"./json-ld\";\n\n/** Structural subset of TanStack's head() ctx — the leaf match plus the full chain. */\nexport interface SeoHeadCtx {\n matches: ReadonlyArray<AnyRouteMatch>;\n match: AnyRouteMatch;\n}\n\nexport interface SeoHead {\n meta: NonNullable<AnyRouteMatch[\"meta\"]>;\n links: Array<{ rel: \"canonical\"; href: string }>;\n}\n\n/** The render API, bound to one site identity. */\nexport interface Seo extends JsonLd {\n seoHead: (ctx: SeoHeadCtx, instance: SeoPageHead) => SeoHead;\n /**\n * `head()` for a static route that declares its head in `staticData.seo.head`:\n *\n * ```ts\n * createFileRoute(\"/pricing\")({\n * staticData: { seo: { kind: \"page\", sitemap, head: { title, description, faqs } } },\n * head: seo.head,\n * })\n * ```\n *\n * The graph, `pageHeads`, and the checks read the same declaration.\n *\n * Generic so TanStack infers nothing from it: a `head` typed with\n * `AnyRouteMatch` would make the route's search and loader types `any`, and\n * that spreads through the route tree.\n */\n head: <Ctx extends SeoHeadCtx>(ctx: Ctx) => SeoHead;\n}\n\n/** Strip a trailing slash from a pathname, keeping the root \"/\" intact. */\nfunction normalizePathname(pathname: string): string {\n return pathname.length > 1 && pathname.endsWith(\"/\")\n ? pathname.slice(0, -1)\n : pathname;\n}\n\nexport function createSeo(config: SeoConfig): Seo {\n const { origin, site } = config;\n const jsonLd = createJsonLd(config);\n\n const seoHead = (ctx: SeoHeadCtx, instance: SeoPageHead): SeoHead => {\n const canonical = `${origin}${instance.canonicalPath ?? normalizePathname(ctx.match.pathname)}`;\n const title = applyTitleTemplate(ctx.match.staticData.seo?.titleTemplate, instance.title);\n const resolvedOgTitle = instance.ogTitle ?? title;\n const resolvedOgDescription =\n instance.ogDescription ?? instance.description;\n const routeSeo = ctx.match.staticData.seo;\n const { article } = instance;\n const jsonLdEntries: Array<JsonLdEntry> = [];\n\n const meta: Array<MetaDescriptor> = [\n { title },\n { name: \"description\", content: instance.description },\n { property: \"og:title\", content: resolvedOgTitle },\n { property: \"og:description\", content: resolvedOgDescription },\n { property: \"og:type\", content: article ? \"article\" : \"website\" },\n { property: \"og:url\", content: canonical },\n { name: \"twitter:card\", content: \"summary_large_image\" },\n { name: \"twitter:title\", content: resolvedOgTitle },\n { name: \"twitter:description\", content: resolvedOgDescription },\n ];\n\n const robots = instance.robots ?? routeSeo?.robots;\n if (robots !== undefined) {\n meta.push({ name: \"robots\", content: robots });\n }\n\n if (article) {\n if (article.tags?.length) {\n meta.push({ name: \"keywords\", content: article.tags.join(\", \") });\n }\n meta.push(\n { property: \"article:published_time\", content: article.publishedAt },\n {\n property: \"article:modified_time\",\n content: article.modifiedAt ?? article.publishedAt,\n },\n );\n if (article.image !== undefined) {\n const imageUrl = absoluteUrl(origin, article.image);\n meta.push(\n { property: \"og:image\", content: imageUrl },\n { name: \"twitter:image\", content: imageUrl },\n );\n }\n }\n\n if (article) {\n jsonLdEntries.push({\n kind: \"article\",\n source: \"generated\",\n document: jsonLd.generateArticleSchema({\n headline: resolvedOgTitle,\n description: instance.description,\n image: article.image,\n datePublished: article.publishedAt,\n dateModified: article.modifiedAt ?? article.publishedAt,\n author: article.author ?? site.defaultAuthor,\n url: canonical,\n }),\n });\n }\n\n if (!article && instance.keywords) {\n meta.push({ name: \"keywords\", content: instance.keywords.join(\", \") });\n }\n\n if (instance.service) {\n jsonLdEntries.push({\n kind: \"service\",\n source: \"generated\",\n document: jsonLd.generateServiceSchema({\n name: instance.service.name,\n description: instance.service.description ?? instance.description,\n serviceType: instance.service.serviceType,\n }),\n });\n }\n\n if (instance.faqs) {\n jsonLdEntries.push({\n kind: \"faq\",\n source: \"generated\",\n document: jsonLd.generateFAQPageSchema([...instance.faqs]),\n });\n }\n\n if (instance.itemList) {\n jsonLdEntries.push({\n kind: \"item-list\",\n source: \"generated\",\n document: jsonLd.generateItemListSchema(\n instance.itemList.name,\n instance.itemList.items,\n ),\n });\n }\n\n // TanStack accumulates every matched route's head output. Only the current leaf\n // owns the BreadcrumbList; otherwise each parent repeats a progressively stale\n // trail alongside the leaf's canonical trail on nested pages.\n const isLeafMatch = ctx.matches.at(-1)?.id === ctx.match.id;\n const trail = isLeafMatch\n ? (instance.breadcrumbs ?? resolveCrumbTrail(ctx.matches))\n : [];\n if (trail.length >= 2) {\n jsonLdEntries.push({\n kind: \"breadcrumb\",\n source: \"generated\",\n document: jsonLd.generateBreadcrumbSchema(\n trail.map((item) => ({ name: item.name, url: item.path })),\n ),\n });\n }\n\n if (instance.jsonLd) {\n jsonLdEntries.push(\n ...instance.jsonLd.map(\n (document): JsonLdEntry => ({\n kind: \"custom\",\n source: \"page\",\n document,\n }),\n ),\n );\n }\n\n for (const document of jsonLd.composeJsonLd(jsonLdEntries, { canonical })) {\n meta.push({ \"script:ld+json\": document });\n }\n\n // React's head types (JSX.IntrinsicElements['meta']) model only <meta> attributes;\n // TanStack renders `script:ld+json` entries as JSON-LD <script> tags at runtime. The\n // installed @tanstack/react-router augments leaf head meta to those JSX attributes, so\n // the JSON-LD entries are asserted to it (same pattern as an inline root JSON-LD).\n const head: SeoHead = {\n meta: meta as NonNullable<AnyRouteMatch[\"meta\"]>,\n links: [{ rel: \"canonical\", href: canonical }],\n };\n return config.transformHead === undefined ? head : config.transformHead(head);\n };\n\n const head = <Ctx extends SeoHeadCtx>(ctx: Ctx): SeoHead => {\n const declared = ctx.match.staticData.seo?.head;\n if (declared === undefined) {\n throw new Error(`seo.head needs staticData.seo.head on route ${ctx.match.routeId}`);\n }\n return seoHead(ctx, declared);\n };\n\n return { seoHead, head, ...jsonLd };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"react.js","names":["composeJsonLd","composeEntries"],"sources":["../src/react/breadcrumbs.tsx","../src/react/json-ld-composition.ts","../src/react/site.ts","../src/react/json-ld.ts","../src/react/create-seo.ts"],"sourcesContent":["import type { AnyRouteMatch } from \"@tanstack/react-router\";\nimport { Link, useMatches } from \"@tanstack/react-router\";\nimport { Fragment } from \"react\";\n\nexport interface CrumbTrailItem {\n name: string;\n path: string;\n}\n\n/**\n * Walk the match chain in order, keeping matches that declare `staticData.seo.crumb`,\n * resolving string | fn form. Shared by <Breadcrumbs/> and seoHead's BreadcrumbList.\n */\nexport function resolveCrumbTrail(matches: ReadonlyArray<AnyRouteMatch>): Array<CrumbTrailItem> {\n const trail: Array<CrumbTrailItem> = [];\n for (const match of matches) {\n const crumb = match.staticData.seo?.crumb;\n if (crumb === undefined) continue;\n const name = typeof crumb === \"function\" ? crumb(match) : crumb;\n trail.push({ name, path: match.pathname });\n }\n return trail;\n}\n\nexport function Breadcrumbs({ className }: { className?: string | undefined }) {\n const trail = resolveCrumbTrail(useMatches());\n if (trail.length < 2) return null;\n\n const lastIndex = trail.length - 1;\n return (\n <nav aria-label=\"Breadcrumb\" className={className}>\n <ol className=\"text-muted-foreground flex items-center gap-2 font-mono text-[11px] tracking-wide uppercase\">\n {trail.map((item, index) => {\n const isLast = index === lastIndex;\n return (\n <Fragment key={item.path}>\n {index > 0 ? (\n <li aria-hidden=\"true\" className=\"text-muted-foreground/50\">\n /\n </li>\n ) : null}\n <li\n aria-current={isLast ? \"page\" : undefined}\n className={isLast ? \"text-foreground truncate\" : undefined}\n >\n {isLast ? (\n item.name\n ) : (\n <Link to={item.path} className=\"hover:text-foreground transition-colors\">\n {item.name}\n </Link>\n )}\n </li>\n </Fragment>\n );\n })}\n </ol>\n </nav>\n );\n}\n","import type {\n Article,\n BreadcrumbList,\n FAQPage,\n Graph,\n IdReference,\n ItemList,\n Organization,\n Service,\n Thing,\n WebSite,\n WithContext,\n} from \"schema-dts\";\n\n/** Any schema.org entity accepted by `schema-dts`. */\nexport type JsonLdNode = Extract<Thing, object>;\n\nimport type { JsonLdDocument } from \"../core/declare\";\n\nexport type { JsonLdDocument };\n\nexport interface JsonLdEntityIds {\n organization: string;\n website: string;\n}\n\nexport type JsonLdEntry =\n | {\n kind: \"organization\";\n source: \"generated\";\n document: WithContext<Organization>;\n }\n | { kind: \"website\"; source: \"generated\"; document: WithContext<WebSite> }\n | { kind: \"article\"; source: \"generated\"; document: WithContext<Article> }\n | { kind: \"service\"; source: \"generated\"; document: WithContext<Service> }\n | { kind: \"faq\"; source: \"generated\"; document: WithContext<FAQPage> }\n | {\n kind: \"breadcrumb\";\n source: \"generated\";\n document: WithContext<BreadcrumbList>;\n }\n | { kind: \"item-list\"; source: \"generated\"; document: WithContext<ItemList> }\n | { kind: \"custom\"; source: \"site\" | \"page\"; document: JsonLdDocument };\n\nexport interface JsonLdTransformContext {\n /** Canonical site origin configured through `createSeo`. */\n origin: string;\n /** Stable identifiers for the plugin's generated site entities. */\n entityIds: JsonLdEntityIds;\n /** Canonical page URL when composition happens for a route. */\n canonical?: string | undefined;\n}\n\n/** Return no document to suppress an entry, one to replace it, or an array to expand it. */\nexport type JsonLdTransformResult =\n | JsonLdDocument\n | ReadonlyArray<JsonLdDocument>\n | false\n | null\n | undefined;\n\nexport type JsonLdTransform = (\n entry: JsonLdEntry,\n context: JsonLdTransformContext,\n) => JsonLdTransformResult;\n\nexport interface SeoJsonLdConfig {\n /** Custom entities included by `generateSiteGraphSchema`. */\n site?:\n | ReadonlyArray<JsonLdDocument>\n | ((entityIds: JsonLdEntityIds) => ReadonlyArray<JsonLdDocument>)\n | undefined;\n /** Open composition hook for generated and custom entries. */\n transform?: JsonLdTransform | undefined;\n}\n\n/** Add the schema.org context to an entity while preserving its concrete type. */\nexport function defineJsonLd<const Node extends JsonLdNode & object>(\n node: Node,\n): Node & { \"@context\": \"https://schema.org\" } {\n return { \"@context\": \"https://schema.org\", ...node };\n}\n\n/** Reference a canonical entity declared elsewhere in the same graph or page. */\nexport function jsonLdRef(id: string): IdReference {\n return { \"@id\": id };\n}\n\n/** Extend a generated or custom document without rebuilding its existing properties. */\nexport function extendJsonLd<\n const Document extends JsonLdDocument,\n const Extension extends object,\n>(document: Document, extension: Extension): Document & Extension {\n return { ...(document as unknown as object), ...extension } as Document &\n Extension;\n}\n\nconst documentNodes = (document: JsonLdDocument): ReadonlyArray<JsonLdNode> => {\n if (\"@graph\" in document) {\n return document[\"@graph\"] as ReadonlyArray<JsonLdNode>;\n }\n const { \"@context\": _context, ...node } = document as unknown as Record<\n string,\n unknown\n >;\n return [node as JsonLdNode];\n};\n\n/** Compose related schema.org documents into one graph without nested `@context` values. */\nexport function jsonLdGraph(documents: ReadonlyArray<JsonLdDocument>): Graph {\n return {\n \"@context\": \"https://schema.org\",\n \"@graph\": documents.flatMap(documentNodes),\n };\n}\n\n/** Apply an optional zero/one/many transform while preserving entry order. */\nexport function composeJsonLd(\n entries: ReadonlyArray<JsonLdEntry>,\n context: JsonLdTransformContext,\n transform?: JsonLdTransform,\n): Array<JsonLdDocument> {\n if (transform === undefined) {\n return entries.map((entry) => entry.document as JsonLdDocument);\n }\n\n return entries.flatMap((entry) => {\n const result = transform(entry, context);\n if (result === undefined || result === null || result === false) return [];\n return Array.isArray(result) ? [...result] : [result as JsonLdDocument];\n });\n}\n","/**\n * Site identity — the values a package cannot know about its host.\n *\n * Bound once through {@link createSeo}, so the ~30 route call sites that render\n * a head never thread an origin or a brand name through their arguments.\n * Everything here is a *value*; the schema construction it feeds lives in\n * `json-ld.ts`.\n */\n\nimport type { OgImageResolver } from \"../core/graph\";\nimport type { SeoHead } from \"./create-seo\";\nimport type { SeoJsonLdConfig } from \"./json-ld-composition\";\n\n/** Absolute URL for an origin-relative path; an already-absolute URL passes through. */\nexport function absoluteUrl(origin: string, pathOrUrl: string): string {\n return pathOrUrl.startsWith(\"http\") ? pathOrUrl : `${origin}${pathOrUrl}`;\n}\n\nexport interface SeoSite {\n /** Brand name: Organization/WebSite `name`, Article publisher, Service provider default. */\n name: string;\n /** Organization `logo`. Origin-relative path or absolute URL. */\n logo: string;\n /** Article `publisher.logo` (Google wants an ImageObject distinct from the brand mark). */\n publisherLogo: string;\n /** Article `image` when the instance declares none. */\n defaultImage: string;\n /** Article `author` when the instance declares none. */\n defaultAuthor: { name: string; url?: string | undefined };\n}\n\n/** Public mailing address rendered as a schema.org PostalAddress. */\nexport interface SeoPostalAddress {\n streetAddress: string;\n addressLocality: string;\n addressRegion: string;\n postalCode: string;\n addressCountry: string;\n}\n\n/** The site-wide Organization entity (Knowledge Graph). */\nexport interface SeoOrganization {\n legalName?: string | undefined;\n description: string;\n sameAs: ReadonlyArray<string>;\n contactPoint: { contactType: string; email: string };\n address?: SeoPostalAddress | undefined;\n}\n\n/** The site-wide WebSite entity (sitelinks search box). */\nexport interface SeoWebsite {\n /** Origin-relative search target carrying the `{search_term_string}` placeholder. */\n searchPath: string;\n}\n\nexport interface SeoConfig {\n /** Canonical origin, no trailing slash — e.g. `https://example.com`. */\n origin: string;\n site: SeoSite;\n organization: SeoOrganization;\n website: SeoWebsite;\n jsonLd?: SeoJsonLdConfig | undefined;\n /** Shared with pagegraph({ ogImage }) so rendered tags and graph heads agree. */\n ogImage?: OgImageResolver | undefined;\n /**\n * Last-mile override for every head `seoHead` renders, e.g. to append\n * site-specific meta. Runs after the canonical, title template, and JSON-LD.\n */\n transformHead?: ((head: SeoHead) => SeoHead) | undefined;\n}\n","/**\n * JSON-LD structured-data generators.\n *\n * schema.org payloads typed against `schema-dts`, which is a types-only devDep —\n * it erases at build, so this module keeps the entry's zero-runtime-dep rule.\n * The brand values (name, logo, sameAs, the Organization description) are the\n * host's, and arrive through {@link SeoConfig}; the schema construction is ours.\n *\n * @see https://developers.google.com/search/docs/appearance/structured-data/intro-structured-data\n * @see https://schema.org/\n */\n\nimport type {\n Article,\n BreadcrumbList,\n FAQPage,\n Graph,\n ItemList,\n Organization,\n Person,\n Question,\n Service,\n WebSite,\n WithContext,\n} from \"schema-dts\";\n\nimport {\n composeJsonLd as composeEntries,\n jsonLdGraph,\n jsonLdRef,\n type JsonLdDocument,\n type JsonLdEntityIds,\n type JsonLdEntry,\n} from \"./json-ld-composition\";\nimport type { SeoFaq } from \"../core/declare\";\nimport { absoluteUrl, type SeoConfig } from \"./site\";\n\nexport interface ServiceParams {\n name: string;\n description: string;\n serviceType: string;\n provider?: string | undefined;\n areaServed?: string | undefined;\n}\n\n\nexport interface BreadcrumbItem {\n name: string;\n url: string;\n}\n\nexport interface ItemListEntry {\n name: string;\n url: string;\n}\n\nexport interface ArticleParams {\n headline: string;\n description: string;\n image?: string | undefined;\n datePublished: string;\n dateModified?: string | undefined;\n author: {\n name: string;\n url?: string | undefined;\n };\n url: string;\n}\n\n/** The JSON-LD generators, bound to one site identity. */\nexport interface JsonLd {\n readonly entityIds: JsonLdEntityIds;\n /** Brand entity for the Knowledge Graph. Render site-wide (root layout). */\n generateOrganizationSchema: () => WithContext<Organization>;\n /** WebSite entity with a sitelinks search box. Render on the homepage. */\n generateWebsiteSchema: () => WithContext<WebSite>;\n generateServiceSchema: (params: ServiceParams) => WithContext<Service>;\n generateFAQPageSchema: (faqs: SeoFaq[]) => WithContext<FAQPage>;\n generateBreadcrumbSchema: (\n items: BreadcrumbItem[],\n ) => WithContext<BreadcrumbList>;\n /** Describe a visibly rendered, ordered collection of canonical pages. */\n generateItemListSchema: (\n name: string,\n items: ReadonlyArray<ItemListEntry>,\n ) => WithContext<ItemList>;\n generateArticleSchema: (params: ArticleParams) => WithContext<Article>;\n /** Compose the Organization, WebSite, and configured site entities into one graph. */\n generateSiteGraphSchema: () => Graph | undefined;\n /** Apply the configured transform to generated or custom entries. */\n composeJsonLd: (\n entries: ReadonlyArray<JsonLdEntry>,\n context?: { canonical?: string | undefined },\n ) => Array<JsonLdDocument>;\n}\n\nexport function createJsonLd(config: SeoConfig): JsonLd {\n const { origin, site, organization, website } = config;\n const organizationId = `${origin}/#organization`;\n const websiteId = `${origin}/#website`;\n const entityIds = { organization: organizationId, website: websiteId };\n\n const generateOrganizationSchema = (): WithContext<Organization> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"Organization\",\n \"@id\": organizationId,\n name: site.name,\n ...(organization.legalName === undefined\n ? {}\n : { legalName: organization.legalName }),\n url: origin,\n logo: absoluteUrl(origin, site.logo),\n description: organization.description,\n sameAs: [...organization.sameAs],\n contactPoint: {\n \"@type\": \"ContactPoint\",\n contactType: organization.contactPoint.contactType,\n email: organization.contactPoint.email,\n },\n ...(organization.address === undefined\n ? {}\n : {\n address: {\n \"@type\": \"PostalAddress\" as const,\n ...organization.address,\n },\n }),\n });\n\n const generateWebsiteSchema = (): WithContext<WebSite> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"WebSite\",\n \"@id\": websiteId,\n name: site.name,\n url: origin,\n publisher: jsonLdRef(organizationId),\n potentialAction: {\n \"@type\": \"SearchAction\",\n target: {\n \"@type\": \"EntryPoint\",\n urlTemplate: absoluteUrl(origin, website.searchPath),\n },\n query: \"required name=search_term_string\",\n },\n });\n\n const generateServiceSchema = (\n params: ServiceParams,\n ): WithContext<Service> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"Service\",\n name: params.name,\n description: params.description,\n serviceType: params.serviceType,\n provider: {\n \"@type\": \"Organization\",\n ...(params.provider === undefined\n ? { \"@id\": organizationId, url: origin }\n : {}),\n name: params.provider ?? site.name,\n },\n areaServed: params.areaServed ?? \"Worldwide\",\n });\n\n const generateFAQPageSchema = (faqs: SeoFaq[]): WithContext<FAQPage> => {\n const questions: Question[] = faqs.map((faq) => ({\n \"@type\": \"Question\",\n name: faq.question,\n acceptedAnswer: {\n \"@type\": \"Answer\",\n text: faq.answer,\n },\n }));\n\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"FAQPage\",\n mainEntity: questions,\n };\n };\n\n const generateBreadcrumbSchema = (\n items: BreadcrumbItem[],\n ): WithContext<BreadcrumbList> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"BreadcrumbList\",\n itemListElement: items.map((item, index) => ({\n \"@type\": \"ListItem\",\n position: index + 1,\n name: item.name,\n item: `${origin}${item.url}`,\n })),\n });\n\n const generateItemListSchema = (\n name: string,\n items: ReadonlyArray<ItemListEntry>,\n ): WithContext<ItemList> => ({\n \"@context\": \"https://schema.org\",\n \"@type\": \"ItemList\",\n name,\n numberOfItems: items.length,\n itemListElement: items.map((item, index) => ({\n \"@type\": \"ListItem\",\n position: index + 1,\n name: item.name,\n url: `${origin}${item.url}`,\n })),\n });\n\n const generateArticleSchema = (\n params: ArticleParams,\n ): WithContext<Article> => {\n const authorSchema: Person = {\n \"@type\": \"Person\",\n name: params.author.name,\n ...(params.author.url && { url: params.author.url }),\n };\n\n return {\n \"@context\": \"https://schema.org\",\n \"@type\": \"Article\",\n headline: params.headline,\n description: params.description,\n // Deliberately NOT absolutized: a declared instance image lands verbatim\n // (og:image is where the absolute form is required, and seoHead does that).\n image: params.image ?? absoluteUrl(origin, site.defaultImage),\n datePublished: params.datePublished,\n dateModified: params.dateModified ?? params.datePublished,\n author: authorSchema,\n publisher: {\n \"@type\": \"Organization\",\n \"@id\": organizationId,\n name: site.name,\n logo: {\n \"@type\": \"ImageObject\",\n url: absoluteUrl(origin, site.publisherLogo),\n },\n },\n mainEntityOfPage: {\n \"@type\": \"WebPage\",\n \"@id\": params.url,\n },\n };\n };\n\n const composeJsonLd: JsonLd[\"composeJsonLd\"] = (entries, context = {}) =>\n composeEntries(\n entries,\n {\n origin,\n entityIds,\n ...(context.canonical === undefined\n ? {}\n : { canonical: context.canonical }),\n },\n config.jsonLd?.transform,\n );\n\n const generateSiteGraphSchema = (): Graph | undefined => {\n const configuredSite =\n typeof config.jsonLd?.site === \"function\"\n ? config.jsonLd.site(entityIds)\n : (config.jsonLd?.site ?? []);\n const entries: Array<JsonLdEntry> = [\n {\n kind: \"organization\",\n source: \"generated\",\n document: generateOrganizationSchema(),\n },\n {\n kind: \"website\",\n source: \"generated\",\n document: generateWebsiteSchema(),\n },\n ...configuredSite.map(\n (document): JsonLdEntry => ({\n kind: \"custom\",\n source: \"site\",\n document,\n }),\n ),\n ];\n const documents = composeJsonLd(entries);\n return documents.length === 0 ? undefined : jsonLdGraph(documents);\n };\n\n return {\n entityIds,\n generateOrganizationSchema,\n generateWebsiteSchema,\n generateServiceSchema,\n generateFAQPageSchema,\n generateBreadcrumbSchema,\n generateItemListSchema,\n generateArticleSchema,\n generateSiteGraphSchema,\n composeJsonLd,\n };\n}\n","/**\n * The render layer: one route's SEO declaration plus its per-instance content,\n * composed into the meta/link descriptors TanStack Router renders into <head>,\n * with the JSON-LD that page warrants.\n *\n * {@link createSeo} binds the site identity once (see `site.ts`) and returns\n * the bound API, so a route file calls `seoHead(ctx, instance)` and never sees\n * an origin or a brand name.\n */\n\nimport type { AnyRouteMatch, MetaDescriptor } from \"@tanstack/react-router\";\n\nimport { applyTitleTemplate, type SeoPageHead } from \"../core/declare\";\n\nimport { resolveOgImage } from \"../core/graph\";\n\nimport { resolveCrumbTrail } from \"./breadcrumbs\";\nimport type { JsonLdDocument, JsonLdEntry } from \"./json-ld-composition\";\nimport type { SeoConfig } from \"./site\";\nimport { createJsonLd, type JsonLd } from \"./json-ld\";\n\n/** Structural subset of TanStack's head() ctx — the leaf match plus the full chain. */\nexport interface SeoHeadCtx {\n matches: ReadonlyArray<AnyRouteMatch>;\n match: AnyRouteMatch;\n}\n\nexport interface SeoHead {\n meta: NonNullable<AnyRouteMatch[\"meta\"]>;\n links: Array<{ rel: \"canonical\"; href: string }>;\n}\n\n/** The render API, bound to one site identity. */\nexport interface Seo extends JsonLd {\n seoHead: (ctx: SeoHeadCtx, instance: SeoPageHead) => SeoHead;\n /**\n * `head()` for a static route that declares its head in `staticData.seo.head`:\n *\n * ```ts\n * createFileRoute(\"/pricing\")({\n * staticData: { seo: { kind: \"page\", sitemap, head: { title, description, faqs } } },\n * head: seo.head,\n * })\n * ```\n *\n * The graph, `pageHeads`, and the checks read the same declaration.\n *\n * Generic so TanStack infers nothing from it: a `head` typed with\n * `AnyRouteMatch` would make the route's search and loader types `any`, and\n * that spreads through the route tree.\n */\n head: <Ctx extends SeoHeadCtx>(ctx: Ctx) => SeoHead;\n}\n\n/** Strip a trailing slash from a pathname, keeping the root \"/\" intact. */\nfunction normalizePathname(pathname: string): string {\n return pathname.length > 1 && pathname.endsWith(\"/\")\n ? pathname.slice(0, -1)\n : pathname;\n}\n\nexport function createSeo(config: SeoConfig): Seo {\n const { origin, site } = config;\n const jsonLd = createJsonLd(config);\n\n const seoHead = (ctx: SeoHeadCtx, instance: SeoPageHead): SeoHead => {\n const canonical = `${origin}${instance.canonicalPath ?? normalizePathname(ctx.match.pathname)}`;\n const title = applyTitleTemplate(ctx.match.staticData.seo?.titleTemplate, instance.title);\n const resolvedOgTitle = instance.ogTitle ?? title;\n const resolvedOgDescription =\n instance.ogDescription ?? instance.description;\n const routeSeo = ctx.match.staticData.seo;\n const { article } = instance;\n const image = resolveOgImage({\n path: instance.canonicalPath ?? normalizePathname(ctx.match.pathname),\n kind: routeSeo?.kind ?? \"page\",\n head: {\n title,\n description: instance.description,\n faqs: instance.faqs?.map(({ question, answer }) => ({ question, answer })),\n image: instance.image ?? (article?.image === undefined ? undefined : { url: article.image }),\n },\n }, origin, config.ogImage);\n const jsonLdEntries: Array<JsonLdEntry> = [];\n\n const meta: Array<MetaDescriptor> = [\n { title },\n { name: \"description\", content: instance.description },\n { property: \"og:title\", content: resolvedOgTitle },\n { property: \"og:description\", content: resolvedOgDescription },\n { property: \"og:type\", content: article ? \"article\" : \"website\" },\n { property: \"og:url\", content: canonical },\n { name: \"twitter:card\", content: image === undefined ? \"summary\" : \"summary_large_image\" },\n { name: \"twitter:title\", content: resolvedOgTitle },\n { name: \"twitter:description\", content: resolvedOgDescription },\n ];\n\n const robots = instance.robots ?? routeSeo?.robots;\n if (robots !== undefined) {\n meta.push({ name: \"robots\", content: robots });\n }\n\n if (article) {\n if (article.tags?.length) {\n meta.push({ name: \"keywords\", content: article.tags.join(\", \") });\n }\n meta.push(\n { property: \"article:published_time\", content: article.publishedAt },\n {\n property: \"article:modified_time\",\n content: article.modifiedAt ?? article.publishedAt,\n },\n );\n }\n\n if (image !== undefined) {\n meta.push({ property: \"og:image\", content: image.url }, { name: \"twitter:image\", content: image.url });\n if (image.width !== undefined) meta.push({ property: \"og:image:width\", content: String(image.width) });\n if (image.height !== undefined) meta.push({ property: \"og:image:height\", content: String(image.height) });\n if (image.alt !== undefined) meta.push({ property: \"og:image:alt\", content: image.alt });\n }\n\n if (article) {\n jsonLdEntries.push({\n kind: \"article\",\n source: \"generated\",\n document: jsonLd.generateArticleSchema({\n headline: resolvedOgTitle,\n description: instance.description,\n image: image?.url,\n datePublished: article.publishedAt,\n dateModified: article.modifiedAt ?? article.publishedAt,\n author: article.author ?? site.defaultAuthor,\n url: canonical,\n }),\n });\n }\n\n if (!article && instance.keywords) {\n meta.push({ name: \"keywords\", content: instance.keywords.join(\", \") });\n }\n\n if (instance.service) {\n jsonLdEntries.push({\n kind: \"service\",\n source: \"generated\",\n document: jsonLd.generateServiceSchema({\n name: instance.service.name,\n description: instance.service.description ?? instance.description,\n serviceType: instance.service.serviceType,\n }),\n });\n }\n\n if (instance.faqs) {\n jsonLdEntries.push({\n kind: \"faq\",\n source: \"generated\",\n document: jsonLd.generateFAQPageSchema([...instance.faqs]),\n });\n }\n\n if (instance.itemList) {\n jsonLdEntries.push({\n kind: \"item-list\",\n source: \"generated\",\n document: jsonLd.generateItemListSchema(\n instance.itemList.name,\n instance.itemList.items,\n ),\n });\n }\n\n // TanStack accumulates every matched route's head output. Only the current leaf\n // owns the BreadcrumbList; otherwise each parent repeats a progressively stale\n // trail alongside the leaf's canonical trail on nested pages.\n const isLeafMatch = ctx.matches.at(-1)?.id === ctx.match.id;\n const trail = isLeafMatch\n ? (instance.breadcrumbs ?? resolveCrumbTrail(ctx.matches))\n : [];\n if (trail.length >= 2) {\n jsonLdEntries.push({\n kind: \"breadcrumb\",\n source: \"generated\",\n document: jsonLd.generateBreadcrumbSchema(\n trail.map((item) => ({ name: item.name, url: item.path })),\n ),\n });\n }\n\n if (instance.jsonLd) {\n jsonLdEntries.push(\n ...instance.jsonLd.map(\n (document): JsonLdEntry => ({\n kind: \"custom\",\n source: \"page\",\n document,\n }),\n ),\n );\n }\n\n for (const document of jsonLd.composeJsonLd(jsonLdEntries, { canonical })) {\n meta.push({ \"script:ld+json\": document });\n }\n\n // React's head types (JSX.IntrinsicElements['meta']) model only <meta> attributes;\n // TanStack renders `script:ld+json` entries as JSON-LD <script> tags at runtime. The\n // installed @tanstack/react-router augments leaf head meta to those JSX attributes, so\n // the JSON-LD entries are asserted to it (same pattern as an inline root JSON-LD).\n const head: SeoHead = {\n meta: meta as NonNullable<AnyRouteMatch[\"meta\"]>,\n links: [{ rel: \"canonical\", href: canonical }],\n };\n return config.transformHead === undefined ? head : config.transformHead(head);\n };\n\n const head = <Ctx extends SeoHeadCtx>(ctx: Ctx): SeoHead => {\n const declared = ctx.match.staticData.seo?.head;\n if (declared === undefined) {\n throw new Error(`seo.head needs staticData.seo.head on route ${ctx.match.routeId}`);\n }\n return seoHead(ctx, declared);\n };\n\n return { seoHead, head, ...jsonLd };\n}\n"],"mappings":"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"}