@lupinum/ginko-content 0.3.0 → 0.3.2
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/dist/core/content/graph.d.ts +15 -6
- package/dist/core/content/graph.js +20 -2
- package/dist/features/validation/links.js +4 -4
- package/dist/module.json +1 -1
- package/dist/module.mjs +1 -1
- package/dist/runtime/app/composables/query-api.js +8 -2
- package/dist/runtime/app/composables/utils.d.ts +7 -7
- package/dist/runtime/app/composables/utils.js +12 -29
- package/dist/storage/graph.d.ts +1 -0
- package/dist/storage/graph.js +1 -0
- package/dist/storage/references.js +9 -7
- package/dist/types/virtual.d.ts +1 -0
- package/dist/web-types.json +1 -1
- package/package.json +1 -1
|
@@ -18,10 +18,9 @@
|
|
|
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.
|
|
21
|
-
* - **`referenceTargets`**:
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
* key.
|
|
21
|
+
* - **`referenceTargets`**: every unambiguous shape the user might write
|
|
22
|
+
* (canonical name, locale-prefixed path, short slug) pre-resolved to its
|
|
23
|
+
* canonical key and owning collection.
|
|
25
24
|
*
|
|
26
25
|
* The graph is rebuilt from scratch on every request in dev; in production
|
|
27
26
|
* it is memoized per-request via `memoizeRuntimeValue(event, 'graph', ...)`.
|
|
@@ -32,6 +31,10 @@ import type { ContentVariantIdentity, ResolvedVariant } from '../../types/runtim
|
|
|
32
31
|
export interface ContentGraphVariant extends ContentVariantIdentity {
|
|
33
32
|
document: ParsedContent;
|
|
34
33
|
}
|
|
34
|
+
export interface ContentGraphReferenceTarget {
|
|
35
|
+
readonly canonicalKey: string;
|
|
36
|
+
readonly collection: string;
|
|
37
|
+
}
|
|
35
38
|
export interface ContentGraph {
|
|
36
39
|
documents: ParsedContent[];
|
|
37
40
|
/** id → document. Keys include the locale suffix; one entry per source file. */
|
|
@@ -52,8 +55,8 @@ export interface ContentGraph {
|
|
|
52
55
|
byRef: Record<string, string>;
|
|
53
56
|
/** navigation path → `{ [locale]: document }`. Drives `.navigation.yml` merging. */
|
|
54
57
|
byNavigationPath: Record<string, Record<string, ParsedContent>>;
|
|
55
|
-
/** Every user-writable reference shape
|
|
56
|
-
referenceTargets: Map<string,
|
|
58
|
+
/** Every unambiguous user-writable reference shape with its collection scope intact. */
|
|
59
|
+
referenceTargets: Map<string, ContentGraphReferenceTarget>;
|
|
57
60
|
/** Exact collection-scoped user-writable reference targets. */
|
|
58
61
|
referenceTargetsByCollection: Record<string, Map<string, string>>;
|
|
59
62
|
}
|
|
@@ -134,6 +137,12 @@ export declare const resolveGraphRouteVariant: (graph: ContentGraph, routePath:
|
|
|
134
137
|
* leak through.
|
|
135
138
|
*/
|
|
136
139
|
export declare const resolveGraphCanonicalKey: (graph: ContentGraph, identity: string, collection?: string) => string | null;
|
|
140
|
+
/**
|
|
141
|
+
* Resolve a user-authored reference without discarding the collection that
|
|
142
|
+
* made the identity unambiguous. Canonical keys are collection-scoped, so
|
|
143
|
+
* callers that subsequently resolve a locale variant must carry both values.
|
|
144
|
+
*/
|
|
145
|
+
export declare const resolveGraphReferenceTarget: (graph: ContentGraph, identity: string, collection?: string) => ContentGraphReferenceTarget | null;
|
|
137
146
|
export declare const resolveGraphCollectionLocales: (graph: ContentGraph, identity: string, collection?: string) => ContentLocaleEntry[];
|
|
138
147
|
export declare const selectGraphDocuments: (graph: ContentGraph, options?: {
|
|
139
148
|
collection?: string;
|
|
@@ -100,7 +100,10 @@ export const buildContentGraph = (documents, options = {}) => {
|
|
|
100
100
|
}
|
|
101
101
|
const referenceTargets = /* @__PURE__ */ new Map();
|
|
102
102
|
for (const [identity, collections] of referenceTargetCollections) {
|
|
103
|
-
if (collections.length
|
|
103
|
+
if (collections.length !== 1) continue;
|
|
104
|
+
const collection = collections[0];
|
|
105
|
+
const canonicalKey = referenceTargetsByCollection[collection].get(identity);
|
|
106
|
+
if (canonicalKey) referenceTargets.set(identity, { canonicalKey, collection });
|
|
104
107
|
}
|
|
105
108
|
return {
|
|
106
109
|
documents,
|
|
@@ -180,9 +183,24 @@ export const resolveGraphCanonicalKey = (graph, identity, collection) => {
|
|
|
180
183
|
if (collection && !(graph.byCollection[collection] || []).length) {
|
|
181
184
|
return null;
|
|
182
185
|
}
|
|
183
|
-
const targetCanonicalKey = collection ? graph.referenceTargetsByCollection[collection]?.get(normalizedIdentity) : graph.referenceTargets.get(normalizedIdentity);
|
|
186
|
+
const targetCanonicalKey = collection ? graph.referenceTargetsByCollection[collection]?.get(normalizedIdentity) : graph.referenceTargets.get(normalizedIdentity)?.canonicalKey;
|
|
184
187
|
return targetCanonicalKey && hasCollectionVariant(targetCanonicalKey) ? targetCanonicalKey : null;
|
|
185
188
|
};
|
|
189
|
+
export const resolveGraphReferenceTarget = (graph, identity, collection) => {
|
|
190
|
+
const normalizedIdentity = normalizeReferenceValue(identity);
|
|
191
|
+
if (!normalizedIdentity) return null;
|
|
192
|
+
if (collection) {
|
|
193
|
+
const canonicalKey = resolveGraphCanonicalKey(graph, normalizedIdentity, collection);
|
|
194
|
+
return canonicalKey ? { canonicalKey, collection } : null;
|
|
195
|
+
}
|
|
196
|
+
const canonicalVariants = graph.byCanonical[normalizedIdentity];
|
|
197
|
+
if (canonicalVariants) {
|
|
198
|
+
const targetCollection = Object.values(canonicalVariants)[0]?.document.collection || "content";
|
|
199
|
+
return { canonicalKey: normalizedIdentity, collection: targetCollection };
|
|
200
|
+
}
|
|
201
|
+
const target = graph.referenceTargets.get(normalizedIdentity);
|
|
202
|
+
return target && getGraphCanonicalVariants(graph, target.canonicalKey, target.collection) ? target : null;
|
|
203
|
+
};
|
|
186
204
|
export const resolveGraphCollectionLocales = (graph, identity, collection) => {
|
|
187
205
|
const canonicalKey = resolveGraphCanonicalKey(graph, identity, collection);
|
|
188
206
|
if (!canonicalKey) {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getGraphCanonicalVariants, resolveGraphReferenceTarget } from "../../core/content/graph.js";
|
|
2
2
|
import { parseRefLink } from "../../core/references/resolve.js";
|
|
3
3
|
const normalizePath = (path) => {
|
|
4
4
|
const normalized = path.replace(/\/{2,}/g, "/").replace(/\/$/, "");
|
|
@@ -98,11 +98,11 @@ export const validateContentLinks = async (documents, options) => {
|
|
|
98
98
|
}
|
|
99
99
|
continue;
|
|
100
100
|
}
|
|
101
|
-
const
|
|
102
|
-
const variants =
|
|
101
|
+
const target = resolveGraphReferenceTarget(options.graph, parsedRef.ref);
|
|
102
|
+
const variants = target ? getGraphCanonicalVariants(options.graph, target.canonicalKey, target.collection) : void 0;
|
|
103
103
|
const variant = variants?.[document.locale || ""] || variants?.[options.defaultLocale || ""] || Object.values(variants || {})[0];
|
|
104
104
|
const targetDocument2 = variant ? options.graph.byId[variant.contentId] : void 0;
|
|
105
|
-
const targetRoute = targetDocument2 ? routeByIdentity.get(identityKey(
|
|
105
|
+
const targetRoute = target && targetDocument2 ? routeByIdentity.get(identityKey(target.collection, target.canonicalKey, variant?.locale)) : void 0;
|
|
106
106
|
if (!targetRoute) {
|
|
107
107
|
addBroken(sourceFile, authoredValue);
|
|
108
108
|
continue;
|
package/dist/module.json
CHANGED
package/dist/module.mjs
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
createPrerenderPathAdder,
|
|
3
|
+
fetchContentApi,
|
|
4
|
+
getContentApiFetcher,
|
|
5
|
+
getPreviewToken
|
|
6
|
+
} from "./utils.js";
|
|
2
7
|
import { getContentRuntime } from "./runtime.js";
|
|
3
8
|
import {
|
|
4
9
|
backlinks as backlinksWithContext,
|
|
@@ -13,13 +18,14 @@ export const createClientContentQueryContext = () => {
|
|
|
13
18
|
const runtime = getContentRuntime();
|
|
14
19
|
const fetcher = getContentApiFetcher();
|
|
15
20
|
const previewToken = getPreviewToken() ?? null;
|
|
21
|
+
const addPrerenderPath = createPrerenderPathAdder();
|
|
16
22
|
return {
|
|
17
23
|
runtime,
|
|
18
24
|
transport: async (endpoint, params) => {
|
|
19
25
|
return await fetchContentApi(
|
|
20
26
|
endpoint,
|
|
21
27
|
params,
|
|
22
|
-
{ fetcher, runtime, previewToken }
|
|
28
|
+
{ fetcher, runtime, previewToken, addPrerenderPath }
|
|
23
29
|
);
|
|
24
30
|
}
|
|
25
31
|
};
|
|
@@ -7,17 +7,17 @@ interface ContentRuntimeShape {
|
|
|
7
7
|
}
|
|
8
8
|
export declare const withContentBase: (url: string) => string;
|
|
9
9
|
export declare const navigationDisabled: () => never;
|
|
10
|
-
export declare const
|
|
10
|
+
export declare const createPrerenderPathAdder: () => ((path: string) => void) | undefined;
|
|
11
11
|
export type ContentApiEndpoint = 'query' | 'navigation';
|
|
12
12
|
export type ContentApiFetcher = (request: string, init?: Record<string, unknown>) => Promise<unknown>;
|
|
13
13
|
export declare const getPreviewToken: () => any;
|
|
14
14
|
export declare const getContentApiFetcher: (fetcher?: ContentApiFetcher) => ContentApiFetcher;
|
|
15
|
-
export declare const buildContentApiPath: (endpoint: ContentApiEndpoint, params: ContentProviderQueryInput, runtime
|
|
15
|
+
export declare const buildContentApiPath: (endpoint: ContentApiEndpoint, params: ContentProviderQueryInput, runtime: ContentRuntimeShape) => string;
|
|
16
16
|
export declare const isHtmlFallbackResponse: (data: unknown) => data is string;
|
|
17
|
-
export declare function fetchContentApi<T>(endpoint: ContentApiEndpoint, params: ContentProviderQueryInput, options
|
|
18
|
-
fetcher
|
|
19
|
-
runtime
|
|
20
|
-
|
|
21
|
-
|
|
17
|
+
export declare function fetchContentApi<T>(endpoint: ContentApiEndpoint, params: ContentProviderQueryInput, options: {
|
|
18
|
+
fetcher: ContentApiFetcher;
|
|
19
|
+
runtime: ContentRuntimeShape;
|
|
20
|
+
previewToken: string | null;
|
|
21
|
+
addPrerenderPath?: (path: string) => void;
|
|
22
22
|
}): Promise<T>;
|
|
23
23
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { withBase } from "ufo";
|
|
2
2
|
import { hash } from "ohash";
|
|
3
|
-
import { useRequestEvent, useRequestFetch } from "#imports";
|
|
3
|
+
import { tryUseNuxtApp, useRequestEvent, useRequestFetch } from "#imports";
|
|
4
4
|
import { encodeQueryParams } from "../../utils/query.js";
|
|
5
5
|
import { useContentPreview } from "./preview.js";
|
|
6
6
|
import { getContentRuntime } from "./runtime.js";
|
|
@@ -11,13 +11,6 @@ export const navigationDisabled = () => {
|
|
|
11
11
|
console.warn("Learn more in the Ginko navigation documentation.");
|
|
12
12
|
throw new Error("Navigation is only accessible when you enable it in module options.");
|
|
13
13
|
};
|
|
14
|
-
const lookupRequestEvent = () => {
|
|
15
|
-
try {
|
|
16
|
-
return useRequestEvent();
|
|
17
|
-
} catch {
|
|
18
|
-
return void 0;
|
|
19
|
-
}
|
|
20
|
-
};
|
|
21
14
|
const addPathToEvent = (event, path) => {
|
|
22
15
|
event.node.res.setHeader(
|
|
23
16
|
"x-nitro-prerender",
|
|
@@ -27,16 +20,12 @@ const addPathToEvent = (event, path) => {
|
|
|
27
20
|
].filter(Boolean).join(",")
|
|
28
21
|
);
|
|
29
22
|
};
|
|
30
|
-
const createPrerenderPathAdder = () => {
|
|
31
|
-
|
|
23
|
+
export const createPrerenderPathAdder = () => {
|
|
24
|
+
if (import.meta.dev || !import.meta.server) return void 0;
|
|
25
|
+
const nuxtApp = tryUseNuxtApp();
|
|
26
|
+
const event = nuxtApp ? useRequestEvent(nuxtApp) : void 0;
|
|
32
27
|
return event ? (path) => addPathToEvent(event, path) : void 0;
|
|
33
28
|
};
|
|
34
|
-
export const addPrerenderPath = (path) => {
|
|
35
|
-
const event = lookupRequestEvent();
|
|
36
|
-
if (event) {
|
|
37
|
-
addPathToEvent(event, path);
|
|
38
|
-
}
|
|
39
|
-
};
|
|
40
29
|
export const getPreviewToken = () => useContentPreview().getPreviewToken();
|
|
41
30
|
export const getContentApiFetcher = (fetcher) => {
|
|
42
31
|
if (fetcher) {
|
|
@@ -48,32 +37,26 @@ export const getContentApiFetcher = (fetcher) => {
|
|
|
48
37
|
return $fetch;
|
|
49
38
|
};
|
|
50
39
|
export const buildContentApiPath = (endpoint, params, runtime) => {
|
|
51
|
-
const content = runtime || readContentRuntime();
|
|
52
40
|
const encodedParams = encodeQueryParams(params);
|
|
53
|
-
const requestKey = import.meta.dev ? "_" : `${hash(params)}.${
|
|
54
|
-
return withBase(`/${endpoint}/${requestKey}/${encodedParams}.json`,
|
|
41
|
+
const requestKey = import.meta.dev ? "_" : `${hash(params)}.${runtime.integrity}`;
|
|
42
|
+
return withBase(`/${endpoint}/${requestKey}/${encodedParams}.json`, runtime.api.baseURL);
|
|
55
43
|
};
|
|
56
44
|
export const isHtmlFallbackResponse = (data) => {
|
|
57
45
|
return typeof data === "string" && data.startsWith("<!DOCTYPE html>");
|
|
58
46
|
};
|
|
59
|
-
export async function fetchContentApi(endpoint, params, options
|
|
47
|
+
export async function fetchContentApi(endpoint, params, options) {
|
|
60
48
|
const apiPath = buildContentApiPath(endpoint, params, options.runtime);
|
|
61
|
-
const
|
|
62
|
-
const addPrerenderPathOnSuccess = !import.meta.dev && import.meta.server ? createPrerenderPathAdder() : void 0;
|
|
63
|
-
const fetcher = getContentApiFetcher(options.fetcher);
|
|
64
|
-
const data = await fetcher(apiPath, {
|
|
49
|
+
const data = await options.fetcher(apiPath, {
|
|
65
50
|
method: "GET",
|
|
66
51
|
responseType: "json",
|
|
67
|
-
...previewToken ? { headers: { "x-nuxt-content-preview": previewToken } } : {}
|
|
52
|
+
...options.previewToken ? { headers: { "x-nuxt-content-preview": options.previewToken } } : {}
|
|
68
53
|
});
|
|
69
54
|
if (isHtmlFallbackResponse(data)) {
|
|
70
|
-
throw new Error(
|
|
55
|
+
throw new Error("Not found");
|
|
71
56
|
}
|
|
72
57
|
if (data === void 0 || data === null) {
|
|
73
58
|
throw new TypeError("Invalid content API response: expected a non-empty JSON body.");
|
|
74
59
|
}
|
|
75
|
-
|
|
76
|
-
addPrerenderPathOnSuccess?.(apiPath);
|
|
77
|
-
}
|
|
60
|
+
options.addPrerenderPath?.(apiPath);
|
|
78
61
|
return data;
|
|
79
62
|
}
|
package/dist/storage/graph.d.ts
CHANGED
|
@@ -19,6 +19,7 @@ export declare const getContentGraph: (event: H3Event) => Promise<ContentGraph>;
|
|
|
19
19
|
export declare function resolveVariant(event: H3Event, canonicalKey: string, requestedLocale?: string, options?: {
|
|
20
20
|
fallback?: string[];
|
|
21
21
|
exact?: boolean;
|
|
22
|
+
collection?: string;
|
|
22
23
|
}): Promise<import("../types/runtime").ResolvedVariant | null>;
|
|
23
24
|
export declare function resolveRouteVariant(event: H3Event, routePath: string, requestedLocale?: string, options?: {
|
|
24
25
|
fallback?: string[];
|
package/dist/storage/graph.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
import { resolveGraphReferenceTarget } from "../core/content/graph.js";
|
|
1
2
|
import { collectMarkdownRefLinks, parseRefLink } from "../core/references/resolve.js";
|
|
2
3
|
import { projectContentRoute } from "../features/localization/route-projector.js";
|
|
3
4
|
import { resolveRuntimeCollectionLocalePolicy } from "../features/localization/config.js";
|
|
4
5
|
import { contentConfig } from "./driver.js";
|
|
5
|
-
import { getContentGraph,
|
|
6
|
+
import { getContentGraph, resolveVariant } from "./graph.js";
|
|
6
7
|
const isConfiguredQuickLink = (href) => {
|
|
7
8
|
const parsed = parseRefLink(href);
|
|
8
9
|
if (!parsed) {
|
|
@@ -33,8 +34,8 @@ const resolveDocumentRefLinks = async (event, content, requestedLocale) => {
|
|
|
33
34
|
if (!parsed) {
|
|
34
35
|
return null;
|
|
35
36
|
}
|
|
36
|
-
const
|
|
37
|
-
if (!
|
|
37
|
+
const target = resolveGraphReferenceTarget(graph, parsed.ref);
|
|
38
|
+
if (!target) {
|
|
38
39
|
if (isConfiguredQuickLink(href)) {
|
|
39
40
|
return [href, href];
|
|
40
41
|
}
|
|
@@ -45,7 +46,9 @@ const resolveDocumentRefLinks = async (event, content, requestedLocale) => {
|
|
|
45
46
|
}
|
|
46
47
|
return [href, href];
|
|
47
48
|
}
|
|
48
|
-
const variant = await resolveVariant(event, canonicalKey, requestedLocale
|
|
49
|
+
const variant = await resolveVariant(event, target.canonicalKey, requestedLocale, {
|
|
50
|
+
collection: target.collection
|
|
51
|
+
});
|
|
49
52
|
if (!variant?.path) {
|
|
50
53
|
if (import.meta.dev) {
|
|
51
54
|
console.warn(
|
|
@@ -60,10 +63,9 @@ const resolveDocumentRefLinks = async (event, content, requestedLocale) => {
|
|
|
60
63
|
);
|
|
61
64
|
}
|
|
62
65
|
const routeLocale = variant.fallback && requestedLocale ? requestedLocale : variant.resolvedLocale;
|
|
63
|
-
const
|
|
64
|
-
const targetPolicy = targetCollection ? resolveRuntimeCollectionLocalePolicy(targetCollection, config) : void 0;
|
|
66
|
+
const targetPolicy = resolveRuntimeCollectionLocalePolicy(target.collection, config);
|
|
65
67
|
if (!targetPolicy) {
|
|
66
|
-
throw new Error(`Missing resolved locale policy for content collection "${
|
|
68
|
+
throw new Error(`Missing resolved locale policy for content collection "${target.collection}".`);
|
|
67
69
|
}
|
|
68
70
|
return [
|
|
69
71
|
href,
|
package/dist/types/virtual.d.ts
CHANGED
|
@@ -51,6 +51,7 @@ declare module '#imports' {
|
|
|
51
51
|
export const useFetch: typeof import('#app').useFetch
|
|
52
52
|
export const useHead: typeof import('#app').useHead
|
|
53
53
|
export const useNuxtApp: typeof import('#app').useNuxtApp
|
|
54
|
+
export const tryUseNuxtApp: typeof import('#app').tryUseNuxtApp
|
|
54
55
|
export const useCookie: typeof import('#app').useCookie
|
|
55
56
|
export const useRequestEvent: typeof import('#app').useRequestEvent
|
|
56
57
|
export const useRequestFetch: typeof import('#app').useRequestFetch
|
package/dist/web-types.json
CHANGED