content-query 0.3.0 → 0.5.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 (39) hide show
  1. package/README.md +46 -15
  2. package/dist/{build-CI4s0oFP.mjs → build-DWpB69_c.mjs} +826 -69
  3. package/dist/{build-jKnjDjQE.d.mts → build-DfoncKL8.d.mts} +17 -5
  4. package/dist/build.d.mts +1 -1
  5. package/dist/build.mjs +1 -1
  6. package/dist/cli.mjs +14 -8
  7. package/dist/{content-N1gYshjg.mjs → collection-Cb5eWGnv.mjs} +998 -344
  8. package/dist/collection-types-CsQtGw7_.d.mts +119 -0
  9. package/dist/computed.d.mts +2 -2
  10. package/dist/{types-FmUJ3cH1.d.mts → descriptor-bcPa6XLk.d.mts} +17 -1
  11. package/dist/{diagnostics-Cu94mZhl.mjs → diagnostics-DmUZtrJ6.mjs} +3 -1
  12. package/dist/{errors-C2iuBM0G.mjs → errors-Bfd0R4_P.mjs} +16 -1
  13. package/dist/file-DSOcdFj0.mjs +81 -0
  14. package/dist/{glob-Bv0dpvCQ.mjs → glob-BXJuwy_p.mjs} +94 -6
  15. package/dist/index.d.mts +76 -18
  16. package/dist/index.mjs +1 -1
  17. package/dist/internal.d.mts +54 -7
  18. package/dist/internal.mjs +424 -19
  19. package/dist/loaders.d.mts +1 -2
  20. package/dist/loaders.mjs +2 -81
  21. package/dist/next.d.mts +3 -2
  22. package/dist/next.mjs +67 -14
  23. package/dist/operators-D-w1-gDg.mjs +391 -0
  24. package/dist/query-style-C2FcT1f9.d.mts +23 -0
  25. package/dist/query.d.mts +25 -2
  26. package/dist/query.mjs +1 -1
  27. package/dist/{relational-types-B45zM34W.d.mts → relational-types-CTcalUNW.d.mts} +241 -50
  28. package/dist/revision-DBwU-aC9.d.mts +56 -0
  29. package/dist/runtime.d.mts +3 -2
  30. package/dist/runtime.mjs +1 -1
  31. package/dist/types-Dz1vTXa2.d.mts +214 -0
  32. package/dist/vite.d.mts +1 -1
  33. package/dist/vite.mjs +77 -35
  34. package/package.json +1 -1
  35. package/dist/content-lwPxE9Yu.d.mts +0 -109
  36. package/dist/descriptor-BUPT2J7X.d.mts +0 -18
  37. package/dist/operators-UqWUhKQJ.mjs +0 -173
  38. package/dist/types-BC8gVJ6u.d.mts +0 -69
  39. package/dist/validation-D_XdE1pF.mjs +0 -84
