@lupinum/ginko-content 0.3.0-rc.3 → 0.3.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 (238) hide show
  1. package/README.md +17 -9
  2. package/dist/cli.mjs +52 -55
  3. package/dist/cms-contract/index.d.ts +7 -7
  4. package/dist/cms-contract/index.js +1 -2
  5. package/dist/cms-contract/mdc.d.ts +3 -9
  6. package/dist/cms-contract/mdc.js +6 -4
  7. package/dist/cms-contract/path.d.ts +3 -5
  8. package/dist/cms-contract/path.js +0 -1
  9. package/dist/cms-contract/render-policy.js +4 -0
  10. package/dist/cms-contract/schema.d.ts +1 -1
  11. package/dist/cms-contract/schema.js +3 -1
  12. package/dist/core/content/graph.d.ts +7 -22
  13. package/dist/core/content/graph.js +38 -52
  14. package/dist/core/content/locale.d.ts +10 -4
  15. package/dist/core/content/locale.js +15 -6
  16. package/dist/core/content/path.d.ts +35 -1
  17. package/dist/core/content/path.js +72 -13
  18. package/dist/core/content/structural.d.ts +1 -2
  19. package/dist/core/content/structural.js +1 -1
  20. package/dist/core/data-source-error.d.ts +5 -0
  21. package/dist/core/data-source-error.js +10 -0
  22. package/dist/core/errors.d.ts +1 -1
  23. package/dist/core/json-value.d.ts +1 -1
  24. package/dist/core/markdown/normalize-comark.d.ts +6 -0
  25. package/dist/core/markdown/normalize-comark.js +15 -0
  26. package/dist/core/markdown/parse-comark.d.ts +3 -0
  27. package/dist/core/markdown/parse-comark.js +30 -0
  28. package/dist/core/provider-errors.js +1 -1
  29. package/dist/core/provider-route-record.d.ts +18 -0
  30. package/dist/core/provider-route-record.js +106 -0
  31. package/dist/core/query/execute.d.ts +16 -23
  32. package/dist/core/query/execute.js +94 -110
  33. package/dist/core/query/filter.d.ts +8 -9
  34. package/dist/core/query/filter.js +145 -53
  35. package/dist/core/query/limits.d.ts +10 -0
  36. package/dist/core/query/limits.js +27 -0
  37. package/dist/core/query/lower.d.ts +12 -6
  38. package/dist/core/query/lower.js +356 -89
  39. package/dist/core/query/operators.d.ts +21 -10
  40. package/dist/core/query/operators.js +18 -11
  41. package/dist/core/query/params.d.ts +7 -10
  42. package/dist/core/query/params.js +47 -33
  43. package/dist/core/query/plan.d.ts +96 -105
  44. package/dist/core/references/resolve.d.ts +1 -1
  45. package/dist/core/references/resolve.js +31 -13
  46. package/dist/core/references/schema.d.ts +2 -0
  47. package/dist/core/references/schema.js +6 -8
  48. package/dist/core/runtime-diagnostics.d.ts +7 -0
  49. package/dist/core/runtime-diagnostics.js +9 -0
  50. package/dist/features/agent/agent-markdown.js +18 -3
  51. package/dist/features/agent/agent-paths.d.ts +0 -1
  52. package/dist/features/agent/agent-paths.js +0 -4
  53. package/dist/features/agent/walker.js +10 -7
  54. package/dist/features/collections/resolve.d.ts +2 -14
  55. package/dist/features/collections/resolve.js +3 -7
  56. package/dist/features/localization/config.d.ts +9 -1
  57. package/dist/features/localization/config.js +6 -1
  58. package/dist/features/localization/locale-policy.d.ts +14 -2
  59. package/dist/features/localization/locale-policy.js +81 -25
  60. package/dist/features/localization/path.d.ts +0 -10
  61. package/dist/features/localization/path.js +1 -23
  62. package/dist/features/localization/reference-path.d.ts +7 -0
  63. package/dist/features/localization/reference-path.js +10 -0
  64. package/dist/features/localization/results.d.ts +8 -43
  65. package/dist/features/localization/results.js +24 -47
  66. package/dist/features/localization/route-projector.d.ts +23 -16
  67. package/dist/features/localization/route-projector.js +35 -29
  68. package/dist/features/navigation/build.js +9 -5
  69. package/dist/features/navigation/canonical.d.ts +2 -8
  70. package/dist/features/navigation/canonical.js +8 -48
  71. package/dist/features/navigation/diagnostics.d.ts +11 -0
  72. package/dist/features/navigation/diagnostics.js +52 -0
  73. package/dist/features/navigation/query.d.ts +3 -5
  74. package/dist/features/navigation/resolve.d.ts +18 -2
  75. package/dist/features/navigation/resolve.js +28 -5
  76. package/dist/features/navigation/tree.d.ts +0 -1
  77. package/dist/features/navigation/tree.js +1 -1
  78. package/dist/features/query/context.d.ts +5 -3
  79. package/dist/features/query/diagnostics.d.ts +4 -0
  80. package/dist/features/query/diagnostics.js +22 -0
  81. package/dist/features/query/documents.js +6 -10
  82. package/dist/features/query/locale-options.js +2 -1
  83. package/dist/features/query/localized-docs.d.ts +0 -7
  84. package/dist/features/query/localized-docs.js +5 -31
  85. package/dist/features/query/navigation.d.ts +4 -4
  86. package/dist/features/query/navigation.js +2 -11
  87. package/dist/features/query/pagination.js +42 -31
  88. package/dist/features/query/populate.d.ts +1 -0
  89. package/dist/features/query/populate.js +76 -13
  90. package/dist/features/query/query-plan-boundary.d.ts +9 -0
  91. package/dist/features/query/query-plan-boundary.js +137 -0
  92. package/dist/features/query/responses.d.ts +28 -3
  93. package/dist/features/query/responses.js +33 -45
  94. package/dist/features/query/routes.d.ts +3 -10
  95. package/dist/features/query/routes.js +23 -13
  96. package/dist/features/query/unified.d.ts +6 -9
  97. package/dist/features/search/sections.js +3 -7
  98. package/dist/features/sitemap/counts.d.ts +1 -4
  99. package/dist/features/sitemap/query.d.ts +3 -6
  100. package/dist/features/sitemap/query.js +1 -1
  101. package/dist/features/validation/links.d.ts +2 -2
  102. package/dist/integrations/nitro/build.d.ts +5 -22
  103. package/dist/integrations/nitro/build.js +14 -47
  104. package/dist/integrations/nitro/ingest.js +18 -6
  105. package/dist/integrations/nitro/preview.js +23 -6
  106. package/dist/module.d.mts +51 -57
  107. package/dist/module.json +1 -1
  108. package/dist/module.mjs +44 -29
  109. package/dist/parsers/csv.js +5 -4
  110. package/dist/parsers/markdown.js +5 -6
  111. package/dist/parsers/path-meta.js +13 -6
  112. package/dist/parsers/reserved.d.ts +1 -2
  113. package/dist/portability/json.js +6 -1
  114. package/dist/portability/mdc.js +9 -4
  115. package/dist/public/agent-registry.d.ts +1 -1
  116. package/dist/public/agent-registry.js +8 -1
  117. package/dist/public/agent.d.ts +1 -2
  118. package/dist/public/client.d.ts +3 -3
  119. package/dist/public/data-source.d.ts +32 -19
  120. package/dist/public/data-source.js +3 -0
  121. package/dist/public/navigation.d.ts +7 -0
  122. package/dist/public/navigation.js +8 -0
  123. package/dist/public/provider-binder.d.ts +1 -1
  124. package/dist/public/provider-binder.js +93 -59
  125. package/dist/public/provider-contract.d.ts +27 -12
  126. package/dist/public/provider-contract.js +8 -0
  127. package/dist/public/provider-document.d.ts +46 -0
  128. package/dist/public/provider-document.js +109 -0
  129. package/dist/public/provider-errors.d.ts +1 -1
  130. package/dist/public/provider-errors.js +18 -9
  131. package/dist/public/provider-query.d.ts +73 -28
  132. package/dist/public/provider-query.js +48 -5
  133. package/dist/public/provider.d.ts +3 -3
  134. package/dist/public/provider.js +1 -1
  135. package/dist/public/server.d.ts +3 -3
  136. package/dist/public/server.js +0 -1
  137. package/dist/runtime/app/components/ContentRenderer.d.vue.ts +2 -2
  138. package/dist/runtime/app/components/ContentRenderer.vue.d.ts +2 -2
  139. package/dist/runtime/app/components/ContentRendererInline.vue +2 -2
  140. package/dist/runtime/app/components/Prose/ProsePre.d.vue.ts +2 -2
  141. package/dist/runtime/app/components/Prose/ProsePre.vue.d.ts +2 -2
  142. package/dist/runtime/app/components/internal/MarkdownRenderer.d.ts +2 -2
  143. package/dist/runtime/app/composables/query-api.d.ts +2 -2
  144. package/dist/runtime/app/composables/search.d.ts +9 -14
  145. package/dist/runtime/app/composables/search.js +4 -4
  146. package/dist/runtime/app/composables/site-data.d.ts +2 -2
  147. package/dist/runtime/app/composables/utils.d.ts +3 -3
  148. package/dist/runtime/app/composables/utils.js +3 -0
  149. package/dist/runtime/server/agent-markdown.js +9 -54
  150. package/dist/runtime/server/agent-site.d.ts +0 -2
  151. package/dist/runtime/server/agent-site.js +13 -27
  152. package/dist/runtime/server/api/agent-raw.js +3 -3
  153. package/dist/runtime/server/api/cache.d.ts +3 -5
  154. package/dist/runtime/server/api/cache.js +1 -1
  155. package/dist/runtime/server/api/locales.js +16 -2
  156. package/dist/runtime/server/api/navigation.js +35 -5
  157. package/dist/runtime/server/api/query.d.ts +1 -1
  158. package/dist/runtime/server/api/query.js +16 -2
  159. package/dist/runtime/server/api/revalidate.d.ts +3 -0
  160. package/dist/runtime/server/api/revalidate.js +108 -21
  161. package/dist/runtime/server/api/search.d.ts +1 -2
  162. package/dist/runtime/server/api/search.js +4 -7
  163. package/dist/runtime/server/api/site-data.d.ts +6 -1
  164. package/dist/runtime/server/api/site-data.js +32 -1
  165. package/dist/runtime/server/cache-adapter.js +2 -2
  166. package/dist/runtime/server/cache-adapters.d.ts +2 -12
  167. package/dist/runtime/server/cache-adapters.js +0 -9
  168. package/dist/runtime/server/collection-helpers.d.ts +1 -1
  169. package/dist/runtime/server/collection-helpers.js +20 -28
  170. package/dist/runtime/server/index.d.ts +1 -1
  171. package/dist/runtime/server/index.js +0 -1
  172. package/dist/runtime/server/middleware/agent-link-headers.js +5 -2
  173. package/dist/runtime/server/middleware/preview.d.ts +2 -0
  174. package/dist/runtime/server/middleware/preview.js +7 -0
  175. package/dist/runtime/server/navigation-query.d.ts +7 -2
  176. package/dist/runtime/server/navigation-query.js +64 -18
  177. package/dist/runtime/server/pagefind.js +8 -9
  178. package/dist/runtime/server/plugins/sitemap.js +1 -2
  179. package/dist/runtime/server/provider-query.d.ts +23 -36
  180. package/dist/runtime/server/provider-query.js +250 -185
  181. package/dist/runtime/server/provider-result.d.ts +5 -5
  182. package/dist/runtime/server/provider-route-facts.d.ts +6 -6
  183. package/dist/runtime/server/provider-route-facts.js +141 -96
  184. package/dist/runtime/server/providers/filesystem.js +75 -84
  185. package/dist/runtime/server/providers/index.d.ts +7 -7
  186. package/dist/runtime/server/providers/index.js +29 -44
  187. package/dist/runtime/server/query-api.d.ts +2 -2
  188. package/dist/runtime/server/query-api.js +28 -9
  189. package/dist/runtime/server/query-executor.d.ts +2 -3
  190. package/dist/runtime/server/query-executor.js +15 -14
  191. package/dist/runtime/server/query-http-validation.d.ts +10 -24
  192. package/dist/runtime/server/query-http-validation.js +100 -299
  193. package/dist/runtime/server/search.d.ts +2 -2
  194. package/dist/runtime/server/search.js +9 -3
  195. package/dist/runtime/server/sitemap-provider.js +11 -9
  196. package/dist/runtime/utils/query.d.ts +4 -4
  197. package/dist/runtime/utils/query.js +2 -2
  198. package/dist/storage/driver.d.ts +1 -16
  199. package/dist/storage/graph.d.ts +3 -14
  200. package/dist/storage/graph.js +7 -22
  201. package/dist/storage/references.d.ts +2 -2
  202. package/dist/storage/references.js +10 -4
  203. package/dist/storage/snapshot-runtime.js +6 -1
  204. package/dist/storage/validation.d.ts +6 -3
  205. package/dist/storage/validation.js +46 -12
  206. package/dist/testing/data-source-contract.d.ts +20 -1
  207. package/dist/testing/data-source-contract.js +63 -29
  208. package/dist/testing/provider-contract.d.ts +19 -4
  209. package/dist/testing/provider-contract.js +96 -16
  210. package/dist/testing/provider-fixture.d.ts +8 -2
  211. package/dist/testing/provider-fixture.js +132 -48
  212. package/dist/types/api.d.ts +12 -2
  213. package/dist/types/config.d.ts +5 -11
  214. package/dist/types/config.js +2 -2
  215. package/dist/types/content.d.ts +10 -8
  216. package/dist/types/fields.d.ts +1 -2
  217. package/dist/types/fields.js +28 -19
  218. package/dist/types/module.d.ts +11 -6
  219. package/dist/types/navigation.d.ts +13 -0
  220. package/dist/types/navigation.js +28 -0
  221. package/dist/types/query-parts/public.d.ts +35 -23
  222. package/dist/types/query-parts/public.js +7 -0
  223. package/dist/types/query-parts/results.d.ts +6 -22
  224. package/dist/types/query-parts/transport.d.ts +21 -80
  225. package/dist/types/query-parts/transport.js +18 -0
  226. package/dist/types/reference.d.ts +14 -1
  227. package/dist/types/reference.js +13 -1
  228. package/dist/types/runtime.d.ts +4 -15
  229. package/dist/web-types.json +1 -1
  230. package/package.json +9 -5
  231. package/dist/core/query/builder.d.ts +0 -18
  232. package/dist/core/query/builder.js +0 -186
  233. package/dist/features/query/public-limits.d.ts +0 -2
  234. package/dist/features/query/public-limits.js +0 -2
  235. package/dist/runtime/server/provider-document.d.ts +0 -44
  236. package/dist/runtime/server/provider-document.js +0 -63
  237. package/dist/runtime/server/provider-query-wire.d.ts +0 -30
  238. package/dist/runtime/server/provider-query-wire.js +0 -28
