content-query 0.5.1 → 0.6.1

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 CHANGED
@@ -78,6 +78,12 @@ An entry imports only its own collection's data, so a route that imports `posts`
78
78
 
79
79
  Queries return only the data they select, not even `id` implicitly. A query must select fields, include a relationship, request a payload, or select adjacent entries; otherwise it fails to type-check and throws an `invalid-query` error. Counts need no selection.
80
80
 
81
+ Later `select()` calls add fields, so a shared builder such as `const postLink = posts.select("id", "title")` can become `postLink.select("publishedAt")` where a page needs more. For detail pages, `posts.byId(id)` starts a query for one entry, and awaiting it returns that entry or `undefined`:
82
+
83
+ ```ts
84
+ const post = await posts.byId("hello").select("title").withPayload("_rendered");
85
+ ```
86
+
81
87
  To follow a relationship, pass its target collection to `using()`. Nothing related loads implicitly:
82
88
 
83
89
  ```ts
@@ -4,7 +4,7 @@ import { i as detach, l as valuePath, o as encode } from "./values-BbTV4QT-.mjs"
4
4
  import { t as assertServerOnly } from "./server-only-CIJUlyL-.mjs";
5
5
  import { n as loadFile } from "./file-DSOcdFj0.mjs";
6
6
  import { I as collectionNames } from "./operators-D-w1-gDg.mjs";
7
- import { a as variantKey, c as CODEC_VERSION, i as isQueryStyle, n as createCollections, o as isRelationship, s as prepareRelationships } from "./collection-Cb5eWGnv.mjs";
7
+ import { a as variantKey, c as CODEC_VERSION, i as isQueryStyle, n as createCollections, o as isRelationship, s as prepareRelationships } from "./collection-DYeLLOXP.mjs";
8
8
  import { t as renderMarkdown } from "./markdown-CHvBBJNa.mjs";
9
9
  import { access, link, mkdir, readFile, readdir, realpath, rm, rmdir, stat, writeFile } from "node:fs/promises";
10
10
  import path from "node:path";
@@ -1,7 +1,7 @@
1
- import { a as InvalidRelationships, r as CollectionHandles, s as ContentError } from "./collection-types-DhIxnCSM.mjs";
2
- import { i as CollectionRegistry } from "./types-WOsFmNnN.mjs";
3
- import { n as QueryStyle } from "./query-style-C2FcT1f9.mjs";
4
- import "./revision-aqGUf6YV.mjs";
1
+ import { a as InvalidRelationships, r as CollectionHandles, s as ContentError } from "./collection-types-BjSEeKN-.mjs";
2
+ import { i as CollectionRegistry } from "./types-B_p8k_g6.mjs";
3
+ import { n as QueryStyle } from "./query-style-k8Qd7wh5.mjs";
4
+ import "./revision-C7GpzihD.mjs";
5
5
  //#region src/preparation/session.d.ts
6
6
  /** A local source captured by a successfully prepared content revision. */
7
7
  type ContentSourceEntry = Readonly<{
package/dist/build.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { a as ContentSourceEvent, i as ContentSourceEntry, n as buildContent, o as ContentSourceSnapshot, r as ContentSession, t as BuildContentOptions } from "./build-Aoi7Unm0.mjs";
1
+ import { a as ContentSourceEvent, i as ContentSourceEntry, n as buildContent, o as ContentSourceSnapshot, r as ContentSession, t as BuildContentOptions } from "./build-MlsJ12d8.mjs";
2
2
  export { type BuildContentOptions, type ContentSession, type ContentSourceEntry, type ContentSourceEvent, type ContentSourceSnapshot, buildContent };
package/dist/build.mjs CHANGED
@@ -1 +1 @@
1
- export { n as buildContent } from "./build-CIw4yWs9.mjs";
1
+ export { n as buildContent } from "./build-CfxMqm_9.mjs";
package/dist/cli.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { o as payloadIdentity, t as ContentError } from "./errors-Bfd0R4_P.mjs";
3
- import { t as buildAtRoot } from "./build-CIw4yWs9.mjs";
3
+ import { t as buildAtRoot } from "./build-CfxMqm_9.mjs";
4
4
  import { cac } from "cac";
5
5
  //#region package.json
6
- var version = "0.5.1";
6
+ var version = "0.6.1";
7
7
  //#endregion
8
8
  //#region src/integrations/cli/arguments.ts
9
9
  /** Validates one path option after CAC's automatic numeric conversion. */
@@ -320,8 +320,28 @@ function checkedPayload(registry, collection, name, request) {
320
320
  return { variant: {} };
321
321
  }
322
322
  const variant = request;
323
- if (!variant || typeof variant !== "object" || Array.isArray(variant) || Object.keys(variant).length !== variants.length || variants.some((key) => !Object.hasOwn(variant, key) || typeof variant[key] !== "string")) invalidQuery(`Payload "${name}" requires exactly these string variant keys: ${variants.join(", ") || "none"}.`);
324
- return { variant: Object.fromEntries([...variants].sort().map((key) => [key, variant[key]])) };
323
+ if (!variant || typeof variant !== "object" || Array.isArray(variant) || Object.keys(variant).length !== variants.length || variants.some((key) => !Object.hasOwn(variant, key) || !isVariantValue(variant[key]))) invalidQuery(`Payload "${name}" requires exactly these variant keys, each a string or a nonempty array of strings: ${variants.join(", ") || "none"}.`);
324
+ let combinations = [{}];
325
+ for (const key of Object.keys(variant)) {
326
+ const values = [variant[key]].flat();
327
+ combinations = combinations.flatMap((partial) => values.map((value) => ({
328
+ ...partial,
329
+ [key]: value
330
+ })));
331
+ }
332
+ const sorted = [...variants].sort();
333
+ const [first, ...fallbacks] = new Map(combinations.map((combination) => {
334
+ const canonical = Object.fromEntries(sorted.map((key) => [key, combination[key]]));
335
+ return [JSON.stringify(canonical), canonical];
336
+ })).values();
337
+ return fallbacks.length ? {
338
+ variant: first,
339
+ fallbacks
340
+ } : { variant: first };
341
+ }
342
+ /** Checks one variant key's request: a value, or a nonempty list of values to try in order. */
343
+ function isVariantValue(value) {
344
+ return typeof value === "string" || Array.isArray(value) && value.length > 0 && value.every((item) => typeof item === "string");
325
345
  }
326
346
  /** Compiles a payloads option into plan requests, omitting payloads set to false. */
327
347
  function checkedPayloads(registry, collection, payloads) {
@@ -481,7 +501,10 @@ function objectPlan(registry, collection, options, many = true, allowAdjacent =
481
501
  //#region src/query/builder.ts
482
502
  const planSymbol = Symbol("content-query.query-plan");
483
503
  const scopeSymbol = Symbol("content-query.query-scope");
484
- /** Creates immutable root or relationship builders over one backend-independent plan. */
504
+ /**
505
+ * Creates immutable root or relationship builders over one backend-independent plan. A root
506
+ * builder that is not `many` is a `byId()` query, which returns one entry or `undefined`.
507
+ */
485
508
  function createBuilder(registry, run, plan, many = true, root = true, scopeToken = {}, allowAdjacent = true, allowPagination = true) {
486
509
  const scope = registry[plan.collection];
487
510
  if (!scope) invalidQuery(`Unknown collection "${plan.collection}".`);
@@ -492,9 +515,12 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
492
515
  const builder = {
493
516
  [planSymbol]: plan,
494
517
  [scopeSymbol]: scopeToken,
518
+ /** Selects fields, adding to earlier selections; a field selected again is kept once. */
495
519
  select(...fields) {
496
- if (plan.select || fields.length === 0) invalidQuery("select() requires distinct prepared fields and can be called once per scope.");
497
- return branch({ select: checkedFields(registry, plan.collection, fields) });
520
+ if (fields.length === 0) invalidQuery("select() requires at least one prepared field.");
521
+ const checked = checkedFields(registry, plan.collection, fields);
522
+ const earlier = plan.select ?? [];
523
+ return branch({ select: [...earlier, ...checked.filter((field) => !earlier.includes(field))] });
498
524
  },
499
525
  where(configure) {
500
526
  if (typeof configure !== "function") invalidQuery("where() requires a configuration callback.");
@@ -503,6 +529,7 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
503
529
  return branch({ filters: [...plan.filters, detach(resolveRelations(registry, plan.collection, expression))] });
504
530
  },
505
531
  orderBy(field, direction = "asc") {
532
+ assertList("orderBy()");
506
533
  return branch({ order: checkedOrder(plan, field, direction, many) });
507
534
  },
508
535
  limit(value) {
@@ -541,6 +568,7 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
541
568
  if (!allowAdjacent || plan.adjacent !== void 0) invalidQuery("Adjacency cannot be repeated or selected recursively.");
542
569
  const [configure] = arguments_;
543
570
  if (configure === void 0 || typeof configure === "string") {
571
+ assertList("Results-scope selectAdjacent()");
544
572
  if (!arguments_.length || arguments_.some((field) => typeof field !== "string")) invalidQuery("selectAdjacent() requires neighbor fields, such as selectAdjacent(\"id\"), or a sibling query.");
545
573
  const selection = {
546
574
  ...emptyPlan(plan.collection),
@@ -569,11 +597,12 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
569
597
  } });
570
598
  },
571
599
  all() {
600
+ assertList("all()");
572
601
  return rows("all");
573
602
  },
574
603
  /** Executes a selected root query, matching direct await behavior. */
575
604
  execute() {
576
- return rows("all");
605
+ return rows(many ? "all" : "first");
577
606
  },
578
607
  first() {
579
608
  return rows("first");
@@ -589,6 +618,7 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
589
618
  */
590
619
  paginate(options) {
591
620
  if (!root) invalidQuery("Nested builders have no terminal methods.");
621
+ assertList("paginate()");
592
622
  if (!hasSelection(plan)) invalidQuery(NO_SELECTION_MESSAGE);
593
623
  return run({
594
624
  ...plan,
@@ -599,7 +629,7 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
599
629
  if (root) Object.defineProperty(builder, "then", { value: (fulfilled, rejected) => {
600
630
  let result;
601
631
  try {
602
- result = rows("all");
632
+ result = rows(many ? "all" : "first");
603
633
  } catch (cause) {
604
634
  result = Promise.reject(cause);
605
635
  }
@@ -614,8 +644,13 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
614
644
  if (!hasSelection(plan)) invalidQuery(NO_SELECTION_MESSAGE);
615
645
  return run(plan, terminal);
616
646
  }
647
+ /** Rejects a list-only clause or terminal on a `byId()` query, which returns one entry. */
648
+ function assertList(member) {
649
+ if (root && !many) invalidQuery(`${member} is unavailable after byId(), which returns one entry.`);
650
+ }
617
651
  /** Checks pagination before creating an independent branch. */
618
652
  function pagination(key, value) {
653
+ assertList(`${key}()`);
619
654
  if (!allowPagination || plan[key] !== void 0) invalidQuery("Pagination requires a nonnegative safe integer, one call per clause, and a many-valued scope.");
620
655
  return branch({ [key]: checkedPagination(value, many) });
621
656
  }
@@ -633,9 +668,38 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
633
668
  * @returns An immutable, reusable root builder.
634
669
  */
635
670
  function createFrom(registry, run, collection) {
671
+ return createBuilder(currentRegistry(registry, collection), run, emptyPlan(collection));
672
+ }
673
+ /**
674
+ * Starts a fluent query for the entry with one ID. The builder returns that entry or `undefined`
675
+ * when awaited, offers no ordering, pagination, `all()`, or results-scope adjacency, and still
676
+ * selects nothing until the query requests data.
677
+ *
678
+ * @param registry - Reads the current query registry, so development builders bind the latest one.
679
+ * @param run - Executes a plan against one pinned revision.
680
+ * @param collection - The collection to query.
681
+ * @param id - The entry's ID.
682
+ * @returns An immutable, reusable single-entry root builder.
683
+ */
684
+ function createById(registry, run, collection, id) {
685
+ const current = currentRegistry(registry, collection);
686
+ if (typeof id !== "string") invalidQuery("byId() requires an entry ID string.");
687
+ const filter = {
688
+ kind: "predicate",
689
+ operator: "eq",
690
+ field: "id",
691
+ value: id
692
+ };
693
+ return createBuilder(current, run, {
694
+ ...emptyPlan(collection),
695
+ filters: [filter]
696
+ }, false);
697
+ }
698
+ /** Reads the registry a new root query binds, rejecting unknown collections. */
699
+ function currentRegistry(registry, collection) {
636
700
  const current = registry();
637
701
  if (typeof collection !== "string" || !Object.hasOwn(current, collection)) invalidQuery(`Unknown collection "${String(collection)}".`);
638
- return createBuilder(current, run, emptyPlan(collection));
702
+ return current;
639
703
  }
640
704
  //#endregion
641
705
  //#region src/query/relational.ts
@@ -1275,20 +1339,29 @@ async function renderedPayload(revision, collection, id) {
1275
1339
  }
1276
1340
  return rendered;
1277
1341
  }
1278
- /** Lazily loads one custom payload variant once per revision, retrying after failures. */
1279
- function loadCustom(revision, collection, id, name, variant) {
1342
+ /**
1343
+ * Lazily loads one custom payload variant once per revision, retrying after failures. The request's
1344
+ * combinations are tried in order, and only the first one the entry has is loaded.
1345
+ */
1346
+ function loadCustom(revision, collection, id, name, request) {
1280
1347
  const entries = revision.collections[collection].payloads?.get(name);
1281
1348
  if (!entries) invalidQuery(`Payload "${name}" no longer matches the prepared collection.`);
1282
- const load = entries.get(id)?.get(variantKey(variant));
1283
- if (!load) return Promise.reject(new ContentError({
1284
- code: "variant-not-found",
1285
- operation: "query",
1286
- collection,
1287
- id,
1288
- payload: name,
1289
- variant,
1290
- message: `Entry "${id}" has no ${payloadIdentity(name, variant)} payload.`
1291
- }));
1349
+ const candidates = [request.variant, ...request.fallbacks ?? []];
1350
+ const variants = entries.get(id);
1351
+ const variant = candidates.find((candidate) => variants?.has(variantKey(candidate)));
1352
+ const load = variant && variants.get(variantKey(variant));
1353
+ if (!variant || !load) {
1354
+ const tried = candidates.map((candidate) => payloadIdentity(name, candidate));
1355
+ return Promise.reject(new ContentError({
1356
+ code: "variant-not-found",
1357
+ operation: "query",
1358
+ collection,
1359
+ id,
1360
+ payload: name,
1361
+ variant: request.variant,
1362
+ message: tried.length === 1 ? `Entry "${id}" has no ${tried[0]} payload.` : `Entry "${id}" has none of the ${tried.join(", ")} payloads.`
1363
+ }));
1364
+ }
1292
1365
  const key = JSON.stringify([
1293
1366
  collection,
1294
1367
  id,
@@ -1313,10 +1386,10 @@ function loadCustom(revision, collection, id, name, variant) {
1313
1386
  return loaded;
1314
1387
  }
1315
1388
  /** Loads one requested payload value from the pinned revision. */
1316
- async function loadPayload(revision, collection, id, name, variant) {
1389
+ async function loadPayload(revision, collection, id, name, request) {
1317
1390
  if (name === "_body") return (await loadBuiltin(revision, collection, id)).body;
1318
1391
  if (name === "_rendered") return renderedPayload(revision, collection, id);
1319
- return loadCustom(revision, collection, id, name, variant);
1392
+ return loadCustom(revision, collection, id, name, request);
1320
1393
  }
1321
1394
  /**
1322
1395
  * Loads every requested payload with bounded concurrency, then attaches detached copies under
@@ -1336,13 +1409,13 @@ async function attachPayloads(revision, requests) {
1336
1409
  collection: request.collection,
1337
1410
  id: request.id,
1338
1411
  name,
1339
- variant: request.payloads[name].variant
1412
+ request: request.payloads[name]
1340
1413
  });
1341
1414
  }
1342
1415
  const keys = [...loads.keys()];
1343
1416
  const values = await mapConcurrent(keys, PAYLOAD_CONCURRENCY, (key) => {
1344
- const { collection, id, name, variant } = loads.get(key);
1345
- return loadPayload(revision, collection, id, name, variant);
1417
+ const { collection, id, name, request } = loads.get(key);
1418
+ return loadPayload(revision, collection, id, name, request);
1346
1419
  });
1347
1420
  const loaded = new Map(keys.map((key, index) => [key, values[index]]));
1348
1421
  for (const request of requests) request.target._payloads = Object.fromEntries(Object.keys(request.payloads).map((name) => [name, detach(loaded.get(JSON.stringify([
@@ -1376,6 +1449,7 @@ function createRunner(state) {
1376
1449
  */
1377
1450
  const queryStyleMembers = {
1378
1451
  fluent: [
1452
+ "byId",
1379
1453
  "select",
1380
1454
  "where",
1381
1455
  "orderBy",
@@ -1602,6 +1676,7 @@ function createCollection(primary, supplied = []) {
1602
1676
  };
1603
1677
  for (const member of queryStyleMembers.fluent) handle[member] = (...arguments_) => {
1604
1678
  allow(member);
1679
+ if (member === "byId") return createById(registry, run, name, arguments_[0]);
1605
1680
  return createFrom(registry, run, name)[member](...arguments_);
1606
1681
  };
1607
1682
  handleSources.set(handle, sources);
@@ -1,6 +1,7 @@
1
- import { _ as Relationship, g as QueryData, i as CollectionRegistry, u as LoadedMarker, v as RelationshipsOf } from "./types-WOsFmNnN.mjs";
2
- import { n as RelationalCountOptions, t as RelationalCollectionQueries, v as Query } from "./relational-types-PtrzENt0.mjs";
3
- import { i as QueryStyleMember, n as QueryStyle, r as QueryStyleDisabled, t as MembersOf } from "./query-style-C2FcT1f9.mjs";
1
+ import { _ as Unavailable } from "./descriptor-BseHJvUB.mjs";
2
+ import { _ as Relationship, g as QueryData, i as CollectionRegistry, u as LoadedMarker, v as RelationshipsOf } from "./types-B_p8k_g6.mjs";
3
+ import { n as RelationalCountOptions, t as RelationalCollectionQueries, v as Query } from "./relational-types-Bh34u01H.mjs";
4
+ import { i as QueryStyleMember, n as QueryStyle, r as QueryStyleDisabled, t as MembersOf } from "./query-style-k8Qd7wh5.mjs";
4
5
  //#region src/shared/errors.d.ts
5
6
  type ContentErrorCode = "variant-not-found" | "not-renderable" | "source-error" | "validation-error" | "duplicate-id" | "invalid-reference" | "invalid-query" | "invalid-cursor" | "artifact-error" | "render-error";
6
7
  type ContentDiagnostic = {
@@ -58,13 +59,23 @@ type LoadedRegistry<Registry extends CollectionRegistry, Loaded extends keyof Re
58
59
  type FluentMember = MembersOf<"fluent">;
59
60
  /** The relational members, apart from `count`, which both styles share. */
60
61
  type RelationalMember = "findMany" | "findFirst" | "paginate";
62
+ /** Fluent starters that only a collection handle offers, not a query builder. */
63
+ type HandleStarters<Registry extends CollectionRegistry, Name extends keyof Registry & string> = {
64
+ /**
65
+ * Starts a query for the entry with this ID. Select its data as usual; awaiting the query, or
66
+ * calling `first()` or `execute()`, returns the entry or `undefined`. Later `where()` filters
67
+ * also apply. Lookups use the collection's ID index.
68
+ *
69
+ * @param id - The entry's ID.
70
+ * @returns A single-entry root query, without ordering, pagination, `all()`, or results-scope
71
+ * adjacency.
72
+ */
73
+ byId(id: string): Query<Registry, Name, {}, false, false>;
74
+ };
61
75
  /** 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; };
76
+ type CollectionMethods<Registry extends CollectionRegistry, Name extends keyof Registry & string> = Pick<Query<Registry, Name>, FluentMember & keyof Query<Registry, Name>> & HandleStarters<Registry, Name> & RelationalCollectionQueries<Registry, Name>;
77
+ /** Members outside the allowed style. They are not callable, and their type gives the message. */
78
+ type DisabledMembers<Name extends string, Style extends QueryStyle> = { readonly [Member in Exclude<FluentMember | RelationalMember, MembersOf<Style>>]: Unavailable<QueryStyleDisabled<Name, Member & QueryStyleMember, Style>>; };
68
79
  /** `count()` counts every entry in both styles; options take relational filters. */
69
80
  type CountMember<Registry extends CollectionRegistry, Name extends keyof Registry & string, Style extends QueryStyle> = [Style] extends ["fluent"] ? {
70
81
  /** Counts every entry. Count filtered entries with `where(...).count()`. */
@@ -1,6 +1,55 @@
1
- import { g as MaybePromise } from "./descriptor-bcPa6XLk.mjs";
2
- import { a as ComputedContext } from "./types-WOsFmNnN.mjs";
1
+ import { g as MaybePromise } from "./descriptor-BseHJvUB.mjs";
2
+ import { a as ComputedContext } from "./types-B_p8k_g6.mjs";
3
3
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
+ //#region src/computed/fields.d.ts
5
+ /** Result schemas for computed fields that one resolver call fills, keyed by field name. */
6
+ type ComputedFieldSchemas = Record<string, StandardSchemaV1>;
7
+ /**
8
+ * The object a shared resolver returns: one value per field, checked by that field's schema.
9
+ * Fields whose schema accepts `undefined` may be omitted.
10
+ */
11
+ type ComputedFieldValues<Fields extends ComputedFieldSchemas> = { [Key in keyof Fields as undefined extends StandardSchemaV1.InferInput<Fields[Key]> ? never : Key]: StandardSchemaV1.InferInput<Fields[Key]>; } & { [Key in keyof Fields as undefined extends StandardSchemaV1.InferInput<Fields[Key]> ? Key : never]?: StandardSchemaV1.InferInput<Fields[Key]>; };
12
+ /** Declares several computed fields and the one resolver that returns all of their values. */
13
+ type ComputedFieldsOptions<Fields extends ComputedFieldSchemas, Data = Record<string, unknown>, Source = unknown> = {
14
+ /**
15
+ * Each field's result schema, keyed by field name. A JSDoc comment on a key documents that
16
+ * field in entry types and query results.
17
+ */
18
+ fields: Fields;
19
+ /**
20
+ * Returns every field's value for one entry. It runs once per entry each time the entry is
21
+ * prepared, however many fields it fills; files it reads through `files` are tracked as usual.
22
+ * Annotate the context, such as `(context: ComputedContext<Post>)`, to type `data`.
23
+ */
24
+ resolve(this: void, context: ComputedContext<Data, Source>): MaybePromise<ComputedFieldValues<Fields>>;
25
+ };
26
+ /** The computed field definitions that {@link computedFields} returns, one per declared field. */
27
+ type ComputedFieldDefinitions<Fields extends ComputedFieldSchemas, Data = Record<string, unknown>, Source = unknown> = { [Key in keyof Fields]: {
28
+ schema: Fields[Key];
29
+ resolve(context: ComputedContext<Data, Source>): Promise<unknown>;
30
+ }; };
31
+ /**
32
+ * Creates computed fields that share one resolver call per entry, for values that come from the
33
+ * same expensive read, such as several properties of one JSON file beside the entry. Spread the
34
+ * result into `computed`; each field is validated by its own schema and is selected, filtered, and
35
+ * sorted like any other computed field.
36
+ *
37
+ * @example
38
+ * ```ts
39
+ * computed: {
40
+ * ...computedFields({
41
+ * fields: { title: schema.string(), description: schema.string() },
42
+ * resolve: async ({ files }) => JSON.parse(await files.text("info/en-US.json")),
43
+ * }),
44
+ * }
45
+ * ```
46
+ *
47
+ * @param options - The fields' result schemas and the resolver that returns their values.
48
+ * @returns One computed field definition per key of `options.fields`.
49
+ * @throws TypeError - When `fields` declares no schemas or `resolve` is not a function.
50
+ */
51
+ export declare function computedFields<const Fields extends ComputedFieldSchemas, Data = Record<string, unknown>, Source = unknown>(options: ComputedFieldsOptions<Fields, Data, Source>): ComputedFieldDefinitions<Fields, Data, Source>;
52
+ //#endregion
4
53
  //#region src/computed/text.d.ts
5
54
  /** The syntax removed before computing text-derived fields; literal text preserves all characters. */
6
55
  type TextFormat = "markdown" | "text" | "html";
@@ -40,4 +89,4 @@ export declare function excerpt<Data = Record<string, unknown>, Source = unknown
40
89
  resolve(context: ComputedContext<Data, Source>): Promise<string>;
41
90
  };
42
91
  //#endregion
43
- export type { ExcerptOptions, ReadingTimeOptions, TextFormat, TextOptions };
92
+ export type { ComputedFieldDefinitions, ComputedFieldSchemas, ComputedFieldValues, ComputedFieldsOptions, ExcerptOptions, ReadingTimeOptions, TextFormat, TextOptions };
package/dist/computed.mjs CHANGED
@@ -3,6 +3,63 @@ import { n as number, r as string } from "./schema-C5Str-E8.mjs";
3
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
+ //#region src/computed/fields.ts
7
+ assertServerOnly();
8
+ /**
9
+ * Creates computed fields that share one resolver call per entry, for values that come from the
10
+ * same expensive read, such as several properties of one JSON file beside the entry. Spread the
11
+ * result into `computed`; each field is validated by its own schema and is selected, filtered, and
12
+ * sorted like any other computed field.
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * computed: {
17
+ * ...computedFields({
18
+ * fields: { title: schema.string(), description: schema.string() },
19
+ * resolve: async ({ files }) => JSON.parse(await files.text("info/en-US.json")),
20
+ * }),
21
+ * }
22
+ * ```
23
+ *
24
+ * @param options - The fields' result schemas and the resolver that returns their values.
25
+ * @returns One computed field definition per key of `options.fields`.
26
+ * @throws TypeError - When `fields` declares no schemas or `resolve` is not a function.
27
+ */
28
+ function computedFields(options) {
29
+ const { fields, resolve } = options ?? {};
30
+ if (!fields || typeof fields !== "object" || Array.isArray(fields)) throw new TypeError("computedFields() fields must be an object of result schemas.");
31
+ const entries = Object.entries(fields);
32
+ if (!entries.length || entries.some(([, schema]) => !isSchema(schema))) throw new TypeError("computedFields() fields must declare at least one field, each with a Standard Schema.");
33
+ if (typeof resolve !== "function") throw new TypeError("computedFields() resolve must be a function.");
34
+ const resolutions = /* @__PURE__ */ new WeakMap();
35
+ /** Resolves every field's value once for the entry being prepared. */
36
+ function values(context) {
37
+ let pending = resolutions.get(context.files);
38
+ if (!pending) {
39
+ pending = (async () => {
40
+ context.signal.throwIfAborted();
41
+ const result = await resolve(context);
42
+ if (!result || typeof result !== "object" || Array.isArray(result)) throw new TypeError("computedFields() resolve must return an object with a value for each field.");
43
+ return result;
44
+ })();
45
+ resolutions.set(context.files, pending);
46
+ }
47
+ return pending;
48
+ }
49
+ return Object.fromEntries(entries.map(([name, schema]) => [name, {
50
+ schema,
51
+ /** Reads this field's value from the entry's shared resolution. */
52
+ async resolve(context) {
53
+ const result = await values(context);
54
+ return Object.hasOwn(result, name) ? result[name] : void 0;
55
+ }
56
+ }]));
57
+ }
58
+ /** Checks for a Standard Schema V1 validator. */
59
+ function isSchema(value) {
60
+ return (typeof value === "object" || typeof value === "function") && value !== null && "~standard" in value;
61
+ }
62
+ //#endregion
6
63
  //#region src/computed/plain-text.ts
