@krantho/seo 0.0.0-stage → 1.0.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/LICENSE +21 -0
- package/dist/archetypes/define-archetype.d.ts +23 -0
- package/dist/archetypes/define-archetype.d.ts.map +1 -0
- package/dist/archetypes/define-archetype.js +51 -0
- package/dist/archetypes/define-archetype.js.map +1 -0
- package/dist/archetypes/index.d.ts +4 -0
- package/dist/archetypes/index.d.ts.map +1 -0
- package/dist/archetypes/index.js +3 -0
- package/dist/archetypes/index.js.map +1 -0
- package/dist/archetypes/presets.d.ts +21 -0
- package/dist/archetypes/presets.d.ts.map +1 -0
- package/dist/archetypes/presets.js +42 -0
- package/dist/archetypes/presets.js.map +1 -0
- package/dist/archetypes/types.d.ts +20 -0
- package/dist/archetypes/types.d.ts.map +1 -0
- package/dist/archetypes/types.js +2 -0
- package/dist/archetypes/types.js.map +1 -0
- package/dist/catalog/auto-mappings.d.ts +39 -0
- package/dist/catalog/auto-mappings.d.ts.map +1 -0
- package/dist/catalog/auto-mappings.js +64 -0
- package/dist/catalog/auto-mappings.js.map +1 -0
- package/dist/catalog/index.d.ts +7 -0
- package/dist/catalog/index.d.ts.map +1 -0
- package/dist/catalog/index.js +4 -0
- package/dist/catalog/index.js.map +1 -0
- package/dist/catalog/properties.d.ts +9 -0
- package/dist/catalog/properties.d.ts.map +1 -0
- package/dist/catalog/properties.js +630 -0
- package/dist/catalog/properties.js.map +1 -0
- package/dist/catalog/property-atoms.d.ts +53 -0
- package/dist/catalog/property-atoms.d.ts.map +1 -0
- package/dist/catalog/property-atoms.js +170 -0
- package/dist/catalog/property-atoms.js.map +1 -0
- package/dist/catalog/types.d.ts +53 -0
- package/dist/catalog/types.d.ts.map +1 -0
- package/dist/catalog/types.js +423 -0
- package/dist/catalog/types.js.map +1 -0
- package/dist/create-seo.d.ts +70 -0
- package/dist/create-seo.d.ts.map +1 -0
- package/dist/create-seo.js +137 -0
- package/dist/create-seo.js.map +1 -0
- package/dist/decorators/breadcrumb.d.ts +12 -0
- package/dist/decorators/breadcrumb.d.ts.map +1 -0
- package/dist/decorators/breadcrumb.js +36 -0
- package/dist/decorators/breadcrumb.js.map +1 -0
- package/dist/decorators/index.d.ts +8 -0
- package/dist/decorators/index.d.ts.map +1 -0
- package/dist/decorators/index.js +13 -0
- package/dist/decorators/index.js.map +1 -0
- package/dist/decorators/nodes.d.ts +12 -0
- package/dist/decorators/nodes.d.ts.map +1 -0
- package/dist/decorators/nodes.js +29 -0
- package/dist/decorators/nodes.js.map +1 -0
- package/dist/decorators/page.d.ts +13 -0
- package/dist/decorators/page.d.ts.map +1 -0
- package/dist/decorators/page.js +68 -0
- package/dist/decorators/page.js.map +1 -0
- package/dist/decorators/registry.d.ts +11 -0
- package/dist/decorators/registry.d.ts.map +1 -0
- package/dist/decorators/registry.js +29 -0
- package/dist/decorators/registry.js.map +1 -0
- package/dist/decorators/sequence.d.ts +8 -0
- package/dist/decorators/sequence.d.ts.map +1 -0
- package/dist/decorators/sequence.js +22 -0
- package/dist/decorators/sequence.js.map +1 -0
- package/dist/decorators/types.d.ts +30 -0
- package/dist/decorators/types.d.ts.map +1 -0
- package/dist/decorators/types.js +2 -0
- package/dist/decorators/types.js.map +1 -0
- package/dist/define-seo.d.ts +21 -0
- package/dist/define-seo.d.ts.map +1 -0
- package/dist/define-seo.js +31 -0
- package/dist/define-seo.js.map +1 -0
- package/dist/graph/build-entity.d.ts +47 -0
- package/dist/graph/build-entity.d.ts.map +1 -0
- package/dist/graph/build-entity.js +69 -0
- package/dist/graph/build-entity.js.map +1 -0
- package/dist/graph/compose-from-config.d.ts +89 -0
- package/dist/graph/compose-from-config.d.ts.map +1 -0
- package/dist/graph/compose-from-config.js +189 -0
- package/dist/graph/compose-from-config.js.map +1 -0
- package/dist/graph/compose-graph.d.ts +40 -0
- package/dist/graph/compose-graph.d.ts.map +1 -0
- package/dist/graph/compose-graph.js +30 -0
- package/dist/graph/compose-graph.js.map +1 -0
- package/dist/graph/effective-config.d.ts +36 -0
- package/dist/graph/effective-config.d.ts.map +1 -0
- package/dist/graph/effective-config.js +84 -0
- package/dist/graph/effective-config.js.map +1 -0
- package/dist/graph/id-conventions.d.ts +47 -0
- package/dist/graph/id-conventions.d.ts.map +1 -0
- package/dist/graph/id-conventions.js +78 -0
- package/dist/graph/id-conventions.js.map +1 -0
- package/dist/graph/index.d.ts +9 -0
- package/dist/graph/index.d.ts.map +1 -0
- package/dist/graph/index.js +4 -0
- package/dist/graph/index.js.map +1 -0
- package/dist/graph/resolve-properties.d.ts +36 -0
- package/dist/graph/resolve-properties.d.ts.map +1 -0
- package/dist/graph/resolve-properties.js +141 -0
- package/dist/graph/resolve-properties.js.map +1 -0
- package/dist/graph/resolve-references.d.ts +14 -0
- package/dist/graph/resolve-references.d.ts.map +1 -0
- package/dist/graph/resolve-references.js +49 -0
- package/dist/graph/resolve-references.js.map +1 -0
- package/dist/graph/typed-value.d.ts +7 -0
- package/dist/graph/typed-value.d.ts.map +1 -0
- package/dist/graph/typed-value.js +43 -0
- package/dist/graph/typed-value.js.map +1 -0
- package/dist/graph/types.d.ts +34 -0
- package/dist/graph/types.d.ts.map +1 -0
- package/dist/graph/types.js +2 -0
- package/dist/graph/types.js.map +1 -0
- package/dist/graph/value-scope.d.ts +19 -0
- package/dist/graph/value-scope.d.ts.map +1 -0
- package/dist/graph/value-scope.js +30 -0
- package/dist/graph/value-scope.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/json-ld.d.ts +24 -0
- package/dist/json-ld.d.ts.map +1 -0
- package/dist/json-ld.js +24 -0
- package/dist/json-ld.js.map +1 -0
- package/dist/profiles/bing.d.ts +10 -0
- package/dist/profiles/bing.d.ts.map +1 -0
- package/dist/profiles/bing.js +13 -0
- package/dist/profiles/bing.js.map +1 -0
- package/dist/profiles/check.d.ts +11 -0
- package/dist/profiles/check.d.ts.map +1 -0
- package/dist/profiles/check.js +66 -0
- package/dist/profiles/check.js.map +1 -0
- package/dist/profiles/eligibility.d.ts +26 -0
- package/dist/profiles/eligibility.d.ts.map +1 -0
- package/dist/profiles/eligibility.js +49 -0
- package/dist/profiles/eligibility.js.map +1 -0
- package/dist/profiles/google.d.ts +4 -0
- package/dist/profiles/google.d.ts.map +1 -0
- package/dist/profiles/google.js +387 -0
- package/dist/profiles/google.js.map +1 -0
- package/dist/profiles/index.d.ts +10 -0
- package/dist/profiles/index.d.ts.map +1 -0
- package/dist/profiles/index.js +13 -0
- package/dist/profiles/index.js.map +1 -0
- package/dist/profiles/registry.d.ts +16 -0
- package/dist/profiles/registry.d.ts.map +1 -0
- package/dist/profiles/registry.js +66 -0
- package/dist/profiles/registry.js.map +1 -0
- package/dist/profiles/types.d.ts +29 -0
- package/dist/profiles/types.d.ts.map +1 -0
- package/dist/profiles/types.js +2 -0
- package/dist/profiles/types.js.map +1 -0
- package/dist/seo-graph.d.ts +39 -0
- package/dist/seo-graph.d.ts.map +1 -0
- package/dist/seo-graph.js +34 -0
- package/dist/seo-graph.js.map +1 -0
- package/dist/types/built-in/content.d.ts +12 -0
- package/dist/types/built-in/content.d.ts.map +1 -0
- package/dist/types/built-in/content.js +79 -0
- package/dist/types/built-in/content.js.map +1 -0
- package/dist/types/built-in/events.d.ts +23 -0
- package/dist/types/built-in/events.d.ts.map +1 -0
- package/dist/types/built-in/events.js +93 -0
- package/dist/types/built-in/events.js.map +1 -0
- package/dist/types/built-in/index.d.ts +4 -0
- package/dist/types/built-in/index.d.ts.map +1 -0
- package/dist/types/built-in/index.js +26 -0
- package/dist/types/built-in/index.js.map +1 -0
- package/dist/types/built-in/media.d.ts +21 -0
- package/dist/types/built-in/media.d.ts.map +1 -0
- package/dist/types/built-in/media.js +104 -0
- package/dist/types/built-in/media.js.map +1 -0
- package/dist/types/built-in/site.d.ts +14 -0
- package/dist/types/built-in/site.d.ts.map +1 -0
- package/dist/types/built-in/site.js +35 -0
- package/dist/types/built-in/site.js.map +1 -0
- package/dist/types/context.d.ts +26 -0
- package/dist/types/context.d.ts.map +1 -0
- package/dist/types/context.js +44 -0
- package/dist/types/context.js.map +1 -0
- package/dist/types/definition.d.ts +58 -0
- package/dist/types/definition.d.ts.map +1 -0
- package/dist/types/definition.js +2 -0
- package/dist/types/definition.js.map +1 -0
- package/dist/types/index.d.ts +4 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/registry.d.ts +14 -0
- package/dist/types/registry.d.ts.map +1 -0
- package/dist/types/registry.js +37 -0
- package/dist/types/registry.js.map +1 -0
- package/dist/types/values.d.ts +23 -0
- package/dist/types/values.d.ts.map +1 -0
- package/dist/types/values.js +52 -0
- package/dist/types/values.js.map +1 -0
- package/dist/validate/check-terms.d.ts +15 -0
- package/dist/validate/check-terms.d.ts.map +1 -0
- package/dist/validate/check-terms.js +42 -0
- package/dist/validate/check-terms.js.map +1 -0
- package/dist/validate/check-value.d.ts +19 -0
- package/dist/validate/check-value.d.ts.map +1 -0
- package/dist/validate/check-value.js +77 -0
- package/dist/validate/check-value.js.map +1 -0
- package/dist/validate/graph-nodes.d.ts +22 -0
- package/dist/validate/graph-nodes.d.ts.map +1 -0
- package/dist/validate/graph-nodes.js +50 -0
- package/dist/validate/graph-nodes.js.map +1 -0
- package/dist/validate/index.d.ts +4 -0
- package/dist/validate/index.d.ts.map +1 -0
- package/dist/validate/index.js +2 -0
- package/dist/validate/index.js.map +1 -0
- package/dist/validate/types.d.ts +22 -0
- package/dist/validate/types.d.ts.map +1 -0
- package/dist/validate/types.js +2 -0
- package/dist/validate/types.js.map +1 -0
- package/dist/validate/validate-graph.d.ts +18 -0
- package/dist/validate/validate-graph.d.ts.map +1 -0
- package/dist/validate/validate-graph.js +109 -0
- package/dist/validate/validate-graph.js.map +1 -0
- package/dist/vocabulary/datatypes.d.ts +7 -0
- package/dist/vocabulary/datatypes.d.ts.map +1 -0
- package/dist/vocabulary/datatypes.js +46 -0
- package/dist/vocabulary/datatypes.js.map +1 -0
- package/dist/vocabulary/index.d.ts +5 -0
- package/dist/vocabulary/index.d.ts.map +1 -0
- package/dist/vocabulary/index.js +4 -0
- package/dist/vocabulary/index.js.map +1 -0
- package/dist/vocabulary/schemaorg.generated.d.ts +4 -0
- package/dist/vocabulary/schemaorg.generated.d.ts.map +1 -0
- package/dist/vocabulary/schemaorg.generated.js +3038 -0
- package/dist/vocabulary/schemaorg.generated.js.map +1 -0
- package/dist/vocabulary/types.d.ts +38 -0
- package/dist/vocabulary/types.d.ts.map +1 -0
- package/dist/vocabulary/types.js +2 -0
- package/dist/vocabulary/types.js.map +1 -0
- package/dist/vocabulary/vocabulary.d.ts +28 -0
- package/dist/vocabulary/vocabulary.d.ts.map +1 -0
- package/dist/vocabulary/vocabulary.js +89 -0
- package/dist/vocabulary/vocabulary.js.map +1 -0
- package/package.json +40 -3
- package/README.md +0 -3
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { PageStructure } from '@krantho/core';
|
|
2
|
+
import type { SeoConfigData } from '@krantho/seo-shared';
|
|
3
|
+
import type { FindingSink } from './resolve-references.js';
|
|
4
|
+
import '../decorators/index.js';
|
|
5
|
+
import type { EntityLevel, ResolvedProperty } from '../types/definition.js';
|
|
6
|
+
import type { JsonLdGraph, SeoGraphConfig } from './types.js';
|
|
7
|
+
import { type ValueScope } from './value-scope.js';
|
|
8
|
+
/** Document metadata from the CMS persistence layer */
|
|
9
|
+
export interface DocumentMeta {
|
|
10
|
+
/** First version creation timestamp (ISO 8601) */
|
|
11
|
+
createdAt: string;
|
|
12
|
+
/** Latest version creation timestamp (ISO 8601) */
|
|
13
|
+
updatedAt?: string | undefined;
|
|
14
|
+
/** Document locale */
|
|
15
|
+
locale: string;
|
|
16
|
+
/** Current version number */
|
|
17
|
+
version: number;
|
|
18
|
+
}
|
|
19
|
+
/** Options for config-driven graph composition */
|
|
20
|
+
export interface ComposeFromConfigOptions {
|
|
21
|
+
/** Effective document-level SEO config (resolved from collection → route → document chain) */
|
|
22
|
+
config: SeoConfigData;
|
|
23
|
+
/** Page fields from CMS */
|
|
24
|
+
fields: Record<string, unknown>;
|
|
25
|
+
/** Site configuration (identity, domain, language, baseUrl) */
|
|
26
|
+
siteConfig: SeoGraphConfig;
|
|
27
|
+
/** Current page path */
|
|
28
|
+
path: string;
|
|
29
|
+
/** Site-level SEO config — entities rendered alongside document entities */
|
|
30
|
+
siteLevel?: SeoConfigData | null | undefined;
|
|
31
|
+
/** Document metadata for system-derived properties */
|
|
32
|
+
documentMeta?: DocumentMeta | undefined;
|
|
33
|
+
/**
|
|
34
|
+
* The structure the page's template renders — its trail, its place in a
|
|
35
|
+
* sequence, its kind. The effective configuration's `structure` gives a
|
|
36
|
+
* trail above the page and a kind when the template states none.
|
|
37
|
+
*/
|
|
38
|
+
structure?: PageStructure | undefined;
|
|
39
|
+
/** Question and answer pairs the page renders */
|
|
40
|
+
faq?: ReadonlyArray<{
|
|
41
|
+
question: string;
|
|
42
|
+
answer: string;
|
|
43
|
+
}> | undefined;
|
|
44
|
+
/** Receives what composition left out because the site configures nothing to refer to */
|
|
45
|
+
onFinding?: FindingSink | undefined;
|
|
46
|
+
}
|
|
47
|
+
/** A property's effective value as an editor sees it */
|
|
48
|
+
export interface ResolvedPropertyView {
|
|
49
|
+
readonly property: string;
|
|
50
|
+
readonly value: unknown;
|
|
51
|
+
readonly source: ResolvedProperty['source'];
|
|
52
|
+
/** The site, collection, URL pattern or document that set the value; computed; or a link to another entity */
|
|
53
|
+
readonly scope: ValueScope;
|
|
54
|
+
/** Where the value was read or how it was computed */
|
|
55
|
+
readonly origin: string;
|
|
56
|
+
/** Whether the SEO settings may set it; references the type's structure depends on may not */
|
|
57
|
+
readonly editable: boolean;
|
|
58
|
+
}
|
|
59
|
+
/** An entity of a page with its resolved properties */
|
|
60
|
+
export interface ResolvedEntity {
|
|
61
|
+
readonly type: string;
|
|
62
|
+
readonly id: string;
|
|
63
|
+
readonly level: EntityLevel;
|
|
64
|
+
readonly properties: readonly ResolvedPropertyView[];
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* What every entity of a page resolves to — each property's value, its
|
|
68
|
+
* source and whether the SEO settings may override it — from the same inputs
|
|
69
|
+
* and in the same order `composeFromConfig` uses, so an editor sees what the
|
|
70
|
+
* page emits and where each value comes from.
|
|
71
|
+
*/
|
|
72
|
+
export declare function resolvePageEntities(options: Omit<ComposeFromConfigOptions, 'onFinding'>): ResolvedEntity[];
|
|
73
|
+
/**
|
|
74
|
+
* Compose a page's JSON-LD `@graph` from its SEO configuration:
|
|
75
|
+
*
|
|
76
|
+
* 1. Site entities (the site's organization, WebSite, Person) from the
|
|
77
|
+
* site-level configuration and the site's identity.
|
|
78
|
+
* 2. Document entities (Article, Service …) from the effective document
|
|
79
|
+
* configuration, the content fields and values computed from the page.
|
|
80
|
+
* 3. Every reference a type emits resolved within the graph — to a site
|
|
81
|
+
* entity of an acceptable type, or left out with a finding.
|
|
82
|
+
*
|
|
83
|
+
* Each property's value comes from one source in a fixed order (see
|
|
84
|
+
* `resolveProperties`). An author comes from content fields, field mappings
|
|
85
|
+
* or configuration — never from version history, whose editor identity is
|
|
86
|
+
* private.
|
|
87
|
+
*/
|
|
88
|
+
export declare function composeFromConfig(options: ComposeFromConfigOptions): JsonLdGraph;
|
|
89
|
+
//# sourceMappingURL=compose-from-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-from-config.d.ts","sourceRoot":"","sources":["../../src/graph/compose-from-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAa,MAAM,eAAe,CAAA;AAC7D,OAAO,KAAK,EAAE,aAAa,EAAmB,MAAM,qBAAqB,CAAA;AAMzE,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAE1D,OAAO,wBAAwB,CAAA;AAG/B,OAAO,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAI3E,OAAO,KAAK,EAAE,WAAW,EAAgB,cAAc,EAAE,MAAM,YAAY,CAAA;AAC3E,OAAO,EAAE,KAAK,UAAU,EAAc,MAAM,kBAAkB,CAAA;AAE9D,uDAAuD;AACvD,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,SAAS,EAAE,MAAM,CAAA;IACjB,mDAAmD;IACnD,SAAS,CAAC,EAAE,MAAM,GAAG,SAAS,CAAA;IAC9B,sBAAsB;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,6BAA6B;IAC7B,OAAO,EAAE,MAAM,CAAA;CAChB;AAED,kDAAkD;AAClD,MAAM,WAAW,wBAAwB;IACvC,8FAA8F;IAC9F,MAAM,EAAE,aAAa,CAAA;IACrB,2BAA2B;IAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,+DAA+D;IAC/D,UAAU,EAAE,cAAc,CAAA;IAC1B,wBAAwB;IACxB,IAAI,EAAE,MAAM,CAAA;IACZ,4EAA4E;IAC5E,SAAS,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAAA;IAC5C,sDAAsD;IACtD,YAAY,CAAC,EAAE,YAAY,GAAG,SAAS,CAAA;IACvC;;;;OAIG;IACH,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IACrC,iDAAiD;IACjD,GAAG,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CAAA;IACrE,yFAAyF;IACzF,SAAS,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;CACpC;AA2FD,wDAAwD;AACxD,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC,QAAQ,CAAC,CAAA;IAC3C,8GAA8G;IAC9G,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAA;IAC1B,sDAAsD;IACtD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,8FAA8F;IAC9F,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAC3B;AAED,uDAAuD;AACvD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAA;IAC3B,QAAQ,CAAC,UAAU,EAAE,SAAS,oBAAoB,EAAE,CAAA;CACrD;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,IAAI,CAAC,wBAAwB,EAAE,WAAW,CAAC,GACnD,cAAc,EAAE,CA2BlB;AAcD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,wBAAwB,GAAG,WAAW,CAehF"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { buildSeoFieldMap, resolveSeoField } from '@krantho/seo-shared';
|
|
2
|
+
import { getTypeDefinition } from '../types/registry.js';
|
|
3
|
+
import { buildEntity, carries, entityInputValues } from './build-entity.js';
|
|
4
|
+
import { resolveReferences } from './resolve-references.js';
|
|
5
|
+
import '../decorators/index.js';
|
|
6
|
+
import { pageTypeFor, registeredDecorators } from '../decorators/index.js';
|
|
7
|
+
import { isSubtypeOf } from '../vocabulary/index.js';
|
|
8
|
+
import { documentId } from './id-conventions.js';
|
|
9
|
+
import { valueScope } from './value-scope.js';
|
|
10
|
+
/** Values computed from the page path and the document's version history */
|
|
11
|
+
function computedValues(options) {
|
|
12
|
+
const page = `${options.siteConfig.baseUrl}${options.path}`;
|
|
13
|
+
const meta = options.documentMeta;
|
|
14
|
+
return {
|
|
15
|
+
url: page,
|
|
16
|
+
mainEntityOfPage: page,
|
|
17
|
+
datePublished: meta?.createdAt,
|
|
18
|
+
dateModified: meta ? (meta.updatedAt ?? meta.createdAt) : undefined,
|
|
19
|
+
inLanguage: meta?.locale,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/** A site entity: the SEO settings and the site configuration, no page content */
|
|
23
|
+
function siteEntityInput(entity, site) {
|
|
24
|
+
return {
|
|
25
|
+
type: entity['@type'],
|
|
26
|
+
level: 'site',
|
|
27
|
+
id: entity['@id'],
|
|
28
|
+
configured: entity.properties,
|
|
29
|
+
fields: {},
|
|
30
|
+
computed: {},
|
|
31
|
+
site,
|
|
32
|
+
path: '',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
/** A document entity; a type defined for the whole site keeps its site-wide `@id` */
|
|
36
|
+
function documentEntityInput(entity, options) {
|
|
37
|
+
const type = entity['@type'];
|
|
38
|
+
const { fields } = options;
|
|
39
|
+
return {
|
|
40
|
+
type,
|
|
41
|
+
level: getTypeDefinition(type)?.scope === 'site' ? 'site' : 'document',
|
|
42
|
+
id: entity['@id'],
|
|
43
|
+
configured: entity.properties,
|
|
44
|
+
fields,
|
|
45
|
+
fieldMappings: options.config.fieldMappings,
|
|
46
|
+
computed: computedValues(options),
|
|
47
|
+
site: options.siteConfig,
|
|
48
|
+
path: options.path,
|
|
49
|
+
title: pageTitle(fields),
|
|
50
|
+
faq: options.faq,
|
|
51
|
+
trail: pageStructure(options).trail,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** A trail or sequence URL made absolute against the site's base URL */
|
|
55
|
+
function absolute(url, site) {
|
|
56
|
+
return url.startsWith('/') ? `${site.baseUrl}${url}` : url;
|
|
57
|
+
}
|
|
58
|
+
/** The page's title, as its content states it */
|
|
59
|
+
function pageTitle(fields) {
|
|
60
|
+
return resolveSeoField(buildSeoFieldMap(Object.keys(fields)), 'title', fields) || undefined;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* The page's structure with absolute URLs: the template's trail, else the
|
|
64
|
+
* configured trail above the page followed by the page; the template's kind,
|
|
65
|
+
* else the configured one. The trail's last step links to the page.
|
|
66
|
+
*/
|
|
67
|
+
function pageStructure(options) {
|
|
68
|
+
const { siteConfig: site, structure, config } = options;
|
|
69
|
+
const page = `${site.baseUrl}${options.path}`;
|
|
70
|
+
const steps = structure?.trail?.length
|
|
71
|
+
? structure.trail
|
|
72
|
+
: config.structure?.trail?.length
|
|
73
|
+
? [...config.structure.trail, { name: pageTitle(options.fields) ?? '', url: page }]
|
|
74
|
+
: undefined;
|
|
75
|
+
const trail = steps?.map((step, index) => {
|
|
76
|
+
const url = step.url ?? (index === steps.length - 1 ? page : undefined);
|
|
77
|
+
return { name: step.name, ...(url ? { url: absolute(url, site) } : {}) };
|
|
78
|
+
});
|
|
79
|
+
const sequence = structure?.sequence && {
|
|
80
|
+
partOf: { ...structure.sequence.partOf, url: absolute(structure.sequence.partOf.url, site) },
|
|
81
|
+
position: structure.sequence.position,
|
|
82
|
+
};
|
|
83
|
+
const kind = structure?.kind ?? config.structure?.kind;
|
|
84
|
+
return {
|
|
85
|
+
...(trail ? { trail } : {}),
|
|
86
|
+
...(sequence ? { sequence } : {}),
|
|
87
|
+
...(kind ? { kind } : {}),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* What every entity of a page resolves to — each property's value, its
|
|
92
|
+
* source and whether the SEO settings may override it — from the same inputs
|
|
93
|
+
* and in the same order `composeFromConfig` uses, so an editor sees what the
|
|
94
|
+
* page emits and where each value comes from.
|
|
95
|
+
*/
|
|
96
|
+
export function resolvePageEntities(options) {
|
|
97
|
+
const inputs = [
|
|
98
|
+
...(options.siteLevel?.entities ?? []).map((entity) => ({
|
|
99
|
+
input: siteEntityInput(entity, options.siteConfig),
|
|
100
|
+
from: { fromSite: true },
|
|
101
|
+
})),
|
|
102
|
+
...options.config.entities.map((entity) => ({
|
|
103
|
+
input: documentEntityInput(entity, options),
|
|
104
|
+
from: { fromSite: false, scopes: entity.scopes },
|
|
105
|
+
})),
|
|
106
|
+
];
|
|
107
|
+
return inputs.map(({ input, from }) => {
|
|
108
|
+
const { id, values, definition } = entityInputValues(input);
|
|
109
|
+
const fixed = new Set(Object.keys(definition?.references ?? {}));
|
|
110
|
+
const carried = [...values].filter(([property, resolved]) => carries(property, resolved, input.type, definition));
|
|
111
|
+
const properties = carried.map(([property, resolved]) => ({
|
|
112
|
+
property,
|
|
113
|
+
value: resolved.value,
|
|
114
|
+
source: resolved.source,
|
|
115
|
+
scope: valueScope(resolved, property, from),
|
|
116
|
+
origin: resolved.origin,
|
|
117
|
+
editable: !fixed.has(property),
|
|
118
|
+
}));
|
|
119
|
+
return { type: input.type, id, level: input.level, properties };
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/** One node per `@id` — a later entity replaces an earlier one */
|
|
123
|
+
function deduplicate(nodes) {
|
|
124
|
+
const byId = new Map();
|
|
125
|
+
const withoutId = [];
|
|
126
|
+
for (const node of nodes) {
|
|
127
|
+
const id = node.entity['@id'];
|
|
128
|
+
if (typeof id === 'string')
|
|
129
|
+
byId.set(id, node);
|
|
130
|
+
else
|
|
131
|
+
withoutId.push(node);
|
|
132
|
+
}
|
|
133
|
+
return [...byId.values(), ...withoutId];
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Compose a page's JSON-LD `@graph` from its SEO configuration:
|
|
137
|
+
*
|
|
138
|
+
* 1. Site entities (the site's organization, WebSite, Person) from the
|
|
139
|
+
* site-level configuration and the site's identity.
|
|
140
|
+
* 2. Document entities (Article, Service …) from the effective document
|
|
141
|
+
* configuration, the content fields and values computed from the page.
|
|
142
|
+
* 3. Every reference a type emits resolved within the graph — to a site
|
|
143
|
+
* entity of an acceptable type, or left out with a finding.
|
|
144
|
+
*
|
|
145
|
+
* Each property's value comes from one source in a fixed order (see
|
|
146
|
+
* `resolveProperties`). An author comes from content fields, field mappings
|
|
147
|
+
* or configuration — never from version history, whose editor identity is
|
|
148
|
+
* private.
|
|
149
|
+
*/
|
|
150
|
+
export function composeFromConfig(options) {
|
|
151
|
+
const siteEntities = (options.siteLevel?.entities ?? []).map((entity) => buildEntity(siteEntityInput(entity, options.siteConfig)));
|
|
152
|
+
const documentEntities = options.config.entities.map((entity) => buildEntity(documentEntityInput(entity, options)));
|
|
153
|
+
const nodes = deduplicate([...siteEntities, ...documentEntities]);
|
|
154
|
+
const graph = resolveReferences(nodes, options.onFinding);
|
|
155
|
+
const page = pageContext(options, documentEntities);
|
|
156
|
+
const decorated = registeredDecorators().reduce((current, decorator) => decorator.decorate(current, page), graph);
|
|
157
|
+
return { '@context': 'https://schema.org', '@graph': decorated };
|
|
158
|
+
}
|
|
159
|
+
/** The page the decorators describe: its page node, its main entity and its structure */
|
|
160
|
+
function pageContext(options, documentEntities) {
|
|
161
|
+
const { siteConfig: site, path } = options;
|
|
162
|
+
const structure = pageStructure(options);
|
|
163
|
+
if (structure.kind !== undefined && !pageTypeFor(structure.kind)) {
|
|
164
|
+
options.onFinding?.({
|
|
165
|
+
severity: 'warning',
|
|
166
|
+
code: 'unknown-type',
|
|
167
|
+
profile: 'schema.org',
|
|
168
|
+
message: `The page kind "${structure.kind}" is neither a known kind nor a current Schema.org WebPage type; the page is a WebPage.`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const isPage = (entity) => typeof entity['@type'] === 'string' && isSubtypeOf(entity['@type'], 'WebPage');
|
|
172
|
+
const pageNode = documentEntities.find((node) => node.level === 'document' && isPage(node.entity));
|
|
173
|
+
const main = documentEntities.find((node) => node.level === 'document' &&
|
|
174
|
+
!isPage(node.entity) &&
|
|
175
|
+
!isSubtypeOf(String(node.entity['@type']), 'BreadcrumbList'));
|
|
176
|
+
const computed = computedValues(options);
|
|
177
|
+
return {
|
|
178
|
+
...structure,
|
|
179
|
+
site,
|
|
180
|
+
path,
|
|
181
|
+
url: `${site.baseUrl}${path}`,
|
|
182
|
+
pageId: pageNode?.entity['@id'] ?? documentId(site.baseUrl, path, 'webpage'),
|
|
183
|
+
mainEntityId: main?.entity['@id'],
|
|
184
|
+
title: pageTitle(options.fields),
|
|
185
|
+
language: computed.inLanguage ?? site.language,
|
|
186
|
+
dateModified: typeof computed.dateModified === 'string' ? computed.dateModified : undefined,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=compose-from-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-from-config.js","sourceRoot":"","sources":["../../src/graph/compose-from-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { PageStructure } from '@krantho/core';
|
|
2
|
+
import type { Archetype } from '../archetypes/types.js';
|
|
3
|
+
import type { JsonLdGraph, SeoGraphConfig } from './types.js';
|
|
4
|
+
/** Options for graph composition */
|
|
5
|
+
export interface ComposeGraphOptions {
|
|
6
|
+
/** The archetype defining which schemas to render */
|
|
7
|
+
archetype: Archetype;
|
|
8
|
+
/** Page fields (resolved by resolvePageContent) */
|
|
9
|
+
fields: Record<string, unknown>;
|
|
10
|
+
/** Site configuration */
|
|
11
|
+
config: SeoGraphConfig;
|
|
12
|
+
/** Current page path, e.g., '/about' or '/blog/my-post' */
|
|
13
|
+
path: string;
|
|
14
|
+
/** The structure the page renders: its trail, its place in a sequence, its kind */
|
|
15
|
+
structure?: PageStructure | undefined;
|
|
16
|
+
/** Optional FAQ pairs for FAQPage schema */
|
|
17
|
+
faq?: ReadonlyArray<{
|
|
18
|
+
question: string;
|
|
19
|
+
answer: string;
|
|
20
|
+
}> | undefined;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Compose a JSON-LD @graph block from an archetype and page data: each of the
|
|
24
|
+
* archetype's types is built as configured with no settings of its own, so
|
|
25
|
+
* values come from the page's content, the page itself and the site's
|
|
26
|
+
* identity — the same composition as `composeFromConfig`.
|
|
27
|
+
*
|
|
28
|
+
* @example
|
|
29
|
+
* ```ts
|
|
30
|
+
* const graph = composeGraph({
|
|
31
|
+
* archetype: homePageArchetype,
|
|
32
|
+
* fields: content.hero.fields,
|
|
33
|
+
* config: siteConfig,
|
|
34
|
+
* path: '/',
|
|
35
|
+
* })
|
|
36
|
+
* // Returns { "@context": "https://schema.org", "@graph": [...] }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export declare function composeGraph(options: ComposeGraphOptions): JsonLdGraph;
|
|
40
|
+
//# sourceMappingURL=compose-graph.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-graph.d.ts","sourceRoot":"","sources":["../../src/graph/compose-graph.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAEvD,OAAO,KAAK,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAE7D,oCAAoC;AACpC,MAAM,WAAW,mBAAmB;IAClC,qDAAqD;IACrD,SAAS,EAAE,SAAS,CAAA;IACpB,mDAAmD;IACnD,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;IAC/B,yBAAyB;IACzB,MAAM,EAAE,cAAc,CAAA;IACtB,2DAA2D;IAC3D,IAAI,EAAE,MAAM,CAAA;IACZ,mFAAmF;IACnF,SAAS,CAAC,EAAE,aAAa,GAAG,SAAS,CAAA;IACrC,4CAA4C;IAC5C,GAAG,CAAC,EAAE,aAAa,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,GAAG,SAAS,CAAA;CACtE;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,mBAAmB,GAAG,WAAW,CAUtE"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { composeFromConfig } from './compose-from-config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compose a JSON-LD @graph block from an archetype and page data: each of the
|
|
4
|
+
* archetype's types is built as configured with no settings of its own, so
|
|
5
|
+
* values come from the page's content, the page itself and the site's
|
|
6
|
+
* identity — the same composition as `composeFromConfig`.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* const graph = composeGraph({
|
|
11
|
+
* archetype: homePageArchetype,
|
|
12
|
+
* fields: content.hero.fields,
|
|
13
|
+
* config: siteConfig,
|
|
14
|
+
* path: '/',
|
|
15
|
+
* })
|
|
16
|
+
* // Returns { "@context": "https://schema.org", "@graph": [...] }
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
export function composeGraph(options) {
|
|
20
|
+
const entities = options.archetype.schemas.map((type) => ({ '@type': type, properties: {} }));
|
|
21
|
+
return composeFromConfig({
|
|
22
|
+
config: { entities },
|
|
23
|
+
fields: options.fields,
|
|
24
|
+
siteConfig: options.config,
|
|
25
|
+
path: options.path,
|
|
26
|
+
structure: options.structure,
|
|
27
|
+
faq: options.faq,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=compose-graph.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-graph.js","sourceRoot":"","sources":["../../src/graph/compose-graph.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,iBAAiB,EAAE,MAAM,0BAA0B,CAAA;AAmB5D;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,YAAY,CAAC,OAA4B;IACvD,MAAM,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC,CAAA;IAC7F,OAAO,iBAAiB,CAAC;QACvB,MAAM,EAAE,EAAE,QAAQ,EAAE;QACpB,MAAM,EAAE,OAAO,CAAC,MAAM;QACtB,UAAU,EAAE,OAAO,CAAC,MAAM;QAC1B,IAAI,EAAE,OAAO,CAAC,IAAI;QAClB,SAAS,EAAE,OAAO,CAAC,SAAS;QAC5B,GAAG,EAAE,OAAO,CAAC,GAAG;KACjB,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { SeoConfigData, SeoConfigEntity } from '@krantho/seo-shared';
|
|
2
|
+
/** A scope of the document-level configuration chain */
|
|
3
|
+
export type ConfigScope = 'collection' | 'route' | 'document';
|
|
4
|
+
/** Scope-keyed SEO config chain passed to `resolveEffectiveConfig`. */
|
|
5
|
+
export interface SeoConfigChain {
|
|
6
|
+
site?: SeoConfigData | null | undefined;
|
|
7
|
+
collection?: SeoConfigData | null | undefined;
|
|
8
|
+
route?: SeoConfigData | null | undefined;
|
|
9
|
+
document?: SeoConfigData | null | undefined;
|
|
10
|
+
}
|
|
11
|
+
/** An entity of the effective configuration, with the scope each of its configured properties came from */
|
|
12
|
+
export interface EffectiveSeoEntity extends SeoConfigEntity {
|
|
13
|
+
readonly scopes: Readonly<Record<string, ConfigScope>>;
|
|
14
|
+
}
|
|
15
|
+
/** The effective document-level configuration: entities merged across the chain */
|
|
16
|
+
export interface EffectiveSeoConfig extends SeoConfigData {
|
|
17
|
+
readonly entities: readonly EffectiveSeoEntity[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Resolve effective SEO config from the document-level scope hierarchy:
|
|
21
|
+
* collection → route → document (most specific wins).
|
|
22
|
+
*
|
|
23
|
+
* Entities are matched by `(@type, role)` tuple across scopes. When the
|
|
24
|
+
* same entity appears in multiple scopes, their properties are merged
|
|
25
|
+
* with the more specific scope's values taking precedence, and each
|
|
26
|
+
* property records the scope that gave its value.
|
|
27
|
+
*
|
|
28
|
+
* Field mappings and the page structure are merged across scopes (most
|
|
29
|
+
* specific wins).
|
|
30
|
+
*
|
|
31
|
+
* **Site scope is NOT included** — it's passed separately to
|
|
32
|
+
* `composeFromConfig` as `siteLevel` because site entities use different
|
|
33
|
+
* id conventions and builder logic.
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveEffectiveConfig(configs: SeoConfigChain): EffectiveSeoConfig;
|
|
36
|
+
//# sourceMappingURL=effective-config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-config.d.ts","sourceRoot":"","sources":["../../src/graph/effective-config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AAEzE,wDAAwD;AACxD,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,OAAO,GAAG,UAAU,CAAA;AAE7D,uEAAuE;AACvE,MAAM,WAAW,cAAc;IAC7B,IAAI,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAAA;IACvC,UAAU,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAAA;IAC7C,KAAK,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAAA;IACxC,QAAQ,CAAC,EAAE,aAAa,GAAG,IAAI,GAAG,SAAS,CAAA;CAC5C;AAED,2GAA2G;AAC3G,MAAM,WAAW,kBAAmB,SAAQ,eAAe;IACzD,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAAA;CACvD;AAED,mFAAmF;AACnF,MAAM,WAAW,kBAAmB,SAAQ,aAAa;IACvD,QAAQ,CAAC,QAAQ,EAAE,SAAS,kBAAkB,EAAE,CAAA;CACjD;AAgBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,kBAAkB,CA0DlF"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unique key for an entity in the scope chain — used to match and merge
|
|
3
|
+
* entities across scopes. Entities with the same `@type` and `role` are
|
|
4
|
+
* considered the same entity.
|
|
5
|
+
*/
|
|
6
|
+
function entityKey(entity) {
|
|
7
|
+
return entity.role ? `${entity['@type']}:${entity.role}` : entity['@type'];
|
|
8
|
+
}
|
|
9
|
+
/** Each property of the entity, set by this scope */
|
|
10
|
+
function scopesOf(entity, scope) {
|
|
11
|
+
return Object.fromEntries(Object.keys(entity.properties).map((property) => [property, scope]));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve effective SEO config from the document-level scope hierarchy:
|
|
15
|
+
* collection → route → document (most specific wins).
|
|
16
|
+
*
|
|
17
|
+
* Entities are matched by `(@type, role)` tuple across scopes. When the
|
|
18
|
+
* same entity appears in multiple scopes, their properties are merged
|
|
19
|
+
* with the more specific scope's values taking precedence, and each
|
|
20
|
+
* property records the scope that gave its value.
|
|
21
|
+
*
|
|
22
|
+
* Field mappings and the page structure are merged across scopes (most
|
|
23
|
+
* specific wins).
|
|
24
|
+
*
|
|
25
|
+
* **Site scope is NOT included** — it's passed separately to
|
|
26
|
+
* `composeFromConfig` as `siteLevel` because site entities use different
|
|
27
|
+
* id conventions and builder logic.
|
|
28
|
+
*/
|
|
29
|
+
export function resolveEffectiveConfig(configs) {
|
|
30
|
+
const entityMap = new Map();
|
|
31
|
+
// Track insertion order: the first scope to introduce an entity determines its
|
|
32
|
+
// position. Since we iterate least-specific → most-specific, the collection
|
|
33
|
+
// scope's ordering is preserved while more specific scopes override properties.
|
|
34
|
+
const entityOrder = [];
|
|
35
|
+
// Process from least specific to most specific (so more specific overwrites)
|
|
36
|
+
const scopes = [
|
|
37
|
+
['collection', configs.collection],
|
|
38
|
+
['route', configs.route],
|
|
39
|
+
['document', configs.document],
|
|
40
|
+
];
|
|
41
|
+
for (const [scope, config] of scopes) {
|
|
42
|
+
if (!config)
|
|
43
|
+
continue;
|
|
44
|
+
for (const entity of config.entities) {
|
|
45
|
+
const key = entityKey(entity);
|
|
46
|
+
const existing = entityMap.get(key);
|
|
47
|
+
if (existing) {
|
|
48
|
+
// Merge properties: more specific scope overrides per-property
|
|
49
|
+
entityMap.set(key, {
|
|
50
|
+
'@type': entity['@type'],
|
|
51
|
+
...(entity['@id'] ? { '@id': entity['@id'] } : {}),
|
|
52
|
+
...(entity.role ? { role: entity.role } : {}),
|
|
53
|
+
properties: { ...existing.properties, ...entity.properties },
|
|
54
|
+
scopes: { ...existing.scopes, ...scopesOf(entity, scope) },
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
entityMap.set(key, { ...entity, scopes: scopesOf(entity, scope) });
|
|
59
|
+
entityOrder.push(key);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
// Field mappings: merge across scopes (most specific wins)
|
|
64
|
+
const fieldMappings = {
|
|
65
|
+
...(configs.collection?.fieldMappings ?? {}),
|
|
66
|
+
...(configs.route?.fieldMappings ?? {}),
|
|
67
|
+
...(configs.document?.fieldMappings ?? {}),
|
|
68
|
+
};
|
|
69
|
+
const entities = entityOrder.flatMap((key) => entityMap.get(key) ?? []);
|
|
70
|
+
const hasFieldMappings = Object.keys(fieldMappings).length > 0;
|
|
71
|
+
// Page structure: each setting from the most specific scope that gives it
|
|
72
|
+
const structure = {
|
|
73
|
+
...configs.collection?.structure,
|
|
74
|
+
...configs.route?.structure,
|
|
75
|
+
...configs.document?.structure,
|
|
76
|
+
};
|
|
77
|
+
const hasStructure = Object.keys(structure).length > 0;
|
|
78
|
+
return {
|
|
79
|
+
entities,
|
|
80
|
+
...(hasFieldMappings ? { fieldMappings } : {}),
|
|
81
|
+
...(hasStructure ? { structure } : {}),
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
//# sourceMappingURL=effective-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"effective-config.js","sourceRoot":"","sources":["../../src/graph/effective-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity @id conventions for Schema.org @graph linking.
|
|
3
|
+
*
|
|
4
|
+
* IDs follow a predictable pattern so entities can cross-reference
|
|
5
|
+
* each other within the same @graph block.
|
|
6
|
+
*
|
|
7
|
+
* Naming convention for the URL fragment:
|
|
8
|
+
* - Site-level entities use a short, distinctive lowercased name keyed off
|
|
9
|
+
* the entity's role: `#business`, `#website`, `#person`.
|
|
10
|
+
* - Document-level entities use the lowercased Schema.org type with any
|
|
11
|
+
* structural suffix dropped:
|
|
12
|
+
* - `BreadcrumbList` → `#breadcrumb` (drops `List`)
|
|
13
|
+
* - `FAQPage` → `#faq` (drops `Page`)
|
|
14
|
+
* - `VideoObject` → `#video` (drops `Object`)
|
|
15
|
+
* - Schema.org types without a structural suffix use the full lowercased
|
|
16
|
+
* name: `#article`, `#event`, `#recipe`, `#howto`, `#course`, `#jobposting`.
|
|
17
|
+
*
|
|
18
|
+
* The reason for the asymmetry: short names are more readable in JSON-LD
|
|
19
|
+
* cross-references, but only some Schema.org types have a redundant suffix
|
|
20
|
+
* that can be dropped without ambiguity. Types like `JobPosting` and
|
|
21
|
+
* `LocalBusiness` would lose meaning if shortened.
|
|
22
|
+
*
|
|
23
|
+
* The fragments are URL components, not Schema.org type names — they only
|
|
24
|
+
* need to be unique within a single @graph block. Consumers of the JSON-LD
|
|
25
|
+
* output should not rely on the fragment matching the type name.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Build a document @id from base URL, path, and fragment: the page's URL with
|
|
29
|
+
* one trailing slash, then the fragment — the home page (`/`) and the site
|
|
30
|
+
* (`''`) both read `https://example.com/#…`
|
|
31
|
+
*/
|
|
32
|
+
export declare function documentId(baseUrl: string, path: string, fragment: string): string;
|
|
33
|
+
export declare function businessId(baseUrl: string): string;
|
|
34
|
+
export declare function websiteId(baseUrl: string): string;
|
|
35
|
+
export declare function personId(baseUrl: string): string;
|
|
36
|
+
export declare function articleId(baseUrl: string, path: string): string;
|
|
37
|
+
export declare function breadcrumbId(baseUrl: string, path: string): string;
|
|
38
|
+
export declare function faqId(baseUrl: string, path: string): string;
|
|
39
|
+
export declare function eventId(baseUrl: string, path: string): string;
|
|
40
|
+
export declare function recipeId(baseUrl: string, path: string): string;
|
|
41
|
+
export declare function howToId(baseUrl: string, path: string): string;
|
|
42
|
+
export declare function courseId(baseUrl: string, path: string): string;
|
|
43
|
+
export declare function jobPostingId(baseUrl: string, path: string): string;
|
|
44
|
+
export declare function videoObjectId(baseUrl: string, path: string): string;
|
|
45
|
+
export declare function serviceId(baseUrl: string, path: string): string;
|
|
46
|
+
export declare function productId(baseUrl: string, path: string): string;
|
|
47
|
+
//# sourceMappingURL=id-conventions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-conventions.d.ts","sourceRoot":"","sources":["../../src/graph/id-conventions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;GAIG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGlF;AAED,wBAAgB,UAAU,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAElD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhD;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE3D;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE9D;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAElE;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/D"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entity @id conventions for Schema.org @graph linking.
|
|
3
|
+
*
|
|
4
|
+
* IDs follow a predictable pattern so entities can cross-reference
|
|
5
|
+
* each other within the same @graph block.
|
|
6
|
+
*
|
|
7
|
+
* Naming convention for the URL fragment:
|
|
8
|
+
* - Site-level entities use a short, distinctive lowercased name keyed off
|
|
9
|
+
* the entity's role: `#business`, `#website`, `#person`.
|
|
10
|
+
* - Document-level entities use the lowercased Schema.org type with any
|
|
11
|
+
* structural suffix dropped:
|
|
12
|
+
* - `BreadcrumbList` → `#breadcrumb` (drops `List`)
|
|
13
|
+
* - `FAQPage` → `#faq` (drops `Page`)
|
|
14
|
+
* - `VideoObject` → `#video` (drops `Object`)
|
|
15
|
+
* - Schema.org types without a structural suffix use the full lowercased
|
|
16
|
+
* name: `#article`, `#event`, `#recipe`, `#howto`, `#course`, `#jobposting`.
|
|
17
|
+
*
|
|
18
|
+
* The reason for the asymmetry: short names are more readable in JSON-LD
|
|
19
|
+
* cross-references, but only some Schema.org types have a redundant suffix
|
|
20
|
+
* that can be dropped without ambiguity. Types like `JobPosting` and
|
|
21
|
+
* `LocalBusiness` would lose meaning if shortened.
|
|
22
|
+
*
|
|
23
|
+
* The fragments are URL components, not Schema.org type names — they only
|
|
24
|
+
* need to be unique within a single @graph block. Consumers of the JSON-LD
|
|
25
|
+
* output should not rely on the fragment matching the type name.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Build a document @id from base URL, path, and fragment: the page's URL with
|
|
29
|
+
* one trailing slash, then the fragment — the home page (`/`) and the site
|
|
30
|
+
* (`''`) both read `https://example.com/#…`
|
|
31
|
+
*/
|
|
32
|
+
export function documentId(baseUrl, path, fragment) {
|
|
33
|
+
const page = `${baseUrl.replace(/\/+$/, '')}${path.replace(/\/+$/, '')}`;
|
|
34
|
+
return `${page}/#${fragment}`;
|
|
35
|
+
}
|
|
36
|
+
export function businessId(baseUrl) {
|
|
37
|
+
return documentId(baseUrl, '', 'business');
|
|
38
|
+
}
|
|
39
|
+
export function websiteId(baseUrl) {
|
|
40
|
+
return documentId(baseUrl, '', 'website');
|
|
41
|
+
}
|
|
42
|
+
export function personId(baseUrl) {
|
|
43
|
+
return documentId(baseUrl, '', 'person');
|
|
44
|
+
}
|
|
45
|
+
export function articleId(baseUrl, path) {
|
|
46
|
+
return documentId(baseUrl, path, 'article');
|
|
47
|
+
}
|
|
48
|
+
export function breadcrumbId(baseUrl, path) {
|
|
49
|
+
return documentId(baseUrl, path, 'breadcrumb');
|
|
50
|
+
}
|
|
51
|
+
export function faqId(baseUrl, path) {
|
|
52
|
+
return documentId(baseUrl, path, 'faq');
|
|
53
|
+
}
|
|
54
|
+
export function eventId(baseUrl, path) {
|
|
55
|
+
return documentId(baseUrl, path, 'event');
|
|
56
|
+
}
|
|
57
|
+
export function recipeId(baseUrl, path) {
|
|
58
|
+
return documentId(baseUrl, path, 'recipe');
|
|
59
|
+
}
|
|
60
|
+
export function howToId(baseUrl, path) {
|
|
61
|
+
return documentId(baseUrl, path, 'howto');
|
|
62
|
+
}
|
|
63
|
+
export function courseId(baseUrl, path) {
|
|
64
|
+
return documentId(baseUrl, path, 'course');
|
|
65
|
+
}
|
|
66
|
+
export function jobPostingId(baseUrl, path) {
|
|
67
|
+
return documentId(baseUrl, path, 'jobposting');
|
|
68
|
+
}
|
|
69
|
+
export function videoObjectId(baseUrl, path) {
|
|
70
|
+
return documentId(baseUrl, path, 'video');
|
|
71
|
+
}
|
|
72
|
+
export function serviceId(baseUrl, path) {
|
|
73
|
+
return documentId(baseUrl, path, 'service');
|
|
74
|
+
}
|
|
75
|
+
export function productId(baseUrl, path) {
|
|
76
|
+
return documentId(baseUrl, path, 'product');
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=id-conventions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"id-conventions.js","sourceRoot":"","sources":["../../src/graph/id-conventions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAEH;;;;GAIG;AACH,MAAM,UAAU,UAAU,CAAC,OAAe,EAAE,IAAY,EAAE,QAAgB;IACxE,MAAM,IAAI,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,CAAA;IACxE,OAAO,GAAG,IAAI,KAAK,QAAQ,EAAE,CAAA;AAC/B,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,OAAe;IACxC,OAAO,UAAU,CAAC,OAAO,EAAE,EAAE,EAAE,UAAU,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe;IACvC,OAAO,UAAU,CAAC,OAAO,EAAE,EAAE,EAAE,SAAS,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe;IACtC,OAAO,UAAU,CAAC,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,IAAY;IACrD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,IAAY;IACxD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAA;AAChD,CAAC;AAED,MAAM,UAAU,KAAK,CAAC,OAAe,EAAE,IAAY;IACjD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,CAAA;AACzC,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAe,EAAE,IAAY;IACnD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,IAAY;IACpD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,OAAO,CAAC,OAAe,EAAE,IAAY;IACnD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,UAAU,QAAQ,CAAC,OAAe,EAAE,IAAY;IACpD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,QAAQ,CAAC,CAAA;AAC5C,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,OAAe,EAAE,IAAY;IACxD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,YAAY,CAAC,CAAA;AAChD,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,OAAe,EAAE,IAAY;IACzD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAA;AAC3C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,IAAY;IACrD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,SAAS,CAAC,OAAe,EAAE,IAAY;IACrD,OAAO,UAAU,CAAC,OAAO,EAAE,IAAI,EAAE,SAAS,CAAC,CAAA;AAC7C,CAAC"}
|