@nifrajs/content 2.2.0 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +2 -2
- package/src/fs.ts +4 -4
- package/src/index.ts +11 -11
- package/src/mdx.ts +9 -9
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# @nifrajs/content
|
|
2
2
|
|
|
3
|
-
Typed, schema-validated content collections for nifra
|
|
3
|
+
Typed, schema-validated content collections for nifra - Markdown + frontmatter, framework-agnostic.
|
|
4
4
|
|
|
5
|
-
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework
|
|
5
|
+
Part of the **[nifra](https://nifra.dev)** full-stack TypeScript framework - one core, five UI libraries, every runtime. Scaffold a new app with `bun create nifra`.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -19,7 +19,7 @@ MIT
|
|
|
19
19
|
|
|
20
20
|
## For AI agents
|
|
21
21
|
|
|
22
|
-
Start with [`LLM.md`](./LLM.md)
|
|
22
|
+
Start with [`LLM.md`](./LLM.md) - this package's contract card (the exports you call + its footguns),
|
|
23
23
|
one cheap read instead of the whole corpus. For the wider framework: the repo's
|
|
24
24
|
[`AGENTS.md`](../../AGENTS.md) is the copy-paste quick reference, and
|
|
25
25
|
[`llms-full.txt`](../../llms-full.txt) is the full machine-readable corpus. Run `nifra check` as the
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nifrajs/content",
|
|
3
|
-
"version": "2.
|
|
4
|
-
"description": "Typed, schema-validated content collections for nifra
|
|
3
|
+
"version": "2.3.0",
|
|
4
|
+
"description": "Typed, schema-validated content collections for nifra - Markdown + frontmatter, framework-agnostic.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"bun",
|
|
7
7
|
"typescript",
|
package/src/fs.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Filesystem-backed content collections
|
|
2
|
+
* Filesystem-backed content collections - `defineCollection` reads a directory of Markdown files and
|
|
3
3
|
* gives you a typed, validated API over them. Build/server-time (Bun/Node/Deno); for the edge, bake the
|
|
4
|
-
* entries at build (the manifest approach `@nifrajs/web/fs` uses for routes)
|
|
4
|
+
* entries at build (the manifest approach `@nifrajs/web/fs` uses for routes) - tracked as a follow-up.
|
|
5
5
|
*/
|
|
6
6
|
import { readdirSync, readFileSync, statSync } from "node:fs"
|
|
7
7
|
import { resolve, sep } from "node:path"
|
|
@@ -18,7 +18,7 @@ export interface CollectionConfig<S extends StandardSchemaV1> {
|
|
|
18
18
|
|
|
19
19
|
/** A typed collection over a content directory. */
|
|
20
20
|
export interface Collection<Frontmatter> {
|
|
21
|
-
/** Every entry in the directory (unordered
|
|
21
|
+
/** Every entry in the directory (unordered - sort/filter in your loader). Throws if any file's
|
|
22
22
|
* frontmatter is invalid. */
|
|
23
23
|
all(): Promise<Array<Entry<Frontmatter>>>
|
|
24
24
|
/** One entry by slug (filename without extension), or `null` if not found. */
|
|
@@ -66,7 +66,7 @@ interface CollectionSnapshot<Frontmatter> {
|
|
|
66
66
|
/**
|
|
67
67
|
* Define a content collection backed by a directory. `all()` discovers + parses every matching file;
|
|
68
68
|
* `get(slug)` loads one. Frontmatter is validated against `schema`, so entries are fully typed and a
|
|
69
|
-
* malformed file fails loudly. Reads the filesystem
|
|
69
|
+
* malformed file fails loudly. Reads the filesystem - use it at build time (SSG/prerender) or on a
|
|
70
70
|
* long-lived server (Bun/Node/Deno).
|
|
71
71
|
*/
|
|
72
72
|
export function defineCollection<S extends StandardSchemaV1>(
|
package/src/index.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Typed content collections for nifra
|
|
2
|
+
* Typed content collections for nifra - the **framework-agnostic core**. Parse a Markdown file's
|
|
3
3
|
* frontmatter + body, validate the frontmatter against a Standard Schema (`@nifrajs/schema`'s `t`, zod,
|
|
4
|
-
* valibot), and render the body to HTML. Pure (no filesystem, no DOM) so it runs anywhere
|
|
4
|
+
* valibot), and render the body to HTML. Pure (no filesystem, no DOM) so it runs anywhere - pair it
|
|
5
5
|
* with `@nifrajs/content/fs`'s `defineCollection` for fs-backed discovery on Bun/Node/Deno + at build.
|
|
6
6
|
*
|
|
7
7
|
* import { t } from "@nifrajs/schema"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* schema: t.object({ title: t.string(), date: t.string(), draft: t.boolean() }),
|
|
13
13
|
* })
|
|
14
14
|
*
|
|
15
|
-
* // in a loader
|
|
15
|
+
* // in a loader - typed + validated entries, no manual fs/frontmatter parsing:
|
|
16
16
|
* const posts = (await blog.all()).filter((p) => !p.frontmatter.draft)
|
|
17
17
|
* // posts[0].frontmatter is { title: string; date: string; draft: boolean }
|
|
18
18
|
* // posts[0].html is the rendered Markdown
|
|
@@ -26,7 +26,7 @@ interface StandardIssue {
|
|
|
26
26
|
}
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* Minimal [Standard Schema](https://standardschema.dev) shape
|
|
29
|
+
* Minimal [Standard Schema](https://standardschema.dev) shape - lets frontmatter validate against any
|
|
30
30
|
* compliant validator (`@nifrajs/schema`'s `t`, zod, valibot, …) without coupling `@nifrajs/content` to one.
|
|
31
31
|
*/
|
|
32
32
|
export interface StandardSchemaV1<Output = unknown> {
|
|
@@ -73,14 +73,14 @@ export function parseFrontmatter(raw: string): { data: unknown; body: string } {
|
|
|
73
73
|
export interface ParseEntryOptions<S extends StandardSchemaV1> {
|
|
74
74
|
readonly raw: string
|
|
75
75
|
readonly slug: string
|
|
76
|
-
/** Standard Schema validating the frontmatter
|
|
76
|
+
/** Standard Schema validating the frontmatter - a typo'd/missing field throws (a build-time failure,
|
|
77
77
|
* never a broken page). */
|
|
78
78
|
readonly schema: S
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
/**
|
|
82
82
|
* Parse one content file: split + validate its frontmatter against `schema`, render its Markdown body
|
|
83
|
-
* to HTML. Throws a descriptive error (naming the slug + the issues) when the frontmatter is invalid
|
|
83
|
+
* to HTML. Throws a descriptive error (naming the slug + the issues) when the frontmatter is invalid -
|
|
84
84
|
* surface it at build/load time rather than shipping a malformed entry. Pure + edge-safe.
|
|
85
85
|
*/
|
|
86
86
|
export async function parseEntry<S extends StandardSchemaV1>(
|
|
@@ -92,7 +92,7 @@ export async function parseEntry<S extends StandardSchemaV1>(
|
|
|
92
92
|
const detail = result.issues.map((issue) => issue.message).join("; ")
|
|
93
93
|
throw new Error(`@nifrajs/content: invalid frontmatter in "${options.slug}": ${detail}`)
|
|
94
94
|
}
|
|
95
|
-
// `marked.parse` is sync by default but may be async with extensions
|
|
95
|
+
// `marked.parse` is sync by default but may be async with extensions - await covers both. Content is
|
|
96
96
|
// author-controlled (your own files), so raw HTML in Markdown is passed through (standard SSG
|
|
97
97
|
// behavior); sanitize yourself if a collection ever holds untrusted input.
|
|
98
98
|
const html = await marked.parse(body)
|
|
@@ -100,7 +100,7 @@ export async function parseEntry<S extends StandardSchemaV1>(
|
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
/**
|
|
103
|
-
* A content collection baked to plain data
|
|
103
|
+
* A content collection baked to plain data - fs-free, so it works at the **edge** (Workers
|
|
104
104
|
* request-time) where `defineCollection`'s `node:fs` reader can't run. Produce one at build/server time
|
|
105
105
|
* with `bakeCollection`, JSON-serialize + ship it in the bundle, then rehydrate with `fromBaked`.
|
|
106
106
|
*/
|
|
@@ -108,7 +108,7 @@ export interface BakedCollection<Frontmatter> {
|
|
|
108
108
|
readonly entries: ReadonlyArray<Entry<Frontmatter>>
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
/** Read-only collection surface (`all()`/`get()`)
|
|
111
|
+
/** Read-only collection surface (`all()`/`get()`) - structurally compatible with `defineCollection`'s
|
|
112
112
|
* `Collection`, but with no filesystem access. */
|
|
113
113
|
export interface StaticCollection<Frontmatter> {
|
|
114
114
|
all(): Promise<ReadonlyArray<Entry<Frontmatter>>>
|
|
@@ -118,7 +118,7 @@ export interface StaticCollection<Frontmatter> {
|
|
|
118
118
|
/**
|
|
119
119
|
* Bake a collection's entries to serializable data at build/server time. The collection does the
|
|
120
120
|
* filesystem read + validation (via `all()`); this just collects the already-parsed result so it can be
|
|
121
|
-
* JSON-serialized into the edge bundle. Pure
|
|
121
|
+
* JSON-serialized into the edge bundle. Pure - safe to import anywhere.
|
|
122
122
|
*/
|
|
123
123
|
export async function bakeCollection<Frontmatter>(collection: {
|
|
124
124
|
all(): Promise<ReadonlyArray<Entry<Frontmatter>>>
|
|
@@ -127,7 +127,7 @@ export async function bakeCollection<Frontmatter>(collection: {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
* Rehydrate a baked collection into a read-only `all()`/`get()` collection
|
|
130
|
+
* Rehydrate a baked collection into a read-only `all()`/`get()` collection - fs-free, edge-safe. The
|
|
131
131
|
* entries were validated when baked (build output, trusted), so they're served as-is. `get` is O(1).
|
|
132
132
|
*/
|
|
133
133
|
export function fromBaked<Frontmatter>(
|
package/src/mdx.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* `mdxBunPlugin`
|
|
2
|
+
* `mdxBunPlugin` - a `Bun.build` plugin that compiles `.mdx` files (Markdown + JSX components) into
|
|
3
3
|
* components for your UI framework, so an `.mdx` file can be a nifra route or be imported like a module.
|
|
4
4
|
* Pass it to `buildClient`/`buildServer`'s `plugins`, and add `.mdx` to your route discovery.
|
|
5
5
|
*
|
|
@@ -13,22 +13,22 @@
|
|
|
13
13
|
* })
|
|
14
14
|
*
|
|
15
15
|
* MDX is JSX-oriented, so this targets the JSX family (React/Preact/Solid) via `jsxImportSource`. Needs
|
|
16
|
-
* `@mdx-js/mdx` installed (an optional peer
|
|
16
|
+
* `@mdx-js/mdx` installed (an optional peer - lazy-loaded at build time only).
|
|
17
17
|
*/
|
|
18
18
|
import type { BunPlugin } from "bun"
|
|
19
19
|
|
|
20
20
|
export interface MdxPluginOptions {
|
|
21
|
-
/** JSX runtime source the compiled MDX imports from
|
|
21
|
+
/** JSX runtime source the compiled MDX imports from - `"react"` (default), `"preact"`, `"solid-js"`. */
|
|
22
22
|
readonly jsxImportSource?: string
|
|
23
23
|
/** Emit development JSX (line numbers, `jsxDEV`). Default `false`. */
|
|
24
24
|
readonly development?: boolean
|
|
25
|
-
/** Extra `@mdx-js/mdx` `compile` options
|
|
25
|
+
/** Extra `@mdx-js/mdx` `compile` options - e.g. `remarkPlugins`/`rehypePlugins` (GFM, slugs, …). */
|
|
26
26
|
readonly compileOptions?: Record<string, unknown>
|
|
27
|
-
/** Advanced: override the compiler module specifier (default `"@mdx-js/mdx"`)
|
|
27
|
+
/** Advanced: override the compiler module specifier (default `"@mdx-js/mdx"`) - e.g. to use a fork. */
|
|
28
28
|
readonly moduleName?: string
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
// Non-literal specifier so tsc/consumers don't need `@mdx-js/mdx`'s types to typecheck this file
|
|
31
|
+
// Non-literal specifier so tsc/consumers don't need `@mdx-js/mdx`'s types to typecheck this file - it's
|
|
32
32
|
// an optional peer, imported only when the plugin actually runs (mirrors `@nifrajs/node`'s lazy `ws`).
|
|
33
33
|
const MDX_MODULE = "@mdx-js/mdx"
|
|
34
34
|
|
|
@@ -53,10 +53,10 @@ export function mdxBunPlugin(options: MdxPluginOptions = {}): BunPlugin {
|
|
|
53
53
|
compile = mod.compile
|
|
54
54
|
} catch {
|
|
55
55
|
throw new Error(
|
|
56
|
-
"@nifrajs/content/mdx: compiling `.mdx` needs the `@mdx-js/mdx` package
|
|
56
|
+
"@nifrajs/content/mdx: compiling `.mdx` needs the `@mdx-js/mdx` package - install it (`bun add @mdx-js/mdx`).",
|
|
57
57
|
)
|
|
58
58
|
}
|
|
59
|
-
// Tolerate a `?query` suffix
|
|
59
|
+
// Tolerate a `?query` suffix - `nifra dev` (Vite) appends an import-cache-busting query so edited
|
|
60
60
|
// `.mdx` re-SSR; strip it before reading off disk.
|
|
61
61
|
build.onLoad({ filter: /\.mdx(\?|$)/ }, async (args) => {
|
|
62
62
|
const path = args.path.split("?")[0] ?? args.path
|
|
@@ -66,7 +66,7 @@ export function mdxBunPlugin(options: MdxPluginOptions = {}): BunPlugin {
|
|
|
66
66
|
development,
|
|
67
67
|
...options.compileOptions,
|
|
68
68
|
})
|
|
69
|
-
// `loader: "jsx"`
|
|
69
|
+
// `loader: "jsx"` - the compiled output still contains JSX; Bun applies the JSX transform with
|
|
70
70
|
// the active `jsxImportSource`. Markup is hydration-equivalent to a hand-written component.
|
|
71
71
|
return { contents: String(compiled), loader: "jsx" }
|
|
72
72
|
})
|