@jxsuite/feed 0.3.5 → 0.3.7

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.
Files changed (2) hide show
  1. package/README.md +30 -99
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -4,32 +4,13 @@
4
4
 
5
5
  ## Overview
6
6
 
7
- `@jxsuite/feed` is the Jx extension that turns a content collection into something readers
8
- subscribe to. It contributes one class (`Feed`) and one project.json section, both reached through
9
- `jx-extension.json`, which names the class descriptor (`src/Feed.class.json`, whose `project` block
10
- declares the section key) and the schema fragment that gives the section its shape:
11
-
12
- - **`feed`**: a map of feed name → feed object. Each entry names its `collection`, the `basePath`
13
- its entries are served under, and optional metadata (`title`, `description`, `author`), output
14
- shape (`formats`, `output`, `pageSize`, `archive`, `contentMode`), frontmatter field names
15
- (`dateField`, `updatedField`) and `language`. The schema
16
- (`schemas/project.fragment.schema.json`, `$id` `https://jxsuite.com/schema/ext/feed/project/v1`)
17
- declares the same defaults `normalizeFeedConfig` applies at runtime (`DEFAULTS` in
18
- `src/shared.ts`), two lists kept in step by hand; it requires at least one feed and rejects
19
- unknown keys.
20
-
21
- The behavior is specified in specs/site-architecture.md §6.7 (`Status: Implemented`), which also
22
- records **why this is an extension** rather than a compiler built-in: a feed is derived from a
23
- content collection, and wiring the compiler to one extension's section is the coupling
24
- specs/extensions.md §1 exists to prevent. The capability contracts are specs/extensions.md §8.4
25
- (`emit`) and §8.6 (`head`). User docs:
26
- [/docs/framework/site/feeds](https://jxsuite.com/docs/framework/site/feeds).
27
-
28
- **Atom 1.0 (RFC 4287) and JSON Feed 1.1. RSS 2.0 is deliberately not offered**: no standards body,
29
- unsettled `<guid>` semantics, and every reader handles Atom. All three standards this package binds
30
- (RFC 4287, JSON Feed 1.1, RFC 5005) are recorded as **Subset** in the Standards Alignment table of
31
- specs/site-architecture.md, each with an explicit list of what is not implemented. Read those rows
32
- before claiming conformance.
7
+ `@jxsuite/feed` is the Jx extension that turns a content collection into something readers subscribe to. It contributes one class (`Feed`) and one project.json section, both reached through `jx-extension.json`, which names the class descriptor (`src/Feed.class.json`, whose `project` block declares the section key) and the schema fragment that gives the section its shape:
8
+
9
+ - **`feed`**: a map of feed name → feed object. Each entry names its `collection`, the `basePath` its entries are served under, and optional metadata (`title`, `description`, `author`), output shape (`formats`, `output`, `pageSize`, `archive`, `contentMode`), frontmatter field names (`dateField`, `updatedField`) and `language`. The schema (`schemas/project.fragment.schema.json`, `$id` `https://jxsuite.com/schema/ext/feed/project/v1`) declares the same defaults `normalizeFeedConfig` applies at runtime (`DEFAULTS` in `src/shared.ts`), two lists kept in step by hand; it requires at least one feed and rejects unknown keys.
10
+
11
+ The behavior is specified in specs/site-architecture.md §6.7 (`Status: Implemented`), which also records **why this is an extension** rather than a compiler built-in: a feed is derived from a content collection, and wiring the compiler to one extension's section is the coupling specs/extensions.md §1 exists to prevent. The capability contracts are specs/extensions.md §8.4 (`emit`) and §8.6 (`head`). User docs: [/docs/framework/site/feeds](https://jxsuite.com/docs/framework/site/feeds).
12
+
13
+ **Atom 1.0 (RFC 4287) and JSON Feed 1.1. RSS 2.0 is deliberately not offered**: no standards body, unsettled `<guid>` semantics, and every reader handles Atom. All three standards this package binds (RFC 4287, JSON Feed 1.1, RFC 5005) are recorded as **Subset** in the Standards Alignment table of specs/site-architecture.md, each with an explicit list of what is not implemented. Read those rows before claiming conformance.
33
14
 
34
15
  ## Enable it
35
16
 
@@ -51,94 +32,44 @@ before claiming conformance.
51
32
  }
