content-query 0.3.0 → 0.4.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 (36) hide show
  1. package/README.md +21 -1
  2. package/dist/{build-CI4s0oFP.mjs → build-CcqIpkes.mjs} +709 -39
  3. package/dist/{build-jKnjDjQE.d.mts → build-DL1GzmRy.d.mts} +12 -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/computed.d.mts +2 -2
  8. package/dist/{content-N1gYshjg.mjs → content-BXrgP7nL.mjs} +271 -81
  9. package/dist/{content-lwPxE9Yu.d.mts → content-CwTKqRs9.d.mts} +39 -14
  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 +61 -18
  16. package/dist/index.mjs +1 -1
  17. package/dist/internal.d.mts +11 -4
  18. package/dist/internal.mjs +29 -5
  19. package/dist/loaders.d.mts +1 -2
  20. package/dist/loaders.mjs +2 -81
  21. package/dist/next.d.mts +1 -1
  22. package/dist/next.mjs +2 -2
  23. package/dist/{operators-UqWUhKQJ.mjs → operators-Cwy8Kx4o.mjs} +88 -2
  24. package/dist/query-style-eXENQJ0O.d.mts +22 -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-C8xJtLG5.d.mts} +113 -38
  28. package/dist/runtime.d.mts +3 -2
  29. package/dist/runtime.mjs +1 -1
  30. package/dist/types-DbFfF6JE.d.mts +181 -0
  31. package/dist/vite.d.mts +1 -1
  32. package/dist/vite.mjs +2 -2
  33. package/package.json +1 -1
  34. package/dist/descriptor-BUPT2J7X.d.mts +0 -18
  35. package/dist/types-BC8gVJ6u.d.mts +0 -69
  36. package/dist/validation-D_XdE1pF.mjs +0 -84
@@ -1,7 +1,7 @@
1
- import { a as invalidQuery, n as contentError, t as ContentError } from "./errors-C2iuBM0G.mjs";
1
+ import { a as invalidQuery, n as contentError, o as payloadIdentity, t as ContentError } from "./errors-Bfd0R4_P.mjs";
2
2
  import { a as detachSelection, c as isScalar, i as detach, n as compareStrings, s as equal, t as compare } from "./values-BbTV4QT-.mjs";
3
3
  import { t as assertServerOnly } from "./server-only-CIJUlyL-.mjs";
4
- import { _ as not, t as and, x as validateExpression, y as or } from "./operators-UqWUhKQJ.mjs";
4
+ import { D as resolveRelations, E as or, S as not, k as validateExpression, l as fieldProxy, t as and } from "./operators-Cwy8Kx4o.mjs";
5
5
  //#region src/artifacts/types.ts
6
6
  const CODEC_VERSION = "devalue-6.0.2";
7
7
  //#endregion
@@ -144,6 +144,18 @@ function isRelationship(value) {
144
144
  ].includes(String(value.referenceSide));
145
145
  }
146
146
  //#endregion
147
+ //#region src/runtime/revision.ts
148
+ /**
149
+ * Serializes a variant combination with sorted keys, so preparation, artifacts, and query plans
150
+ * agree on one identity. A payload without variants uses `{}`.
151
+ *
152
+ * @param variant - The variant combination.
153
+ * @returns The canonical variant key.
154
+ */
155
+ function variantKey(variant) {
156
+ return JSON.stringify(Object.fromEntries(Object.keys(variant).sort().map((key) => [key, variant[key]])));
157
+ }
158
+ //#endregion
147
159
  //#region src/shared/concurrency.ts
148
160
  /**
149
161
  * Maps items through an asynchronous callback with at most `limit` callbacks in flight.
@@ -235,9 +247,17 @@ function emptyPlan(collection) {
235
247
  with: {}
236
248
  };
237
249
  }
238
- /** Treats explicitly selected relationships and adjacency as row projections too. */
250
+ /** Explains the explicit-selection rule wherever a scope returns rows without selecting data. */
251
+ const NO_SELECTION_MESSAGE = "Select fields, relationships, payloads, or adjacent entries before running this query.";
252
+ /**
253
+ * Checks whether a row-returning scope explicitly requests data. Nothing is implicit, not even
254
+ * `id`: a scope must select fields, include a relationship, request a payload, or select adjacency.
255
+ *
256
+ * @param plan - The scope to check; `undefined` when a callback returned no builder.
257
+ * @returns `true` when the scope requests at least one kind of data.
258
+ */
239
259
  function hasSelection(plan) {
240
- return !!plan && (plan.select !== void 0 || !!plan.allFields || !!plan.adjacent || Object.keys(plan.with).length > 0);
260
+ return !!plan && (!!plan.select?.length || !!plan.adjacent || Object.keys(plan.with).length > 0 || Object.keys(plan.payloads ?? {}).length > 0);
241
261
  }
