@pitlane/content 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,35 @@
1
1
  # @pitlane/content
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 6be8e56: Documentation only. No code changed.
8
+
9
+ - Every README now says where the documentation is published as Markdown for AI agents and other LLM tools: `https://pitlane.tools/llms.txt` indexes every page, `https://pitlane.tools/llms-full.txt` holds them all in one file, and any page URL with `.md` appended returns that page as Markdown.
10
+ - The `@pitlane/content` README gains the Vite setup the content guide describes. That setup covers the `satteri` and `vite-plugin-satteri` dev dependencies, and a `vite.config.ts` registering `satteri()` with `jsxImportSource: "remix/ui"`, `headings()`, `rawStyles()`, and `contentLayer()` before `remix()`. Before this, the README named `contentLayer()` but not the plugins it has to sit beside, and an MDX file compiled without `jsxImportSource: "remix/ui"` is a React component rather than a Remix one.
11
+ - The `@pitlane/content` entry-point table lists `@pitlane/content/hot`, and the README describes the four `@pitlane/content/internal/*` entry points as internal and unstable, with what each one is for: reuse by a plugin for a bundler other than Vite.
12
+
13
+ - d4dfade: Documentation comments, plus two type-only exports from `@pitlane/content`. No runtime behavior changed.
14
+
15
+ - `@pitlane/content` now exports the `Content` and `ReferenceSchema` types. `Content<T>` is what `createContent()` returns, and `ReferenceSchema<C>` is what `c.reference(collection)` returns. Both already appeared in those signatures; now code can import them by name.
16
+ - The TSDoc that editors show and the reference at pitlane.tools is built from now covers more. `remix()` and `createContent()` have examples. The package entry points and the main functions link to their guides. `RemixPluginOptions`, `PrerenderConfig`, `PrerenderOption`, `CrawlOptions`, `D1DatabaseOptions`, `D1DriverOptions`, `D1Meta`, `D1PreparedStatement`, and `D1Result` have summaries. Every `@pitlane/content` entry point and the `@pitlane/theme/default` and `@pitlane/theme/dtcg` entry points have module summaries.
17
+ - `loaders.file()` describes the file shapes it accepts and when `options.parser` is required. `ThemeResult.extend()` describes what its patch may hold. `DefaultTheme` lists its top-level token groups.
18
+
19
+ `@pitlane/dev/assets` documents each `?assets` import form and `pitlane:dev`, and has its own reference page.
20
+
21
+ ## 0.2.1
22
+
23
+ ### Patch Changes
24
+
25
+ - f2fa73e: Read a heading's `text`, and the `id` made from it, from what the heading shows on the page. Three kinds of heading change, and an anchor written by hand against one of them needs checking once:
26
+
27
+ - Inline HTML now contributes its words and not its tags. `## <span>Visible</span> text` was `<span>Visible</span> text` with the `id` `spanvisiblespan-text`, and is now `Visible text` with `visible-text`.
28
+ - An image no longer contributes its alt text, which matches GitHub and Astro. `## [A](url) ![Cat photo](cat.png)` was `a-cat-photo` and is now `a-`, keeping the space before the image as GitHub does.
29
+ - In MDX, an expression that is a single string literal now contributes its value. `## Hello {"world"}` was `hello-` and is now `hello-world`, and `## The {"{"} key` reads `The { key`. Any other expression, such as `{name}`, still contributes nothing.
30
+
31
+ Headings without inline HTML, images, or expressions keep their `text` and `id`.
32
+
3
33
  ## 0.2.0
4
34
 
5
35
  ### Minor Changes