52
33
  ```
53
34
 
54
- `@jxsuite/parser` is what loads the content collections this publishes; a feed names one of them by
55
- its collection key. A default build writes `dist/feed.xml` and `dist/feed.json`, plus
56
- `dist/feed/archive/<n>.xml` when `archive` is on, and adds the discovery links to every page's
57
- `<head>`.
35
+ `@jxsuite/parser` is what loads the content collections this publishes; a feed names one of them by its collection key. A default build writes `dist/feed.xml` and `dist/feed.json`, plus `dist/feed/archive/<n>.xml` when `archive` is on, and adds the discovery links to every page's `<head>`.
58
36
 
59
- **`url` is not optional.** A feed's entry identities are absolute URLs, so without it `head` returns
60
- `[]` silently, and `emit` returns `[]` after warning that `url` is not set in project.json.
37
+ **`url` is not optional.** A feed's entry identities are absolute URLs, so without it `head` returns `[]` silently, and `emit` returns `[]` after warning that `url` is not set in project.json.
61
38
 
62
39
  ## The `Feed` class
63
40
 
64
- `src/Feed.class.json` declares three capabilities, all implemented by the plain object exported as
65
- `Feed` from `src/feed.ts`:
66
-
67
- - **`projectData(sectionValue, ctx?)`**: timing `["compiler", "server"]`, the only one that also
68
- runs on the server. Normalizes the section (defaults applied) into `_project.feed`.
69
- - **`head(sectionValue, ctx)`**: timing `["compiler"]`. Returns one `<link rel="alternate">` per
70
- configured format (one per format _and_ locale when the collection is localized), typed
71
- `application/atom+xml` / `application/feed+json` and titled with the feed's `title` (or
72
- `"Feed"`). Both links survive `<head>` dedup because the merger keys a link on `rel` plus `href`
73
- plus whichever of `hreflang`, `type`, `media` or `sizes` is present
74
- (specs/site-architecture.md §8.3). For two formats that qualifier is `type`; for a localized
75
- feed it is `hreflang`, and the links differ by `href` anyway.
76
- - **`emit(sectionValue, ctx)`**: timing `["compiler"]`. Returns `{ path, content }[]`; the host
77
- writes them.
78
-
79
- `head` exists separately from `emit` because the two answer different questions at different times
80
- (specs/extensions.md §8.6): `head` derives entries from **configuration** and runs once before the
81
- first page is built, while `emit` derives files from loaded content and runs after the last page has
82
- been written, too late to reach any `<head>`.
41
+ `src/Feed.class.json` declares three capabilities, all implemented by the plain object exported as `Feed` from `src/feed.ts`:
42
+
43
+ - **`projectData(sectionValue, ctx?)`**: timing `["compiler", "server"]`, the only one that also runs on the server. Normalizes the section (defaults applied) into `_project.feed`.
44
+ - **`head(sectionValue, ctx)`**: timing `["compiler"]`. Returns one `<link rel="alternate">` per configured format (one per format _and_ locale when the collection is localized), typed `application/atom+xml` / `application/feed+json` and titled with the feed's `title` (or `"Feed"`). Both links survive `<head>` dedup because the merger keys a link on `rel` plus `href` plus whichever of `hreflang`, `type`, `media` or `sizes` is present (specs/site-architecture.md §8.3). For two formats that qualifier is `type`; for a localized feed it is `hreflang`, and the links differ by `href` anyway.
45
+ - **`emit(sectionValue, ctx)`**: timing `["compiler"]`. Returns `{ path, content }[]`; the host writes them.
46
+
47
+ `head` exists separately from `emit` because the two answer different questions at different times (specs/extensions.md §8.6): `head` derives entries from **configuration** and runs once before the first page is built, while `emit` derives files from loaded content and runs after the last page has been written, too late to reach any `<head>`.
83
48
 
84
49
  ## Emission model
85
50
 
86
- Everything happens inside the compiler's site build. `emit` runs at step 6e: after routes,
87
- components and the worker are generated, and before redirects and the `public/` copy. The **host**
88
- writes the returned records under `outDir` (paths are outDir-relative, a leading `/` is tolerated,
89
- and a path escaping `outDir` is a build error). The package never touches the filesystem, which is
90
- what makes both serializers testable against a literal array of items.
51
+ Everything happens inside the compiler's site build. `emit` runs at step 6e: after routes, components and the worker are generated, and before redirects and the `public/` copy. The **host** writes the returned records under `outDir` (paths are outDir-relative, a leading `/` is tolerated, and a path escaping `outDir` is a build error). The package never touches the filesystem, which is what makes both serializers testable against a literal array of items.
91
52
 
92
53
  Consequences worth knowing:
93
54
 
94
55
  - The `public/` copy runs after `emit`, so a same-named file in `public/` shadows an emitted feed.
95
- - The compiler writes `_headers` Content-Type rules: `application/atom+xml; charset=utf-8` for
96
- `/feed.xml` and `/feed/archive/*`, `application/feed+json; charset=utf-8` for `/feed.json`. It
97
- writes them only for files the build produced, and the names it looks for are the **default**
98
- ones (`packages/compiler/src/site/headers-emitter.ts`). A custom `output` gets no rule.
56
+ - The compiler writes `_headers` Content-Type rules: `application/atom+xml; charset=utf-8` for `/feed.xml` and `/feed/archive/*`, `application/feed+json; charset=utf-8` for `/feed.json`. It writes them only for files the build produced, and the names it looks for are the **default** ones (`packages/compiler/src/site/headers-emitter.ts`). A custom `output` gets no rule.
99
57
 
100
- Subpath exports (`@jxsuite/feed/atom`, `/json-feed`, `/shared`) resolve to TypeScript source, so the
101
- serializers and the pure feed model are importable on their own: `normalizeFeedConfig`,
102
- `entryToItem`, `sortItems`, `feedUpdated`, `paginate`, `feedPath`.
58
+ Subpath exports (`@jxsuite/feed/atom`, `/json-feed`, `/shared`) resolve to TypeScript source, so the serializers and the pure feed model are importable on their own: `normalizeFeedConfig`, `entryToItem`, `sortItems`, `feedUpdated`, `paginate`, `feedPath`.
103
59
 
104
60
  ## Surprises
105
61
 
106
- - **`pageSize` (default 20) trims the subscription document even when `archive` is `false`.** The
107
- older entries are then published nowhere. `archive: true` is what preserves them.
108
- - **Archives are chunked from the oldest end.** Chunking from the newest would reshuffle every
109
- boundary on each new entry; counting from the oldest means archive 1 keeps its contents forever
110
- and only the newest archive changes. RFC 5005 §2 asks that a published archive not change.
111
- - **`<fh:complete/>` needs no archives _and_ nothing trimmed.** A document trimmed by `pageSize`
112
- with archives off is not complete, and says so by omission.
113
- - **RFC 5005 archives are Atom-only.** The JSON Feed branch writes exactly one document and never
114
- sets `next_url`, rather than mixing two pagination conventions in one feed.
115
- - **Dates are RFC 3339 or nothing.** Only `YYYY-MM-DD` (expanded to `T00:00:00Z`) and a full
116
- timestamp with `Z` or a numeric offset are accepted; anything else becomes `null` rather than a
117
- guess. Give the collection schema `"format": "date"` so the parser's date coercion
118
- (`coerceEntryDates`, specs/parser.md §9.3) normalizes the field first. An entry with no readable
119
- date falls back to `_meta.mtime`.
120
- - **The feed-level `<updated>` is the newest item, never the build time**, because a feed stamped
121
- with the build re-notifies every subscriber on every deploy. An empty feed renders
122
- `<updated>1970-01-01T00:00:00Z</updated>`.
123
- - **A localized collection is several feeds, not one, and this is not configurable.** When the named
124
- collection's `source` contains `{locale}`, each language is published in its own URL prefix
125
- (`/feed.xml`, `/fr-ca/feed.xml`) holding only that language's entries, stating its language with
126
- `xml:lang` / JSON Feed `language`. A locale with no matching entries is not written at all.
127
- Discovery advertises every language with `hreflang`, because `head` runs before routing and cannot
128
- know which locale its page is in.
129
- - **`contentMode` branches only on `"full"`** (`src/shared.ts`), which is what adds
130
- `<content type="html">` / `content_html`; the summary is emitted either way. A third value,
131
- `"none"`, was accepted until 0.3.0 and did nothing. JSON Feed 1.1 requires one of
132
- `content_html`/`content_text`, so "omit the content" was never expressible.
133
- - **A feed naming a collection that is not loaded is skipped with a console warning**, not a build
134
- error.
135
- - **The schema requires `collection` and `basePath`, but the runtime tolerates their absence**:
136
- `collection` falls back to the section key and `basePath` to `"/"`. Validation, not
137
- `normalizeFeedConfig`, is what enforces them.
62
+ - **`pageSize` (default 20) trims the subscription document even when `archive` is `false`.** The older entries are then published nowhere. `archive: true` is what preserves them.
63
+ - **Archives are chunked from the oldest end.** Chunking from the newest would reshuffle every boundary on each new entry; counting from the oldest means archive 1 keeps its contents forever and only the newest archive changes. RFC 5005 §2 asks that a published archive not change.
64
+ - **`<fh:complete/>` needs no archives _and_ nothing trimmed.** A document trimmed by `pageSize` with archives off is not complete, and says so by omission.
65
+ - **RFC 5005 archives are Atom-only.** The JSON Feed branch writes exactly one document and never sets `next_url`, rather than mixing two pagination conventions in one feed.
66
+ - **Dates are RFC 3339 or nothing.** Only `YYYY-MM-DD` (expanded to `T00:00:00Z`) and a full timestamp with `Z` or a numeric offset are accepted; anything else becomes `null` rather than a guess. Give the collection schema `"format": "date"` so the parser's date coercion (`coerceEntryDates`, specs/parser.md §9.3) normalizes the field first. An entry with no readable date falls back to `_meta.mtime`.
67
+ - **The feed-level `<updated>` is the newest item, never the build time**, because a feed stamped with the build re-notifies every subscriber on every deploy. An empty feed renders `<updated>1970-01-01T00:00:00Z</updated>`.
68
+ - **A localized collection is several feeds, not one, and this is not configurable.** When the named collection's `source` contains `{locale}`, each language is published in its own URL prefix (`/feed.xml`, `/fr-ca/feed.xml`) holding only that language's entries, stating its language with `xml:lang` / JSON Feed `language`. A locale with no matching entries is not written at all. Discovery advertises every language with `hreflang`, because `head` runs before routing and cannot know which locale its page is in.
69
+ - **`contentMode` branches only on `"full"`** (`src/shared.ts`), which is what adds `<content type="html">` / `content_html`; the summary is emitted either way. A third value, `"none"`, was accepted until 0.3.0 and did nothing. JSON Feed 1.1 requires one of `content_html`/`content_text`, so "omit the content" was never expressible.
70
+ - **A feed naming a collection that is not loaded is skipped with a console warning**, not a build error.
71
+ - **The schema requires `collection` and `basePath`, but the runtime tolerates their absence**: `collection` falls back to the section key and `basePath` to `"/"`. Validation, not `normalizeFeedConfig`, is what enforces them.
138
72
 
139
73
  ## Versioning
140
74
 
141
- Published to npm as `@jxsuite/feed`, TypeScript source like every `@jxsuite` package, following
142
- the monorepo's release train. Its only runtime dependency is `@jxsuite/schema`; extensions may
143
- depend on core packages but never the reverse: no core package lists this one as a runtime
144
- dependency or imports it from `src/`, and `scripts/check-dep-rules.ts` enforces that.
75
+ Published to npm as `@jxsuite/feed`, TypeScript source like every `@jxsuite` package, following the monorepo's release train. Its only runtime dependency is `@jxsuite/schema`; extensions may depend on core packages but never the reverse: no core package lists this one as a runtime dependency or imports it from `src/`, and `scripts/check-dep-rules.ts` enforces that.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/feed",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Syndication feeds for Jx: Atom (RFC 4287) and JSON Feed emitted from content collections at build time",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  "upgrade": "bun update --latest && bun install"
36
36
  },
37
37
  "dependencies": {
38
- "@jxsuite/schema": "^2.1.0"
38
+ "@jxsuite/schema": "^2.3.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "ajv": "^8.20.0"