@aotter/mantle-web 0.1.0-alpha.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.
Files changed (115) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +25 -0
  3. package/dist/MantleWeb.d.ts +23 -0
  4. package/dist/MantleWeb.d.ts.map +1 -0
  5. package/dist/MantleWeb.js +19 -0
  6. package/dist/MantleWeb.js.map +1 -0
  7. package/dist/dto/ComposeEntrySeoMetaRequest.d.ts +19 -0
  8. package/dist/dto/ComposeEntrySeoMetaRequest.d.ts.map +1 -0
  9. package/dist/dto/ComposeEntrySeoMetaRequest.js +2 -0
  10. package/dist/dto/ComposeEntrySeoMetaRequest.js.map +1 -0
  11. package/dist/dto/ComposeLlmsTxtRequest.d.ts +14 -0
  12. package/dist/dto/ComposeLlmsTxtRequest.d.ts.map +1 -0
  13. package/dist/dto/ComposeLlmsTxtRequest.js +2 -0
  14. package/dist/dto/ComposeLlmsTxtRequest.js.map +1 -0
  15. package/dist/dto/ComposeSitemapRequest.d.ts +14 -0
  16. package/dist/dto/ComposeSitemapRequest.d.ts.map +1 -0
  17. package/dist/dto/ComposeSitemapRequest.js +2 -0
  18. package/dist/dto/ComposeSitemapRequest.js.map +1 -0
  19. package/dist/dto/PreviewEntryRequest.d.ts +18 -0
  20. package/dist/dto/PreviewEntryRequest.d.ts.map +1 -0
  21. package/dist/dto/PreviewEntryRequest.js +2 -0
  22. package/dist/dto/PreviewEntryRequest.js.map +1 -0
  23. package/dist/dto/RenderEntryLiveRequest.d.ts +19 -0
  24. package/dist/dto/RenderEntryLiveRequest.d.ts.map +1 -0
  25. package/dist/dto/RenderEntryLiveRequest.js +2 -0
  26. package/dist/dto/RenderEntryLiveRequest.js.map +1 -0
  27. package/dist/dto/RenderListLiveRequest.d.ts +15 -0
  28. package/dist/dto/RenderListLiveRequest.d.ts.map +1 -0
  29. package/dist/dto/RenderListLiveRequest.js +2 -0
  30. package/dist/dto/RenderListLiveRequest.js.map +1 -0
  31. package/dist/dto/index.d.ts +7 -0
  32. package/dist/dto/index.d.ts.map +1 -0
  33. package/dist/dto/index.js +7 -0
  34. package/dist/dto/index.js.map +1 -0
  35. package/dist/index.d.ts +12 -0
  36. package/dist/index.d.ts.map +1 -0
  37. package/dist/index.js +8 -0
  38. package/dist/index.js.map +1 -0
  39. package/dist/model/SeoMeta.d.ts +47 -0
  40. package/dist/model/SeoMeta.d.ts.map +1 -0
  41. package/dist/model/SeoMeta.js +2 -0
  42. package/dist/model/SeoMeta.js.map +1 -0
  43. package/dist/model/TemplateRegistry.d.ts +50 -0
  44. package/dist/model/TemplateRegistry.d.ts.map +1 -0
  45. package/dist/model/TemplateRegistry.js +17 -0
  46. package/dist/model/TemplateRegistry.js.map +1 -0
  47. package/dist/model/WebSiteConfig.d.ts +4 -0
  48. package/dist/model/WebSiteConfig.d.ts.map +1 -0
  49. package/dist/model/WebSiteConfig.js +2 -0
  50. package/dist/model/WebSiteConfig.js.map +1 -0
  51. package/dist/service/AbsoluteUrl.d.ts +6 -0
  52. package/dist/service/AbsoluteUrl.d.ts.map +1 -0
  53. package/dist/service/AbsoluteUrl.js +33 -0
  54. package/dist/service/AbsoluteUrl.js.map +1 -0
  55. package/dist/service/HtmlEscaping.d.ts +2 -0
  56. package/dist/service/HtmlEscaping.d.ts.map +1 -0
  57. package/dist/service/HtmlEscaping.js +9 -0
  58. package/dist/service/HtmlEscaping.js.map +1 -0
  59. package/dist/service/HtmlRenderer.d.ts +39 -0
  60. package/dist/service/HtmlRenderer.d.ts.map +1 -0
  61. package/dist/service/HtmlRenderer.js +73 -0
  62. package/dist/service/HtmlRenderer.js.map +1 -0
  63. package/dist/service/MarkdownSerializer.d.ts +46 -0
  64. package/dist/service/MarkdownSerializer.d.ts.map +1 -0
  65. package/dist/service/MarkdownSerializer.js +154 -0
  66. package/dist/service/MarkdownSerializer.js.map +1 -0
  67. package/dist/service/PreviewBanner.d.ts +10 -0
  68. package/dist/service/PreviewBanner.d.ts.map +1 -0
  69. package/dist/service/PreviewBanner.js +30 -0
  70. package/dist/service/PreviewBanner.js.map +1 -0
  71. package/dist/service/PublicPathResolver.d.ts +46 -0
  72. package/dist/service/PublicPathResolver.d.ts.map +1 -0
  73. package/dist/service/PublicPathResolver.js +36 -0
  74. package/dist/service/PublicPathResolver.js.map +1 -0
  75. package/dist/service/SeoMetaComposer.d.ts +61 -0
  76. package/dist/service/SeoMetaComposer.d.ts.map +1 -0
  77. package/dist/service/SeoMetaComposer.js +213 -0
  78. package/dist/service/SeoMetaComposer.js.map +1 -0
  79. package/dist/service/SitemapSerializer.d.ts +20 -0
  80. package/dist/service/SitemapSerializer.d.ts.map +1 -0
  81. package/dist/service/SitemapSerializer.js +27 -0
  82. package/dist/service/SitemapSerializer.js.map +1 -0
  83. package/dist/service/io/MediaAssetReferences.d.ts +15 -0
  84. package/dist/service/io/MediaAssetReferences.d.ts.map +1 -0
  85. package/dist/service/io/MediaAssetReferences.js +48 -0
  86. package/dist/service/io/MediaAssetReferences.js.map +1 -0
  87. package/dist/usecase/ComposeEntrySeoMetaUseCase.d.ts +29 -0
  88. package/dist/usecase/ComposeEntrySeoMetaUseCase.d.ts.map +1 -0
  89. package/dist/usecase/ComposeEntrySeoMetaUseCase.js +66 -0
  90. package/dist/usecase/ComposeEntrySeoMetaUseCase.js.map +1 -0
  91. package/dist/usecase/ComposeLlmsTxtUseCase.d.ts +18 -0
  92. package/dist/usecase/ComposeLlmsTxtUseCase.d.ts.map +1 -0
  93. package/dist/usecase/ComposeLlmsTxtUseCase.js +47 -0
  94. package/dist/usecase/ComposeLlmsTxtUseCase.js.map +1 -0
  95. package/dist/usecase/ComposeSitemapUseCase.d.ts +15 -0
  96. package/dist/usecase/ComposeSitemapUseCase.d.ts.map +1 -0
  97. package/dist/usecase/ComposeSitemapUseCase.js +38 -0
  98. package/dist/usecase/ComposeSitemapUseCase.js.map +1 -0
  99. package/dist/usecase/PreviewEntryUseCase.d.ts +28 -0
  100. package/dist/usecase/PreviewEntryUseCase.d.ts.map +1 -0
  101. package/dist/usecase/PreviewEntryUseCase.js +72 -0
  102. package/dist/usecase/PreviewEntryUseCase.js.map +1 -0
  103. package/dist/usecase/RenderEntryLiveUseCase.d.ts +36 -0
  104. package/dist/usecase/RenderEntryLiveUseCase.d.ts.map +1 -0
  105. package/dist/usecase/RenderEntryLiveUseCase.js +62 -0
  106. package/dist/usecase/RenderEntryLiveUseCase.js.map +1 -0
  107. package/dist/usecase/RenderListLiveUseCase.d.ts +25 -0
  108. package/dist/usecase/RenderListLiveUseCase.d.ts.map +1 -0
  109. package/dist/usecase/RenderListLiveUseCase.js +46 -0
  110. package/dist/usecase/RenderListLiveUseCase.js.map +1 -0
  111. package/dist/usecase/index.d.ts +7 -0
  112. package/dist/usecase/index.d.ts.map +1 -0
  113. package/dist/usecase/index.js +7 -0
  114. package/dist/usecase/index.js.map +1 -0
  115. package/package.json +46 -0