@@ -0,0 +1,119 @@
1
+ import { _ as Relationship, g as QueryData, i as CollectionRegistry, u as LoadedMarker, v as RelationshipsOf } from "./types-Dz1vTXa2.mjs";
2
+ import { n as RelationalCountOptions, t as RelationalCollectionQueries, v as Query } from "./relational-types-CTcalUNW.mjs";
3
+ import { i as QueryStyleMember, n as QueryStyle, r as QueryStyleDisabled, t as MembersOf } from "./query-style-C2FcT1f9.mjs";
4
+ //#region src/shared/errors.d.ts
5
+ type ContentErrorCode = "variant-not-found" | "not-renderable" | "source-error" | "validation-error" | "duplicate-id" | "invalid-reference" | "invalid-query" | "invalid-cursor" | "artifact-error" | "render-error";
6
+ type ContentDiagnostic = {
7
+ code: ContentErrorCode;
8
+ message: string;
9
+ operation?: string;
10
+ collection?: string;
11
+ id?: string;
12
+ source?: string;
13
+ path?: readonly PropertyKey[];
14
+ /** The custom payload being resolved or loaded. */
15
+ payload?: string;
16
+ /** The payload's variant combination, with keys in sorted order. */
17
+ variant?: Readonly<Record<string, string>>;
18
+ issues?: readonly unknown[];
19
+ cause?: unknown;
20
+ };
21
+ /** A contextual library failure; inspect code and diagnostics instead of message text. */
22
+ declare class ContentError extends Error {
23
+ /** Recognizes contextual errors across preparation and separately bundled loader modules. */
24
+ static [Symbol.hasInstance](value: unknown): boolean;
25
+ readonly code: ContentErrorCode;
26
+ readonly operation?: string;
27
+ readonly collection?: string;
28
+ readonly id?: string;
29
+ readonly source?: string;
30
+ readonly path?: readonly PropertyKey[];
31
+ readonly payload?: string;
32
+ readonly variant?: Readonly<Record<string, string>>;
33
+ readonly issues?: readonly unknown[];
34
+ readonly diagnostics: readonly ContentDiagnostic[];
35
+ /** Creates one failure or a grouped report preserving individual causes and issues. */
36
+ constructor(options: ContentDiagnostic & {
37
+ diagnostics?: readonly ContentDiagnostic[];
38
+ });
39
+ }
40
+ //#endregion
41
+ //#region src/collection/registry-types.d.ts
42
+ type Scalar = string | number | bigint | boolean | Date;
43
+ type Element<Value> = NonNullable<Value> extends readonly (infer Item)[] ? NonNullable<Item> : NonNullable<Value>;
44
+ type Compatible<Left, Right> = NonNullable<Left> extends readonly unknown[] ? NonNullable<Right> extends readonly unknown[] ? false : ScalarCompatible<Left, Right> : ScalarCompatible<Left, Right>;
45
+ 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;
46
+ 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";
47
+ /** Registry-level relationship checks require the complete inferred collection map. */
48
+ 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];
49
+ //#endregion
50
+ //#region src/runtime/collection-types.d.ts
51
+ declare const collectionType: unique symbol;
52
+ /**
53
+ * The registry a query scope sees: only the loaded collections, each marked so it offers only the
54
+ * relationships into loaded collections.
55
+ */
56
+ type LoadedRegistry<Registry extends CollectionRegistry, Loaded extends keyof Registry & string> = { [Name in Loaded]: Registry[Name] & LoadedMarker<Loaded>; };
57
+ /** The members that start a fluent query; terminals such as `all()` follow one of them. */
58
+ type FluentMember = MembersOf<"fluent">;
59
+ /** The relational members, apart from `count`, which both styles share. */
60
+ type RelationalMember = "findMany" | "findFirst" | "paginate";
61
+ /** Every member before `queryStyle` gating. */
62
+ type CollectionMethods<Registry extends CollectionRegistry, Name extends keyof Registry & string> = Pick<Query<Registry, Name>, FluentMember & keyof Query<Registry, Name>> & RelationalCollectionQueries<Registry, Name>;
63
+ /**
64
+ * Members outside the allowed style. Calls report the message as the required `this` type,
65
+ * whatever arguments they pass.
66
+ */
67
+ type DisabledMembers<Name extends string, Style extends QueryStyle> = { readonly [Member in Exclude<FluentMember | RelationalMember, MembersOf<Style>>]: (this: QueryStyleDisabled<Name, Member & QueryStyleMember, Style>, ...arguments_: readonly unknown[]) => never; };
68
+ /** `count()` counts every entry in both styles; options take relational filters. */
69
+ type CountMember<Registry extends CollectionRegistry, Name extends keyof Registry & string, Style extends QueryStyle> = [Style] extends ["fluent"] ? {
70
+ /** Counts every entry. Count filtered entries with `where(...).count()`. */
71
+ count(): Promise<number>;
72
+ } : {
73
+ /** Counts matching entries before pagination, like the fluent `count()` terminal. */
74
+ count(options?: RelationalCountOptions<Registry, Name>): Promise<number>;
75
+ };
76
+ /** Reads the collections a collection handle loads. */
77
+ type LoadedOf<Handle> = Handle extends {
78
+ readonly [collectionType]: {
79
+ loaded: infer Loaded;
80
+ };
81
+ } ? Loaded : never;
82
+ /** Any collection handle, as `using()` accepts it. */
83
+ type AnyCollection = {
84
+ readonly [collectionType]: {
85
+ loaded: string;
86
+ };
87
+ };
88
+ /**
89
+ * One collection's query API, as its generated entry exports it.
90
+ *
91
+ * Queries load only this collection and the collections passed to `using()`. Relationships into
92
+ * any other collection are type errors here and `invalid-query` errors at runtime. `Style` narrows
93
+ * the members to the `queryStyle` exported by content-query.config; the default allows both.
94
+ *
95
+ * @typeParam Registry - The configured collection registry.
96
+ * @typeParam Name - The queried collection.
97
+ * @typeParam Style - The allowed query style.
98
+ * @typeParam Loaded - The collections this handle loads, including `Name`.
99
+ */
100
+ type Collection<Registry extends CollectionRegistry, Name extends keyof Registry & string, Style extends QueryStyle = QueryStyle, Loaded extends keyof Registry & string = Name> = InvalidRelationships<Registry> extends never ? Pick<CollectionMethods<LoadedRegistry<Registry, Loaded | Name>, Name>, Extract<FluentMember | RelationalMember, MembersOf<Style>>> & DisabledMembers<Name, Style> & CountMember<LoadedRegistry<Registry, Loaded | Name>, Name, Style> & {
101
+ /**
102
+ * Supplies other collections, so this collection's queries can follow relationships
103
+ * into them. Nested relationships need each collection they reach.
104
+ *
105
+ * @param collections - Collections imported from their own entries.
106
+ * @returns A handle over this collection that also loads the supplied collections.
107
+ */
108
+ using<const Supplied extends readonly AnyCollection[]>(...collections: Supplied): Collection<Registry, Name, Style, Loaded | (LoadedOf<Supplied[number]> & keyof Registry & string)>;
109
+ readonly [collectionType]: {
110
+ readonly name: Name;
111
+ readonly loaded: Loaded | Name;
112
+ };
113
+ } : {
114
+ readonly invalidRelationships: InvalidRelationships<Registry>;
115
+ };
116
+ /** One handle per collection, keyed by collection name, each loading only its own collection. */
117
+ type CollectionHandles<Registry extends CollectionRegistry, Style extends QueryStyle = QueryStyle> = { readonly [Name in keyof Registry & string]: Collection<Registry, Name, Style>; };
118
+ //#endregion
119
+ export { InvalidRelationships as a, ContentErrorCode as c, LoadedRegistry as i, Collection as n, ContentDiagnostic as o, CollectionHandles as r, ContentError as s, AnyCollection as t };
@@ -1,5 +1,5 @@
1
- import { m as MaybePromise } from "./types-FmUJ3cH1.mjs";
2
- import { a as ComputedContext } from "./types-BC8gVJ6u.mjs";
1
+ import { g as MaybePromise } from "./descriptor-bcPa6XLk.mjs";
2
+ import { a as ComputedContext } from "./types-Dz1vTXa2.mjs";
3
3
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
4
  //#region src/computed/text.d.ts
