@contentrain/query 7.0.15 → 7.2.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 +92 -6
- package/dist/astro/index.cjs +142 -0
- package/dist/astro/index.d.cts +81 -0
- package/dist/astro/index.d.cts.map +1 -0
- package/dist/astro/index.d.mts +81 -0
- package/dist/astro/index.d.mts.map +1 -0
- package/dist/astro/index.mjs +143 -0
- package/dist/astro/index.mjs.map +1 -0
- package/dist/cdn/index.cjs +2 -1
- package/dist/cdn/index.d.cts +2 -2
- package/dist/cdn/index.d.mts +2 -2
- package/dist/cdn/index.mjs +2 -2
- package/dist/{cdn-C8dqHhdG.mjs → cdn-C5yiVdfQ.mjs} +102 -27
- package/dist/cdn-C5yiVdfQ.mjs.map +1 -0
- package/dist/{cdn-DrQCLLj6.cjs → cdn-DJw9oksO.cjs} +106 -25
- package/dist/cli.cjs +1 -1
- package/dist/cli.mjs +1 -1
- package/dist/frontmatter-CVjPyhOq.mjs +248 -0
- package/dist/frontmatter-CVjPyhOq.mjs.map +1 -0
- package/dist/frontmatter-DSFLOTwS.cjs +287 -0
- package/dist/{generate-mpKW9Y8v.mjs → generate-B7jooE0f.mjs} +4 -244
- package/dist/generate-B7jooE0f.mjs.map +1 -0
- package/dist/{generate-DN3mAbvC.cjs → generate-D3C6itur.cjs} +14 -254
- package/dist/generator/generate.cjs +1 -1
- package/dist/generator/generate.mjs +1 -1
- package/dist/{index-TgljULTn.d.mts → index-BFurv7WX.d.cts} +134 -28
- package/dist/index-BFurv7WX.d.cts.map +1 -0
- package/dist/{index-mxo0dHvb.d.cts → index-VXFz-DDN.d.mts} +134 -28
- package/dist/index-VXFz-DDN.d.mts.map +1 -0
- package/dist/index.cjs +2 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +2 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +39 -15
- package/dist/cdn-C8dqHhdG.mjs.map +0 -1
- package/dist/generate-mpKW9Y8v.mjs.map +0 -1
- package/dist/index-TgljULTn.d.mts.map +0 -1
- package/dist/index-mxo0dHvb.d.cts.map +0 -1
package/README.md
CHANGED
|
@@ -307,22 +307,55 @@ asset.meta.alt // 'Hero image'
|
|
|
307
307
|
|
|
308
308
|
### Forms
|
|
309
309
|
|
|
310
|
-
|
|
310
|
+
The browser side of Studio's public `/api/forms/v1` — no API key travels with
|
|
311
|
+
these requests (the endpoints are unauthenticated by design, and a page cannot
|
|
312
|
+
keep a secret; Studio's CORS for them allows only `Content-Type`):
|
|
311
313
|
|
|
312
314
|
```ts
|
|
313
315
|
const form = client.form()
|
|
314
316
|
|
|
315
|
-
//
|
|
317
|
+
// Public form config: exposed fields keyed by id, captcha, honeypot
|
|
316
318
|
const config = await form.config('contact')
|
|
317
|
-
// config.
|
|
319
|
+
// config.locale → 'en' (what the submission is validated against)
|
|
320
|
+
// config.fields → { name: { type: 'string', required: true }, email: { type: 'email', … } }
|
|
321
|
+
// config.captcha → 'turnstile' | null (mount the widget with config.captchaSiteKey)
|
|
322
|
+
// config.honeypotField → '_hp' | null (render a hidden input with that name, leave it empty)
|
|
318
323
|
|
|
319
|
-
// Submit
|
|
324
|
+
// Submit: values under `data`; captcha token and honeypot value beside it
|
|
320
325
|
const result = await form.submit('contact', {
|
|
321
326
|
name: 'Alice',
|
|
322
327
|
email: 'alice@example.com',
|
|
323
328
|
message: 'Hello!',
|
|
324
|
-
}, { captchaToken: 'tok_xxx' })
|
|
325
|
-
//
|
|
329
|
+
}, { captchaToken: 'tok_xxx', honeypot: '' })
|
|
330
|
+
// → { success: true, message: 'Thank you!' }
|
|
331
|
+
// → { success: false, errors: [{ field: 'email', message }] } (a verdict, not an exception)
|
|
332
|
+
// 403 / 404 / 429 reject with ContentrainError { status, message }
|
|
333
|
+
```
|
|
334
|
+
|
|
335
|
+
### Comments
|
|
336
|
+
|
|
337
|
+
The browser side of Studio's public `/api/comments/v1`. Only approved comments
|
|
338
|
+
come back, nested under their roots; email, IP, user agent and referrer never
|
|
339
|
+
leave the server. `body` is plain text — render it escaped.
|
|
340
|
+
|
|
341
|
+
```ts
|
|
342
|
+
const comments = client.comments()
|
|
343
|
+
|
|
344
|
+
const thread = await comments.thread('posts', entryId, { locale: 'en', page: 1, limit: 20, sort: 'oldest' })
|
|
345
|
+
// thread.config → { closed, requireApproval, requireEmail, maxDepth, maxBodyLength, captcha, captchaSiteKey, honeypotField }
|
|
346
|
+
// thread.comments → [{ id, parentId, depth, author: { name, url, isModerator }, body, type, createdAt, replies: [...] }]
|
|
347
|
+
// thread.total → root comments in total (pagination is over roots)
|
|
348
|
+
|
|
349
|
+
const result = await comments.submit('posts', entryId, {
|
|
350
|
+
author: { name: 'Ada', email: 'ada@example.com', url: 'https://ada.dev' },
|
|
351
|
+
body: 'Great post',
|
|
352
|
+
parentId: thread.comments[0]?.id, // reply; omit for a root comment
|
|
353
|
+
captchaToken: 'tok_xxx',
|
|
354
|
+
honeypot: '',
|
|
355
|
+
}, { locale: 'en' })
|
|
356
|
+
// → { success: true, status: 'pending' | 'approved', comment } (pending is only echoed to its author)
|
|
357
|
+
// → { success: false, errors: [{ field: 'author.name' | 'author.email' | 'author.url' | 'body' | 'parentId' | 'captcha', message }] }
|
|
358
|
+
// 403 (thread closed) / 404 / 429 reject with ContentrainError
|
|
326
359
|
```
|
|
327
360
|
|
|
328
361
|
### Conversation API
|
|
@@ -398,6 +431,55 @@ Behavior:
|
|
|
398
431
|
| Caching | In-memory (embedded) | ETag-based HTTP cache + optional bundle preload |
|
|
399
432
|
| Use case | SSG, build-time | SSR, client-side, serverless |
|
|
400
433
|
|
|
434
|
+
## 🚀 Astro Content Layer
|
|
435
|
+
|
|
436
|
+
`contentrain generate` gives application code a typed client. Astro would
|
|
437
|
+
rather own the content itself, so pages can use `getCollection()`, `getEntry()`,
|
|
438
|
+
`reference()` and render documents with `<Content />`. `@contentrain/query/astro`
|
|
439
|
+
reads the same `.contentrain` files the generator reads — one project, one
|
|
440
|
+
answer about what its content is.
|
|
441
|
+
|
|
442
|
+
```ts
|
|
443
|
+
// src/content.config.ts
|
|
444
|
+
import { defineCollection, z } from 'astro:content'
|
|
445
|
+
import { contentrainLoader } from '@contentrain/query/astro'
|
|
446
|
+
|
|
447
|
+
export const collections = {
|
|
448
|
+
posts: defineCollection({
|
|
449
|
+
loader: contentrainLoader({ model: 'blog-post', locale: 'en' }),
|
|
450
|
+
schema: z.object({ title: z.string(), publishedAt: z.string() }).passthrough(),
|
|
451
|
+
}),
|
|
452
|
+
articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),
|
|
453
|
+
}
|
|
454
|
+
```
|
|
455
|
+
|
|
456
|
+
```astro
|
|
457
|
+
---
|
|
458
|
+
import { getCollection, getEntry, render } from 'astro:content'
|
|
459
|
+
const posts = await getCollection('posts')
|
|
460
|
+
const article = await getEntry('articles', 'en/welcome-post')
|
|
461
|
+
const { Content } = await render(article)
|
|
462
|
+
---
|
|
463
|
+
<ul>{posts.map((p) => <li>{p.data.title}</li>)}</ul>
|
|
464
|
+
<Content />
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
| Model kind | Becomes |
|
|
468
|
+
|---|---|
|
|
469
|
+
| `collection` | One entry per record; `data.id` is the entry id |
|
|
470
|
+
| `document` | One entry per file — frontmatter in `data`, markdown in `body`, rendered so `<Content />` works |
|
|
471
|
+
| `dictionary` | One entry per key (`{ key, value }`) — a key is a semantic address, and a single blob would make `getEntry()` useless on it |
|
|
472
|
+
| `singleton` | One entry, id `<model>` (or the locale) |
|
|
473
|
+
|
|
474
|
+
`locale` picks one language; omit it on an i18n model and every supported locale
|
|
475
|
+
loads with ids prefixed (`en/my-post`) — Astro ids are unique per collection, so
|
|
476
|
+
without the prefix one language would overwrite the other. `root` points at the
|
|
477
|
+
project holding `.contentrain` (default: `process.cwd()`).
|
|
478
|
+
|
|
479
|
+
Astro is not a dependency, not even a peer: the loader is structurally what
|
|
480
|
+
`defineCollection` accepts, so any Astro 5 version works and non-Astro consumers
|
|
481
|
+
of this package carry nothing extra.
|
|
482
|
+
|
|
401
483
|
## CommonJS Usage
|
|
402
484
|
|
|
403
485
|
Generated clients support CommonJS through `init()`:
|
|
@@ -450,6 +532,10 @@ CDN transport:
|
|
|
450
532
|
|
|
451
533
|
- `@contentrain/query/cdn` — CDN client with `HttpTransport`, async query classes, `MediaAccessor`, `FormsClient`, `ConversationClient`
|
|
452
534
|
|
|
535
|
+
Astro content layer:
|
|
536
|
+
|
|
537
|
+
- `@contentrain/query/astro` — `contentrainLoader()` for `defineCollection({ loader })`
|
|
538
|
+
|
|
453
539
|
## 🧠 Design Constraints
|
|
454
540
|
|
|
455
541
|
This package intentionally:
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
const require_frontmatter = require("../frontmatter-DSFLOTwS.cjs");
|
|
3
|
+
let node_path = require("node:path");
|
|
4
|
+
let node_url = require("node:url");
|
|
5
|
+
//#region src/astro/loader.ts
|
|
6
|
+
/**
|
|
7
|
+
* A loader for one Contentrain model.
|
|
8
|
+
*
|
|
9
|
+
* ```ts
|
|
10
|
+
* // src/content.config.ts
|
|
11
|
+
* import { defineCollection } from 'astro:content'
|
|
12
|
+
* import { contentrainLoader } from '@contentrain/query/astro'
|
|
13
|
+
*
|
|
14
|
+
* export const collections = {
|
|
15
|
+
* posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),
|
|
16
|
+
* articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),
|
|
17
|
+
* }
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* Each model kind maps to the shape Astro can actually use:
|
|
21
|
+
* collection → one entry per record · document → one entry per file, markdown
|
|
22
|
+
* in `body` (and rendered, so `<Content />` works) · dictionary → one entry per
|
|
23
|
+
* key, because a single blob would make `getEntry()` useless · singleton → one
|
|
24
|
+
* entry, id `<model>` or the locale.
|
|
25
|
+
*/
|
|
26
|
+
function contentrainLoader(options) {
|
|
27
|
+
const root = options.root ?? process.cwd();
|
|
28
|
+
let watching = false;
|
|
29
|
+
return {
|
|
30
|
+
name: `contentrain:${options.model}`,
|
|
31
|
+
async load(context) {
|
|
32
|
+
const manifest = await require_frontmatter.readProjectManifest(root);
|
|
33
|
+
const model = manifest.models.find((m) => m.id === options.model);
|
|
34
|
+
if (!model) {
|
|
35
|
+
const known = manifest.models.map((m) => m.id).join(", ") || "none";
|
|
36
|
+
throw new Error(`contentrainLoader: model "${options.model}" not found in ${(0, node_path.join)(root, ".contentrain", "models")}. Models here: ${known}.`);
|
|
37
|
+
}
|
|
38
|
+
const refs = manifest.contentFiles.filter((ref) => ref.modelId === model.id && (options.locale === void 0 || ref.locale === options.locale));
|
|
39
|
+
if (options.locale !== void 0 && refs.length === 0 && model.i18n) context.logger?.warn(`contentrainLoader: model "${model.id}" has no content for locale "${options.locale}" — collection is empty.`);
|
|
40
|
+
const prefixLocale = model.i18n === true && options.locale === void 0;
|
|
41
|
+
const entries = (await Promise.all(refs.map((ref) => entriesOf(ref, model, prefixLocale)))).flat();
|
|
42
|
+
const siteRoot = toPath(context.config?.root);
|
|
43
|
+
context.store.clear();
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const data = context.parseData ? await context.parseData({
|
|
46
|
+
id: entry.id,
|
|
47
|
+
data: entry.data,
|
|
48
|
+
filePath: entry.filePath
|
|
49
|
+
}) : entry.data;
|
|
50
|
+
const filePath = siteRoot === void 0 ? void 0 : (0, node_path.relative)(siteRoot, entry.filePath);
|
|
51
|
+
context.store.set({
|
|
52
|
+
id: entry.id,
|
|
53
|
+
data,
|
|
54
|
+
...filePath === void 0 || filePath.startsWith("..") || (0, node_path.isAbsolute)(filePath) ? {} : { filePath },
|
|
55
|
+
...entry.body === void 0 ? {} : { body: entry.body },
|
|
56
|
+
...context.generateDigest ? { digest: context.generateDigest(entry.data) } : {},
|
|
57
|
+
...entry.body !== void 0 && context.renderMarkdown ? { rendered: await context.renderMarkdown(entry.body) } : {}
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
context.logger?.info(`Loaded ${entries.length} ${model.kind} ${entries.length === 1 ? "entry" : "entries"} from ${model.id}`);
|
|
61
|
+
if (context.watcher && !watching) {
|
|
62
|
+
watching = true;
|
|
63
|
+
const paths = new Set(refs.map((ref) => ref.filePath));
|
|
64
|
+
for (const path of paths) context.watcher.add(path);
|
|
65
|
+
context.watcher.add((0, node_path.join)(root, ".contentrain", "models", `${model.id}.json`));
|
|
66
|
+
const reload = (path) => {
|
|
67
|
+
if (!paths.has(path) && !path.endsWith(`${model.id}.json`)) return;
|
|
68
|
+
this.load(context);
|
|
69
|
+
};
|
|
70
|
+
context.watcher.on("change", reload);
|
|
71
|
+
context.watcher.on("add", reload);
|
|
72
|
+
context.watcher.on("unlink", reload);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/** Astro hands `config.root` as a file URL; tests and other hosts may use a path. */
|
|
78
|
+
function toPath(root) {
|
|
79
|
+
if (root === void 0) return void 0;
|
|
80
|
+
if (typeof root !== "string") return (0, node_url.fileURLToPath)(root);
|
|
81
|
+
return root.startsWith("file:") ? (0, node_url.fileURLToPath)(root) : root;
|
|
82
|
+
}
|
|
83
|
+
async function entriesOf(ref, model, prefixLocale) {
|
|
84
|
+
const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : "";
|
|
85
|
+
const withLocale = (data) => ref.locale === null ? data : {
|
|
86
|
+
...data,
|
|
87
|
+
locale: ref.locale
|
|
88
|
+
};
|
|
89
|
+
switch (ref.kind) {
|
|
90
|
+
case "collection": {
|
|
91
|
+
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
92
|
+
if (!raw) return [];
|
|
93
|
+
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([id, fields]) => ({
|
|
94
|
+
id: `${prefix}${id}`,
|
|
95
|
+
data: withLocale({
|
|
96
|
+
id,
|
|
97
|
+
...fields
|
|
98
|
+
}),
|
|
99
|
+
filePath: ref.filePath
|
|
100
|
+
}));
|
|
101
|
+
}
|
|
102
|
+
case "singleton": {
|
|
103
|
+
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
104
|
+
if (!raw) return [];
|
|
105
|
+
return [{
|
|
106
|
+
id: prefix ? `${ref.locale}` : model.id,
|
|
107
|
+
data: withLocale(raw),
|
|
108
|
+
filePath: ref.filePath
|
|
109
|
+
}];
|
|
110
|
+
}
|
|
111
|
+
case "dictionary": {
|
|
112
|
+
const raw = await require_frontmatter.readJson(ref.filePath);
|
|
113
|
+
if (!raw) return [];
|
|
114
|
+
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
115
|
+
id: `${prefix}${key}`,
|
|
116
|
+
data: withLocale({
|
|
117
|
+
key,
|
|
118
|
+
value
|
|
119
|
+
}),
|
|
120
|
+
filePath: ref.filePath
|
|
121
|
+
}));
|
|
122
|
+
}
|
|
123
|
+
case "document": {
|
|
124
|
+
const text = await require_frontmatter.readText(ref.filePath);
|
|
125
|
+
if (text === null) return [];
|
|
126
|
+
const { frontmatter, body } = require_frontmatter.parseFrontmatter(text, require_frontmatter.stringLikeFieldKeys(model));
|
|
127
|
+
const slug = ref.slug ?? model.id;
|
|
128
|
+
return [{
|
|
129
|
+
id: `${prefix}${slug}`,
|
|
130
|
+
data: withLocale({
|
|
131
|
+
slug,
|
|
132
|
+
...frontmatter
|
|
133
|
+
}),
|
|
134
|
+
body,
|
|
135
|
+
filePath: ref.filePath
|
|
136
|
+
}];
|
|
137
|
+
}
|
|
138
|
+
default: return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
//#endregion
|
|
142
|
+
exports.contentrainLoader = contentrainLoader;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/astro/loader.d.ts
|
|
2
|
+
/** The slice of Astro's `LoaderContext` this loader uses. */
|
|
3
|
+
interface ContentrainLoaderContext {
|
|
4
|
+
store: {
|
|
5
|
+
clear: () => void;
|
|
6
|
+
set: (entry: {
|
|
7
|
+
id: string;
|
|
8
|
+
data: Record<string, unknown>;
|
|
9
|
+
body?: string;
|
|
10
|
+
digest?: string;
|
|
11
|
+
rendered?: {
|
|
12
|
+
html: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
filePath?: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
};
|
|
18
|
+
logger?: {
|
|
19
|
+
info: (msg: string) => void;
|
|
20
|
+
warn: (msg: string) => void;
|
|
21
|
+
};
|
|
22
|
+
/** Astro's resolved config; only `root` is read, to make file paths relative. */
|
|
23
|
+
config?: {
|
|
24
|
+
root?: URL | string;
|
|
25
|
+
};
|
|
26
|
+
generateDigest?: (data: unknown) => string;
|
|
27
|
+
parseData?: (entry: {
|
|
28
|
+
id: string;
|
|
29
|
+
data: Record<string, unknown>;
|
|
30
|
+
filePath?: string;
|
|
31
|
+
}) => Promise<Record<string, unknown>>;
|
|
32
|
+
renderMarkdown?: (content: string) => Promise<{
|
|
33
|
+
html: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}>;
|
|
36
|
+
watcher?: {
|
|
37
|
+
add: (path: string) => void;
|
|
38
|
+
on: (event: string, cb: (path: string) => void) => void;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Structurally Astro's `Loader`. */
|
|
42
|
+
interface ContentrainLoader {
|
|
43
|
+
name: string;
|
|
44
|
+
load: (context: ContentrainLoaderContext) => Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
interface ContentrainLoaderOptions {
|
|
47
|
+
/** Model id — the `id` in `.contentrain/models/<id>.json`. */
|
|
48
|
+
model: string;
|
|
49
|
+
/** Project root holding `.contentrain`. Default: `process.cwd()`. */
|
|
50
|
+
root?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Load only this locale. Omitted on an i18n model, every supported locale is
|
|
53
|
+
* loaded and entry ids are prefixed (`en/my-post`) so the two languages of one
|
|
54
|
+
* post do not collide — Astro ids are unique per collection.
|
|
55
|
+
*/
|
|
56
|
+
locale?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A loader for one Contentrain model.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* // src/content.config.ts
|
|
63
|
+
* import { defineCollection } from 'astro:content'
|
|
64
|
+
* import { contentrainLoader } from '@contentrain/query/astro'
|
|
65
|
+
*
|
|
66
|
+
* export const collections = {
|
|
67
|
+
* posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),
|
|
68
|
+
* articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* Each model kind maps to the shape Astro can actually use:
|
|
73
|
+
* collection → one entry per record · document → one entry per file, markdown
|
|
74
|
+
* in `body` (and rendered, so `<Content />` works) · dictionary → one entry per
|
|
75
|
+
* key, because a single blob would make `getEntry()` useless · singleton → one
|
|
76
|
+
* entry, id `<model>` or the locale.
|
|
77
|
+
*/
|
|
78
|
+
declare function contentrainLoader(options: ContentrainLoaderOptions): ContentrainLoader;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { type ContentrainLoader, type ContentrainLoaderContext, type ContentrainLoaderOptions, contentrainLoader };
|
|
81
|
+
//# sourceMappingURL=index.d.cts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.cts","names":[],"sources":["../../src/astro/loader.ts"],"mappings":";;UAsBiB,wBAAA;EACf,KAAA;IACE,KAAA;IACA,GAAA,GAAM,KAAA;MACJ,EAAA;MACA,IAAA,EAAM,MAAA;MACN,IAAA;MACA,MAAA;MACA,QAAA;QAAa,IAAA;QAAc,QAAA,GAAW,MAAA;MAAA;MACtC,QAAA;IAAA;EAAA;EAGJ,MAAA;IAAW,IAAA,GAAO,GAAA;IAAsB,IAAA,GAAO,GAAA;EAAA;EAN3C;EAQJ,MAAA;IAAW,IAAA,GAAO,GAAA;EAAA;EAClB,cAAA,IAAkB,IAAA;EAClB,SAAA,IAAa,KAAA;IAAS,EAAA;IAAY,IAAA,EAAM,MAAA;IAAyB,QAAA;EAAA,MAAwB,OAAA,CAAQ,MAAA;EACjG,cAAA,IAAkB,OAAA,aAAoB,OAAA;IAAU,IAAA;IAAc,QAAA,GAAW,MAAA;EAAA;EACzE,OAAA;IAAY,GAAA,GAAM,IAAA;IAAuB,EAAA,GAAK,KAAA,UAAe,EAAA,GAAK,IAAA;EAAA;AAAA;;UAInD,iBAAA;EACf,IAAA;EACA,IAAA,GAAO,OAAA,EAAS,wBAAA,KAA6B,OAAA;AAAA;AAAA,UAG9B,wBAAA;EAXkF;EAajG,KAAA;EAZkB;EAclB,IAAA;EAdgD;;;;;EAoBhD,MAAA;AAAA;;;;;;AAfF;;;;;;;;;;;AAKA;;;;iBAwCgB,iBAAA,CAAkB,OAAA,EAAS,wBAAA,GAA2B,iBAAA"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
//#region src/astro/loader.d.ts
|
|
2
|
+
/** The slice of Astro's `LoaderContext` this loader uses. */
|
|
3
|
+
interface ContentrainLoaderContext {
|
|
4
|
+
store: {
|
|
5
|
+
clear: () => void;
|
|
6
|
+
set: (entry: {
|
|
7
|
+
id: string;
|
|
8
|
+
data: Record<string, unknown>;
|
|
9
|
+
body?: string;
|
|
10
|
+
digest?: string;
|
|
11
|
+
rendered?: {
|
|
12
|
+
html: string;
|
|
13
|
+
metadata?: Record<string, unknown>;
|
|
14
|
+
};
|
|
15
|
+
filePath?: string;
|
|
16
|
+
}) => void;
|
|
17
|
+
};
|
|
18
|
+
logger?: {
|
|
19
|
+
info: (msg: string) => void;
|
|
20
|
+
warn: (msg: string) => void;
|
|
21
|
+
};
|
|
22
|
+
/** Astro's resolved config; only `root` is read, to make file paths relative. */
|
|
23
|
+
config?: {
|
|
24
|
+
root?: URL | string;
|
|
25
|
+
};
|
|
26
|
+
generateDigest?: (data: unknown) => string;
|
|
27
|
+
parseData?: (entry: {
|
|
28
|
+
id: string;
|
|
29
|
+
data: Record<string, unknown>;
|
|
30
|
+
filePath?: string;
|
|
31
|
+
}) => Promise<Record<string, unknown>>;
|
|
32
|
+
renderMarkdown?: (content: string) => Promise<{
|
|
33
|
+
html: string;
|
|
34
|
+
metadata?: Record<string, unknown>;
|
|
35
|
+
}>;
|
|
36
|
+
watcher?: {
|
|
37
|
+
add: (path: string) => void;
|
|
38
|
+
on: (event: string, cb: (path: string) => void) => void;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** Structurally Astro's `Loader`. */
|
|
42
|
+
interface ContentrainLoader {
|
|
43
|
+
name: string;
|
|
44
|
+
load: (context: ContentrainLoaderContext) => Promise<void>;
|
|
45
|
+
}
|
|
46
|
+
interface ContentrainLoaderOptions {
|
|
47
|
+
/** Model id — the `id` in `.contentrain/models/<id>.json`. */
|
|
48
|
+
model: string;
|
|
49
|
+
/** Project root holding `.contentrain`. Default: `process.cwd()`. */
|
|
50
|
+
root?: string;
|
|
51
|
+
/**
|
|
52
|
+
* Load only this locale. Omitted on an i18n model, every supported locale is
|
|
53
|
+
* loaded and entry ids are prefixed (`en/my-post`) so the two languages of one
|
|
54
|
+
* post do not collide — Astro ids are unique per collection.
|
|
55
|
+
*/
|
|
56
|
+
locale?: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A loader for one Contentrain model.
|
|
60
|
+
*
|
|
61
|
+
* ```ts
|
|
62
|
+
* // src/content.config.ts
|
|
63
|
+
* import { defineCollection } from 'astro:content'
|
|
64
|
+
* import { contentrainLoader } from '@contentrain/query/astro'
|
|
65
|
+
*
|
|
66
|
+
* export const collections = {
|
|
67
|
+
* posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),
|
|
68
|
+
* articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),
|
|
69
|
+
* }
|
|
70
|
+
* ```
|
|
71
|
+
*
|
|
72
|
+
* Each model kind maps to the shape Astro can actually use:
|
|
73
|
+
* collection → one entry per record · document → one entry per file, markdown
|
|
74
|
+
* in `body` (and rendered, so `<Content />` works) · dictionary → one entry per
|
|
75
|
+
* key, because a single blob would make `getEntry()` useless · singleton → one
|
|
76
|
+
* entry, id `<model>` or the locale.
|
|
77
|
+
*/
|
|
78
|
+
declare function contentrainLoader(options: ContentrainLoaderOptions): ContentrainLoader;
|
|
79
|
+
//#endregion
|
|
80
|
+
export { type ContentrainLoader, type ContentrainLoaderContext, type ContentrainLoaderOptions, contentrainLoader };
|
|
81
|
+
//# sourceMappingURL=index.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../../src/astro/loader.ts"],"mappings":";;UAsBiB,wBAAA;EACf,KAAA;IACE,KAAA;IACA,GAAA,GAAM,KAAA;MACJ,EAAA;MACA,IAAA,EAAM,MAAA;MACN,IAAA;MACA,MAAA;MACA,QAAA;QAAa,IAAA;QAAc,QAAA,GAAW,MAAA;MAAA;MACtC,QAAA;IAAA;EAAA;EAGJ,MAAA;IAAW,IAAA,GAAO,GAAA;IAAsB,IAAA,GAAO,GAAA;EAAA;EAN3C;EAQJ,MAAA;IAAW,IAAA,GAAO,GAAA;EAAA;EAClB,cAAA,IAAkB,IAAA;EAClB,SAAA,IAAa,KAAA;IAAS,EAAA;IAAY,IAAA,EAAM,MAAA;IAAyB,QAAA;EAAA,MAAwB,OAAA,CAAQ,MAAA;EACjG,cAAA,IAAkB,OAAA,aAAoB,OAAA;IAAU,IAAA;IAAc,QAAA,GAAW,MAAA;EAAA;EACzE,OAAA;IAAY,GAAA,GAAM,IAAA;IAAuB,EAAA,GAAK,KAAA,UAAe,EAAA,GAAK,IAAA;EAAA;AAAA;;UAInD,iBAAA;EACf,IAAA;EACA,IAAA,GAAO,OAAA,EAAS,wBAAA,KAA6B,OAAA;AAAA;AAAA,UAG9B,wBAAA;EAXkF;EAajG,KAAA;EAZkB;EAclB,IAAA;EAdgD;;;;;EAoBhD,MAAA;AAAA;;;;;;AAfF;;;;;;;;;;;AAKA;;;;iBAwCgB,iBAAA,CAAkB,OAAA,EAAS,wBAAA,GAA2B,iBAAA"}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { a as readJson, n as stringLikeFieldKeys, o as readText, r as readProjectManifest, t as parseFrontmatter } from "../frontmatter-CVjPyhOq.mjs";
|
|
2
|
+
import { isAbsolute, join, relative } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
//#region src/astro/loader.ts
|
|
5
|
+
/**
|
|
6
|
+
* A loader for one Contentrain model.
|
|
7
|
+
*
|
|
8
|
+
* ```ts
|
|
9
|
+
* // src/content.config.ts
|
|
10
|
+
* import { defineCollection } from 'astro:content'
|
|
11
|
+
* import { contentrainLoader } from '@contentrain/query/astro'
|
|
12
|
+
*
|
|
13
|
+
* export const collections = {
|
|
14
|
+
* posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),
|
|
15
|
+
* articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),
|
|
16
|
+
* }
|
|
17
|
+
* ```
|
|
18
|
+
*
|
|
19
|
+
* Each model kind maps to the shape Astro can actually use:
|
|
20
|
+
* collection → one entry per record · document → one entry per file, markdown
|
|
21
|
+
* in `body` (and rendered, so `<Content />` works) · dictionary → one entry per
|
|
22
|
+
* key, because a single blob would make `getEntry()` useless · singleton → one
|
|
23
|
+
* entry, id `<model>` or the locale.
|
|
24
|
+
*/
|
|
25
|
+
function contentrainLoader(options) {
|
|
26
|
+
const root = options.root ?? process.cwd();
|
|
27
|
+
let watching = false;
|
|
28
|
+
return {
|
|
29
|
+
name: `contentrain:${options.model}`,
|
|
30
|
+
async load(context) {
|
|
31
|
+
const manifest = await readProjectManifest(root);
|
|
32
|
+
const model = manifest.models.find((m) => m.id === options.model);
|
|
33
|
+
if (!model) {
|
|
34
|
+
const known = manifest.models.map((m) => m.id).join(", ") || "none";
|
|
35
|
+
throw new Error(`contentrainLoader: model "${options.model}" not found in ${join(root, ".contentrain", "models")}. Models here: ${known}.`);
|
|
36
|
+
}
|
|
37
|
+
const refs = manifest.contentFiles.filter((ref) => ref.modelId === model.id && (options.locale === void 0 || ref.locale === options.locale));
|
|
38
|
+
if (options.locale !== void 0 && refs.length === 0 && model.i18n) context.logger?.warn(`contentrainLoader: model "${model.id}" has no content for locale "${options.locale}" — collection is empty.`);
|
|
39
|
+
const prefixLocale = model.i18n === true && options.locale === void 0;
|
|
40
|
+
const entries = (await Promise.all(refs.map((ref) => entriesOf(ref, model, prefixLocale)))).flat();
|
|
41
|
+
const siteRoot = toPath(context.config?.root);
|
|
42
|
+
context.store.clear();
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const data = context.parseData ? await context.parseData({
|
|
45
|
+
id: entry.id,
|
|
46
|
+
data: entry.data,
|
|
47
|
+
filePath: entry.filePath
|
|
48
|
+
}) : entry.data;
|
|
49
|
+
const filePath = siteRoot === void 0 ? void 0 : relative(siteRoot, entry.filePath);
|
|
50
|
+
context.store.set({
|
|
51
|
+
id: entry.id,
|
|
52
|
+
data,
|
|
53
|
+
...filePath === void 0 || filePath.startsWith("..") || isAbsolute(filePath) ? {} : { filePath },
|
|
54
|
+
...entry.body === void 0 ? {} : { body: entry.body },
|
|
55
|
+
...context.generateDigest ? { digest: context.generateDigest(entry.data) } : {},
|
|
56
|
+
...entry.body !== void 0 && context.renderMarkdown ? { rendered: await context.renderMarkdown(entry.body) } : {}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
context.logger?.info(`Loaded ${entries.length} ${model.kind} ${entries.length === 1 ? "entry" : "entries"} from ${model.id}`);
|
|
60
|
+
if (context.watcher && !watching) {
|
|
61
|
+
watching = true;
|
|
62
|
+
const paths = new Set(refs.map((ref) => ref.filePath));
|
|
63
|
+
for (const path of paths) context.watcher.add(path);
|
|
64
|
+
context.watcher.add(join(root, ".contentrain", "models", `${model.id}.json`));
|
|
65
|
+
const reload = (path) => {
|
|
66
|
+
if (!paths.has(path) && !path.endsWith(`${model.id}.json`)) return;
|
|
67
|
+
this.load(context);
|
|
68
|
+
};
|
|
69
|
+
context.watcher.on("change", reload);
|
|
70
|
+
context.watcher.on("add", reload);
|
|
71
|
+
context.watcher.on("unlink", reload);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
/** Astro hands `config.root` as a file URL; tests and other hosts may use a path. */
|
|
77
|
+
function toPath(root) {
|
|
78
|
+
if (root === void 0) return void 0;
|
|
79
|
+
if (typeof root !== "string") return fileURLToPath(root);
|
|
80
|
+
return root.startsWith("file:") ? fileURLToPath(root) : root;
|
|
81
|
+
}
|
|
82
|
+
async function entriesOf(ref, model, prefixLocale) {
|
|
83
|
+
const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : "";
|
|
84
|
+
const withLocale = (data) => ref.locale === null ? data : {
|
|
85
|
+
...data,
|
|
86
|
+
locale: ref.locale
|
|
87
|
+
};
|
|
88
|
+
switch (ref.kind) {
|
|
89
|
+
case "collection": {
|
|
90
|
+
const raw = await readJson(ref.filePath);
|
|
91
|
+
if (!raw) return [];
|
|
92
|
+
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([id, fields]) => ({
|
|
93
|
+
id: `${prefix}${id}`,
|
|
94
|
+
data: withLocale({
|
|
95
|
+
id,
|
|
96
|
+
...fields
|
|
97
|
+
}),
|
|
98
|
+
filePath: ref.filePath
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
101
|
+
case "singleton": {
|
|
102
|
+
const raw = await readJson(ref.filePath);
|
|
103
|
+
if (!raw) return [];
|
|
104
|
+
return [{
|
|
105
|
+
id: prefix ? `${ref.locale}` : model.id,
|
|
106
|
+
data: withLocale(raw),
|
|
107
|
+
filePath: ref.filePath
|
|
108
|
+
}];
|
|
109
|
+
}
|
|
110
|
+
case "dictionary": {
|
|
111
|
+
const raw = await readJson(ref.filePath);
|
|
112
|
+
if (!raw) return [];
|
|
113
|
+
return Object.entries(raw).toSorted(([a], [b]) => a.localeCompare(b, "en")).map(([key, value]) => ({
|
|
114
|
+
id: `${prefix}${key}`,
|
|
115
|
+
data: withLocale({
|
|
116
|
+
key,
|
|
117
|
+
value
|
|
118
|
+
}),
|
|
119
|
+
filePath: ref.filePath
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
case "document": {
|
|
123
|
+
const text = await readText(ref.filePath);
|
|
124
|
+
if (text === null) return [];
|
|
125
|
+
const { frontmatter, body } = parseFrontmatter(text, stringLikeFieldKeys(model));
|
|
126
|
+
const slug = ref.slug ?? model.id;
|
|
127
|
+
return [{
|
|
128
|
+
id: `${prefix}${slug}`,
|
|
129
|
+
data: withLocale({
|
|
130
|
+
slug,
|
|
131
|
+
...frontmatter
|
|
132
|
+
}),
|
|
133
|
+
body,
|
|
134
|
+
filePath: ref.filePath
|
|
135
|
+
}];
|
|
136
|
+
}
|
|
137
|
+
default: return [];
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//#endregion
|
|
141
|
+
export { contentrainLoader };
|
|
142
|
+
|
|
143
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/astro/loader.ts"],"sourcesContent":["// Astro content-layer loader for a `.contentrain` project.\n//\n// `contentrain generate` produces a typed client for application code; Astro\n// wants its content in the content layer instead, so pages can query it with\n// `getCollection()` and render documents with `<Content />`. This loader reads\n// the same `.contentrain` files the generator reads — through the same manifest\n// — so a project never has two answers about what its content is.\n//\n// Astro is NOT a dependency here, not even a peer: the returned object is\n// structurally what `defineCollection({ loader })` accepts, and the context is\n// typed by the small surface this loader actually uses. That keeps\n// @contentrain/query dependency-free and usable from any Astro 5 version.\n\nimport type { ModelDefinition } from '@contentrain/types'\nimport { isAbsolute, join, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport type { ContentFileRef } from '../generator/config-reader.js'\nimport { readProjectManifest } from '../generator/config-reader.js'\nimport { readJson, readText } from '../generator/utils.js'\nimport { parseFrontmatter, stringLikeFieldKeys } from '../shared/frontmatter.js'\n\n/** The slice of Astro's `LoaderContext` this loader uses. */\nexport interface ContentrainLoaderContext {\n store: {\n clear: () => void\n set: (entry: {\n id: string\n data: Record<string, unknown>\n body?: string\n digest?: string\n rendered?: { html: string, metadata?: Record<string, unknown> }\n filePath?: string\n }) => void\n }\n logger?: { info: (msg: string) => void, warn: (msg: string) => void }\n /** Astro's resolved config; only `root` is read, to make file paths relative. */\n config?: { root?: URL | string }\n generateDigest?: (data: unknown) => string\n parseData?: (entry: { id: string, data: Record<string, unknown>, filePath?: string }) => Promise<Record<string, unknown>>\n renderMarkdown?: (content: string) => Promise<{ html: string, metadata?: Record<string, unknown> }>\n watcher?: { add: (path: string) => void, on: (event: string, cb: (path: string) => void) => void }\n}\n\n/** Structurally Astro's `Loader`. */\nexport interface ContentrainLoader {\n name: string\n load: (context: ContentrainLoaderContext) => Promise<void>\n}\n\nexport interface ContentrainLoaderOptions {\n /** Model id — the `id` in `.contentrain/models/<id>.json`. */\n model: string\n /** Project root holding `.contentrain`. Default: `process.cwd()`. */\n root?: string\n /**\n * Load only this locale. Omitted on an i18n model, every supported locale is\n * loaded and entry ids are prefixed (`en/my-post`) so the two languages of one\n * post do not collide — Astro ids are unique per collection.\n */\n locale?: string\n}\n\ninterface Entry {\n id: string\n data: Record<string, unknown>\n body?: string\n filePath: string\n}\n\n/**\n * A loader for one Contentrain model.\n *\n * ```ts\n * // src/content.config.ts\n * import { defineCollection } from 'astro:content'\n * import { contentrainLoader } from '@contentrain/query/astro'\n *\n * export const collections = {\n * posts: defineCollection({ loader: contentrainLoader({ model: 'blog-post', locale: 'en' }) }),\n * articles: defineCollection({ loader: contentrainLoader({ model: 'blog-article' }) }),\n * }\n * ```\n *\n * Each model kind maps to the shape Astro can actually use:\n * collection → one entry per record · document → one entry per file, markdown\n * in `body` (and rendered, so `<Content />` works) · dictionary → one entry per\n * key, because a single blob would make `getEntry()` useless · singleton → one\n * entry, id `<model>` or the locale.\n */\nexport function contentrainLoader(options: ContentrainLoaderOptions): ContentrainLoader {\n const root = options.root ?? process.cwd()\n let watching = false\n\n return {\n name: `contentrain:${options.model}`,\n async load(context) {\n const manifest = await readProjectManifest(root)\n const model = manifest.models.find(m => m.id === options.model)\n if (!model) {\n const known = manifest.models.map(m => m.id).join(', ') || 'none'\n throw new Error(\n `contentrainLoader: model \"${options.model}\" not found in ${join(root, '.contentrain', 'models')}. Models here: ${known}.`,\n )\n }\n\n const refs = manifest.contentFiles.filter(ref =>\n ref.modelId === model.id && (options.locale === undefined || ref.locale === options.locale),\n )\n if (options.locale !== undefined && refs.length === 0 && model.i18n) {\n context.logger?.warn(\n `contentrainLoader: model \"${model.id}\" has no content for locale \"${options.locale}\" — collection is empty.`,\n )\n }\n // Prefix ids only when one collection really holds several languages;\n // a single-locale load keeps the natural id so routes stay clean.\n const prefixLocale = model.i18n === true && options.locale === undefined\n\n const perFile = await Promise.all(refs.map(ref => entriesOf(ref, model, prefixLocale)))\n const entries = perFile.flat()\n // Astro rejects an absolute filePath (\"must be relative to the site\n // root\"), and a `.contentrain` outside the Astro root has no relative\n // form it accepts — there we send none rather than a path it refuses.\n const siteRoot = toPath(context.config?.root)\n\n context.store.clear()\n for (const entry of entries) {\n const data = context.parseData\n ? await context.parseData({ id: entry.id, data: entry.data, filePath: entry.filePath })\n : entry.data\n const filePath = siteRoot === undefined ? undefined : relative(siteRoot, entry.filePath)\n context.store.set({\n id: entry.id,\n data,\n ...(filePath === undefined || filePath.startsWith('..') || isAbsolute(filePath) ? {} : { filePath }),\n ...(entry.body === undefined ? {} : { body: entry.body }),\n ...(context.generateDigest ? { digest: context.generateDigest(entry.data) } : {}),\n ...(entry.body !== undefined && context.renderMarkdown\n ? { rendered: await context.renderMarkdown(entry.body) }\n : {}),\n })\n }\n context.logger?.info(`Loaded ${entries.length} ${model.kind} ${entries.length === 1 ? 'entry' : 'entries'} from ${model.id}`)\n\n // Dev server: reload when the files behind this collection change. The\n // listener is registered once — `load` runs again on every change, and a\n // listener per run would multiply the work on every save.\n if (context.watcher && !watching) {\n watching = true\n const paths = new Set(refs.map(ref => ref.filePath))\n for (const path of paths) context.watcher.add(path)\n context.watcher.add(join(root, '.contentrain', 'models', `${model.id}.json`))\n const reload = (path: string) => {\n if (!paths.has(path) && !path.endsWith(`${model.id}.json`)) return\n void this.load(context)\n }\n context.watcher.on('change', reload)\n context.watcher.on('add', reload)\n context.watcher.on('unlink', reload)\n }\n },\n }\n}\n\n/** Astro hands `config.root` as a file URL; tests and other hosts may use a path. */\nfunction toPath(root: URL | string | undefined): string | undefined {\n if (root === undefined) return undefined\n if (typeof root !== 'string') return fileURLToPath(root)\n return root.startsWith('file:') ? fileURLToPath(root) : root\n}\n\nasync function entriesOf(ref: ContentFileRef, model: ModelDefinition, prefixLocale: boolean): Promise<Entry[]> {\n const prefix = prefixLocale && ref.locale ? `${ref.locale}/` : ''\n const withLocale = (data: Record<string, unknown>): Record<string, unknown> =>\n ref.locale === null ? data : { ...data, locale: ref.locale }\n\n switch (ref.kind) {\n case 'collection': {\n const raw = await readJson<Record<string, Record<string, unknown>>>(ref.filePath)\n if (!raw) return []\n return Object.entries(raw)\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([id, fields]) => ({\n id: `${prefix}${id}`,\n data: withLocale({ id, ...fields }),\n filePath: ref.filePath,\n }))\n }\n\n case 'singleton': {\n const raw = await readJson<Record<string, unknown>>(ref.filePath)\n if (!raw) return []\n return [{\n id: prefix ? `${ref.locale}` : model.id,\n data: withLocale(raw),\n filePath: ref.filePath,\n }]\n }\n\n case 'dictionary': {\n // One entry per key: a dictionary key is a semantic address, and a single\n // blob entry would make getEntry() and reference() useless on it.\n const raw = await readJson<Record<string, string>>(ref.filePath)\n if (!raw) return []\n return Object.entries(raw)\n .toSorted(([a], [b]) => a.localeCompare(b, 'en'))\n .map(([key, value]) => ({\n id: `${prefix}${key}`,\n data: withLocale({ key, value }),\n filePath: ref.filePath,\n }))\n }\n\n case 'document': {\n const text = await readText(ref.filePath)\n if (text === null) return []\n const { frontmatter, body } = parseFrontmatter(text, stringLikeFieldKeys(model))\n const slug = ref.slug ?? model.id\n return [{\n id: `${prefix}${slug}`,\n data: withLocale({ slug, ...frontmatter }),\n body,\n filePath: ref.filePath,\n }]\n }\n\n default:\n return []\n }\n}\n"],"mappings":"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"}
|
package/dist/cdn/index.cjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
-
const require_cdn = require("../cdn-
|
|
2
|
+
const require_cdn = require("../cdn-DJw9oksO.cjs");
|
|
3
3
|
exports.CdnCollectionQuery = require_cdn.CdnCollectionQuery;
|
|
4
4
|
exports.CdnDictionaryAccessor = require_cdn.CdnDictionaryAccessor;
|
|
5
5
|
exports.CdnDocumentQuery = require_cdn.CdnDocumentQuery;
|
|
6
6
|
exports.CdnSingletonAccessor = require_cdn.CdnSingletonAccessor;
|
|
7
|
+
exports.CommentsClient = require_cdn.CommentsClient;
|
|
7
8
|
exports.ContentrainError = require_cdn.ContentrainError;
|
|
8
9
|
exports.ConversationClient = require_cdn.ConversationClient;
|
|
9
10
|
exports.FormsClient = require_cdn.FormsClient;
|
package/dist/cdn/index.d.cts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
export { CdnCollectionQuery, CdnDictionaryAccessor, CdnDocumentQuery, CdnSingletonAccessor, CollectionDataSource, ContentrainCDNClient, ContentrainCDNConfig, ContentrainError, ConversationClient, ConversationClientConfig, ConversationContext, ConversationHistory, ConversationMessage, ConversationResponse, ConversationSendOptions, ConversationToolResult, ConversationUsage, DictionaryDataSource, DocumentDataSource, DocumentIndexEntry, FormConfig, FormFieldConfig, FormSubmitResult, FormsClient, FormsClientConfig, HttpTransport, MediaAccessor, MediaAsset, MediaAssetMeta, MediaManifest, SingletonDataSource, TransportConfig, createContentrain };
|
|
1
|
+
import { A as FormsClient, C as CommentsClientConfig, D as FormFieldError, E as FormFieldConfig, F as MediaManifest, G as TransportConfig, I as CdnDocumentQuery, J as DocumentDataSource, K as CollectionDataSource, L as CdnDictionaryAccessor, M as MediaAccessor, N as MediaAsset, O as FormSubmitOptions, P as MediaAssetMeta, R as CdnSingletonAccessor, S as CommentsClient, T as FormConfig, W as HttpTransport, X as SingletonDataSource, Y as DocumentIndexEntry, _ as CommentSubmitResult, a as ConversationClient, b as CommentThreadQuery, c as ConversationHistory, d as ConversationSendOptions, f as ConversationToolResult, g as CommentSubmission, h as CommentFieldError, i as ContentrainError, j as FormsClientConfig, k as FormSubmitResult, l as ConversationMessage, m as CommentAuthor, n as ContentrainCDNConfig, o as ConversationClientConfig, p as ConversationUsage, q as DictionaryDataSource, r as createContentrain, s as ConversationContext, t as ContentrainCDNClient, u as ConversationResponse, v as CommentThread, w as PublicComment, x as CommentType, y as CommentThreadConfig, z as CdnCollectionQuery } from "../index-BFurv7WX.cjs";
|
|
2
|
+
export { CdnCollectionQuery, CdnDictionaryAccessor, CdnDocumentQuery, CdnSingletonAccessor, CollectionDataSource, CommentAuthor, CommentFieldError, CommentSubmission, CommentSubmitResult, CommentThread, CommentThreadConfig, CommentThreadQuery, CommentType, CommentsClient, CommentsClientConfig, ContentrainCDNClient, ContentrainCDNConfig, ContentrainError, ConversationClient, ConversationClientConfig, ConversationContext, ConversationHistory, ConversationMessage, ConversationResponse, ConversationSendOptions, ConversationToolResult, ConversationUsage, DictionaryDataSource, DocumentDataSource, DocumentIndexEntry, FormConfig, FormFieldConfig, FormFieldError, FormSubmitOptions, FormSubmitResult, FormsClient, FormsClientConfig, HttpTransport, MediaAccessor, MediaAsset, MediaAssetMeta, MediaManifest, PublicComment, SingletonDataSource, TransportConfig, createContentrain };
|
package/dist/cdn/index.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { C as
|
|
2
|
-
export { CdnCollectionQuery, CdnDictionaryAccessor, CdnDocumentQuery, CdnSingletonAccessor, CollectionDataSource, ContentrainCDNClient, ContentrainCDNConfig, ContentrainError, ConversationClient, ConversationClientConfig, ConversationContext, ConversationHistory, ConversationMessage, ConversationResponse, ConversationSendOptions, ConversationToolResult, ConversationUsage, DictionaryDataSource, DocumentDataSource, DocumentIndexEntry, FormConfig, FormFieldConfig, FormSubmitResult, FormsClient, FormsClientConfig, HttpTransport, MediaAccessor, MediaAsset, MediaAssetMeta, MediaManifest, SingletonDataSource, TransportConfig, createContentrain };
|
|
1
|
+
import { A as FormsClient, C as CommentsClientConfig, D as FormFieldError, E as FormFieldConfig, F as MediaManifest, G as TransportConfig, I as CdnDocumentQuery, J as DocumentDataSource, K as CollectionDataSource, L as CdnDictionaryAccessor, M as MediaAccessor, N as MediaAsset, O as FormSubmitOptions, P as MediaAssetMeta, R as CdnSingletonAccessor, S as CommentsClient, T as FormConfig, W as HttpTransport, X as SingletonDataSource, Y as DocumentIndexEntry, _ as CommentSubmitResult, a as ConversationClient, b as CommentThreadQuery, c as ConversationHistory, d as ConversationSendOptions, f as ConversationToolResult, g as CommentSubmission, h as CommentFieldError, i as ContentrainError, j as FormsClientConfig, k as FormSubmitResult, l as ConversationMessage, m as CommentAuthor, n as ContentrainCDNConfig, o as ConversationClientConfig, p as ConversationUsage, q as DictionaryDataSource, r as createContentrain, s as ConversationContext, t as ContentrainCDNClient, u as ConversationResponse, v as CommentThread, w as PublicComment, x as CommentType, y as CommentThreadConfig, z as CdnCollectionQuery } from "../index-VXFz-DDN.mjs";
|
|
2
|
+
export { CdnCollectionQuery, CdnDictionaryAccessor, CdnDocumentQuery, CdnSingletonAccessor, CollectionDataSource, CommentAuthor, CommentFieldError, CommentSubmission, CommentSubmitResult, CommentThread, CommentThreadConfig, CommentThreadQuery, CommentType, CommentsClient, CommentsClientConfig, ContentrainCDNClient, ContentrainCDNConfig, ContentrainError, ConversationClient, ConversationClientConfig, ConversationContext, ConversationHistory, ConversationMessage, ConversationResponse, ConversationSendOptions, ConversationToolResult, ConversationUsage, DictionaryDataSource, DocumentDataSource, DocumentIndexEntry, FormConfig, FormFieldConfig, FormFieldError, FormSubmitOptions, FormSubmitResult, FormsClient, FormsClientConfig, HttpTransport, MediaAccessor, MediaAsset, MediaAssetMeta, MediaManifest, PublicComment, SingletonDataSource, TransportConfig, createContentrain };
|
package/dist/cdn/index.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
2
|
-
export { CdnCollectionQuery, CdnDictionaryAccessor, CdnDocumentQuery, CdnSingletonAccessor, ContentrainError, ConversationClient, FormsClient, HttpTransport, MediaAccessor, createContentrain };
|
|
1
|
+
import { a as MediaAccessor, c as CdnSingletonAccessor, d as ContentrainError, i as FormsClient, l as CdnCollectionQuery, n as ConversationClient, o as CdnDocumentQuery, r as CommentsClient, s as CdnDictionaryAccessor, t as createContentrain, u as HttpTransport } from "../cdn-C5yiVdfQ.mjs";
|
|
2
|
+
export { CdnCollectionQuery, CdnDictionaryAccessor, CdnDocumentQuery, CdnSingletonAccessor, CommentsClient, ContentrainError, ConversationClient, FormsClient, HttpTransport, MediaAccessor, createContentrain };
|