@lupinum/ginko-content 0.1.2 → 0.1.3

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 (189) hide show
  1. package/README.md +18 -4
  2. package/compatibility.json +1 -1
  3. package/dist/cli.mjs +317 -294
  4. package/dist/cms-import/index.js +5 -1
  5. package/dist/config.d.mts +1 -1
  6. package/dist/config.mjs +1 -1
  7. package/dist/core/cache-hints.d.ts +9 -1
  8. package/dist/core/content/collection.js +1 -1
  9. package/dist/core/content/path.js +1 -1
  10. package/dist/core/errors.js +5 -1
  11. package/dist/core/provider-errors +1 -0
  12. package/dist/core/provider-errors.d.ts +10 -0
  13. package/dist/core/provider-errors.js +27 -0
  14. package/dist/core/query/execute.js +12 -3
  15. package/dist/core/query/filter.js +2 -2
  16. package/dist/core/query/lower.js +5 -4
  17. package/dist/features/navigation/build.js +14 -6
  18. package/dist/features/navigation/canonical.js +1 -1
  19. package/dist/features/sitemap/query.js +2 -2
  20. package/dist/integrations/nitro/ingest.d.ts +3 -3
  21. package/dist/integrations/nitro/ingest.js +3 -3
  22. package/dist/integrations/nitro/storage.js +3 -1
  23. package/dist/module.d.mts +151 -526
  24. package/dist/module.json +1 -1
  25. package/dist/module.mjs +1091 -722
  26. package/dist/parsers/csv.js +4 -3
  27. package/dist/parsers/index.d.ts +2 -5
  28. package/dist/parsers/index.js +2 -2
  29. package/dist/parsers/markdown-plugins.d.ts +1 -0
  30. package/dist/parsers/markdown-plugins.js +83 -1
  31. package/dist/parsers/markdown.js +1 -1
  32. package/dist/public/client.d.ts +3 -2
  33. package/dist/public/client.js +2 -2
  34. package/dist/public/provider-errors.d.ts +2 -1
  35. package/dist/public/provider-errors.js +1 -18
  36. package/dist/public/provider.d.ts +3 -10
  37. package/dist/public/server.d.ts +6 -1
  38. package/dist/public/server.js +33 -0
  39. package/dist/runtime/agent-paths +1 -0
  40. package/dist/runtime/agent-paths.d.ts +6 -0
  41. package/dist/runtime/agent-paths.js +41 -0
  42. package/dist/runtime/app/components/internal/MarkdownRenderer.d.ts +1 -1
  43. package/dist/runtime/app/composables/head.d.ts +3 -0
  44. package/dist/runtime/app/composables/head.js +2 -1
  45. package/dist/runtime/app/composables/hot-reload +1 -0
  46. package/dist/runtime/app/composables/hot-reload.d.ts +5 -0
  47. package/dist/runtime/app/composables/hot-reload.js +15 -0
  48. package/dist/runtime/app/composables/locale-context.d.ts +1 -1
  49. package/dist/runtime/app/composables/locale-context.js +2 -1
  50. package/dist/runtime/app/composables/locale.js +2 -2
  51. package/dist/runtime/app/composables/search.js +10 -6
  52. package/dist/runtime/app/composables/use-content-document +1 -0
  53. package/dist/runtime/app/composables/use-content-document.d.ts +29 -0
  54. package/dist/runtime/app/composables/use-content-document.js +44 -0
  55. package/dist/runtime/app/composables/use-content-list +1 -0
  56. package/dist/runtime/app/composables/use-content-list.d.ts +43 -0
  57. package/dist/runtime/app/composables/use-content-list.js +98 -0
  58. package/dist/runtime/app/composables/use-content-navigation +1 -0
  59. package/dist/runtime/app/composables/use-content-navigation.d.ts +40 -0
  60. package/dist/runtime/app/composables/use-content-navigation.js +102 -0
  61. package/dist/runtime/app/composables/use-content-page +1 -0
  62. package/dist/runtime/app/composables/use-content-page.d.ts +47 -0
  63. package/dist/runtime/app/composables/use-content-page.js +142 -0
  64. package/dist/runtime/app/composables/use-content-shared +1 -0
  65. package/dist/runtime/app/composables/use-content-shared.d.ts +13 -0
  66. package/dist/runtime/app/composables/use-content-shared.js +29 -0
  67. package/dist/runtime/app/composables/use-content.d.ts +11 -206
  68. package/dist/runtime/app/composables/use-content.js +4 -377
  69. package/dist/runtime/app/plugins/hot-reload +1 -0
  70. package/dist/runtime/app/plugins/hot-reload.js +6 -0
  71. package/dist/runtime/query/backlinks +1 -0
  72. package/dist/runtime/query/backlinks.d.ts +8 -0
  73. package/dist/runtime/query/backlinks.js +90 -0
  74. package/dist/runtime/query/context +1 -0
  75. package/dist/runtime/query/context.d.ts +20 -0
  76. package/dist/runtime/query/context.js +0 -0
  77. package/dist/runtime/query/documents +1 -0
  78. package/dist/runtime/query/documents.d.ts +6 -0
  79. package/dist/runtime/query/documents.js +98 -0
  80. package/dist/runtime/query/errors +1 -0
  81. package/dist/runtime/query/errors.d.ts +1 -0
  82. package/dist/runtime/query/errors.js +4 -0
  83. package/dist/runtime/query/handles +1 -0
  84. package/dist/runtime/query/handles.d.ts +2 -0
  85. package/dist/runtime/query/handles.js +7 -0
  86. package/dist/runtime/query/locale-options +1 -0
  87. package/dist/runtime/query/locale-options.d.ts +5 -0
  88. package/dist/runtime/query/locale-options.js +14 -0
  89. package/dist/runtime/query/localized-docs +1 -0
  90. package/dist/runtime/query/localized-docs.d.ts +17 -0
  91. package/dist/runtime/query/localized-docs.js +44 -0
  92. package/dist/runtime/query/navigation +1 -0
  93. package/dist/runtime/query/navigation.d.ts +22 -0
  94. package/dist/runtime/query/navigation.js +73 -0
  95. package/dist/runtime/query/pagination +1 -0
  96. package/dist/runtime/query/pagination.d.ts +7 -0
  97. package/dist/runtime/query/pagination.js +78 -0
  98. package/dist/runtime/query/populate +1 -0
  99. package/dist/runtime/query/populate.d.ts +9 -0
  100. package/dist/runtime/query/populate.js +106 -0
  101. package/dist/runtime/query/responses +1 -0
  102. package/dist/runtime/query/responses.d.ts +10 -0
  103. package/dist/runtime/query/responses.js +47 -0
  104. package/dist/runtime/query/unified.d.ts +4 -20
  105. package/dist/runtime/query/unified.js +14 -544
  106. package/dist/runtime/query/variants +1 -0
  107. package/dist/runtime/query/variants.d.ts +11 -0
  108. package/dist/runtime/query/variants.js +36 -0
  109. package/dist/runtime/server/agent-http +1 -0
  110. package/dist/runtime/server/agent-http.d.ts +7 -0
  111. package/dist/runtime/server/agent-http.js +36 -0
  112. package/dist/runtime/server/agent-markdown +1 -0
  113. package/dist/runtime/server/agent-markdown.d.ts +83 -0
  114. package/dist/runtime/server/agent-markdown.js +449 -0
  115. package/dist/runtime/server/agent-site +1 -0
  116. package/dist/runtime/server/agent-site.d.ts +38 -0
  117. package/dist/runtime/server/agent-site.js +273 -0
  118. package/dist/runtime/server/api/agent-llms +1 -0
  119. package/dist/runtime/server/api/agent-llms-full +1 -0
  120. package/dist/runtime/server/api/agent-llms-full.d.ts +2 -0
  121. package/dist/runtime/server/api/agent-llms-full.js +13 -0
  122. package/dist/runtime/server/api/agent-llms.d.ts +2 -0
  123. package/dist/runtime/server/api/agent-llms.js +14 -0
  124. package/dist/runtime/server/api/agent-raw +1 -0
  125. package/dist/runtime/server/api/agent-raw.d.ts +2 -0
  126. package/dist/runtime/server/api/agent-raw.js +17 -0
  127. package/dist/runtime/server/api/locales.d.ts +2 -2
  128. package/dist/runtime/server/api/locales.js +4 -2
  129. package/dist/runtime/server/api/navigation.d.ts +1 -1
  130. package/dist/runtime/server/api/query.d.ts +1 -1
  131. package/dist/runtime/server/api/query.js +4 -1
  132. package/dist/runtime/server/api/revalidate.js +2 -2
  133. package/dist/runtime/server/api/site-data.d.ts +1 -1
  134. package/dist/runtime/server/collection-helpers.js +2 -1
  135. package/dist/runtime/server/middleware/agent-link-headers +1 -0
  136. package/dist/runtime/server/middleware/agent-link-headers.d.ts +2 -0
  137. package/dist/runtime/server/middleware/agent-link-headers.js +42 -0
  138. package/dist/runtime/server/middleware/agent-markdown +1 -0
  139. package/dist/runtime/server/middleware/agent-markdown.d.ts +2 -0
  140. package/dist/runtime/server/middleware/agent-markdown.js +43 -0
  141. package/dist/runtime/server/plugins/sitemap.js +1 -0
  142. package/dist/runtime/server/provider-query.d.ts +8 -5
  143. package/dist/runtime/server/provider-query.js +50 -8
  144. package/dist/runtime/server/provider-result.d.ts +19 -2
  145. package/dist/runtime/server/providers/filesystem.js +4 -1
  146. package/dist/runtime/server/providers/index.d.ts +3 -1
  147. package/dist/runtime/server/providers/index.js +2 -2
  148. package/dist/runtime/server/query-api.js +2 -1
  149. package/dist/runtime/server/query-executor.d.ts +3 -2
  150. package/dist/runtime/server/query-executor.js +13 -5
  151. package/dist/runtime/server/search.js +1 -1
  152. package/dist/runtime/server/sitemap-provider.js +40 -1
  153. package/dist/runtime/server/sitemap.js +3 -2
  154. package/dist/runtime/transformers/component-resolver.js +2 -1
  155. package/dist/runtime/utils/content-components.d.ts +0 -5
  156. package/dist/storage/content.d.ts +2 -9
  157. package/dist/storage/references.d.ts +1 -8
  158. package/dist/storage/references.js +9 -4
  159. package/dist/testing/provider-contract.js +3 -4
  160. package/dist/testing/provider-fixture.d.ts +3 -1
  161. package/dist/testing/provider-fixture.js +27 -14
  162. package/dist/types/config.d.ts +77 -0
  163. package/dist/types/config.js +23 -0
  164. package/dist/types/module.d.ts +15 -5
  165. package/dist/types/query-parts/collections +1 -0
  166. package/dist/types/query-parts/collections.d.ts +15 -0
  167. package/dist/types/query-parts/collections.js +0 -0
  168. package/dist/types/query-parts/public +1 -0
  169. package/dist/types/query-parts/public.d.ts +202 -0
  170. package/dist/types/query-parts/public.js +0 -0
  171. package/dist/types/query-parts/results +1 -0
  172. package/dist/types/query-parts/results.d.ts +95 -0
  173. package/dist/types/query-parts/results.js +0 -0
  174. package/dist/types/query-parts/transport +1 -0
  175. package/dist/types/query-parts/transport.d.ts +103 -0
  176. package/dist/types/query-parts/transport.js +0 -0
  177. package/dist/types/query.d.ts +4 -870
  178. package/dist/types/query.js +4 -0
  179. package/dist/types/runtime.d.ts +2 -2
  180. package/dist/types/virtual.d.ts +54 -0
  181. package/dist/types.d.mts +2 -2
  182. package/dist/web-types.json +1 -1
  183. package/package.json +5 -5
  184. package/dist/runtime/app/composables/web-socket +0 -1
  185. package/dist/runtime/app/composables/web-socket.d.ts +0 -3
  186. package/dist/runtime/app/composables/web-socket.js +0 -65
  187. package/dist/runtime/app/plugins/ws +0 -1
  188. package/dist/runtime/app/plugins/ws.js +0 -7
  189. /package/dist/runtime/app/plugins/{ws.d.ts → hot-reload.d.ts} +0 -0