5
5
  /** The syntax removed before computing text-derived fields; literal text preserves all characters. */
@@ -149,4 +149,20 @@ type LoaderSource<Value> = Value extends {
149
149
  readonly [loaderSourceType]?: infer Source;
150
150
  } ? Source : unknown;
151
151
  //#endregion
152
- export { FileSource as a, LoaderContext as c, LoaderWatcher as d, MetaStore as f, FileEntrySource as i, LoaderLogger as l, MaybePromise as m, DataEntry as n, Loader as o, RecordLoader as p, DataStore as r, LoaderConfig as s, BodyLoader as t, LoaderSource as u };
152
+ //#region src/loaders/descriptor.d.ts
153
+ type GlobOptions = {
154
+ pattern: string | string[];
155
+ base?: string | URL;
156
+ generateId?: (context: {
157
+ entry: string;
158
+ base: URL;
159
+ data: Record<string, unknown>;
160
+ }) => string;
161
+ retainBody?: boolean;
162
+ deferRender?: boolean;
163
+ };
164
+ type FileOptions = {
165
+ parser?: (text: string) => MaybePromise<unknown>;
166
+ };
167
+ //#endregion
168
+ export { DataStore as a, Loader as c, LoaderLogger as d, LoaderSource as f, MaybePromise as g, RecordLoader as h, DataEntry as i, LoaderConfig as l, MetaStore as m, GlobOptions as n, FileEntrySource as o, LoaderWatcher as p, BodyLoader as r, FileSource as s, FileOptions as t, LoaderContext as u };
@@ -1,4 +1,4 @@
1
- import { t as ContentError } from "./errors-C2iuBM0G.mjs";
1
+ import { t as ContentError } from "./errors-Bfd0R4_P.mjs";
2
2
  import { o as encode, r as decode } from "./values-BbTV4QT-.mjs";
3
3
  //#region src/runtime/diagnostics.ts
4
4
  /** Transports Error nodes as records while preserving the supported diagnostic value graph. */
@@ -59,6 +59,8 @@ function serializeContentError(error) {
59
59
  id: error.id,
60
60
  source: error.source,
61
61
  path: error.path,
62
+ payload: error.payload,
63
+ variant: error.variant,
62
64
  issues: error.issues,
63
65
  cause: error.cause,
64
66
  diagnostics: error.diagnostics
@@ -12,6 +12,8 @@ var ContentError = class extends Error {
12
12
  id;
13
13
  source;
14
14
  path;
15
+ payload;
16
+ variant;
15
17
  issues;
16
18
  diagnostics;
17
19
  /** Creates one failure or a grouped report preserving individual causes and issues. */
@@ -25,10 +27,23 @@ var ContentError = class extends Error {
25
27
  this.id = options.id;
26
28
  this.source = options.source;
27
29
  this.path = options.path;
30
+ this.payload = options.payload;
31
+ this.variant = options.variant;
28
32
  this.issues = options.issues;
29
33
  this.diagnostics = options.diagnostics ?? [{ ...options }];
30
34
  }
31
35
  };
36
+ /**
37
+ * Formats a payload and its variant as a readable identity, such as `detail[locale=en-US]`.
38
+ *
39
+ * @param payload - The payload name.
40
+ * @param variant - The variant combination; keys are listed in sorted order.
41
+ * @returns The payload name, followed by its variant pairs when it has any.
42
+ */
43
+ function payloadIdentity(payload, variant = {}) {
44
+ const pairs = Object.keys(variant).sort().map((key) => `${key}=${variant[key]}`);
45
+ return pairs.length ? `${payload}[${pairs.join(",")}]` : payload;
46
+ }
32
47
  /** Rejects invalid query structure before any execution or payload access. */
33
48
  function invalidQuery(message) {
34
49
  throw new ContentError({
@@ -69,4 +84,4 @@ function withContext(cause, context) {
69
84
  })));
70
85
  }
71
86
  //#endregion