@@ -0,0 +1,50 @@
1
+ import type { Entry } from "@aotter/mantle-spec";
2
+ import type { SeoMeta } from "./SeoMeta.js";
3
+ import type { WebSiteConfig } from "./WebSiteConfig.js";
4
+ import type { MediaAsset } from "@aotter/mantle-runtime";
5
+ /**
6
+ * Render-pipeline types + consumer-supplied template registry.
7
+ *
8
+ * The runtime stays string-typed — templates return complete HTML
9
+ * body strings (no doctype prefix; the HTML renderer adds it) so
10
+ * the runtime carries no JSX dependency. Consumers using JSX in
11
+ * their public surface (Hono + hono/jsx) call their JSX → string
12
+ * function before the template returns.
13
+ *
14
+ * Schemas without a registered entry/list template skip HTML.
15
+ * Their agent mirrors are emitted only when a public path resolves
16
+ * and the entry has serializable content.
17
+ *
18
+ * `seo` is populated when the renderer is given a composed SeoMeta
19
+ * block — templates render `<SeoTags seo={seo} />` (or read fields
20
+ * directly) when present. Renderers that skip composition leave
21
+ * `seo` undefined so opt-out templates don't break.
22
+ *
23
+ * Lives in `domain/model/` because runtime render use cases and
24
+ * consumer-supplied templates both reference it.
25
+ */
26
+ export interface EntryContext {
27
+ readonly entry: Entry;
28
+ readonly site: WebSiteConfig;
29
+ readonly mediaAssets?: ReadonlyMap<string, MediaAsset>;
30
+ readonly seo?: SeoMeta;
31
+ }
32
+ export interface ListContext {
33
+ readonly collection: string;
34
+ readonly locale: string;
35
+ readonly entries: readonly Entry[];
36
+ readonly site: WebSiteConfig;
37
+ readonly mediaAssets?: ReadonlyMap<string, MediaAsset>;
38
+ readonly seo?: SeoMeta;
39
+ }
40
+ export type EntryTemplate = (ctx: EntryContext) => string;
41
+ export type ListTemplate = (ctx: ListContext) => string;
42
+ export declare class TemplateRegistry {
43
+ private readonly entries;
44
+ private readonly lists;
45
+ registerEntryTemplate(collection: string, t: EntryTemplate): void;
46
+ registerListTemplate(collection: string, t: ListTemplate): void;
47
+ getEntryTemplate(collection: string): EntryTemplate | undefined;
48
+ getListTemplate(collection: string): ListTemplate | undefined;
49
+ }
50
+ //# sourceMappingURL=TemplateRegistry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateRegistry.d.ts","sourceRoot":"","sources":["../../src/model/TemplateRegistry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAC5C,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,SAAS,KAAK,EAAE,CAAC;IACnC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,YAAY,KAAK,MAAM,CAAC;AAC1D,MAAM,MAAM,YAAY,GAAG,CAAC,GAAG,EAAE,WAAW,KAAK,MAAM,CAAC;AAExD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoC;IAC5D,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAmC;IAEzD,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,aAAa,GAAG,IAAI;IAIjE,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,CAAC,EAAE,YAAY,GAAG,IAAI;IAI/D,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI/D,eAAe,CAAC,UAAU,EAAE,MAAM,GAAG,YAAY,GAAG,SAAS;CAG9D"}
@@ -0,0 +1,17 @@
1
+ export class TemplateRegistry {
2
+ entries = new Map();
3
+ lists = new Map();
4
+ registerEntryTemplate(collection, t) {
5
+ this.entries.set(collection, t);
6
+ }
7
+ registerListTemplate(collection, t) {
8
+ this.lists.set(collection, t);
9
+ }
10
+ getEntryTemplate(collection) {
11
+ return this.entries.get(collection);
12
+ }
13
+ getListTemplate(collection) {
14
+ return this.lists.get(collection);
15
+ }
16
+ }
17
+ //# sourceMappingURL=TemplateRegistry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"TemplateRegistry.js","sourceRoot":"","sources":["../../src/model/TemplateRegistry.ts"],"names":[],"mappings":"AA6CA,MAAM,OAAO,gBAAgB;IACV,OAAO,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC3C,KAAK,GAAG,IAAI,GAAG,EAAwB,CAAC;IAEzD,qBAAqB,CAAC,UAAkB,EAAE,CAAgB;QACxD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAClC,CAAC;IAED,oBAAoB,CAAC,UAAkB,EAAE,CAAe;QACtD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IAChC,CAAC;IAED,gBAAgB,CAAC,UAAkB;QACjC,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IAED,eAAe,CAAC,UAAkB;QAChC,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACpC,CAAC;CACF"}
@@ -0,0 +1,4 @@
1
+ import type { SiteConfig } from "@aotter/mantle-spec";
2
+ /** Site fields consumed by public document composition. */
3
+ export type WebSiteConfig = Pick<SiteConfig, "title" | "description" | "origin" | "locales" | "canonicalLocale" | "brand" | "icons" | "ga4MeasurementId" | "facebookPixelId">;
4
+ //# sourceMappingURL=WebSiteConfig.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSiteConfig.d.ts","sourceRoot":"","sources":["../../src/model/WebSiteConfig.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEtD,2DAA2D;AAC3D,MAAM,MAAM,aAAa,GAAG,IAAI,CAC9B,UAAU,EACR,OAAO,GACP,aAAa,GACb,QAAQ,GACR,SAAS,GACT,iBAAiB,GACjB,OAAO,GACP,OAAO,GACP,kBAAkB,GAClB,iBAAiB,CACpB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=WebSiteConfig.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"WebSiteConfig.js","sourceRoot":"","sources":["../../src/model/WebSiteConfig.ts"],"names":[],"mappings":""}
@@ -0,0 +1,6 @@
1
+ export declare function absoluteUrl(origin: string, path: string): string;
2
+ /** Append `.md` before any query string / hash. Used by the SEO
3
+ * composer + sitemap to derive markdown-mirror URLs without
4
+ * mangling query params. */
5
+ export declare function appendMarkdownExt(absoluteOrPath: string): string;
6
+ //# sourceMappingURL=AbsoluteUrl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbsoluteUrl.d.ts","sourceRoot":"","sources":["../../src/service/AbsoluteUrl.ts"],"names":[],"mappings":"AAcA,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAMhE;AAED;;6BAE6B;AAC7B,wBAAgB,iBAAiB,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAIhE"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Absolutize a path against a `SiteConfig.origin`. Single chokepoint
3
+ * so trailing-slash handling is consistent across every URL the SDK
4
+ * emits — sitemap, llms.txt mirrors, SEO canonical, hreflang siblings.
5
+ *
6
+ * Two divergent strip behaviors used to live across the codebase
7
+ * (`/\/$/` vs `/\/+$/`); both flow through here now.
8
+ *
9
+ * Empty `origin` returns the path unchanged — useful for tests and
10
+ * for templates that intentionally emit relative URLs. Already-
11
+ * absolute `path` (http(s)://...) returns unchanged.
12
+ */
13
+ const TRAILING_SLASHES = /\/+$/;
14
+ export function absoluteUrl(origin, path) {
15
+ if (!origin)
16
+ return path;
17
+ if (path.startsWith("http://") || path.startsWith("https://"))
18
+ return path;
19
+ const cleanOrigin = origin.replace(TRAILING_SLASHES, "");
20
+ if (!path)
21
+ return cleanOrigin;
22
+ return `${cleanOrigin}${path.startsWith("/") ? "" : "/"}${path}`;
23
+ }
24
+ /** Append `.md` before any query string / hash. Used by the SEO
25
+ * composer + sitemap to derive markdown-mirror URLs without
26
+ * mangling query params. */
27
+ export function appendMarkdownExt(absoluteOrPath) {
28
+ const match = /^([^?#]*)(.*)$/.exec(absoluteOrPath);
29
+ if (!match)
30
+ return `${absoluteOrPath}.md`;
31
+ return `${match[1]}.md${match[2]}`;
32
+ }
33
+ //# sourceMappingURL=AbsoluteUrl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AbsoluteUrl.js","sourceRoot":"","sources":["../../src/service/AbsoluteUrl.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,MAAM,gBAAgB,GAAG,MAAM,CAAC;AAEhC,MAAM,UAAU,WAAW,CAAC,MAAc,EAAE,IAAY;IACtD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IACzB,IAAI,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;QAAE,OAAO,IAAI,CAAC;IAC3E,MAAM,WAAW,GAAG,MAAM,CAAC,OAAO,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC,IAAI;QAAE,OAAO,WAAW,CAAC;IAC9B,OAAO,GAAG,WAAW,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,GAAG,IAAI,EAAE,CAAC;AACnE,CAAC;AAED;;6BAE6B;AAC7B,MAAM,UAAU,iBAAiB,CAAC,cAAsB;IACtD,MAAM,KAAK,GAAG,gBAAgB,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;IACpD,IAAI,CAAC,KAAK;QAAE,OAAO,GAAG,cAAc,KAAK,CAAC;IAC1C,OAAO,GAAG,KAAK,CAAC,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AACrC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export declare function escapeHtml(value: string): string;
2
+ //# sourceMappingURL=HtmlEscaping.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HtmlEscaping.d.ts","sourceRoot":"","sources":["../../src/service/HtmlEscaping.ts"],"names":[],"mappings":"AAAA,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOhD"}
@@ -0,0 +1,9 @@
1
+ export function escapeHtml(value) {
2
+ return value
3
+ .replace(/&/g, "&amp;")
4
+ .replace(/</g, "&lt;")
5
+ .replace(/>/g, "&gt;")
6
+ .replace(/"/g, "&quot;")
7
+ .replace(/'/g, "&#39;");
8
+ }
9
+ //# sourceMappingURL=HtmlEscaping.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HtmlEscaping.js","sourceRoot":"","sources":["../../src/service/HtmlEscaping.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,OAAO,KAAK;SACT,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AAC5B,CAAC"}
@@ -0,0 +1,39 @@
1
+ import type { Entry } from "@aotter/mantle-spec";
2
+ import type { TemplateRegistry } from "../model/TemplateRegistry.js";
3
+ import type { SeoMeta } from "../model/SeoMeta.js";
4
+ import type { WebSiteConfig } from "../model/WebSiteConfig.js";
5
+ import type { MediaAsset } from "@aotter/mantle-runtime";
6
+ export interface RenderEntryArgs {
7
+ readonly entry: Entry;
8
+ readonly site: WebSiteConfig;
9
+ readonly templates: TemplateRegistry;
10
+ /** Defaults to `<!doctype html>`. Pipelines that want the
11
+ * upper-case `<!DOCTYPE html>\n` shape pass it explicitly. */
12
+ readonly doctype?: string;
13
+ readonly mediaAssets?: ReadonlyMap<string, MediaAsset>;
14
+ /** Optional pre-composed SEO/AEO block. Threaded into the
15
+ * `EntryContext.seo` field so templates can emit `<SeoTags
16
+ * seo={seo}/>` inside `<head>`. Renderers that skip composition
17
+ * leave it undefined — opt-out templates keep working. */
18
+ readonly seo?: SeoMeta;
19
+ }
20
+ /** Returns the full HTML doc (doctype + body) or `null` if no entry
21
+ * template is registered for `entry.collection`. */
22
+ export declare function renderEntryHtml(args: RenderEntryArgs): string | null;
23
+ export interface RenderListArgs {
24
+ readonly collection: string;
25
+ readonly locale: string;
26
+ readonly entries: ReadonlyArray<Entry>;
27
+ readonly site: WebSiteConfig;
28
+ readonly templates: TemplateRegistry;
29
+ readonly doctype?: string;
30
+ readonly mediaAssets?: ReadonlyMap<string, MediaAsset>;
31
+ readonly seo?: SeoMeta;
32
+ }
33
+ /** Returns the full HTML doc or `null` if no list template is
34
+ * registered for `collection`. */
35
+ export declare function renderListHtml(args: RenderListArgs): string | null;
36
+ export declare function renderTrackingTagsHtml(site: WebSiteConfig): string;
37
+ export declare function normalizeGa4MeasurementId(value: string | undefined): string | null;
38
+ export declare function normalizeFacebookPixelId(value: string | undefined): string | null;
39
+ //# sourceMappingURL=HtmlRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HtmlRenderer.d.ts","sourceRoot":"","sources":["../../src/service/HtmlRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAGV,gBAAgB,EACjB,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC/D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AAWzD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC;mEAC+D;IAC/D,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvD;;;+DAG2D;IAC3D,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;qDACqD;AACrD,wBAAgB,eAAe,CAAC,IAAI,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAepE;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,aAAa,CAAC,KAAK,CAAC,CAAC;IACvC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,SAAS,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,WAAW,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IACvD,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;mCACmC;AACnC,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,GAAG,IAAI,CAelE;AAED,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAalE;AAYD,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGlF;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,IAAI,CAGjF"}
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Pure render functions over the consumer-supplied template registry.
3
+ * Request-time public and preview renderers share this template lookup
4
+ * and doctype concatenation so the contract stays single-sourced
5
+ * — adding a doctype mode, an OG-meta wrapper, or a per-collection
6
+ * pre/post hook is one edit. No I/O; no DB; no env access.
7
+ */
8
+ const DEFAULT_DOCTYPE = "<!doctype html>";
9
+ /** Returns the full HTML doc (doctype + body) or `null` if no entry
10
+ * template is registered for `entry.collection`. */
11
+ export function renderEntryHtml(args) {
12
+ const tpl = args.templates.getEntryTemplate(args.entry.collection);
13
+ if (!tpl)
14
+ return null;
15
+ return injectTrackingTags((args.doctype ?? DEFAULT_DOCTYPE) +
16
+ tpl({
17
+ entry: args.entry,
18
+ site: args.site,
19
+ mediaAssets: args.mediaAssets,
20
+ seo: args.seo,
21
+ }), args.site);
22
+ }
23
+ /** Returns the full HTML doc or `null` if no list template is
24
+ * registered for `collection`. */
25
+ export function renderListHtml(args) {
26
+ const tpl = args.templates.getListTemplate(args.collection);
27
+ if (!tpl)
28
+ return null;
29
+ return injectTrackingTags((args.doctype ?? DEFAULT_DOCTYPE) +
30
+ tpl({
31
+ collection: args.collection,
32
+ locale: args.locale,
33
+ entries: args.entries,
34
+ site: args.site,
35
+ mediaAssets: args.mediaAssets,
36
+ seo: args.seo,
37
+ }), args.site);
38
+ }
39
+ export function renderTrackingTagsHtml(site) {
40
+ const ga4Id = normalizeGa4MeasurementId(site.ga4MeasurementId);
41
+ const pixelId = normalizeFacebookPixelId(site.facebookPixelId);
42
+ const parts = [];
43
+ if (ga4Id) {
44
+ parts.push(`<script async src="https://www.googletagmanager.com/gtag/js?id=${ga4Id}"></script>`);
45
+ parts.push(`<script>window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments);}gtag('js',new Date());gtag('config','${ga4Id}');</script>`);
46
+ }
47
+ if (pixelId) {
48
+ parts.push(`<script>!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?n.callMethod.apply(n,arguments):n.queue.push(arguments);};if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s);}(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');fbq('init','${pixelId}');fbq('track','PageView');</script>`);
49
+ parts.push(`<noscript><img height="1" width="1" style="display:none" src="https://www.facebook.com/tr?id=${pixelId}&ev=PageView&noscript=1"/></noscript>`);
50
+ }
51
+ return parts.length > 0 ? `\n${parts.join("\n")}\n` : "";
52
+ }
53
+ function injectTrackingTags(html, site) {
54
+ const tags = renderTrackingTagsHtml(site);
55
+ if (!tags)
56
+ return html;
57
+ const headClose = /<\/head>/i;
58
+ if (headClose.test(html))
59
+ return html.replace(headClose, `${tags}</head>`);
60
+ const bodyOpen = /<body[^>]*>/i;
61
+ if (bodyOpen.test(html))
62
+ return html.replace(bodyOpen, (match) => `${match}${tags}`);
63
+ return `${tags}${html}`;
64
+ }
65
+ export function normalizeGa4MeasurementId(value) {
66
+ const trimmed = value?.trim().toUpperCase() ?? "";
67
+ return /^G-[A-Z0-9]{4,32}$/.test(trimmed) ? trimmed : null;
68
+ }
69
+ export function normalizeFacebookPixelId(value) {
70
+ const trimmed = value?.trim() ?? "";
71
+ return /^[0-9]{5,32}$/.test(trimmed) ? trimmed : null;
72
+ }
73
+ //# sourceMappingURL=HtmlRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HtmlRenderer.js","sourceRoot":"","sources":["../../src/service/HtmlRenderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,46 @@
1
+ import type { Entry } from "@aotter/mantle-spec";
2
+ import type { WebSiteConfig } from "../model/WebSiteConfig.js";
3
+ /**
4
+ * Built-in markdown serializer. Fixed format, applied to every entry
5
+ * whose Schema produces a `content` field. Intentionally not user-
6
+ * customizable — agents that consume `.md` mirrors and `/llms.txt`
7
+ * benefit from a predictable site-wide shape.
8
+ *
9
+ * ---
10
+ * title: ...
11
+ * description: ...
12
+ * slug: ...
13
+ * locale: ...
14
+ * publishedAt: ...
15
+ * ---
16
+ *
17
+ * # <title>
18
+ *
19
+ * > <description>
20
+ *
21
+ * <content>
22
+ *
23
+ * Entries without serializable public content skip the `.md` mirror.
24
+ *
25
+ * Pure transformation — no I/O. Lives in `domain/service/`.
26
+ */
27
+ /**
28
+ * `content`/`body` win, then structured sections, then a non-empty
29
+ * description/summary. One predicate keeps mirrors and metadata in sync.
30
+ */
31
+ export declare function getMarkdownBody(entry: Entry): string | null;
32
+ export declare function hasMarkdownBody(entry: Entry): boolean;
33
+ export declare function getEntryDescription(entry: Entry): string | null;
34
+ export declare function serializeEntryAsMarkdown(entry: Entry): string | null;
35
+ /**
36
+ * Per-locale `/llms.txt` index. One section per collection, one bullet
37
+ * per entry. The shape follows the llms.txt convention (Howard, 2024-11)
38
+ * so agent consumers can rely on a stable structure.
39
+ */
40
+ export declare function serializeLlmsTxt(args: {
41
+ readonly site: WebSiteConfig;
42
+ readonly locale: string;
43
+ readonly entriesByCollection: ReadonlyMap<string, readonly Entry[]>;
44
+ readonly pathFor: (entry: Entry) => string | null;
45
+ }): string | null;
46
+ //# sourceMappingURL=MarkdownSerializer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownSerializer.d.ts","sourceRoot":"","sources":["../../src/service/MarkdownSerializer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAG/D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAS3D;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAErD;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAG/D;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAuBpE;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE;IACrC,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,mBAAmB,EAAE,WAAW,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,CAAC,CAAC;IACpE,QAAQ,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,MAAM,GAAG,IAAI,CAAC;CACnD,GAAG,MAAM,GAAG,IAAI,CAsBhB"}
@@ -0,0 +1,154 @@
1
+ import { absoluteUrl, appendMarkdownExt } from "./AbsoluteUrl.js";
2
+ /**
3
+ * Built-in markdown serializer. Fixed format, applied to every entry
4
+ * whose Schema produces a `content` field. Intentionally not user-
5
+ * customizable — agents that consume `.md` mirrors and `/llms.txt`
6
+ * benefit from a predictable site-wide shape.
7
+ *
8
+ * ---
9
+ * title: ...
10
+ * description: ...
11
+ * slug: ...
12
+ * locale: ...
13
+ * publishedAt: ...
14
+ * ---
15
+ *
16
+ * # <title>
17
+ *
18
+ * > <description>
19
+ *
20
+ * <content>
21
+ *
22
+ * Entries without serializable public content skip the `.md` mirror.
23
+ *
24
+ * Pure transformation — no I/O. Lives in `domain/service/`.
25
+ */
26
+ /**
27
+ * `content`/`body` win, then structured sections, then a non-empty
28
+ * description/summary. One predicate keeps mirrors and metadata in sync.
29
+ */
30
+ export function getMarkdownBody(entry) {
31
+ const data = entry.data;
32
+ for (const key of ["content", "body"]) {
33
+ const value = nonEmptyString(data[key]);
34
+ if (value)
35
+ return value;
36
+ }
37
+ const sections = serializeSections(data["sections"]);
38
+ if (sections)
39
+ return sections;
40
+ return getEntryDescription(entry);
41
+ }
42
+ export function hasMarkdownBody(entry) {
43
+ return getMarkdownBody(entry) !== null;
44
+ }
45
+ export function getEntryDescription(entry) {
46
+ return nonEmptyString(entry.data["description"])
47
+ ?? nonEmptyString(entry.data["summary"]);
48
+ }
49
+ export function serializeEntryAsMarkdown(entry) {
50
+ const data = entry.data;
51
+ const content = getMarkdownBody(entry);
52
+ if (content == null)
53
+ return null;
54
+ const fm = [];
55
+ for (const k of ["title", "slug", "locale", "publishedAt"]) {
56
+ const v = k === "locale" ? entry.locale : data[k];
57
+ if (typeof v === "string" && v.length > 0)
58
+ fm.push([k, v]);
59
+ }
60
+ const title = data["title"] ?? entry.id;
61
+ const description = getEntryDescription(entry);
62
+ if (description)
63
+ fm.splice(1, 0, ["description", description]);
64
+ let out = "---\n";
65
+ for (const [k, v] of fm)
66
+ out += `${k}: ${yamlScalar(v)}\n`;
67
+ out += "---\n\n";
68
+ out += `# ${title}\n\n`;
69
+ if (description)
70
+ out += `> ${description}\n\n`;
71
+ out += content;
72
+ if (!content.endsWith("\n"))
73
+ out += "\n";
74
+ return out;
75
+ }
76
+ /**
77
+ * Per-locale `/llms.txt` index. One section per collection, one bullet
78
+ * per entry. The shape follows the llms.txt convention (Howard, 2024-11)
79
+ * so agent consumers can rely on a stable structure.
80
+ */
81
+ export function serializeLlmsTxt(args) {
82
+ const { site, locale, entriesByCollection, pathFor } = args;
83
+ const sections = [];
84
+ for (const [collection, entries] of entriesByCollection) {
85
+ const bullets = [];
86
+ for (const e of entries) {
87
+ if (!hasMarkdownBody(e))
88
+ continue;
89
+ const data = e.data;
90
+ const title = data["title"] ?? e.id;
91
+ const path = pathFor(e);
92
+ if (!path)
93
+ continue;
94
+ const desc = getEntryDescription(e) ?? "";
95
+ const url = appendMarkdownExt(absoluteUrl(site.origin, path));
96
+ bullets.push(desc ? `- [${title}](${url}): ${desc}` : `- [${title}](${url})`);
97
+ }
98
+ if (bullets.length > 0)
99
+ sections.push(`## ${collection}\n\n${bullets.join("\n")}`);
100
+ }
101
+ if (sections.length === 0)
102
+ return null;
103
+ let out = `# ${site.title}\n\n`;
104
+ if (site.description)
105
+ out += `> ${site.description}\n\n`;
106
+ if (locale)
107
+ out += `Locale: ${locale}\n\n`;
108
+ return `${out}${sections.join("\n\n")}\n`;
109
+ }
110
+ function serializeSections(value) {
111
+ if (!Array.isArray(value))
112
+ return null;
113
+ const sections = value.map(serializeSection).filter((section) => section.length > 0);
114
+ return sections.length > 0 ? sections.join("\n\n") : null;
115
+ }
116
+ function serializeSection(value) {
117
+ if (!isRecord(value))
118
+ return "";
119
+ const heading = nonEmptyString(value["title"]);
120
+ const lines = heading ? [`## ${heading}`] : [];
121
+ for (const key of ["eyebrow", "body", "description", "quote", "value"]) {
122
+ const text = nonEmptyString(value[key]);
123
+ if (text && text !== heading)
124
+ lines.push(text);
125
+ }
126
+ if (Array.isArray(value["items"])) {
127
+ for (const item of value["items"]) {
128
+ if (!isRecord(item))
129
+ continue;
130
+ const title = nonEmptyString(item["title"]);
131
+ const body = nonEmptyString(item["body"])
132
+ ?? nonEmptyString(item["description"])
133
+ ?? nonEmptyString(item["quote"]);
134
+ if (title && body)
135
+ lines.push(`- **${title}:** ${body}`);
136
+ else if (title ?? body)
137
+ lines.push(`- ${title ?? body}`);
138
+ }
139
+ }
140
+ return lines.join("\n\n");
141
+ }
142
+ function nonEmptyString(value) {
143
+ return typeof value === "string" && value.trim() ? value : null;
144
+ }
145
+ function isRecord(value) {
146
+ return typeof value === "object" && value !== null && !Array.isArray(value);
147
+ }
148
+ function yamlScalar(s) {
149
+ if (/^[A-Za-z0-9._/-][\w. /:-]*$/.test(s) && !/^(true|false|null|yes|no|on|off)$/i.test(s)) {
150
+ return s;
151
+ }
152
+ return JSON.stringify(s);
153
+ }
154
+ //# sourceMappingURL=MarkdownSerializer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MarkdownSerializer.js","sourceRoot":"","sources":["../../src/service/MarkdownSerializer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ export declare function injectPreviewBanner(html: string, banner: string): string;
2
+ /**
3
+ * Build the SDK's default preview banner markup. Both `status` and
4
+ * `slug` are HTML-escaped — `status` is a closed enum so escaping is
5
+ * defense-in-depth, but `slug` is caller-supplied and a slug
6
+ * containing `<script>` would otherwise inject script into the
7
+ * preview surface.
8
+ */
9
+ export declare function defaultPreviewBanner(status: string, slug: string): string;
10
+ //# sourceMappingURL=PreviewBanner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PreviewBanner.d.ts","sourceRoot":"","sources":["../../src/service/PreviewBanner.ts"],"names":[],"mappings":"AAgBA,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEzE"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Inject a preview banner just inside the rendered `<body>`. Used by
3
+ * the request-time `?preview=1` route so authors can see in-progress
4
+ * drafts with a visible "this isn't published" mark.
5
+ *
6
+ * The injection looks for the first `<body...>` opening tag and
7
+ * appends `banner` immediately after it. Documents without a body tag
8
+ * (fragments, partial templates) fall through unchanged — the caller
9
+ * already validated the renderer produced a full HTML document, so a
10
+ * missing body is a contract violation, not user input.
11
+ *
12
+ * Pure string transformation. Lives in `domain/service/` so the
13
+ * regex (and any future DOM-safer replacement) stays in one place.
14
+ */
15
+ const BODY_OPEN = /<body([^>]*)>/;
16
+ export function injectPreviewBanner(html, banner) {
17
+ return html.replace(BODY_OPEN, `<body$1>${banner}`);
18
+ }
19
+ /**
20
+ * Build the SDK's default preview banner markup. Both `status` and
21
+ * `slug` are HTML-escaped — `status` is a closed enum so escaping is
22
+ * defense-in-depth, but `slug` is caller-supplied and a slug
23
+ * containing `<script>` would otherwise inject script into the
24
+ * preview surface.
25
+ */
26
+ export function defaultPreviewBanner(status, slug) {
27
+ return `<div class="preview-banner">Preview · ${escapeHtml(status)} · ${escapeHtml(slug)}</div>`;
28
+ }
29
+ import { escapeHtml } from "./HtmlEscaping.js";
30
+ //# sourceMappingURL=PreviewBanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PreviewBanner.js","sourceRoot":"","sources":["../../src/service/PreviewBanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,MAAM,SAAS,GAAG,eAAe,CAAC;AAElC,MAAM,UAAU,mBAAmB,CAAC,IAAY,EAAE,MAAc;IAC9D,OAAO,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,WAAW,MAAM,EAAE,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,oBAAoB,CAAC,MAAc,EAAE,IAAY;IAC/D,OAAO,yCAAyC,UAAU,CAAC,MAAM,CAAC,MAAM,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC;AACnG,CAAC;AACD,OAAO,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC"}
@@ -0,0 +1,46 @@
1
+ import { type Entry } from "@aotter/mantle-spec";
2
+ export declare function entrySlug(entry: {
3
+ id: string;
4
+ data: Record<string, unknown>;
5
+ }): string;
6
+ export declare function entryPublicPath(entry: Entry): string;
7
+ /**
8
+ * Resolves a public URL path for an entry, given the consumer's
9
+ * collection→URL configuration. One source of truth for both the
10
+ * router (URL → collection/slug lookup) and outbound URL emission
11
+ * (sitemap, hreflang siblings, SEO canonical).
12
+ *
13
+ * The resolver's `forEntry` returns `null` when:
14
+ * - the entry's collection has no public URL (e.g. parent `posts`
15
+ * surfaces only via `post-translations` children)
16
+ * - the entry has no slug AND no canonical id-based URL pattern is
17
+ * configured for the collection
18
+ *
19
+ * Pure path math — no I/O. Lives in `domain/service/`.
20
+ */
21
+ export interface CollectionRoute {
22
+ /** URL segment for entries of this collection.
23
+ *
24
+ * - non-empty string: `/{locale}/{segment}/{slug}` (e.g. `posts`)
25
+ * - empty string: `/{locale}/{slug}` (entries directly under locale)
26
+ * - null: collection has no public URL
27
+ */
28
+ readonly segment: string | null;
29
+ /** When the entry's slug equals this value, collapse the URL to
30
+ * `/{locale}` (no trailing segment + slug). Used for home pages
31
+ * that live in a translations collection. */
32
+ readonly homeSlug?: string;
33
+ }
34
+ export interface PublicPathResolverConfig {
35
+ readonly collectionRoutes: Readonly<Record<string, CollectionRoute>>;
36
+ }
37
+ export interface PublicPathResolver {
38
+ /** Returns `/{locale}/{segment}/{slug}` style path, or `null` if the
39
+ * entry has no public URL under this configuration. */
40
+ forEntry(entry: Entry): string | null;
41
+ /** Returns the configured route for `collection`, or `undefined` if
42
+ * the collection isn't mapped. */
43
+ routeFor(collection: string): CollectionRoute | undefined;
44
+ }
45
+ export declare function createPublicPathResolver(config: PublicPathResolverConfig): PublicPathResolver;
46
+ //# sourceMappingURL=PublicPathResolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"PublicPathResolver.d.ts","sourceRoot":"","sources":["../../src/service/PublicPathResolver.ts"],"names":[],"mappings":"AAAA,OAAO,EAAe,KAAK,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAE9D,wBAAgB,SAAS,CAAC,KAAK,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAAE,GAAG,MAAM,CAMtF;AAED,wBAAgB,eAAe,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,CAIpD;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,eAAe;IAC9B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC;;kDAE8C;IAC9C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,gBAAgB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC,CAAC;CACtE;AAED,MAAM,WAAW,kBAAkB;IACjC;4DACwD;IACxD,QAAQ,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,IAAI,CAAC;IACtC;uCACmC;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS,CAAC;CAC3D;AAED,wBAAgB,wBAAwB,CACtC,MAAM,EAAE,wBAAwB,GAC/B,kBAAkB,CAmBpB"}