package/README.md CHANGED
@@ -10,11 +10,14 @@ Use it when you want content files to stay simple, but your Nuxt app still
10
10
  needs explicit APIs for route resolution, typed frontmatter, localized content,
11
11
  and server-side reads.
12
12
 
13
+ This README describes the stable `0.3` line.
14
+
13
15
  ## Requirements
14
16
 
15
- - Node.js 22 or later
17
+ - Node.js 22.18–22.x, 24.11–24.x, or 26+
16
18
  - Nuxt 4.4.7 through Nuxt 4.x
17
- - Vue 3.5 or later
19
+ - Vue 3.5.35 through Vue 3.x
20
+ - ESM; CommonJS `require()` is not supported
18
21
 
19
22
  ## Install
20
23
 
@@ -75,7 +78,7 @@ Render the current route through the collection:
75
78
  ```vue
76
79
  <!-- pages/[...slug].vue -->
77
80
  <script setup lang="ts">
78
- import { pages } from '~/content.config'
81
+ import { pages } from '~~/content.config'
79
82
 
80
83
  const { page } = await useContentPage(pages)
81
84
  </script>
@@ -94,9 +97,10 @@ const { page } = await useContentPage(pages)
94
97
  - locale-aware content routing
95
98
  - route-aware page loading with `useContentPage(pages)`
96
99
  - semantic previous/next route-page data through `useContentPage(pages, { surround })`
97
- - server reads through `one`, `many`, `paginate`, `resolveOne`, `tree`, and
98
- `neighbors`
99
- - Vue composables for the same read model
100
+ - server reads through `one`, `many`, `paginate`, `resolveOne`, `navigation`,
101
+ and `surround`
102
+ - route and search composables, plus pure client query functions for other
103
+ reads
100
104
  - search helpers for MiniSearch, Pagefind, and provider-owned search
101
105
  - sitemap integration for public content routes
102
106
  - a server-side provider contract for advanced custom sources
@@ -116,7 +120,8 @@ Do not duplicate docs, blog, pricing, privacy, or translated locale paths in
116
120
  `@nuxtjs/sitemap >= 8.0.15 < 9` when translated static app slugs such as `/preise`
117
121
  and `/en/pricing` need cross-locale sitemap alternates.
118
122
 
119
- See the public guide: [Sitemap and prerender](../../docs/content/docs/4.guides/4.routing-and-seo/3.sitemap-and-prerender.md).
123
+ See the public guide:
124
+ [Sitemap and prerender](https://github.com/lupinum-dev/ginko-content/blob/main/docs/content/docs/4.guides/4.routing-and-seo/3.sitemap-and-prerender.md).
120
125
 
121
126
  ## Integration Dependencies
122
127
 
@@ -134,10 +139,13 @@ See the public guide: [Sitemap and prerender](../../docs/content/docs/4.guides/4
134
139
  The default provider reads files from your Nuxt project. The package does not
135
140
  include a CMS UI, Studio, admin panel, or content editing workflow.
136
141
 
137
- ## Docs
142
+ ## Project links
138
143
 
139
- - Documentation: [ginko-content.nuxt.dev](https://ginko-content.nuxt.dev)
144
+ - Documentation source: [Getting started](https://github.com/lupinum-dev/ginko-content/blob/main/docs/content/docs/3.get-started/1.installation.md)
140
145
  - Repository: [github.com/lupinum-dev/ginko-content](https://github.com/lupinum-dev/ginko-content)
146
+ - Issues: [GitHub issues](https://github.com/lupinum-dev/ginko-content/issues)
147
+ - Contributing: [CONTRIBUTING.md](https://github.com/lupinum-dev/ginko-content/blob/main/CONTRIBUTING.md)
148
+ - Security: [SECURITY.md](https://github.com/lupinum-dev/ginko-content/blob/main/SECURITY.md)
141
149
 
142
150
  ## Credits
143
151
 
package/dist/cli.mjs CHANGED
@@ -316,62 +316,63 @@ function extractLocaleCodesFromConfig(text) {
316
316
 
317
317
  async function detectI18n(rootDir) {
318
318
  const nuxtConfigPath = join(rootDir, "nuxt.config.ts");
319
- const contentConfigPath = join(rootDir, "content.config.ts");
320
319
  const nuxtConfig = await readTextIfPresent(nuxtConfigPath);
321
- const contentConfig = await readTextIfPresent(contentConfigPath);
322
320
  const packageJson = await readPackageJson(rootDir);
323
- const locales = /* @__PURE__ */ new Set([
324
- ...extractLocaleCodesFromConfig(nuxtConfig),
325
- ...extractLocaleCodesFromConfig(contentConfig)
326
- ]);
327
- const contentDir = join(rootDir, "content");
328
- if (existsSync(contentDir)) {
329
- const entries = await readdir(contentDir, { withFileTypes: true });
330
- for (const entry of entries) {
331
- if (entry.isDirectory() && localeCodePattern.test(entry.name)) {
332
- locales.add(entry.name);
333
- }
334
- }
335
- }
321
+ const contentI18nBlocks = findObjectPropertyBlocks(nuxtConfig, "content").flatMap((block) => findObjectPropertyBlocks(block, "i18n"));
322
+ const nuxtI18nBlocks = findObjectPropertyBlocks(nuxtConfig, "i18n");
323
+ for (const contentI18nBlock of contentI18nBlocks) {
324
+ const index = nuxtI18nBlocks.indexOf(contentI18nBlock);
325
+ if (index !== -1) {
326
+ nuxtI18nBlocks.splice(index, 1);
327
+ }
328
+ }
329
+ const nuxtI18nConfig = nuxtI18nBlocks.join("\n");
330
+ const contentI18nConfig = contentI18nBlocks.join("\n");
331
+ const hasNuxtI18nModule = /['"]@nuxtjs\/i18n['"]/.test(nuxtConfig);
332
+ const authorityConfig = hasNuxtI18nModule ? nuxtI18nConfig : contentI18nConfig;
336
333
  return {
337
- locales: [...locales].sort(),
338
- hasNuxtI18nModule: /['"]@nuxtjs\/i18n['"]/.test(nuxtConfig),
339
- hasContentI18nConfig: /\bcontent\s*:\s*\{[\s\S]*?\bi18n\s*:/.test(nuxtConfig),
340
- hasNuxtI18nDependency: hasDependency(packageJson, "@nuxtjs/i18n")
334
+ locales: extractLocaleCodesFromConfig(authorityConfig).sort(),
335
+ hasNuxtI18nModule,
336
+ hasNuxtI18nDependency: hasDependency(packageJson, "@nuxtjs/i18n"),
337
+ nuxtI18nDeclaresLocales: /\blocales\s*:/.test(nuxtI18nConfig),
338
+ nuxtI18nDeclaresDefaultLocale: /\bdefaultLocale\s*:/.test(nuxtI18nConfig),
339
+ contentI18nDeclaresLocales: /\blocales\s*:/.test(contentI18nConfig),
340
+ contentI18nDeclaresDefaultLocale: /\bdefaultLocale\s*:/.test(contentI18nConfig)
341
341
  };
342
342
  }
343
- async function inspectI18nConfig(rootDir, detected) {
343
+ function inspectI18nConfig(detected) {
344
344
  const findings = [];
345
- if (!detected.locales.length) {
346
- findings.push({
347
- severity: "error",
348
- file: "nuxt.config.ts",
349
- message: "No i18n locales detected.",
350
- suggestion: "Configure @nuxtjs/i18n locales and content.i18n.locales, or create content/<locale>/ folders."
351
- });
352
- }
353
- if (!detected.hasNuxtI18nDependency) {
354
- findings.push({
355
- severity: "error",
356
- file: "package.json",
357
- message: "Direct @nuxtjs/i18n dependency is missing for an i18n app.",
358
- suggestion: "Install @nuxtjs/i18n directly so Nuxt owns route localization explicitly."
359
- });
360
- }
361
- if (!detected.hasNuxtI18nModule) {
362
- findings.push({
363
- severity: "error",
364
- file: "nuxt.config.ts",
365
- message: "@nuxtjs/i18n module is not registered.",
366
- suggestion: "Add @nuxtjs/i18n to modules before relying on localized content routes."
367
- });
368
- }
369
- if (!detected.hasContentI18nConfig) {
345
+ if (detected.hasNuxtI18nModule) {
346
+ if (!detected.hasNuxtI18nDependency) {
347
+ findings.push({
348
+ severity: "error",
349
+ file: "package.json",
350
+ message: "Direct @nuxtjs/i18n dependency is missing for an i18n app.",
351
+ suggestion: "Install @nuxtjs/i18n directly so Nuxt owns route localization explicitly."
352
+ });
353
+ }
354
+ if (!detected.nuxtI18nDeclaresLocales || !detected.nuxtI18nDeclaresDefaultLocale) {
355
+ findings.push({
356
+ severity: "error",
357
+ file: "nuxt.config.ts",
358
+ message: "Nuxt I18n locale authority is incomplete.",
359
+ suggestion: "Declare locales and defaultLocale under top-level i18n in nuxt.config.ts."
360
+ });
361
+ }
362
+ if (detected.contentI18nDeclaresLocales || detected.contentI18nDeclaresDefaultLocale) {
363
+ findings.push({
364
+ severity: "error",
365
+ file: "nuxt.config.ts",
366
+ message: "Duplicate locale authority found in content.i18n.",
367
+ suggestion: "Remove locales and defaultLocale from content.i18n; Nuxt I18n is authoritative when registered. Keep only fallback and translatedSlugs there."
368
+ });
369
+ }
370
+ } else if (!detected.contentI18nDeclaresLocales || !detected.contentI18nDeclaresDefaultLocale) {
370
371
  findings.push({
371
372
  severity: "error",
372
373
  file: "nuxt.config.ts",
373
- message: "content.i18n runtime config is missing.",
374
- suggestion: "Add content.i18n with defaultLocale and locales so collections resolve localized documents."
374
+ message: "Content-owned locale authority is incomplete.",
375
+ suggestion: "Without @nuxtjs/i18n, declare locales and defaultLocale under content.i18n in nuxt.config.ts."
375
376
  });
376
377
  }
377
378
  return findings;
@@ -599,7 +600,7 @@ async function inspectI18nSearchIndex(rootDir, locales) {
599
600
  async function inspectI18n(rootDir) {
600
601
  const detected = await detectI18n(rootDir);
601
602
  return [
602
- ...await inspectI18nConfig(rootDir, detected),
603
+ ...inspectI18nConfig(detected),
603
604
  ...await inspectI18nCollections(rootDir),
604
605
  ...await inspectI18nContentFolders(rootDir, detected.locales),
605
606
  ...await inspectI18nDuplicateContentGroups(rootDir, detected.locales),
@@ -746,15 +747,11 @@ async function inspectPublicApiUsage(rootDir) {
746
747
  {
747
748
  // `content.preview` is a real Ginko option (ContentPreviewOptions), unlike
748
749
  // `content.database`/`content.build`, so it is not flagged as a v3
749
- // leftover. This rule only reminds authors of its actual scope: the
750
- // filesystem provider always shows drafts/partials in development, and a
751
- // configured token additionally unlocks visibility for files already
752
- // present in a deployed filesystem checkout. It does not fetch
753
- // not-yet-published content — that kind of production preview workflow
754
- // is owned by a CMS provider, not the filesystem provider.
750
+ // leftover. It protects preview-storage overlay reads; it is not a
751
+ // production filesystem-preview switch or a partial-visibility switch.
755
752
  pattern: /\bcontent\.preview\b/,
756
753
  message: "content.preview configuration found.",
757
- suggestion: "content.preview only gates draft/partial visibility for the filesystem provider; it always applies in development. In production it unlocks drafts already present in the deployed content directory \u2014 it is not a substitute for a provider-owned preview workflow that fetches unpublished content.",
754
+ suggestion: "content.preview protects filesystem preview-storage overlays. Ordinary drafts are visible in development, partials remain structural non-routes, and authenticated filesystem preview is unsupported in production; use provider-owned preview there.",
758
755
  severity: "info"
759
756
  },
760
757
  {
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * @lupinum/ginko-content/cms-contract
3
3
  *
4
- * Pure, runtime-safe surface that ginko-cms consumes. This subpath contains
4
+ * Pure, runtime-safe surface for CMS/provider consumers. This subpath contains
5
5
  * NO Node, Nuxt, h3, nitropack, filesystem, or @nuxt/kit dependencies. Every
6
- * module re-exported from here MUST be importable from a V8 isolate (Convex
7
- * component, Cloudflare Workers, browser).
6
+ * module re-exported from here MUST be importable from a V8 isolate, worker,
7
+ * or browser.
8
8
  *
9
- * The CMS uses this surface to:
9
+ * Consumers use this surface to:
10
10
  * - normalize a host's `content.config.ts` into the one portable
11
11
  * `ResolvedContentContractV1` artifact (`buildResolvedContentContract`),
12
12
  * - produce RFC 8785 canonical JSON and incremental SHA-256 hashes without
@@ -18,12 +18,12 @@
18
18
  * - parse MDC into the same AST the filesystem provider uses (`parseMdcBody`).
19
19
  *
20
20
  * If a future change introduces a Node/Nuxt dependency anywhere in this
21
- * tree, the CMS Convex build will break loudly. That's intentional.
21
+ * tree, isolate builds will break loudly. That's intentional.
22
22
  */
23
23
  export { RESOLVED_CONTENT_CONTRACT_VERSION, buildResolvedContentContract, type BuildResolvedContentContractInput, type BuildResolvedContentContractOptions, } from './build.js';
24
24
  export type { PortableComponentPolicyV1, PortableMediaType, ResolvedContentCollectionV1, ResolvedContentContractV1, ResolvedContentFieldTypeV1, ResolvedContentFieldV1, ResolvedContentValidationV1, ContentCmsCollectionConfig, ContentCmsFieldConfig, ContentCmsFieldType, ContentCmsRelationConfig, ContentCollectionConfig, ContentCollectionI18nConfig, } from './types.js';
25
- export { describeId, generatePath, generateCanonicalKey, generateTitle, isDraftPath, isPartialPath, longestMountForPath, mountContentPath, normalizeContentPath, normalizeRouteMounts, prefixPathWithLocale, refineUrlPart, routeRemainder, routeToContentPathCandidates, slugifyUrlSegment, stripLocalePrefix, } from './path.js';
26
- export { CONTENT_FIELD_METADATA_KEY, CONTENT_REFERENCE_PREFIX, collectTopLevelReferenceFields, getContentFieldMetadata, getObjectShape, getReferenceDescriptor, getSchemaDef, getSchemaTypeName, unwrapSchema, type ContentFieldMetadata, type ContentFieldSchema, } from './schema.js';
25
+ export { describeId, generatePath, generateCanonicalKey, generateTitle, isDraftPath, isPartialPath, mountContentPath, normalizeContentPath, normalizeRouteMounts, prefixPathWithLocale, refineUrlPart, routeRemainder, routeToContentPathCandidates, slugifyUrlSegment, stripLocalePrefix, } from './path.js';
26
+ export { CONTENT_FIELD_METADATA_KEY, CONTENT_REFERENCE_METADATA_KEY, collectTopLevelReferenceFields, getContentFieldMetadata, getObjectShape, getReferenceDescriptor, getSchemaDef, getSchemaTypeName, unwrapSchema, type ContentFieldMetadata, type ContentFieldSchema, } from './schema.js';
27
27
  export { parseMdcBody, type ParseMdcBodyOptions, type ParseMdcBodyResult, } from './mdc.js';
28
28
  export { assertPublicMarkdownAst, isSafePublicMarkdownUrl, PublicMarkdownValidationError, validatePublicMarkdownAst, type PublicMarkdownIssue, type PublicMarkdownIssueCode, type PublicMarkdownValidationResult, } from './render-policy.js';
29
29
  export { verifyPublicImageBytes, type VerifiedPublicImage, } from './asset-bytes.js';
@@ -9,7 +9,6 @@ export {
9
9
  generateTitle,
10
10
  isDraftPath,
11
11
  isPartialPath,
12
- longestMountForPath,
13
12
  mountContentPath,
14
13
  normalizeContentPath,
15
14
  normalizeRouteMounts,
@@ -22,7 +21,7 @@ export {
22
21
  } from "./path.js";
23
22
  export {
24
23
  CONTENT_FIELD_METADATA_KEY,
25
- CONTENT_REFERENCE_PREFIX,
24
+ CONTENT_REFERENCE_METADATA_KEY,
26
25
  collectTopLevelReferenceFields,
27
26
  getContentFieldMetadata,
28
27
  getObjectShape,
@@ -1,13 +1,7 @@
1
1
  /**
2
- * parseMdcBody - single canonical MDC parser entry point for the CMS.
3
- *
4
- * The CMS used to fake a parsed MDC body at read-time by wrapping the raw
5
- * markdown string in a paragraph element and regexing headings. That broke
6
- * MDC components, links, tables, and any non-trivial markdown.
7
- *
8
- * Per Gate 0: parse once at publish time using ginko-content's real parser,
9
- * store the AST on `publicEntries`, and have the public provider return the
10
- * stored AST verbatim. The CMS imports this function from the pure subpath.
2
+ * Single canonical MDC parser entry point for external CMS integrations.
3
+ * Parse at publish time, persist the resulting AST, and return that AST from
4
+ * the public provider instead of maintaining a second markdown parser.
11
5
  */
12
6
  import type { MarkdownRoot, Toc } from '../types/content.js';
13
7
  export interface ParseMdcBodyOptions {
@@ -1,9 +1,11 @@
1
- import { parse } from "comark";
1
+ import { normalizeComarkNodes } from "../core/markdown/normalize-comark.js";
2
+ import { parseComark } from "../core/markdown/parse-comark.js";
2
3
  import { mapMarkdownNodes, toMarkdownRoot } from "../core/markdown/tree.js";
3
4
  export async function parseMdcBody(raw, options = {}) {
4
- const tree = await parse(raw ?? "");
5
- const toc = deriveToc(tree.nodes, options);
6
- const body = toMarkdownRoot(tree.nodes, toc);
5
+ const tree = await parseComark(raw ?? "");
6
+ const nodes = normalizeComarkNodes(tree.nodes);
7
+ const toc = deriveToc(nodes, options);
8
+ const body = toMarkdownRoot(nodes, toc);
7
9
  const searchText = renderPlainText(body);
8
10
  return { body, toc, searchText };
9
11
  }
@@ -1,12 +1,10 @@
1
1
  /**
2
2
  * Pure path helpers re-exported from `ginko-content`'s canonical path engine.
3
3
  *
4
- * The CMS uses these instead of duplicating its own path-generation logic.
5
- * Per Gate 0 of the refactor: ginko-content owns path semantics; ginko-cms
6
- * imports them. No more parallel implementations of `generatePath` /
7
- * `generateCanonicalKey` / `slugifyUrlSegment`.
4
+ * External CMS integrations use these instead of duplicating path-generation
5
+ * logic. Ginko Content remains the sole owner of public path semantics.
8
6
  *
9
7
  * All re-exports MUST be runtime-pure (no Node, no Nuxt, no h3). They are.
10
8
  */
11
- export { describeId, generatePath, generateCanonicalKey, generateTitle, isDraftPath, isPartialPath, normalizeContentPath, normalizeRouteMounts, longestMountForPath, routeRemainder, mountContentPath, prefixPathWithLocale, stripLocalePrefix, refineUrlPart, routeToContentPathCandidates, } from '../core/content/path.js';
9
+ export { describeId, generatePath, generateCanonicalKey, generateTitle, isDraftPath, isPartialPath, normalizeContentPath, normalizeRouteMounts, routeRemainder, mountContentPath, prefixPathWithLocale, stripLocalePrefix, refineUrlPart, routeToContentPathCandidates, } from '../core/content/path.js';
12
10
  export { slugifyUrlSegment } from '../core/content/slug.js';
@@ -7,7 +7,6 @@ export {
7
7
  isPartialPath,
8
8
  normalizeContentPath,
9
9
  normalizeRouteMounts,
10
- longestMountForPath,
11
10
  routeRemainder,
12
11
  mountContentPath,
13
12
  prefixPathWithLocale,
@@ -226,6 +226,10 @@ export function validatePublicMarkdownAst(value, policy = { components: {} }) {
226
226
  continue;
227
227
  }
228
228
  if (name === "style" && tag === "span" && isSafeShikiStyle(propValue)) continue;
229
+ if (tag === "blockquote" && name === "data-alert") {
230
+ if (typeof propValue !== "string" || !["note", "tip", "important", "warning", "caution"].includes(propValue)) report("invalid_prop_value", propPath, "Blockquote alert metadata is invalid.");
231
+ continue;
232
+ }
229
233
  if (!name || /^on/i.test(name) || /^v-|^@|^:|^#/.test(name) || FORBIDDEN_PROPS.has(lower)) {
230
234
  report("unsafe_prop", propPath, `Property "${name}" is not render-safe.`);
231
235
  continue;
@@ -15,4 +15,4 @@ export { getSchemaDef, getSchemaTypeName, unwrapSchema, getObjectShape, getRefer
15
15
  */
16
16
  export { CONTENT_FIELD_METADATA_KEY, type ContentFieldMetadata, type ContentFieldSchema, } from '../types/fields.js';
17
17
  export { getContentFieldMetadata } from '../types/fields.js';
18
- export { CONTENT_REFERENCE_PREFIX } from '../types/reference.js';
18
+ export { CONTENT_REFERENCE_METADATA_KEY, } from '../types/reference.js';
@@ -10,4 +10,6 @@ export {
10
10
  CONTENT_FIELD_METADATA_KEY
11
11
  } from "../types/fields.js";
12
12
  export { getContentFieldMetadata } from "../types/fields.js";
13
- export { CONTENT_REFERENCE_PREFIX } from "../types/reference.js";
13
+ export {
14
+ CONTENT_REFERENCE_METADATA_KEY
15
+ } from "../types/reference.js";
@@ -14,7 +14,7 @@
14
14
  * Canonical keys are only unique within a collection.
15
15
  * - **`byCanonical`**: a derived convenience index containing only keys
16
16
  * that occur in one collection. Ambiguous unscoped lookups fail closed.
17
- * - **`byRoute`**: route path (user-visible URL) content id. Used by the
17
+ * - **`byRoute`**: locale plus route path → canonical key. Used by the
18
18
  * page resolver when the request arrives as a localized URL.
19
19
  * - **`byRef`**: normalized ref string → canonical key. This is how
20
20
  * markdown links such as `[Ada]($authors.ada)` find their target without a scan.
@@ -28,8 +28,8 @@
28
28
  */
29
29
  import type { ParsedContent } from '../../types/content';
30
30
  import type { ContentLocaleEntry } from '../../types/query';
31
- import type { ContentManifest, ManifestVariant, ResolvedVariant } from '../../types/runtime';
32
- export interface ContentGraphVariant extends ManifestVariant {
31
+ import type { ContentVariantIdentity, ResolvedVariant } from '../../types/runtime';
32
+ export interface ContentGraphVariant extends ContentVariantIdentity {
33
33
  document: ParsedContent;
34
34
  }
35
35
  export interface ContentGraph {
@@ -44,7 +44,9 @@ export interface ContentGraph {
44
44
  byCollectionCanonical: Record<string, Record<string, Record<string, ContentGraphVariant>>>;
45
45
  /** Unambiguous canonical keys only. Derived from `byCollectionCanonical`. */
46
46
  byCanonical: Record<string, Record<string, ContentGraphVariant>>;
47
- /** route pathcontent id. Used by page resolution. */
47
+ /** Exact collectionref canonical key alias index. */
48
+ byCollectionRef: Record<string, Record<string, string>>;
49
+ /** `"<locale>:<route path>"` → canonical key. Used by page resolution. */
48
50
  byRoute: Record<string, string>;
49
51
  /** normalized ref string → canonical key. */
50
52
  byRef: Record<string, string>;
@@ -54,25 +56,7 @@ export interface ContentGraph {
54
56
  referenceTargets: Map<string, string>;
55
57
  /** Exact collection-scoped user-writable reference targets. */
56
58
  referenceTargetsByCollection: Record<string, Map<string, string>>;
57
- manifest: ContentManifest;
58
59
  }
59
- /**
60
- * Build the locale resolution chain for a request.
61
- *
62
- * Order, from highest to lowest priority:
63
- * 1. The explicitly requested locale (if any).
64
- * 2. Author-configured fallbacks for that locale.
65
- * 3. The site-wide default locale.
66
- *
67
- * Duplicates are removed while preserving first-occurrence order, so the
68
- * caller can walk the chain and stop at the first hit without worrying
69
- * about re-visiting the default.
70
- *
71
- * @example
72
- * resolveLocaleChain('fr', 'en', { fr: ['de'] }) // ['fr', 'de', 'en']
73
- * resolveLocaleChain(undefined, 'en') // ['en']
74
- */
75
- export declare const resolveLocaleChain: (requestedLocale: string | undefined, defaultLocale?: string, fallback?: Record<string, string[]>) => string[];
76
60
  /**
77
61
  * Build a `ContentGraph` from a flat list of parsed documents.
78
62
  *
@@ -88,6 +72,7 @@ export declare const resolveLocaleChain: (requestedLocale: string | undefined, d
88
72
  export declare const buildContentGraph: (documents: ParsedContent[], options?: {
89
73
  locales?: string[];
90
74
  defaultLocale?: string;
75
+ referencePathAliases?: (document: ParsedContent) => readonly string[];
91
76
  }) => ContentGraph;
92
77
  /** Resolve the exact collection-scoped variant set, or an unambiguous one. */
93
78
  export declare const getGraphCanonicalVariants: (graph: ContentGraph, canonicalKey: string, collection?: string) => Record<string, ContentGraphVariant> | undefined;
@@ -1,34 +1,21 @@
1
1
  import { isNavigationFile } from "./structural.js";
2
2
  import { normalizeReferenceValue, buildReferenceTargets } from "../references/resolve.js";
3
- import { sortLocalesCanonically } from "./locale.js";
3
+ import { resolveLocaleChain, sortLocalesCanonically } from "./locale.js";
4
4
  const normalizePath = (path) => {
5
5
  if (!path || path === "/") {
6
6
  return "/";
7
7
  }
8
8
  return path.startsWith("/") ? path.endsWith("/") ? path.slice(0, -1) || "/" : path : `/${path.replace(/\/+$/, "")}`;
9
9
  };
10
- const emptyManifest = () => ({
11
- byCollectionCanonical: {},
12
- byCanonical: {},
13
- byCollectionRef: {},
14
- byRef: {},
15
- byRoute: {},
16
- paths: {},
17
- collections: {}
18
- });
19
- export const resolveLocaleChain = (requestedLocale, defaultLocale, fallback = {}) => {
20
- const chain = [
21
- ...requestedLocale ? [requestedLocale] : [],
22
- ...requestedLocale && fallback[requestedLocale] || [],
23
- ...defaultLocale ? [defaultLocale] : []
24
- ].filter(Boolean);
25
- return Array.from(new Set(chain));
26
- };
27
10
  export const buildContentGraph = (documents, options = {}) => {
28
- const manifest = emptyManifest();
29
11
  const byId = {};
12
+ const byCollection = {};
13
+ const byPath = {};
30
14
  const byCollectionCanonical = {};
31
15
  const byCanonical = {};
16
+ const byCollectionRef = {};
17
+ const byRef = {};
18
+ const byRoute = {};
32
19
  const byNavigationPath = {};
33
20
  const defaultLocale = options.defaultLocale || "";
34
21
  for (const document of documents) {
@@ -38,15 +25,15 @@ export const buildContentGraph = (documents, options = {}) => {
38
25
  continue;
39
26
  }
40
27
  const path = normalizePath(document.path);
41
- manifest.paths[path] ||= [];
28
+ byPath[path] ||= [];
42
29
  if (document.locale === defaultLocale) {
43
- manifest.paths[path].unshift(documentId);
30
+ byPath[path].unshift(documentId);
44
31
  } else {
45
- manifest.paths[path].push(documentId);
32
+ byPath[path].push(documentId);
46
33
  }
47
34
  if (document.collection) {
48
- manifest.collections[document.collection] ||= [];
49
- manifest.collections[document.collection].push(documentId);
35
+ byCollection[document.collection] ||= [];
36
+ byCollection[document.collection].push(documentId);
50
37
  }
51
38
  if (isNavigationFile(document)) {
52
39
  const locale2 = document.locale || defaultLocale;
@@ -66,45 +53,43 @@ export const buildContentGraph = (documents, options = {}) => {
66
53
  path,
67
54
  document
68
55
  };
69
- manifest.byCollectionCanonical[collection] ||= {};
70
- manifest.byCollectionCanonical[collection][document.canonicalKey] ||= {};
71
- manifest.byCollectionCanonical[collection][document.canonicalKey][locale] = variant;
72
- manifest.byRoute[`${locale}:${path}`] = document.canonicalKey;
56
+ byCollectionCanonical[collection] ||= {};
57
+ byCollectionCanonical[collection][document.canonicalKey] ||= {};
58
+ byCollectionCanonical[collection][document.canonicalKey][locale] = variant;
59
+ byRoute[`${locale}:${path}`] = document.canonicalKey;
73
60
  if (document.type === "markdown" && typeof document.ref === "string" && document.ref.length) {
74
- manifest.byCollectionRef[collection] ||= {};
75
- manifest.byCollectionRef[collection][document.ref] = document.canonicalKey;
61
+ byCollectionRef[collection] ||= {};
62
+ byCollectionRef[collection][document.ref] = document.canonicalKey;
76
63
  }
77
64
  }
78
65
  const collectionsByCanonical = /* @__PURE__ */ new Map();
79
- for (const [collection, canonicalEntries] of Object.entries(manifest.byCollectionCanonical)) {
80
- byCollectionCanonical[collection] = {};
81
- for (const [canonicalKey, variants] of Object.entries(canonicalEntries)) {
82
- const graphVariants = Object.fromEntries(
83
- Object.entries(variants).map(([locale, variant]) => [locale, { ...variant, document: byId[variant.contentId] }])
84
- );
85
- byCollectionCanonical[collection][canonicalKey] = graphVariants;
66
+ for (const [collection, canonicalEntries] of Object.entries(byCollectionCanonical)) {
67
+ for (const canonicalKey of Object.keys(canonicalEntries)) {
86
68
  collectionsByCanonical.set(canonicalKey, [...collectionsByCanonical.get(canonicalKey) || [], collection]);
87
69
  }
88
70
  }
89
71
  for (const [canonicalKey, collections] of collectionsByCanonical) {
90
72
  if (collections.length !== 1) continue;
91
73
  const collection = collections[0];
92
- manifest.byCanonical[canonicalKey] = manifest.byCollectionCanonical[collection][canonicalKey];
93
74
  byCanonical[canonicalKey] = byCollectionCanonical[collection][canonicalKey];
94
75
  }
95
76
  const collectionsByRef = /* @__PURE__ */ new Map();
96
- for (const [collection, refs] of Object.entries(manifest.byCollectionRef)) {
77
+ for (const [collection, refs] of Object.entries(byCollectionRef)) {
97
78
  for (const ref of Object.keys(refs)) {
98
79
  collectionsByRef.set(ref, [...collectionsByRef.get(ref) || [], collection]);
99
80
  }
100
81
  }
101
82
  for (const [ref, collections] of collectionsByRef) {
102
- if (collections.length === 1) manifest.byRef[ref] = manifest.byCollectionRef[collections[0]][ref];
83
+ if (collections.length === 1) byRef[ref] = byCollectionRef[collections[0]][ref];
103
84
  }
104
85
  const referenceTargetsByCollection = Object.fromEntries(
105
- Object.keys(manifest.byCollectionCanonical).map((collection) => [
86
+ Object.keys(byCollectionCanonical).map((collection) => [
106
87
  collection,
107
- buildReferenceTargets(documents.filter((document) => (document.collection || "content") === collection && !document.partial && !isNavigationFile(document)), options.locales || [])
88
+ buildReferenceTargets(
89
+ documents.filter((document) => (document.collection || "content") === collection && !document.partial && !isNavigationFile(document)),
90
+ options.locales || [],
91
+ options.referencePathAliases
92
+ )
108
93
  ])
109
94
  );
110
95
  const referenceTargetCollections = /* @__PURE__ */ new Map();
@@ -120,16 +105,16 @@ export const buildContentGraph = (documents, options = {}) => {
120
105
  return {
121
106
  documents,
122
107
  byId,
123
- byCollection: manifest.collections,
124
- byPath: manifest.paths,
108
+ byCollection,
109
+ byPath,
125
110
  byCollectionCanonical,
126
111
  byCanonical,
127
- byRoute: manifest.byRoute,
128
- byRef: manifest.byRef,
112
+ byCollectionRef,
113
+ byRoute,
114
+ byRef,
129
115
  byNavigationPath,
130
116
  referenceTargets,
131
- referenceTargetsByCollection,
132
- manifest
117
+ referenceTargetsByCollection
133
118
  };
134
119
  };
135
120
  export const getGraphCanonicalVariants = (graph, canonicalKey, collection) => {
@@ -145,8 +130,9 @@ export const resolveGraphVariant = (graph, canonicalKey, requestedLocale, option
145
130
  return null;
146
131
  }
147
132
  const availableLocales = sortLocalesCanonically(Object.keys(variants), options);
148
- const localeChain = options.exact ? requestedLocale ? [requestedLocale] : [] : options.fallback?.length ? Array.from(new Set([requestedLocale, ...options.fallback].filter(Boolean))) : resolveLocaleChain(requestedLocale, options.defaultLocale, options.localeFallback || {});
149
- const resolvedLocale = localeChain.find((locale) => variants[locale]) || (options.exact ? void 0 : availableLocales[0]);
133
+ const hasExplicitFallback = options.fallback !== void 0;
134
+ const localeChain = options.exact ? requestedLocale ? [requestedLocale] : [] : hasExplicitFallback ? Array.from(new Set([requestedLocale, ...options.fallback || []].filter(Boolean))) : resolveLocaleChain(requestedLocale, options.defaultLocale, options.localeFallback || {});
135
+ const resolvedLocale = localeChain.find((locale) => variants[locale]) || (options.exact || hasExplicitFallback ? void 0 : availableLocales[0]);
150
136
  if (resolvedLocale === void 0) {
151
137
  return null;
152
138
  }
@@ -164,7 +150,7 @@ export const resolveGraphVariant = (graph, canonicalKey, requestedLocale, option
164
150
  };
165
151
  export const resolveGraphRouteVariant = (graph, routePath, requestedLocale, options = {}) => {
166
152
  const normalizedPath = normalizePath(routePath);
167
- const localeChain = options.exact ? requestedLocale ? [requestedLocale] : [] : options.fallback?.length ? Array.from(new Set([requestedLocale, ...options.fallback].filter(Boolean))) : resolveLocaleChain(requestedLocale, options.defaultLocale, options.localeFallback || {});
153
+ const localeChain = options.exact ? requestedLocale ? [requestedLocale] : [] : options.fallback !== void 0 ? Array.from(new Set([requestedLocale, ...options.fallback || []].filter(Boolean))) : resolveLocaleChain(requestedLocale, options.defaultLocale, options.localeFallback || {});
168
154
  const localesToSearch = localeChain.length ? localeChain : sortLocalesCanonically(getGraphVariantLocales(graph, options.collection), options);
169
155
  for (const locale of localesToSearch) {
170
156
  const canonicalKey = graph.byRoute[`${locale}:${normalizedPath}`];
@@ -187,7 +173,7 @@ export const resolveGraphCanonicalKey = (graph, identity, collection) => {
187
173
  if (hasCollectionVariant(normalizedIdentity)) {
188
174
  return normalizedIdentity;
189
175
  }
190
- const refCanonicalKey = collection ? graph.manifest.byCollectionRef[collection]?.[normalizedIdentity] : graph.byRef[normalizedIdentity];
176
+ const refCanonicalKey = collection ? graph.byCollectionRef[collection]?.[normalizedIdentity] : graph.byRef[normalizedIdentity];
191
177
  if (refCanonicalKey && hasCollectionVariant(refCanonicalKey)) {
192
178
  return refCanonicalKey;
193
179
  }
@@ -23,10 +23,14 @@ import type { ContentCollectionI18nConfig } from '../../types/config';
23
23
  /** Separator between source id and locale in inline-variant ids. */
24
24
  export declare const INLINE_LOCALE_ID_SEPARATOR = "#__locale=";
25
25
  /**
26
- * Produce the fallback chain for a specific locale (excluding the locale
27
- * itself). The caller prepends the requested locale if it wants a
28
- * walkable chain — this helper is what to look at *after* the requested
29
- * locale missed.
26
+ * Build the locale resolution chain for a request. The requested locale,
27
+ * configured fallbacks, and default locale are ordered by priority and
28
+ * deduplicated without reordering.
29
+ */
30
+ export declare const resolveLocaleChain: (requestedLocale: string | undefined, defaultLocale?: string, fallback?: Readonly<Record<string, readonly string[]>>) => string[];
31
+ /**
32
+ * Produce the fallback portion of the canonical locale chain, excluding the
33
+ * requested locale itself.
30
34
  *
31
35
  * @example
32
36
  * buildLocaleFallbackChain('fr', 'en', { fr: ['de'] }) // ['de', 'en']
@@ -64,3 +68,5 @@ export declare const splitInlineLocaleVariantId: (id: string) => {
64
68
  * override should not fail ingest for the whole document.
65
69
  */
66
70
  export declare const expandDataLocaleVariants: (document: ParsedContent, i18nConfig?: ContentCollectionI18nConfig) => ParsedContent[];
71
+ /** Physical locale identity used when content has no configured locale authority. */
72
+ export declare const DEFAULT_CONTENT_LOCALE = "en";