72
- export { invalidQuery as a, groupDiagnostics as i, contentError as n, withContext as o, describeCause as r, ContentError as t };
87
+ export { invalidQuery as a, groupDiagnostics as i, contentError as n, payloadIdentity as o, describeCause as r, withContext as s, ContentError as t };
@@ -0,0 +1,81 @@
1
+ import { a as recordData, f as builtinPath, i as parseData, o as populateStore, p as builtinSymbol, r as coerceIdentity } from "./glob-BXJuwy_p.mjs";
2
+ import { i as groupDiagnostics, n as contentError, r as describeCause, t as ContentError } from "./errors-Bfd0R4_P.mjs";
3
+ import { readFile } from "node:fs/promises";
4
+ import path from "node:path";
5
+ import { fileURLToPath } from "node:url";
6
+ //#region src/loaders/file.ts
7
+ /** Declares a multi-entry data file with typed source paths and record keys, plus an optional parser. */
8
+ function file(filename, options = {}) {
9
+ if (typeof filename !== "string" || !filename) throw new TypeError("file() requires a filename.");
10
+ const descriptor = {
11
+ kind: "file",
12
+ filename,
13
+ options
14
+ };
15
+ return {
16
+ [builtinSymbol]: descriptor,
17
+ name: "content-query/file",
18
+ async load(context) {
19
+ const entries = await loadFile(descriptor, fileURLToPath(context.config.root), context.signal ?? new AbortController().signal);
20
+ await populateStore(context, entries);
21
+ }
22
+ };
23
+ }
24
+ /** Captures a complete multi-entry data source without applying collection validation. */
25
+ async function loadFile(descriptor, root, signal) {
26
+ const resolved = builtinPath(descriptor, root);
27
+ let parsed;
28
+ try {
29
+ const text = await readFile(resolved, {
30
+ encoding: "utf8",
31
+ signal
32
+ });
33
+ parsed = descriptor.options.parser ? await descriptor.options.parser(text) : parseData(text, resolved);
34
+ } catch (cause) {
35
+ signal.throwIfAborted();
36
+ throw contentError(cause, {
37
+ code: "source-error",
38
+ source: resolved,
39
+ message: `Could not load "${descriptor.filename}": ${describeCause(cause)}`,
40
+ cause
41
+ });
42
+ }
43
+ const array = Array.isArray(parsed);
44
+ if (!array && (!parsed || typeof parsed !== "object")) throw new ContentError({
45
+ code: "source-error",
46
+ source: resolved,
47
+ message: `"${descriptor.filename}" must parse to an array or keyed record.`
48
+ });
49
+ const diagnostics = [];
50
+ const entries = Object.entries(parsed).flatMap(([key, value]) => {
51
+ if (!array && key === "$schema" && typeof value === "string") return [];
52
+ const source = `${resolved}#${key}`;
53
+ try {
54
+ const data = recordData(value);
55
+ const identity = array ? data.id ?? data.slug : key;
56
+ if (identity == null || coerceIdentity(identity) === "") throw new Error("Array entries require a nonempty id or slug.");
57
+ const { id: _transportId, ...metadata } = data;
58
+ return [{
59
+ id: coerceIdentity(identity),
60
+ data: metadata,
61
+ source: array ? source : `${resolved}#${coerceIdentity(identity)}`,
62
+ sourceData: {
63
+ filePath: path.relative(root, resolved).split(path.sep).join("/"),
64
+ key
65
+ }
66
+ }];
67
+ } catch (cause) {
68
+ diagnostics.push({
69
+ code: "source-error",
70
+ source,
71
+ message: `Invalid entry "${key}" in "${descriptor.filename}": ${describeCause(cause)}`,
72
+ cause
73
+ });
74
+ return [];
75
+ }
76
+ });
77
+ if (diagnostics.length) throw groupDiagnostics(diagnostics);
78
+ return entries;
79
+ }
80
+ //#endregion
81
+ export { loadFile as n, file as t };
@@ -1,5 +1,5 @@
1
- import { n as normalizeSnapshot, o as builtinPath, s as builtinSymbol } from "./validation-D_XdE1pF.mjs";
2
- import { i as groupDiagnostics, n as contentError, r as describeCause, t as ContentError } from "./errors-C2iuBM0G.mjs";
1
+ import { i as groupDiagnostics, n as contentError, r as describeCause, t as ContentError } from "./errors-Bfd0R4_P.mjs";
2
+ import { i as detach } from "./values-BbTV4QT-.mjs";
3
3
  import { n as slug } from "./github-slugger-BMgR_Q0I.mjs";
4
4
  import { readFile } from "node:fs/promises";
5
5
  import path from "node:path";
@@ -8,6 +8,85 @@ import { glob } from "tinyglobby";
8
8
  import matter from "gray-matter";
9
9
  import { parse } from "yaml";
10
10
  import { parse as parse$1 } from "smol-toml";