@@ -4,14 +4,51 @@ import { normalizeContentQueryParams } from "../../core/query/params";
4
4
  import { normalizeI18nConfig, resolveRuntimeCollectionI18nConfig } from "../../features/localization/config";
5
5
  import { normalizeReferenceValue } from "../../core/references/resolve";
6
6
  import { getContentRuntimeConfig } from "./runtime-config.js";
7
+ import { createContentProviderError } from "../../public/provider-errors";
8
+ const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
9
+ const isProviderFindResponse = (response) => isObject(response) && Array.isArray(response.result) && typeof response.skip === "number" && typeof response.limit === "number" && typeof response.total === "number";
7
10
  export const normalizeProviderQueryResult = (response) => {
8
- if (Array.isArray(response)) {
11
+ if (isProviderFindResponse(response)) {
12
+ return response.result;
13
+ }
14
+ return [];
15
+ };
16
+ const isProviderFindOneResponse = (response) => isObject(response) && "result" in response && Object.keys(response).length === 1;
17
+ const isProviderCountResponse = (response) => isObject(response) && typeof response.result === "number" && Object.keys(response).length === 1;
18
+ const hasListEnvelopeKeys = (response) => ["skip", "limit", "total"].some((key) => key in response);
19
+ const describeProviderResponse = (response) => {
20
+ if (Array.isArray(response)) return "array";
21
+ if (response === null) return "null";
22
+ return typeof response;
23
+ };
24
+ const invalidProviderQueryResult = (params, message, response, providerName) => {
25
+ throw createContentProviderError("provider_result_invalid", message, {
26
+ collection: params.collection,
27
+ provider: providerName,
28
+ mode: params.count ? "count" : params.first ? "first" : "many",
29
+ responseType: describeProviderResponse(response)
30
+ });
31
+ };
32
+ export const normalizeProviderQueryResponse = (params, response, providerName) => {
33
+ if (params.count) {
34
+ if (isProviderCountResponse(response)) {
35
+ return response;
36
+ }
37
+ return invalidProviderQueryResult(params, "Provider count queries must return a count envelope: { result: number }.", response, providerName);
38
+ }
39
+ if (params.first) {
40
+ if (isProviderFindOneResponse(response)) {
41
+ return response;
42
+ }
43
+ return invalidProviderQueryResult(params, "Provider first queries must return a find-one envelope: { result: item | undefined }.", response, providerName);
44
+ }
45
+ if (isProviderFindResponse(response)) {
9
46
  return response;
10
47
  }
11
- if (response && typeof response === "object" && "result" in response) {
12
- return Array.isArray(response.result) ? response.result : [];
48
+ if (isObject(response) && hasListEnvelopeKeys(response)) {
49
+ return invalidProviderQueryResult(params, "Provider list query envelopes must include array result, skip, limit, and total.", response, providerName);
13
50
  }
14
- return [];
51
+ return invalidProviderQueryResult(params, "Provider list queries must return a list envelope: { result: item[], skip, limit, total }.", response, providerName);
15
52
  };
16
53
  const identityMatchesDocument = (document, identity) => {
17
54
  const normalizedIdentity = normalizeReferenceValue(identity);
@@ -45,13 +82,16 @@ export const resolveProviderContentVariants = async (event, identity, options) =
45
82
  const documents = localesToQuery.length ? (await Promise.all(
46
83
  localesToQuery.map(
47
84
  async (locale) => normalizeProviderQueryResult(
48
- await provider.query(event, {
85
+ normalizeProviderQueryResponse({
86
+ ...baseQuery,
87
+ resolveLocale: { locale, exact: true }
88
+ }, await provider.query(event, {
49
89
  ...baseQuery,
50
90
  resolveLocale: { locale, exact: true }
51
- })
91
+ }), provider.name)
52
92
  )
53
93
  )
54
- )).flat() : normalizeProviderQueryResult(await provider.query(event, baseQuery));
94
+ )).flat() : normalizeProviderQueryResult(normalizeProviderQueryResponse(baseQuery, await provider.query(event, baseQuery), provider.name));
55
95
  const matched = documents.find((document) => identityMatchesDocument(document, normalizedReference));
56
96
  const canonicalKey = matched?._canonicalKey;
57
97
  if (!canonicalKey) {
@@ -84,7 +124,9 @@ export const resolveProviderContentVariants = async (event, identity, options) =
84
124
  export const createServerProviderQueryFetch = (event) => {
85
125
  return async (query) => {
86
126
  const { getContentProvider } = await import("./providers/index.js");
87
- return await (await getContentProvider(event)).query(event, query.params());
127
+ const provider = await getContentProvider(event);
128
+ const response = await provider.query(event, query.params());
129
+ return normalizeProviderQueryResponse(query.params(), response, provider.name);
88
130
  };
89
131
  };
90
132
  const getServerCollectionI18n = (collection) => {
@@ -1,10 +1,27 @@
1
1
  import type { H3Event } from 'h3';
2
- import type { ContentProvider, ContentProviderResult, MaybeContentProviderResult } from '../../public/provider';
2
+ import type { ContentQueryResponse } from '../../types/api';
3
+ import type { NavItem, ParsedContent } from '../../types/content';
4
+ import type { ContentCollectionItemSurroundingsOptions, ContentCollectionNavigationOptions, ContentCollectionPageOptions, ContentCollectionRouteMetaOptions, ContentCollectionSearchSectionsOptions, ContentPageResult, ContentQueryBuilderParams, ContentRouteMeta, ContentSearchSection, ContentSitemapEntry } from '../../types/query';
5
+ import type { QueryCollectionsSitemapEntriesOptions } from '../../features/sitemap/query';
6
+ import type { ContentProvider, ContentProviderResult, ContentProviderSiteDataRequest, ContentProviderSiteDataResponse, MaybeContentProviderResult } from '../../public/provider';
7
+ import type { ContentProviderSearchRequest, ContentSearchResult } from '../../types/search';
3
8
  export declare const isContentProviderResult: <T = unknown>(value: unknown) => value is ContentProviderResult<T>;
4
9
  export declare const unwrapContentProviderResult: <T>(event: H3Event, result: MaybeContentProviderResult<T>) => T;
10
+ export interface RuntimeContentProvider extends Omit<ContentProvider, 'query' | 'navigationQuery' | 'navigation' | 'surroundings' | 'searchSections' | 'search' | 'siteData' | 'page' | 'routeMeta' | 'sitemapEntries'> {
11
+ query: <T = ParsedContent>(event: H3Event, query: ContentQueryBuilderParams) => Promise<ContentQueryResponse<T>>;
12
+ navigationQuery?: (event: H3Event, query: ContentQueryBuilderParams) => Promise<NavItem[]>;
13
+ navigation?: (event: H3Event, collection: string, options?: string[] | ContentCollectionNavigationOptions) => Promise<NavItem[]>;
14
+ surroundings?: (event: H3Event, collection: string, path: string, options?: ContentCollectionItemSurroundingsOptions) => Promise<Array<NavItem | null>>;
15
+ searchSections?: (event: H3Event, collection: string, options?: ContentCollectionSearchSectionsOptions) => Promise<ContentSearchSection[]>;
16
+ search?: (event: H3Event, request: ContentProviderSearchRequest) => Promise<ContentSearchResult[]>;
17
+ siteData?: <T = unknown>(event: H3Event, request: ContentProviderSiteDataRequest) => Promise<ContentProviderSiteDataResponse<T>>;
18
+ page?: <T = ParsedContent>(event: H3Event, collection: string, routeOrPath?: string, options?: ContentCollectionPageOptions) => Promise<ContentPageResult<T> | null>;
19
+ routeMeta?: (event: H3Event, collection: string, routeOrPath?: string, options?: ContentCollectionRouteMetaOptions) => Promise<ContentRouteMeta | null>;
20
+ sitemapEntries?: (event: H3Event, options?: QueryCollectionsSitemapEntriesOptions) => Promise<ContentSitemapEntry[]>;
21
+ }
5
22
  /**
6
23
  * External providers may return `{ data, cache }` from any provider method.
7
24
  * Runtime consumers should continue receiving the historical raw data shape,
8
25
  * while the request-local cache collector records the cache hint.
9
26
  */
10
- export declare const wrapContentProviderCacheResults: (event: H3Event, provider: ContentProvider) => ContentProvider;
27
+ export declare const wrapContentProviderCacheResults: (event: H3Event, provider: ContentProvider) => RuntimeContentProvider;
@@ -9,6 +9,7 @@ import {
9
9
  queryFilesystemCollectionSearchSections
10
10
  } from "../collection-helpers.js";
11
11
  import { queryFilesystemCollectionsSitemapEntries } from "../sitemap.js";
12
+ import { createContentProviderError } from "../../../public/provider-errors";
12
13
  export const filesystemProvider = {
13
14
  name: "filesystem",
14
15
  capabilities: {
@@ -33,7 +34,9 @@ export const filesystemProvider = {
33
34
  },
34
35
  query: (event, query) => {
35
36
  if (containsStandaloneRegexOptions(query.where)) {
36
- throw new TypeError("Query operator $options requires $regex.");
37
+ throw createContentProviderError("unsupported_query_shape", "Query operator $options requires $regex.", {
38
+ operator: "$options"
39
+ });
37
40
  }
38
41
  return executeFilesystemContentQuery(event, query);
39
42
  },
@@ -1,3 +1,5 @@
1
1
  import type { H3Event } from 'h3';
2
2
  import type { ContentProvider } from '../../../public/provider';
3
- export declare const getContentProvider: (event?: H3Event) => Promise<ContentProvider>;
3
+ import { type RuntimeContentProvider } from '../provider-result';
4
+ export declare function getContentProvider(): Promise<ContentProvider>;
5
+ export declare function getContentProvider(event: H3Event): Promise<RuntimeContentProvider>;
@@ -160,7 +160,7 @@ const validateContentProvider = (providerName, provider) => {
160
160
  }
161
161
  return provider;
162
162
  };
163
- export const getContentProvider = async (event) => {
163
+ export async function getContentProvider(event) {
164
164
  const runtime = event ? useRuntimeConfig(event) : useRuntimeConfig();
165
165
  const eventContent = event?.context?.contentRuntime;
166
166
  const provider = eventContent?.provider || runtime.content?.provider || runtime.public?.content?.provider || "filesystem";
@@ -187,4 +187,4 @@ export const getContentProvider = async (event) => {
187
187
  }
188
188
  }
189
189
  throw createContentProviderError("unknown_provider", `Unknown content provider: ${provider}`, { provider });
190
- };
190
+ }
@@ -11,6 +11,7 @@ import {
11
11
  import { getContentProvider } from "./providers/index.js";
12
12
  import { createContentProviderError } from "../../public/provider-errors";
13
13
  import { getContentRuntimeConfig } from "./runtime-config.js";
14
+ import { normalizeProviderQueryResponse } from "./provider-query.js";
14
15
  export const createServerContentQueryContext = async (event) => {
15
16
  const provider = await getContentProvider(event);
16
17
  return {
@@ -24,7 +25,7 @@ export const createServerContentQueryContext = async (event) => {
24
25
  }
25
26
  return await provider.navigationQuery(event, params);
26
27
  }
27
- return await provider.query(event, params);
28
+ return normalizeProviderQueryResponse(params, await provider.query(event, params), provider.name);
28
29
  }
29
30
  };
30
31
  };
@@ -1,4 +1,5 @@
1
1
  import { type H3Event } from 'h3';
2
+ import type { ContentQueryResponse } from '../../types/api';
2
3
  import type { ContentQueryBuilderParams } from '../../types/query';
3
- export declare const executeFilesystemContentQuery: <T = unknown>(event: H3Event, inputQuery: ContentQueryBuilderParams) => Promise<T | T[]>;
4
- export declare const executeContentQuery: <T = unknown>(event: H3Event, inputQuery: ContentQueryBuilderParams) => Promise<T | T[]>;
4
+ export declare const executeFilesystemContentQuery: <T = unknown>(event: H3Event, inputQuery: ContentQueryBuilderParams) => Promise<ContentQueryResponse<T>>;
5
+ export declare const executeContentQuery: <T = unknown>(event: H3Event, inputQuery: ContentQueryBuilderParams) => Promise<ContentQueryResponse<T>>;
@@ -72,13 +72,15 @@ const normalizePublicQuery = (event, query, config) => {
72
72
  includeDraftFilter: false
73
73
  });
74
74
  if (enforceProductionVisibility) {
75
+ const where = Array.isArray(normalized.where) ? normalized.where : normalized.where ? [normalized.where] : [];
75
76
  normalized.where = [
76
- ...normalized.where || [],
77
+ ...where,
77
78
  { _draft: { $ne: true } },
78
79
  { _partial: { $ne: true } }
79
80
  ];
80
81
  }
81
- normalized.where = (normalized.where || []).map((condition) => compileWhere(condition)).filter((condition) => Boolean(condition));
82
+ const normalizedWhere = Array.isArray(normalized.where) ? normalized.where : normalized.where ? [normalized.where] : [];
83
+ normalized.where = normalizedWhere.map((condition) => compileWhere(condition)).filter((condition) => Boolean(condition));
82
84
  const unsupported = findUnsupportedQueryOperator(normalized.where);
83
85
  if (unsupported) {
84
86
  throw createContentProviderError("unsupported_query_operator", `Unsupported query operator: ${unsupported}`, {
@@ -106,7 +108,7 @@ export const executeFilesystemContentQuery = async (event, inputQuery) => {
106
108
  collections: config.collections
107
109
  });
108
110
  if (plan.mode === "count") {
109
- return response.result;
111
+ return response;
110
112
  }
111
113
  const requestedLocale = plan.resolveVariant?.locale || plan.resolveLocale?.locale;
112
114
  if (plan.mode === "first") {
@@ -114,8 +116,14 @@ export const executeFilesystemContentQuery = async (event, inputQuery) => {
114
116
  if (!content) {
115
117
  notFound(query, plan.resolveVariant ? "Could not find document for the given route variant." : void 0);
116
118
  }
117
- return await withResolvedRefs(event, content, requestedLocale);
119
+ return {
120
+ result: await withResolvedRefs(event, content, requestedLocale)
121
+ };
118
122
  }
119
- return await withResolvedRefsList(event, Array.isArray(response.result) ? response.result : [], requestedLocale);
123
+ const listResponse = response;
124
+ return {
125
+ ...listResponse,
126
+ result: await withResolvedRefsList(event, Array.isArray(response.result) ? response.result : [], requestedLocale)
127
+ };
120
128
  };
121
129
  export const executeContentQuery = executeFilesystemContentQuery;
@@ -14,7 +14,7 @@ const isRouteBackedSearchCollection = (config) => {
14
14
  return config.type === "page" || hasRouteMount(config.route) || config.sitemap === true;
15
15
  };
16
16
  export const resolveSearchCollections = (runtimeContent, collectionsOverride) => {
17
- const configuredCollections = collectionsOverride || (runtimeContent.search && runtimeContent.search !== false ? runtimeContent.search.collections : void 0);
17
+ const configuredCollections = collectionsOverride || (runtimeContent.search ? runtimeContent.search.collections : void 0);
18
18
  if (configuredCollections?.length) {
19
19
  return unique(configuredCollections);
20
20
  }
@@ -1,4 +1,43 @@
1
1
  import { createContentProviderError } from "../../public/provider-errors";
2
+ const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
3
+ const describeShape = (value) => {
4
+ if (Array.isArray(value)) return "array";
5
+ if (value === null) return "null";
6
+ return typeof value;
7
+ };
8
+ const assertSitemapEntry = (entry, index, provider) => {
9
+ if (!isObject(entry) || typeof entry.loc !== "string" || !entry.loc.trim()) {
10
+ throw createContentProviderError("provider_result_invalid", `${provider} returned an invalid sitemap entry.`, {
11
+ provider,
12
+ operation: "sitemap entries",
13
+ index,
14
+ field: "loc",
15
+ actual: describeShape(entry)
16
+ });
17
+ }
18
+ if (entry.alternatives !== void 0 && (!Array.isArray(entry.alternatives) || entry.alternatives.some(
19
+ (alternative) => !isObject(alternative) || typeof alternative.hreflang !== "string" || typeof alternative.href !== "string"
20
+ ))) {
21
+ throw createContentProviderError("provider_result_invalid", `${provider} returned invalid sitemap alternatives.`, {
22
+ provider,
23
+ operation: "sitemap entries",
24
+ index,
25
+ field: "alternatives",
26
+ actual: describeShape(entry.alternatives)
27
+ });
28
+ }
29
+ };
30
+ const assertSitemapEntries = (value, provider) => {
31
+ if (!Array.isArray(value)) {
32
+ throw createContentProviderError("provider_result_invalid", `${provider} returned invalid sitemap entries.`, {
33
+ provider,
34
+ operation: "sitemap entries",
35
+ actual: describeShape(value)
36
+ });
37
+ }
38
+ value.forEach((entry, index) => assertSitemapEntry(entry, index, provider));
39
+ return value;
40
+ };
2
41
  export async function queryCollectionsSitemapEntries(event, options = {}) {
3
42
  const { getContentProvider } = await import("./providers/index.js");
4
43
  const provider = await getContentProvider(event);
@@ -7,5 +46,5 @@ export async function queryCollectionsSitemapEntries(event, options = {}) {
7
46
  provider: provider.name
8
47
  });
9
48
  }
10
- return await provider.sitemapEntries(event, options);
49
+ return assertSitemapEntries(await provider.sitemapEntries(event, options), provider.name);
11
50
  }
@@ -18,12 +18,13 @@ const sitemapPageFields = [
18
18
  ];
19
19
  export async function queryFilesystemCollectionsSitemapEntries(event, options = {}) {
20
20
  const runtimeConfig = useRuntimeConfig(event);
21
+ const sitemapRuntime = runtimeConfig;
21
22
  const requestUrl = event?.node?.req ? getRequestURL(event) : null;
22
23
  return await queryCollectionsSitemapEntriesData({
23
24
  collections: contentConfig().collections,
24
25
  defaultLocale: contentConfig().defaultLocale,
25
- runtimeSiteUrl: runtimeConfig.public.siteUrl,
26
- localeConfigs: runtimeConfig.public.i18n?.locales || [],
26
+ runtimeSiteUrl: sitemapRuntime.public.content?.siteUrl || sitemapRuntime.public.siteUrl,
27
+ localeConfigs: sitemapRuntime.public.i18n?.locales || [],
27
28
  requestSiteUrl: requestUrl ? `${requestUrl.protocol}//${requestUrl.host}` : void 0
28
29
  }, {
29
30
  loadCollectionPages: async (collection) => {
@@ -20,8 +20,9 @@ export default defineTransformer({
20
20
  if (import.meta.server) {
21
21
  return content;
22
22
  }
23
+ const transformerOptions = typeof options === "object" && options !== null ? options : {};
23
24
  const _components = await resolveContentComponents(content.body, {
24
- ...options?.tags || {},
25
+ ...transformerOptions.tags || {},
25
26
  ...content._components || {}
26
27
  });
27
28
  content._components = _components;
@@ -1,9 +1,4 @@
1
1
  import type { AsyncComponentLoader } from 'vue';
2
- declare module '#build/content-components' {
3
- const globalComponents: string[];
4
- const localComponents: string[];
5
- const localComponentLoaders: Record<string, AsyncComponentLoader>;
6
- }
7
2
  export declare const globalComponents: string[];
8
3
  export declare const localComponents: string[];
9
4
  export declare const localComponentLoaders: Record<string, AsyncComponentLoader>;
@@ -1,14 +1,7 @@
1
1
  import type { H3Event } from 'h3';
2
2
  import type { ParsedContent } from '../types/content';
3
- import type { ContentCollectionMap, ContentLocaleEntry, ContentQueryBuilderParams, ContentQueryRequest, CollectionQueryBuilder, ResolveContentReferenceOptions } from '../types/query';
4
- export declare const createServerQueryFetch: <T = ParsedContent>(event: H3Event) => (query: ContentQueryRequest) => Promise<import("../types/api").ContentQueryCountResponse | {
5
- result: T | T[] | undefined;
6
- } | {
7
- result: T | T[] | undefined;
8
- skip: number;
9
- limit: number;
10
- total: number;
11
- }>;
3
+ import type { ContentCollectionMap, ContentLocaleEntry, ContentQueryBuilderParams, ContentQueryFetcher, CollectionQueryBuilder, ResolveContentReferenceOptions } from '../types/query';
4
+ export declare const createServerQueryFetch: <T = ParsedContent>(event: H3Event) => ContentQueryFetcher<T>;
12
5
  /**
13
6
  * Resolve an authored content reference value to a concrete document.
14
7
  *
@@ -3,11 +3,4 @@ import type { ContentQueryBuilderParams } from '../types/query';
3
3
  import type { ContentQueryResponse } from '../types/api';
4
4
  export declare const withResolvedRefs: <T>(event: H3Event, content: T, requestedLocale?: string) => Promise<T>;
5
5
  export declare const withResolvedRefsList: <T>(event: H3Event, items: T[], requestedLocale?: string) => Promise<T[]>;
6
- export declare const withResolvedRefsQueryResponse: <T>(event: H3Event, response: ContentQueryResponse<T>, params: ContentQueryBuilderParams) => Promise<import("../types/api").ContentQueryCountResponse | {
7
- result: T | T[] | undefined;
8
- } | {
9
- result: T | T[] | undefined;
10
- skip: number;
11
- limit: number;
12
- total: number;
13
- }>;
6
+ export declare const withResolvedRefsQueryResponse: <T>(event: H3Event, response: ContentQueryResponse<T>, params: ContentQueryBuilderParams) => Promise<ContentQueryResponse<T>>;
@@ -66,13 +66,18 @@ export const withResolvedRefsQueryResponse = async (event, response, params) =>
66
66
  }
67
67
  const requestedLocale = params.resolveLocale?.locale;
68
68
  if (params.first) {
69
+ const firstResponse = response;
69
70
  return {
70
- ...response,
71
- result: await withResolvedRefs(event, response.result, requestedLocale)
71
+ ...firstResponse,
72
+ result: await withResolvedRefs(event, firstResponse.result, requestedLocale)
72
73
  };
73
74
  }
75
+ if (!Array.isArray(response.result)) {
76
+ return response;
77
+ }
78
+ const listResponse = response;
74
79
  return {
75
- ...response,
76
- result: Array.isArray(response.result) ? await withResolvedRefsList(event, response.result, requestedLocale) : response.result
80
+ ...listResponse,
81
+ result: await withResolvedRefsList(event, listResponse.result, requestedLocale)
77
82
  };
78
83
  };
@@ -1,7 +1,6 @@
1
1
  import { expect, test } from "vitest";
2
2
  import { contentProviderResultMarker } from "../public/provider";
3
- import { getContentCacheHint } from "../runtime/server/cache-hints";
4
- import { createAuthorDependencyProviderFixture, createFixtureContentProvider, createProviderFixtureEvent } from "./provider-fixture.js";
3
+ import { createAuthorDependencyProviderFixture, createFixtureContentProvider, createProviderFixtureEvent, getProviderFixtureCacheHint } from "./provider-fixture.js";
5
4
  const defaultCollectNavPaths = (items) => items.flatMap((item) => [
6
5
  item._path,
7
6
  ...defaultCollectNavPaths(item.children || [])
@@ -137,7 +136,7 @@ export const runSaasProviderFixtureContractSuite = ({
137
136
  const provider = await loadProvider();
138
137
  const event = createEvent();
139
138
  await provider.page?.(event, "posts", "/de/magazin/mehrsprachiges-onboarding");
140
- expect(getContentCacheHint(event)).toMatchObject({
139
+ expect(getProviderFixtureCacheHint(event)).toMatchObject({
141
140
  tags: expect.arrayContaining([
142
141
  "entry:posts:posts.onboarding",
143
142
  "entry:authors:emily",
@@ -149,7 +148,7 @@ export const runSaasProviderFixtureContractSuite = ({
149
148
  if (provider.siteData) {
150
149
  const siteDataEvent = createEvent();
151
150
  await provider.siteData(siteDataEvent, { key: "settings", locale: "de" });
152
- expect(getContentCacheHint(siteDataEvent)).toMatchObject({
151
+ expect(getProviderFixtureCacheHint(siteDataEvent)).toMatchObject({
153
152
  tags: ["site-data:settings:de"]
154
153
  });
155
154
  }
@@ -1,4 +1,4 @@
1
- import { type ContentProvider } from '../public/provider';
1
+ import { type ContentCacheHint, type ContentProvider } from '../public/provider';
2
2
  import type { H3Event } from 'h3';
3
3
  import type { ParsedContent } from '../types/content';
4
4
  import { type ContentGraph } from '../core/content/graph';
@@ -55,6 +55,8 @@ export interface ProviderFixtureEventOptions {
55
55
  params?: Record<string, string>;
56
56
  context?: Record<string, unknown>;
57
57
  }
58
+ export declare const collectProviderFixtureCacheHint: (event: H3Event, hint: ContentCacheHint | false | undefined) => void;
59
+ export declare const getProviderFixtureCacheHint: (event: H3Event) => ContentCacheHint | false | undefined;
58
60
  export declare const createProviderFixtureDocument: (input: Partial<ParsedContent> & Record<string, unknown>) => ParsedContent;
59
61
  export declare const createProviderFixture: (input: ProviderFixtureInput) => ProviderFixture;
60
62
  export declare const createProviderFixtureEvent: (options?: ProviderFixtureEventOptions) => H3Event;
@@ -3,19 +3,18 @@ import { buildContentGraph } from "../core/content/graph";
3
3
  import { executeQueryPlan } from "../core/query/execute";
4
4
  import { lowerQueryPlan } from "../core/query/lower";
5
5
  import { findUnsupportedQueryOperator, SUPPORTED_QUERY_OPERATORS } from "../core/query/operators";
6
+ import { mergeContentCacheHints } from "../core/cache-hints";
6
7
  import { normalizeRouteMounts, projectContentPathToLocale } from "../features/localization/path";
7
8
  import { createRouteMeta, localizePageResult } from "../features/localization/results";
8
9
  import { createContentProviderError } from "../public/provider-errors";
9
- import { collectContentCacheHint } from "../runtime/server/cache-hints";
10
10
  const trimSlashes = (value) => value.replace(/^\/+|\/+$/g, "");
11
+ const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
11
12
  const unwrapResponseResult = (response) => {
12
- if (response && typeof response === "object" && response[contentProviderResultMarker] === true) {
13
+ if (isObject(response) && response[contentProviderResultMarker] === true) {
13
14
  return unwrapResponseResult(response.data);
14
15
  }
15
- if (response && typeof response === "object" && "result" in response) {
16
- return response.result;
17
- }
18
- return response;
16
+ const envelope = response;
17
+ return envelope.result;
19
18
  };
20
19
  const normalizeQueryResult = (value) => {
21
20
  if (Array.isArray(value)) return value;
@@ -72,6 +71,19 @@ const referenceEntryTags = (fixture, doc) => {
72
71
  }));
73
72
  return Array.from(new Set(collectStringValues(doc).map((value) => knownRefs.get(value)).filter((tag) => typeof tag === "string")));
74
73
  };
74
+ const getProviderFixtureRuntimeContext = (event) => {
75
+ const context = event.context ||= {};
76
+ context.__contentRuntime ||= {};
77
+ return context.__contentRuntime;
78
+ };
79
+ export const collectProviderFixtureCacheHint = (event, hint) => {
80
+ if (typeof hint === "undefined") {
81
+ return;
82
+ }
83
+ const runtime = getProviderFixtureRuntimeContext(event);
84
+ runtime.cacheHint = mergeContentCacheHints(runtime.cacheHint, hint);
85
+ };
86
+ export const getProviderFixtureCacheHint = (event) => getProviderFixtureRuntimeContext(event).cacheHint;
75
87
  const createCacheHintForDocument = (fixture, doc, path) => {
76
88
  const collection = doc._collection || "content";
77
89
  return {
@@ -224,7 +236,7 @@ export const createFixtureContentProvider = (fixture, name = fixture.providerNam
224
236
  operator: unsupported
225
237
  });
226
238
  }
227
- collectContentCacheHint(event, {
239
+ collectProviderFixtureCacheHint(event, {
228
240
  tags: params.collection ? [collectionTag(params.collection)] : []
229
241
  });
230
242
  return executeQueryPlan(
@@ -258,7 +270,7 @@ export const createFixtureContentProvider = (fixture, name = fixture.providerNam
258
270
  query,
259
271
  navigationQuery: async (event, params) => {
260
272
  const docs = await docsForNavigation(event, params);
261
- collectContentCacheHint(event, {
273
+ collectProviderFixtureCacheHint(event, {
262
274
  tags: [
263
275
  ...params.collection ? [collectionTag(params.collection)] : [],
264
276
  ...params.resolveLocale?.locale ? [`nav:${params.collection || "all"}:${params.resolveLocale.locale}`] : []
@@ -277,7 +289,7 @@ export const createFixtureContentProvider = (fixture, name = fixture.providerNam
277
289
  assertCollection(collection);
278
290
  const fields = Array.isArray(options) ? options : options.fields || [];
279
291
  const locale = typeof options === "object" && !Array.isArray(options) ? options.locale : void 0;
280
- collectContentCacheHint(event, {
292
+ collectProviderFixtureCacheHint(event, {
281
293
  tags: [`nav:${collection}:${locale || fixture.defaultLocale}`, collectionTag(collection)]
282
294
  });
283
295
  const docs = await docsForNavigation(event, {
@@ -318,7 +330,7 @@ export const createFixtureContentProvider = (fixture, name = fixture.providerNam
318
330
  },
319
331
  searchSections: async (event, collection, options = {}) => {
320
332
  assertCollection(collection);
321
- collectContentCacheHint(event, {
333
+ collectProviderFixtureCacheHint(event, {
322
334
  tags: [`search:${options.locale || fixture.defaultLocale}`, collectionTag(collection)]
323
335
  });
324
336
  const docs = await docsForNavigation(event, {
@@ -335,7 +347,8 @@ export const createFixtureContentProvider = (fixture, name = fixture.providerNam
335
347
  id: localizePath(fixture, collection, doc._path || "/", options.locale || doc._locale),
336
348
  title: doc.title || "",
337
349
  titles: [doc.title || ""],
338
- content: String(doc.description || doc.title || "")
350
+ content: String(doc.description || doc.title || ""),
351
+ level: 1
339
352
  }));
340
353
  },
341
354
  search: async (_event, request) => {
@@ -350,7 +363,7 @@ export const createFixtureContentProvider = (fixture, name = fixture.providerNam
350
363
  }));
351
364
  },
352
365
  siteData: async (event, request) => {
353
- collectContentCacheHint(event, {
366
+ collectProviderFixtureCacheHint(event, {
354
367
  tags: [`site-data:${request.key}:${request.locale || fixture.defaultLocale}`]
355
368
  });
356
369
  return {
@@ -389,7 +402,7 @@ export const createFixtureContentProvider = (fixture, name = fixture.providerNam
389
402
  routeMountsFor(fixture, collection)
390
403
  );
391
404
  const hint = createCacheHintForDocument(fixture, doc, page.path);
392
- collectContentCacheHint(event, hint);
405
+ collectProviderFixtureCacheHint(event, hint);
393
406
  recordRouteDependencies(cache, page.path, hint.tags || []);
394
407
  return page;
395
408
  },
@@ -398,7 +411,7 @@ export const createFixtureContentProvider = (fixture, name = fixture.providerNam
398
411
  return page ? createRouteMeta(page, options.locale || page.locale, fixture.defaultLocale, routeMountsFor(fixture, collection)) : null;
399
412
  },
400
413
  sitemapEntries: async (event, options = {}) => {
401
- collectContentCacheHint(event, { tags: ["sitemap"] });
414
+ collectProviderFixtureCacheHint(event, { tags: ["sitemap"] });
402
415
  const explicitInclude = Boolean(options.include?.length);
403
416
  const include = explicitInclude ? options.include : Object.keys(fixture.collections);
404
417
  const entries = [];