7
64
  const markdown = new src_default({ html: true });
8
65
  const ignoredElements = /* @__PURE__ */ new Set([
@@ -136,4 +193,4 @@ function excerpt(options = {}) {
136
193
  return textField(string(), options, (text) => truncate(text, maxLength));
137
194
  }
138
195
  //#endregion
139
- export { excerpt, readingTime, wordCount };
196
+ export { computedFields, excerpt, readingTime, wordCount };
@@ -1,6 +1,12 @@
1
1
  import { r as RenderedContent } from "./types-Dh-Sydyw.mjs";
2
2
  //#region src/shared/types.d.ts
3
3
  type MaybePromise<Value> = Value | Promise<Value>;
4
+ /**
5
+ * The type of a member that a query or configuration does not allow. It has no call signatures,
6
+ * so calling the member fails with the reason near the start of the error:
7
+ * `This expression is not callable. Type 'Unavailable<"…">' has no call signatures.`
8
+ */
9
+ type Unavailable<Reason extends string> = { readonly [Key in Reason]: never; };
4
10
  //#endregion
5
11
  //#region src/loaders/types.d.ts
6
12
  declare const loaderSourceType: unique symbol;
@@ -165,4 +171,4 @@ type FileOptions = {
165
171
  parser?: (text: string) => MaybePromise<unknown>;
166
172
  };
167
173
  //#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 };
174
+ export { Unavailable as _, 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 };
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
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";
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-BseHJvUB.mjs";
2
2
  import { n as ContentRenderResult, r as RenderedContent, t as ContentHeading } from "./types-Dh-Sydyw.mjs";
3
- import { D as VariantValues, E as VariantOf, T as VariantField, _ 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 VariantContext, x as SelectedData, y as Schema } from "./types-WOsFmNnN.mjs";
4
- import { n as QueryStyle } from "./query-style-C2FcT1f9.mjs";
3
+ import { D as VariantRequest, E as VariantOf, O as VariantValues, T as VariantField, _ 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 VariantContext, x as SelectedData, y as Schema } from "./types-B_p8k_g6.mjs";
4
+ import { n as QueryStyle } from "./query-style-k8Qd7wh5.mjs";
5
5
  import { StandardSchemaV1 } from "@standard-schema/spec";
6
6
  //#region src/collection/source.d.ts
7
7
  type CollectionSource = string | ({
@@ -134,4 +134,4 @@ export declare function defineCollection<const SourceSchema extends StandardSche
134
134
  */
135
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>;
136
136
  //#endregion
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, SelectedData, VariantContext, VariantField, VariantOf, VariantValues };
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, SelectedData, VariantContext, VariantField, VariantOf, VariantRequest, VariantValues };
@@ -1,9 +1,9 @@
1
- import { n as Collection, r as CollectionHandles, s as ContentError } from "./collection-types-DhIxnCSM.mjs";
1
+ import { n as Collection, r as CollectionHandles, s as ContentError } from "./collection-types-BjSEeKN-.mjs";
2
2
  import { n as ContentRenderResult } from "./types-Dh-Sydyw.mjs";
3
- import { i as CollectionRegistry } from "./types-WOsFmNnN.mjs";
4
- import { O as QueryRegistry } from "./relational-types-PtrzENt0.mjs";
5
- import { n as QueryStyle } from "./query-style-C2FcT1f9.mjs";
6
- import { n as PreparedRevision, t as ContentState } from "./revision-aqGUf6YV.mjs";
3
+ import { i as CollectionRegistry } from "./types-B_p8k_g6.mjs";
4
+ import { O as QueryRegistry } from "./relational-types-Bh34u01H.mjs";
5
+ import { n as QueryStyle } from "./query-style-k8Qd7wh5.mjs";
6
+ import { n as PreparedRevision, t as ContentState } from "./revision-C7GpzihD.mjs";
7
7
  //#region src/artifacts/types.d.ts
8
8
  type ArtifactManifest = {
9
9
  version: number;
package/dist/internal.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { t as ContentError } from "./errors-Bfd0R4_P.mjs";
2
2
  import { r as decode } from "./values-BbTV4QT-.mjs";
3
- import { i as isQueryStyle, n as createCollections, r as createLiveCollection, t as createCollection } from "./collection-Cb5eWGnv.mjs";
3
+ import { i as isQueryStyle, n as createCollections, r as createLiveCollection, t as createCollection } from "./collection-DYeLLOXP.mjs";
4
4
  import { t as restoreContentError } from "./diagnostics-DmUZtrJ6.mjs";
5
5
  //#region src/artifacts/runtime.ts
6
6
  /** Checks that a value is a plain record before reading generated payload tables from it. */
@@ -1,4 +1,4 @@
1
- import { a as DataStore, c as Loader, d as LoaderLogger, f as LoaderSource, 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, t as FileOptions, u as LoaderContext } from "./descriptor-bcPa6XLk.mjs";
1
+ import { a as DataStore, c as Loader, d as LoaderLogger, f as LoaderSource, 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, t as FileOptions, u as LoaderContext } from "./descriptor-BseHJvUB.mjs";
2
2
  //#region src/loaders/file.d.ts
3
3
  /** Declares a multi-entry data file with typed source paths and record keys, plus an optional parser. */
4
4
  export declare function file(filename: string, options?: FileOptions): RecordLoader<FileEntrySource>;
package/dist/next.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { t as BuildContentOptions } from "./build-Aoi7Unm0.mjs";
1
+ import { t as BuildContentOptions } from "./build-MlsJ12d8.mjs";
2
2
  import { NextConfig } from "next";
3
3
  //#region src/integrations/next/plugin.d.ts
4
4
  /** Content locations for Next.js; the adapter chooses generated storage and the watch lifecycle. */
package/dist/next.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { I as collectionNames } from "./operators-D-w1-gDg.mjs";
2
- import { c as collectionDeclarations, i as resolveConfigPath, t as buildAtRoot } from "./build-CIw4yWs9.mjs";
2
+ import { c as collectionDeclarations, i as resolveConfigPath, t as buildAtRoot } from "./build-CfxMqm_9.mjs";
3
3
  import { n as serializeContentError } from "./diagnostics-DmUZtrJ6.mjs";
4
4
  import path from "node:path";
5
5
  import { randomUUID } from "node:crypto";
@@ -7,7 +7,7 @@ type QueryStyle = "fluent" | "relational";
7
7
  * belongs to both styles; `count(options)` takes relational options.
8
8
  */
9
9
  declare const queryStyleMembers: {
10
- readonly fluent: readonly ["select", "where", "orderBy", "limit", "offset", "with", "withPayload", "selectAdjacent"];
10
+ readonly fluent: readonly ["byId", "select", "where", "orderBy", "limit", "offset", "with", "withPayload", "selectAdjacent"];
11
11
  readonly relational: readonly ["findMany", "findFirst", "paginate", "count(options)"];
12
12
  };
13
13
  /** A collection member that belongs to exactly one query style. */
package/dist/query.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { C as Selection, E as Expression, S as ScalarField, T as Where, _ as PaginateResult, a as RelationalQueryOptions, b as RelationField, c as Adjacent, d as Field, f as Fields, g as PaginateOptions, h as PageOptions, i as RelationalQueries, l as CursorOptions, m as Ordered, n as RelationalCountOptions, o as RelationalQueryResult, p as NumberedPage, r as RelationalPaginateOptions, s as RelationalWhere, u as CursorPage, w as SelectionResult, x as Scalar, y as QueryFields } from "./relational-types-PtrzENt0.mjs";
1
+ import { C as Selection, E as Expression, S as ScalarField, T as Where, _ as PaginateResult, a as RelationalQueryOptions, b as RelationField, c as Adjacent, d as Field, f as Fields, g as PaginateOptions, h as PageOptions, i as RelationalQueries, l as CursorOptions, m as Ordered, n as RelationalCountOptions, o as RelationalQueryResult, p as NumberedPage, r as RelationalPaginateOptions, s as RelationalWhere, u as CursorPage, w as SelectionResult, x as Scalar, y as QueryFields } from "./relational-types-Bh34u01H.mjs";
2
2
  //#region src/query/operators.d.ts
3
3
  /** Matches a scalar using strict value/date equality. */
4
4
  export declare function eq<Value>(field: ScalarField<Value>, value: NoInfer<Value>): Expression;
@@ -1,4 +1,5 @@
1
- import { C as UnavailableRelationshipsOf, E as VariantOf, S as UnavailableRelationship, _ as Relationship, g as QueryData, h as PayloadsOf, i as CollectionRegistry, v as RelationshipsOf, x as SelectedData } from "./types-WOsFmNnN.mjs";
1
+ import { _ as Unavailable } from "./descriptor-BseHJvUB.mjs";
2
+ import { C as UnavailableRelationshipsOf, D as VariantRequest, E as VariantOf, S as UnavailableRelationship, _ as Relationship, g as QueryData, h as PayloadsOf, i as CollectionRegistry, v as RelationshipsOf, x as SelectedData } from "./types-B_p8k_g6.mjs";
2
3
  //#region src/query/plan.d.ts
3
4
  type PredicateOperator = "eq" | "ne" | "gt" | "gte" | "lt" | "lte" | "in-array" | "not-in-array" | "contains" | "starts-with" | "ends-with" | "like" | "ilike" | "array-contains" | "array-contains-any" | "array-contains-all" | "array-contained" | "is-null" | "is-undefined" | "is-missing";
4
5
  type Expression = {
@@ -67,9 +68,13 @@ type AdjacentPlan = {
67
68
  scope: "results" | "collection";
68
69
  plan: QueryPlan;
69
70
  };
70
- /** One requested payload; variant keys are empty for payloads without variants. */
71
+ /**
72
+ * One requested payload; variant keys are empty for payloads without variants. `fallbacks` lists
73
+ * further combinations to try, in order, when the entry lacks `variant`.
74
+ */
71
75
  type PlanPayload = {
72
76
  variant: Readonly<Record<string, string>>;
77
+ fallbacks?: readonly Readonly<Record<string, string>>[];
73
78
  };
74
79
  /** How a plan returns its results; `paginate` requires the plan's `paginate` request. */
75
80
  type QueryTerminal = "all" | "first" | "count" | "paginate";
@@ -96,8 +101,6 @@ declare const PAGINATE_AFTER_LIMIT_MESSAGE: "paginate() sets the limit and offse
96
101
  //#endregion
97
102
  //#region src/query/types.d.ts
98
103
  declare const queryType: unique symbol;
99
- /** Running an unselected query reports the explicit-selection rule as its required `this` type. */
100
- type UnselectedTerminal = (this: typeof NO_SELECTION_MESSAGE, ...arguments_: readonly unknown[]) => never;
101
104
  /** A `then` that cannot resolve a value, so awaiting an unselected query fails to compile. */
102
105
  type UnselectedThen = (selectBeforeAwaiting: typeof NO_SELECTION_MESSAGE) => never;
103
106
  declare const fieldType: unique symbol;
@@ -127,12 +130,23 @@ type QueryInfo<Result = unknown, Selected extends boolean = boolean, Filtered ex
127
130
  filtered: Filtered;
128
131
  };
129
132
  };
130
- type Simplify$1<Value> = { [Key in keyof Value]: Value[Key]; };
131
- /** Adds one requested payload to a result's `_payloads`, keeping earlier requests. */
132
- type WithPayload<Result, Key extends PropertyKey, Value> = Omit<Result, "_payloads"> & {
133
- _payloads: Simplify$1<(Result extends {
134
- _payloads: infer Existing;
135
- } ? Existing : {}) & { [Name in Key]: Value; }>;
133
+ /**
134
+ * Flattens an intersection into one object type, keeping each property's declaration. The
135
+ * conditional resolves to a mapped type that no alias owns, so TypeScript compares two rows by
136
+ * their properties instead of by the collection definitions in their type arguments. Comparing
137
+ * those definitions computes variances deep inside schema libraries such as Zod, which can exceed
138
+ * the instantiation limit when `Promise.all()` or `Awaited` infers a query's result.
139
+ */
140
+ type Simplify$1<Value> = Value extends (infer Shape) ? { [Key in keyof Shape]: Shape[Key]; } : never;
141
+ /** One returned entry: the accumulated result, flattened, with its `_payloads` flattened too. */
142
+ type Row<Result> = Result extends (infer Shape) ? { [Key in keyof Shape]: Key extends "_payloads" ? Simplify$1<Shape[Key]> : Shape[Key]; } : never;
143
+ /**
144
+ * Adds one requested payload under `_payloads`. Intersecting with earlier requests merges their
145
+ * `_payloads` objects; {@link Row} flattens them when the query returns. Unlike `Omit`, this adds
146
+ * no type that later calls must re-evaluate, so long chains stay cheap to check.
147
+ */
148
+ type WithPayload<Result, Key extends PropertyKey, Value> = Result & {
149
+ _payloads: { [Name in Key]: Value; };
136
150
  };
137
151
  type ResultOf<Configured> = Configured extends QueryInfo<infer Result> ? Result : never;
138
152
  type FilteredOf<Configured> = Configured extends QueryInfo<unknown, boolean, infer Filtered> ? Filtered : false;
@@ -140,9 +154,10 @@ type RelationOf<Registry extends CollectionRegistry, Name extends keyof Registry
140
154
  type RelatedName$1<Registry extends CollectionRegistry, Relation extends Relationship> = Relation["collection"] & keyof Registry & string;
141
155
  /** Applies declared relationship cardinality, optional reference fields, and filtering to a result. */
142
156
  type RelationshipResult<Definition, Relation extends Relationship, Result, Filtered extends boolean = false> = Relation["type"] extends "many" ? Result[] : Result | (Filtered extends true ? null : Relation["referenceSide"] extends "from" ? Relation["from"] extends keyof QueryData<Definition> ? null extends QueryData<Definition>[Relation["from"]] ? null : undefined extends QueryData<Definition>[Relation["from"]] ? null : never : null : null);
143
- type RelationValue<Definition, Relation extends Relationship, Configured> = RelationshipResult<Definition, Relation, ResultOf<Configured>, FilteredOf<Configured>>;
157
+ type RelationValue<Definition, Relation extends Relationship, Configured> = RelationshipResult<Definition, Relation, Row<ResultOf<Configured>>, FilteredOf<Configured>>;
144
158
  type Query<Registry extends CollectionRegistry, Name extends keyof Registry & string, Result = {}, Selected extends boolean = false, Many extends boolean = true, Root extends boolean = true, Filtered extends boolean = false, AllowAdjacent extends boolean = true, Pagination extends boolean = true, Paged extends boolean = false> = QueryInfo<Result, Selected, Filtered> & {
145
- select<const Keys extends readonly [keyof QueryData<Registry[Name]> & string, ...(keyof QueryData<Registry[Name]> & string)[]]>(...keys: Keys): Query<Registry, Name, Simplify$1<{ [Key in Exclude<keyof Result, keyof QueryData<Registry[Name]>>]: Result[Key]; } & SelectedData<QueryData<Registry[Name]>, Keys[number]>>, true, Many, Root, Filtered, AllowAdjacent, Pagination, Paged>;
159
+ /** Selects fields, adding to fields that earlier `select()` calls on this scope selected. */
160
+ select<const Keys extends readonly [keyof QueryData<Registry[Name]> & string, ...(keyof QueryData<Registry[Name]> & string)[]]>(...keys: Keys): Query<Registry, Name, Simplify$1<Result & SelectedData<QueryData<Registry[Name]>, Keys[number]>>, true, Many, Root, Filtered, AllowAdjacent, Pagination, Paged>;
146
161
  where(configure: (fields: QueryFields<Registry, Name>) => Expression): Query<Registry, Name, Result, Selected, Many, Root, true, AllowAdjacent, Pagination, Paged>;
147
162
  with<Key extends keyof RelationshipsOf<Registry[Name]> & string, Configured extends QueryInfo<unknown, true>>(name: Key, configure: (query: Query<Registry, RelatedName$1<Registry, RelationOf<Registry, Name, Key>>, {}, false, RelationOf<Registry, Name, Key>["type"] extends "many" ? true : false, false, false, AllowAdjacent>) => Configured): Query<Registry, Name, Result & { [Property in Key]: RelationValue<Registry[Name], RelationOf<Registry, Name, Key>, Configured>; }, true, Many, Root, Filtered, AllowAdjacent, Pagination, Paged>;
148
163
  /** A relationship into a collection the query has not loaded reports which one to supply. */
@@ -152,36 +167,42 @@ type Query<Registry extends CollectionRegistry, Name extends keyof Registry & st
152
167
  } : {}) & (Many extends true ? Pagination extends true ? {
153
168
  limit(value: number): Query<Registry, Name, Result, Selected, Many, Root, Filtered, AllowAdjacent, Pagination, true>;
154
169
  offset(value: number): Query<Registry, Name, Result, Selected, Many, Root, Filtered, AllowAdjacent, Pagination, true>;
155
- } : {} : {}) & (AllowAdjacent extends true ? {
156
- /** Attaches a payload under `_payloads` after filtering, ordering, and pagination. */
157
- withPayload<Key extends keyof PayloadsOf<Registry[Name]> & string>(name: Key, ...variant: keyof VariantOf<Registry[Name], Key> extends never ? [] : [variant: Readonly<VariantOf<Registry[Name], Key>>]): Query<Registry, Name, WithPayload<Result, Key, PayloadsOf<Registry[Name]>[Key]>, true, Many, Root, Filtered, AllowAdjacent, Pagination, Paged>;
170
+ } : {} : {}) & (AllowAdjacent extends true ? [Root, Many] extends [true, false] ? {} : {
158
171
  /** Selects neighbor fields within this scope's filtered and ordered results. */
159
172
  selectAdjacent<const Keys extends readonly [keyof QueryData<Registry[Name]> & string, ...(keyof QueryData<Registry[Name]> & string)[]]>(...fields: Keys): Query<Registry, Name, Result & {
160
173
  _adjacent: Adjacent<SelectedData<QueryData<Registry[Name]>, Keys[number]>>;
161
174
  }, true, Many, Root, Filtered, AllowAdjacent, Pagination, Paged>;
175
+ } : {}) & (AllowAdjacent extends true ? {
176
+ /** Attaches a payload under `_payloads` after filtering, ordering, and pagination. */
177
+ withPayload<Key extends keyof PayloadsOf<Registry[Name]> & string>(name: Key, ...variant: keyof VariantOf<Registry[Name], Key> extends never ? [] : [variant: VariantRequest<VariantOf<Registry[Name], Key>>]): Query<Registry, Name, WithPayload<Result, Key, PayloadsOf<Registry[Name]>[Key]>, true, Many, Root, Filtered, AllowAdjacent, Pagination, Paged>;
178
+ /** Selects neighbors from an independent sibling scope. */
162
179
  selectAdjacent<Configured extends QueryInfo<unknown, true>>(configure: (siblings: Query<Registry, Name, {}, false, true, false, false, false, false>) => Configured): Query<Registry, Name, Result & {
163
180
  _adjacent: Adjacent<ResultOf<Configured>>;
164
181
  }, true, Many, Root, Filtered, AllowAdjacent, Pagination, Paged>;
165
182
  selectAdjacent<const Options extends AdjacentOptions<Registry, Name>>(options: Options & CheckedOptions<Registry, Name, NoInfer<Options>, true, false, false>): Query<Registry, Name, Result & {
166
183
  _adjacent: Adjacent<SelectionResult<Registry, Name, Options["select"]>>;
167
184
  }, true, Many, Root, Filtered, AllowAdjacent, Pagination, Paged>;
168
- } : {}) & (Root extends true ? {
169
- all: Selected extends true ? () => Promise<Simplify$1<Result>[]> : UnselectedTerminal;
170
- execute: Selected extends true ? () => Promise<Simplify$1<Result>[]> : UnselectedTerminal;
171
- then: Selected extends true ? Promise<Simplify$1<Result>[]>["then"] : UnselectedThen;
172
- first: Selected extends true ? () => Promise<Simplify$1<Result> | undefined> : UnselectedTerminal;
173
- /** Counts matching entries without a selection; unavailable once payloads are requested. */
174
- count: [Result] extends [never] ? () => Promise<number> : [Result] extends [{
175
- _payloads: unknown;
176
- }] ? never : () => Promise<number>;
185
+ } : {}) & (Root extends true ? (Many extends true ? {
186
+ all: Selected extends true ? () => Promise<Row<Result>[]> : Unavailable<typeof NO_SELECTION_MESSAGE>;
187
+ execute: Selected extends true ? () => Promise<Row<Result>[]> : Unavailable<typeof NO_SELECTION_MESSAGE>;
188
+ then: Selected extends true ? Promise<Row<Result>[]>["then"] : UnselectedThen;
177
189
  /**
178
190
  * Returns one page from one revision: `{ page, size }` for page numbers, or
179
- * `{ size, after?, before?, total? }` for cursors. Unavailable after `limit()` or `offset()`.
191
+ * `{ size, after?, before?, total? }` for cursors. Unavailable after `limit()` or
192
+ * `offset()`.
180
193
  */
181
- paginate: Selected extends true ? [Paged] extends [true] ? PaginateAfterLimit : Paginate<Simplify$1<Result>> : UnselectedTerminal;
194
+ paginate: Selected extends true ? [Paged] extends [true] ? Unavailable<typeof PAGINATE_AFTER_LIMIT_MESSAGE> : Paginate<Row<Result>> : Unavailable<typeof NO_SELECTION_MESSAGE>;
195
+ } : {
196
+ /** Runs a `byId()` query, matching direct await: the entry, or `undefined`. */
197
+ execute: Selected extends true ? () => Promise<Row<Result> | undefined> : Unavailable<typeof NO_SELECTION_MESSAGE>;
198
+ then: Selected extends true ? Promise<Row<Result> | undefined>["then"] : UnselectedThen;
199
+ }) & {
200
+ first: Selected extends true ? () => Promise<Row<Result> | undefined> : Unavailable<typeof NO_SELECTION_MESSAGE>;
201
+ /** Counts matching entries without a selection; unavailable once payloads are requested. */
202
+ count: [Result] extends [never] ? () => Promise<number> : [Result] extends [{
203
+ _payloads: unknown;
204
+ }] ? Unavailable<"Counts cannot request payloads; count without withPayload()."> : () => Promise<number>;
182
205
  } : {});
183
- /** `paginate()` after `limit()` or `offset()` reports why as its required `this` type. */
184
- type PaginateAfterLimit = (this: typeof PAGINATE_AFTER_LIMIT_MESSAGE, ...arguments_: readonly unknown[]) => never;
185
206
  /** The `paginate()` overloads for one item type. */
186
207
  type Paginate<Item> = {
187
208
  (options: PageOptions): Promise<NumberedPage<Item>>;
@@ -364,12 +385,15 @@ type RelationalWhere<Registry extends CollectionRegistry, Name extends keyof Reg
364
385
  NOT?: RelationalWhere<Registry, Name> | readonly RelationalWhere<Registry, Name>[];
365
386
  RAW?: (fields: Fields<QueryData<Registry[Name]>>) => Expression;
366
387
  };
367
- /** Requests one payload: `true` without variants, or a record giving every variant key. */
368
- type PayloadRequest<Variant> = keyof Variant extends never ? true : Readonly<Variant>;
388
+ /**
389
+ * Requests one payload: `true` without variants, or a record giving every variant key a value or
390
+ * a nonempty list of values to try in order.
391
+ */
392
+ type PayloadRequest<Variant> = keyof Variant extends never ? true : VariantRequest<Variant>;
369
393
  /** Payloads to attach under `_payloads`; payloads set to false are omitted. */
370
394
  type PayloadOptions<Registry extends CollectionRegistry, Name extends keyof Registry & string> = { [Key in keyof PayloadsOf<Registry[Name]>]?: false | PayloadRequest<VariantOf<Registry[Name], Key>>; };
371
395
  /** Rejects variant keys a payload does not declare, including through inferred variables. */
372
- type CheckedPayloadRequest<Request, Variant> = Request extends boolean ? Request : { [Key in keyof Request]: Key extends keyof Variant ? string : never; };
396
+ type CheckedPayloadRequest<Request, Variant> = Request extends boolean ? Request : { [Key in keyof Request]: Key extends keyof Variant ? string | readonly [string, ...string[]] : never; };
373
397
  /**
374
398
  * Collection-scoped metadata queries with Drizzle-style columns, relationships, and filters.
375
399
  * Nothing is selected implicitly, not even `id`: list fields in `columns`, include relationships
@@ -1,8 +1,8 @@
1
- import { s as ContentError } from "./collection-types-DhIxnCSM.mjs";
1
+ import { s as ContentError } from "./collection-types-BjSEeKN-.mjs";
2
2
  import { n as ContentRenderResult } from "./types-Dh-Sydyw.mjs";
3
- import { _ as Relationship } from "./types-WOsFmNnN.mjs";
4
- import { D as QueryPlan, O as QueryRegistry, k as QueryTerminal } from "./relational-types-PtrzENt0.mjs";
5
- import { n as QueryStyle } from "./query-style-C2FcT1f9.mjs";
3
+ import { _ as Relationship } from "./types-B_p8k_g6.mjs";
4
+ import { D as QueryPlan, O as QueryRegistry, k as QueryTerminal } from "./relational-types-Bh34u01H.mjs";
5
+ import { n as QueryStyle } from "./query-style-k8Qd7wh5.mjs";
6
6
  //#region src/runtime/revision.d.ts
7
7
  type QueryRecord = Record<string, unknown> & {
8
8
  id: string;
@@ -1,4 +1,5 @@
1
- import { c as ContentErrorCode, i as LoadedRegistry, n as Collection, o as ContentDiagnostic, r as CollectionHandles, s as ContentError, t as AnyCollection } from "./collection-types-DhIxnCSM.mjs";
1
+ import { c as ContentErrorCode, i as LoadedRegistry, n as Collection, o as ContentDiagnostic, r as CollectionHandles, s as ContentError, t as AnyCollection } from "./collection-types-BjSEeKN-.mjs";
2
+ import { _ as Unavailable } from "./descriptor-BseHJvUB.mjs";
2
3
  import { n as ContentRenderResult, t as ContentHeading } from "./types-Dh-Sydyw.mjs";
3
- import { n as QueryStyle, r as QueryStyleDisabled } from "./query-style-C2FcT1f9.mjs";
4
- export { type AnyCollection, type Collection, type CollectionHandles, type ContentDiagnostic, ContentError, type ContentErrorCode, type ContentHeading, type ContentRenderResult, type LoadedRegistry, type QueryStyle, type QueryStyleDisabled };
4
+ import { n as QueryStyle, r as QueryStyleDisabled } from "./query-style-k8Qd7wh5.mjs";
5
+ export { type AnyCollection, type Collection, type CollectionHandles, type ContentDiagnostic, ContentError, type ContentErrorCode, type ContentHeading, type ContentRenderResult, type LoadedRegistry, type QueryStyle, type QueryStyleDisabled, type Unavailable };
@@ -1,4 +1,4 @@
1
- import { c as Loader, n as GlobOptions, t as FileOptions } from "./descriptor-bcPa6XLk.mjs";
1
+ import { c as Loader, n as GlobOptions, t as FileOptions } from "./descriptor-BseHJvUB.mjs";
2
2
  import { n as ContentRenderResult, r as RenderedContent } from "./types-Dh-Sydyw.mjs";
3
3
  import { StandardSchemaV1 } from "@standard-schema/spec";
4
4
  //#region src/collection/types.d.ts
@@ -227,5 +227,11 @@ type PayloadsOf<Definition> = (Definition extends {
227
227
  type PayloadData<Definition, Name extends keyof PayloadsOf<Definition>> = PayloadsOf<Definition>[Name];
228
228
  /** The variant keys a payload request must supply; empty for built-in and single-value payloads. */
229
229
  type VariantOf<Definition, Name> = Name extends keyof CustomPayloadsOf<Definition> ? CustomPayloadsOf<Definition>[Name] extends CustomPayload<unknown, infer Variant> ? Variant : {} : {};
230
+ /**
231
+ * How a query requests a payload variant: each key gives one value, or a nonempty list of values
232
+ * to try in order, such as `{ locale: ["fr-FR", "en-US"] }`. The query returns the first
233
+ * combination the entry has; with lists for several keys, earlier keys change slowest.
234
+ */
235
+ type VariantRequest<Variant> = { readonly [Key in keyof Variant]: Variant[Key] | readonly [Variant[Key], ...Variant[Key][]]; };
230
236
  //#endregion
231
- export { UnavailableRelationshipsOf as C, VariantValues as D, VariantOf as E, UnavailableRelationship as S, VariantField as T, Relationship as _, ComputedContext as a, SchemaOutput as b, EntryFiles as c, PayloadContext as d, PayloadData as f, QueryData as g, PayloadsOf as h, CollectionRegistry as i, FileEntry as l, PayloadDefinition as m, BuiltinPayloads as n, CustomPayload as o, PayloadDeclaration as p, CollectionDefinition as r, CustomPayloadMarker as s, BuiltinPayloadMarker as t, LoadedMarker as u, RelationshipsOf as v, VariantContext as w, SelectedData as x, Schema as y };
237
+ export { UnavailableRelationshipsOf as C, VariantRequest as D, VariantOf as E, VariantValues as O, UnavailableRelationship as S, VariantField as T, Relationship as _, ComputedContext as a, SchemaOutput as b, EntryFiles as c, PayloadContext as d, PayloadData as f, QueryData as g, PayloadsOf as h, CollectionRegistry as i, FileEntry as l, PayloadDefinition as m, BuiltinPayloads as n, CustomPayload as o, PayloadDeclaration as p, CollectionDefinition as r, CustomPayloadMarker as s, BuiltinPayloadMarker as t, LoadedMarker as u, RelationshipsOf as v, VariantContext as w, SelectedData as x, Schema as y };
package/dist/vite.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as ContentSourceEvent, o as ContentSourceSnapshot, t as BuildContentOptions } from "./build-Aoi7Unm0.mjs";
1
+ import { a as ContentSourceEvent, o as ContentSourceSnapshot, t as BuildContentOptions } from "./build-MlsJ12d8.mjs";
2
2
  import { Plugin } from "vite";
3
3
  //#region src/integrations/vite/plugin.d.ts
4
4
  /** Public, format-neutral hooks for integrations sharing this plugin's preparation session. */
package/dist/vite.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { I as collectionNames } from "./operators-D-w1-gDg.mjs";
2
- import { a as sessionInternals, i as resolveConfigPath, l as virtualDeclarations, o as withinDirectory, r as configCandidates, s as writeChanged, t as buildAtRoot, u as revisionModules } from "./build-CIw4yWs9.mjs";
2
+ import { a as sessionInternals, i as resolveConfigPath, l as virtualDeclarations, o as withinDirectory, r as configCandidates, s as writeChanged, t as buildAtRoot, u as revisionModules } from "./build-CfxMqm_9.mjs";
3
3
  import { n as serializeContentError } from "./diagnostics-DmUZtrJ6.mjs";
4
4
  import { createRequire } from "node:module";
5
5
  import path from "node:path";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "content-query",
3
- "version": "0.5.1",
3
+ "version": "0.6.1",
4
4
  "description": "Typed, framework-independent content collections, queries, and rendering.",
5
5
  "homepage": "https://github.com/yangshun/content-query#readme",
6
6
  "bugs": {