11
+ //#region src/loaders/descriptor.ts
12
+ const builtinSymbol = Symbol.for("content-query.builtin-loader");
13
+ /** Finds library-owned loader settings without depending on module instance identity. */
14
+ function builtinLoader(loader) {
15
+ return loader[builtinSymbol];
16
+ }
17
+ /** Resolves a loader's watch root from the application root, including file URLs. */
18
+ function builtinPath(descriptor, root) {
19
+ if (descriptor.kind === "file") return path.resolve(root, descriptor.filename);
20
+ return descriptor.options.base instanceof URL ? path.resolve(fileURLToPath(descriptor.options.base)) : path.resolve(root, descriptor.options.base ?? ".");
21
+ }
22
+ //#endregion
23
+ //#region src/preparation/validation.ts
24
+ /** Checks records independently from validation-provider schema introspection. */
25
+ function isRecord(value) {
26
+ return !!value && typeof value === "object" && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
27
+ }
28
+ /** Validates one result, retaining structured provider issues and their original detail. */
29
+ async function validate(schema, value, context) {
30
+ if (!schema) return value;
31
+ try {
32
+ const result = await schema["~standard"].validate(value);
33
+ if (result.issues) {
34
+ const firstPath = result.issues[0]?.path?.map((segment) => typeof segment === "object" ? segment.key : segment);
35
+ throw new ContentError({
36
+ ...context,
37
+ code: "validation-error",
38
+ message: result.issues.map((issue) => issue.message).join("; "),
39
+ issues: result.issues,
40
+ path: [...context.path ?? [], ...firstPath ?? []]
41
+ });
42
+ }
43
+ return result.value;
44
+ } catch (cause) {
45
+ throw contentError(cause, {
46
+ ...context,
47
+ code: "validation-error",
48
+ message: "Schema validation failed."
49
+ });
50
+ }
51
+ }
52
+ /** Checks loader transport structure and rejects duplicate snapshot/upsert identities. */
53
+ function normalizeSnapshot(entries, collection) {
54
+ if (!Array.isArray(entries)) throw new ContentError({
55
+ code: "source-error",
56
+ collection,
57
+ message: "load() must return an array of structured entries."
58
+ });
59
+ const snapshot = /* @__PURE__ */ new Map();
60
+ for (const entry of entries) {
61
+ if (!entry || typeof entry.id !== "string" || !entry.id || !isRecord(entry.data) || entry.body !== void 0 && typeof entry.body !== "string" || entry.source !== void 0 && typeof entry.source !== "string") throw new ContentError({
62
+ code: "source-error",
63
+ collection,
64
+ id: entry && typeof entry === "object" && typeof entry.id === "string" ? entry.id : void 0,
65
+ source: entry && typeof entry === "object" && typeof entry.source === "string" ? entry.source : void 0,
66
+ message: "Invalid structured loader entry."
67
+ });
68
+ if (snapshot.has(entry.id)) throw new ContentError({
69
+ code: "duplicate-id",
70
+ collection,
71
+ id: entry.id,
72
+ source: entry.source,
73
+ message: `Duplicate entry ID "${entry.id}".`
74
+ });
75
+ snapshot.set(entry.id, entry);
76
+ }
77
+ return snapshot;
78
+ }
79
+ /** Validates supplied rendering data and normalizes omitted headings. */
80
+ function renderedPayload(entry) {
81
+ if (entry.rendered === void 0) return;
82
+ const rendered = entry.rendered;
83
+ if (!rendered || typeof rendered.html !== "string" || rendered.metadata !== void 0 && (!isRecord(rendered.metadata) || rendered.metadata.headings !== void 0 && (!Array.isArray(rendered.metadata.headings) || rendered.metadata.headings.some((heading) => !heading || !Number.isInteger(heading.depth) || heading.depth < 1 || heading.depth > 6 || typeof heading.slug !== "string" || typeof heading.text !== "string")))) throw new Error("Invalid supplied HTML/headings payload.");
84
+ return {
85
+ html: rendered.html,
86
+ headings: detach(rendered.metadata?.headings ?? [])
87
+ };
88
+ }
89
+ //#endregion
11
90
  //#region src/loaders/populate.ts
12
91
  /** Validates a complete built-in source before replacing its collection store. */
13
92
  async function populateStore(context, entries) {
@@ -67,15 +146,24 @@ function glob$1(options) {
67
146
  }
68
147
  };
69
148
  }
