@lupinum/ginko-content 0.3.0-rc.4 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -5
- package/dist/cms-contract/index.d.ts +2 -2
- package/dist/cms-contract/index.js +1 -2
- package/dist/cms-contract/mdc.js +6 -4
- package/dist/cms-contract/path.d.ts +1 -1
- package/dist/cms-contract/path.js +0 -1
- package/dist/cms-contract/render-policy.js +4 -0
- package/dist/cms-contract/schema.d.ts +1 -1
- package/dist/cms-contract/schema.js +3 -1
- package/dist/core/content/graph.d.ts +7 -5
- package/dist/core/content/graph.js +33 -40
- package/dist/core/content/locale.d.ts +3 -1
- package/dist/core/content/locale.js +1 -0
- package/dist/core/content/path.d.ts +26 -1
- package/dist/core/content/path.js +43 -13
- package/dist/core/content/structural.d.ts +1 -2
- package/dist/core/content/structural.js +1 -1
- package/dist/core/data-source-error.d.ts +5 -0
- package/dist/core/data-source-error.js +10 -0
- package/dist/core/errors.d.ts +1 -1
- package/dist/core/markdown/normalize-comark.d.ts +6 -0
- package/dist/core/markdown/normalize-comark.js +15 -0
- package/dist/core/markdown/parse-comark.d.ts +3 -0
- package/dist/core/markdown/parse-comark.js +30 -0
- package/dist/core/provider-route-record.d.ts +18 -0
- package/dist/core/provider-route-record.js +106 -0
- package/dist/core/query/execute.d.ts +6 -6
- package/dist/core/query/execute.js +46 -67
- package/dist/core/query/limits.d.ts +2 -0
- package/dist/core/query/limits.js +2 -0
- package/dist/core/query/lower.d.ts +9 -3
- package/dist/core/query/lower.js +170 -89
- package/dist/core/query/plan.d.ts +88 -97
- package/dist/core/references/resolve.d.ts +1 -1
- package/dist/core/references/resolve.js +31 -13
- package/dist/core/references/schema.d.ts +2 -0
- package/dist/core/references/schema.js +6 -8
- package/dist/core/runtime-diagnostics.d.ts +7 -0
- package/dist/core/runtime-diagnostics.js +9 -0
- package/dist/features/agent/walker.js +10 -7
- package/dist/features/collections/resolve.d.ts +0 -2
- package/dist/features/collections/resolve.js +2 -3
- package/dist/features/localization/config.d.ts +8 -0
- package/dist/features/localization/config.js +3 -1
- package/dist/features/localization/locale-policy.d.ts +10 -2
- package/dist/features/localization/locale-policy.js +81 -35
- package/dist/features/localization/path.d.ts +0 -10
- package/dist/features/localization/path.js +1 -23
- package/dist/features/localization/reference-path.d.ts +7 -0
- package/dist/features/localization/reference-path.js +10 -0
- package/dist/features/localization/results.d.ts +3 -26
- package/dist/features/localization/results.js +10 -34
- package/dist/features/localization/route-projector.d.ts +17 -9
- package/dist/features/localization/route-projector.js +35 -29
- package/dist/features/navigation/build.js +2 -1
- package/dist/features/navigation/canonical.d.ts +2 -8
- package/dist/features/navigation/canonical.js +8 -48
- package/dist/features/navigation/diagnostics.d.ts +11 -0
- package/dist/features/navigation/diagnostics.js +52 -0
- package/dist/features/navigation/resolve.d.ts +18 -2
- package/dist/features/navigation/resolve.js +28 -5
- package/dist/features/query/context.d.ts +4 -2
- package/dist/features/query/diagnostics.d.ts +4 -0
- package/dist/features/query/diagnostics.js +22 -0
- package/dist/features/query/documents.js +6 -10
- package/dist/features/query/localized-docs.d.ts +0 -7
- package/dist/features/query/localized-docs.js +5 -30
- package/dist/features/query/navigation.js +2 -11
- package/dist/features/query/pagination.js +3 -6
- package/dist/features/query/populate.d.ts +1 -0
- package/dist/features/query/populate.js +76 -13
- package/dist/features/query/query-plan-boundary.d.ts +9 -0
- package/dist/features/query/query-plan-boundary.js +137 -0
- package/dist/features/query/responses.d.ts +3 -0
- package/dist/features/query/responses.js +4 -4
- package/dist/features/query/routes.d.ts +3 -10
- package/dist/features/query/routes.js +23 -13
- package/dist/integrations/nitro/build.js +14 -14
- package/dist/integrations/nitro/ingest.js +14 -5
- package/dist/integrations/nitro/preview.js +23 -6
- package/dist/module.d.mts +2 -0
- package/dist/module.json +1 -1
- package/dist/module.mjs +27 -14
- package/dist/parsers/markdown.js +5 -6
- package/dist/parsers/path-meta.js +13 -6
- package/dist/portability/mdc.js +9 -4
- package/dist/public/client.d.ts +1 -0
- package/dist/public/data-source.d.ts +29 -16
- package/dist/public/data-source.js +3 -0
- package/dist/public/navigation.d.ts +7 -0
- package/dist/public/navigation.js +8 -0
- package/dist/public/provider-binder.js +62 -32
- package/dist/public/provider-contract.d.ts +15 -4
- package/dist/public/provider-document.d.ts +19 -17
- package/dist/public/provider-document.js +4 -12
- package/dist/public/provider-query.d.ts +63 -30
- package/dist/public/provider-query.js +44 -14
- package/dist/public/provider.d.ts +1 -1
- package/dist/public/server.d.ts +2 -2
- package/dist/public/server.js +0 -1
- package/dist/runtime/app/components/ContentRendererInline.vue +2 -2
- package/dist/runtime/app/composables/utils.js +3 -0
- package/dist/runtime/server/agent-markdown.js +9 -54
- package/dist/runtime/server/agent-site.js +12 -24
- package/dist/runtime/server/api/locales.js +1 -1
- package/dist/runtime/server/api/navigation.js +15 -11
- package/dist/runtime/server/api/query.d.ts +1 -1
- package/dist/runtime/server/api/query.js +2 -1
- package/dist/runtime/server/api/revalidate.js +2 -2
- package/dist/runtime/server/cache-adapter.js +2 -2
- package/dist/runtime/server/cache-adapters.d.ts +2 -12
- package/dist/runtime/server/cache-adapters.js +0 -9
- package/dist/runtime/server/collection-helpers.js +16 -19
- package/dist/runtime/server/index.d.ts +1 -1
- package/dist/runtime/server/index.js +0 -1
- package/dist/runtime/server/middleware/agent-link-headers.js +5 -2
- package/dist/runtime/server/middleware/preview.d.ts +2 -0
- package/dist/runtime/server/middleware/preview.js +7 -0
- package/dist/runtime/server/navigation-query.d.ts +7 -2
- package/dist/runtime/server/navigation-query.js +60 -16
- package/dist/runtime/server/pagefind.js +8 -9
- package/dist/runtime/server/provider-query.d.ts +14 -17
- package/dist/runtime/server/provider-query.js +108 -105
- package/dist/runtime/server/provider-result.d.ts +2 -2
- package/dist/runtime/server/provider-route-facts.js +68 -90
- package/dist/runtime/server/providers/filesystem.js +43 -62
- package/dist/runtime/server/providers/index.js +5 -5
- package/dist/runtime/server/query-api.js +12 -5
- package/dist/runtime/server/query-executor.d.ts +2 -2
- package/dist/runtime/server/query-executor.js +6 -2
- package/dist/runtime/server/query-http-validation.d.ts +8 -22
- package/dist/runtime/server/query-http-validation.js +91 -341
- package/dist/storage/graph.js +6 -1
- package/dist/storage/references.js +10 -4
- package/dist/storage/snapshot-runtime.js +6 -1
- package/dist/storage/validation.d.ts +5 -2
- package/dist/storage/validation.js +48 -6
- package/dist/testing/data-source-contract.d.ts +19 -0
- package/dist/testing/data-source-contract.js +32 -3
- package/dist/testing/provider-contract.js +4 -11
- package/dist/testing/provider-fixture.d.ts +8 -2
- package/dist/testing/provider-fixture.js +86 -17
- package/dist/types/api.d.ts +10 -0
- package/dist/types/config.d.ts +2 -9
- package/dist/types/config.js +2 -2
- package/dist/types/fields.d.ts +1 -2
- package/dist/types/fields.js +28 -19
- package/dist/types/module.d.ts +3 -3
- package/dist/types/navigation.d.ts +13 -0
- package/dist/types/navigation.js +28 -0
- package/dist/types/query-parts/public.d.ts +4 -3
- package/dist/types/query-parts/results.d.ts +0 -1
- package/dist/types/reference.d.ts +14 -1
- package/dist/types/reference.js +13 -1
- package/dist/types/runtime.d.ts +4 -15
- package/dist/web-types.json +1 -1
- package/package.json +6 -1
package/README.md
CHANGED
|
@@ -10,8 +10,7 @@ Use it when you want content files to stay simple, but your Nuxt app still
|
|
|
10
10
|
needs explicit APIs for route resolution, typed frontmatter, localized content,
|
|
11
11
|
and server-side reads.
|
|
12
12
|
|
|
13
|
-
This README describes the `0.3`
|
|
14
|
-
channel; `latest` remains `0.2.1` until the stable cutover.
|
|
13
|
+
This README describes the stable `0.3` line.
|
|
15
14
|
|
|
16
15
|
## Requirements
|
|
17
16
|
|
|
@@ -23,14 +22,14 @@ channel; `latest` remains `0.2.1` until the stable cutover.
|
|
|
23
22
|
## Install
|
|
24
23
|
|
|
25
24
|
```bash
|
|
26
|
-
npx nuxi module add @lupinum/ginko-content
|
|
25
|
+
npx nuxi module add @lupinum/ginko-content
|
|
27
26
|
```
|
|
28
27
|
|
|
29
28
|
The Nuxt CLI installs the package and registers the module in `nuxt.config.ts`.
|
|
30
29
|
If you prefer to install by hand:
|
|
31
30
|
|
|
32
31
|
```bash
|
|
33
|
-
pnpm add @lupinum/ginko-content
|
|
32
|
+
pnpm add @lupinum/ginko-content
|
|
34
33
|
```
|
|
35
34
|
|
|
36
35
|
```ts
|
|
@@ -79,7 +78,7 @@ Render the current route through the collection:
|
|
|
79
78
|
```vue
|
|
80
79
|
<!-- pages/[...slug].vue -->
|
|
81
80
|
<script setup lang="ts">
|
|
82
|
-
import { pages } from '
|
|
81
|
+
import { pages } from '~~/content.config'
|
|
83
82
|
|
|
84
83
|
const { page } = await useContentPage(pages)
|
|
85
84
|
</script>
|
|
@@ -22,8 +22,8 @@
|
|
|
22
22
|
*/
|
|
23
23
|
export { RESOLVED_CONTENT_CONTRACT_VERSION, buildResolvedContentContract, type BuildResolvedContentContractInput, type BuildResolvedContentContractOptions, } from './build.js';
|
|
24
24
|
export type { PortableComponentPolicyV1, PortableMediaType, ResolvedContentCollectionV1, ResolvedContentContractV1, ResolvedContentFieldTypeV1, ResolvedContentFieldV1, ResolvedContentValidationV1, ContentCmsCollectionConfig, ContentCmsFieldConfig, ContentCmsFieldType, ContentCmsRelationConfig, ContentCollectionConfig, ContentCollectionI18nConfig, } from './types.js';
|
|
25
|
-
export { describeId, generatePath, generateCanonicalKey, generateTitle, isDraftPath, isPartialPath,
|
|
26
|
-
export { CONTENT_FIELD_METADATA_KEY,
|
|
25
|
+
export { describeId, generatePath, generateCanonicalKey, generateTitle, isDraftPath, isPartialPath, mountContentPath, normalizeContentPath, normalizeRouteMounts, prefixPathWithLocale, refineUrlPart, routeRemainder, routeToContentPathCandidates, slugifyUrlSegment, stripLocalePrefix, } from './path.js';
|
|
26
|
+
export { CONTENT_FIELD_METADATA_KEY, CONTENT_REFERENCE_METADATA_KEY, collectTopLevelReferenceFields, getContentFieldMetadata, getObjectShape, getReferenceDescriptor, getSchemaDef, getSchemaTypeName, unwrapSchema, type ContentFieldMetadata, type ContentFieldSchema, } from './schema.js';
|
|
27
27
|
export { parseMdcBody, type ParseMdcBodyOptions, type ParseMdcBodyResult, } from './mdc.js';
|
|
28
28
|
export { assertPublicMarkdownAst, isSafePublicMarkdownUrl, PublicMarkdownValidationError, validatePublicMarkdownAst, type PublicMarkdownIssue, type PublicMarkdownIssueCode, type PublicMarkdownValidationResult, } from './render-policy.js';
|
|
29
29
|
export { verifyPublicImageBytes, type VerifiedPublicImage, } from './asset-bytes.js';
|
|
@@ -9,7 +9,6 @@ export {
|
|
|
9
9
|
generateTitle,
|
|
10
10
|
isDraftPath,
|
|
11
11
|
isPartialPath,
|
|
12
|
-
longestMountForPath,
|
|
13
12
|
mountContentPath,
|
|
14
13
|
normalizeContentPath,
|
|
15
14
|
normalizeRouteMounts,
|
|
@@ -22,7 +21,7 @@ export {
|
|
|
22
21
|
} from "./path.js";
|
|
23
22
|
export {
|
|
24
23
|
CONTENT_FIELD_METADATA_KEY,
|
|
25
|
-
|
|
24
|
+
CONTENT_REFERENCE_METADATA_KEY,
|
|
26
25
|
collectTopLevelReferenceFields,
|
|
27
26
|
getContentFieldMetadata,
|
|
28
27
|
getObjectShape,
|
package/dist/cms-contract/mdc.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { normalizeComarkNodes } from "../core/markdown/normalize-comark.js";
|
|
2
|
+
import { parseComark } from "../core/markdown/parse-comark.js";
|
|
2
3
|
import { mapMarkdownNodes, toMarkdownRoot } from "../core/markdown/tree.js";
|
|
3
4
|
export async function parseMdcBody(raw, options = {}) {
|
|
4
|
-
const tree = await
|
|
5
|
-
const
|
|
6
|
-
const
|
|
5
|
+
const tree = await parseComark(raw ?? "");
|
|
6
|
+
const nodes = normalizeComarkNodes(tree.nodes);
|
|
7
|
+
const toc = deriveToc(nodes, options);
|
|
8
|
+
const body = toMarkdownRoot(nodes, toc);
|
|
7
9
|
const searchText = renderPlainText(body);
|
|
8
10
|
return { body, toc, searchText };
|
|
9
11
|
}
|
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
*
|
|
7
7
|
* All re-exports MUST be runtime-pure (no Node, no Nuxt, no h3). They are.
|
|
8
8
|
*/
|
|
9
|
-
export { describeId, generatePath, generateCanonicalKey, generateTitle, isDraftPath, isPartialPath, normalizeContentPath, normalizeRouteMounts,
|
|
9
|
+
export { describeId, generatePath, generateCanonicalKey, generateTitle, isDraftPath, isPartialPath, normalizeContentPath, normalizeRouteMounts, routeRemainder, mountContentPath, prefixPathWithLocale, stripLocalePrefix, refineUrlPart, routeToContentPathCandidates, } from '../core/content/path.js';
|
|
10
10
|
export { slugifyUrlSegment } from '../core/content/slug.js';
|
|
@@ -226,6 +226,10 @@ export function validatePublicMarkdownAst(value, policy = { components: {} }) {
|
|
|
226
226
|
continue;
|
|
227
227
|
}
|
|
228
228
|
if (name === "style" && tag === "span" && isSafeShikiStyle(propValue)) continue;
|
|
229
|
+
if (tag === "blockquote" && name === "data-alert") {
|
|
230
|
+
if (typeof propValue !== "string" || !["note", "tip", "important", "warning", "caution"].includes(propValue)) report("invalid_prop_value", propPath, "Blockquote alert metadata is invalid.");
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
229
233
|
if (!name || /^on/i.test(name) || /^v-|^@|^:|^#/.test(name) || FORBIDDEN_PROPS.has(lower)) {
|
|
230
234
|
report("unsafe_prop", propPath, `Property "${name}" is not render-safe.`);
|
|
231
235
|
continue;
|
|
@@ -15,4 +15,4 @@ export { getSchemaDef, getSchemaTypeName, unwrapSchema, getObjectShape, getRefer
|
|
|
15
15
|
*/
|
|
16
16
|
export { CONTENT_FIELD_METADATA_KEY, type ContentFieldMetadata, type ContentFieldSchema, } from '../types/fields.js';
|
|
17
17
|
export { getContentFieldMetadata } from '../types/fields.js';
|
|
18
|
-
export {
|
|
18
|
+
export { CONTENT_REFERENCE_METADATA_KEY, } from '../types/reference.js';
|
|
@@ -10,4 +10,6 @@ export {
|
|
|
10
10
|
CONTENT_FIELD_METADATA_KEY
|
|
11
11
|
} from "../types/fields.js";
|
|
12
12
|
export { getContentFieldMetadata } from "../types/fields.js";
|
|
13
|
-
export {
|
|
13
|
+
export {
|
|
14
|
+
CONTENT_REFERENCE_METADATA_KEY
|
|
15
|
+
} from "../types/reference.js";
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
* Canonical keys are only unique within a collection.
|
|
15
15
|
* - **`byCanonical`**: a derived convenience index containing only keys
|
|
16
16
|
* that occur in one collection. Ambiguous unscoped lookups fail closed.
|
|
17
|
-
* - **`byRoute`**: route path
|
|
17
|
+
* - **`byRoute`**: locale plus route path → canonical key. Used by the
|
|
18
18
|
* page resolver when the request arrives as a localized URL.
|
|
19
19
|
* - **`byRef`**: normalized ref string → canonical key. This is how
|
|
20
20
|
* markdown links such as `[Ada]($authors.ada)` find their target without a scan.
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
*/
|
|
29
29
|
import type { ParsedContent } from '../../types/content';
|
|
30
30
|
import type { ContentLocaleEntry } from '../../types/query';
|
|
31
|
-
import type {
|
|
32
|
-
export interface ContentGraphVariant extends
|
|
31
|
+
import type { ContentVariantIdentity, ResolvedVariant } from '../../types/runtime';
|
|
32
|
+
export interface ContentGraphVariant extends ContentVariantIdentity {
|
|
33
33
|
document: ParsedContent;
|
|
34
34
|
}
|
|
35
35
|
export interface ContentGraph {
|
|
@@ -44,7 +44,9 @@ export interface ContentGraph {
|
|
|
44
44
|
byCollectionCanonical: Record<string, Record<string, Record<string, ContentGraphVariant>>>;
|
|
45
45
|
/** Unambiguous canonical keys only. Derived from `byCollectionCanonical`. */
|
|
46
46
|
byCanonical: Record<string, Record<string, ContentGraphVariant>>;
|
|
47
|
-
/**
|
|
47
|
+
/** Exact collection → ref → canonical key alias index. */
|
|
48
|
+
byCollectionRef: Record<string, Record<string, string>>;
|
|
49
|
+
/** `"<locale>:<route path>"` → canonical key. Used by page resolution. */
|
|
48
50
|
byRoute: Record<string, string>;
|
|
49
51
|
/** normalized ref string → canonical key. */
|
|
50
52
|
byRef: Record<string, string>;
|
|
@@ -54,7 +56,6 @@ export interface ContentGraph {
|
|
|
54
56
|
referenceTargets: Map<string, string>;
|
|
55
57
|
/** Exact collection-scoped user-writable reference targets. */
|
|
56
58
|
referenceTargetsByCollection: Record<string, Map<string, string>>;
|
|
57
|
-
manifest: ContentManifest;
|
|
58
59
|
}
|
|
59
60
|
/**
|
|
60
61
|
* Build a `ContentGraph` from a flat list of parsed documents.
|
|
@@ -71,6 +72,7 @@ export interface ContentGraph {
|
|
|
71
72
|
export declare const buildContentGraph: (documents: ParsedContent[], options?: {
|
|
72
73
|
locales?: string[];
|
|
73
74
|
defaultLocale?: string;
|
|
75
|
+
referencePathAliases?: (document: ParsedContent) => readonly string[];
|
|
74
76
|
}) => ContentGraph;
|
|
75
77
|
/** Resolve the exact collection-scoped variant set, or an unambiguous one. */
|
|
76
78
|
export declare const getGraphCanonicalVariants: (graph: ContentGraph, canonicalKey: string, collection?: string) => Record<string, ContentGraphVariant> | undefined;
|
|
@@ -7,20 +7,15 @@ const normalizePath = (path) => {
|
|
|
7
7
|
}
|
|
8
8
|
return path.startsWith("/") ? path.endsWith("/") ? path.slice(0, -1) || "/" : path : `/${path.replace(/\/+$/, "")}`;
|
|
9
9
|
};
|
|
10
|
-
const emptyManifest = () => ({
|
|
11
|
-
byCollectionCanonical: {},
|
|
12
|
-
byCanonical: {},
|
|
13
|
-
byCollectionRef: {},
|
|
14
|
-
byRef: {},
|
|
15
|
-
byRoute: {},
|
|
16
|
-
paths: {},
|
|
17
|
-
collections: {}
|
|
18
|
-
});
|
|
19
10
|
export const buildContentGraph = (documents, options = {}) => {
|
|
20
|
-
const manifest = emptyManifest();
|
|
21
11
|
const byId = {};
|
|
12
|
+
const byCollection = {};
|
|
13
|
+
const byPath = {};
|
|
22
14
|
const byCollectionCanonical = {};
|
|
23
15
|
const byCanonical = {};
|
|
16
|
+
const byCollectionRef = {};
|
|
17
|
+
const byRef = {};
|
|
18
|
+
const byRoute = {};
|
|
24
19
|
const byNavigationPath = {};
|
|
25
20
|
const defaultLocale = options.defaultLocale || "";
|
|
26
21
|
for (const document of documents) {
|
|
@@ -30,15 +25,15 @@ export const buildContentGraph = (documents, options = {}) => {
|
|
|
30
25
|
continue;
|
|
31
26
|
}
|
|
32
27
|
const path = normalizePath(document.path);
|
|
33
|
-
|
|
28
|
+
byPath[path] ||= [];
|
|
34
29
|
if (document.locale === defaultLocale) {
|
|
35
|
-
|
|
30
|
+
byPath[path].unshift(documentId);
|
|
36
31
|
} else {
|
|
37
|
-
|
|
32
|
+
byPath[path].push(documentId);
|
|
38
33
|
}
|
|
39
34
|
if (document.collection) {
|
|
40
|
-
|
|
41
|
-
|
|
35
|
+
byCollection[document.collection] ||= [];
|
|
36
|
+
byCollection[document.collection].push(documentId);
|
|
42
37
|
}
|
|
43
38
|
if (isNavigationFile(document)) {
|
|
44
39
|
const locale2 = document.locale || defaultLocale;
|
|
@@ -58,45 +53,43 @@ export const buildContentGraph = (documents, options = {}) => {
|
|
|
58
53
|
path,
|
|
59
54
|
document
|
|
60
55
|
};
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
56
|
+
byCollectionCanonical[collection] ||= {};
|
|
57
|
+
byCollectionCanonical[collection][document.canonicalKey] ||= {};
|
|
58
|
+
byCollectionCanonical[collection][document.canonicalKey][locale] = variant;
|
|
59
|
+
byRoute[`${locale}:${path}`] = document.canonicalKey;
|
|
65
60
|
if (document.type === "markdown" && typeof document.ref === "string" && document.ref.length) {
|
|
66
|
-
|
|
67
|
-
|
|
61
|
+
byCollectionRef[collection] ||= {};
|
|
62
|
+
byCollectionRef[collection][document.ref] = document.canonicalKey;
|
|
68
63
|
}
|
|
69
64
|
}
|
|
70
65
|
const collectionsByCanonical = /* @__PURE__ */ new Map();
|
|
71
|
-
for (const [collection, canonicalEntries] of Object.entries(
|
|
72
|
-
|
|
73
|
-
for (const [canonicalKey, variants] of Object.entries(canonicalEntries)) {
|
|
74
|
-
const graphVariants = Object.fromEntries(
|
|
75
|
-
Object.entries(variants).map(([locale, variant]) => [locale, { ...variant, document: byId[variant.contentId] }])
|
|
76
|
-
);
|
|
77
|
-
byCollectionCanonical[collection][canonicalKey] = graphVariants;
|
|
66
|
+
for (const [collection, canonicalEntries] of Object.entries(byCollectionCanonical)) {
|
|
67
|
+
for (const canonicalKey of Object.keys(canonicalEntries)) {
|
|
78
68
|
collectionsByCanonical.set(canonicalKey, [...collectionsByCanonical.get(canonicalKey) || [], collection]);
|
|
79
69
|
}
|
|
80
70
|
}
|
|
81
71
|
for (const [canonicalKey, collections] of collectionsByCanonical) {
|
|
82
72
|
if (collections.length !== 1) continue;
|
|
83
73
|
const collection = collections[0];
|
|
84
|
-
manifest.byCanonical[canonicalKey] = manifest.byCollectionCanonical[collection][canonicalKey];
|
|
85
74
|
byCanonical[canonicalKey] = byCollectionCanonical[collection][canonicalKey];
|
|
86
75
|
}
|
|
87
76
|
const collectionsByRef = /* @__PURE__ */ new Map();
|
|
88
|
-
for (const [collection, refs] of Object.entries(
|
|
77
|
+
for (const [collection, refs] of Object.entries(byCollectionRef)) {
|
|
89
78
|
for (const ref of Object.keys(refs)) {
|
|
90
79
|
collectionsByRef.set(ref, [...collectionsByRef.get(ref) || [], collection]);
|
|
91
80
|
}
|
|
92
81
|
}
|
|
93
82
|
for (const [ref, collections] of collectionsByRef) {
|
|
94
|
-
if (collections.length === 1)
|
|
83
|
+
if (collections.length === 1) byRef[ref] = byCollectionRef[collections[0]][ref];
|
|
95
84
|
}
|
|
96
85
|
const referenceTargetsByCollection = Object.fromEntries(
|
|
97
|
-
Object.keys(
|
|
86
|
+
Object.keys(byCollectionCanonical).map((collection) => [
|
|
98
87
|
collection,
|
|
99
|
-
buildReferenceTargets(
|
|
88
|
+
buildReferenceTargets(
|
|
89
|
+
documents.filter((document) => (document.collection || "content") === collection && !document.partial && !isNavigationFile(document)),
|
|
90
|
+
options.locales || [],
|
|
91
|
+
options.referencePathAliases
|
|
92
|
+
)
|
|
100
93
|
])
|
|
101
94
|
);
|
|
102
95
|
const referenceTargetCollections = /* @__PURE__ */ new Map();
|
|
@@ -112,16 +105,16 @@ export const buildContentGraph = (documents, options = {}) => {
|
|
|
112
105
|
return {
|
|
113
106
|
documents,
|
|
114
107
|
byId,
|
|
115
|
-
byCollection
|
|
116
|
-
byPath
|
|
108
|
+
byCollection,
|
|
109
|
+
byPath,
|
|
117
110
|
byCollectionCanonical,
|
|
118
111
|
byCanonical,
|
|
119
|
-
|
|
120
|
-
|
|
112
|
+
byCollectionRef,
|
|
113
|
+
byRoute,
|
|
114
|
+
byRef,
|
|
121
115
|
byNavigationPath,
|
|
122
116
|
referenceTargets,
|
|
123
|
-
referenceTargetsByCollection
|
|
124
|
-
manifest
|
|
117
|
+
referenceTargetsByCollection
|
|
125
118
|
};
|
|
126
119
|
};
|
|
127
120
|
export const getGraphCanonicalVariants = (graph, canonicalKey, collection) => {
|
|
@@ -180,7 +173,7 @@ export const resolveGraphCanonicalKey = (graph, identity, collection) => {
|
|
|
180
173
|
if (hasCollectionVariant(normalizedIdentity)) {
|
|
181
174
|
return normalizedIdentity;
|
|
182
175
|
}
|
|
183
|
-
const refCanonicalKey = collection ? graph.
|
|
176
|
+
const refCanonicalKey = collection ? graph.byCollectionRef[collection]?.[normalizedIdentity] : graph.byRef[normalizedIdentity];
|
|
184
177
|
if (refCanonicalKey && hasCollectionVariant(refCanonicalKey)) {
|
|
185
178
|
return refCanonicalKey;
|
|
186
179
|
}
|
|
@@ -27,7 +27,7 @@ export declare const INLINE_LOCALE_ID_SEPARATOR = "#__locale=";
|
|
|
27
27
|
* configured fallbacks, and default locale are ordered by priority and
|
|
28
28
|
* deduplicated without reordering.
|
|
29
29
|
*/
|
|
30
|
-
export declare const resolveLocaleChain: (requestedLocale: string | undefined, defaultLocale?: string, fallback?: Record<string, string[]
|
|
30
|
+
export declare const resolveLocaleChain: (requestedLocale: string | undefined, defaultLocale?: string, fallback?: Readonly<Record<string, readonly string[]>>) => string[];
|
|
31
31
|
/**
|
|
32
32
|
* Produce the fallback portion of the canonical locale chain, excluding the
|
|
33
33
|
* requested locale itself.
|
|
@@ -68,3 +68,5 @@ export declare const splitInlineLocaleVariantId: (id: string) => {
|
|
|
68
68
|
* override should not fail ingest for the whole document.
|
|
69
69
|
*/
|
|
70
70
|
export declare const expandDataLocaleVariants: (document: ParsedContent, i18nConfig?: ContentCollectionI18nConfig) => ParsedContent[];
|
|
71
|
+
/** Physical locale identity used when content has no configured locale authority. */
|
|
72
|
+
export declare const DEFAULT_CONTENT_LOCALE = "en";
|
|
@@ -29,14 +29,39 @@ export declare const generateCanonicalKey: (parts: string[], { translatedSlugs,
|
|
|
29
29
|
export declare const generateTitle: (path: string) => string;
|
|
30
30
|
export type RouteMounts = Record<string, string>;
|
|
31
31
|
export declare const normalizeRouteMounts: (route: string | Record<string, string> | undefined, locales?: string[], defaultLocale?: string) => RouteMounts | undefined;
|
|
32
|
-
export declare const longestMountForPath: (path: string, mounts: RouteMounts) => [string, string];
|
|
33
32
|
export declare const routeRemainder: (path: string, mount: string) => string;
|
|
33
|
+
/**
|
|
34
|
+
* Remove a configured route mount from a filesystem-derived path when that
|
|
35
|
+
* exact mount is present. Source layouts are allowed to live elsewhere, so
|
|
36
|
+
* unlike provider unmounting this operation leaves non-matching paths intact.
|
|
37
|
+
*/
|
|
38
|
+
export declare const canonicalizeSourcePath: (path: string, mount?: string) => {
|
|
39
|
+
path: string;
|
|
40
|
+
removedSegments: number;
|
|
41
|
+
};
|
|
34
42
|
export declare const mountContentPath: (remainder: string, locale: string | undefined, mounts?: RouteMounts) => string;
|
|
35
43
|
export declare const prefixPathWithLocale: (path: string, locale?: string, defaultLocale?: string) => string;
|
|
36
44
|
export declare const stripLocalePrefix: (path: string, locales?: string[], defaultLocale?: string, explicitLocale?: string) => {
|
|
37
45
|
locale: string | undefined;
|
|
38
46
|
path: string;
|
|
39
47
|
};
|
|
48
|
+
export declare const lowerRouteToCanonicalCandidates: (route: string, requestedLocale: string | undefined, localeChain: readonly string[], defaultLocale: string, locales: readonly string[], mountForLocale: (locale: string) => string) => {
|
|
49
|
+
locale: string;
|
|
50
|
+
contentPath: string;
|
|
51
|
+
}[];
|
|
52
|
+
/**
|
|
53
|
+
* Released CMS-contract adapter returning mounted provider coordinates.
|
|
54
|
+
*
|
|
55
|
+
* This function GUESSES: it unions locales from the mount map, the requested
|
|
56
|
+
* chain, and the default locale, and it substitutes `/` for any locale with no
|
|
57
|
+
* declared mount. The content engine deliberately does not call it — route
|
|
58
|
+
* lowering goes through `lowerRouteToCandidates`
|
|
59
|
+
* (`features/localization/route-projector.ts`), which validates the resolved
|
|
60
|
+
* locale's configured mount and fails instead of substituting one.
|
|
61
|
+
*
|
|
62
|
+
* It exists only so CMS adapters compiled against the released contract keep
|
|
63
|
+
* working. New code should use the resolved collection locale policy.
|
|
64
|
+
*/
|
|
40
65
|
export declare const routeToContentPathCandidates: (route: string, requestedLocale: string | undefined, localeChain: string[], defaultLocale?: string, mounts?: RouteMounts) => {
|
|
41
66
|
locale: string;
|
|
42
67
|
path: string;
|
|
@@ -103,25 +103,37 @@ export const normalizeRouteMounts = (route, locales = [], defaultLocale) => {
|
|
|
103
103
|
const entries = Object.entries(route).filter(([, mount]) => typeof mount === "string" && mount.length > 0).map(([locale, mount]) => [locale, normalizeContentPath(mount)]);
|
|
104
104
|
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
105
105
|
};
|
|
106
|
-
export const longestMountForPath = (path, mounts) => {
|
|
107
|
-
const normalized = normalizeContentPath(path);
|
|
108
|
-
return Object.entries(mounts).filter(([, mount]) => normalized === mount || normalized.startsWith(`${mount}/`)).sort((a, b) => b[1].length - a[1].length)[0];
|
|
109
|
-
};
|
|
110
106
|
export const routeRemainder = (path, mount) => {
|
|
111
107
|
const normalizedPath = normalizeContentPath(path);
|
|
112
108
|
const normalizedMount = normalizeContentPath(mount);
|
|
109
|
+
if (normalizedMount === "/") {
|
|
110
|
+
return normalizedPath;
|
|
111
|
+
}
|
|
113
112
|
if (normalizedPath === normalizedMount) {
|
|
114
113
|
return "/";
|
|
115
114
|
}
|
|
116
115
|
return normalizeContentPath(normalizedPath.slice(normalizedMount.length) || "/");
|
|
117
116
|
};
|
|
117
|
+
export const canonicalizeSourcePath = (path, mount) => {
|
|
118
|
+
const normalizedPath = normalizeContentPath(path);
|
|
119
|
+
const normalizedMount = mount ? normalizeContentPath(mount) : "/";
|
|
120
|
+
const removesMount = normalizedMount !== "/" && (normalizedPath === normalizedMount || normalizedPath.startsWith(`${normalizedMount}/`));
|
|
121
|
+
return {
|
|
122
|
+
path: removesMount ? routeRemainder(normalizedPath, normalizedMount) : normalizedPath,
|
|
123
|
+
removedSegments: removesMount ? normalizedMount.split("/").filter(Boolean).length : 0
|
|
124
|
+
};
|
|
125
|
+
};
|
|
118
126
|
export const mountContentPath = (remainder, locale, mounts) => {
|
|
119
127
|
const mount = locale && mounts?.[locale];
|
|
120
128
|
if (!mount) {
|
|
121
129
|
return normalizeContentPath(remainder);
|
|
122
130
|
}
|
|
123
131
|
const suffix = normalizeContentPath(remainder);
|
|
124
|
-
|
|
132
|
+
const normalizedMount = normalizeContentPath(mount);
|
|
133
|
+
if (normalizedMount === "/") {
|
|
134
|
+
return suffix;
|
|
135
|
+
}
|
|
136
|
+
return normalizeContentPath(suffix === "/" ? normalizedMount : `${normalizedMount}${suffix}`);
|
|
125
137
|
};
|
|
126
138
|
const isLocalePrefixedPath = (path, locales) => {
|
|
127
139
|
const segments = path.split("/").filter(Boolean);
|
|
@@ -142,19 +154,37 @@ export const stripLocalePrefix = (path, locales = [], defaultLocale, explicitLoc
|
|
|
142
154
|
const routePath = routeLocale ? normalizeContentPath("/" + segments.slice(1).join("/")) : normalized;
|
|
143
155
|
return { locale, path: routePath };
|
|
144
156
|
};
|
|
157
|
+
export const lowerRouteToCanonicalCandidates = (route, requestedLocale, localeChain, defaultLocale, locales, mountForLocale) => {
|
|
158
|
+
const stripped = stripLocalePrefix(route, [...locales], defaultLocale, requestedLocale);
|
|
159
|
+
const activeLocale = stripped.locale || requestedLocale || defaultLocale;
|
|
160
|
+
const activeMount = normalizeContentPath(mountForLocale(activeLocale));
|
|
161
|
+
const path = normalizeContentPath(stripped.path);
|
|
162
|
+
const matchesExpectedMount = activeMount === "/" || path === activeMount || path.startsWith(`${activeMount}/`);
|
|
163
|
+
if (!matchesExpectedMount) {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
const contentPath = routeRemainder(path, activeMount);
|
|
167
|
+
return localeChain.map((locale) => ({
|
|
168
|
+
locale,
|
|
169
|
+
contentPath
|
|
170
|
+
}));
|
|
171
|
+
};
|
|
145
172
|
export const routeToContentPathCandidates = (route, requestedLocale, localeChain, defaultLocale, mounts) => {
|
|
146
173
|
const locales = Array.from(new Set([...Object.keys(mounts || {}), ...localeChain, defaultLocale].filter(Boolean)));
|
|
147
|
-
const stripped = stripLocalePrefix(route, locales, defaultLocale, requestedLocale);
|
|
148
174
|
if (!mounts) {
|
|
175
|
+
const stripped = stripLocalePrefix(route, locales, defaultLocale, requestedLocale);
|
|
149
176
|
return localeChain.map((locale) => ({ locale, path: stripped.path }));
|
|
150
177
|
}
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
178
|
+
return lowerRouteToCanonicalCandidates(
|
|
179
|
+
route,
|
|
180
|
+
requestedLocale,
|
|
181
|
+
localeChain,
|
|
182
|
+
defaultLocale || localeChain[0] || "",
|
|
183
|
+
locales,
|
|
184
|
+
(locale) => mounts[locale] || "/"
|
|
185
|
+
).map((candidate) => ({
|
|
186
|
+
locale: candidate.locale,
|
|
187
|
+
path: mountContentPath(candidate.contentPath, candidate.locale, mounts)
|
|
158
188
|
}));
|
|
159
189
|
};
|
|
160
190
|
export const pathHasLocalePrefix = isLocalePrefixedPath;
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import type { ParsedContent } from '../../types/content';
|
|
2
1
|
/**
|
|
3
2
|
* Module-private source classification. `navigationFile` intentionally does
|
|
4
3
|
* not belong to the public `ParsedContent` contract: it only exists between
|
|
5
4
|
* parsing and structural graph/navigation processing.
|
|
6
5
|
*/
|
|
7
|
-
export declare const isNavigationFile: (document:
|
|
6
|
+
export declare const isNavigationFile: (document: unknown) => boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const isNavigationFile = (document) => document.navigationFile === true;
|
|
1
|
+
export const isNavigationFile = (document) => Boolean(document) && typeof document === "object" && document.navigationFile === true;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export type ContentDataSourceErrorCode = 'QUERY_CURSOR_INVALID' | 'BACKEND_FAILURE';
|
|
2
|
+
export declare const createContentDataSourceError: (code: ContentDataSourceErrorCode) => Error;
|
|
3
|
+
export declare const isContentDataSourceError: (value: unknown) => value is Error & {
|
|
4
|
+
code: ContentDataSourceErrorCode;
|
|
5
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class ContentDataSourceError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
constructor(code) {
|
|
4
|
+
super(code === "QUERY_CURSOR_INVALID" ? "Content data-source query cursor is invalid." : "Content data-source operation failed.");
|
|
5
|
+
this.name = "ContentDataSourceError";
|
|
6
|
+
this.code = code;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
export const createContentDataSourceError = (code) => new ContentDataSourceError(code);
|
|
10
|
+
export const isContentDataSourceError = (value) => value instanceof ContentDataSourceError;
|
package/dist/core/errors.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* a `ContentError` with one of the codes below. Boundary code (HTTP handler,
|
|
6
6
|
* CLI) maps codes to user-facing responses; tests pattern-match on `.code`.
|
|
7
7
|
*/
|
|
8
|
-
export type ContentErrorCode = 'PARSE_FAILED' | 'TRANSFORM_FAILED' | 'VALIDATION_FAILED' | 'INVALID_CONTENT' | 'DUPLICATE_CANONICAL_ID' | 'DUPLICATE_LOCALIZED_PATH' | 'CONFLICTING_REFS' | 'INVALID_REF_VALUE' | 'INVALID_NAVIGATION_YAML' | 'SCHEMA_VALIDATION_FAILED' | 'NON_JSON_VALUE' | 'CONFLICTING_COLLECTION_MATCH' | 'TRANSLATED_SLUG_CONFLICT';
|
|
8
|
+
export type ContentErrorCode = 'PARSE_FAILED' | 'TRANSFORM_FAILED' | 'VALIDATION_FAILED' | 'INVALID_CONTENT' | 'DUPLICATE_CANONICAL_ID' | 'DUPLICATE_LOCALIZED_PATH' | 'CONFLICTING_REFS' | 'INVALID_REF_VALUE' | 'INVALID_NAVIGATION_YAML' | 'INVALID_NAVIGATION_METADATA' | 'SCHEMA_VALIDATION_FAILED' | 'NON_JSON_VALUE' | 'CONFLICTING_COLLECTION_MATCH' | 'TRANSLATED_SLUG_CONFLICT';
|
|
9
9
|
export declare class ContentError extends Error {
|
|
10
10
|
readonly code: ContentErrorCode;
|
|
11
11
|
readonly context: Record<string, unknown>;
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalizes parser-owned Comark metadata into the inert public AST contract.
|
|
3
|
+
* Keep this deliberately narrow: authored structural props such as `as` remain
|
|
4
|
+
* untouched and are rejected by the render and portability policies.
|
|
5
|
+
*/
|
|
6
|
+
export declare function normalizeComarkNodes(nodes: unknown[]): unknown[];
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const GFM_ALERTS = /* @__PURE__ */ new Set(["note", "tip", "important", "warning", "caution"]);
|
|
2
|
+
export function normalizeComarkNodes(nodes) {
|
|
3
|
+
const normalize = (node) => {
|
|
4
|
+
if (!Array.isArray(node)) return node;
|
|
5
|
+
const [tag, rawProps, ...children] = node;
|
|
6
|
+
const props = isRecord(rawProps) ? { ...rawProps } : rawProps;
|
|
7
|
+
if (tag === "blockquote" && isRecord(props) && typeof props.as === "string" && GFM_ALERTS.has(props.as.toLowerCase())) {
|
|
8
|
+
props["data-alert"] = props.as.toLowerCase();
|
|
9
|
+
delete props.as;
|
|
10
|
+
}
|
|
11
|
+
return [tag, props, ...children.map(normalize)];
|
|
12
|
+
};
|
|
13
|
+
return nodes.map(normalize);
|
|
14
|
+
}
|
|
15
|
+
const isRecord = (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { ComarkPlugin } from 'comark';
|
|
2
|
+
/** The single configured Comark entry point used by every parsing boundary. */
|
|
3
|
+
export declare const parseComark: (markdown: string, plugins?: readonly ComarkPlugin[]) => Promise<import("comark").ComarkTree<Record<string, any>, Record<string, any>>>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { createParse, defineComarkPlugin, parseFrontmatter } from "comark";
|
|
2
|
+
const typedComponentFrontmatter = defineComarkPlugin(() => ({
|
|
3
|
+
name: "ginko-typed-component-frontmatter",
|
|
4
|
+
markdownItPlugins: [
|
|
5
|
+
(markdown) => {
|
|
6
|
+
markdown.core.ruler.after("block", "ginko_typed_component_frontmatter", (state) => {
|
|
7
|
+
const lines = state.src.split(/\r?\n/);
|
|
8
|
+
for (const token of state.tokens) {
|
|
9
|
+
if (token.type !== "mdc_block_open" || !token.map) continue;
|
|
10
|
+
const [startLine, endLine] = token.map;
|
|
11
|
+
if (lines[startLine + 1]?.trim() !== "---") continue;
|
|
12
|
+
const parsed = parseFrontmatter(lines.slice(startLine + 1, endLine).join("\n"));
|
|
13
|
+
if (!parsed.frontmatterText) continue;
|
|
14
|
+
const yamlEntries = Object.entries(parsed.data);
|
|
15
|
+
const yamlKeys = new Set(yamlEntries.map(([key]) => key));
|
|
16
|
+
token.attrs = [
|
|
17
|
+
...(token.attrs ?? []).filter(([key]) => !yamlKeys.has(key)),
|
|
18
|
+
...yamlEntries
|
|
19
|
+
];
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}));
|
|
25
|
+
export const parseComark = async (markdown, plugins = []) => {
|
|
26
|
+
const parse = createParse({
|
|
27
|
+
plugins: [typedComponentFrontmatter(), ...plugins]
|
|
28
|
+
});
|
|
29
|
+
return await parse(markdown);
|
|
30
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { ContentProviderRouteFact, ContentRouteRecord } from '../public/provider-contract';
|
|
2
|
+
export declare const CONTENT_ROUTE_LIMITS: Readonly<{
|
|
3
|
+
maxRouteRecordBytes: number;
|
|
4
|
+
maxTotalRouteBytes: number;
|
|
5
|
+
maxSitemapImagesPerRoute: 16;
|
|
6
|
+
maxSitemapImageLocationBytes: number;
|
|
7
|
+
}>;
|
|
8
|
+
export type ContentRouteRecordValidationCode = 'RESPONSE_INVALID' | 'RESULT_LIMIT_EXCEEDED';
|
|
9
|
+
export declare class ContentRouteRecordValidationError extends Error {
|
|
10
|
+
readonly code: ContentRouteRecordValidationCode;
|
|
11
|
+
readonly field: string;
|
|
12
|
+
constructor(code: ContentRouteRecordValidationCode, field: string, message: string);
|
|
13
|
+
}
|
|
14
|
+
export declare const normalizeRawProviderRouteFact: (value: unknown, field?: string) => ContentProviderRouteFact;
|
|
15
|
+
export declare const normalizeRawContentRouteRecord: (value: unknown, field?: string) => {
|
|
16
|
+
record: ContentRouteRecord;
|
|
17
|
+
serializedBytes: number;
|
|
18
|
+
};
|