242
262
  //#endregion
243
263
  //#region src/query/relational.ts
@@ -314,9 +334,9 @@ function relationalWhere(registry, collection, where) {
314
334
  expressions.push(field === "OR" ? or(...children) : field === "NOT" ? and(...children.map(not)) : and(...children));
315
335
  } else if (field === "RAW") {
316
336
  if (typeof condition !== "function") invalidQuery("RAW requires a structured-expression callback.");
317
- const expression = condition(new Proxy(Object.create(null), { get: (_target, name) => ({ name }) }));
337
+ const expression = condition(fieldProxy());
318
338
  validateExpression(expression);
319
- expressions.push(expression);
339
+ expressions.push(resolveRelations(registry, collection, expression));
320
340
  } else if (Object.hasOwn(registry[collection].relationships, field)) {
321
341
  const relation = registry[collection].relationships[field];
322
342
  const expression = {
@@ -350,18 +370,11 @@ function relationalPlan(registry, collection, options = {}, many = true, allowAd
350
370
  if (Object.keys(options).some((key) => !allowed.includes(key))) invalidQuery("Unknown relational query option or unsupported scope option.");
351
371
  const plan = emptyPlan(collection);
352
372
  if (options.where !== void 0) plan.filters = [relationalWhere(registry, collection, options.where)];
353
- if (options.columns === void 0) plan.allFields = true;
354
- else {
373
+ if (options.columns !== void 0) {
355
374
  record$1(options.columns, "columns");
356
375
  const columns = options.columns;
357
- if (Object.values(columns).some((value) => typeof value !== "boolean")) invalidQuery("Column selection requires boolean values.");
358
- const names = checkedFields(registry, collection, Object.keys(columns));
359
- const included = names.filter((name) => columns[name] === true);
360
- if (included.length || !names.length) plan.select = included;
361
- else {
362
- plan.allFields = true;
363
- plan.exclude = names;
364
- }
376
+ if (Object.values(columns).some((value) => value !== true)) invalidQuery("Columns list the fields to include with `true`; omit fields you do not need.");
377
+ plan.select = checkedFields(registry, collection, Object.keys(columns));
365
378
  }
366
379
  if (options.with !== void 0) {
367
380
  record$1(options.with, "with");
@@ -370,7 +383,8 @@ function relationalPlan(registry, collection, options = {}, many = true, allowAd
370
383
  const relation = Object.hasOwn(registry[collection].relationships, name) && registry[collection].relationships[name];
371
384
  if (!relation) invalidQuery(`Unknown relationship "${name}".`);
372
385
  if (selected === false || selected === void 0) continue;
373
- related[name] = relationalPlan(registry, relation.collection, selected === true ? {} : selected, relation.type === "many", allowAdjacent);
386
+ if (selected === true) invalidQuery(`Relationship "${name}" requires nested options that select data, such as { columns: { id: true } }.`);
387
+ related[name] = selectedPlan(relationalPlan(registry, relation.collection, selected, relation.type === "many", allowAdjacent));
374
388
  }
375
389
  plan.with = related;
376
390
  }
@@ -385,7 +399,37 @@ function relationalPlan(registry, collection, options = {}, many = true, allowAd
385
399
  const payloads = checkedPayloads(registry, collection, options.payloads);
386
400
  if (payloads) plan.payloads = payloads;
387
401
  }
388
- if (options.adjacent !== void 0) plan.adjacent = options.adjacent === true ? true : relationalPlan(registry, collection, options.adjacent, true, false, false);
402
+ if (options.adjacent !== void 0) plan.adjacent = relationalAdjacent(registry, collection, options.adjacent);
403
+ return plan;
404
+ }
405
+ /**
406
+ * Compiles relational `adjacent` options. `{ scope: "results", columns, with }` selects neighbors
407
+ * within the current results; other options describe an independent sibling scope.
408
+ *
409
+ * @param registry - The query registry.
410
+ * @param collection - The collection whose neighbors are selected.
411
+ * @param adjacent - The `adjacent` option value.
412
+ * @returns The adjacency plan, which always selects explicit data.
413
+ */
414
+ function relationalAdjacent(registry, collection, adjacent) {
415
+ if (adjacent === true) invalidQuery("Adjacent entries select their own data, such as { scope: \"results\", columns: { id: true } }.");
416
+ record$1(adjacent, "adjacent");
417
+ const { scope, ...options } = adjacent;
418
+ if (scope !== void 0 && scope !== "results" && scope !== "collection") invalidQuery("Adjacent scope must be \"results\" or \"collection\".");
419
+ if (scope === "results" && Object.keys(options).some((key) => key !== "columns" && key !== "with")) invalidQuery("Results-scope neighbors only select data; they share the query's filters and order.");
420
+ return {
421
+ scope: scope === "results" ? "results" : "collection",
422
+ plan: selectedPlan(relationalPlan(registry, collection, options, true, false, false))
423
+ };
424
+ }
425
+ /**
426
+ * Rejects a row-returning scope that requests no data.
427
+ *
428
+ * @param plan - A root, relationship, or sibling scope.
429
+ * @returns The same plan when it selects fields, relationships, payloads, or adjacency.
430
+ */
431
+ function selectedPlan(plan) {
432
+ if (!hasSelection(plan)) invalidQuery(NO_SELECTION_MESSAGE);
389
433
  return plan;
390
434
  }
391
435
  /** Creates stable, live collection queries in a dedicated, prototype-free namespace. */
@@ -396,8 +440,8 @@ function createQueryNamespace(registry, run) {
396
440
  let cached = queries.get(name);
397
441
  if (!cached) {
398
442
  cached = {
399
- findMany: (options) => run(relationalPlan(registry(), name, options), "all"),
400
- findFirst: (options) => run(relationalPlan(registry(), name, options), "first")
443
+ findMany: (options) => run(selectedPlan(relationalPlan(registry(), name, options)), "all"),
444
+ findFirst: (options) => run(selectedPlan(relationalPlan(registry(), name, options)), "first")
401
445
  };
402
446
  queries.set(name, cached);
403
447
  }
@@ -492,6 +536,27 @@ function objectWhere(where) {
492
536
  return detach(expression);
493
537
  }
494
538
  /** Compiles explicit object options into the backend-independent fluent query contract. */
539
+ /**
540
+ * Compiles an object-form `_adjacent` selection. `{ scope: "results", select }` selects neighbors
541
+ * within the current results; other options describe an independent sibling scope.
542
+ *
543
+ * @param registry - The query registry.
544
+ * @param collection - The collection whose neighbors are selected.
545
+ * @param selection - The `_adjacent` value from an object selection.
546
+ * @returns The adjacency plan, which always selects explicit data.
547
+ */
548
+ function objectAdjacent(registry, collection, selection) {
549
+ record(selection, "_adjacent");
550
+ const { scope, ...options } = selection;
551
+ if (scope !== void 0 && scope !== "results" && scope !== "collection") invalidQuery("Adjacent scope must be \"results\" or \"collection\".");
552
+ if (scope === "results" && Object.keys(options).some((key) => key !== "select")) invalidQuery("Results-scope neighbors only select data; they share the query's filters and order.");
553
+ const plan = objectPlan(registry, collection, options, true, false, false);
554
+ if (!hasSelection(plan)) invalidQuery("Adjacent entries require explicit selection.");
555
+ return {
556
+ scope: scope === "results" ? "results" : "collection",
557
+ plan
558
+ };
559
+ }
495
560
  function objectPlan(registry, collection, options, many = true, allowAdjacent = true, pagination = true) {
496
561
  if (!Object.hasOwn(registry, collection)) invalidQuery(`Unknown collection "${collection}".`);
497
562
  record(options, "Query");
@@ -504,9 +569,9 @@ function objectPlan(registry, collection, options, many = true, allowAdjacent =
504
569
  if (Object.keys(options).some((key) => !allowed.includes(key))) invalidQuery("Unknown query option or pagination in an adjacency scope.");
505
570
  let plan = emptyPlan(collection);
506
571
  if (options.where !== void 0) {
507
- const expression = typeof options.where === "function" ? options.where(new Proxy(Object.create(null), { get: (_target, name) => ({ name }) })) : objectWhere(options.where);
572
+ const expression = typeof options.where === "function" ? options.where(fieldProxy()) : objectWhere(options.where);
508
573
  validateExpression(expression);
509
- plan.filters = [detach(expression)];
574
+ plan.filters = [detach(resolveRelations(registry, collection, expression))];
510
575
  }
511
576
  if (options.orderBy !== void 0) for (const order of Array.isArray(options.orderBy) ? options.orderBy : [options.orderBy]) {
512
577
  record(order, "orderBy");
@@ -522,8 +587,7 @@ function objectPlan(registry, collection, options, many = true, allowAdjacent =
522
587
  const related = Object.create(null);
523
588
  for (const [name, selection] of selected) if (name === "_adjacent") {
524
589
  if (!allowAdjacent) invalidQuery("Adjacency cannot be selected recursively.");
525
- plan.adjacent = selection === true ? true : objectPlan(registry, collection, selection, true, false, false);
526
- if (plan.adjacent !== true && !plan.adjacent.select) invalidQuery("Adjacent entries require explicit selection.");
590
+ plan.adjacent = objectAdjacent(registry, collection, selection);
527
591
  } else if (Object.hasOwn(registry[collection].relationships, name)) {
528
592
  const relation = registry[collection].relationships[name];
529
593
  const nested = objectPlan(registry, relation.collection, selection, relation.type === "many", allowAdjacent);
@@ -558,17 +622,13 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
558
622
  [scopeSymbol]: scopeToken,
559
623
  select(...fields) {
560
624
  if (plan.select || fields.length === 0) invalidQuery("select() requires distinct prepared fields and can be called once per scope.");
561
- return branch({
562
- select: checkedFields(registry, plan.collection, fields),
563
- allFields: void 0,
564
- exclude: void 0
565
- });
625
+ return branch({ select: checkedFields(registry, plan.collection, fields) });
566
626
  },
567
627
  where(configure) {
568
628
  if (typeof configure !== "function") invalidQuery("where() requires a configuration callback.");
569
- const expression = configure(new Proxy(Object.create(null), { get: (_target, name) => ({ name }) }));
629
+ const expression = configure(fieldProxy());
570
630
  validateExpression(expression);
571
- return branch({ filters: [...plan.filters, detach(expression)] });
631
+ return branch({ filters: [...plan.filters, detach(resolveRelations(registry, plan.collection, expression))] });
572
632
  },
573
633
  orderBy(field, direction = "asc") {
574
634
  return branch({ order: checkedOrder(plan, field, direction, many) });
@@ -598,39 +658,75 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
598
658
  [name]: checkedPayload(registry, plan.collection, name, variant)
599
659
  } });
600
660
  },
601
- selectAdjacent(configure) {
661
+ /**
662
+ * Selects previous and next entries. Field names select neighbors within this scope's results;
663
+ * a callback or options object describes an independent sibling scope.
664
+ */
665
+ selectAdjacent(...arguments_) {
602
666
  if (!allowAdjacent || plan.adjacent !== void 0) invalidQuery("Adjacency cannot be repeated or selected recursively.");
603
- if (configure === void 0) return branch({ adjacent: true });
667
+ const [configure] = arguments_;
668
+ if (configure === void 0 || typeof configure === "string") {
669
+ if (!arguments_.length || arguments_.some((field) => typeof field !== "string")) invalidQuery("selectAdjacent() requires neighbor fields, such as selectAdjacent(\"id\"), or a sibling query.");
670
+ const selection = {
671
+ ...emptyPlan(plan.collection),
672
+ select: checkedFields(registry, plan.collection, arguments_)
673
+ };
674
+ return branch({ adjacent: {
675
+ scope: "results",
676
+ plan: selection
677
+ } });
678
+ }
604
679
  if (typeof configure !== "function") {
605
680
  const adjacent = objectPlan(registry, plan.collection, configure, true, false, false);
606
- if (!adjacent.select) invalidQuery("Adjacent entries require explicit selection.");
607
- return branch({ adjacent });
681
+ if (!hasSelection(adjacent)) invalidQuery("Adjacent entries require explicit selection.");
682
+ return branch({ adjacent: {
683
+ scope: "collection",
684
+ plan: adjacent
685
+ } });
608
686
  }
609
687
  const nestedScope = {};
610
688
  const configured = configure(createBuilder(registry, run, emptyPlan(plan.collection), true, false, nestedScope, false, false));
611
689
  const adjacent = configured && typeof configured === "object" && planSymbol in configured ? configured[planSymbol] : void 0;
612
690
  if (!hasSelection(adjacent) || !(scopeSymbol in configured) || configured[scopeSymbol] !== nestedScope) invalidQuery("selectAdjacent() must return its explicitly selected sibling query.");
613
- return branch({ adjacent });
691
+ return branch({ adjacent: {
692
+ scope: "collection",
693
+ plan: adjacent
694
+ } });
614
695
  },
615
696
  all() {
616
- if (!root) invalidQuery("Nested builders have no terminal methods.");
617
- return run(plan, "all");
697
+ return rows("all");
618
698
  },
619
699
  /** Executes a selected root query, matching direct await behavior. */
620
700
  execute() {
621
- if (!root) invalidQuery("Nested builders have no terminal methods.");
622
- return run(plan, "all");
701
+ return rows("all");
623
702
  },
624
703
  first() {
625
- if (!root) invalidQuery("Nested builders have no terminal methods.");
626
- return run(plan, "first");
704
+ return rows("first");
627
705
  },
706
+ /** Counts matching entries; counting returns no rows, so it needs no selection. */
628
707
  count() {
629
708
  if (!root) invalidQuery("Nested builders have no terminal methods.");
630
709
  return run(plan, "count");
631
710
  }
632
711
  };
633
- if (root && hasSelection(plan)) Object.defineProperty(builder, "then", { value: (fulfilled, rejected) => run(plan, "all").then(fulfilled, rejected) });
712
+ if (root) Object.defineProperty(builder, "then", { value: (fulfilled, rejected) => {
713
+ let result;
714
+ try {
715
+ result = rows("all");
716
+ } catch (cause) {
717
+ result = Promise.reject(cause);
718
+ }
719
+ return result.then(fulfilled, rejected);
720
+ } });
721
+ /**
722
+ * Runs a row-returning terminal. Awaiting an unselected root rejects rather than resolving to the
723
+ * builder, so a missing selection fails loudly in JavaScript as well as in TypeScript.
724
+ */
725
+ function rows(terminal) {
726
+ if (!root) invalidQuery("Nested builders have no terminal methods.");
727
+ if (!hasSelection(plan)) invalidQuery(NO_SELECTION_MESSAGE);
728
+ return run(plan, terminal);
729
+ }
634
730
  /** Checks pagination before creating an independent branch. */
635
731
  function pagination(key, value) {
636
732
  if (!allowPagination || plan[key] !== void 0) invalidQuery("Pagination requires a nonnegative safe integer, one call per clause, and a many-valued scope.");
@@ -639,20 +735,20 @@ function createBuilder(registry, run, plan, many = true, root = true, scopeToken
639
735
  return builder;
640
736
  }
641
737
  //#endregion
642
- //#region src/query/select.ts
643
- /** Defers collection validation until from(), preserving the immutable selection across reuse. */
644
- function createSelect(registry, run, fields) {
645
- const selected = [...fields];
646
- return {
647
- /** Binds the current registry and prepares a reusable root query. */
648
- from(collection) {
649
- const current = registry();
650
- if (!Object.hasOwn(current, collection)) invalidQuery(`Unknown collection "${collection}".`);
651
- const plan = emptyPlan(collection);
652
- if (selected.length) plan.select = checkedFields(current, collection, selected);
653
- else plan.allFields = true;
654
- return createBuilder(current, run, plan);
655
- } };
738
+ //#region src/query/from.ts
739
+ /**
740
+ * Starts a fluent root query over one collection. The builder selects nothing until `select()`,
741
+ * `with()`, `withPayload()`, or `selectAdjacent()` requests data; `count()` needs no selection.
742
+ *
743
+ * @param registry - Reads the current query registry, so development builders bind the latest one.
744
+ * @param run - Executes a plan against one pinned revision.
745
+ * @param collection - The collection to query.
746
+ * @returns An immutable, reusable root builder.
747
+ */
748
+ function createFrom(registry, run, collection) {
749
+ const current = registry();
750
+ if (typeof collection !== "string" || !Object.hasOwn(current, collection)) invalidQuery(`Unknown collection "${String(collection)}".`);
751
+ return createBuilder(current, run, emptyPlan(collection));
656
752
  }
657
753
  //#endregion
658
754
  //#region src/query/normalize.ts
@@ -667,7 +763,10 @@ function normalizePlan(plan) {
667
763
  ...plan.select === void 0 && Object.keys(plan.with).length ? { select: [] } : {},
668
764
  filters: plan.filters.flatMap(conjunction),
669
765
  with: Object.fromEntries(Object.entries(plan.with).map(([name, nested]) => [name, normalizePlan(nested)])),
670
- ...plan.adjacent && plan.adjacent !== true ? { adjacent: normalizePlan(plan.adjacent) } : {}
766
+ ...plan.adjacent ? { adjacent: {
767
+ scope: plan.adjacent.scope,
768
+ plan: normalizePlan(plan.adjacent.plan)
769
+ } } : {}
671
770
  };
672
771
  }
673
772
  //#endregion
@@ -698,6 +797,7 @@ function matches(record, expression, revision, collection) {
698
797
  if (!revision || !relation || relation.collection !== expression.target) invalidQuery("Relationship filter no longer matches the prepared collection scope.");
699
798
  return (revision.relations.get(relationKey(expression.collection, expression.field))?.get(record.id) ?? []).some((candidate) => matches(candidate, expression.expression, revision, expression.target));
700
799
  }
800
+ if (expression.kind === "has") invalidQuery("has() filters must be passed to a query's where() before execution.");
701
801
  if (expression.kind !== "predicate") return false;
702
802
  const actual = Object.hasOwn(record, expression.field) ? record[expression.field] : void 0;
703
803
  const expected = expression.value;
@@ -788,19 +888,17 @@ function pageRecords(records, plan, revision, preserveOrder = false) {
788
888
  }
789
889
  /** Builds a sibling index once per scope, before pagination and within the pinned revision. */
790
890
  function neighborScope(plan, records, preserveOrder, scopes, revision) {
791
- const selection = plan.adjacent;
792
- const siblingPlan = selection === true ? {
891
+ const { scope: neighborsScope, plan: selection } = plan.adjacent;
892
+ const siblingPlan = neighborsScope === "results" ? {
793
893
  ...plan,
794
- select: ["id"],
795
- allFields: void 0,
796
- exclude: void 0,
797
- with: {},
894
+ select: selection.select,
895
+ with: selection.with,
798
896
  adjacent: void 0,
799
897
  payloads: void 0,
800
898
  limit: void 0,
801
899
  offset: void 0
802
900
  } : selection;
803
- const key = selection === true ? plan : siblingPlan;
901
+ const key = neighborsScope === "results" ? plan : siblingPlan;
804
902
  let candidates = scopes.get(key);
805
903
  if (!candidates) {
806
904
  candidates = /* @__PURE__ */ new WeakMap();
@@ -808,7 +906,7 @@ function neighborScope(plan, records, preserveOrder, scopes, revision) {
808
906
  }
809
907
  let scope = candidates.get(records);
810
908
  if (!scope) {
811
- const ordered = pageRecords([...new Map(records.map((entry) => [entry.id, entry])).values()], siblingPlan, revision, selection === true && preserveOrder);
909
+ const ordered = pageRecords([...new Map(records.map((entry) => [entry.id, entry])).values()], siblingPlan, revision, neighborsScope === "results" && preserveOrder);
812
910
  scope = {
813
911
  plan: siblingPlan,
814
912
  records: ordered,
@@ -823,9 +921,8 @@ function neighborScope(plan, records, preserveOrder, scopes, revision) {
823
921
  * Requested payloads are recorded for the caller to load after projection.
824
922
  */
825
923
  function project(record, plan, revision, scopes, scopeRecords, requests, preserveScopeOrder = false) {
826
- if (!plan.select && !plan.allFields && !plan.adjacent) invalidQuery("Row-returning queries require select().");
827
- const fields = plan.allFields ? Object.keys(record).filter((field) => !plan.exclude?.includes(field)) : plan.select ?? [];
828
- const result = detachSelection(record, fields);
924
+ if (!hasSelection(plan)) invalidQuery(NO_SELECTION_MESSAGE);
925
+ const result = detachSelection(record, plan.select ?? []);
829
926
  for (const [name, nested] of Object.entries(plan.with)) {
830
927
  const relation = revision.registry[plan.collection].relationships[name];
831
928
  if (!relation || relation.collection !== nested.collection || relation.type === "one" && (nested.limit !== void 0 || nested.offset !== void 0 || nested.order.length > 0)) invalidQuery(`Relationship "${name}" no longer matches the prepared query scope.`);
@@ -840,7 +937,7 @@ function project(record, plan, revision, scopes, scopeRecords, requests, preserv
840
937
  });
841
938
  }
842
939
  if (plan.adjacent) {
843
- const candidates = plan.adjacent === true ? scopeRecords : revision.collections[plan.collection].records;
940
+ const candidates = plan.adjacent.scope === "results" ? scopeRecords : revision.collections[plan.collection].records;
844
941
  const scope = neighborScope(plan, candidates, preserveScopeOrder, scopes, revision);
845
942
  const index = scope.positions.get(record.id);
846
943
  const previous = index === void 0 ? void 0 : scope.records[index - 1];
@@ -869,12 +966,50 @@ async function executeQuery(revision, plan, terminal, requests = []) {
869
966
  if (plan.payloads) invalidQuery("Counts cannot request payloads.");
870
967
  return queryCandidates(collection.records, plan).filter((record) => plan.filters.every((expression) => matches(record, expression, revision, plan.collection))).length;
871
968
  }
872
- if (!plan.select && !plan.allFields && !plan.adjacent) invalidQuery("Row-returning queries require select().");
969
+ if (!hasSelection(plan)) invalidQuery(NO_SELECTION_MESSAGE);
873
970
  const records = pageRecords(queryCandidates(collection.records, plan), plan, revision);
874
971
  const scopes = /* @__PURE__ */ new WeakMap();
875
972
  return terminal === "first" ? records[0] ? project(records[0], plan, revision, scopes, collection.records, requests) : void 0 : records.map((record) => project(record, plan, revision, scopes, collection.records, requests));
876
973
  }
877
974
  //#endregion
975
+ //#region src/runtime/query-style.ts
976
+ /**
977
+ * Root `content` members that start each query style. Every other query API hangs off these
978
+ * members, so gating them gates payloads, relationships, and adjacency as well.
979
+ */
980
+ const queryStyleMembers = {
981
+ fluent: ["from"],
982
+ relational: ["query", "count"]
983
+ };
984
+ /**
985
+ * Checks whether a configured or manifest value names a supported query style.
986
+ *
987
+ * @param value - The untrusted `queryStyle` value.
988
+ * @returns `true` for `"fluent"` or `"relational"`.
989
+ */
990
+ function isQueryStyle(value) {
991
+ return value === "fluent" || value === "relational";
992
+ }
993
+ /**
994
+ * Finds the style that owns a root member.
995
+ *
996
+ * @param member - A gated root `content` member.
997
+ * @returns The style whose queries start at that member.
998
+ */
999
+ function styleOfMember(member) {
1000
+ return queryStyleMembers.fluent.includes(member) ? "fluent" : "relational";
1001
+ }
1002
+ /**
1003
+ * Formats the runtime message that matches {@link QueryStyleDisabled}.
1004
+ *
1005
+ * @param member - The disabled root member.
1006
+ * @param style - The only style the configuration allows.
1007
+ * @returns The error message.
1008
+ */
1009
+ function queryStyleDisabledMessage(member, style) {
1010
+ return `content.${member} is unavailable because content-query.config sets queryStyle to "${style}".`;
1011
+ }
1012
+ //#endregion
878
1013
  //#region src/runtime/content.ts
879
1014
  assertServerOnly();
880
1015
  /** Maximum payload loads in flight for one query. */
@@ -951,11 +1086,48 @@ async function renderedPayload(revision, collection, id) {
951
1086
  }
952
1087
  return rendered;
953
1088
  }
1089
+ /** Lazily loads one custom payload variant once per revision, retrying after failures. */
1090
+ function loadCustom(revision, collection, id, name, variant) {
1091
+ const entries = revision.collections[collection].payloads?.get(name);
1092
+ if (!entries) invalidQuery(`Payload "${name}" no longer matches the prepared collection.`);
1093
+ const load = entries.get(id)?.get(variantKey(variant));
1094
+ if (!load) return Promise.reject(new ContentError({
1095
+ code: "variant-not-found",
1096
+ operation: "query",
1097
+ collection,
1098
+ id,
1099
+ payload: name,
1100
+ variant,
1101
+ message: `Entry "${id}" has no ${payloadIdentity(name, variant)} payload.`
1102
+ }));
1103
+ const key = JSON.stringify([
1104
+ collection,
1105
+ id,
1106
+ name,
1107
+ variantKey(variant)
1108
+ ]);
1109
+ let loaded = revision.payloadCache.get(key);
1110
+ if (!loaded) {
1111
+ loaded = Promise.resolve().then(load).catch((cause) => {
1112
+ revision.payloadCache.delete(key);
1113
+ throw contentError(cause, {
1114
+ code: "artifact-error",
1115
+ collection,
1116
+ id,
1117
+ payload: name,
1118
+ variant,
1119
+ message: `Could not load the prepared ${payloadIdentity(name, variant)} payload.`
1120
+ });
1121
+ });
1122
+ revision.payloadCache.set(key, loaded);
1123
+ }
1124
+ return loaded;
1125
+ }
954
1126
  /** Loads one requested payload value from the pinned revision. */
955
- async function loadPayload(revision, collection, id, name) {
1127
+ async function loadPayload(revision, collection, id, name, variant) {
956
1128
  if (name === "_body") return (await loadBuiltin(revision, collection, id)).body;
957
1129
  if (name === "_rendered") return renderedPayload(revision, collection, id);
958
- invalidQuery(`Payload "${name}" no longer matches the prepared collection.`);
1130
+ return loadCustom(revision, collection, id, name, variant);
959
1131
  }
960
1132
  /**
961
1133
  * Loads every requested payload with bounded concurrency, then attaches detached copies under
@@ -974,13 +1146,14 @@ async function attachPayloads(revision, requests) {
974
1146
  if (!loads.has(key)) loads.set(key, {
975
1147
  collection: request.collection,
976
1148
  id: request.id,
977
- name
1149
+ name,
1150
+ variant: request.payloads[name].variant
978
1151
  });
979
1152
  }
980
1153
  const keys = [...loads.keys()];
981
1154
  const values = await mapConcurrent(keys, PAYLOAD_CONCURRENCY, (key) => {
982
- const { collection, id, name } = loads.get(key);
983
- return loadPayload(revision, collection, id, name);
1155
+ const { collection, id, name, variant } = loads.get(key);
1156
+ return loadPayload(revision, collection, id, name, variant);
984
1157
  });
985
1158
  const loaded = new Map(keys.map((key, index) => [key, values[index]]));
986
1159
  for (const request of requests) request.target._payloads = Object.fromEntries(Object.keys(request.payloads).map((name) => [name, detach(loaded.get(JSON.stringify([
@@ -990,7 +1163,13 @@ async function attachPayloads(revision, requests) {
990
1163
  request.payloads[name]
991
1164
  ])))]));
992
1165
  }
993
- /** Creates the initialized runtime without importing source configuration or schemas. */
1166
+ /**
1167
+ * Creates the initialized runtime without importing source configuration or schemas.
1168
+ *
1169
+ * @param state - Mutable session or artifact state; each query reads its current revision.
1170
+ * @returns The `content` API. Root members outside the current revision's `queryStyle` throw an
1171
+ * `invalid-query` ContentError when accessed or called.
1172
+ */
994
1173
  function createRuntime(state) {
995
1174
  /** Pins query execution and payload loading to exactly the same revision and executor. */
996
1175
  const run = (plan, terminal) => pinned(state, async (revision) => {
@@ -1002,12 +1181,23 @@ function createRuntime(state) {
1002
1181
  });
1003
1182
  /** Reads the current registry without capturing a development revision in either API. */
1004
1183
  const registry = () => state.registry ?? state.revision?.registry ?? {};
1184
+ /** Rejects a root member that the current revision's configured query style disables. */
1185
+ function allow(member) {
1186
+ const style = state.revision?.queryStyle;
1187
+ if (style && styleOfMember(member) !== style) invalidQuery(queryStyleDisabledMessage(member, style));
1188
+ }
1189
+ const queries = createQueryNamespace(registry, run);
1005
1190
  return {
1006
- select(...fields) {
1007
- return createSelect(registry, run, fields);
1191
+ from(collection) {
1192
+ allow("from");
1193
+ return createFrom(registry, run, collection);
1194
+ },
1195
+ get query() {
1196
+ allow("query");
1197
+ return queries;
1008
1198
  },
1009
- query: createQueryNamespace(registry, run),
1010
1199
  count(collection, options = {}) {
1200
+ allow("count");
1011
1201
  return run(relationalPlan(registry(), collection, options), "count");
1012
1202
  }
1013
1203
  };
@@ -1025,4 +1215,4 @@ function createLiveRuntime(initial) {
1025
1215
  };
1026
1216
  }
1027
1217
  //#endregion
1028
- export { CODEC_VERSION as a, prepareRelationships as i, createRuntime as n, isRelationship as r, createLiveRuntime as t };
1218
+ export { isRelationship as a, variantKey as i, createRuntime as n, prepareRelationships as o, isQueryStyle as r, CODEC_VERSION as s, createLiveRuntime as t };