70
- /** Captures complete matching Markdown and data sources before deployment. */
71
- async function loadGlob(descriptor, root, signal) {
149
+ /**
150
+ * Captures complete matching Markdown and data sources before deployment.
151
+ *
152
+ * @param descriptor - The glob loader's options.
153
+ * @param root - The project root that relative bases and source paths use.
154
+ * @param signal - Cancels outstanding reads.
155
+ * @param options - `warnEmpty: false` skips the warning when nothing matches, for callers where an
156
+ * empty result is expected, such as resolvers' `files.glob()`.
157
+ * @returns Loader entries in sorted path order.
158
+ */
159
+ async function loadGlob(descriptor, root, signal, { warnEmpty = true } = {}) {
72
160
  const resolved = builtinPath(descriptor, root);
73
161
  const entries = await glob(descriptor.options.pattern, {
74
162
  cwd: resolved,
75
163
  onlyFiles: true,
76
164
  dot: false
77
165
  });
78
- if (entries.length === 0) console.warn(`[content-query] No content matched ${JSON.stringify(descriptor.options.pattern)} in ${resolved}.`);
166
+ if (entries.length === 0 && warnEmpty) console.warn(`[content-query] No content matched ${JSON.stringify(descriptor.options.pattern)} in ${resolved}.`);
79
167
  const base = pathToFileURL(`${resolved}${path.sep}`);
80
168
  entries.sort();
81
169
  const results = [];
@@ -156,4 +244,4 @@ async function loadEntry(descriptor, entry, filename, base, signal) {
156
244
  };
157
245
  }
158
246
  //#endregion
159
- export { populateStore as a, recordData as i, coerceIdentity as n, parseData as r, glob$1 as t };
247
+ export { recordData as a, normalizeSnapshot as c, builtinLoader as d, builtinPath as f, parseData as i, renderedPayload as l, loadGlob as n, populateStore as o, builtinSymbol as p, coerceIdentity as r, isRecord as s, glob$1 as t, validate as u };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { a as FileSource, c as LoaderContext, d as LoaderWatcher, f as MetaStore, i as FileEntrySource, l as LoaderLogger, m as MaybePromise, n as DataEntry, o as Loader, p as RecordLoader, r as DataStore, s as LoaderConfig, t as BodyLoader, u as LoaderSource } from "./types-FmUJ3cH1.mjs";
1
+ import { a as DataStore, c as Loader, d as LoaderLogger, f as LoaderSource, g as MaybePromise, h as RecordLoader, i as DataEntry, l as LoaderConfig, m as MetaStore, n as GlobOptions, o as FileEntrySource, p as LoaderWatcher, r as BodyLoader, s as FileSource, u as LoaderContext } from "./descriptor-bcPa6XLk.mjs";
2
2
  import { n as ContentRenderResult, r as RenderedContent, t as ContentHeading } from "./types-Dh-Sydyw.mjs";
3
- import { a as ComputedContext, c as Relationship, d as SchemaOutput, i as CollectionRegistry, n as BuiltinPayloads, o as PayloadsOf, r as CollectionDefinition, s as QueryData, t as BuiltinPayloadMarker, u as Schema } from "./types-BC8gVJ6u.mjs";
4
- import { n as GlobOptions } from "./descriptor-BUPT2J7X.mjs";
3
+ import { C as VariantContext, E as VariantValues, T as VariantOf, _ as Relationship, a as ComputedContext, b as SchemaOutput, c as EntryFiles, d as PayloadContext, f as PayloadData, g as QueryData, h as PayloadsOf, i as CollectionRegistry, l as FileEntry, m as PayloadDefinition, n as BuiltinPayloads, o as CustomPayload, p as PayloadDeclaration, r as CollectionDefinition, s as CustomPayloadMarker, t as BuiltinPayloadMarker, w as VariantField, y as Schema } from "./types-Dz1vTXa2.mjs";
4
+ import { n as QueryStyle } from "./query-style-C2FcT1f9.mjs";
5
5
  import { StandardSchemaV1 } from "@standard-schema/spec";
6
6
  //#region src/collection/source.d.ts
7
7
  type CollectionSource = string | ({
@@ -15,7 +15,35 @@ type CollectionSource = string | ({
15
15
  type RecordSource = Extract<CollectionSource, object> & {
16
16
  type: "json" | "yaml" | "toml";
17
17
  };
18
- type CollectionFields<SourceSchema extends Schema | undefined, ComputedSchemas extends Record<string, Schema>, Relationships extends Record<string, Relationship>, Source> = {
18
+ /** Variant values are typed `string`, so inference never depends on declaration order. */
19
+ type VariantRecord<Keys> = { [Key in keyof Keys]: string; };
20
+ /** Metadata plus computed fields, as payload enumerators and resolvers receive them. */
21
+ type PayloadData$1<SourceSchema extends Schema | undefined, ComputedSchemas extends Record<string, Schema>> = SchemaOutput<NoInfer<SourceSchema>> & { [Field in keyof NoInfer<ComputedSchemas>]: SchemaOutput<NoInfer<ComputedSchemas>[Field]>; };
22
+ /**
23
+ * Custom payload declarations. Schemas and variant keys are inferred through separate mapped
24
+ * types, so resolvers are contextually typed without annotations. Variant values are data field
25
+ * names or fixed lists rather than callbacks: TypeScript cannot infer keys from an object whose
26
+ * values are all unannotated callbacks.
27
+ */
28
+ type PayloadDeclarations<SourceSchema extends Schema | undefined, ComputedSchemas extends Record<string, Schema>, PayloadSchemas extends Record<string, Schema>, PayloadVariants extends Record<string, unknown>, Source> = { [Name in keyof PayloadSchemas]: {
29
+ schema: PayloadSchemas[Name];
30
+ }; } & { [Name in keyof PayloadVariants]: {
31
+ variants?: { [Key in keyof PayloadVariants[Name]]: VariantValues<PayloadData$1<SourceSchema, ComputedSchemas>>; };
32
+ combinations?(context: VariantContext<PayloadData$1<SourceSchema, ComputedSchemas>, Source> & {
33
+ variants: Readonly<{ [Key in keyof NoInfer<PayloadVariants>[Name]]: readonly string[]; }>;
34
+ }): Iterable<VariantRecord<NoInfer<PayloadVariants>[Name]>> | PromiseLike<Iterable<VariantRecord<NoInfer<PayloadVariants>[Name]>>>;
35
+ resolve(context: PayloadContext<PayloadData$1<SourceSchema, ComputedSchemas>, Source, VariantRecord<NoInfer<PayloadVariants>[Name]>>): unknown;
36
+ /** Set to `false` to resolve again in every revision. */
37
+ cache?: boolean;
38
+ }; } & ReservedPayloadNames<NoInfer<PayloadSchemas>>;
39
+ /**
40
+ * Rejects underscore payload names, which built-ins own. A conditional keeps declaration inference
41
+ * intact, unlike a key-remapped mapped type.
42
+ */
43
+ type ReservedPayloadNames<PayloadSchemas> = Extract<keyof PayloadSchemas, `_${string}`> extends never ? unknown : {
44
+ "Custom payload names cannot start with an underscore": Extract<keyof PayloadSchemas, `_${string}`>;
45
+ };
46
+ type CollectionFields<SourceSchema extends Schema | undefined, ComputedSchemas extends Record<string, Schema>, Relationships extends Record<string, Relationship>, PayloadSchemas extends Record<string, Schema>, PayloadVariants extends Record<string, unknown>, Source> = {
19
47
  schema?: SourceSchema;
20
48
  computed?: { [Name in keyof ComputedSchemas]: {
21
49
  schema: ComputedSchemas[Name];
@@ -26,16 +54,27 @@ type CollectionFields<SourceSchema extends Schema | undefined, ComputedSchemas e
26
54
  id: string;
27
55
  } & { [Field in keyof ComputedSchemas]: SchemaOutput<ComputedSchemas[Field]>; }) & string;
28
56
  }; };
57
+ /** Directories, relative to the project root, that resolvers' `files` helpers may also read. */
58
+ sharedFiles?: readonly string[];
59
+ /**
60
+ * The singular name in generated types, such as `"Category"` for `ContentCategory`. Defaults to
61
+ * the export name without one trailing `s`, so `jobs` generates `ContentJob`.
62
+ */
63
+ typeName?: string;
64
+ /** Named per-entry data resolved during preparation and loaded only when a query requests it. */
65
+ payloads?: PayloadDeclarations<SourceSchema, ComputedSchemas, PayloadSchemas, PayloadVariants, Source>;
29
66
  };
30
- type Definition<SourceSchema extends Schema | undefined, ComputedSchemas extends Record<string, Schema>, Relationships extends Record<string, Relationship>, ContentLoader extends Loader> = ({
67
+ type Definition<SourceSchema extends Schema | undefined, ComputedSchemas extends Record<string, Schema>, Relationships extends Record<string, Relationship>, PayloadSchemas extends Record<string, Schema>, PayloadVariants extends Record<string, unknown>, ContentLoader extends Loader> = ({
31
68
  source: CollectionSource;
32
69
  loader?: never;
33
- } & CollectionFields<SourceSchema, ComputedSchemas, Relationships, FileSource>) | ({
70
+ } & CollectionFields<SourceSchema, ComputedSchemas, Relationships, PayloadSchemas, PayloadVariants, FileSource>) | ({
34
71
  loader: ContentLoader;
35
72
  source?: never;
36
- } & CollectionFields<SourceSchema, ComputedSchemas, Relationships, LoaderSource<NoInfer<ContentLoader>>>);
73
+ } & CollectionFields<SourceSchema, ComputedSchemas, Relationships, PayloadSchemas, PayloadVariants, LoaderSource<NoInfer<ContentLoader>>>);
74
+ /** Records each declared payload's validated value type and variant record. */
75
+ type PayloadMarker<PayloadSchemas extends Record<string, Schema>, PayloadVariants extends Record<string, unknown>> = CustomPayloadMarker<{ [Name in keyof PayloadSchemas & string]: CustomPayload<SchemaOutput<PayloadSchemas[Name]>, VariantRecord<PayloadVariants[Name & keyof PayloadVariants]>>; }>;
37
76
  /** Normalizes either declaration form into a definition with a native loader. */
38
- declare function collectionOf<SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined, ComputedSchemas extends Record<string, Schema>, Relationships extends Record<string, Relationship>, ContentLoader extends Loader>(definition: Definition<SourceSchema, ComputedSchemas, Relationships, ContentLoader>): {
77
+ declare function collectionOf<SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined, ComputedSchemas extends Record<string, Schema>, Relationships extends Record<string, Relationship>, PayloadSchemas extends Record<string, Schema>, PayloadVariants extends Record<string, unknown>, ContentLoader extends Loader>(definition: Definition<SourceSchema, ComputedSchemas, Relationships, PayloadSchemas, PayloadVariants, ContentLoader>): {
39
78
  loader: ContentLoader | Loader<FileSource>;
40
79
  schema: SourceSchema;
41
80
  computed: { [Name in keyof ComputedSchemas]: {
@@ -47,6 +86,15 @@ declare function collectionOf<SourceSchema extends StandardSchemaV1<unknown, Rec
47
86
  }; };
48
87
  relationships: Relationships;
49
88
  source: CollectionSource;
89
+ /** Directories, relative to the project root, that resolvers' `files` helpers may also read. */
90
+ sharedFiles?: readonly string[];
91
+ /**
92
+ * The singular name in generated types, such as `"Category"` for `ContentCategory`. Defaults to
93
+ * the export name without one trailing `s`, so `jobs` generates `ContentJob`.
94
+ */
95
+ typeName?: string;
96
+ /** Named per-entry data resolved during preparation and loaded only when a query requests it. */
97
+ payloads?: PayloadDeclarations<SourceSchema, ComputedSchemas, PayloadSchemas, PayloadVariants, FileSource> | undefined;
50
98
  } | {
51
99
  loader: ContentLoader | Loader<FileSource>;
52
100
  schema: SourceSchema;
@@ -59,21 +107,31 @@ declare function collectionOf<SourceSchema extends StandardSchemaV1<unknown, Rec
59
107
  }; };
60
108
  relationships: Relationships;
61
109
  source?: never;
110
+ /** Directories, relative to the project root, that resolvers' `files` helpers may also read. */
111
+ sharedFiles?: readonly string[];
112
+ /**
113
+ * The singular name in generated types, such as `"Category"` for `ContentCategory`. Defaults to
114
+ * the export name without one trailing `s`, so `jobs` generates `ContentJob`.
115
+ */
116
+ typeName?: string;
117
+ /** Named per-entry data resolved during preparation and loaded only when a query requests it. */
118
+ payloads?: PayloadDeclarations<SourceSchema, ComputedSchemas, PayloadSchemas, PayloadVariants, LoaderSource<NoInfer<ContentLoader>>> | undefined;
62
119
  };
63
- type Defined<SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined, ComputedSchemas extends Record<string, Schema>, Relationships extends Record<string, Relationship>, ContentLoader extends Loader> = ReturnType<typeof collectionOf<SourceSchema, ComputedSchemas, Relationships, ContentLoader>>;
120
+ type Defined<SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined, ComputedSchemas extends Record<string, Schema>, Relationships extends Record<string, Relationship>, PayloadSchemas extends Record<string, Schema>, PayloadVariants extends Record<string, unknown>, ContentLoader extends Loader> = ReturnType<typeof collectionOf<SourceSchema, ComputedSchemas, Relationships, PayloadSchemas, PayloadVariants, ContentLoader>> & PayloadMarker<PayloadSchemas, PayloadVariants>;
64
121
  /**
65
- * Declares fields with inferred schema data and optional loader source metadata, plus relationships.
66
- * JSON, YAML, and TOML sources carry no built-in `_body` or `_rendered` payloads.
122
+ * Declares fields with inferred schema data and optional loader source metadata, plus
123
+ * relationships and custom payloads. JSON, YAML, and TOML sources carry no built-in `_body` or
124
+ * `_rendered` payloads.
67
125
  */
68
- export declare function defineCollection<const SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined = undefined, const ComputedSchemas extends Record<string, Schema> = {}, const Relationships extends Record<string, Relationship> = {}>(definition: {
126
+ export declare function defineCollection<const SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined = undefined, const ComputedSchemas extends Record<string, Schema> = {}, const Relationships extends Record<string, Relationship> = {}, const PayloadSchemas extends Record<string, Schema> = {}, const PayloadVariants extends Record<string, unknown> = {}>(definition: {
69
127
  source: RecordSource;
70
128
  loader?: never;
71
- } & CollectionFields<SourceSchema, ComputedSchemas, Relationships, FileSource>): Defined<SourceSchema, ComputedSchemas, Relationships, Loader> & BuiltinPayloadMarker<never>;
129
+ } & CollectionFields<SourceSchema, ComputedSchemas, Relationships, PayloadSchemas, PayloadVariants, FileSource>): Defined<SourceSchema, ComputedSchemas, Relationships, PayloadSchemas, PayloadVariants, Loader> & BuiltinPayloadMarker<never>;
72
130
  /**
73
- * Declares fields with inferred schema data and optional loader source metadata, plus relationships.
74
- * Record loaders such as `file()` carry no built-in `_body` or `_rendered` payloads, and body
75
- * loaders carry `_body` without `_rendered`.
131
+ * Declares fields with inferred schema data and optional loader source metadata, plus
132
+ * relationships and custom payloads. Record loaders such as `file()` carry no built-in `_body` or
133
+ * `_rendered` payloads, and body loaders carry `_body` without `_rendered`.
76
134
  */
77
- export declare function defineCollection<const SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined = undefined, const ComputedSchemas extends Record<string, Schema> = {}, const Relationships extends Record<string, Relationship> = {}, const ContentLoader extends Loader = Loader>(definition: Definition<SourceSchema, ComputedSchemas, Relationships, ContentLoader>): Defined<SourceSchema, ComputedSchemas, Relationships, ContentLoader> & BuiltinPayloadMarker<[ContentLoader] extends [RecordLoader] ? never : [ContentLoader] extends [BodyLoader] ? "_body" : keyof BuiltinPayloads>;
135
+ export declare function defineCollection<const SourceSchema extends StandardSchemaV1<unknown, Record<string, unknown>> | undefined = undefined, const ComputedSchemas extends Record<string, Schema> = {}, const Relationships extends Record<string, Relationship> = {}, const PayloadSchemas extends Record<string, Schema> = {}, const PayloadVariants extends Record<string, unknown> = {}, const ContentLoader extends Loader = Loader>(definition: Definition<SourceSchema, ComputedSchemas, Relationships, PayloadSchemas, PayloadVariants, ContentLoader>): Defined<SourceSchema, ComputedSchemas, Relationships, PayloadSchemas, PayloadVariants, ContentLoader> & BuiltinPayloadMarker<[ContentLoader] extends [RecordLoader] ? never : [ContentLoader] extends [BodyLoader] ? "_body" : keyof BuiltinPayloads>;
78
136
  //#endregion
79
- export type { BodyLoader, BuiltinPayloads, CollectionDefinition, CollectionRegistry, CollectionSource, ComputedContext, ContentHeading, ContentRenderResult, DataEntry, DataStore, FileEntrySource, FileSource, Loader, LoaderConfig, LoaderContext, LoaderLogger, LoaderSource, LoaderWatcher, MaybePromise, MetaStore, PayloadsOf, QueryData, RecordLoader, Relationship, RenderedContent };
137
+ export type { BodyLoader, BuiltinPayloads, CollectionDefinition, CollectionRegistry, CollectionSource, ComputedContext, ContentHeading, ContentRenderResult, DataEntry, DataStore, EntryFiles, FileEntry, FileEntrySource, FileSource, Loader, LoaderConfig, LoaderContext, LoaderLogger, LoaderSource, LoaderWatcher, MaybePromise, MetaStore, PayloadContext, PayloadData, PayloadDeclaration, PayloadDefinition, PayloadsOf, QueryData, QueryStyle, RecordLoader, Relationship, RenderedContent, VariantContext, VariantField, VariantOf, VariantValues };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { t as glob } from "./glob-Bv0dpvCQ.mjs";
1
+ import { t as glob } from "./glob-BXJuwy_p.mjs";
2
2
  import { t as assertServerOnly } from "./server-only-CIJUlyL-.mjs";
3
3
  //#region src/collection/source.ts
4
4
  const patterns = {