@lupinum/ginko-content 0.1.6 → 0.2.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.
- package/compatibility.json +1 -1
- package/dist/cli.mjs +2 -2
- package/dist/cms-contract/build.js +15 -20
- package/dist/cms-contract/types.d.ts +12 -7
- package/dist/core/content/document +1 -0
- package/dist/core/content/document.d.ts +16 -0
- package/dist/core/content/document.js +2 -0
- package/dist/core/content/graph.d.ts +3 -1
- package/dist/core/content/graph.js +21 -20
- package/dist/core/content/locale.d.ts +6 -1
- package/dist/core/content/locale.js +17 -6
- package/dist/core/content/path.d.ts +5 -5
- package/dist/core/content/path.js +8 -8
- package/dist/core/content/snapshot +1 -0
- package/dist/core/content/snapshot.d.ts +25 -0
- package/dist/core/content/snapshot.js +99 -0
- package/dist/core/provider-errors.d.ts +1 -1
- package/dist/core/provider-errors.js +1 -0
- package/dist/core/query/builder.js +3 -3
- package/dist/core/query/execute.js +75 -36
- package/dist/core/query/filter.js +2 -2
- package/dist/core/query/lower.js +79 -3
- package/dist/core/query/params.js +7 -7
- package/dist/core/query/plan.d.ts +18 -0
- package/dist/core/query/plan.js +7 -0
- package/dist/core/references/resolve.js +6 -9
- package/dist/features/agent/agent-markdown +1 -0
- package/dist/features/agent/agent-markdown.d.ts +116 -0
- package/dist/features/agent/agent-markdown.js +138 -0
- package/dist/features/agent/walker +1 -0
- package/dist/features/agent/walker.d.ts +10 -0
- package/dist/features/agent/walker.js +174 -0
- package/dist/features/collections/resolve.d.ts +2 -2
- package/dist/features/collections/resolve.js +19 -16
- package/dist/features/localization/links.js +1 -1
- package/dist/features/localization/results.d.ts +3 -3
- package/dist/features/localization/results.js +46 -41
- package/dist/features/localization/translated-slugs.js +5 -5
- package/dist/features/navigation/build.d.ts +2 -2
- package/dist/features/navigation/build.js +23 -26
- package/dist/features/navigation/canonical.d.ts +10 -11
- package/dist/features/navigation/canonical.js +26 -21
- package/dist/features/navigation/resolve.js +4 -4
- package/dist/features/navigation/tree.js +3 -3
- package/dist/{runtime → features}/query/backlinks.js +3 -5
- package/dist/{runtime → features}/query/documents.js +1 -1
- package/dist/{runtime → features}/query/localized-docs.d.ts +1 -1
- package/dist/{runtime → features}/query/localized-docs.js +3 -6
- package/dist/{runtime → features}/query/navigation.d.ts +1 -1
- package/dist/{runtime → features}/query/navigation.js +8 -8
- package/dist/{runtime → features}/query/populate.js +6 -14
- package/dist/{runtime → features}/query/unified.d.ts +4 -4
- package/dist/{runtime → features}/query/unified.js +4 -2
- package/dist/{runtime → features}/query/variants.js +4 -4
- package/dist/features/search/navigation.js +2 -2
- package/dist/features/search/sections.d.ts +1 -1
- package/dist/features/search/sections.js +1 -1
- package/dist/features/sitemap/query.d.ts +5 -5
- package/dist/features/sitemap/query.js +6 -6
- package/dist/integrations/nitro/ingest.js +9 -20
- package/dist/integrations/nitro/storage.d.ts +1 -0
- package/dist/integrations/nitro/storage.js +22 -8
- package/dist/module.d.mts +135 -812
- package/dist/module.json +1 -1
- package/dist/module.mjs +70 -27
- package/dist/parsers/csv.js +3 -3
- package/dist/parsers/index.js +3 -3
- package/dist/parsers/json.js +8 -6
- package/dist/parsers/markdown-plugins.js +9 -5
- package/dist/parsers/markdown.js +7 -6
- package/dist/parsers/path-meta.js +21 -19
- package/dist/parsers/reserved +1 -0
- package/dist/parsers/reserved.d.ts +15 -0
- package/dist/parsers/reserved.js +35 -0
- package/dist/parsers/yaml.js +6 -4
- package/dist/public/agent +1 -0
- package/dist/public/agent.d.ts +17 -0
- package/dist/public/agent.js +34 -0
- package/dist/public/client.d.ts +2 -3
- package/dist/public/client.js +1 -2
- package/dist/public/provider-query +1 -0
- package/dist/public/provider-query.d.ts +58 -0
- package/dist/public/provider-query.js +5 -0
- package/dist/public/provider.d.ts +8 -3
- package/dist/public/provider.js +2 -0
- package/dist/public/server.d.ts +7 -12
- package/dist/public/server.js +5 -36
- package/dist/runtime/app/components/ContentRenderer.d.vue.ts +3 -3
- package/dist/runtime/app/components/ContentRenderer.vue +2 -2
- package/dist/runtime/app/components/ContentRenderer.vue.d.ts +3 -3
- package/dist/runtime/app/components/ContentRendererInline.d.vue.ts +1 -1
- package/dist/runtime/app/components/ContentRendererInline.vue.d.ts +1 -1
- package/dist/runtime/app/components/Prose/ProseImg.d.vue.ts +4 -4
- package/dist/runtime/app/components/Prose/ProseImg.vue.d.ts +4 -4
- package/dist/runtime/app/components/Prose/ProsePre.d.vue.ts +1 -1
- package/dist/runtime/app/components/Prose/ProsePre.vue.d.ts +1 -1
- package/dist/runtime/app/components/internal/ContentRendererMarkdown.d.vue.ts +1 -1
- package/dist/runtime/app/components/internal/ContentRendererMarkdown.vue +4 -4
- package/dist/runtime/app/components/internal/ContentRendererMarkdown.vue.d.ts +1 -1
- package/dist/runtime/app/components/internal/MarkdownRenderer.d.ts +3 -3
- package/dist/runtime/app/composables/preview.d.ts +1 -1
- package/dist/runtime/app/composables/query-api.d.ts +5 -5
- package/dist/runtime/app/composables/query-api.js +5 -3
- package/dist/runtime/app/composables/route.d.ts +1 -1
- package/dist/runtime/app/composables/route.js +4 -4
- package/dist/runtime/app/composables/search.js +1 -1
- package/dist/runtime/app/composables/use-content-document.js +1 -1
- package/dist/runtime/app/composables/use-content-list.js +1 -1
- package/dist/runtime/app/composables/use-content-navigation.js +2 -2
- package/dist/runtime/app/composables/use-content-page.js +4 -4
- package/dist/runtime/app/pages/content.vue +2 -1
- package/dist/runtime/markdown/plugins.js +6 -8
- package/dist/runtime/server/agent-markdown.d.ts +5 -65
- package/dist/runtime/server/agent-markdown.js +40 -315
- package/dist/runtime/server/agent-site.js +1 -1
- package/dist/runtime/server/api/agent-raw.js +1 -1
- package/dist/runtime/server/api/cache.d.ts +1 -1
- package/dist/runtime/server/api/cache.js +43 -4
- package/dist/runtime/server/api/locales.js +3 -3
- package/dist/runtime/server/api/navigation.d.ts +1 -1
- package/dist/runtime/server/api/navigation.js +3 -1
- package/dist/runtime/server/api/query.js +2 -2
- package/dist/runtime/server/api/search-index.d.ts +1 -1
- package/dist/runtime/server/api/sitemap.d.ts +1 -1
- package/dist/runtime/server/cache-adapters.d.ts +18 -0
- package/dist/runtime/server/cache-adapters.js +12 -0
- package/dist/runtime/server/collection-helpers.d.ts +2 -2
- package/dist/runtime/server/collection-helpers.js +15 -11
- package/dist/runtime/server/index.d.ts +6 -4
- package/dist/runtime/server/index.js +10 -3
- package/dist/runtime/server/middleware/agent-link-headers.js +1 -1
- package/dist/runtime/server/middleware/agent-markdown.js +1 -1
- package/dist/runtime/server/navigation-query.d.ts +2 -2
- package/dist/runtime/server/navigation-query.js +52 -7
- package/dist/runtime/server/navigation.d.ts +1 -1
- package/dist/runtime/server/provider-document +1 -0
- package/dist/runtime/server/provider-document.d.ts +59 -0
- package/dist/runtime/server/provider-document.js +48 -0
- package/dist/runtime/server/provider-query-wire +1 -0
- package/dist/runtime/server/provider-query-wire.d.ts +32 -0
- package/dist/runtime/server/provider-query-wire.js +27 -0
- package/dist/runtime/server/provider-query.d.ts +18 -9
- package/dist/runtime/server/provider-query.js +62 -28
- package/dist/runtime/server/provider-result.d.ts +4 -4
- package/dist/runtime/server/providers/filesystem.js +3 -11
- package/dist/runtime/server/providers/index.d.ts +18 -1
- package/dist/runtime/server/providers/index.js +72 -20
- package/dist/runtime/server/query-api.d.ts +5 -5
- package/dist/runtime/server/query-api.js +9 -6
- package/dist/runtime/server/query-executor.d.ts +3 -3
- package/dist/runtime/server/query-executor.js +43 -59
- package/dist/runtime/server/search.d.ts +1 -1
- package/dist/runtime/server/search.js +4 -4
- package/dist/runtime/server/sitemap.js +5 -5
- package/dist/runtime/server/storage-access.d.ts +4 -1
- package/dist/runtime/server/storage-access.js +27 -2
- package/dist/runtime/transformers/component-resolver.d.ts +1 -1
- package/dist/storage/content.d.ts +2 -6
- package/dist/storage/content.js +19 -8
- package/dist/storage/contents.d.ts +9 -1
- package/dist/storage/contents.js +26 -19
- package/dist/storage/driver.d.ts +2 -2
- package/dist/storage/driver.js +2 -1
- package/dist/storage/graph.js +4 -0
- package/dist/storage/manifest.js +3 -1
- package/dist/storage/references.js +9 -5
- package/dist/storage/snapshot-runtime +1 -0
- package/dist/storage/snapshot-runtime.d.ts +11 -0
- package/dist/storage/snapshot-runtime.js +46 -0
- package/dist/storage/validation.d.ts +1 -1
- package/dist/storage/validation.js +47 -102
- package/dist/testing/provider-contract.d.ts +14 -4
- package/dist/testing/provider-contract.js +121 -68
- package/dist/testing/provider-fixture.d.ts +1 -1
- package/dist/testing/provider-fixture.js +124 -120
- package/dist/types/config.d.ts +8 -4
- package/dist/types/content.d.ts +104 -70
- package/dist/types/fields.js +1 -1
- package/dist/types/module.d.ts +1 -1
- package/dist/types/query-parts/public.d.ts +14 -5
- package/dist/types/query-parts/results.d.ts +6 -2
- package/dist/types/query-parts/transport.d.ts +19 -0
- package/dist/types/virtual.d.ts +6 -0
- package/dist/types.d.mts +2 -2
- package/dist/web-types.json +1 -1
- package/package.json +39 -9
- package/dist/runtime/app/plugins/markdown-components +0 -1
- package/dist/runtime/app/plugins/markdown-components.d.ts +0 -2
- package/dist/runtime/app/plugins/markdown-components.js +0 -19
- /package/dist/{runtime → features/agent}/agent-paths +0 -0
- /package/dist/{runtime → features/agent}/agent-paths.d.ts +0 -0
- /package/dist/{runtime → features/agent}/agent-paths.js +0 -0
- /package/dist/{runtime → features}/query/backlinks +0 -0
- /package/dist/{runtime → features}/query/backlinks.d.ts +0 -0
- /package/dist/{runtime → features}/query/context +0 -0
- /package/dist/{runtime → features}/query/context.d.ts +0 -0
- /package/dist/{runtime → features}/query/context.js +0 -0
- /package/dist/{runtime → features}/query/documents +0 -0
- /package/dist/{runtime → features}/query/documents.d.ts +0 -0
- /package/dist/{runtime → features}/query/errors +0 -0
- /package/dist/{runtime → features}/query/errors.d.ts +0 -0
- /package/dist/{runtime → features}/query/errors.js +0 -0
- /package/dist/{runtime → features}/query/handles +0 -0
- /package/dist/{runtime → features}/query/handles.d.ts +0 -0
- /package/dist/{runtime → features}/query/handles.js +0 -0
- /package/dist/{runtime → features}/query/locale-options +0 -0
- /package/dist/{runtime → features}/query/locale-options.d.ts +0 -0
- /package/dist/{runtime → features}/query/locale-options.js +0 -0
- /package/dist/{runtime → features}/query/localized-docs +0 -0
- /package/dist/{runtime → features}/query/navigation +0 -0
- /package/dist/{runtime → features}/query/pagination +0 -0
- /package/dist/{runtime → features}/query/pagination.d.ts +0 -0
- /package/dist/{runtime → features}/query/pagination.js +0 -0
- /package/dist/{runtime → features}/query/populate +0 -0
- /package/dist/{runtime → features}/query/populate.d.ts +0 -0
- /package/dist/{runtime → features}/query/public-limits +0 -0
- /package/dist/{runtime → features}/query/public-limits.d.ts +0 -0
- /package/dist/{runtime → features}/query/public-limits.js +0 -0
- /package/dist/{runtime → features}/query/resolve +0 -0
- /package/dist/{runtime → features}/query/resolve.d.ts +0 -0
- /package/dist/{runtime → features}/query/resolve.js +0 -0
- /package/dist/{runtime → features}/query/responses +0 -0
- /package/dist/{runtime → features}/query/responses.d.ts +0 -0
- /package/dist/{runtime → features}/query/responses.js +0 -0
- /package/dist/{runtime → features}/query/routes +0 -0
- /package/dist/{runtime → features}/query/routes.d.ts +0 -0
- /package/dist/{runtime → features}/query/routes.js +0 -0
- /package/dist/{runtime → features}/query/unified +0 -0
- /package/dist/{runtime → features}/query/variants +0 -0
- /package/dist/{runtime → features}/query/variants.d.ts +0 -0
|
@@ -1,10 +1,35 @@
|
|
|
1
1
|
import { joinURL, withLeadingSlash } from "ufo";
|
|
2
2
|
import { createQuery, wrapQueryBuilder } from "../../core/query/builder";
|
|
3
3
|
import { normalizeContentQueryParams } from "../../core/query/params";
|
|
4
|
+
import { containsStandaloneRegexOptions, findUnsupportedQueryOperator } from "../../core/query/operators";
|
|
4
5
|
import { normalizeI18nConfig, resolveRuntimeCollectionI18nConfig } from "../../features/localization/config";
|
|
6
|
+
import { sortLocalesCanonically } from "../../core/content/locale";
|
|
5
7
|
import { normalizeReferenceValue } from "../../core/references/resolve";
|
|
6
8
|
import { getContentRuntimeConfig } from "./runtime-config.js";
|
|
7
9
|
import { createContentProviderError } from "../../public/provider-errors";
|
|
10
|
+
import { toContentProviderQuery } from "../../public/provider-query";
|
|
11
|
+
export { toContentProviderNavigationQuery as createProviderNavigationQuery } from "../../public/provider-query";
|
|
12
|
+
export const createProviderQuery = (params) => {
|
|
13
|
+
const unsupported = findUnsupportedQueryOperator(params.where);
|
|
14
|
+
if (unsupported) {
|
|
15
|
+
throw createContentProviderError("unsupported_query_operator", `Unsupported query operator: ${unsupported}`, {
|
|
16
|
+
operator: unsupported
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
if (containsStandaloneRegexOptions(params.where)) {
|
|
20
|
+
throw createContentProviderError("unsupported_query_shape", "Query operator $options requires $regex.", {
|
|
21
|
+
operator: "$options"
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const config = getContentRuntimeConfig().content || {};
|
|
25
|
+
const normalized = normalizeContentQueryParams(params, {
|
|
26
|
+
collectionI18n: params.collection ? config.collections?.[params.collection]?.i18n : void 0,
|
|
27
|
+
defaultLocale: config.defaultLocale,
|
|
28
|
+
localeFallback: config.localeFallback,
|
|
29
|
+
includeDraftFilter: false
|
|
30
|
+
});
|
|
31
|
+
return toContentProviderQuery(normalized);
|
|
32
|
+
};
|
|
8
33
|
const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
9
34
|
const isProviderFindResponse = (response) => isObject(response) && Array.isArray(response.result) && typeof response.skip === "number" && typeof response.limit === "number" && typeof response.total === "number";
|
|
10
35
|
export const normalizeProviderQueryResult = (response) => {
|
|
@@ -55,7 +80,7 @@ const identityMatchesDocument = (document, identity) => {
|
|
|
55
80
|
if (!normalizedIdentity) {
|
|
56
81
|
return false;
|
|
57
82
|
}
|
|
58
|
-
return [document.
|
|
83
|
+
return [document.canonicalKey, document.path, document.ref].filter((value) => typeof value === "string").some((value) => normalizeReferenceValue(value) === normalizedIdentity);
|
|
59
84
|
};
|
|
60
85
|
export const resolveProviderContentVariants = async (event, identity, options) => {
|
|
61
86
|
if (!options.collection) {
|
|
@@ -77,38 +102,43 @@ export const resolveProviderContentVariants = async (event, identity, options) =
|
|
|
77
102
|
const provider = await getContentProvider(event);
|
|
78
103
|
const baseQuery = {
|
|
79
104
|
collection: options.collection,
|
|
80
|
-
only: ["
|
|
105
|
+
only: ["path", "canonicalKey", "locale", "ref", "title", "description", "body"]
|
|
81
106
|
};
|
|
82
107
|
const documents = localesToQuery.length ? (await Promise.all(
|
|
83
|
-
localesToQuery.map(
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
)).flat() : normalizeProviderQueryResult(normalizeProviderQueryResponse(baseQuery, await provider.query(event, baseQuery), provider.name));
|
|
108
|
+
localesToQuery.map(async (locale) => {
|
|
109
|
+
const localeQuery = { ...baseQuery, resolveLocale: { locale, exact: true } };
|
|
110
|
+
return normalizeProviderQueryResult(
|
|
111
|
+
normalizeProviderQueryResponse(
|
|
112
|
+
localeQuery,
|
|
113
|
+
await provider.query(event, createProviderQuery(localeQuery)),
|
|
114
|
+
provider.name
|
|
115
|
+
)
|
|
116
|
+
);
|
|
117
|
+
})
|
|
118
|
+
)).flat() : normalizeProviderQueryResult(normalizeProviderQueryResponse(baseQuery, await provider.query(event, createProviderQuery(baseQuery)), provider.name));
|
|
95
119
|
const matched = documents.find((document) => identityMatchesDocument(document, normalizedReference));
|
|
96
|
-
const canonicalKey = matched?.
|
|
120
|
+
const canonicalKey = matched?.canonicalKey;
|
|
97
121
|
if (!canonicalKey) {
|
|
98
122
|
return null;
|
|
99
123
|
}
|
|
100
|
-
const variants = documents.filter((document) => document.
|
|
124
|
+
const variants = documents.filter((document) => document.canonicalKey === canonicalKey);
|
|
101
125
|
if (!variants.length) {
|
|
102
126
|
return null;
|
|
103
127
|
}
|
|
104
|
-
const selectByLocale = (locale) => locale ? variants.find((document) => document.
|
|
128
|
+
const selectByLocale = (locale) => locale ? variants.find((document) => document.locale === locale) : void 0;
|
|
105
129
|
const selected = selectByLocale(options.locale) || (!options.exact ? fallbackLocales.map(selectByLocale).find(Boolean) : void 0) || (!options.exact ? selectByLocale(collectionI18n?.defaultLocale || runtimeContent.defaultLocale) : void 0) || (!options.exact ? variants[0] : void 0);
|
|
106
130
|
if (!selected) {
|
|
107
131
|
return null;
|
|
108
132
|
}
|
|
109
|
-
const availableLocales =
|
|
133
|
+
const availableLocales = sortLocalesCanonically(
|
|
134
|
+
Array.from(new Set(variants.map((document) => document.locale).filter(Boolean))),
|
|
135
|
+
{
|
|
136
|
+
defaultLocale: collectionI18n?.defaultLocale || runtimeContent.defaultLocale,
|
|
137
|
+
locales: collectionI18n?.locales
|
|
138
|
+
}
|
|
139
|
+
);
|
|
110
140
|
const variantPaths = Object.fromEntries(
|
|
111
|
-
variants.filter((document) => document.
|
|
141
|
+
variants.filter((document) => document.locale && document.path).map((document) => [document.locale, document.path])
|
|
112
142
|
);
|
|
113
143
|
return {
|
|
114
144
|
canonicalKey,
|
|
@@ -117,16 +147,17 @@ export const resolveProviderContentVariants = async (event, identity, options) =
|
|
|
117
147
|
availableLocales,
|
|
118
148
|
variantPaths,
|
|
119
149
|
requestedLocale: options.locale,
|
|
120
|
-
resolvedLocale: selected.
|
|
121
|
-
fallback: Boolean(options.locale && selected.
|
|
150
|
+
resolvedLocale: selected.locale,
|
|
151
|
+
fallback: Boolean(options.locale && selected.locale && selected.locale !== options.locale)
|
|
122
152
|
};
|
|
123
153
|
};
|
|
124
154
|
export const createServerProviderQueryFetch = (event) => {
|
|
125
155
|
return async (query) => {
|
|
126
156
|
const { getContentProvider } = await import("./providers/index.js");
|
|
127
157
|
const provider = await getContentProvider(event);
|
|
128
|
-
const
|
|
129
|
-
|
|
158
|
+
const params = query.params();
|
|
159
|
+
const response = await provider.query(event, createProviderQuery(params));
|
|
160
|
+
return normalizeProviderQueryResponse(params, response, provider.name);
|
|
130
161
|
};
|
|
131
162
|
};
|
|
132
163
|
const getServerCollectionI18n = (collection) => {
|
|
@@ -164,10 +195,13 @@ export const resolveContentReference = async (event, reference, options = {}) =>
|
|
|
164
195
|
}
|
|
165
196
|
return {
|
|
166
197
|
...resolved.selected,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
198
|
+
resolved: {
|
|
199
|
+
...resolved.selected.resolved || {},
|
|
200
|
+
requestedLocale: resolved.requestedLocale,
|
|
201
|
+
locale: resolved.resolvedLocale,
|
|
202
|
+
fallback: resolved.fallback,
|
|
203
|
+
availableLocales: resolved.availableLocales,
|
|
204
|
+
variantPaths: resolved.variantPaths
|
|
205
|
+
}
|
|
172
206
|
};
|
|
173
207
|
};
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
2
|
import type { ContentQueryResponse } from '../../types/api';
|
|
3
3
|
import type { NavItem, ParsedContent } from '../../types/content';
|
|
4
|
-
import type { ContentCollectionItemSurroundingsOptions, ContentCollectionNavigationOptions, ContentCollectionPageOptions, ContentCollectionRouteMetaOptions, ContentCollectionSearchSectionsOptions, ContentPageResult,
|
|
4
|
+
import type { ContentCollectionItemSurroundingsOptions, ContentCollectionNavigationOptions, ContentCollectionPageOptions, ContentCollectionRouteMetaOptions, ContentCollectionSearchSectionsOptions, ContentPageResult, ContentRouteMeta, ContentSearchSection, ContentSitemapEntry } from '../../types/query';
|
|
5
5
|
import type { QueryCollectionsSitemapEntriesOptions } from '../../features/sitemap/query';
|
|
6
|
-
import type { ContentProvider, ContentProviderResult, ContentProviderSiteDataRequest, ContentProviderSiteDataResponse, MaybeContentProviderResult } from '../../public/provider';
|
|
6
|
+
import type { ContentProvider, ContentProviderNavigationOptions, ContentProviderQuery, ContentProviderResult, ContentProviderSiteDataRequest, ContentProviderSiteDataResponse, MaybeContentProviderResult } from '../../public/provider';
|
|
7
7
|
import type { ContentProviderSearchRequest, ContentSearchResult } from '../../types/search';
|
|
8
8
|
export declare const isContentProviderResult: <T = unknown>(value: unknown) => value is ContentProviderResult<T>;
|
|
9
9
|
export declare const unwrapContentProviderResult: <T>(event: H3Event, result: MaybeContentProviderResult<T>) => T;
|
|
10
10
|
export interface RuntimeContentProvider extends Omit<ContentProvider, 'query' | 'navigationQuery' | 'navigation' | 'surroundings' | 'searchSections' | 'search' | 'siteData' | 'page' | 'routeMeta' | 'sitemapEntries'> {
|
|
11
|
-
query: <T = ParsedContent>(event: H3Event, query:
|
|
12
|
-
navigationQuery?: (event: H3Event, query:
|
|
11
|
+
query: <T = ParsedContent>(event: H3Event, query: ContentProviderQuery) => Promise<ContentQueryResponse<T>>;
|
|
12
|
+
navigationQuery?: (event: H3Event, query: ContentProviderQuery, options?: ContentProviderNavigationOptions) => Promise<NavItem[]>;
|
|
13
13
|
navigation?: (event: H3Event, collection: string, options?: string[] | ContentCollectionNavigationOptions) => Promise<NavItem[]>;
|
|
14
14
|
surroundings?: (event: H3Event, collection: string, path: string, options?: ContentCollectionItemSurroundingsOptions) => Promise<Array<NavItem | null>>;
|
|
15
15
|
searchSections?: (event: H3Event, collection: string, options?: ContentCollectionSearchSectionsOptions) => Promise<ContentSearchSection[]>;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SUPPORTED_QUERY_OPERATORS } from "../../../core/query/operators";
|
|
2
2
|
import { executeFilesystemContentQuery } from "../query-executor.js";
|
|
3
3
|
import { resolveContentNavigation } from "../navigation-query.js";
|
|
4
4
|
import {
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
queryFilesystemCollectionSearchSections
|
|
10
10
|
} from "../collection-helpers.js";
|
|
11
11
|
import { queryFilesystemCollectionsSitemapEntries } from "../sitemap.js";
|
|
12
|
-
import { createContentProviderError } from "../../../public/provider-errors";
|
|
13
12
|
export const filesystemProvider = {
|
|
14
13
|
name: "filesystem",
|
|
15
14
|
capabilities: {
|
|
@@ -32,15 +31,8 @@ export const filesystemProvider = {
|
|
|
32
31
|
count: true
|
|
33
32
|
}
|
|
34
33
|
},
|
|
35
|
-
query: (event, query) =>
|
|
36
|
-
|
|
37
|
-
throw createContentProviderError("unsupported_query_shape", "Query operator $options requires $regex.", {
|
|
38
|
-
operator: "$options"
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
return executeFilesystemContentQuery(event, query);
|
|
42
|
-
},
|
|
43
|
-
navigationQuery: (event, query) => resolveContentNavigation(event, query),
|
|
34
|
+
query: (event, query) => executeFilesystemContentQuery(event, query.plan),
|
|
35
|
+
navigationQuery: (event, query, options) => resolveContentNavigation(event, query, options),
|
|
44
36
|
navigation: (event, collection, options) => queryFilesystemCollectionNavigation(event, collection, options),
|
|
45
37
|
surroundings: (event, collection, path, options) => queryFilesystemCollectionItemSurroundings(event, collection, path, options),
|
|
46
38
|
searchSections: (event, collection, options) => queryFilesystemCollectionSearchSections(event, collection, options),
|
|
@@ -1,5 +1,22 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
|
-
import type
|
|
2
|
+
import { type ContentProvider, type ContentProviderQuery } from '../../../public/provider';
|
|
3
|
+
import type { FilterExpr } from '../../../core/query/plan';
|
|
3
4
|
import { type RuntimeContentProvider } from '../provider-result';
|
|
5
|
+
/**
|
|
6
|
+
* Derive the operator vocabulary a plan actually exercises by walking its
|
|
7
|
+
* `FilterExpr` tree (CS-5 trap 3). Only `compare` nodes carry operators; we
|
|
8
|
+
* recurse through `and`/`or`/`not` to reach every one. Operators are reported
|
|
9
|
+
* in the builder's `$`-prefixed vocabulary so they compare directly against
|
|
10
|
+
* `capabilities.query.operators`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const collectPlanFilterOperators: (filter: FilterExpr, operators?: Set<string>) => Set<string>;
|
|
13
|
+
/**
|
|
14
|
+
* Dev-mode guard: the provider wire is JSON-pure by contract (CS-5). A stray
|
|
15
|
+
* `RegExp`/`Date` in `plan.filter` would corrupt any provider that serializes
|
|
16
|
+
* the query, so we fail fast in dev instead of shipping a silently mangled
|
|
17
|
+
* wire. No-op in production (the walk is not free).
|
|
18
|
+
*/
|
|
19
|
+
export declare const assertJsonPureProviderQuery: (provider: ContentProvider, query: ContentProviderQuery) => void;
|
|
20
|
+
export declare const enforceProviderCapabilities: (provider: ContentProvider) => ContentProvider;
|
|
4
21
|
export declare function getContentProvider(): Promise<ContentProvider>;
|
|
5
22
|
export declare function getContentProvider(event: H3Event): Promise<RuntimeContentProvider>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { useRuntimeConfig } from "nitropack/runtime";
|
|
2
2
|
import { loadExternalContentProvider } from "#content/virtual/providers";
|
|
3
|
+
import { PROVIDER_QUERY_VERSION } from "../../../public/provider";
|
|
3
4
|
import { createContentProviderError } from "../../../public/provider-errors";
|
|
4
5
|
import { wrapContentProviderCacheResults } from "../provider-result.js";
|
|
5
6
|
const isObject = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -24,43 +25,91 @@ const assertProviderField = (providerName, condition, field) => {
|
|
|
24
25
|
const assertProviderMethod = (providerName, provider, method) => {
|
|
25
26
|
assertProviderField(providerName, typeof provider[method] === "function", method);
|
|
26
27
|
};
|
|
27
|
-
const
|
|
28
|
-
|
|
28
|
+
export const collectPlanFilterOperators = (filter, operators = /* @__PURE__ */ new Set()) => {
|
|
29
|
+
switch (filter.type) {
|
|
30
|
+
case "true":
|
|
31
|
+
break;
|
|
32
|
+
case "compare":
|
|
33
|
+
operators.add(`$${filter.operator}`);
|
|
34
|
+
break;
|
|
35
|
+
case "and":
|
|
36
|
+
case "or":
|
|
37
|
+
for (const clause of filter.clauses) {
|
|
38
|
+
collectPlanFilterOperators(clause, operators);
|
|
39
|
+
}
|
|
40
|
+
break;
|
|
41
|
+
case "not":
|
|
42
|
+
collectPlanFilterOperators(filter.clause, operators);
|
|
43
|
+
break;
|
|
44
|
+
default:
|
|
45
|
+
throw new TypeError(`Unknown query filter node: ${filter.type}`);
|
|
46
|
+
}
|
|
47
|
+
return operators;
|
|
48
|
+
};
|
|
49
|
+
const findNonJsonValue = (value, path) => {
|
|
50
|
+
if (value === null || value === void 0) return void 0;
|
|
51
|
+
const kind = typeof value;
|
|
52
|
+
if (kind === "string" || kind === "number" || kind === "boolean") return void 0;
|
|
53
|
+
if (kind === "function" || kind === "bigint" || kind === "symbol") return path;
|
|
29
54
|
if (Array.isArray(value)) {
|
|
30
|
-
for (
|
|
31
|
-
const
|
|
32
|
-
if (
|
|
55
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
56
|
+
const offender = findNonJsonValue(value[index], `${path}[${index}]`);
|
|
57
|
+
if (offender) return offender;
|
|
33
58
|
}
|
|
34
59
|
return void 0;
|
|
35
60
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
61
|
+
if (kind === "object") {
|
|
62
|
+
const prototype = Object.getPrototypeOf(value);
|
|
63
|
+
if (prototype !== Object.prototype && prototype !== null) return path;
|
|
64
|
+
for (const [key, child] of Object.entries(value)) {
|
|
65
|
+
const offender = findNonJsonValue(child, `${path}.${key}`);
|
|
66
|
+
if (offender) return offender;
|
|
67
|
+
}
|
|
68
|
+
return void 0;
|
|
69
|
+
}
|
|
70
|
+
return path;
|
|
71
|
+
};
|
|
72
|
+
export const assertJsonPureProviderQuery = (provider, query) => {
|
|
73
|
+
if (!import.meta.dev) return;
|
|
74
|
+
const offender = findNonJsonValue(query, "query");
|
|
75
|
+
if (offender) {
|
|
76
|
+
throw createContentProviderError("provider_query_not_json_pure", `${provider.name} received a non-JSON-pure query at ${offender}. Provider queries must survive JSON.parse(JSON.stringify(query)).`, {
|
|
77
|
+
provider: provider.name,
|
|
78
|
+
field: offender
|
|
79
|
+
});
|
|
40
80
|
}
|
|
41
81
|
};
|
|
42
82
|
const assertProviderQuerySupported = (provider, query) => {
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (unsupportedOperator) {
|
|
46
|
-
throw createContentProviderError("unsupported_query_operator", `${provider.name} does not support query operator: ${unsupportedOperator}`, {
|
|
83
|
+
if (query.v !== PROVIDER_QUERY_VERSION) {
|
|
84
|
+
throw createContentProviderError("unsupported_query_shape", `${provider.name} received unsupported provider query version: ${String(query.v)}.`, {
|
|
47
85
|
provider: provider.name,
|
|
48
|
-
|
|
86
|
+
field: "v"
|
|
49
87
|
});
|
|
50
88
|
}
|
|
51
|
-
|
|
89
|
+
const capabilities = provider.capabilities.query;
|
|
90
|
+
const supported = new Set(capabilities.operators);
|
|
91
|
+
const usedOperators = collectPlanFilterOperators(query.plan.filter);
|
|
92
|
+
for (const operator of usedOperators) {
|
|
93
|
+
if (!supported.has(operator)) {
|
|
94
|
+
throw createContentProviderError("unsupported_query_operator", `${provider.name} does not support query operator: ${operator}`, {
|
|
95
|
+
provider: provider.name,
|
|
96
|
+
operator
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
if (!capabilities.limit && query.plan.limit !== void 0) {
|
|
52
101
|
throw createContentProviderError("unsupported_query_shape", `${provider.name} does not support query limits.`, {
|
|
53
102
|
provider: provider.name,
|
|
54
103
|
field: "limit"
|
|
55
104
|
});
|
|
56
105
|
}
|
|
57
|
-
if (!capabilities.skip && query.skip
|
|
106
|
+
if (!capabilities.skip && query.plan.skip > 0) {
|
|
58
107
|
throw createContentProviderError("unsupported_query_shape", `${provider.name} does not support query offsets.`, {
|
|
59
108
|
provider: provider.name,
|
|
60
109
|
field: "skip"
|
|
61
110
|
});
|
|
62
111
|
}
|
|
63
|
-
if (!capabilities.count && query.
|
|
112
|
+
if (!capabilities.count && query.plan.mode === "count") {
|
|
64
113
|
throw createContentProviderError("unsupported_query_shape", `${provider.name} does not support count queries.`, {
|
|
65
114
|
provider: provider.name,
|
|
66
115
|
field: "count"
|
|
@@ -75,15 +124,18 @@ const assertProviderOperationSupported = (provider, supported, operation) => {
|
|
|
75
124
|
});
|
|
76
125
|
}
|
|
77
126
|
};
|
|
78
|
-
const enforceProviderCapabilities = (provider) => ({
|
|
127
|
+
export const enforceProviderCapabilities = (provider) => ({
|
|
79
128
|
...provider,
|
|
80
129
|
query: async (event, query) => {
|
|
130
|
+
assertJsonPureProviderQuery(provider, query);
|
|
81
131
|
assertProviderQuerySupported(provider, query);
|
|
82
132
|
return await provider.query(event, query);
|
|
83
133
|
},
|
|
84
|
-
navigationQuery: provider.navigationQuery ? async (
|
|
134
|
+
navigationQuery: provider.navigationQuery ? async (event, query, options) => {
|
|
85
135
|
assertProviderOperationSupported(provider, provider.capabilities.navigation, "navigation");
|
|
86
|
-
|
|
136
|
+
assertJsonPureProviderQuery(provider, query);
|
|
137
|
+
assertProviderQuerySupported(provider, query);
|
|
138
|
+
return await provider.navigationQuery(event, query, options);
|
|
87
139
|
} : void 0,
|
|
88
140
|
navigation: provider.navigation ? async (...args) => {
|
|
89
141
|
assertProviderOperationSupported(provider, provider.capabilities.navigation, "navigation");
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { H3Event } from 'h3';
|
|
2
2
|
import type { ParsedContent } from '../../types/content';
|
|
3
3
|
import type { ContentCollectionHandle } from '../../types/config';
|
|
4
|
-
import type { BacklinksOptions, BacklinksResult, BacklinkSource, ContentVariant, ContentTreeItem, DocumentFromHandle, ManyOptions, LocalizedDoc, NeighborsOptions, NeighborsResult, OneOptions, PopulateSpec, PopulateFromOptions, PopulatedDocument, PaginationOptions, PaginationResult, ResolveOneOptions, ResolveOneResult, TreeOptions, VariantsOptions } from '../../types/query';
|
|
5
|
-
import { type ContentQueryContext } from '
|
|
4
|
+
import type { BacklinksOptions, BacklinksResult, BacklinkSource, ContentVariant, ContentTreeItem, DocumentFromHandle, ManyOptions, LocalizedDoc, NeighborsOptions, NeighborsResult, OneOptions, OptionsArg, PopulateSpec, PopulateFromOptions, PopulatedDocument, PaginationOptions, PaginationResult, ResolveOneOptions, ResolveOneResult, TreeOptions, VariantsOptions } from '../../types/query';
|
|
5
|
+
import { type ContentQueryContext } from '../../features/query/unified';
|
|
6
6
|
export declare const createServerContentQueryContext: (event: H3Event) => Promise<ContentQueryContext>;
|
|
7
7
|
export declare function resolveOne<const H extends ContentCollectionHandle | string, O extends ResolveOneOptions<H, PopulateSpec | undefined>>(event: H3Event, handle: H, options: O): Promise<ResolveOneResult<PopulatedDocument<DocumentFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
8
8
|
export declare function one<const H extends ContentCollectionHandle | string, O extends OneOptions<H, PopulateSpec | undefined>>(event: H3Event, handle: H, options: O): Promise<LocalizedDoc<PopulatedDocument<DocumentFromHandle<H>, PopulateFromOptions<O>>> | null>;
|
|
9
|
-
export declare function many<const H extends ContentCollectionHandle | string, O extends ManyOptions<H, PopulateSpec | undefined>>(event: H3Event, handle: H,
|
|
9
|
+
export declare function many<const H extends ContentCollectionHandle | string, O extends ManyOptions<H, PopulateSpec | undefined>>(event: H3Event, handle: H, ...args: OptionsArg<H, O>): Promise<Array<LocalizedDoc<PopulatedDocument<DocumentFromHandle<H>, PopulateFromOptions<O>>>>>;
|
|
10
10
|
export declare function paginate<const H extends ContentCollectionHandle | string, O extends PaginationOptions<H, PopulateSpec | undefined>>(event: H3Event, handle: H, options: O): Promise<PaginationResult<PopulatedDocument<DocumentFromHandle<H>, PopulateFromOptions<O>>>>;
|
|
11
11
|
export declare function backlinks<const Target extends ContentCollectionHandle | string, const Source extends BacklinkSource | ReadonlyArray<BacklinkSource>, P extends PopulateSpec | undefined = undefined>(event: H3Event, handle: Target, options: BacklinksOptions<Target, Source, P>): Promise<BacklinksResult<Source, P>>;
|
|
12
12
|
export declare function variants<H extends ContentCollectionHandle | string>(event: H3Event, handle: H, options: VariantsOptions<H>): Promise<Array<ContentVariant<H extends {
|
|
@@ -14,9 +14,9 @@ export declare function variants<H extends ContentCollectionHandle | string>(eve
|
|
|
14
14
|
_output: infer O;
|
|
15
15
|
};
|
|
16
16
|
} ? O & ParsedContent : ParsedContent>>>;
|
|
17
|
-
export declare function tree<H extends ContentCollectionHandle | string, Fields extends ReadonlyArray<string> | undefined = undefined>(event: H3Event, handle: H,
|
|
17
|
+
export declare function tree<H extends ContentCollectionHandle | string, Fields extends ReadonlyArray<string> | undefined = undefined>(event: H3Event, handle: H, ...args: OptionsArg<H, Omit<TreeOptions<H>, 'fields'> & {
|
|
18
18
|
fields?: Fields;
|
|
19
|
-
}): Promise<ContentTreeItem<H extends {
|
|
19
|
+
}>): Promise<ContentTreeItem<H extends {
|
|
20
20
|
__schema: {
|
|
21
21
|
_output: infer O;
|
|
22
22
|
};
|
|
@@ -7,11 +7,11 @@ import {
|
|
|
7
7
|
resolveOne as resolveOneWithContext,
|
|
8
8
|
tree as treeWithContext,
|
|
9
9
|
variants as variantsWithContext
|
|
10
|
-
} from "
|
|
10
|
+
} from "../../features/query/unified";
|
|
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
|
+
import { createProviderNavigationQuery, createProviderQuery, normalizeProviderQueryResponse } from "./provider-query.js";
|
|
15
15
|
export const createServerContentQueryContext = async (event) => {
|
|
16
16
|
const provider = await getContentProvider(event);
|
|
17
17
|
return {
|
|
@@ -23,9 +23,10 @@ export const createServerContentQueryContext = async (event) => {
|
|
|
23
23
|
provider: provider.name
|
|
24
24
|
});
|
|
25
25
|
}
|
|
26
|
-
|
|
26
|
+
const { query, options } = createProviderNavigationQuery(params);
|
|
27
|
+
return await provider.navigationQuery(event, query, options);
|
|
27
28
|
}
|
|
28
|
-
return normalizeProviderQueryResponse(params, await provider.query(event, params), provider.name);
|
|
29
|
+
return normalizeProviderQueryResponse(params, await provider.query(event, createProviderQuery(params)), provider.name);
|
|
29
30
|
}
|
|
30
31
|
};
|
|
31
32
|
};
|
|
@@ -35,7 +36,8 @@ export async function resolveOne(event, handle, options) {
|
|
|
35
36
|
export async function one(event, handle, options) {
|
|
36
37
|
return await oneWithContext(await createServerContentQueryContext(event), handle, options);
|
|
37
38
|
}
|
|
38
|
-
export async function many(event, handle,
|
|
39
|
+
export async function many(event, handle, ...args) {
|
|
40
|
+
const options = args[0] ?? {};
|
|
39
41
|
return await manyWithContext(await createServerContentQueryContext(event), handle, options);
|
|
40
42
|
}
|
|
41
43
|
export async function paginate(event, handle, options) {
|
|
@@ -47,7 +49,8 @@ export async function backlinks(event, handle, options) {
|
|
|
47
49
|
export async function variants(event, handle, options) {
|
|
48
50
|
return await variantsWithContext(await createServerContentQueryContext(event), handle, options);
|
|
49
51
|
}
|
|
50
|
-
export async function tree(event, handle,
|
|
52
|
+
export async function tree(event, handle, ...args) {
|
|
53
|
+
const options = args[0] ?? {};
|
|
51
54
|
return await treeWithContext(await createServerContentQueryContext(event), handle, options);
|
|
52
55
|
}
|
|
53
56
|
export async function neighbors(event, handle, options) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type H3Event } from 'h3';
|
|
2
2
|
import type { ContentQueryResponse } from '../../types/api';
|
|
3
|
-
import type {
|
|
4
|
-
export declare const executeFilesystemContentQuery: <T = unknown>(event: H3Event,
|
|
5
|
-
export declare const executeContentQuery: <T = unknown>(event: H3Event,
|
|
3
|
+
import type { ContentQueryPlan } from '../../core/query/plan';
|
|
4
|
+
export declare const executeFilesystemContentQuery: <T = unknown>(event: H3Event, inputPlan: ContentQueryPlan) => Promise<ContentQueryResponse<T>>;
|
|
5
|
+
export declare const executeContentQuery: <T = unknown>(event: H3Event, inputPlan: ContentQueryPlan) => Promise<ContentQueryResponse<T>>;
|
|
@@ -1,34 +1,30 @@
|
|
|
1
1
|
import { createError } from "h3";
|
|
2
|
-
import {
|
|
2
|
+
import { isPlanRegex } from "../../core/query/plan";
|
|
3
3
|
import { executeQueryPlan } from "../../core/query/execute";
|
|
4
4
|
import { ContentError } from "../../core/errors";
|
|
5
|
-
import { normalizeContentQueryParams } from "../../core/query/params";
|
|
6
|
-
import { compileWhere } from "../../core/query/filter";
|
|
7
|
-
import { containsStandaloneRegexOptions, findUnsupportedQueryOperator } from "../../core/query/operators";
|
|
8
5
|
import { withResolvedRefs, withResolvedRefsList } from "../../storage/references";
|
|
9
6
|
import { getContentGraph } from "../../storage/graph";
|
|
10
7
|
import { getContentRuntimeConfig } from "./runtime-config.js";
|
|
11
8
|
import { isPreview } from "../../integrations/nitro/preview";
|
|
12
|
-
import {
|
|
13
|
-
|
|
14
|
-
const notFound = (query, description = "Could not find document for the given query.") => {
|
|
9
|
+
import { MAX_PUBLIC_QUERY_LIMIT, MAX_PUBLIC_QUERY_SKIP } from "../../features/query/public-limits";
|
|
10
|
+
const notFound = (plan, description = "Could not find document for the given query.") => {
|
|
15
11
|
throw createError({
|
|
16
12
|
statusMessage: "Document not found!",
|
|
17
13
|
statusCode: 404,
|
|
18
14
|
data: {
|
|
19
15
|
description,
|
|
20
|
-
query
|
|
16
|
+
query: plan
|
|
21
17
|
}
|
|
22
18
|
});
|
|
23
19
|
};
|
|
24
20
|
const statusForContentError = {
|
|
25
21
|
INVALID_REF_VALUE: 422
|
|
26
22
|
};
|
|
27
|
-
const toHttpError = (error,
|
|
23
|
+
const toHttpError = (error, plan) => createError({
|
|
28
24
|
statusCode: statusForContentError[error.code] ?? 500,
|
|
29
25
|
statusMessage: error.code,
|
|
30
26
|
message: error.message,
|
|
31
|
-
data: { code: error.code, context: error.context, query }
|
|
27
|
+
data: { code: error.code, context: error.context, query: plan }
|
|
32
28
|
});
|
|
33
29
|
const badQuery = (message) => {
|
|
34
30
|
throw createError({
|
|
@@ -37,71 +33,59 @@ const badQuery = (message) => {
|
|
|
37
33
|
message
|
|
38
34
|
});
|
|
39
35
|
};
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
36
|
+
const planFilterContainsRegex = (filter) => {
|
|
37
|
+
switch (filter.type) {
|
|
38
|
+
case "true":
|
|
39
|
+
return false;
|
|
40
|
+
case "compare":
|
|
41
|
+
return filter.operator === "regex" || isPlanRegex(filter.value);
|
|
42
|
+
case "and":
|
|
43
|
+
case "or":
|
|
44
|
+
return filter.clauses.some(planFilterContainsRegex);
|
|
45
|
+
case "not":
|
|
46
|
+
return planFilterContainsRegex(filter.clause);
|
|
47
|
+
default:
|
|
48
|
+
throw new TypeError(`Unknown query filter node: ${filter.type}`);
|
|
43
49
|
}
|
|
44
|
-
if (!value || typeof value !== "object") {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
if (Array.isArray(value)) {
|
|
48
|
-
return value.some(containsPublicRegex);
|
|
49
|
-
}
|
|
50
|
-
const record = value;
|
|
51
|
-
return Object.entries(record).some(([key, child]) => key === "$regex" || containsPublicRegex(child));
|
|
52
50
|
};
|
|
53
|
-
const
|
|
54
|
-
|
|
51
|
+
const andPlanFilters = (base, ...extra) => {
|
|
52
|
+
const clauses = [base, ...extra].filter((clause) => clause.type !== "true");
|
|
53
|
+
if (!clauses.length) return { type: "true" };
|
|
54
|
+
if (clauses.length === 1) return clauses[0];
|
|
55
|
+
return { type: "and", clauses };
|
|
56
|
+
};
|
|
57
|
+
const applyFilesystemQueryPolicy = (event, plan) => {
|
|
58
|
+
if (!plan.collection) {
|
|
55
59
|
badQuery("Public content queries must target a collection.");
|
|
56
60
|
}
|
|
57
|
-
if (
|
|
61
|
+
if (planFilterContainsRegex(plan.filter)) {
|
|
58
62
|
badQuery("Public content queries do not accept RegExp filters.");
|
|
59
63
|
}
|
|
60
|
-
if (containsStandaloneRegexOptions(query.where)) {
|
|
61
|
-
badQuery("Query operator $options requires $regex.");
|
|
62
|
-
}
|
|
63
64
|
const enforceProductionVisibility = !import.meta.dev && !isPreview(event);
|
|
64
|
-
const
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const where = Array.isArray(normalized.where) ? normalized.where : normalized.where ? [normalized.where] : [];
|
|
76
|
-
normalized.where = [
|
|
77
|
-
...where,
|
|
78
|
-
{ _draft: { $ne: true } },
|
|
79
|
-
{ _partial: { $ne: true } }
|
|
80
|
-
];
|
|
81
|
-
}
|
|
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));
|
|
84
|
-
const unsupported = findUnsupportedQueryOperator(normalized.where);
|
|
85
|
-
if (unsupported) {
|
|
86
|
-
throw createContentProviderError("unsupported_query_operator", `Unsupported query operator: ${unsupported}`, {
|
|
87
|
-
operator: unsupported
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
return normalized;
|
|
65
|
+
const filter = enforceProductionVisibility ? andPlanFilters(
|
|
66
|
+
plan.filter,
|
|
67
|
+
{ type: "compare", field: "draft", operator: "ne", value: true },
|
|
68
|
+
{ type: "compare", field: "partial", operator: "ne", value: true }
|
|
69
|
+
) : plan.filter;
|
|
70
|
+
return {
|
|
71
|
+
...plan,
|
|
72
|
+
filter,
|
|
73
|
+
limit: typeof plan.limit === "number" ? Math.max(0, Math.min(plan.limit, MAX_PUBLIC_QUERY_LIMIT)) : plan.limit,
|
|
74
|
+
skip: Math.max(0, Math.min(plan.skip, MAX_PUBLIC_QUERY_SKIP))
|
|
75
|
+
};
|
|
91
76
|
};
|
|
92
|
-
export const executeFilesystemContentQuery = async (event,
|
|
77
|
+
export const executeFilesystemContentQuery = async (event, inputPlan) => {
|
|
93
78
|
const config = getContentRuntimeConfig().content || {};
|
|
94
|
-
const
|
|
79
|
+
const plan = applyFilesystemQueryPolicy(event, inputPlan);
|
|
95
80
|
let graph;
|
|
96
81
|
try {
|
|
97
82
|
graph = await getContentGraph(event);
|
|
98
83
|
} catch (cause) {
|
|
99
84
|
if (cause instanceof ContentError) {
|
|
100
|
-
throw toHttpError(cause,
|
|
85
|
+
throw toHttpError(cause, plan);
|
|
101
86
|
}
|
|
102
87
|
throw cause;
|
|
103
88
|
}
|
|
104
|
-
const plan = lowerQueryPlan(query);
|
|
105
89
|
const response = executeQueryPlan(graph, plan, {
|
|
106
90
|
defaultLocale: config.defaultLocale,
|
|
107
91
|
localeFallback: config.localeFallback,
|
|
@@ -114,7 +98,7 @@ export const executeFilesystemContentQuery = async (event, inputQuery) => {
|
|
|
114
98
|
if (plan.mode === "first") {
|
|
115
99
|
const content = response.result;
|
|
116
100
|
if (!content) {
|
|
117
|
-
notFound(
|
|
101
|
+
notFound(plan, plan.resolveVariant ? "Could not find document for the given route variant." : void 0);
|
|
118
102
|
}
|
|
119
103
|
return {
|
|
120
104
|
result: await withResolvedRefs(event, content, requestedLocale)
|
|
@@ -3,7 +3,7 @@ import type { ParsedContent } from '../../types/content';
|
|
|
3
3
|
import type { ContentQueryBuilderWhere } from '../../types/query';
|
|
4
4
|
import type { ContentSearchIndexRecord } from '../../types/search';
|
|
5
5
|
export { searchRecords } from '../shared/search';
|
|
6
|
-
type SearchablePage = Pick<ParsedContent, '
|
|
6
|
+
type SearchablePage = Pick<ParsedContent, 'path' | 'locale' | 'title' | 'description' | 'body'> & Record<string, unknown>;
|
|
7
7
|
type RuntimeSearchConfig = {
|
|
8
8
|
search?: {
|
|
9
9
|
collections?: string[];
|