content-query 0.1.0 → 0.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.
Files changed (47) hide show
  1. package/README.md +23 -1
  2. package/dist/{build-DSyoRxkZ.d.mts → build-DfKd2bpf.d.mts} +6 -5
  3. package/dist/{build-C5ef0kkR.mjs → build-Dxz7P1ur.mjs} +68 -30
  4. package/dist/build.d.mts +1 -1
  5. package/dist/build.mjs +1 -2
  6. package/dist/cli.d.mts +1 -1
  7. package/dist/cli.mjs +3 -3
  8. package/dist/computed.d.mts +19 -13
  9. package/dist/computed.mjs +6 -5
  10. package/dist/{content-Crag1Uch.d.mts → content-G69dPPuo.d.mts} +26 -16
  11. package/dist/{content-Dj2fQCJ5.mjs → content-N1gYshjg.mjs} +203 -77
  12. package/dist/{descriptor-Xbo9u5eR.d.mts → descriptor-D9Th5hzk.d.mts} +1 -2
  13. package/dist/{diagnostics-Dol6ANtq.mjs → diagnostics-Cu94mZhl.mjs} +16 -15
  14. package/dist/{dist-BQGVLZyl.mjs → dist-C7_FOzY_.mjs} +21 -12
  15. package/dist/{glob-DJmebCMM.mjs → glob-Bv0dpvCQ.mjs} +3 -3
  16. package/dist/index.d.mts +34 -16
  17. package/dist/index.mjs +7 -4
  18. package/dist/internal/markdown.d.mts +3 -5
  19. package/dist/internal/markdown.mjs +1 -2
  20. package/dist/internal.d.mts +12 -12
  21. package/dist/internal.mjs +7 -7
  22. package/dist/loaders.d.mts +5 -6
  23. package/dist/loaders.mjs +6 -5
  24. package/dist/{markdown-CZBL4Ux_.mjs → markdown-CHvBBJNa.mjs} +2 -2
  25. package/dist/next.d.mts +3 -4
  26. package/dist/next.mjs +2 -2
  27. package/dist/{operators-BQpLfJlx.mjs → operators-UqWUhKQJ.mjs} +2 -2
  28. package/dist/query.d.mts +22 -23
  29. package/dist/query.mjs +1 -2
  30. package/dist/{relational-types-DLniKi5S.d.mts → relational-types-DgK3rLuw.d.mts} +62 -29
  31. package/dist/{chunk-CJs4Fogs.mjs → rolldown-runtime-Bg1v5YCA.mjs} +2 -2
  32. package/dist/runtime.d.mts +2 -2
  33. package/dist/runtime.mjs +3 -4
  34. package/dist/{schema-CnCbMosm.mjs → schema-C5Str-E8.mjs} +1 -1
  35. package/dist/schema.d.mts +0 -2
  36. package/dist/schema.mjs +1 -2
  37. package/dist/{types-BPh2cBPs.d.mts → types-C-kdMblM.d.mts} +27 -6
  38. package/dist/{types-hmDOw87q.d.mts → types-ChaNhj42.d.mts} +14 -6
  39. package/dist/{validation-BvvjSUFc.mjs → validation-D_XdE1pF.mjs} +2 -2
  40. package/dist/{values-B7lo_2_x.mjs → values-BbTV4QT-.mjs} +109 -108
  41. package/dist/vite.d.mts +3 -5
  42. package/dist/vite.mjs +5 -5
  43. package/package.json +6 -4
  44. /package/dist/{errors-C8lWyiy2.mjs → errors-C2iuBM0G.mjs} +0 -0
  45. /package/dist/{github-slugger-BCAkwEOL.mjs → github-slugger-BMgR_Q0I.mjs} +0 -0
  46. /package/dist/{server-only-CIE-FHx0.mjs → server-only-CIJUlyL-.mjs} +0 -0
  47. /package/dist/{types-DcvHRmxO.d.mts → types-Dh-Sydyw.d.mts} +0 -0
package/README.md CHANGED
@@ -9,7 +9,7 @@ Use it with your existing framework. Your application owns its routes, layouts,
9
9
  - **Typed schemas.** Validate metadata with bundled schema builders or any Standard Schema V1 provider, with inferred TypeScript types.
10
10
  - **Flexible sources.** Load local files, use Astro-compatible content loaders, or write your own loader.
11
11
  - **Queries and relationships.** Select fields, filter, sort, paginate, and load related entries with validated references.
12
- - **Markdown rendering.** Get HTML, headings, and syntax highlighting separately from metadata queries.
12
+ - **Markdown rendering.** Request HTML, headings, and syntax highlighting as query payloads, loaded only when a query asks for them.
13
13
  - **Framework integrations.** Use Vite, Next.js, or the standalone CLI, with content updates during development.
14
14
 
15
15
  ## Usage
@@ -54,10 +54,28 @@ const posts = await content
54
54
  .from("posts")
55
55
  .where((fields) => eq(fields.draft, false))
