@jdevalk/seo-graph-core 0.6.1 → 0.7.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/AGENTS.md +369 -11
- package/README.md +15 -8
- package/dist/assemble.js.map +1 -1
- package/dist/dedupe.js.map +1 -1
- package/dist/ids.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/indexnow.d.ts +2 -2
- package/dist/indexnow.d.ts.map +1 -1
- package/dist/indexnow.js +11 -5
- package/dist/indexnow.js.map +1 -1
- package/dist/pieces/article.d.ts +9 -4
- package/dist/pieces/article.d.ts.map +1 -1
- package/dist/pieces/article.js.map +1 -1
- package/dist/pieces/breadcrumb.d.ts +2 -1
- package/dist/pieces/breadcrumb.d.ts.map +1 -1
- package/dist/pieces/breadcrumb.js.map +1 -1
- package/dist/pieces/custom.d.ts +3 -2
- package/dist/pieces/custom.d.ts.map +1 -1
- package/dist/pieces/custom.js +2 -0
- package/dist/pieces/custom.js.map +1 -1
- package/dist/pieces/image.d.ts +2 -1
- package/dist/pieces/image.d.ts.map +1 -1
- package/dist/pieces/image.js.map +1 -1
- package/dist/pieces/navigation.d.ts +2 -2
- package/dist/pieces/navigation.d.ts.map +1 -1
- package/dist/pieces/navigation.js.map +1 -1
- package/dist/pieces/video.d.ts +2 -2
- package/dist/pieces/video.d.ts.map +1 -1
- package/dist/pieces/video.js.map +1 -1
- package/dist/pieces/webpage.d.ts +2 -2
- package/dist/pieces/webpage.d.ts.map +1 -1
- package/dist/pieces/webpage.js.map +1 -1
- package/dist/pieces/website.d.ts +2 -2
- package/dist/pieces/website.d.ts.map +1 -1
- package/dist/pieces/website.js.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -4
package/AGENTS.md
CHANGED
|
@@ -24,6 +24,38 @@ Two packages:
|
|
|
24
24
|
|
|
25
25
|
---
|
|
26
26
|
|
|
27
|
+
## Contents
|
|
28
|
+
|
|
29
|
+
**Schema core** — concepts and builders for the JSON-LD graph:
|
|
30
|
+
|
|
31
|
+
- [Architecture](#architecture)
|
|
32
|
+
- [Installation](#installation)
|
|
33
|
+
- [The @id system](#the-id-system)
|
|
34
|
+
- [Piece builders reference](#piece-builders-reference)
|
|
35
|
+
|
|
36
|
+
**Recipes and patterns** — how to model real sites:
|
|
37
|
+
|
|
38
|
+
- [Site type recipes](#site-type-recipes)
|
|
39
|
+
- [Trust and credibility signals](#trust-and-credibility-signals)
|
|
40
|
+
- [Choosing the right Article subtype](#choosing-the-right-article-subtype)
|
|
41
|
+
- [Actions: telling agents what they can do](#actions-telling-agents-what-they-can-do)
|
|
42
|
+
- [Multi-type entities](#multi-type-entities)
|
|
43
|
+
- [Rich Organization patterns](#rich-organization-patterns)
|
|
44
|
+
- [Rich Person patterns](#rich-person-patterns)
|
|
45
|
+
- [Reference implementations](#reference-implementations)
|
|
46
|
+
|
|
47
|
+
**Astro integration** — runtime component and build-time checks:
|
|
48
|
+
|
|
49
|
+
- [Astro integration guide](#astro-integration-guide) — the `<Seo>` component, hreflang, schema endpoints
|
|
50
|
+
- [Build-time integration](#build-time-integration) — `seoGraph()` hook, H1 validation, metadata uniqueness, IndexNow, `llms.txt`
|
|
51
|
+
- [Complete integration example](#complete-integration-example)
|
|
52
|
+
- [Advanced patterns](#advanced-patterns)
|
|
53
|
+
- [Common mistakes](#common-mistakes)
|
|
54
|
+
- [Validating your output](#validating-your-output)
|
|
55
|
+
- [Repository structure](#repository-structure)
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
27
59
|
## Architecture
|
|
28
60
|
|
|
29
61
|
```
|
|
@@ -2484,10 +2516,89 @@ export const GET = createSchemaMap({
|
|
|
2484
2516
|
**Schema map entry options:**
|
|
2485
2517
|
| Field | Type | Default | Purpose |
|
|
2486
2518
|
|---|---|---|---|
|
|
2487
|
-
| `path` | `string` | — |
|
|
2488
|
-
| `lastModified` | `Date` | — | Last
|
|
2489
|
-
|
|
2490
|
-
|
|
2519
|
+
| `path` | `string` | — | Path of the schema endpoint. Leading slash optional. |
|
|
2520
|
+
| `lastModified` | `Date` | — | Last-modified date. Rendered as `YYYY-MM-DD`. |
|
|
2521
|
+
|
|
2522
|
+
**`createSchemaMap` options:**
|
|
2523
|
+
| Option | Type | Default | Purpose |
|
|
2524
|
+
|---|---|---|---|
|
|
2525
|
+
| `siteUrl` | `string` | — | Canonical origin. Trailing slash stripped. |
|
|
2526
|
+
| `entries` | `SchemaMapEntry[]` | — | One entry per schema endpoint. |
|
|
2527
|
+
| `cacheControl` | `string \| null` | `'max-age=300'` | Cache-Control header. `null` to omit. |
|
|
2528
|
+
|
|
2529
|
+
`<changefreq>` and `<priority>` are deliberately not emitted —
|
|
2530
|
+
Google and other major crawlers ignore them, and fabricating defaults
|
|
2531
|
+
is worse than omitting.
|
|
2532
|
+
|
|
2533
|
+
### API catalog (RFC 9727)
|
|
2534
|
+
|
|
2535
|
+
Serve a standards-compliant API catalog at `/.well-known/api-catalog`
|
|
2536
|
+
([RFC 9727](https://www.rfc-editor.org/rfc/rfc9727)) so agent crawlers
|
|
2537
|
+
can discover the site's APIs in one fetch:
|
|
2538
|
+
|
|
2539
|
+
```ts
|
|
2540
|
+
// src/pages/.well-known/api-catalog.ts
|
|
2541
|
+
import { createApiCatalog } from '@jdevalk/astro-seo-graph';
|
|
2542
|
+
|
|
2543
|
+
export const GET = createApiCatalog({
|
|
2544
|
+
siteUrl: 'https://example.com',
|
|
2545
|
+
schemaEndpoints: [
|
|
2546
|
+
{ path: '/schema/post.json', schemaType: 'BlogPosting', serviceDoc: '/seo-graph/' },
|
|
2547
|
+
{ path: '/schema/page.json', schemaType: 'WebPage', serviceDoc: '/seo-graph/' },
|
|
2548
|
+
],
|
|
2549
|
+
schemaMap: { path: '/schemamap.xml', serviceDoc: '/seo-graph/' },
|
|
2550
|
+
additional: [
|
|
2551
|
+
{ anchor: '/ask', serviceDoc: '/ask-docs/', type: 'https://schema.org/SearchAction' },
|
|
2552
|
+
],
|
|
2553
|
+
});
|
|
2554
|
+
```
|
|
2555
|
+
|
|
2556
|
+
Output is `application/linkset+json` ([RFC 9264](https://www.rfc-editor.org/rfc/rfc9264)).
|
|
2557
|
+
`schemaEndpoints` get auto-typed as `https://schema.org/<schemaType>`;
|
|
2558
|
+
`schemaMap` is emitted without a `type` (no standard one exists);
|
|
2559
|
+
`additional` accepts free-form `anchor`/`serviceDoc`/`type` (each
|
|
2560
|
+
either `string` or `string[]`). Relative paths are absolutized against
|
|
2561
|
+
`siteUrl`; absolute URLs pass through unchanged.
|
|
2562
|
+
|
|
2563
|
+
The package also exports a `CATALOG_PATH` constant
|
|
2564
|
+
(`'/.well-known/api-catalog'`) so callers can reference the canonical
|
|
2565
|
+
location without duplicating the string.
|
|
2566
|
+
|
|
2567
|
+
**Options:**
|
|
2568
|
+
| Option | Type | Default | Purpose |
|
|
2569
|
+
|---|---|---|---|
|
|
2570
|
+
| `siteUrl` | `string` | — | Canonical origin. Trailing slash stripped. |
|
|
2571
|
+
| `schemaEndpoints` | `ApiCatalogSchemaEndpointEntry[]` | — | Schema.org JSON endpoints with `path` + `schemaType`. |
|
|
2572
|
+
| `schemaMap` | `ApiCatalogSchemaMapEntry` | — | Path of the `createSchemaMap` route. |
|
|
2573
|
+
| `additional` | `ApiCatalogEntry[]` | — | Site-specific APIs not covered by the package factories. |
|
|
2574
|
+
| `cacheControl` | `string \| null` | `'max-age=300'` | Cache-Control header. `null` to omit. |
|
|
2575
|
+
| `contentType` | `string` | `'application/linkset+json'` | Override only with reason. |
|
|
2576
|
+
| `indent` | `number` | `2` | JSON indentation. `0` for compact. |
|
|
2577
|
+
|
|
2578
|
+
### Last-modified dates from git
|
|
2579
|
+
|
|
2580
|
+
`gitLastmod` reads the committer date of the most recent git commit
|
|
2581
|
+
that touched a file, with configurable `excludeCommits` (skip bulk
|
|
2582
|
+
imports / reformats / renames) and `depth` (how many commits to
|
|
2583
|
+
inspect). Use it to feed `dateModified` on JSON-LD pieces or
|
|
2584
|
+
`<lastmod>` in sitemaps without trusting filesystem `mtime` (which
|
|
2585
|
+
gets rewritten on every CI checkout):
|
|
2586
|
+
|
|
2587
|
+
```ts
|
|
2588
|
+
import { gitLastmod } from '@jdevalk/astro-seo-graph';
|
|
2589
|
+
|
|
2590
|
+
const last = gitLastmod(`src/content/blog/${entry.id}/index.md`, {
|
|
2591
|
+
excludeCommits: ['52130a9', '989dc47'],
|
|
2592
|
+
depth: 20,
|
|
2593
|
+
});
|
|
2594
|
+
```
|
|
2595
|
+
|
|
2596
|
+
Returns `null` when the file has no git history, git isn't on the
|
|
2597
|
+
PATH, or every commit in the inspected window is excluded — fall back
|
|
2598
|
+
to `publishDate` in that case. `excludeCommits` matches on the first 7
|
|
2599
|
+
characters of the SHA, so short hashes from `git log --oneline` work
|
|
2600
|
+
directly. Build-time only — shells out to the `git` binary via
|
|
2601
|
+
`execFileSync`.
|
|
2491
2602
|
|
|
2492
2603
|
### The `aggregate` function
|
|
2493
2604
|
|
|
@@ -2544,18 +2655,24 @@ const blog = defineCollection({
|
|
|
2544
2655
|
}
|
|
2545
2656
|
```
|
|
2546
2657
|
|
|
2547
|
-
### `
|
|
2658
|
+
### `buildSeoContext`
|
|
2548
2659
|
|
|
2549
|
-
Pure-TS function that powers `<Seo>` internally.
|
|
2550
|
-
|
|
2660
|
+
Pure-TS function that powers `<Seo>` internally. Returns a flat,
|
|
2661
|
+
render-ready normalization of `SeoProps` (resolved title, canonical, OG
|
|
2662
|
+
fields, hreflang entries, robots directives, twitter overrides). Use when
|
|
2663
|
+
you want to render the head yourself:
|
|
2551
2664
|
|
|
2552
2665
|
```ts
|
|
2553
|
-
import {
|
|
2666
|
+
import { buildSeoContext } from '@jdevalk/astro-seo-graph';
|
|
2554
2667
|
|
|
2555
|
-
const
|
|
2556
|
-
//
|
|
2668
|
+
const ctx = buildSeoContext(mySeoProps, Astro.url.href);
|
|
2669
|
+
// ctx.title, ctx.canonical, ctx.og.*, ctx.twitter, ctx.hreflangs, ...
|
|
2557
2670
|
```
|
|
2558
2671
|
|
|
2672
|
+
The companion constant `ROBOTS_EXTRAS` exposes the
|
|
2673
|
+
`max-snippet:-1, max-image-preview:large, max-video-preview:-1`
|
|
2674
|
+
directives that `<Seo>` always appends to the robots tag.
|
|
2675
|
+
|
|
2559
2676
|
### `buildAlternateLinks`
|
|
2560
2677
|
|
|
2561
2678
|
Pure helper for hreflang link generation. No Astro runtime needed — safe
|
|
@@ -2576,6 +2693,246 @@ const links = buildAlternateLinks({
|
|
|
2576
2693
|
|
|
2577
2694
|
---
|
|
2578
2695
|
|
|
2696
|
+
## Build-time integration
|
|
2697
|
+
|
|
2698
|
+
`@jdevalk/astro-seo-graph/integration` exports a default `seoGraph()`
|
|
2699
|
+
function that returns an Astro integration. It hooks `astro:build:done`
|
|
2700
|
+
to run cross-page SEO checks and optional post-build actions against the
|
|
2701
|
+
static HTML output. SSR pages aren't on disk at build time, so they're
|
|
2702
|
+
not checked.
|
|
2703
|
+
|
|
2704
|
+
```js
|
|
2705
|
+
// astro.config.mjs
|
|
2706
|
+
import { defineConfig } from 'astro/config';
|
|
2707
|
+
import seoGraph from '@jdevalk/astro-seo-graph/integration';
|
|
2708
|
+
|
|
2709
|
+
export default defineConfig({
|
|
2710
|
+
site: 'https://example.com',
|
|
2711
|
+
integrations: [
|
|
2712
|
+
seoGraph({
|
|
2713
|
+
// All options below are optional; listed with defaults.
|
|
2714
|
+
validateH1: true,
|
|
2715
|
+
validateUniqueMetadata: true,
|
|
2716
|
+
validateImageAlt: true,
|
|
2717
|
+
validateMetadataLength: true, // or { title: { max: 60 }, ... }
|
|
2718
|
+
validateInternalLinks: true, // or { skip: (href) => href.startsWith('/api/') }
|
|
2719
|
+
// indexNow: { ... },
|
|
2720
|
+
// llmsTxt: { ... },
|
|
2721
|
+
}),
|
|
2722
|
+
],
|
|
2723
|
+
});
|
|
2724
|
+
```
|
|
2725
|
+
|
|
2726
|
+
### Options
|
|
2727
|
+
|
|
2728
|
+
| Option | Default | Purpose |
|
|
2729
|
+
| ------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
2730
|
+
| `validateH1` | `true` | Warn when a page has zero or >1 `<h1>` elements. |
|
|
2731
|
+
| `validateUniqueMetadata` | `true` | Warn when two pages share the same `<title>` or meta description. |
|
|
2732
|
+
| `validateImageAlt` | `true` | Warn when a page has `<img>` tags missing an `alt` attribute. |
|
|
2733
|
+
| `validateMetadataLength` | `true` | Warn when `<title>` or description length falls outside SERP-friendly bounds (title 30–65, description 70–200 by default; overridable per-field). |
|
|
2734
|
+
| `validateInternalLinks` | `true` | Warn on trailing-slash mismatches and links to pages not in the build. Accepts `{ skip }` to exclude SSR-only routes. |
|
|
2735
|
+
| `indexNow` | — | Submit built URLs to IndexNow. Omit to disable. |
|
|
2736
|
+
| `llmsTxt` | — | Generate `llms.txt` at the root of the build output. Omit to disable. |
|
|
2737
|
+
|
|
2738
|
+
### H1 and metadata validation
|
|
2739
|
+
|
|
2740
|
+
`validateH1` flags pages with missing or duplicate `<h1>` elements — the
|
|
2741
|
+
most common on-page SEO/accessibility miss. `validateUniqueMetadata`
|
|
2742
|
+
flags `<title>` or `<meta name="description">` values that repeat across
|
|
2743
|
+
pages; duplicates hurt Google's ability to pick a canonical result and
|
|
2744
|
+
can only be spotted across the whole corpus. `validateImageAlt` flags
|
|
2745
|
+
`<img>` tags missing an `alt` attribute. WCAG-sanctioned decorative
|
|
2746
|
+
markers are respected: `alt=""` (the canonical pattern) and
|
|
2747
|
+
`role="presentation"`/`role="none"` (removes the image from the
|
|
2748
|
+
accessibility tree) are not flagged. Only a tag with neither triggers a
|
|
2749
|
+
warning.
|
|
2750
|
+
|
|
2751
|
+
`validateMetadataLength` flags `<title>` and `<meta name="description">`
|
|
2752
|
+
values outside the configured bounds. Pass `true` for the defaults
|
|
2753
|
+
(title 30–65, description 70–200) or an object to override per-field:
|
|
2754
|
+
|
|
2755
|
+
```ts
|
|
2756
|
+
validateMetadataLength: {
|
|
2757
|
+
title: { min: 40, max: 60 },
|
|
2758
|
+
description: { max: 160 },
|
|
2759
|
+
},
|
|
2760
|
+
```
|
|
2761
|
+
|
|
2762
|
+
`validateInternalLinks` scans `<a href>` values across every built page
|
|
2763
|
+
and flags two classes of issue: trailing-slash mismatches (e.g. linking
|
|
2764
|
+
to `/about-me` when the built page is `/about-me/` — "works" via
|
|
2765
|
+
redirect but wastes a round-trip on every click) and true 404s (links
|
|
2766
|
+
to paths not in the build). Only same-origin (via `config.site`) and
|
|
2767
|
+
root-relative links are checked; external URLs, `mailto:`, `tel:`, and
|
|
2768
|
+
fragment-only links are skipped.
|
|
2769
|
+
|
|
2770
|
+
Explicit redirects are honored by default: sources in
|
|
2771
|
+
`public/_redirects` (Netlify / Cloudflare Pages format) and literal
|
|
2772
|
+
keys in Astro's `redirects` config are treated as valid link targets.
|
|
2773
|
+
Dynamic rules (`*`, `:splat`, `[slug]` params) are skipped — use
|
|
2774
|
+
`skip` for those cases. Set `honorRedirects: false` to opt out (useful
|
|
2775
|
+
when auditing for redirect hops).
|
|
2776
|
+
|
|
2777
|
+
Pass `{ skip: (href) => boolean }` to exclude SSR-only routes or paths
|
|
2778
|
+
handled at the host/CDN layer.
|
|
2779
|
+
|
|
2780
|
+
The extractors, helpers, and the length resolver are exported for reuse:
|
|
2781
|
+
|
|
2782
|
+
```ts
|
|
2783
|
+
import {
|
|
2784
|
+
classifyInternalLink,
|
|
2785
|
+
collectAstroRedirectSources,
|
|
2786
|
+
countH1s,
|
|
2787
|
+
DEFAULT_METADATA_LENGTH_BOUNDS,
|
|
2788
|
+
extractAnchorHrefs,
|
|
2789
|
+
extractTitle,
|
|
2790
|
+
extractMetaDescription,
|
|
2791
|
+
findImagesWithoutAlt,
|
|
2792
|
+
htmlFileToPath,
|
|
2793
|
+
parseNetlifyRedirects,
|
|
2794
|
+
resolveMetadataLengthBounds,
|
|
2795
|
+
} from '@jdevalk/astro-seo-graph/integration';
|
|
2796
|
+
```
|
|
2797
|
+
|
|
2798
|
+
### IndexNow submission
|
|
2799
|
+
|
|
2800
|
+
```js
|
|
2801
|
+
seoGraph({
|
|
2802
|
+
indexNow: {
|
|
2803
|
+
key: process.env.INDEXNOW_KEY!, // 8–128 chars from [A-Za-z0-9-]
|
|
2804
|
+
host: 'example.com',
|
|
2805
|
+
siteUrl: 'https://example.com',
|
|
2806
|
+
// keyLocation?: defaults to https://<host>/<key>.txt
|
|
2807
|
+
// endpoint?: defaults to api.indexnow.org
|
|
2808
|
+
// filter?: (url) => boolean — composed on top of the built-in
|
|
2809
|
+
// /404 exclusion. Example skipping paginated archives:
|
|
2810
|
+
// filter: (url) => !/^\/blog\/\d+\/$/.test(new URL(url).pathname),
|
|
2811
|
+
},
|
|
2812
|
+
});
|
|
2813
|
+
```
|
|
2814
|
+
|
|
2815
|
+
Only URLs on `host` are submitted. `index.html` paths are rewritten to
|
|
2816
|
+
their trailing-slash form. `/404` (and `/404/`) are always excluded —
|
|
2817
|
+
search engines don't need to be notified about the 404 page and
|
|
2818
|
+
submitting it wastes daily IndexNow quota.
|
|
2819
|
+
|
|
2820
|
+
**Deploy the key file first.** IndexNow verifies ownership by fetching
|
|
2821
|
+
`https://<host>/<key>.txt` on every submission. Submissions sent before
|
|
2822
|
+
the key is reachable in production get rejected (HTTP 403) and the key
|
|
2823
|
+
is treated as invalid — you'll have to rotate it. Serve the key via
|
|
2824
|
+
`createIndexNowKeyRoute` (see below), deploy, confirm the `.txt` loads
|
|
2825
|
+
over HTTPS, _then_ enable `indexNow` in the integration.
|
|
2826
|
+
|
|
2827
|
+
```ts
|
|
2828
|
+
// src/pages/[your-key-here].txt.ts
|
|
2829
|
+
import { createIndexNowKeyRoute } from '@jdevalk/astro-seo-graph';
|
|
2830
|
+
|
|
2831
|
+
export const GET = createIndexNowKeyRoute({ key: 'your-key-here' });
|
|
2832
|
+
```
|
|
2833
|
+
|
|
2834
|
+
The filename (minus `.txt.ts`) must equal the key.
|
|
2835
|
+
|
|
2836
|
+
#### Gating submission to the production branch
|
|
2837
|
+
|
|
2838
|
+
Preview and feature-branch deploys share the same IndexNow key as
|
|
2839
|
+
production but shouldn't notify search engines — submitting preview
|
|
2840
|
+
URLs wastes daily quota and can get the key flagged invalid (the host
|
|
2841
|
+
hasn't served those URLs yet).
|
|
2842
|
+
|
|
2843
|
+
`indexNowOnBranch(branch, options, productionBranch?)` passes the
|
|
2844
|
+
options through unchanged when `branch` matches `productionBranch`
|
|
2845
|
+
(default `"main"`) and returns `undefined` otherwise, so `seoGraph()`
|
|
2846
|
+
skips submission entirely on non-production deploys.
|
|
2847
|
+
|
|
2848
|
+
> **`import.meta.env` is not available in `astro.config.mjs`.** Astro
|
|
2849
|
+
> only exposes `import.meta.env` inside components and pages processed
|
|
2850
|
+
> by the Vite pipeline. Config files run in Node.js before Vite
|
|
2851
|
+
> starts, so you must read branch variables via `process.env` instead.
|
|
2852
|
+
|
|
2853
|
+
```js
|
|
2854
|
+
import { defineConfig } from 'astro/config';
|
|
2855
|
+
import seoGraph, { indexNowOnBranch } from '@jdevalk/astro-seo-graph/integration';
|
|
2856
|
+
|
|
2857
|
+
export default defineConfig({
|
|
2858
|
+
integrations: [
|
|
2859
|
+
seoGraph({
|
|
2860
|
+
indexNow: indexNowOnBranch(process.env.CF_PAGES_BRANCH ?? '', {
|
|
2861
|
+
key: process.env.INDEXNOW_KEY,
|
|
2862
|
+
host: 'example.com',
|
|
2863
|
+
siteUrl: 'https://example.com',
|
|
2864
|
+
}),
|
|
2865
|
+
}),
|
|
2866
|
+
],
|
|
2867
|
+
});
|
|
2868
|
+
```
|
|
2869
|
+
|
|
2870
|
+
Platform branch variables:
|
|
2871
|
+
|
|
2872
|
+
- Cloudflare Pages: `process.env.CF_PAGES_BRANCH`
|
|
2873
|
+
- Vercel: `process.env.VERCEL_GIT_COMMIT_REF`
|
|
2874
|
+
- Netlify: `process.env.BRANCH`
|
|
2875
|
+
|
|
2876
|
+
Pass a third argument to use a branch name other than `"main"`:
|
|
2877
|
+
|
|
2878
|
+
```js
|
|
2879
|
+
indexNowOnBranch(process.env.CF_PAGES_BRANCH ?? '', options, 'production');
|
|
2880
|
+
```
|
|
2881
|
+
|
|
2882
|
+
### llms.txt generation
|
|
2883
|
+
|
|
2884
|
+
Generates an [`llms.txt`](https://llmstxt.org) file — a markdown summary
|
|
2885
|
+
of the site that LLMs can use as a concise entry point.
|
|
2886
|
+
|
|
2887
|
+
```js
|
|
2888
|
+
seoGraph({
|
|
2889
|
+
llmsTxt: {
|
|
2890
|
+
title: 'Example Site',
|
|
2891
|
+
siteUrl: 'https://example.com',
|
|
2892
|
+
summary: 'A demo site about X, Y, and Z.',
|
|
2893
|
+
// details?: extra paragraphs between summary and sections
|
|
2894
|
+
// sections?: user-supplied sections (skips auto-collection)
|
|
2895
|
+
// filter?: (url) => boolean — drop URLs from auto-section
|
|
2896
|
+
// autoSectionName?: defaults to 'Pages'
|
|
2897
|
+
// outputPath?: defaults to 'llms.txt'
|
|
2898
|
+
},
|
|
2899
|
+
});
|
|
2900
|
+
```
|
|
2901
|
+
|
|
2902
|
+
By default, one "Pages" section is auto-generated from every built HTML
|
|
2903
|
+
file's `<title>` + meta description. Supply `sections` to take full
|
|
2904
|
+
control of the structure:
|
|
2905
|
+
|
|
2906
|
+
```js
|
|
2907
|
+
llmsTxt: {
|
|
2908
|
+
title: 'Example Site',
|
|
2909
|
+
siteUrl: 'https://example.com',
|
|
2910
|
+
sections: [
|
|
2911
|
+
{
|
|
2912
|
+
name: 'Docs',
|
|
2913
|
+
links: [
|
|
2914
|
+
{ url: 'https://example.com/docs/intro/', title: 'Intro', description: 'Start here' },
|
|
2915
|
+
],
|
|
2916
|
+
},
|
|
2917
|
+
{ name: 'Blog', links: [/* ... */] },
|
|
2918
|
+
],
|
|
2919
|
+
}
|
|
2920
|
+
```
|
|
2921
|
+
|
|
2922
|
+
The renderer is also exported for non-Astro contexts:
|
|
2923
|
+
|
|
2924
|
+
```ts
|
|
2925
|
+
import { renderLlmsTxt } from '@jdevalk/astro-seo-graph';
|
|
2926
|
+
|
|
2927
|
+
const markdown = renderLlmsTxt({
|
|
2928
|
+
title: 'Example',
|
|
2929
|
+
summary: 'A demo site.',
|
|
2930
|
+
sections: [{ name: 'Pages', links: [{ url: 'https://x/', title: 'Home' }] }],
|
|
2931
|
+
});
|
|
2932
|
+
```
|
|
2933
|
+
|
|
2934
|
+
---
|
|
2935
|
+
|
|
2579
2936
|
## Complete integration example
|
|
2580
2937
|
|
|
2581
2938
|
Here's how joost.blog wires everything together. Use this as a reference
|
|
@@ -3095,7 +3452,8 @@ seo-graph/
|
|
|
3095
3452
|
│ ├── alternates.ts # buildAlternateLinks
|
|
3096
3453
|
│ ├── content.ts # seoSchema, imageSchema
|
|
3097
3454
|
│ └── components/
|
|
3098
|
-
│
|
|
3455
|
+
│ ├── seo-props.ts # SeoProps interface
|
|
3456
|
+
│ └── seo-context.ts # buildSeoContext
|
|
3099
3457
|
├── AGENTS.md # This file
|
|
3100
3458
|
├── README.md # Project overview
|
|
3101
3459
|
└── pnpm-workspace.yaml
|
package/README.md
CHANGED
|
@@ -35,19 +35,26 @@ npm install @jdevalk/seo-graph-core
|
|
|
35
35
|
| `assembleGraph(pieces)` | Wraps pieces in a `{ @context, @graph }` envelope with first-wins deduplication by `@id`. |
|
|
36
36
|
| `deduplicateByGraphId(entities)` | The dedup engine on its own, in case you need custom assembly. |
|
|
37
37
|
|
|
38
|
+
Pass `{ warnOnDanglingReferences: true }` to `assembleGraph` to emit a
|
|
39
|
+
console warning when any `{ '@id': '...' }` reference doesn't resolve to
|
|
40
|
+
an entity in the graph — a cheap sanity check to catch typos and
|
|
41
|
+
missing pieces during development.
|
|
42
|
+
|
|
38
43
|
### IndexNow
|
|
39
44
|
|
|
40
|
-
| API
|
|
41
|
-
|
|
|
42
|
-
| `submitToIndexNow`
|
|
43
|
-
| `generateIndexNowKey`
|
|
44
|
-
| `validateIndexNowKey`
|
|
45
|
-
| `getIndexNowKeyFileContent`
|
|
45
|
+
| API | Purpose |
|
|
46
|
+
| --------------------------- | ------------------------------------------------------------------------------ |
|
|
47
|
+
| `submitToIndexNow` | POST URLs to the IndexNow aggregator. Filters by host, dedupes, chunks at 10k. |
|
|
48
|
+
| `generateIndexNowKey` | Generate a random hex key (Web Crypto). |
|
|
49
|
+
| `validateIndexNowKey` | Verify a key is 8–128 chars from `[A-Za-z0-9-]` (IndexNow spec allow-list). |
|
|
50
|
+
| `getIndexNowKeyFileContent` | Body to serve at `/<key>.txt` for host verification. |
|
|
46
51
|
|
|
47
52
|
### Piece builders
|
|
48
53
|
|
|
49
|
-
|
|
50
|
-
object with `@type` and `@id`.
|
|
54
|
+
The dedicated builders take an input object and the `IdFactory`, and return a
|
|
55
|
+
plain object with `@type` and `@id`. `buildPiece` is the exception: it takes
|
|
56
|
+
only the input object — pass `@id` and `@type` inline in that object. Builders
|
|
57
|
+
for CreativeWork subtypes (`WebSite`,
|
|
51
58
|
`WebPage`, `Article`) share a common set of optional fields via
|
|
52
59
|
`CreativeWorkFields`: `description`, `inLanguage`, `datePublished`,
|
|
53
60
|
`dateModified`, `about`, `copyrightHolder`, `copyrightYear`,
|
package/dist/assemble.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"assemble.js","sourceRoot":"","sources":["../src/assemble.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAcnD;;;GAGG;AACH,SAAS,iBAAiB,CAAC,KAAc,EAAE,IAAiB;IACxD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO;IAE/E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAClC,CAAC;QACD,OAAO;IACX,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAC;IAE7C,iEAAiE;IACjE,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;QAC/D,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACrB,OAAO;IACX,CAAC;IAED,kDAAkD;IAClD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CACzB,MAAoB,EACpB,OAA8B;IAE9B,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAE3C,IAAI,OAAO,EAAE,wBAAwB,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACpC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACjC,CAAC;QACL,CAAC;QAED,oEAAoE;QACpE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;QACvC,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;YACpC,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9C,IAAI,GAAG,KAAK,KAAK;oBAAE,SAAS;gBAC5B,iBAAiB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACtC,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;gBAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YAClD,CAAC;QACL,CAAC;QAED,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CACR,qCAAqC,UAAU,eAAe,GAAG,6CAA6C,CACjH,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO;QACH,UAAU,EAAE,oBAAoB;QAChC,QAAQ,EAAE,KAAK;KAClB,CAAC;AACN,CAAC"}
|
|
1
|
+
{"version":3,"file":"assemble.js","sourceRoot":"","sources":["../src/assemble.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAcnD;;;GAGG;AACH,SAAS,iBAAiB,CAAC,KAAc,EAAE,IAAiB;IACxD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO;IAE/E,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACvB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACvB,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAClC,CAAC;QACD,OAAO;IACX,CAAC;IAED,MAAM,GAAG,GAAG,KAAgC,CAAC;IAE7C,iEAAiE;IACjE,IAAI,OAAO,GAAG,CAAC,KAAK,CAAC,KAAK,QAAQ,IAAI,GAAG,CAAC,OAAO,CAAC,KAAK,SAAS,EAAE,CAAC;QAC/D,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;QACrB,OAAO;IACX,CAAC;IAED,kDAAkD;IAClD,KAAK,MAAM,GAAG,IAAI,MAAM,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACnC,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;IACjC,CAAC;AACL,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CACzB,MAAoB,EACpB,OAA8B;IAE9B,MAAM,KAAK,GAAG,oBAAoB,CAAC,MAAM,CAAC,CAAC;IAE3C,IAAI,OAAO,EAAE,wBAAwB,EAAE,CAAC;QACpC,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;QACpC,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;YACzB,IAAI,OAAO,MAAM,CAAC,KAAK,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACpC,SAAS,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC;YACjC,CAAC;QACL,CAAC;QAED,oEAAoE;QACpE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;QACvC,KAAK,MAAM,MAAM,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YAC3C,MAAM,SAAS,GAAG,IAAI,GAAG,EAAU,CAAC;YACpC,KAAK,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC9C,IAAI,GAAG,KAAK,KAAK;oBAAE,SAAS;gBAC5B,iBAAiB,CAAC,GAAG,EAAE,SAAS,CAAC,CAAC;YACtC,CAAC;YACD,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;gBAC1B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;oBAAE,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,CAAC;YAClD,CAAC;QACL,CAAC;QAED,KAAK,MAAM,CAAC,GAAG,EAAE,UAAU,CAAC,IAAI,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;gBACtB,OAAO,CAAC,IAAI,CACR,qCAAqC,UAAU,eAAe,GAAG,6CAA6C,CACjH,CAAC;YACN,CAAC;QACL,CAAC;IACL,CAAC;IAED,OAAO;QACH,UAAU,EAAE,oBAAoB;QAChC,QAAQ,EAAE,KAAK;KAClB,CAAC;AACN,CAAC","sourcesContent":["import type { GraphEntity } from './types.js';\nimport { deduplicateByGraphId } from './dedupe.js';\n\nexport interface AssembleGraphOptions {\n /**\n * When `true`, logs a warning for every `{ '@id': '...' }` reference\n * in the graph that doesn't resolve to an entity with a matching\n * `@id`. Helps catch broken links in the graph (e.g. a WebSite\n * referencing a Person that was never included).\n *\n * Defaults to `false`.\n */\n warnOnDanglingReferences?: boolean;\n}\n\n/**\n * Walk a value recursively and collect every `{ '@id': string }` reference\n * that is NOT a top-level entity (i.e. doesn't have `@type`).\n */\nfunction collectReferences(value: unknown, refs: Set<string>): void {\n if (value === null || value === undefined || typeof value !== 'object') return;\n\n if (Array.isArray(value)) {\n for (const item of value) {\n collectReferences(item, refs);\n }\n return;\n }\n\n const obj = value as Record<string, unknown>;\n\n // An object with @id but no @type is a reference, not an entity.\n if (typeof obj['@id'] === 'string' && obj['@type'] === undefined) {\n refs.add(obj['@id']);\n return;\n }\n\n // Recurse into values of entities/nested objects.\n for (const val of Object.values(obj)) {\n collectReferences(val, refs);\n }\n}\n\n/**\n * Wrap a list of pieces in a `@context + @graph` envelope. Pieces are\n * deduplicated by `@id` before assembly; first occurrence wins.\n *\n * When `warnOnDanglingReferences` is enabled, logs warnings for any\n * `{ '@id': '...' }` reference that doesn't resolve to an entity in\n * the graph.\n */\nexport function assembleGraph<T extends GraphEntity>(\n pieces: readonly T[],\n options?: AssembleGraphOptions,\n): { '@context': 'https://schema.org'; '@graph': T[] } {\n const graph = deduplicateByGraphId(pieces);\n\n if (options?.warnOnDanglingReferences) {\n const entityIds = new Set<string>();\n for (const entity of graph) {\n if (typeof entity['@id'] === 'string') {\n entityIds.add(entity['@id']);\n }\n }\n\n // Map from referenced @id → @type of the entity that references it.\n const refs = new Map<string, string>();\n for (const entity of graph) {\n const sourceType = String(entity['@type']);\n const collected = new Set<string>();\n for (const [key, val] of Object.entries(entity)) {\n if (key === '@id') continue;\n collectReferences(val, collected);\n }\n for (const ref of collected) {\n if (!refs.has(ref)) refs.set(ref, sourceType);\n }\n }\n\n for (const [ref, sourceType] of refs) {\n if (!entityIds.has(ref)) {\n console.warn(\n `[seo-graph] Dangling reference in ${sourceType}: { \"@id\": \"${ref}\" } does not match any entity in the graph.`,\n );\n }\n }\n }\n\n return {\n '@context': 'https://schema.org',\n '@graph': graph,\n };\n}\n"]}
|
package/dist/dedupe.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAwB,QAAsB;IAC9E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACnB,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,SAAS;YAC3B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC"}
|
|
1
|
+
{"version":3,"file":"dedupe.js","sourceRoot":"","sources":["../src/dedupe.ts"],"names":[],"mappings":"AAEA;;;GAGG;AACH,MAAM,UAAU,oBAAoB,CAAwB,QAAsB;IAC9E,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,MAAM,GAAQ,EAAE,CAAC;IACvB,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC5B,MAAM,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QACzB,IAAI,EAAE,KAAK,SAAS,EAAE,CAAC;YACnB,IAAI,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;gBAAE,SAAS;YAC3B,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACjB,CAAC;QACD,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IACD,OAAO,MAAM,CAAC;AAClB,CAAC","sourcesContent":["import type { GraphEntity } from './types.js';\n\n/**\n * Deduplicate entities by their `@id`. First occurrence wins. Entities\n * without an `@id` are always kept.\n */\nexport function deduplicateByGraphId<T extends GraphEntity>(entities: readonly T[]): T[] {\n const seen = new Set<string>();\n const result: T[] = [];\n for (const entity of entities) {\n const id = entity['@id'];\n if (id !== undefined) {\n if (seen.has(id)) continue;\n seen.add(id);\n }\n result.push(entity);\n }\n return result;\n}\n"]}
|
package/dist/ids.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ids.js","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AA2CA;;;GAGG;AACH,SAAS,kBAAkB,CAAC,GAAW;IACnC,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACpC,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC3C,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;IAEpE,OAAO;QACH,MAAM,EAAE,GAAG,MAAM,qBAAqB;QACtC,WAAW,EAAE,GAAG,IAAI,cAAc;QAClC,OAAO,EAAE,GAAG,IAAI,uBAAuB;QACvC,UAAU,EAAE,GAAG,IAAI,mBAAmB;QACtC,YAAY,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,8BAA8B,IAAI,EAAE;QAC3E,OAAO,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,yBAAyB,IAAI,CAAC,WAAW,EAAE,EAAE;QAC/E,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG;QAC7B,UAAU,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,aAAa;QAChD,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,UAAU;QAC1C,WAAW,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,QAAQ;QAC5C,YAAY,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,eAAe;KACvD,CAAC;AACN,CAAC"}
|
|
1
|
+
{"version":3,"file":"ids.js","sourceRoot":"","sources":["../src/ids.ts"],"names":[],"mappings":"AA2CA;;;GAGG;AACH,SAAS,kBAAkB,CAAC,GAAW;IACnC,OAAO,GAAG,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,GAAW;IACpC,OAAO,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG,CAAC;AAC/C,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,OAAO,CAAC,OAAuB;IAC3C,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,mBAAmB,CAAC,OAAO,CAAC,SAAS,IAAI,GAAG,IAAI,GAAG,CAAC,CAAC;IAEpE,OAAO;QACH,MAAM,EAAE,GAAG,MAAM,qBAAqB;QACtC,WAAW,EAAE,GAAG,IAAI,cAAc;QAClC,OAAO,EAAE,GAAG,IAAI,uBAAuB;QACvC,UAAU,EAAE,GAAG,IAAI,mBAAmB;QACtC,YAAY,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,8BAA8B,IAAI,EAAE;QAC3E,OAAO,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,GAAG,IAAI,yBAAyB,IAAI,CAAC,WAAW,EAAE,EAAE;QAC/E,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG;QAC7B,UAAU,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,aAAa;QAChD,OAAO,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,UAAU;QAC1C,WAAW,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,QAAQ;QAC5C,YAAY,EAAE,CAAC,GAAW,EAAE,EAAE,CAAC,GAAG,GAAG,eAAe;KACvD,CAAC;AACN,CAAC","sourcesContent":["/**\n * Factory for stable @id URIs used to cross-reference entities in the graph.\n *\n * Site-wide singletons live at `${siteUrl}/#/schema.org/<Type>` (WebSite,\n * Person, etc). Per-page entities live at `${pageUrl}#<suffix>` (Article,\n * Breadcrumb, PrimaryImage). The `person` id defaults to the site root but\n * can be relocated to e.g. `/about-me/` via `personUrl`.\n */\nexport interface IdFactory {\n /** Site-wide Person id. Defaults to `${siteUrl}/#/schema.org/Person`. */\n readonly person: string;\n /** Site-wide Person image id (friendly-named fragment). */\n readonly personImage: string;\n /** Site-wide WebSite id. */\n readonly website: string;\n /** Site-wide SiteNavigationElement id. */\n readonly navigation: string;\n /** Per-org id factory — pass a stable slug. */\n organization(slug: string): string;\n /** Per-country id factory — pass an ISO 3166 alpha-2 code. */\n country(code: string): string;\n /** WebPage id equals the canonical URL itself. */\n webPage(url: string): string;\n /** Breadcrumb id for a given page URL. */\n breadcrumb(url: string): string;\n /** Article id for a given page URL. */\n article(url: string): string;\n /** VideoObject id for a given page URL. */\n videoObject(url: string): string;\n /** Primary ImageObject id for a given page URL. */\n primaryImage(url: string): string;\n}\n\nexport interface MakeIdsOptions {\n /** Canonical site URL, with or without trailing slash. E.g. `https://joost.blog`. */\n siteUrl: string;\n /**\n * URL where the site-wide Person entity lives, with trailing slash.\n * Defaults to `${siteUrl}/`. joost.blog uses `${siteUrl}/about-me/`.\n */\n personUrl?: string;\n}\n\n/**\n * Normalize a base URL: strip any trailing slash so we can append `/#...`\n * consistently.\n */\nfunction stripTrailingSlash(url: string): string {\n return url.replace(/\\/+$/, '');\n}\n\n/**\n * Normalize a person URL: ensure a single trailing slash so the resulting\n * id reads `${personUrl}#/schema.org/Person` with the `/` already present.\n */\nfunction ensureTrailingSlash(url: string): string {\n return url.endsWith('/') ? url : `${url}/`;\n}\n\n/**\n * Build an IdFactory for a given site. The returned factory is stateless —\n * call it as many times as you like, on any piece builder.\n */\nexport function makeIds(options: MakeIdsOptions): IdFactory {\n const site = stripTrailingSlash(options.siteUrl);\n const person = ensureTrailingSlash(options.personUrl ?? `${site}/`);\n\n return {\n person: `${person}#/schema.org/Person`,\n personImage: `${site}/#personlogo`,\n website: `${site}/#/schema.org/WebSite`,\n navigation: `${site}/#site-navigation`,\n organization: (slug: string) => `${site}/#/schema.org/Organization/${slug}`,\n country: (code: string) => `${site}/#/schema.org/Country/${code.toLowerCase()}`,\n webPage: (url: string) => url,\n breadcrumb: (url: string) => `${url}#breadcrumb`,\n article: (url: string) => `${url}#article`,\n videoObject: (url: string) => `${url}#video`,\n primaryImage: (url: string) => `${url}#primaryimage`,\n };\n}\n"]}
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAG3E,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpG,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9C,iBAAiB;AACjB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,oBAAoB;AACpB,OAAO,EACH,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,GAChC,MAAM,eAAe,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAG3E,OAAO,EAAE,uBAAuB,EAAE,yBAAyB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAEpG,OAAO,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACnC,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9C,iBAAiB;AACjB,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,0BAA0B,EAAE,MAAM,wBAAwB,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAGnD,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAG7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAGrD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAEhD,oBAAoB;AACpB,OAAO,EACH,gBAAgB,EAChB,mBAAmB,EACnB,mBAAmB,EACnB,yBAAyB,EACzB,yBAAyB,EACzB,6BAA6B,GAChC,MAAM,eAAe,CAAC","sourcesContent":["// @jdevalk/seo-graph-core — agent-ready schema.org JSON-LD graph builders.\n\nexport type { GraphEntity, Reference, SchemaGraph, CreativeWorkFields } from './types.js';\nexport { applyCreativeWorkFields, spreadRemainingProperties, CREATIVE_WORK_KEYS } from './types.js';\nexport type { IdFactory, MakeIdsOptions } from './ids.js';\nexport { makeIds } from './ids.js';\nexport { deduplicateByGraphId } from './dedupe.js';\nexport { assembleGraph } from './assemble.js';\nexport type { AssembleGraphOptions } from './assemble.js';\n\n// Piece builders\nexport { buildWebSite } from './pieces/website.js';\nexport type { WebSiteInput } from './pieces/website.js';\n\nexport { buildSiteNavigationElement } from './pieces/navigation.js';\nexport type { NavigationItem, SiteNavigationInput } from './pieces/navigation.js';\n\nexport { buildWebPage } from './pieces/webpage.js';\nexport type { WebPageInput, WebPageType } from './pieces/webpage.js';\n\nexport { buildArticle } from './pieces/article.js';\nexport type { ArticleInput, ArticleType } from './pieces/article.js';\n\nexport { buildBreadcrumbList } from './pieces/breadcrumb.js';\nexport type { BreadcrumbItem, BreadcrumbListInput } from './pieces/breadcrumb.js';\n\nexport { buildImageObject } from './pieces/image.js';\nexport type { ImageObjectInput } from './pieces/image.js';\n\nexport { buildVideoObject } from './pieces/video.js';\nexport type { VideoObjectInput } from './pieces/video.js';\n\nexport { buildPiece } from './pieces/custom.js';\n\n// IndexNow protocol\nexport {\n submitToIndexNow,\n validateIndexNowKey,\n generateIndexNowKey,\n getIndexNowKeyFileContent,\n DEFAULT_INDEXNOW_ENDPOINT,\n INDEXNOW_MAX_URLS_PER_REQUEST,\n} from './indexnow.js';\nexport type { SubmitToIndexNowOptions, IndexNowSubmitResult } from './indexnow.js';\n"]}
|
package/dist/indexnow.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export declare const INDEXNOW_MAX_URLS_PER_REQUEST = 10000;
|
|
|
5
5
|
export interface SubmitToIndexNowOptions {
|
|
6
6
|
/** Bare host, e.g. `example.com`. No scheme, no trailing slash. */
|
|
7
7
|
host: string;
|
|
8
|
-
/** 8–128
|
|
8
|
+
/** 8–128 character key from `[A-Za-z0-9-]`. Must match the key file served on the host. */
|
|
9
9
|
key: string;
|
|
10
10
|
/**
|
|
11
11
|
* Optional absolute URL where the key file is hosted. Defaults to
|
|
@@ -36,7 +36,7 @@ export interface IndexNowSubmitResult {
|
|
|
36
36
|
* per chunk; a single-chunk submission returns a one-element array.
|
|
37
37
|
*/
|
|
38
38
|
export declare function submitToIndexNow(options: SubmitToIndexNowOptions): Promise<IndexNowSubmitResult[]>;
|
|
39
|
-
/** Returns true when `key` is 8–128
|
|
39
|
+
/** Returns true when `key` is 8–128 characters drawn from `[A-Za-z0-9-]`. */
|
|
40
40
|
export declare function validateIndexNowKey(key: string): boolean;
|
|
41
41
|
/**
|
|
42
42
|
* Generate a cryptographically random IndexNow key of `length` hex
|
package/dist/indexnow.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexnow.d.ts","sourceRoot":"","sources":["../src/indexnow.ts"],"names":[],"mappings":"AAIA,2EAA2E;AAC3E,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAE7E,2DAA2D;AAC3D,eAAO,MAAM,6BAA6B,QAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"indexnow.d.ts","sourceRoot":"","sources":["../src/indexnow.ts"],"names":[],"mappings":"AAIA,2EAA2E;AAC3E,eAAO,MAAM,yBAAyB,sCAAsC,CAAC;AAE7E,2DAA2D;AAC3D,eAAO,MAAM,6BAA6B,QAAS,CAAC;AAapD,MAAM,WAAW,uBAAuB;IACpC,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;IACb,2FAA2F;IAC3F,GAAG,EAAE,MAAM,CAAC;IACZ;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gEAAgE;IAChE,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,oBAAoB;IACjC,yEAAyE;IACzE,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,EAAE,EAAE,OAAO,CAAC;IACZ,sEAAsE;IACtE,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;CACrB;AAED;;;;GAIG;AACH,wBAAsB,gBAAgB,CAClC,OAAO,EAAE,uBAAuB,GACjC,OAAO,CAAC,oBAAoB,EAAE,CAAC,CAgDjC;AA0BD,6EAA6E;AAC7E,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAIxD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,SAAK,GAAG,MAAM,CAWvD;AAED;;;;GAIG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAO7D"}
|
package/dist/indexnow.js
CHANGED
|
@@ -8,7 +8,13 @@ export const INDEXNOW_MAX_URLS_PER_REQUEST = 10_000;
|
|
|
8
8
|
/** Key length constraints per the spec. */
|
|
9
9
|
const MIN_KEY_LENGTH = 8;
|
|
10
10
|
const MAX_KEY_LENGTH = 128;
|
|
11
|
-
|
|
11
|
+
// Per the IndexNow spec the key may contain [A-Za-z0-9-]. The wording at
|
|
12
|
+
// indexnow.org calls these "hexadecimal characters" but the explicit
|
|
13
|
+
// allow-list is the broader set, and Bing / Yandex both issue and accept
|
|
14
|
+
// non-hex keys (Yandex's own docs use a mixed-case example).
|
|
15
|
+
// Refs: https://www.indexnow.org/documentation
|
|
16
|
+
// https://yandex.com/support/webmaster/en/indexnow/key
|
|
17
|
+
const KEY_RE = /^[A-Za-z0-9-]+$/;
|
|
12
18
|
/**
|
|
13
19
|
* Submit URLs to IndexNow. Filters out URLs not on `host`, deduplicates,
|
|
14
20
|
* and chunks at {@link INDEXNOW_MAX_URLS_PER_REQUEST}. Returns one result
|
|
@@ -17,7 +23,7 @@ const HEX_KEY_RE = /^[a-f0-9]+$/i;
|
|
|
17
23
|
export async function submitToIndexNow(options) {
|
|
18
24
|
const { host, key, urls } = options;
|
|
19
25
|
if (!validateIndexNowKey(key)) {
|
|
20
|
-
throw new Error(`IndexNow key must be ${MIN_KEY_LENGTH}–${MAX_KEY_LENGTH}
|
|
26
|
+
throw new Error(`IndexNow key must be ${MIN_KEY_LENGTH}–${MAX_KEY_LENGTH} characters from [A-Za-z0-9-].`);
|
|
21
27
|
}
|
|
22
28
|
if (!host || host.includes('/') || host.includes(':')) {
|
|
23
29
|
throw new Error(`IndexNow host must be a bare host (got: ${host}).`);
|
|
@@ -86,13 +92,13 @@ function filterUrlsForHost(urls, host) {
|
|
|
86
92
|
}
|
|
87
93
|
return out;
|
|
88
94
|
}
|
|
89
|
-
/** Returns true when `key` is 8–128
|
|
95
|
+
/** Returns true when `key` is 8–128 characters drawn from `[A-Za-z0-9-]`. */
|
|
90
96
|
export function validateIndexNowKey(key) {
|
|
91
97
|
if (typeof key !== 'string')
|
|
92
98
|
return false;
|
|
93
99
|
if (key.length < MIN_KEY_LENGTH || key.length > MAX_KEY_LENGTH)
|
|
94
100
|
return false;
|
|
95
|
-
return
|
|
101
|
+
return KEY_RE.test(key);
|
|
96
102
|
}
|
|
97
103
|
/**
|
|
98
104
|
* Generate a cryptographically random IndexNow key of `length` hex
|
|
@@ -117,7 +123,7 @@ export function generateIndexNowKey(length = 32) {
|
|
|
117
123
|
*/
|
|
118
124
|
export function getIndexNowKeyFileContent(key) {
|
|
119
125
|
if (!validateIndexNowKey(key)) {
|
|
120
|
-
throw new Error(`IndexNow key must be ${MIN_KEY_LENGTH}–${MAX_KEY_LENGTH}
|
|
126
|
+
throw new Error(`IndexNow key must be ${MIN_KEY_LENGTH}–${MAX_KEY_LENGTH} characters from [A-Za-z0-9-].`);
|
|
121
127
|
}
|
|
122
128
|
return key;
|
|
123
129
|
}
|
package/dist/indexnow.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"indexnow.js","sourceRoot":"","sources":["../src/indexnow.ts"],"names":[],"mappings":"AAAA,0EAA0E;AAC1E,oEAAoE;AACpE,6EAA6E;AAE7E,2EAA2E;AAC3E,MAAM,CAAC,MAAM,yBAAyB,GAAG,mCAAmC,CAAC;AAE7E,2DAA2D;AAC3D,MAAM,CAAC,MAAM,6BAA6B,GAAG,MAAM,CAAC;AAEpD,2CAA2C;AAC3C,MAAM,cAAc,GAAG,CAAC,CAAC;AACzB,MAAM,cAAc,GAAG,GAAG,CAAC;AAC3B,MAAM,UAAU,GAAG,cAAc,CAAC;AAgClC;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAClC,OAAgC;IAEhC,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,IAAI,EAAE,GAAG,OAAO,CAAC;IACpC,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACX,wBAAwB,cAAc,IAAI,cAAc,kBAAkB,CAC7E,CAAC;IACN,CAAC;IACD,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACpD,MAAM,IAAI,KAAK,CAAC,2CAA2C,IAAI,IAAI,CAAC,CAAC;IACzE,CAAC;IAED,MAAM,QAAQ,GAAG,iBAAiB,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC/C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAErC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,yBAAyB,CAAC;IAC/D,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,WAAW,IAAI,IAAI,GAAG,MAAM,CAAC;IACxE,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,KAAK,CAAC;IAEvC,MAAM,OAAO,GAA2B,EAAE,CAAC;IAC3C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,QAAQ,CAAC,MAAM,EAAE,CAAC,IAAI,6BAA6B,EAAE,CAAC;QACtE,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,GAAG,6BAA6B,CAAC,CAAC;QACnE,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,GAAG,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAC;QACxE,IAAI,CAAC;YACD,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,QAAQ,EAAE;gBAChC,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE;oBACL,cAAc,EAAE,iCAAiC;oBACjD,MAAM,EAAE,kBAAkB;iBAC7B;gBACD,IAAI;aACP,CAAC,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,IAAI,CAAC;gBACT,MAAM,EAAE,GAAG,CAAC,MAAM;gBAClB,EAAE,EAAE,GAAG,CAAC,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,GAAG;gBAC5C,OAAO,EAAE,IAAI;gBACb,SAAS,EAAE,KAAK,CAAC,MAAM;aAC1B,CAAC,CAAC;QACP,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACX,OAAO,CAAC,IAAI,CAAC;gBACT,MAAM,EAAE,CAAC;gBACT,EAAE,EAAE,KAAK;gBACT,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;gBACzD,SAAS,EAAE,KAAK,CAAC,MAAM;aAC1B,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IACD,OAAO,OAAO,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,SAAS,iBAAiB,CAAC,IAAuB,EAAE,IAAY;IAC5D,MAAM,SAAS,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,MAAW,CAAC;QAChB,IAAI,CAAC;YACD,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACL,SAAS;QACb,CAAC;QACD,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,KAAK,SAAS;YAAE,SAAS;QAC1D,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC;QACrC,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;YAAE,SAAS;QACnC,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACrB,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACzB,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,mBAAmB,CAAC,GAAW;IAC3C,IAAI,OAAO,GAAG,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC1C,IAAI,GAAG,CAAC,MAAM,GAAG,cAAc,IAAI,GAAG,CAAC,MAAM,GAAG,cAAc;QAAE,OAAO,KAAK,CAAC;IAC7E,OAAO,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,MAAM,GAAG,EAAE;IAC3C,IAAI,MAAM,GAAG,cAAc,IAAI,MAAM,GAAG,cAAc,IAAI,MAAM,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,KAAK,CACX,sDAAsD,cAAc,QAAQ,cAAc,GAAG,CAChG,CAAC;IACN,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IACzC,MAAM,CAAC,eAAe,CAAC,KAAK,CAAC,CAAC;IAC9B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,CAAC,IAAI,KAAK;QAAE,GAAG,IAAI,CAAC,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAC9D,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAW;IACjD,IAAI,CAAC,mBAAmB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,KAAK,CACX,wBAAwB,cAAc,IAAI,cAAc,kBAAkB,CAC7E,CAAC;IACN,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC"}
|
|
1
|
+
{"version":3,"file":"indexnow.js","sourceRoot":"","sources":["../src/indexnow.ts"],"names":[],"mappings":"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","sourcesContent":["// IndexNow protocol support. Submits URLs to participating search engines\n// (Bing, Yandex, Seznam, Naver, Yep) through the neutral aggregator\n// endpoint at api.indexnow.org. Spec: https://www.indexnow.org/documentation\n\n/** Default aggregator endpoint. Fans out to every participating engine. */\nexport const DEFAULT_INDEXNOW_ENDPOINT = 'https://api.indexnow.org/IndexNow';\n\n/** IndexNow accepts up to 10,000 URLs per bulk request. */\nexport const INDEXNOW_MAX_URLS_PER_REQUEST = 10_000;\n\n/** Key length constraints per the spec. */\nconst MIN_KEY_LENGTH = 8;\nconst MAX_KEY_LENGTH = 128;\n// Per the IndexNow spec the key may contain [A-Za-z0-9-]. The wording at\n// indexnow.org calls these \"hexadecimal characters\" but the explicit\n// allow-list is the broader set, and Bing / Yandex both issue and accept\n// non-hex keys (Yandex's own docs use a mixed-case example).\n// Refs: https://www.indexnow.org/documentation\n// https://yandex.com/support/webmaster/en/indexnow/key\nconst KEY_RE = /^[A-Za-z0-9-]+$/;\n\nexport interface SubmitToIndexNowOptions {\n /** Bare host, e.g. `example.com`. No scheme, no trailing slash. */\n host: string;\n /** 8–128 character key from `[A-Za-z0-9-]`. Must match the key file served on the host. */\n key: string;\n /**\n * Optional absolute URL where the key file is hosted. Defaults to\n * `https://<host>/<key>.txt`. Supply this when the key file lives at\n * a non-default path.\n */\n keyLocation?: string;\n /** URLs to submit. Must all be on `host`. Empty list is a no-op. */\n urls: readonly string[];\n /** Override the endpoint. Defaults to {@link DEFAULT_INDEXNOW_ENDPOINT}. */\n endpoint?: string;\n /** Injectable fetch for testing. Defaults to global `fetch`. */\n fetch?: typeof fetch;\n}\n\nexport interface IndexNowSubmitResult {\n /** HTTP status from the IndexNow server. `0` when the request failed. */\n status: number;\n /** Whether the submission was accepted (200 or 202). */\n ok: boolean;\n /** Response body text if any, or error message on network failure. */\n message: string;\n /** Number of URLs actually submitted after filtering/dedup. */\n submitted: number;\n}\n\n/**\n * Submit URLs to IndexNow. Filters out URLs not on `host`, deduplicates,\n * and chunks at {@link INDEXNOW_MAX_URLS_PER_REQUEST}. Returns one result\n * per chunk; a single-chunk submission returns a one-element array.\n */\nexport async function submitToIndexNow(\n options: SubmitToIndexNowOptions,\n): Promise<IndexNowSubmitResult[]> {\n const { host, key, urls } = options;\n if (!validateIndexNowKey(key)) {\n throw new Error(\n `IndexNow key must be ${MIN_KEY_LENGTH}–${MAX_KEY_LENGTH} characters from [A-Za-z0-9-].`,\n );\n }\n if (!host || host.includes('/') || host.includes(':')) {\n throw new Error(`IndexNow host must be a bare host (got: ${host}).`);\n }\n\n const filtered = filterUrlsForHost(urls, host);\n if (filtered.length === 0) return [];\n\n const endpoint = options.endpoint ?? DEFAULT_INDEXNOW_ENDPOINT;\n const keyLocation = options.keyLocation ?? `https://${host}/${key}.txt`;\n const doFetch = options.fetch ?? fetch;\n\n const results: IndexNowSubmitResult[] = [];\n for (let i = 0; i < filtered.length; i += INDEXNOW_MAX_URLS_PER_REQUEST) {\n const chunk = filtered.slice(i, i + INDEXNOW_MAX_URLS_PER_REQUEST);\n const body = JSON.stringify({ host, key, keyLocation, urlList: chunk });\n try {\n const res = await doFetch(endpoint, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json; charset=utf-8',\n Accept: 'application/json',\n },\n body,\n });\n const text = await res.text().catch(() => '');\n results.push({\n status: res.status,\n ok: res.status === 200 || res.status === 202,\n message: text,\n submitted: chunk.length,\n });\n } catch (err) {\n results.push({\n status: 0,\n ok: false,\n message: err instanceof Error ? err.message : String(err),\n submitted: chunk.length,\n });\n }\n }\n return results;\n}\n\n/**\n * Keep only URLs whose hostname matches `host` (case-insensitive) and\n * deduplicate while preserving first-seen order.\n */\nfunction filterUrlsForHost(urls: readonly string[], host: string): string[] {\n const lowerHost = host.toLowerCase();\n const seen = new Set<string>();\n const out: string[] = [];\n for (const raw of urls) {\n let parsed: URL;\n try {\n parsed = new URL(raw);\n } catch {\n continue;\n }\n if (parsed.hostname.toLowerCase() !== lowerHost) continue;\n const normalized = parsed.toString();\n if (seen.has(normalized)) continue;\n seen.add(normalized);\n out.push(normalized);\n }\n return out;\n}\n\n/** Returns true when `key` is 8–128 characters drawn from `[A-Za-z0-9-]`. */\nexport function validateIndexNowKey(key: string): boolean {\n if (typeof key !== 'string') return false;\n if (key.length < MIN_KEY_LENGTH || key.length > MAX_KEY_LENGTH) return false;\n return KEY_RE.test(key);\n}\n\n/**\n * Generate a cryptographically random IndexNow key of `length` hex\n * characters. Default 32. Uses Web Crypto, available in Node 20+,\n * browsers, and edge/worker runtimes.\n */\nexport function generateIndexNowKey(length = 32): string {\n if (length < MIN_KEY_LENGTH || length > MAX_KEY_LENGTH || length % 2 !== 0) {\n throw new Error(\n `IndexNow key length must be an even number between ${MIN_KEY_LENGTH} and ${MAX_KEY_LENGTH}.`,\n );\n }\n const bytes = new Uint8Array(length / 2);\n crypto.getRandomValues(bytes);\n let hex = '';\n for (const b of bytes) hex += b.toString(16).padStart(2, '0');\n return hex;\n}\n\n/**\n * Plain-text body to serve at `https://<host>/<key>.txt`. IndexNow\n * verifies ownership by fetching this file and comparing to the\n * submitted key.\n */\nexport function getIndexNowKeyFileContent(key: string): string {\n if (!validateIndexNowKey(key)) {\n throw new Error(\n `IndexNow key must be ${MIN_KEY_LENGTH}–${MAX_KEY_LENGTH} characters from [A-Za-z0-9-].`,\n );\n }\n return key;\n}\n"]}
|
package/dist/pieces/article.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { ArticleLeaf } from 'schema-dts';
|
|
2
2
|
import type { IdFactory } from '../ids.js';
|
|
3
|
-
import type { Reference, CreativeWorkFields } from '../types.js';
|
|
3
|
+
import type { Reference, CreativeWorkFields, GraphEntity } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Concrete Article subtype. `Article` is the default; use `BlogPosting`
|
|
6
6
|
* for blog posts, `NewsArticle` for journalism, `TechArticle` for
|
|
@@ -11,8 +11,13 @@ export type ArticleType = 'Article' | 'BlogPosting' | 'NewsArticle' | 'TechArtic
|
|
|
11
11
|
interface ArticleCoreFields extends CreativeWorkFields {
|
|
12
12
|
/** Canonical URL of the article's page. The @id is `${url}#article`. */
|
|
13
13
|
url: string;
|
|
14
|
-
/**
|
|
15
|
-
|
|
14
|
+
/**
|
|
15
|
+
* Reference to the enclosing entity, usually the WebPage
|
|
16
|
+
* (`ids.webPage(url)`). Pass an array to link the Article to more than
|
|
17
|
+
* one parent — e.g. both its WebPage and a Blog
|
|
18
|
+
* (`[{ '@id': ids.webPage(url) }, { '@id': blogId }]`).
|
|
19
|
+
*/
|
|
20
|
+
isPartOf: Reference | Reference[];
|
|
16
21
|
/** Author reference. May include a `name` alongside the `@id`. */
|
|
17
22
|
author: Reference;
|
|
18
23
|
/** Publisher reference. Usually the same as the author for personal blogs. */
|
|
@@ -33,6 +38,6 @@ export type ArticleInput = ArticleCoreFields & Omit<Partial<ArticleLeaf>, keyof
|
|
|
33
38
|
/**
|
|
34
39
|
* Build a schema.org Article piece.
|
|
35
40
|
*/
|
|
36
|
-
export declare function buildArticle(input: ArticleInput, ids: IdFactory, type?: ArticleType):
|
|
41
|
+
export declare function buildArticle(input: ArticleInput, ids: IdFactory, type?: ArticleType): GraphEntity;
|
|
37
42
|
export {};
|
|
38
43
|
//# sourceMappingURL=article.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article.d.ts","sourceRoot":"","sources":["../../src/pieces/article.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"article.d.ts","sourceRoot":"","sources":["../../src/pieces/article.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO9E;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACjB,SAAS,GACT,aAAa,GACb,aAAa,GACb,aAAa,GACb,kBAAkB,GAClB,QAAQ,CAAC;AAEf,UAAU,iBAAkB,SAAQ,kBAAkB;IAClD,wEAAwE;IACxE,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;OAKG;IACH,QAAQ,EAAE,SAAS,GAAG,SAAS,EAAE,CAAC;IAClC,kEAAkE;IAClE,MAAM,EAAE,SAAS,CAAC;IAClB,8EAA8E;IAC9E,SAAS,EAAE,SAAS,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,WAAW,EAAE,MAAM,CAAC;IACpB,6FAA6F;IAC7F,aAAa,EAAE,IAAI,CAAC;IACpB,oDAAoD;IACpD,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,uDAAuD;IACvD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GACxC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,iBAAiB,GAAG,OAAO,CAAC,CAAC;AAelE;;GAEG;AACH,wBAAgB,YAAY,CACxB,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,SAAS,EACd,IAAI,GAAE,WAAuB,GAC9B,WAAW,CAmBb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"article.js","sourceRoot":"","sources":["../../src/pieces/article.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,GACrB,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"article.js","sourceRoot":"","sources":["../../src/pieces/article.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,GACrB,MAAM,aAAa,CAAC;AA8CrB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACjC,GAAG,kBAAkB;IACrB,KAAK;IACL,UAAU;IACV,QAAQ;IACR,WAAW;IACX,UAAU;IACV,OAAO;IACP,gBAAgB;IAChB,WAAW;IACX,aAAa;CAChB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,YAAY,CACxB,KAAmB,EACnB,GAAc,EACd,OAAoB,SAAS;IAE7B,MAAM,KAAK,GAAgB;QACvB,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;QAC7B,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,gBAAgB,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;QACnD,SAAS,EAAE,KAAK,CAAC,SAAS;KAC7B,CAAC;IAEF,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IACzD,uBAAuB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS;QAAE,KAAK,CAAC,cAAc,GAAG,KAAK,CAAC,cAAc,CAAC;IACpF,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC,SAAS,CAAC;IACrE,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IAC3E,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAEtD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import type { ArticleLeaf } from 'schema-dts';\n\nimport type { IdFactory } from '../ids.js';\nimport type { Reference, CreativeWorkFields, GraphEntity } from '../types.js';\nimport {\n applyCreativeWorkFields,\n spreadRemainingProperties,\n CREATIVE_WORK_KEYS,\n} from '../types.js';\n\n/**\n * Concrete Article subtype. `Article` is the default; use `BlogPosting`\n * for blog posts, `NewsArticle` for journalism, `TechArticle` for\n * technical docs, `ScholarlyArticle` for academic papers, or `Report`\n * for data/research reports.\n */\nexport type ArticleType =\n | 'Article'\n | 'BlogPosting'\n | 'NewsArticle'\n | 'TechArticle'\n | 'ScholarlyArticle'\n | 'Report';\n\ninterface ArticleCoreFields extends CreativeWorkFields {\n /** Canonical URL of the article's page. The @id is `${url}#article`. */\n url: string;\n /**\n * Reference to the enclosing entity, usually the WebPage\n * (`ids.webPage(url)`). Pass an array to link the Article to more than\n * one parent — e.g. both its WebPage and a Blog\n * (`[{ '@id': ids.webPage(url) }, { '@id': blogId }]`).\n */\n isPartOf: Reference | Reference[];\n /** Author reference. May include a `name` alongside the `@id`. */\n author: Reference;\n /** Publisher reference. Usually the same as the author for personal blogs. */\n publisher: Reference;\n headline: string;\n /** Required for articles — overrides the optional inherited field. */\n description: string;\n /** Required for articles — overrides the optional inherited field. Emitted as ISO string. */\n datePublished: Date;\n /** Reference to the primary ImageObject, if any. */\n image?: Reference;\n /** Top-level category, emitted as `articleSection`. */\n articleSection?: string;\n wordCount?: number;\n articleBody?: string;\n}\n\nexport type ArticleInput = ArticleCoreFields &\n Omit<Partial<ArticleLeaf>, keyof ArticleCoreFields | '@type'>;\n\nconst HANDLED_KEYS = new Set<string>([\n ...CREATIVE_WORK_KEYS,\n 'url',\n 'isPartOf',\n 'author',\n 'publisher',\n 'headline',\n 'image',\n 'articleSection',\n 'wordCount',\n 'articleBody',\n]);\n\n/**\n * Build a schema.org Article piece.\n */\nexport function buildArticle(\n input: ArticleInput,\n ids: IdFactory,\n type: ArticleType = 'Article',\n): GraphEntity {\n const piece: GraphEntity = {\n '@type': type,\n '@id': ids.article(input.url),\n isPartOf: input.isPartOf,\n author: input.author,\n headline: input.headline,\n mainEntityOfPage: { '@id': ids.webPage(input.url) },\n publisher: input.publisher,\n };\n\n if (input.image !== undefined) piece.image = input.image;\n applyCreativeWorkFields(piece, input);\n if (input.articleSection !== undefined) piece.articleSection = input.articleSection;\n if (input.wordCount !== undefined) piece.wordCount = input.wordCount;\n if (input.articleBody !== undefined) piece.articleBody = input.articleBody;\n spreadRemainingProperties(piece, input, HANDLED_KEYS);\n\n return piece;\n}\n"]}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { BreadcrumbListLeaf } from 'schema-dts';
|
|
2
2
|
import type { IdFactory } from '../ids.js';
|
|
3
|
+
import type { GraphEntity } from '../types.js';
|
|
3
4
|
export interface BreadcrumbItem {
|
|
4
5
|
/** Display name for this crumb, e.g. 'Home', 'Blog', 'Open Source'. */
|
|
5
6
|
name: string;
|
|
@@ -18,6 +19,6 @@ export type BreadcrumbListInput = BreadcrumbListCoreFields & Omit<Partial<Breadc
|
|
|
18
19
|
/**
|
|
19
20
|
* Build a schema.org BreadcrumbList piece.
|
|
20
21
|
*/
|
|
21
|
-
export declare function buildBreadcrumbList(input: BreadcrumbListInput, ids: IdFactory):
|
|
22
|
+
export declare function buildBreadcrumbList(input: BreadcrumbListInput, ids: IdFactory): GraphEntity;
|
|
22
23
|
export {};
|
|
23
24
|
//# sourceMappingURL=breadcrumb.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../src/pieces/breadcrumb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"breadcrumb.d.ts","sourceRoot":"","sources":["../../src/pieces/breadcrumb.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,MAAM,WAAW,cAAc;IAC3B,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,0FAA0F;IAC1F,EAAE,CAAC,EAAE,MAAM,CAAC;CACf;AAED,UAAU,wBAAwB;IAC9B,sDAAsD;IACtD,GAAG,EAAE,MAAM,CAAC;IACZ,uDAAuD;IACvD,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;CACpC;AAED,MAAM,MAAM,mBAAmB,GAAG,wBAAwB,GACtD,IAAI,CAAC,OAAO,CAAC,kBAAkB,CAAC,EAAE,MAAM,wBAAwB,GAAG,OAAO,CAAC,CAAC;AAIhF;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,mBAAmB,EAAE,GAAG,EAAE,SAAS,GAAG,WAAW,CAsB3F"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"breadcrumb.js","sourceRoot":"","sources":["../../src/pieces/breadcrumb.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"breadcrumb.js","sourceRoot":"","sources":["../../src/pieces/breadcrumb.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAqBxD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC;AAEvD;;GAEG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA0B,EAAE,GAAc;IAC1E,MAAM,SAAS,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACzC,MAAM,eAAe,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACtD,OAAO,EAAE,UAAU;QACnB,QAAQ,EAAE,KAAK,GAAG,CAAC;QACnB,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,IAAI,EAAE,IAAI,CAAC,EAAE;YACT,CAAC,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,EAAE,EAAE;YACpB,CAAC,CAAC,KAAK,KAAK,SAAS;gBACnB,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE;gBAClC,CAAC,CAAC,IAAI,CAAC,GAAG;KACnB,CAAC,CAAC,CAAC;IAEJ,MAAM,KAAK,GAAgB;QACvB,OAAO,EAAE,gBAAgB;QACzB,KAAK,EAAE,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,GAAG,CAAC;QAChC,eAAe;KAClB,CAAC;IAEF,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAEtD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import type { BreadcrumbListLeaf } from 'schema-dts';\n\nimport type { IdFactory } from '../ids.js';\nimport type { GraphEntity } from '../types.js';\nimport { spreadRemainingProperties } from '../types.js';\n\nexport interface BreadcrumbItem {\n /** Display name for this crumb, e.g. 'Home', 'Blog', 'Open Source'. */\n name: string;\n /** URL for this crumb. */\n url: string;\n /** Optional @id override. When set, the `item` value uses this @id instead of the URL. */\n id?: string;\n}\n\ninterface BreadcrumbListCoreFields {\n /** The URL of the page this breadcrumb belongs to. */\n url: string;\n /** Pre-computed ordered list of crumbs, root first. */\n items: readonly BreadcrumbItem[];\n}\n\nexport type BreadcrumbListInput = BreadcrumbListCoreFields &\n Omit<Partial<BreadcrumbListLeaf>, keyof BreadcrumbListCoreFields | '@type'>;\n\nconst HANDLED_KEYS = new Set<string>(['url', 'items']);\n\n/**\n * Build a schema.org BreadcrumbList piece.\n */\nexport function buildBreadcrumbList(input: BreadcrumbListInput, ids: IdFactory): GraphEntity {\n const lastIndex = input.items.length - 1;\n const itemListElement = input.items.map((item, index) => ({\n '@type': 'ListItem',\n position: index + 1,\n name: item.name,\n item: item.id\n ? { '@id': item.id }\n : index === lastIndex\n ? { '@id': ids.webPage(item.url) }\n : item.url,\n }));\n\n const piece: GraphEntity = {\n '@type': 'BreadcrumbList',\n '@id': ids.breadcrumb(input.url),\n itemListElement,\n };\n\n spreadRemainingProperties(piece, input, HANDLED_KEYS);\n\n return piece;\n}\n"]}
|
package/dist/pieces/custom.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { Thing } from 'schema-dts';
|
|
2
|
+
import type { GraphEntity } from '../types.js';
|
|
2
3
|
/**
|
|
3
4
|
* Build an arbitrary schema.org piece from a raw object.
|
|
4
5
|
*
|
|
@@ -25,9 +26,9 @@ export declare function buildPiece<T extends Thing, TType extends string = strin
|
|
|
25
26
|
}>> & {
|
|
26
27
|
'@type': TType;
|
|
27
28
|
'@id'?: string;
|
|
28
|
-
}):
|
|
29
|
+
}): GraphEntity;
|
|
29
30
|
export declare function buildPiece(raw: Record<string, unknown> & {
|
|
30
31
|
'@type': string | readonly string[];
|
|
31
32
|
'@id'?: string;
|
|
32
|
-
}):
|
|
33
|
+
}): GraphEntity;
|
|
33
34
|
//# sourceMappingURL=custom.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/pieces/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,SAAS,MAAM,GAAG,MAAM,EACrE,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC,GAAG;IAC3C,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,GACF,
|
|
1
|
+
{"version":3,"file":"custom.d.ts","sourceRoot":"","sources":["../../src/pieces/custom.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,UAAU,CAAC,CAAC,SAAS,KAAK,EAAE,KAAK,SAAS,MAAM,GAAG,MAAM,EACrE,GAAG,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC,EAAE;IAAE,OAAO,EAAE,KAAK,CAAA;CAAE,CAAC,CAAC,GAAG;IAC3C,OAAO,EAAE,KAAK,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,GACF,WAAW,CAAC;AACf,wBAAgB,UAAU,CACtB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG;IAC3B,OAAO,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAClB,GACF,WAAW,CAAC"}
|
package/dist/pieces/custom.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"custom.js","sourceRoot":"","sources":["../../src/pieces/custom.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"custom.js","sourceRoot":"","sources":["../../src/pieces/custom.ts"],"names":[],"mappings":"AAqCA,MAAM,UAAU,UAAU,CAAC,GAA4B;IACnD,oEAAoE;IACpE,uEAAuE;IACvE,OAAO,GAAkB,CAAC;AAC9B,CAAC","sourcesContent":["import type { Thing } from 'schema-dts';\n\nimport type { GraphEntity } from '../types.js';\n\n/**\n * Build an arbitrary schema.org piece from a raw object.\n *\n * Pass a `schema-dts` type as the generic parameter to get autocomplete.\n * The `@type` value in your input narrows the union to the matching leaf\n * type, so `buildPiece<Product>` with `'@type': 'Product'` gives you\n * full ProductLeaf autocomplete — no need to import Leaf types.\n *\n * ```ts\n * import type { Product } from 'schema-dts';\n * buildPiece<Product>({\n * '@type': 'Product',\n * '@id': `${url}#product`,\n * name: 'Running Shoe',\n * color: 'Black', // ← autocomplete from schema-dts\n * sku: 'ABC123', // ← autocomplete from schema-dts\n * });\n * ```\n *\n * Without a generic, the input is untyped — any properties are accepted.\n */\nexport function buildPiece<T extends Thing, TType extends string = string>(\n raw: Partial<Extract<T, { '@type': TType }>> & {\n '@type': TType;\n '@id'?: string;\n },\n): GraphEntity;\nexport function buildPiece(\n raw: Record<string, unknown> & {\n '@type': string | readonly string[];\n '@id'?: string;\n },\n): GraphEntity;\nexport function buildPiece(raw: Record<string, unknown>): GraphEntity {\n // The public overloads both require `@type` on `raw`, so the result\n // always satisfies GraphEntity; the implementation signature is wider.\n return raw as GraphEntity;\n}\n"]}
|
package/dist/pieces/image.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ImageObjectLeaf } from 'schema-dts';
|
|
2
2
|
import type { IdFactory } from '../ids.js';
|
|
3
|
+
import type { GraphEntity } from '../types.js';
|
|
3
4
|
interface ImageObjectCoreFields {
|
|
4
5
|
pageUrl?: string;
|
|
5
6
|
id?: string;
|
|
@@ -16,6 +17,6 @@ export type ImageObjectInput = ImageObjectCoreFields & Omit<Partial<ImageObjectL
|
|
|
16
17
|
* primary image (id = `${pageUrl}#primaryimage`), or `id` for a site-
|
|
17
18
|
* wide image like a personal logo.
|
|
18
19
|
*/
|
|
19
|
-
export declare function buildImageObject(input: ImageObjectInput, ids: IdFactory):
|
|
20
|
+
export declare function buildImageObject(input: ImageObjectInput, ids: IdFactory): GraphEntity;
|
|
20
21
|
export {};
|
|
21
22
|
//# sourceMappingURL=image.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/pieces/image.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"image.d.ts","sourceRoot":"","sources":["../../src/pieces/image.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG/C,UAAU,qBAAqB;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,0EAA0E;IAC1E,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,GAChD,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,qBAAqB,GAAG,OAAO,CAAC,CAAC;AAY1E;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,SAAS,GAAG,WAAW,CAoBrF"}
|
package/dist/pieces/image.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/pieces/image.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"image.js","sourceRoot":"","sources":["../../src/pieces/image.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAgBxD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACjC,SAAS;IACT,IAAI;IACJ,KAAK;IACL,OAAO;IACP,QAAQ;IACR,YAAY;IACZ,SAAS;CACZ,CAAC,CAAC;AAEH;;;;GAIG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAuB,EAAE,GAAc;IACpE,MAAM,UAAU,GACZ,KAAK,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,GAAG,CAAC,YAAY,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC5F,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC3B,MAAM,IAAI,KAAK,CAAC,wDAAwD,CAAC,CAAC;IAC9E,CAAC;IAED,MAAM,KAAK,GAAgB;QACvB,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,UAAU;QACjB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,UAAU,EAAE,KAAK,CAAC,GAAG;QACrB,KAAK,EAAE,KAAK,CAAC,KAAK;QAClB,MAAM,EAAE,KAAK,CAAC,MAAM;KACvB,CAAC;IACF,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC/D,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACxE,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAEtD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import type { ImageObjectLeaf } from 'schema-dts';\n\nimport type { IdFactory } from '../ids.js';\nimport type { GraphEntity } from '../types.js';\nimport { spreadRemainingProperties } from '../types.js';\n\ninterface ImageObjectCoreFields {\n pageUrl?: string;\n id?: string;\n /** Public URL of the image file. Used for both `url` and `contentUrl`. */\n url: string;\n width: number;\n height: number;\n inLanguage?: string;\n caption?: string;\n}\n\nexport type ImageObjectInput = ImageObjectCoreFields &\n Omit<Partial<ImageObjectLeaf>, keyof ImageObjectCoreFields | '@type'>;\n\nconst HANDLED_KEYS = new Set<string>([\n 'pageUrl',\n 'id',\n 'url',\n 'width',\n 'height',\n 'inLanguage',\n 'caption',\n]);\n\n/**\n * Build a schema.org ImageObject piece. Pass `pageUrl` for a page's\n * primary image (id = `${pageUrl}#primaryimage`), or `id` for a site-\n * wide image like a personal logo.\n */\nexport function buildImageObject(input: ImageObjectInput, ids: IdFactory): GraphEntity {\n const resolvedId =\n input.id ?? (input.pageUrl !== undefined ? ids.primaryImage(input.pageUrl) : undefined);\n if (resolvedId === undefined) {\n throw new Error('buildImageObject: either `id` or `pageUrl` is required');\n }\n\n const piece: GraphEntity = {\n '@type': 'ImageObject',\n '@id': resolvedId,\n url: input.url,\n contentUrl: input.url,\n width: input.width,\n height: input.height,\n };\n if (input.caption !== undefined) piece.caption = input.caption;\n if (input.inLanguage !== undefined) piece.inLanguage = input.inLanguage;\n spreadRemainingProperties(piece, input, HANDLED_KEYS);\n\n return piece;\n}\n"]}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SiteNavigationElementLeaf } from 'schema-dts';
|
|
2
2
|
import type { IdFactory } from '../ids.js';
|
|
3
|
-
import type { Reference } from '../types.js';
|
|
3
|
+
import type { Reference, GraphEntity } from '../types.js';
|
|
4
4
|
export interface NavigationItem {
|
|
5
5
|
name: string;
|
|
6
6
|
url: string;
|
|
@@ -15,6 +15,6 @@ export type SiteNavigationInput = SiteNavigationCoreFields & Omit<Partial<SiteNa
|
|
|
15
15
|
* Build a schema.org SiteNavigationElement whose `hasPart` is a list of
|
|
16
16
|
* sub-SiteNavigationElement entries (one per nav link).
|
|
17
17
|
*/
|
|
18
|
-
export declare function buildSiteNavigationElement(input: SiteNavigationInput, ids: IdFactory):
|
|
18
|
+
export declare function buildSiteNavigationElement(input: SiteNavigationInput, ids: IdFactory): GraphEntity;
|
|
19
19
|
export {};
|
|
20
20
|
//# sourceMappingURL=navigation.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/pieces/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"navigation.d.ts","sourceRoot":"","sources":["../../src/pieces/navigation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAE5D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1D,MAAM,WAAW,cAAc;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACf;AAED,UAAU,wBAAwB;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,SAAS,CAAC;IACpB,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;CACpC;AAED,MAAM,MAAM,mBAAmB,GAAG,wBAAwB,GACtD,IAAI,CAAC,OAAO,CAAC,yBAAyB,CAAC,EAAE,MAAM,wBAAwB,GAAG,OAAO,CAAC,CAAC;AAIvF;;;GAGG;AACH,wBAAgB,0BAA0B,CACtC,KAAK,EAAE,mBAAmB,EAC1B,GAAG,EAAE,SAAS,GACf,WAAW,CAkBb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/pieces/navigation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAgBxD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;AAEpE;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACtC,KAA0B,EAC1B,GAAc;IAEd,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvC,OAAO,EAAE,uBAAuB;QAChC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,EAAE,IAAI,CAAC,GAAG;KAChB,CAAC,CAAC,CAAC;IAEJ,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"navigation.js","sourceRoot":"","sources":["../../src/pieces/navigation.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAgBxD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS,CAAC,MAAM,EAAE,UAAU,EAAE,OAAO,CAAC,CAAC,CAAC;AAEpE;;;GAGG;AACH,MAAM,UAAU,0BAA0B,CACtC,KAA0B,EAC1B,GAAc;IAEd,MAAM,OAAO,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACvC,OAAO,EAAE,uBAAuB;QAChC,IAAI,EAAE,IAAI,CAAC,IAAI;QACf,GAAG,EAAE,IAAI,CAAC,GAAG;KAChB,CAAC,CAAC,CAAC;IAEJ,MAAM,KAAK,GAAgB;QACvB,OAAO,EAAE,uBAAuB;QAChC,KAAK,EAAE,GAAG,CAAC,UAAU;QACrB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,OAAO;KACV,CAAC;IAEF,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAEtD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import type { SiteNavigationElementLeaf } from 'schema-dts';\n\nimport type { IdFactory } from '../ids.js';\nimport type { Reference, GraphEntity } from '../types.js';\nimport { spreadRemainingProperties } from '../types.js';\n\nexport interface NavigationItem {\n name: string;\n url: string;\n}\n\ninterface SiteNavigationCoreFields {\n name: string;\n isPartOf: Reference;\n items: readonly NavigationItem[];\n}\n\nexport type SiteNavigationInput = SiteNavigationCoreFields &\n Omit<Partial<SiteNavigationElementLeaf>, keyof SiteNavigationCoreFields | '@type'>;\n\nconst HANDLED_KEYS = new Set<string>(['name', 'isPartOf', 'items']);\n\n/**\n * Build a schema.org SiteNavigationElement whose `hasPart` is a list of\n * sub-SiteNavigationElement entries (one per nav link).\n */\nexport function buildSiteNavigationElement(\n input: SiteNavigationInput,\n ids: IdFactory,\n): GraphEntity {\n const hasPart = input.items.map((item) => ({\n '@type': 'SiteNavigationElement',\n name: item.name,\n url: item.url,\n }));\n\n const piece: GraphEntity = {\n '@type': 'SiteNavigationElement',\n '@id': ids.navigation,\n name: input.name,\n isPartOf: input.isPartOf,\n hasPart,\n };\n\n spreadRemainingProperties(piece, input, HANDLED_KEYS);\n\n return piece;\n}\n"]}
|
package/dist/pieces/video.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { VideoObjectLeaf } from 'schema-dts';
|
|
2
2
|
import type { IdFactory } from '../ids.js';
|
|
3
|
-
import type { Reference } from '../types.js';
|
|
3
|
+
import type { Reference, GraphEntity } from '../types.js';
|
|
4
4
|
interface VideoObjectCoreFields {
|
|
5
5
|
url: string;
|
|
6
6
|
name: string;
|
|
@@ -17,6 +17,6 @@ export type VideoObjectInput = VideoObjectCoreFields & Omit<Partial<VideoObjectL
|
|
|
17
17
|
/**
|
|
18
18
|
* Build a schema.org VideoObject piece.
|
|
19
19
|
*/
|
|
20
|
-
export declare function buildVideoObject(input: VideoObjectInput, ids: IdFactory):
|
|
20
|
+
export declare function buildVideoObject(input: VideoObjectInput, ids: IdFactory): GraphEntity;
|
|
21
21
|
export {};
|
|
22
22
|
//# sourceMappingURL=video.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../src/pieces/video.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"video.d.ts","sourceRoot":"","sources":["../../src/pieces/video.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAElD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAG1D,UAAU,qBAAqB;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,gBAAgB,GAAG,qBAAqB,GAChD,IAAI,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,MAAM,qBAAqB,GAAG,OAAO,CAAC,CAAC;AAe1E;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,gBAAgB,EAAE,GAAG,EAAE,SAAS,GAAG,WAAW,CA6BrF"}
|
package/dist/pieces/video.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/pieces/video.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAkBxD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACjC,KAAK;IACL,MAAM;IACN,aAAa;IACb,UAAU;IACV,WAAW;IACX,cAAc;IACd,UAAU;IACV,YAAY;IACZ,UAAU;IACV,YAAY;CACf,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAuB,EAAE,GAAc;IACpE,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"video.js","sourceRoot":"","sources":["../../src/pieces/video.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAkBxD,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACjC,KAAK;IACL,MAAM;IACN,aAAa;IACb,UAAU;IACV,WAAW;IACX,cAAc;IACd,UAAU;IACV,YAAY;IACZ,UAAU;IACV,YAAY;CACf,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAAuB,EAAE,GAAc;IACpE,MAAM,KAAK,GAAgB;QACvB,OAAO,EAAE,aAAa;QACtB,KAAK,EAAE,GAAG,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC;QACjC,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;KAC3B,CAAC;IAEF,MAAM,SAAS,GACX,KAAK,CAAC,YAAY;QAClB,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS;YAC1B,CAAC,CAAC,8BAA8B,KAAK,CAAC,SAAS,oBAAoB;YACnE,CAAC,CAAC,SAAS,CAAC,CAAC;IACrB,IAAI,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,YAAY,GAAG,SAAS,CAAC;IAE5D,MAAM,KAAK,GACP,KAAK,CAAC,QAAQ;QACd,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS;YAC1B,CAAC,CAAC,0CAA0C,KAAK,CAAC,SAAS,EAAE;YAC7D,CAAC,CAAC,SAAS,CAAC,CAAC;IACrB,IAAI,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC;IAEhD,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC;IACtF,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS;QAAE,KAAK,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC;IAClE,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACxE,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAEtD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import type { VideoObjectLeaf } from 'schema-dts';\n\nimport type { IdFactory } from '../ids.js';\nimport type { Reference, GraphEntity } from '../types.js';\nimport { spreadRemainingProperties } from '../types.js';\n\ninterface VideoObjectCoreFields {\n url: string;\n name: string;\n description: string;\n isPartOf: Reference;\n youtubeId?: string;\n thumbnailUrl?: string;\n embedUrl?: string;\n uploadDate?: Date;\n duration?: string;\n transcript?: string;\n}\n\nexport type VideoObjectInput = VideoObjectCoreFields &\n Omit<Partial<VideoObjectLeaf>, keyof VideoObjectCoreFields | '@type'>;\n\nconst HANDLED_KEYS = new Set<string>([\n 'url',\n 'name',\n 'description',\n 'isPartOf',\n 'youtubeId',\n 'thumbnailUrl',\n 'embedUrl',\n 'uploadDate',\n 'duration',\n 'transcript',\n]);\n\n/**\n * Build a schema.org VideoObject piece.\n */\nexport function buildVideoObject(input: VideoObjectInput, ids: IdFactory): GraphEntity {\n const piece: GraphEntity = {\n '@type': 'VideoObject',\n '@id': ids.videoObject(input.url),\n name: input.name,\n description: input.description,\n isPartOf: input.isPartOf,\n };\n\n const thumbnail =\n input.thumbnailUrl ??\n (input.youtubeId !== undefined\n ? `https://img.youtube.com/vi/${input.youtubeId}/maxresdefault.jpg`\n : undefined);\n if (thumbnail !== undefined) piece.thumbnailUrl = thumbnail;\n\n const embed =\n input.embedUrl ??\n (input.youtubeId !== undefined\n ? `https://www.youtube-nocookie.com/embed/${input.youtubeId}`\n : undefined);\n if (embed !== undefined) piece.embedUrl = embed;\n\n if (input.uploadDate !== undefined) piece.uploadDate = input.uploadDate.toISOString();\n if (input.duration !== undefined) piece.duration = input.duration;\n if (input.transcript !== undefined) piece.transcript = input.transcript;\n spreadRemainingProperties(piece, input, HANDLED_KEYS);\n\n return piece;\n}\n"]}
|
package/dist/pieces/webpage.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WebPageLeaf } from 'schema-dts';
|
|
2
2
|
import type { IdFactory } from '../ids.js';
|
|
3
|
-
import type { Reference, CreativeWorkFields } from '../types.js';
|
|
3
|
+
import type { Reference, CreativeWorkFields, GraphEntity } from '../types.js';
|
|
4
4
|
/**
|
|
5
5
|
* Concrete WebPage subtype. `WebPage` is the default; use `ProfilePage`
|
|
6
6
|
* for /about-me style pages and `CollectionPage` for index/listing pages.
|
|
@@ -27,6 +27,6 @@ export type WebPageInput = WebPageCoreFields & Omit<Partial<WebPageLeaf>, keyof
|
|
|
27
27
|
/**
|
|
28
28
|
* Build a schema.org WebPage (or ProfilePage / CollectionPage) piece.
|
|
29
29
|
*/
|
|
30
|
-
export declare function buildWebPage(input: WebPageInput, ids: IdFactory, type?: WebPageType):
|
|
30
|
+
export declare function buildWebPage(input: WebPageInput, ids: IdFactory, type?: WebPageType): GraphEntity;
|
|
31
31
|
export {};
|
|
32
32
|
//# sourceMappingURL=webpage.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webpage.d.ts","sourceRoot":"","sources":["../../src/pieces/webpage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"webpage.d.ts","sourceRoot":"","sources":["../../src/pieces/webpage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO9E;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,aAAa,GAAG,gBAAgB,CAAC;AAEvE,UAAU,iBAAkB,SAAQ,kBAAkB;IAClD,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,mCAAmC;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,QAAQ,EAAE,SAAS,CAAC;IACpB,qDAAqD;IACrD,UAAU,CAAC,EAAE,SAAS,CAAC;IACvB,oDAAoD;IACpD,YAAY,CAAC,EAAE,SAAS,CAAC;IACzB;;;OAGG;IACH,eAAe,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;CAC5D;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GACxC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,iBAAiB,GAAG,OAAO,CAAC,CAAC;AAYlE;;GAEG;AACH,wBAAgB,YAAY,CACxB,KAAK,EAAE,YAAY,EACnB,GAAG,EAAE,SAAS,EACd,IAAI,GAAE,WAAuB,GAC9B,WAAW,CAoBb"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"webpage.js","sourceRoot":"","sources":["../../src/pieces/webpage.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,GACrB,MAAM,aAAa,CAAC;AA6BrB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACjC,GAAG,kBAAkB;IACrB,KAAK;IACL,MAAM;IACN,UAAU;IACV,YAAY;IACZ,cAAc;IACd,iBAAiB;CACpB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,YAAY,CACxB,KAAmB,EACnB,GAAc,EACd,OAAoB,SAAS;IAE7B,MAAM,eAAe,GAA2C,KAAK,CAAC,eAAe,IAAI;QACrF,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;KACjD,CAAC;IAEF,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"webpage.js","sourceRoot":"","sources":["../../src/pieces/webpage.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,GACrB,MAAM,aAAa,CAAC;AA6BrB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACjC,GAAG,kBAAkB;IACrB,KAAK;IACL,MAAM;IACN,UAAU;IACV,YAAY;IACZ,cAAc;IACd,iBAAiB;CACpB,CAAC,CAAC;AAEH;;GAEG;AACH,MAAM,UAAU,YAAY,CACxB,KAAmB,EACnB,GAAc,EACd,OAAoB,SAAS;IAE7B,MAAM,eAAe,GAA2C,KAAK,CAAC,eAAe,IAAI;QACrF,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE;KACjD,CAAC;IAEF,MAAM,KAAK,GAAgB;QACvB,OAAO,EAAE,IAAI;QACb,KAAK,EAAE,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC;QAC7B,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,eAAe;KAClB,CAAC;IAEF,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACxE,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS;QAAE,KAAK,CAAC,kBAAkB,GAAG,KAAK,CAAC,YAAY,CAAC;IACpF,uBAAuB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACtC,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAEtD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import type { WebPageLeaf } from 'schema-dts';\n\nimport type { IdFactory } from '../ids.js';\nimport type { Reference, CreativeWorkFields, GraphEntity } from '../types.js';\nimport {\n applyCreativeWorkFields,\n spreadRemainingProperties,\n CREATIVE_WORK_KEYS,\n} from '../types.js';\n\n/**\n * Concrete WebPage subtype. `WebPage` is the default; use `ProfilePage`\n * for /about-me style pages and `CollectionPage` for index/listing pages.\n */\nexport type WebPageType = 'WebPage' | 'ProfilePage' | 'CollectionPage';\n\ninterface WebPageCoreFields extends CreativeWorkFields {\n /** Canonical URL of the page. The WebPage @id equals this URL. */\n url: string;\n /** Page title (becomes `name`). */\n name: string;\n /** Reference to the site-wide WebSite (usually ids.website). */\n isPartOf: Reference;\n /** Reference to the BreadcrumbList for this page. */\n breadcrumb?: Reference;\n /** Reference to the primary ImageObject, if any. */\n primaryImage?: Reference;\n /**\n * Custom potentialAction. If omitted, defaults to a single ReadAction\n * targeting the page URL.\n */\n potentialAction?: ReadonlyArray<Record<string, unknown>>;\n}\n\nexport type WebPageInput = WebPageCoreFields &\n Omit<Partial<WebPageLeaf>, keyof WebPageCoreFields | '@type'>;\n\nconst HANDLED_KEYS = new Set<string>([\n ...CREATIVE_WORK_KEYS,\n 'url',\n 'name',\n 'isPartOf',\n 'breadcrumb',\n 'primaryImage',\n 'potentialAction',\n]);\n\n/**\n * Build a schema.org WebPage (or ProfilePage / CollectionPage) piece.\n */\nexport function buildWebPage(\n input: WebPageInput,\n ids: IdFactory,\n type: WebPageType = 'WebPage',\n): GraphEntity {\n const potentialAction: ReadonlyArray<Record<string, unknown>> = input.potentialAction ?? [\n { '@type': 'ReadAction', target: [input.url] },\n ];\n\n const piece: GraphEntity = {\n '@type': type,\n '@id': ids.webPage(input.url),\n url: input.url,\n name: input.name,\n isPartOf: input.isPartOf,\n potentialAction,\n };\n\n if (input.breadcrumb !== undefined) piece.breadcrumb = input.breadcrumb;\n if (input.primaryImage !== undefined) piece.primaryImageOfPage = input.primaryImage;\n applyCreativeWorkFields(piece, input);\n spreadRemainingProperties(piece, input, HANDLED_KEYS);\n\n return piece;\n}\n"]}
|
package/dist/pieces/website.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { WebSiteLeaf } from 'schema-dts';
|
|
2
2
|
import type { IdFactory } from '../ids.js';
|
|
3
|
-
import type { Reference, CreativeWorkFields } from '../types.js';
|
|
3
|
+
import type { Reference, CreativeWorkFields, GraphEntity } from '../types.js';
|
|
4
4
|
interface WebSiteCoreFields extends CreativeWorkFields {
|
|
5
5
|
/** Site URL, typically with trailing slash. */
|
|
6
6
|
url: string;
|
|
@@ -15,6 +15,6 @@ export type WebSiteInput = WebSiteCoreFields & Omit<Partial<WebSiteLeaf>, keyof
|
|
|
15
15
|
* Build a schema.org WebSite piece. This is the site-wide singleton;
|
|
16
16
|
* every page's WebPage should reference it via `isPartOf`.
|
|
17
17
|
*/
|
|
18
|
-
export declare function buildWebSite(input: WebSiteInput, ids: IdFactory):
|
|
18
|
+
export declare function buildWebSite(input: WebSiteInput, ids: IdFactory): GraphEntity;
|
|
19
19
|
export {};
|
|
20
20
|
//# sourceMappingURL=website.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website.d.ts","sourceRoot":"","sources":["../../src/pieces/website.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"website.d.ts","sourceRoot":"","sources":["../../src/pieces/website.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,KAAK,EAAE,SAAS,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO9E,UAAU,iBAAkB,SAAQ,kBAAkB;IAClD,+CAA+C;IAC/C,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,uEAAuE;IACvE,SAAS,EAAE,SAAS,CAAC;IACrB,2DAA2D;IAC3D,OAAO,CAAC,EAAE,SAAS,CAAC;CACvB;AAED,MAAM,MAAM,YAAY,GAAG,iBAAiB,GACxC,IAAI,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,MAAM,iBAAiB,GAAG,OAAO,CAAC,CAAC;AAUlE;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,EAAE,SAAS,GAAG,WAAW,CAc7E"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"website.js","sourceRoot":"","sources":["../../src/pieces/website.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,GACrB,MAAM,aAAa,CAAC;AAerB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACjC,GAAG,kBAAkB;IACrB,KAAK;IACL,MAAM;IACN,WAAW;IACX,SAAS;CACZ,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAmB,EAAE,GAAc;IAC5D,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"website.js","sourceRoot":"","sources":["../../src/pieces/website.ts"],"names":[],"mappings":"AAIA,OAAO,EACH,uBAAuB,EACvB,yBAAyB,EACzB,kBAAkB,GACrB,MAAM,aAAa,CAAC;AAerB,MAAM,YAAY,GAAG,IAAI,GAAG,CAAS;IACjC,GAAG,kBAAkB;IACrB,KAAK;IACL,MAAM;IACN,WAAW;IACX,SAAS;CACZ,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAmB,EAAE,GAAc;IAC5D,MAAM,KAAK,GAAgB;QACvB,OAAO,EAAE,SAAS;QAClB,KAAK,EAAE,GAAG,CAAC,OAAO;QAClB,GAAG,EAAE,KAAK,CAAC,GAAG;QACd,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,SAAS,EAAE,KAAK,CAAC,SAAS;KAC7B,CAAC;IAEF,uBAAuB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IACtC,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC/D,yBAAyB,CAAC,KAAK,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;IAEtD,OAAO,KAAK,CAAC;AACjB,CAAC","sourcesContent":["import type { WebSiteLeaf } from 'schema-dts';\n\nimport type { IdFactory } from '../ids.js';\nimport type { Reference, CreativeWorkFields, GraphEntity } from '../types.js';\nimport {\n applyCreativeWorkFields,\n spreadRemainingProperties,\n CREATIVE_WORK_KEYS,\n} from '../types.js';\n\ninterface WebSiteCoreFields extends CreativeWorkFields {\n /** Site URL, typically with trailing slash. */\n url: string;\n name: string;\n /** Publisher entity — usually the site-wide Person or Organization. */\n publisher: Reference;\n /** Optional navigation reference (e.g. ids.navigation). */\n hasPart?: Reference;\n}\n\nexport type WebSiteInput = WebSiteCoreFields &\n Omit<Partial<WebSiteLeaf>, keyof WebSiteCoreFields | '@type'>;\n\nconst HANDLED_KEYS = new Set<string>([\n ...CREATIVE_WORK_KEYS,\n 'url',\n 'name',\n 'publisher',\n 'hasPart',\n]);\n\n/**\n * Build a schema.org WebSite piece. This is the site-wide singleton;\n * every page's WebPage should reference it via `isPartOf`.\n */\nexport function buildWebSite(input: WebSiteInput, ids: IdFactory): GraphEntity {\n const piece: GraphEntity = {\n '@type': 'WebSite',\n '@id': ids.website,\n url: input.url,\n name: input.name,\n publisher: input.publisher,\n };\n\n applyCreativeWorkFields(piece, input);\n if (input.hasPart !== undefined) piece.hasPart = input.hasPart;\n spreadRemainingProperties(piece, input, HANDLED_KEYS);\n\n return piece;\n}\n"]}
|
package/dist/types.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA6DA;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACnC,KAA8B,EAC9B,KAAyB;IAEzB,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IAC3E,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IACzD,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACxE,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS;QAAE,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;IAC/F,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS;QAAE,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC;IAC5F,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS;QAAE,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC;IACvF,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS;QAAE,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;IACjF,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS;QAAE,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC;IACvF,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC/D,IAAI,KAAK,CAAC,mBAAmB,KAAK,SAAS;QACvC,KAAK,CAAC,mBAAmB,GAAG,KAAK,CAAC,mBAAmB,CAAC;AAC9D,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAS;IAC9C,aAAa;IACb,OAAO;IACP,YAAY;IACZ,eAAe;IACf,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,iBAAiB;IACjB,SAAS;IACT,qBAAqB;CACxB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CACrC,KAA8B,EAC9B,KAAa,EACb,WAAgC;IAEhC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/C,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;IACL,CAAC;AACL,CAAC"}
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AA6DA;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACnC,KAA8B,EAC9B,KAAyB;IAEzB,IAAI,KAAK,CAAC,WAAW,KAAK,SAAS;QAAE,KAAK,CAAC,WAAW,GAAG,KAAK,CAAC,WAAW,CAAC;IAC3E,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC;IACzD,IAAI,KAAK,CAAC,UAAU,KAAK,SAAS;QAAE,KAAK,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACxE,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS;QAAE,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC,WAAW,EAAE,CAAC;IAC/F,IAAI,KAAK,CAAC,YAAY,KAAK,SAAS;QAAE,KAAK,CAAC,YAAY,GAAG,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC;IAC5F,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS;QAAE,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC;IACvF,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS;QAAE,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC,aAAa,CAAC;IACjF,IAAI,KAAK,CAAC,eAAe,KAAK,SAAS;QAAE,KAAK,CAAC,eAAe,GAAG,KAAK,CAAC,eAAe,CAAC;IACvF,IAAI,KAAK,CAAC,OAAO,KAAK,SAAS;QAAE,KAAK,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC;IAC/D,IAAI,KAAK,CAAC,mBAAmB,KAAK,SAAS;QACvC,KAAK,CAAC,mBAAmB,GAAG,KAAK,CAAC,mBAAmB,CAAC;AAC9D,CAAC;AAED,mDAAmD;AACnD,MAAM,CAAC,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAS;IAC9C,aAAa;IACb,OAAO;IACP,YAAY;IACZ,eAAe;IACf,cAAc;IACd,iBAAiB;IACjB,eAAe;IACf,iBAAiB;IACjB,SAAS;IACT,qBAAqB;CACxB,CAAC,CAAC;AAEH;;;GAGG;AACH,MAAM,UAAU,yBAAyB,CACrC,KAA8B,EAC9B,KAAa,EACb,WAAgC;IAEhC,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/C,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;YAC/C,KAAK,CAAC,GAAG,CAAC,GAAG,KAAK,CAAC;QACvB,CAAC;IACL,CAAC;AACL,CAAC","sourcesContent":["import type { Thing, WithContext } from 'schema-dts';\n\n/**\n * A reference to another entity in the @graph, by its @id.\n *\n * Most schema.org entities link to each other via `{ '@id': string }`.\n * Some consumers (notably joost.blog's Article.author) include the `name`\n * alongside the id, which is redundant but valid schema.org. The optional\n * `name` field lets callers opt into that pattern.\n */\nexport interface Reference {\n '@id': string;\n name?: string;\n}\n\n/**\n * The assembled @graph produced by `assembleGraph`.\n */\nexport type SchemaGraph<T extends Thing = Thing> = WithContext<Thing> & {\n '@context': 'https://schema.org';\n '@graph': T[];\n};\n\n/**\n * A single entity in the graph, as returned by a piece builder.\n * All entities should carry an `@id` so they can be cross-referenced.\n */\nexport interface GraphEntity {\n '@type': string | readonly string[];\n '@id'?: string;\n [key: string]: unknown;\n}\n\n/**\n * Optional schema.org CreativeWork properties shared across WebSite,\n * WebPage, and Article builders. Extend your input interface from this\n * to inherit the fields.\n */\nexport interface CreativeWorkFields {\n /** Short description / summary of this entity. */\n description?: string;\n /** What this entity is about — e.g. a Person, Organization, or other entity. */\n about?: Reference;\n /** Content language, e.g. 'en-US'. */\n inLanguage?: string;\n /** Publish date — emitted as ISO string. */\n datePublished?: Date;\n /** Update date — emitted as ISO string. */\n dateModified?: Date;\n /** Who holds the copyright — typically a Person or Organization reference. */\n copyrightHolder?: Reference;\n /** Year copyright was first asserted. */\n copyrightYear?: number;\n /** Human-readable copyright text, e.g. '© 2026 Jane Doe. All rights reserved.' */\n copyrightNotice?: string;\n /** License URL or CreativeWork reference (e.g. a Creative Commons URL). */\n license?: string;\n /** Whether the content is free to access. */\n isAccessibleForFree?: boolean;\n}\n\n/**\n * Apply shared CreativeWork fields to a piece under construction.\n * Call from any builder whose input extends `CreativeWorkFields`.\n */\nexport function applyCreativeWorkFields(\n piece: Record<string, unknown>,\n input: CreativeWorkFields,\n): void {\n if (input.description !== undefined) piece.description = input.description;\n if (input.about !== undefined) piece.about = input.about;\n if (input.inLanguage !== undefined) piece.inLanguage = input.inLanguage;\n if (input.datePublished !== undefined) piece.datePublished = input.datePublished.toISOString();\n if (input.dateModified !== undefined) piece.dateModified = input.dateModified.toISOString();\n if (input.copyrightHolder !== undefined) piece.copyrightHolder = input.copyrightHolder;\n if (input.copyrightYear !== undefined) piece.copyrightYear = input.copyrightYear;\n if (input.copyrightNotice !== undefined) piece.copyrightNotice = input.copyrightNotice;\n if (input.license !== undefined) piece.license = input.license;\n if (input.isAccessibleForFree !== undefined)\n piece.isAccessibleForFree = input.isAccessibleForFree;\n}\n\n/** Keys that `applyCreativeWorkFields` handles. */\nexport const CREATIVE_WORK_KEYS = new Set<string>([\n 'description',\n 'about',\n 'inLanguage',\n 'datePublished',\n 'dateModified',\n 'copyrightHolder',\n 'copyrightYear',\n 'copyrightNotice',\n 'license',\n 'isAccessibleForFree',\n]);\n\n/**\n * Spread all properties from `input` into `piece`, skipping keys that\n * the builder handles specially. Call after all explicit field handling.\n */\nexport function spreadRemainingProperties(\n piece: Record<string, unknown>,\n input: object,\n handledKeys: ReadonlySet<string>,\n): void {\n for (const [key, value] of Object.entries(input)) {\n if (!handledKeys.has(key) && value !== undefined) {\n piece[key] = value;\n }\n }\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jdevalk/seo-graph-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Pure schema.org JSON-LD graph builders. Runtime-agnostic core for agent-ready SEO.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"schema.org",
|
|
@@ -40,9 +40,9 @@
|
|
|
40
40
|
"schema-dts": "^2.0.0"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
|
-
"@types/node": "^
|
|
44
|
-
"typescript": "^
|
|
45
|
-
"vitest": "^
|
|
43
|
+
"@types/node": "^25.9.3",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"vitest": "^4.1.9"
|
|
46
46
|
},
|
|
47
47
|
"scripts": {
|
|
48
48
|
"build": "tsc -p tsconfig.build.json && cp ../../AGENTS.md .",
|