package/README.md CHANGED
@@ -47,14 +47,63 @@ if (post) {
47
47
 
48
48
  The loaders are ordinary runtime code, which covers Node, Bun, Deno, and container hosts. For a host with no filesystem, add `contentLayer()` from `@pitlane/content/vite` and the build resolves the collections ahead of time, inlining entry data and compiling Markdown bodies into the bundle. The collection declarations do not change.
49
49
 
50
+ ## Configuring Vite
51
+
52
+ With a Vite build, Markdown and MDX need two build-only dependencies. [`@pitlane/dev`](https://pitlane.tools/guides/vite-plugin) is assumed and installs the same way:
53
+
54
+ ```sh
55
+ npm install --save-dev satteri vite-plugin-satteri
56
+ ```
57
+
58
+ Register the Sätteri plugin and `contentLayer()` in your Vite config, before `remix()`:
59
+
60
+ ```ts
61
+ // vite.config.ts
62
+ import { headings, rawStyles } from "@pitlane/content/satteri";
63
+ import { contentLayer } from "@pitlane/content/vite";
64
+ import { remix } from "@pitlane/dev";
65
+ import { defineConfig } from "vite";
66
+ import satteri from "vite-plugin-satteri";
67
+
68
+ export default defineConfig({
69
+ plugins: [
70
+ satteri({
71
+ mdx: { jsxImportSource: "remix/ui" },
72
+ mdastPlugins: [headings()],
73
+ hastPlugins: [rawStyles()],
74
+ }),
75
+ contentLayer(),
76
+ remix(),
77
+ ],
78
+ });
79
+ ```
80
+
81
+ `satteri()` compiles Markdown and MDX bodies during the build. `jsxImportSource: "remix/ui"` is required, because it is what makes a compiled MDX file a Remix component rather than a React one. `headings()` collects the heading list that `render()` returns, and `rawStyles()` keeps the CSS inside a `<style>` element intact, which any content with a highlighted code block produces.
82
+
83
+ `contentLayer()` executes `app/content.ts` in Node during the build and inlines every collection's entries into the bundle, so a deployed application never reads the filesystem. Name a module at another path with `contentLayer({ entry: "app/collections.ts" })`. A collection of only JSON or YAML files needs `contentLayer()` alone, with none of the Sätteri setup.
84
+
85
+ The [content guide](https://pitlane.tools/guides/content#configuring-vite) covers this setup in full.
86
+
50
87
  ## Entry points
51
88
 
52
- | Entry point | Exports |
53
- | -------------------------- | ------------------------------------------- |
54
- | `@pitlane/content` | `createContent` and the collection types |
55
- | `@pitlane/content/loaders` | `glob`, `file` |
56
- | `@pitlane/content/satteri` | `headings` and `rawStyles`, Sätteri plugins |
57
- | `@pitlane/content/vite` | `contentLayer`, the build-time plugin |
89
+ | Entry point | Exports |
90
+ | -------------------------- | ------------------------------------------------------------ |
91
+ | `@pitlane/content` | `createContent` and the collection types |
92
+ | `@pitlane/content/loaders` | `glob`, `file` |
93
+ | `@pitlane/content/satteri` | `headings` and `rawStyles`, Sätteri plugins |
94
+ | `@pitlane/content/vite` | `contentLayer`, the build-time plugin |
95
+ | `@pitlane/content/hot` | `hotContent`, which reloads the browser when content changes |
96
+
97
+ `hotContent()` is for an application that runs from source with no build. It does nothing unless `remix/node-hmr` supervises the process, so it can stay in production code.
98
+
99
+ ### Internal entry points
100
+
101
+ Four more entry points exist so that a plugin for a bundler other than Vite can reuse the pieces `contentLayer()` is built from. They are internal and unstable: they have no reference pages, and they can change in any release.
102
+
103
+ - `@pitlane/content/internal/manifest` is the module a build plugin replaces with the collections it prebuilt. As published, it declares that nothing was prebuilt, so without a plugin every collection falls through to its loader.
104
+ - `@pitlane/content/internal/prebuild` is the channel between the build plugin and `createContent()`. `openPrebuild()` and `closePrebuild()` bracket evaluating the content module, and the returned channel carries the collections, watched paths, and pending loads recorded in between.
105
+ - `@pitlane/content/internal/codegen` writes the manifest module as JavaScript source with `manifestModule()`, and exports `BODY_PREFIX`, the prefix of the virtual modules that carry each entry's body.
106
+ - `@pitlane/content/internal/mdx` exports `readEsm()`, which separates the imports in an MDX document's top-level ESM block from the rest of it.
58
107
 
59
108
  ## Without Remix
60
109
 
@@ -67,6 +116,8 @@ The loaders, schema validation, and query methods work with any [Standard Schema
67
116
  - [Custom loaders](https://pitlane.tools/guides/content#custom-loaders)
68
117
  - [API reference](https://pitlane.tools/package/content/)
69
118
 
119
+ For AI agents and other LLM tools, the documentation is also published as Markdown. [`llms.txt`](https://pitlane.tools/llms.txt) indexes every page, [`llms-full.txt`](https://pitlane.tools/llms-full.txt) holds them all in one file, and every page has a Markdown twin at its URL plus `.md`, or plus `index.md` when the URL ends in `/`, such as [`https://pitlane.tools/guides/content.md`](https://pitlane.tools/guides/content.md).
120
+
70
121
  ## License
71
122
 
72
123
  MIT
@@ -1,4 +1,4 @@
1
- import { p as LoadedEntry } from "./types-xSR1WTBq.mjs";
1
+ import { p as LoadedEntry } from "./types-C13GEJhr.mjs";
2
2
  //#region src/codegen.d.ts
3
3
  /** The prefix of the virtual modules that carry each entry's raw body. */
4
4
  declare const BODY_PREFIX = "\0pitlane-content/entry/";
package/dist/hot.d.mts CHANGED
@@ -37,6 +37,8 @@ interface BrowserHmrChannel {
37
37
  * follow this module into a Worker bundle.
38
38
  *
39
39
  * @param content The object `createContent` returned.
40
+ *
41
+ * @see {@link https://pitlane.tools/guides/content-no-build | Content guide, without a build}
40
42
  */
41
43
  declare function hotContent(content: Record<string, unknown>): Promise<void>;
42
44
  /**
package/dist/hot.mjs CHANGED
@@ -3,6 +3,15 @@ import { contentRoot } from "./prebuild.mjs";
3
3
  import * as path from "node:path";
4
4
  //#region src/hot.ts
5
5
  /**
6
+ * Browser reloads during development when a file behind a collection
7
+ * changes, for servers that `remix/node-hmr` supervises rather than Vite.
8
+ * See {@link hotContent}.
9
+ *
10
+ * @see {@link https://pitlane.tools/guides/content-no-build | Content guide, without a build}
11
+ *
12
+ * @module
13
+ */
14
+ /**
6
15
  * Reloads the browser when a file behind a collection changes.
7
16
  *
8
17
  * Call it once, beside `createContent`, and leave it in for production: it
@@ -13,6 +22,8 @@ import * as path from "node:path";
13
22
  * follow this module into a Worker bundle.
14
23
  *
15
24
  * @param content The object `createContent` returned.
25
+ *
26
+ * @see {@link https://pitlane.tools/guides/content-no-build | Content guide, without a build}
16
27
  */
17
28
  async function hotContent(content) {
18
29
  if (!process.env.REMIX_NODE_HMR) return;
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { _ as Reference, a as ContentBuilder, c as EntryBody, d as LiveEntry, f as LiveLoader, h as LoaderContext, i as Content, l as GenerateIdOptions, m as Loader, n as CollectionDefinition, o as ContentLoader, p as LoadedEntry, r as CollectionEntry, s as Entry, t as Collection, u as Heading, v as RenderedEntry } from "./types-xSR1WTBq.mjs";
1
+ import { _ as Reference, a as ContentBuilder, c as EntryBody, d as LiveEntry, f as LiveLoader, h as LoaderContext, i as Content, l as GenerateIdOptions, m as Loader, n as CollectionDefinition, o as ContentLoader, p as LoadedEntry, r as CollectionEntry, s as Entry, t as Collection, u as Heading, v as ReferenceSchema, y as RenderedEntry } from "./types-C13GEJhr.mjs";
2
2
  //#region src/content.d.ts
3
3
  /**
4
4
  * Declares a set of content collections.
@@ -8,7 +8,42 @@ import { _ as Reference, a as ContentBuilder, c as EntryBody, d as LiveEntry, f
8
8
  *
9
9
  * Under `contentLayer()`, registers deferred population work. The plugin
10
10
  * awaits it after evaluating the declarations and before emitting the bundle.
11
+ *
12
+ * @param build - Receives the {@link ContentBuilder} and returns one
13
+ * collection per key, each declared with `c.collection({ loader, schema })`
14
+ * @returns One {@link Collection} per key of the object `build` returned
15
+ * @throws Error when `c.reference()` names a collection `build` did not declare
16
+ *
17
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
18
+ * @see {@link https://pitlane.tools/guides/content-no-build | Content guide, without a build}
19
+ *
20
+ * @example
21
+ * ```ts
22
+ * // app/content.ts
23
+ * import { createContent } from "@pitlane/content";
24
+ * import * as loaders from "@pitlane/content/loaders";
25
+ * import * as s from "remix/data-schema";
26
+ * import * as coerce from "remix/data-schema/coerce";
27
+ *
28
+ * export let content = createContent(c => ({
29
+ * blog: c.collection({
30
+ * loader: loaders.glob({ base: "app/content/blog", pattern: "*.{md,mdx}" }),
31
+ * schema: s.object({
32
+ * title: s.string(),
33
+ * pubDate: coerce.date(),
34
+ * author: c.reference("authors"),
35
+ * }),
36
+ * }),
37
+ * authors: c.collection({
38
+ * loader: loaders.file("app/content/authors.json"),
39
+ * schema: s.object({ name: s.string() }),
40
+ * }),
41
+ * }));
42
+ *
43
+ * let posts = await content.blog.getCollection();
44
+ * let post = await content.blog.getEntry("hello-world");
45
+ * ```
11
46
  */
12
47
  declare function createContent<T extends Record<string, CollectionDefinition>>(build: (c: ContentBuilder) => T): Content<T>;
13
48
  //#endregion
14
- export { type Collection, type CollectionDefinition, type CollectionEntry, type ContentBuilder, type ContentLoader, type Entry, type EntryBody, type GenerateIdOptions, type Heading, type LiveEntry, type LiveLoader, type LoadedEntry, type Loader, type LoaderContext, type Reference, type RenderedEntry, createContent };
49
+ export { type Collection, type CollectionDefinition, type CollectionEntry, type Content, type ContentBuilder, type ContentLoader, type Entry, type EntryBody, type GenerateIdOptions, type Heading, type LiveEntry, type LiveLoader, type LoadedEntry, type Loader, type LoaderContext, type Reference, type ReferenceSchema, type RenderedEntry, createContent };
package/dist/index.mjs CHANGED
@@ -126,6 +126,41 @@ function relativeTo(root, filePath) {
126
126
  *
127
127
  * Under `contentLayer()`, registers deferred population work. The plugin
128
128
  * awaits it after evaluating the declarations and before emitting the bundle.
129
+ *
130
+ * @param build - Receives the {@link ContentBuilder} and returns one
131
+ * collection per key, each declared with `c.collection({ loader, schema })`
132
+ * @returns One {@link Collection} per key of the object `build` returned
133
+ * @throws Error when `c.reference()` names a collection `build` did not declare
134
+ *
135
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
136
+ * @see {@link https://pitlane.tools/guides/content-no-build | Content guide, without a build}
137
+ *
138
+ * @example
139
+ * ```ts
140
+ * // app/content.ts
141
+ * import { createContent } from "@pitlane/content";
142
+ * import * as loaders from "@pitlane/content/loaders";
143
+ * import * as s from "remix/data-schema";
144
+ * import * as coerce from "remix/data-schema/coerce";
145
+ *
146
+ * export let content = createContent(c => ({
147
+ * blog: c.collection({
148
+ * loader: loaders.glob({ base: "app/content/blog", pattern: "*.{md,mdx}" }),
149
+ * schema: s.object({
150
+ * title: s.string(),
151
+ * pubDate: coerce.date(),
152
+ * author: c.reference("authors"),
153
+ * }),
154
+ * }),
155
+ * authors: c.collection({
156
+ * loader: loaders.file("app/content/authors.json"),
157
+ * schema: s.object({ name: s.string() }),
158
+ * }),
159
+ * }));
160
+ *
161
+ * let posts = await content.blog.getCollection();
162
+ * let post = await content.blog.getEntry("hello-world");
163
+ * ```
129
164
  */
130
165
  function createContent(build) {
131
166
  let referenced = [];
@@ -1,4 +1,4 @@
1
- import { l as GenerateIdOptions, o as ContentLoader } from "./types-xSR1WTBq.mjs";
1
+ import { l as GenerateIdOptions, o as ContentLoader } from "./types-C13GEJhr.mjs";
2
2
  import { CompileOptions } from "satteri";
3
3
  //#region src/loaders/file.d.ts
4
4
  /**
@@ -9,6 +9,8 @@ import { CompileOptions } from "satteri";
9
9
  * TOML reader — is typed as returning a JSON-value union, which a narrower
10
10
  * type rejects. `entriesOf` refuses an unusable result by name, so demanding
11
11
  * the caller narrow first would buy a wrapper and no safety.
12
+ *
13
+ * @inline
12
14
  */
13
15
  type Parser = (text: string) => unknown;
14
16
  /**
@@ -16,6 +18,13 @@ type Parser = (text: string) => unknown;
16
18
  *
17
19
  * Every entry is data rather than a document: a file of records has no body to
18
20
  * render, so `render()` on one of these entries is a mistake and says so.
21
+ *
22
+ * The file holds either an object whose keys are entry ids or an array of
23
+ * entries that each carry an `id`. `.json`, `.yaml`, and `.yml` parse without
24
+ * options; any other format needs `options.parser`, which turns the file's
25
+ * text into that object or array.
26
+ *
27
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
19
28
  */
20
29
  declare function file(fileName: string, options?: {
21
30
  parser?: Parser;
@@ -27,6 +36,8 @@ declare function file(fileName: string, options?: {
27
36
  *
28
37
  * `pattern` is an ordinary runtime value — computed, read from the environment,
29
38
  * or assembled in a loop — because nothing about it is read statically.
39
+ *
40
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
30
41
  */
31
42
  declare function glob(options: {
32
43
  pattern: string | string[];
package/dist/loaders.mjs CHANGED
@@ -49,6 +49,13 @@ function read(parse, text, filePath) {
49
49
  *
50
50
  * Every entry is data rather than a document: a file of records has no body to
51
51
  * render, so `render()` on one of these entries is a mistake and says so.
52
+ *
53
+ * The file holds either an object whose keys are entry ids or an array of
54
+ * entries that each carry an `id`. `.json`, `.yaml`, and `.yml` parse without
55
+ * options; any other format needs `options.parser`, which turns the file's
56
+ * text into that object or array.
57
+ *
58
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
52
59
  */
53
60
  function file(fileName, options) {
54
61
  let watched = [];
@@ -163,6 +170,8 @@ let formats = {
163
170
  *
164
171
  * `pattern` is an ordinary runtime value — computed, read from the environment,
165
172
  * or assembled in a loop — because nothing about it is read statically.
173
+ *
174
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
166
175
  */
167
176
  function glob(options) {
168
177
  let watched = [];
@@ -1,4 +1,4 @@
1
- import { g as PrebuiltCollections } from "./types-xSR1WTBq.mjs";
1
+ import { g as PrebuiltCollections } from "./types-C13GEJhr.mjs";
2
2
  //#region src/prebuild.d.ts
3
3
  /**
4
4
  * The channel between `contentLayer()` and `createContent`.
@@ -3,7 +3,8 @@ import { HastPluginEntry, MdastPluginEntry } from "satteri";
3
3
  /**
4
4
  * Collects every heading of a document as `{ depth, slug, text }`, publishes the
5
5
  * list as `data.headings`, and gives each heading an `id` matching its slug so an
6
- * anchor link lands on it. On MDX the list is also appended to the tree as
6
+ * anchor link lands on it. A heading's text is the text it shows on the page,
7
+ * as `visibleText` reads it. On MDX the list is also appended to the tree as
7
8
  * `export const headings`, so the compiled module carries its own table of
8
9
  * contents.
9
10
  *
package/dist/satteri.mjs CHANGED
@@ -47,9 +47,73 @@ function uniqueSlug(text, taken) {
47
47
  return slug;
48
48
  }
49
49
  /**
50
+ * The text a heading shows on the page, which is what GitHub and Astro slug.
51
+ * Sätteri's `textContent` differs in three places: it keeps raw HTML tags as
52
+ * text, includes image alt text, and reads every MDX expression as nothing.
53
+ * Its options drop the first two, but an expression only carries its source,
54
+ * so the walk is done here.
55
+ *
56
+ * Raw HTML nodes hold only the tags; the text between them is already its own
57
+ * sibling node, so dropping `html` leaves the visible words. Images render no
58
+ * text.
59
+ */
60
+ function visibleText(node) {
61
+ switch (node.type) {
62
+ case "html":
63
+ case "image":
64
+ case "imageReference": return "";
65
+ case "mdxTextExpression": return stringLiteralValue(node.value ?? "") ?? "";
66
+ }
67
+ if (node.children) return node.children.map(visibleText).join("");
68
+ return node.value ?? "";
69
+ }
70
+ /** A lone JavaScript string or template literal, surrounded by nothing but whitespace. */
71
+ const STRING_LITERAL = /^\s*(?:"((?:[^"\\\n\r]|\\[^])*)"|'((?:[^'\\\n\r]|\\[^])*)'|`((?:[^`\\$]|\\[^]|\$(?!\{))*)`)\s*$/;
72
+ /**
73
+ * One escape sequence: a code point in either `\u` form, a `\x` byte, `\0` not
74
+ * followed by a digit, a line continuation, or any other escaped character.
75
+ */
76
+ const ESCAPE = /\\(?:u\{([\da-fA-F]+)\}|u([\da-fA-F]{4})|x([\da-fA-F]{2})|(0)(?!\d)|(\r\n|[\n\r\u2028\u2029])|([^]))/g;
77
+ const SINGLE_CHARACTER_ESCAPES = {
78
+ b: "\b",
79
+ f: "\f",
80
+ n: "\n",
81
+ r: "\r",
82
+ t: " ",
83
+ v: "\v"
84
+ };
85
+ /**
86
+ * The value of an MDX expression whose source is a lone string literal, such
87
+ * as `{"{"}`, the usual way to write a brace in MDX text. `undefined` for any
88
+ * other expression, whose value is only known once the module runs.
89
+ *
90
+ * Escapes decode as in a module, which is strict code: a legacy octal escape
91
+ * such as `\1`, a `\8`, or a malformed `\x` or `\u` makes the literal invalid.
92
+ */
93
+ function stringLiteralValue(source) {
94
+ let match = STRING_LITERAL.exec(source);
95
+ if (!match) return void 0;
96
+ let [, double, single, template] = match;
97
+ let body = double ?? single ?? template.replace(/\r\n?/g, "\n");
98
+ let valid = true;
99
+ let value = body.replace(ESCAPE, (_, braced, unit, byte, nul, lineBreak, other) => {
100
+ if (braced !== void 0) {
101
+ let codePoint = Number.parseInt(braced, 16);
102
+ if (codePoint <= 1114111) return String.fromCodePoint(codePoint);
103
+ } else if (unit !== void 0 || byte !== void 0) return String.fromCharCode(Number.parseInt(unit ?? byte, 16));
104
+ else if (nul !== void 0) return "\0";
105
+ else if (lineBreak !== void 0) return "";
106
+ else if (!/[ux\d]/.test(other)) return SINGLE_CHARACTER_ESCAPES[other] ?? other;
107
+ valid = false;
108
+ return "";
109
+ });
110
+ return valid ? value : void 0;
111
+ }
112
+ /**
50
113
  * Collects every heading of a document as `{ depth, slug, text }`, publishes the
51
114
  * list as `data.headings`, and gives each heading an `id` matching its slug so an
52
- * anchor link lands on it. On MDX the list is also appended to the tree as
115
+ * anchor link lands on it. A heading's text is the text it shows on the page,
116
+ * as `visibleText` reads it. On MDX the list is also appended to the tree as
53
117
  * `export const headings`, so the compiled module carries its own table of
54
118
  * contents.
55
119
  *
@@ -72,7 +136,7 @@ function headings() {
72
136
  return {
73
137
  name: "pitlane-headings",
74
138
  heading(node, context) {
75
- let text = context.textContent(node);
139
+ let text = visibleText(node);
76
140
  let slug = uniqueSlug(text, taken);
77
141
  collected.push({
78
142
  depth: node.depth,
@@ -83,7 +83,14 @@ interface ChainableSchema<Input, Output> {
83
83
  refine: (predicate: (value: Output) => boolean, message?: string) => ChainableSchema<Input, Output>;
84
84
  transform: <Next>(transformer: (value: Output) => Next) => ChainableSchema<Input, Next>;
85
85
  }
86
- /** The schema `c.reference(collection)` returns. */
86
+ /**
87
+ * The schema `c.reference(collection)` returns: a Standard Schema that reads
88
+ * an entry id (a string) and outputs a {@link Reference} into `collection`.
89
+ *
90
+ * It composes inside `remix/data-schema`'s `s.object`, `s.array`, and
91
+ * `s.optional` like any other schema. It does not check that the target entry
92
+ * exists; a dangling id surfaces as `getEntry` resolving to `undefined`.
93
+ */
87
94
  type ReferenceSchema<C extends string> = ChainableSchema<string, Reference<C>>;
88
95
  /** A heading collected from a Markdown or MDX document. */
89
96
  interface Heading {
@@ -200,7 +207,11 @@ interface ContentBuilder {
200
207
  }): CollectionDefinition<S>;
201
208
  reference<C extends string>(collection: C): ReferenceSchema<C>;
202
209
  }
203
- /** The object `createContent` returns: one {@link Collection} per key. */
210
+ /**
211
+ * The object `createContent` returns: one {@link Collection} per key of the
212
+ * definitions its callback returned, named after that key and typed by the
213
+ * output of that collection's schema.
214
+ */
204
215
  type Content<T extends Record<string, CollectionDefinition>> = { [K in keyof T]: Collection<K & string, InferSchema<T[K]["schema"]>>; };
205
216
  /** How `contentLayer()` spells an entry's body in the manifest it emits. */
206
217
  type PrebuiltBody = {
@@ -235,4 +246,4 @@ interface GenerateIdOptions {
235
246
  data: Record<string, unknown>;
236
247
  }
237
248
  //#endregion
238
- export { Reference as _, ContentBuilder as a, EntryBody as c, LiveEntry as d, LiveLoader as f, PrebuiltCollections as g, LoaderContext as h, Content as i, GenerateIdOptions as l, Loader as m, CollectionDefinition as n, ContentLoader as o, LoadedEntry as p, CollectionEntry as r, Entry as s, Collection as t, Heading as u, RenderedEntry as v };
249
+ export { Reference as _, ContentBuilder as a, EntryBody as c, LiveEntry as d, LiveLoader as f, PrebuiltCollections as g, LoaderContext as h, Content as i, GenerateIdOptions as l, Loader as m, CollectionDefinition as n, ContentLoader as o, LoadedEntry as p, CollectionEntry as r, Entry as s, Collection as t, Heading as u, ReferenceSchema as v, RenderedEntry as y };
package/dist/vite.d.mts CHANGED
@@ -10,6 +10,8 @@ import { Plugin } from "vite";
10
10
  *
11
11
  * The result is a host with no filesystem serving the collections the
12
12
  * application declared, with no change to the declarations.
13
+ *
14
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
13
15
  */
14
16
  declare function contentLayer(options?: {
15
17
  entry?: string;
package/dist/vite.mjs CHANGED
@@ -4,6 +4,15 @@ import { dirname, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { createRunnableDevEnvironment, createServer } from "vite";
6
6
  //#region src/vite.ts
7
+ /**
8
+ * The {@link contentLayer} Vite plugin, which resolves every collection
9
+ * during the build and inlines the entries into the bundle, so a host with no
10
+ * filesystem serves the same collections.
11
+ *
12
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
13
+ *
14
+ * @module
15
+ */
7
16
  const MANIFEST_OWNER = "@pitlane/content";
8
17
  const MANIFEST_SPECIFIER = `${MANIFEST_OWNER}/internal/manifest`;
9
18
  const VIRTUAL_MANIFEST = "\0pitlane-content/manifest";
@@ -17,6 +26,8 @@ const VIRTUAL_MANIFEST = "\0pitlane-content/manifest";
17
26
  *
18
27
  * The result is a host with no filesystem serving the collections the
19
28
  * application declared, with no change to the declarations.
29
+ *
30
+ * @see {@link https://pitlane.tools/guides/content | Content guide}
20
31
  */
21
32
  function contentLayer(options) {
22
33
  let entry = options?.entry ?? "app/content.ts";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pitlane/content",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Schema-validated content collections for Remix.",
5
5
  "keywords": [
6
6
  "content",