56
56
  .orderBy("title");
57
+
58
+ const post = await content.query.posts.findFirst({
59
+ where: { id: posts[0]!.id },
60
+ payloads: { _rendered: true },
61
+ });
62
+ const { html, headings } = post!._payloads._rendered;
57
63
  ```
58
64
 
59
65
  Add `.content-query/` to `.gitignore`. Follow the quick start for a complete walkthrough, or choose a framework example below.
60
66
 
67
+ ## Migrating to 0.2
68
+
69
+ Version 0.2.0 removes `content.readBody()` and `content.render()`. Request bodies and rendered output as payloads in the query that loads the entry:
70
+
71
+ | 0.1 | 0.2 |
72
+ | ------------------------------------- | -------------------------------------------------------------------------------------------------------------- |
73
+ | `await content.readBody("posts", id)` | `(await content.query.posts.findFirst({ where: { id }, payloads: { _body: true } }))?._payloads._body` |
74
+ | `await content.render("posts", id)` | `(await content.query.posts.findFirst({ where: { id }, payloads: { _rendered: true } }))?._payloads._rendered` |
75
+ | Fluent queries | Add `.withPayload("_rendered")` or `.withPayload("_body")` before the terminal. |
76
+
77
+ Built-in payload names start with an underscore: `_body` and `_rendered`. A query for a missing entry returns `undefined` instead of rejecting with `entry-not-found`, which no longer exists. JSON, YAML, and TOML sources and `file()` collections offer no payloads. Rebuild generated content after upgrading; 0.1 artifacts are rejected.
78
+
61
79
  ## Documentation
62
80
 
63
81
  - [Quick start](https://github.com/yangshun/content-query/blob/main/website/content/guides/quick-start.md)
@@ -67,4 +85,8 @@ Add `.content-query/` to `.gitignore`. Follow the quick start for a complete wal
67
85
 
68
86
  The Vite integration supports Vite 8, and the Next.js integration supports Next.js 16. Both integrations are optional; standalone builds and queries require neither framework.
69
87
 
88
+ Pass `contentQuery()` directly to Vite or Vite+'s `plugins` array. No `Plugin` type cast is needed; workspace consumers share the pinned toolchain versions in the dependency catalog.
89
+
90
+ Repository development checks package types and isolated consumers with [tsrs](https://github.com/oxc-project/tsrs). Run `vp run check` in the package directory for formatting, linting, and type checks; build tools use the shared TypeScript 6 compatibility package for its `6.0.3` JavaScript API. Native TypeScript `7.1.0-beta` is available at the repository root as `vp exec tsc`.
91
+
70
92
  Released under the [MIT license](https://github.com/yangshun/content-query/blob/main/LICENSE).
@@ -1,6 +1,5 @@
1
- import { n as CollectionRegistry } from "./types-BPh2cBPs.mjs";
2
- import { s as InvalidRelationships, t as Content } from "./content-Crag1Uch.mjs";
3
-
1
+ import { i as CollectionRegistry } from "./types-C-kdMblM.mjs";
2
+ import { s as InvalidRelationships, t as Content } from "./content-G69dPPuo.mjs";
4
3
  //#region src/preparation/session.d.ts
5
4
  type ContentSession<Registry extends CollectionRegistry = {}> = {
6
5
  content: Content<Registry>;
@@ -10,8 +9,10 @@ type ContentSession<Registry extends CollectionRegistry = {}> = {
10
9
  //#region src/preparation/build.d.ts
11
10
  /** Options shared by standalone content builds and host integrations. */
12
11
  type BuildContentOptions = {
13
- /** Exact config path; omission discovers one content-query.config.{ts,mts,cts,js,mjs,cjs} file. */config?: string;
14
- backend?: "in-memory" | "generated"; /** Generated backend output directory, relative to the application root; defaults to ./.content-query. */
12
+ /** Exact config path; omission discovers one content-query.config.{ts,mts,cts,js,mjs,cjs} file. */
13
+ config?: string;
14
+ backend?: "in-memory" | "generated";
15
+ /** Generated backend output directory, relative to the application root; defaults to ./.content-query. */
15
16
  outDir?: string;
16
17
  watch?: boolean;
17
18
  };
@@ -1,9 +1,9 @@
1
- import { a as builtinLoader, i as validate, n as normalizeSnapshot, o as builtinPath, r as renderedPayload, t as isRecord } from "./validation-BvvjSUFc.mjs";
2
- import { i as groupDiagnostics, n as contentError, o as withContext, t as ContentError } from "./errors-C8lWyiy2.mjs";
3
- import { i as detach, l as valuePath, o as encode } from "./values-B7lo_2_x.mjs";
4
- import { t as assertServerOnly } from "./server-only-CIE-FHx0.mjs";
5
- import { a as CODEC_VERSION, i as prepareRelationships, n as createRuntime, r as isRelationship } from "./content-Dj2fQCJ5.mjs";
6
- import { t as renderMarkdown } from "./markdown-CZBL4Ux_.mjs";
1
+ import { a as builtinLoader, i as validate, n as normalizeSnapshot, o as builtinPath, r as renderedPayload, t as isRecord } from "./validation-D_XdE1pF.mjs";
2
+ import { i as groupDiagnostics, n as contentError, o as withContext, t as ContentError } from "./errors-C2iuBM0G.mjs";
3
+ import { i as detach, l as valuePath, o as encode } from "./values-BbTV4QT-.mjs";
4
+ import { t as assertServerOnly } from "./server-only-CIJUlyL-.mjs";
5
+ import { a as CODEC_VERSION, i as prepareRelationships, n as createRuntime, r as isRelationship } from "./content-N1gYshjg.mjs";
6
+ import { t as renderMarkdown } from "./markdown-CHvBBJNa.mjs";
7
7
  import { access, link, mkdir, readFile, readdir, realpath, rm, rmdir, stat, writeFile } from "node:fs/promises";
8
8
  import path from "node:path";
9
9
  import { pathToFileURL } from "node:url";
@@ -33,7 +33,7 @@ function compactJson(source) {
33
33
  async function revisionModules(revision, { payloadChunkBytes = 0 } = {}) {
34
34
  const modules = /* @__PURE__ */ new Map();
35
35
  const manifest = {
36
- version: 1,
36
+ version: 2,
37
37
  codec: CODEC_VERSION,
38
38
  collections: revision.registry
39
39
  };
@@ -62,7 +62,7 @@ async function revisionModules(revision, { payloadChunkBytes = 0 } = {}) {
62
62
  batch = [];
63
63
  batchBytes = 0;
64
64
  }
65
- for (const [id, load] of collection.payloads) {
65
+ for (const [id, load] of collection.builtins) {
66
66
  const payload = await load();
67
67
  const encoded = encode(payload);
68
68
  needsRenderer ||= payload.markdown !== void 0;
@@ -87,7 +87,7 @@ async function revisionModules(revision, { payloadChunkBytes = 0 } = {}) {
87
87
  const helpers = [...chunks.length ? ["createPayloadLoaders"] : [], ...compressedRecords ? ["decodeCompressed"] : []];
88
88
  const loaders = helpers.length ? `import { ${helpers.join(", ")} } from "content-query/internal";\n` : "";
89
89
  const loading = chunks.length ? `createPayloadLoaders([${chunks.join(",\n")}])` : `{ ${payloads.join(",\n")} }`;
90
- const code = `${loaders}export default { records: ${compressedRecords ? `decodeCompressed(${compressedRecords})` : recordSource}, payloads: ${loading} };\n`;
90
+ const code = `${loaders}export default { records: ${compressedRecords ? `decodeCompressed(${compressedRecords})` : recordSource}, builtins: ${loading} };\n`;
91
91
  const index = `index-${digest(code)}.js`;
92
92
  modules.set(`${directory}/${index}`, code);
93
93
  modules.set(`${directory}/index.js`, `export { default } from ${JSON.stringify(`./${index}`)};\n`);
@@ -224,6 +224,37 @@ async function writeArtifacts(revision, outDir, config, { signal, prune = false
224
224
  if (prune) await pruneArtifacts(outDir, modules);
225
225
  }
226
226
  //#endregion
227
+ //#region src/collection/registry.ts
228
+ /** Built-in payload names start with an underscore, which custom payload names cannot use. */
229
+ const builtinPayloads = {
230
+ _body: { variants: [] },
231
+ _rendered: { variants: [] }
232
+ };
233
+ /**
234
+ * Reports whether a collection can carry built-in `_body` and `_rendered` payloads.
235
+ * This depends only on the declaration, never on the entries a source currently contains.
236
+ *
237
+ * @param definition - A collection definition, including the `source` shorthand when used.
238
+ * @returns False for JSON, YAML, and TOML sources and `file()` loaders; true otherwise.
239
+ */
240
+ function hasBuiltinPayloads(definition) {
241
+ const source = definition.source;
242
+ if (source !== void 0) return typeof source === "string" || source.type === "markdown";
243
+ return builtinLoader(definition.loader)?.kind !== "file";
244
+ }
245
+ /**
246
+ * Builds the query registry that runtimes validate queries against, without resolvers or schemas.
247
+ *
248
+ * @param registry - The configured collection definitions.
249
+ * @returns Relationship and payload descriptors for each collection.
250
+ */
251
+ function queryRegistry(registry) {
252
+ return Object.fromEntries(Object.entries(registry).map(([name, definition]) => [name, {
253
+ relationships: definition.relationships ?? {},
254
+ payloads: hasBuiltinPayloads(definition) ? { ...builtinPayloads } : {}
255
+ }]));
256
+ }
257
+ //#endregion
227
258
  //#region src/preparation/prepare.ts
228
259
  /** Prepares one immutable revision with loader-defined source metadata, aggregating failures. */
229
260
  async function prepareRevision(registry, sources, cache, signal, development, sourceDiagnostics = [], parsedData) {
@@ -231,11 +262,15 @@ async function prepareRevision(registry, sources, cache, signal, development, so
231
262
  const collections = Object.create(null);
232
263
  for (const [collectionName, definition] of Object.entries(registry)) {
233
264
  const records = [];
234
- const payloads = /* @__PURE__ */ new Map();
265
+ const builtins = /* @__PURE__ */ new Map();
235
266
  const computed = definition.computed ?? {};
236
267
  const relationships = definition.relationships ?? {};
237
268
  const declarationNames = [...Object.keys(computed), ...Object.keys(relationships)];
238
- if (declarationNames.some((name) => name === "id" || name === "_adjacent") || new Set(declarationNames).size !== declarationNames.length || Object.values(relationships).some((relation) => !isRelationship(relation))) {
269
+ if (declarationNames.some((name) => [
270
+ "id",
271
+ "_adjacent",
272
+ "_payloads"
273
+ ].includes(name)) || new Set(declarationNames).size !== declarationNames.length || Object.values(relationships).some((relation) => !isRelationship(relation))) {
239
274
  diagnostics.push({
240
275
  code: "validation-error",
241
276
  operation: "build",
@@ -261,7 +296,7 @@ async function prepareRevision(registry, sources, cache, signal, development, so
261
296
  const previous = cache.get(key);
262
297
  if (signature !== void 0 && previous?.signature === signature && previous.definition === definition) {
263
298
  records.push(previous.record);
264
- if (Object.keys(previous.payload).length) payloads.set(entry.id, async () => previous.payload);
299
+ if (Object.keys(previous.payload).length) builtins.set(entry.id, async () => previous.payload);
265
300
  continue;
266
301
  }
267
302
  try {
@@ -269,7 +304,7 @@ async function prepareRevision(registry, sources, cache, signal, development, so
269
304
  const validated = parsed?.has(entry.data) && parsed.get(entry.data) === definition.schema ? entry.data : await validate(definition.schema, entry.data, context);
270
305
  signal.throwIfAborted();
271
306
  if (!isRecord(validated)) throw new Error("Validated metadata must be a record of named fields.");
272
- if (Object.hasOwn(validated, "id") || Object.hasOwn(validated, "_adjacent") || declarationNames.some((name) => Object.hasOwn(validated, name))) throw new Error("Metadata, computed fields, relationships, and reserved id/_adjacent must have distinct names.");
307
+ if (Object.hasOwn(validated, "id") || Object.hasOwn(validated, "_adjacent") || Object.hasOwn(validated, "_payloads") || declarationNames.some((name) => Object.hasOwn(validated, name))) throw new Error("Metadata, computed fields, relationships, and reserved id/_adjacent/_payloads must have distinct names.");
273
308
  const metadata = detach(validated);
274
309
  const values = Object.create(null);
275
310
  const before = diagnostics.length;
@@ -306,16 +341,18 @@ async function prepareRevision(registry, sources, cache, signal, development, so
306
341
  if (entry.body !== void 0 && (!markdown || descriptor?.kind !== "glob" || descriptor.options.retainBody !== false)) payload.body = entry.body;
307
342
  const supplied = renderedPayload(entry);
308
343
  if (supplied) payload.rendered = supplied;
309
- else if (markdown && entry.body !== void 0) if (development || descriptor.options.deferRender === true) payload.markdown = entry.body;
310
- else try {
311
- payload.rendered = await renderMarkdown(entry.body);
312
- } catch (cause) {
313
- throw new ContentError({
314
- ...context,
315
- code: "render-error",
316
- message: "Eager Markdown compilation failed.",
317
- cause
318
- });
344
+ else if (markdown && entry.body !== void 0) {
345
+ if (development || descriptor.options.deferRender === true) payload.markdown = entry.body;
346
+ else try {
347
+ payload.rendered = await renderMarkdown(entry.body);
348
+ } catch (cause) {
349
+ throw new ContentError({
350
+ ...context,
351
+ code: "render-error",
352
+ message: "Eager Markdown compilation failed.",
353
+ cause
354
+ });
355
+ }
319
356
  }
320
357
  encode(payload);
321
358
  cache.set(key, {
@@ -325,7 +362,7 @@ async function prepareRevision(registry, sources, cache, signal, development, so
325
362
  payload
326
363
  });
327
364
  records.push(record);
328
- if (Object.keys(payload).length) payloads.set(entry.id, async () => payload);
365
+ if (Object.keys(payload).length) builtins.set(entry.id, async () => payload);
329
366
  } catch (cause) {
330
367
  const formatted = cause && typeof cause === "object" && "path" in cause && typeof cause.path === "string" ? cause.path : "";
331
368
  const message = cause instanceof Error ? cause.message : "Entry preparation failed.";
@@ -340,14 +377,14 @@ async function prepareRevision(registry, sources, cache, signal, development, so
340
377
  collections[collectionName] = {
341
378
  records,
342
379
  relationships,
343
- payloads,
380
+ builtins,
344
381
  sources: new Map([...sources.get(collectionName)?.values() ?? []].map((entry) => [entry.id, entry.source]))
345
382
  };
346
383
  }
347
384
  signal.throwIfAborted();
348
385
  const revision = {
349
386
  collections,
350
- registry: Object.fromEntries(Object.entries(collections).map(([name, collection]) => [name, { relationships: collection.relationships }])),
387
+ registry: queryRegistry(Object.fromEntries(Object.keys(collections).map((name) => [name, registry[name]]))),
351
388
  relations: /* @__PURE__ */ new Map(),
352
389
  active: 0,
353
390
  payloadCache: /* @__PURE__ */ new Map(),
@@ -364,7 +401,7 @@ async function prepareRevision(registry, sources, cache, signal, development, so
364
401
  /** Loads build-only configuration without retaining a previous config module value. */
365
402
  async function loadRegistry(config) {
366
403
  const filename = await realpath(config);
367
- const dependencies = new Set([filename]);
404
+ const dependencies = /* @__PURE__ */ new Set([filename]);
368
405
  const transpiler = createJiti(filename, {
369
406
  moduleCache: false,
370
407
  fsCache: false
@@ -707,7 +744,7 @@ function createStores(current, parsed, changed) {
707
744
  function guardedWatcher(watcher, run, failed) {
708
745
  const listeners = [];
709
746
  let running = Promise.resolve();
710
- const registration = new Set([
747
+ const registration = /* @__PURE__ */ new Set([
711
748
  "on",
712
749
  "once",
713
750
  "addListener",
@@ -1151,7 +1188,7 @@ async function createSession(options) {
1151
1188
  registry = nextRegistry;
1152
1189
  loaders = nextLoaders;
1153
1190
  configDependencies = loaded.dependencies;
1154
- state.registry = Object.fromEntries(Object.entries(registry).map(([name, definition]) => [name, { relationships: definition.relationships ?? {} }]));
1191
+ state.registry = queryRegistry(registry);
1155
1192
  candidates.clear();
1156
1193
  cache.clear();
1157
1194
  loadFailures.clear();
@@ -1166,7 +1203,8 @@ async function createSession(options) {
1166
1203
  const signal = attempt.signal;
1167
1204
  loading = names;
1168
1205
  const acquired = await Promise.allSettled([...names].map(async (name) => {
1169
- return [name, normalizeSnapshot(await loaders.get(name).load({ signal }), name)];
1206
+ const entries = await loaders.get(name).load({ signal });
1207
+ return [name, normalizeSnapshot(entries, name)];
1170
1208
  }));
1171
1209
  if (disposed || currentGeneration !== generation) return;
1172
1210
  loading = /* @__PURE__ */ new Set();
package/dist/build.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { n as buildContent, r as ContentSession, t as BuildContentOptions } from "./build-DSyoRxkZ.mjs";
1
+ import { n as buildContent, r as ContentSession, t as BuildContentOptions } from "./build-DfKd2bpf.mjs";
2
2
  export { type BuildContentOptions, type ContentSession, buildContent };
package/dist/build.mjs CHANGED
@@ -1,2 +1 @@
1
- import { n as buildContent } from "./build-C5ef0kkR.mjs";
2
- export { buildContent };
1
+ export { n as buildContent } from "./build-Dxz7P1ur.mjs";
package/dist/cli.d.mts CHANGED
@@ -1 +1 @@
1
- export { };
1
+ export {}
package/dist/cli.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
- import { t as ContentError } from "./errors-C8lWyiy2.mjs";
3
- import { t as buildAtRoot } from "./build-C5ef0kkR.mjs";
2
+ import { t as ContentError } from "./errors-C2iuBM0G.mjs";
3
+ import { t as buildAtRoot } from "./build-Dxz7P1ur.mjs";
4
4
  import { cac } from "cac";
5
5
  //#region package.json
6
- var version = "0.1.0";
6
+ var version = "0.2.0";
7
7
  //#endregion
8
8
  //#region src/integrations/cli/arguments.ts
9
9
  /** Validates one path option after CAC's automatic numeric conversion. */
@@ -1,37 +1,43 @@
1
- import { f as MaybePromise } from "./types-hmDOw87q.mjs";
2
- import { r as ComputedContext } from "./types-BPh2cBPs.mjs";
1
+ import { p as MaybePromise } from "./types-ChaNhj42.mjs";
2
+ import { a as ComputedContext } from "./types-C-kdMblM.mjs";
3
3
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
-
5
4
  //#region src/computed/text.d.ts
6
5
  /** The syntax removed before computing text-derived fields; literal text preserves all characters. */
7
6
  type TextFormat = "markdown" | "text" | "html";
8
7
  /** Selects text from validated entry data or loader metadata instead of the captured body. */
9
8
  type TextOptions<Data = Record<string, unknown>, Source = unknown> = {
10
- /** Defaults to readBody(); undefined text is treated as an empty string. Async selectors are supported. */from?: (context: ComputedContext<Data, Source>) => MaybePromise<string | undefined>; /** Input syntax, defaulting to markdown. */
9
+ /** Defaults to readBody(); undefined text is treated as an empty string. Async selectors are supported. */
10
+ from?: (context: ComputedContext<Data, Source>) => MaybePromise<string | undefined>;
11
+ /** Input syntax, defaulting to markdown. */
11
12
  format?: TextFormat;
12
13
  };
13
14
  /** Controls the reading-speed estimate in addition to selecting its input text. */
14
15
  type ReadingTimeOptions<Data = Record<string, unknown>, Source = unknown> = TextOptions<Data, Source> & {
15
- /** A finite positive reading speed, defaulting to 200 words per minute. */wordsPerMinute?: number;
16
+ /** A finite positive reading speed, defaulting to 200 words per minute. */
17
+ wordsPerMinute?: number;
16
18
  };
17
19
  /** Controls the maximum excerpt size in addition to selecting its input text. */
18
20
  type ExcerptOptions<Data = Record<string, unknown>, Source = unknown> = TextOptions<Data, Source> & {
19
- /** A positive integer, defaulting to 160. Counts graphemes and includes the trailing ellipsis. */maxLength?: number;
21
+ /** A positive integer, defaulting to 160. Counts graphemes and includes the trailing ellipsis. */
22
+ maxLength?: number;
20
23
  };
21
24
  /** Creates a numeric computed field counting normalized text with reading-time's word boundaries. */
22
- declare function wordCount<Data = Record<string, unknown>, Source = unknown>(options?: TextOptions<Data, Source>): {
23
- schema: StandardSchemaV1<unknown, number>; /** Resolves selected text during preparation and honors cancellation around asynchronous input. */
25
+ export declare function wordCount<Data = Record<string, unknown>, Source = unknown>(options?: TextOptions<Data, Source>): {
26
+ schema: StandardSchemaV1<unknown, number>;
27
+ /** Resolves selected text during preparation and honors cancellation around asynchronous input. */
24
28
  resolve(context: ComputedContext<Data, Source>): Promise<number>;
25
29
  };
26
30
  /** Creates a numeric computed field estimating whole reading minutes, rounded up; empty text returns zero. */
27
- declare function readingTime<Data = Record<string, unknown>, Source = unknown>(options?: ReadingTimeOptions<Data, Source>): {
28
- schema: StandardSchemaV1<unknown, number>; /** Resolves selected text during preparation and honors cancellation around asynchronous input. */
31
+ export declare function readingTime<Data = Record<string, unknown>, Source = unknown>(options?: ReadingTimeOptions<Data, Source>): {
32
+ schema: StandardSchemaV1<unknown, number>;
33
+ /** Resolves selected text during preparation and honors cancellation around asynchronous input. */
29
34
  resolve(context: ComputedContext<Data, Source>): Promise<number>;
30
35
  };
31
36
  /** Creates a string computed field containing a normalized plain-text excerpt with a bounded length. */
32
- declare function excerpt<Data = Record<string, unknown>, Source = unknown>(options?: ExcerptOptions<Data, Source>): {
33
- schema: StandardSchemaV1<unknown, string>; /** Resolves selected text during preparation and honors cancellation around asynchronous input. */
37
+ export declare function excerpt<Data = Record<string, unknown>, Source = unknown>(options?: ExcerptOptions<Data, Source>): {
38
+ schema: StandardSchemaV1<unknown, string>;
39
+ /** Resolves selected text during preparation and honors cancellation around asynchronous input. */
34
40
  resolve(context: ComputedContext<Data, Source>): Promise<string>;
35
41
  };
36
42
  //#endregion
37
- export { type ExcerptOptions, type ReadingTimeOptions, type TextFormat, type TextOptions, excerpt, readingTime, wordCount };
43
+ export type { ExcerptOptions, ReadingTimeOptions, TextFormat, TextOptions };
package/dist/computed.mjs CHANGED
@@ -1,18 +1,18 @@
1
- import { t as assertServerOnly } from "./server-only-CIE-FHx0.mjs";
2
- import { n as number, r as string } from "./schema-CnCbMosm.mjs";
3
- import { t as src_default } from "./dist-BQGVLZyl.mjs";
1
+ import { t as assertServerOnly } from "./server-only-CIJUlyL-.mjs";
2
+ import { n as number, r as string } from "./schema-C5Str-E8.mjs";
3
+ import { t as src_default } from "./dist-C7_FOzY_.mjs";
4
4
  import getReadingTime from "reading-time";
5
5
  import { Parser } from "htmlparser2";
6
6
  //#region src/computed/plain-text.ts
7
7
  const markdown = new src_default({ html: true });
8
- const ignoredElements = new Set([
8
+ const ignoredElements = /* @__PURE__ */ new Set([
9
9
  "head",
10
10
  "script",
11
11
  "style",
12
12
  "template",
13
13
  "noscript"
14
14
  ]);
15
- const blockElements = new Set([
15
+ const blockElements = /* @__PURE__ */ new Set([
16
16
  "address",
17
17
  "article",
18
18
  "aside",
@@ -96,6 +96,7 @@ function textField(schema, options, compute) {
96
96
  if (from !== void 0 && typeof from !== "function") throw new TypeError("Text from must be a function.");
97
97
  return {
98
98
  schema,
99
+ /** Resolves selected text during preparation and honors cancellation around asynchronous input. */
99
100
  async resolve(context) {
100
101
  context.signal.throwIfAborted();
101
102
  const input = await (from ? from(context) : context.readBody());
@@ -1,7 +1,6 @@
1
- import { n as ContentRenderResult } from "./types-DcvHRmxO.mjs";
2
- import { a as Relationship, i as QueryData, n as CollectionRegistry, o as RelationshipsOf } from "./types-BPh2cBPs.mjs";
3
- import { _ as QueryPlan, n as RelationalQueries, t as RelationalCountOptions, u as Query, v as QueryRegistry } from "./relational-types-DLniKi5S.mjs";
4
-
1
+ import { n as ContentRenderResult } from "./types-Dh-Sydyw.mjs";
2
+ import { c as Relationship, i as CollectionRegistry, l as RelationshipsOf, s as QueryData } from "./types-C-kdMblM.mjs";
3
+ import { _ as QueryPlan, n as RelationalQueries, t as RelationalCountOptions, u as Query, v as QueryRegistry } from "./relational-types-DgK3rLuw.mjs";
5
4
  //#region src/collection/registry-types.d.ts
6
5
  type Scalar = string | number | bigint | boolean | Date;
7
6
  type Element<Value> = NonNullable<Value> extends readonly (infer Item)[] ? NonNullable<Item> : NonNullable<Value>;
@@ -9,16 +8,17 @@ type Compatible<Left, Right> = NonNullable<Left> extends readonly unknown[] ? No
9
8
  type ScalarCompatible<Left, Right> = [Element<Left> | Element<Right>] extends [Scalar] ? [Element<Left>] extends [never] ? true : [Element<Right>] extends [never] ? true : [Extract<Element<Left>, Element<Right>> | Extract<Element<Right>, Element<Left>>] extends [never] ? false : true : false;
10
9
  type CheckRelation<Registry extends CollectionRegistry, Name extends keyof Registry, Link extends Relationship> = Link["collection"] extends keyof Registry ? Link["from"] extends keyof QueryData<Registry[Name]> ? Link["to"] extends keyof QueryData<Registry[Link["collection"]]> ? Compatible<QueryData<Registry[Name]>[Link["from"]], QueryData<Registry[Link["collection"]]>[Link["to"]]> extends true ? never : "incompatible-relationship-fields" : "unknown-target-field" : "unknown-source-field" : "unknown-target-collection";
11
10
  /** Registry-level relationship checks require the complete inferred collection map. */
12
- type InvalidRelationships<Registry extends CollectionRegistry> = { [Name in keyof Registry]: { [Link in keyof RelationshipsOf<Registry[Name]>]: RelationshipsOf<Registry[Name]>[Link] extends Relationship ? CheckRelation<Registry, Name, RelationshipsOf<Registry[Name]>[Link]> : "invalid-relationship" }[keyof RelationshipsOf<Registry[Name]>] }[keyof Registry];
11
+ type InvalidRelationships<Registry extends CollectionRegistry> = { [Name in keyof Registry]: { [Link in keyof RelationshipsOf<Registry[Name]>]: RelationshipsOf<Registry[Name]>[Link] extends Relationship ? CheckRelation<Registry, Name, RelationshipsOf<Registry[Name]>[Link]> : "invalid-relationship"; }[keyof RelationshipsOf<Registry[Name]>]; }[keyof Registry];
13
12
  //#endregion
14
13
  //#region src/query/select.d.ts
15
14
  /** A selection whose collection and selected result are inferred when from() is supplied. */
16
15
  type SelectFrom<Registry extends CollectionRegistry, Keys extends readonly string[]> = {
17
- /** Binds the collection and rejects selected keys that are absent from its prepared data type. */from<Name extends keyof Registry & string>(collection: Name & (Exclude<Keys[number], keyof QueryData<Registry[NoInfer<Name>]>> extends never ? unknown : never)): Query<Registry, Name, Keys extends readonly [] ? QueryData<Registry[Name]> : { [Key in Keys[number]]-?: Key extends keyof QueryData<Registry[Name]> ? QueryData<Registry[Name]>[Key] : never }, true>;
16
+ /** Binds the collection and rejects selected keys that are absent from its prepared data type. */
17
+ from<Name extends keyof Registry & string>(collection: Name & (Exclude<Keys[number], keyof QueryData<Registry[NoInfer<Name>]>> extends never ? unknown : never)): Query<Registry, Name, Keys extends readonly [] ? QueryData<Registry[Name]> : { [Key in Keys[number]]-?: Key extends keyof QueryData<Registry[Name]> ? QueryData<Registry[Name]>[Key] : never; }, true>;
18
18
  };
19
19
  //#endregion
20
20
  //#region src/shared/errors.d.ts
21
- type ContentErrorCode = "entry-not-found" | "not-renderable" | "source-error" | "validation-error" | "duplicate-id" | "invalid-reference" | "invalid-query" | "artifact-error" | "render-error";
21
+ type ContentErrorCode = "variant-not-found" | "not-renderable" | "source-error" | "validation-error" | "duplicate-id" | "invalid-reference" | "invalid-query" | "artifact-error" | "render-error";
22
22
  type ContentDiagnostic = {
23
23
  code: ContentErrorCode;
24
24
  message: string;
@@ -52,7 +52,8 @@ declare class ContentError extends Error {
52
52
  type QueryRecord = Record<string, unknown> & {
53
53
  id: string;
54
54
  };
55
- type ItemPayload = {
55
+ /** The built-in side payload shared by an entry's `_body` and `_rendered` payloads. */
56
+ type BuiltinPayload = {
56
57
  body?: string;
57
58
  rendered?: ContentRenderResult;
58
59
  markdown?: string;
@@ -60,7 +61,8 @@ type ItemPayload = {
60
61
  type PreparedCollection = {
61
62
  records: readonly QueryRecord[];
62
63
  relationships: Record<string, Relationship>;
63
- payloads: ReadonlyMap<string, () => Promise<ItemPayload>>;
64
+ /** Lazy built-in payloads, present only for entries with a body or rendered output. */
65
+ builtins: ReadonlyMap<string, () => Promise<BuiltinPayload>>;
64
66
  sources?: ReadonlyMap<string, string | undefined>;
65
67
  };
66
68
  type PreparedRevision = {
@@ -68,10 +70,17 @@ type PreparedRevision = {
68
70
  collections: Record<string, PreparedCollection>;
69
71
  relations: Map<string, Map<string, readonly QueryRecord[]>>;
70
72
  active: number;
71
- payloadCache: Map<string, Promise<ItemPayload>>;
73
+ payloadCache: Map<string, Promise<BuiltinPayload>>;
72
74
  renderCache: Map<string, Promise<ContentRenderResult>>;
73
75
  renderMarkdown?: (body: string) => Promise<ContentRenderResult>;
74
- execute?: (plan: QueryPlan, terminal: "all" | "first" | "count") => Promise<unknown>;
76
+ execute?: (plan: QueryPlan, terminal: "all" | "first" | "count", requests: PayloadRequest[]) => Promise<unknown>;
77
+ };
78
+ /** A projected row awaiting the payloads its query scope requested. */
79
+ type PayloadRequest = {
80
+ target: Record<string, unknown>;
81
+ collection: string;
82
+ id: string;
83
+ payloads: NonNullable<QueryPlan["payloads"]>;
75
84
  };
76
85
  type ContentState = {
77
86
  revision?: PreparedRevision;
@@ -81,11 +90,12 @@ type ContentState = {
81
90
  //#endregion
82
91
  //#region src/runtime/content.d.ts
83
92
  type ContentMethods<Registry extends CollectionRegistry = {}> = {
84
- /** Starts a SQL-ordered fluent query; field names are checked against the collection in from(). */select<const Keys extends readonly string[]>(...fields: Keys): SelectFrom<Registry, Keys>; /** Collection-scoped relational queries with typed columns, relationships, and object filters. */
85
- readonly query: RelationalQueries<Registry>; /** Counts matching entries before pagination, like the fluent count terminal. */
86
- count<Name extends keyof Registry & string>(collection: Name, options?: RelationalCountOptions<Registry, Name>): Promise<number>; /** Reads retained raw text separately from prepared metadata; missing entries reject. */
87
- readBody(collection: keyof Registry & string, id: string): Promise<string | undefined>; /** Loads supplied output or renders captured Markdown, returning detached HTML and headings. */
88
- render(collection: keyof Registry & string, id: string): Promise<ContentRenderResult>;
93
+ /** Starts a SQL-ordered fluent query; field names are checked against the collection in from(). */
94
+ select<const Keys extends readonly string[]>(...fields: Keys): SelectFrom<Registry, Keys>;
95
+ /** Collection-scoped relational queries with typed columns, relationships, payloads, and filters. */
96
+ readonly query: RelationalQueries<Registry>;
97
+ /** Counts matching entries before pagination, like the fluent count terminal. */
98
+ count<Name extends keyof Registry & string>(collection: Name, options?: RelationalCountOptions<Registry, Name>): Promise<number>;
89
99
  };
90
100
  type Content<Registry extends CollectionRegistry = {}> = InvalidRelationships<Registry> extends never ? ContentMethods<Registry> : {
91
101
  readonly invalidRelationships: InvalidRelationships<Registry>;