joist-core 2.3.0-next.99 → 2.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 (131) hide show
  1. package/build/EntityManager.cjs +1 -1
  2. package/build/EntityManager.cjs.map +1 -1
  3. package/build/EntityManager.d.cts +9 -8
  4. package/build/EntityManager.d.cts.map +1 -1
  5. package/build/EntityManager.d.mts +9 -8
  6. package/build/EntityManager.d.mts.map +1 -1
  7. package/build/EntityManager.js +1 -1
  8. package/build/EntityManager.js.map +1 -1
  9. package/build/dataloaders/findCountDataLoader.cjs +2 -0
  10. package/build/dataloaders/findCountDataLoader.cjs.map +1 -1
  11. package/build/dataloaders/findCountDataLoader.d.cts.map +1 -1
  12. package/build/dataloaders/findCountDataLoader.d.mts.map +1 -1
  13. package/build/dataloaders/findCountDataLoader.js +2 -0
  14. package/build/dataloaders/findCountDataLoader.js.map +1 -1
  15. package/build/dataloaders/findDataLoader.cjs +1 -0
  16. package/build/dataloaders/findDataLoader.cjs.map +1 -1
  17. package/build/dataloaders/findDataLoader.d.cts.map +1 -1
  18. package/build/dataloaders/findDataLoader.d.mts.map +1 -1
  19. package/build/dataloaders/findDataLoader.js +2 -1
  20. package/build/dataloaders/findDataLoader.js.map +1 -1
  21. package/build/dataloaders/findIdsDataLoader.cjs +2 -0
  22. package/build/dataloaders/findIdsDataLoader.cjs.map +1 -1
  23. package/build/dataloaders/findIdsDataLoader.d.cts.map +1 -1
  24. package/build/dataloaders/findIdsDataLoader.d.mts.map +1 -1
  25. package/build/dataloaders/findIdsDataLoader.js +2 -0
  26. package/build/dataloaders/findIdsDataLoader.js.map +1 -1
  27. package/build/dataloaders/findPaginatedDataLoader.cjs +2 -0
  28. package/build/dataloaders/findPaginatedDataLoader.cjs.map +1 -1
  29. package/build/dataloaders/findPaginatedDataLoader.d.cts.map +1 -1
  30. package/build/dataloaders/findPaginatedDataLoader.d.mts.map +1 -1
  31. package/build/dataloaders/findPaginatedDataLoader.js +2 -0
  32. package/build/dataloaders/findPaginatedDataLoader.js.map +1 -1
  33. package/build/index.cjs +6 -3
  34. package/build/index.cjs.map +1 -1
  35. package/build/index.d.cts +6 -4
  36. package/build/index.d.cts.map +1 -1
  37. package/build/index.d.mts +6 -4
  38. package/build/index.d.mts.map +1 -1
  39. package/build/index.js +6 -5
  40. package/build/index.js.map +1 -1
  41. package/build/keys.cjs +6 -1
  42. package/build/keys.cjs.map +1 -1
  43. package/build/keys.d.cts +3 -1
  44. package/build/keys.d.cts.map +1 -1
  45. package/build/keys.d.mts +3 -1
  46. package/build/keys.d.mts.map +1 -1
  47. package/build/keys.js +6 -2
  48. package/build/keys.js.map +1 -1
  49. package/build/queries/find/QueryParser.cjs +1 -2
  50. package/build/queries/find/QueryParser.cjs.map +1 -1
  51. package/build/queries/find/QueryParser.d.cts.map +1 -1
  52. package/build/queries/find/QueryParser.d.mts.map +1 -1
  53. package/build/queries/find/QueryParser.js +2 -3
  54. package/build/queries/find/QueryParser.js.map +1 -1
  55. package/build/queries/find/QueryVisitor.cjs +17 -0
  56. package/build/queries/find/QueryVisitor.cjs.map +1 -1
  57. package/build/queries/find/QueryVisitor.d.cts +3 -1
  58. package/build/queries/find/QueryVisitor.d.cts.map +1 -1
  59. package/build/queries/find/QueryVisitor.d.mts +3 -1
  60. package/build/queries/find/QueryVisitor.d.mts.map +1 -1
  61. package/build/queries/find/QueryVisitor.js +17 -1
  62. package/build/queries/find/QueryVisitor.js.map +1 -1
  63. package/build/queries/sql/Expr.cjs +18 -1
  64. package/build/queries/sql/Expr.cjs.map +1 -1
  65. package/build/queries/sql/Expr.d.cts +13 -3
  66. package/build/queries/sql/Expr.d.cts.map +1 -1
  67. package/build/queries/sql/Expr.d.mts +13 -3
  68. package/build/queries/sql/Expr.d.mts.map +1 -1
  69. package/build/queries/sql/Expr.js +18 -2
  70. package/build/queries/sql/Expr.js.map +1 -1
  71. package/build/queries/sql/JoinTree.cjs +72 -0
  72. package/build/queries/sql/JoinTree.cjs.map +1 -0
  73. package/build/queries/sql/JoinTree.d.cts +107 -0
  74. package/build/queries/sql/JoinTree.d.cts.map +1 -0
  75. package/build/queries/sql/JoinTree.d.mts +107 -0
  76. package/build/queries/sql/JoinTree.d.mts.map +1 -0
  77. package/build/queries/sql/JoinTree.js +71 -0
  78. package/build/queries/sql/JoinTree.js.map +1 -0
  79. package/build/queries/sql/Tables.cjs +103 -6
  80. package/build/queries/sql/Tables.cjs.map +1 -1
  81. package/build/queries/sql/Tables.d.cts +72 -13
  82. package/build/queries/sql/Tables.d.cts.map +1 -1
  83. package/build/queries/sql/Tables.d.mts +72 -13
  84. package/build/queries/sql/Tables.d.mts.map +1 -1
  85. package/build/queries/sql/Tables.js +101 -10
  86. package/build/queries/sql/Tables.js.map +1 -1
  87. package/build/queries/sql/custom.cjs +52 -0
  88. package/build/queries/sql/custom.cjs.map +1 -0
  89. package/build/queries/sql/custom.d.cts +85 -0
  90. package/build/queries/sql/custom.d.cts.map +1 -0
  91. package/build/queries/sql/custom.d.mts +85 -0
  92. package/build/queries/sql/custom.d.mts.map +1 -0
  93. package/build/queries/sql/custom.js +49 -0
  94. package/build/queries/sql/custom.js.map +1 -0
  95. package/build/queries/sql/execute.cjs +68 -28
  96. package/build/queries/sql/execute.cjs.map +1 -1
  97. package/build/queries/sql/execute.d.cts +110 -11
  98. package/build/queries/sql/execute.d.cts.map +1 -1
  99. package/build/queries/sql/execute.d.mts +110 -11
  100. package/build/queries/sql/execute.d.mts.map +1 -1
  101. package/build/queries/sql/execute.js +69 -29
  102. package/build/queries/sql/execute.js.map +1 -1
  103. package/build/queries/sql/expressions/types.d.cts +5 -5
  104. package/build/queries/sql/expressions/types.d.cts.map +1 -1
  105. package/build/queries/sql/expressions/types.d.mts +5 -5
  106. package/build/queries/sql/expressions/types.d.mts.map +1 -1
  107. package/build/queries/sql/keywords.cjs +1 -1
  108. package/build/queries/sql/keywords.cjs.map +1 -1
  109. package/build/queries/sql/keywords.d.cts +1 -1
  110. package/build/queries/sql/keywords.d.mts +1 -1
  111. package/build/queries/sql/keywords.js +1 -1
  112. package/build/queries/sql/keywords.js.map +1 -1
  113. package/build/queries/sql/query.cjs +100 -38
  114. package/build/queries/sql/query.cjs.map +1 -1
  115. package/build/queries/sql/query.d.cts +96 -46
  116. package/build/queries/sql/query.d.cts.map +1 -1
  117. package/build/queries/sql/query.d.mts +96 -46
  118. package/build/queries/sql/query.d.mts.map +1 -1
  119. package/build/queries/sql/query.js +102 -40
  120. package/build/queries/sql/query.js.map +1 -1
  121. package/build/queries/valueFilters.cjs +28 -36
  122. package/build/queries/valueFilters.cjs.map +1 -1
  123. package/build/queries/valueFilters.d.cts +2 -4
  124. package/build/queries/valueFilters.d.cts.map +1 -1
  125. package/build/queries/valueFilters.d.mts +2 -4
  126. package/build/queries/valueFilters.d.mts.map +1 -1
  127. package/build/queries/valueFilters.js +29 -36
  128. package/build/queries/valueFilters.js.map +1 -1
  129. package/build/serde/serde.cjs.map +1 -1
  130. package/build/serde/serde.js.map +1 -1
  131. package/package.json +2 -2
@@ -5,13 +5,14 @@ import { isDeferredAliasCondition } from "../find/DeferredAlias.js";
5
5
  import { ConditionBuilder } from "../ConditionBuilder.js";
6
6
  import { filterSoftDeletes, lazyExcludedSelects, stiSubtypeFilter } from "../entityQueryUtils.js";
7
7
  import { deepFindConditions } from "../parsedConditions.js";
8
- import { BaseExpr, RefExpr, TemplateExpr, asNode, deferredCondition, deferredSym, isExpr, orderByToSql, resolveDeferredConditions } from "./Expr.js";
8
+ import { BaseExpr, TemplateExpr, asNode, deferredCondition, deferredSym, isExpr, orderByToSql, resolveDeferredConditions, selectKeyBrand } from "./Expr.js";
9
9
  import { fail } from "../../utils.js";
10
10
  import { getBaseMeta } from "../../EntityMetadata.js";
11
11
  import { buildWhereClause } from "../renderConditions.js";
12
12
  import { AliasAssigner } from "./AliasAssigner.js";
13
- import { JoinTableHandle, collectionJoin, getTableMetadata, getTableMgmt, isTable, m2mJoinTable, referenceJoinSource } from "./Tables.js";
13
+ import { JoinTableHandle, collectionJoin, getTableMetadata, getTableMgmt, inQueryCondition, isEntityTable, isTable, m2mJoinTable, referenceJoinSource, tableSqlName } from "./Tables.js";
14
14
  import { buildExpr } from "./expressions/expression.js";
15
+ import { compileJoinTree } from "./JoinTree.js";
15
16
  //#region src/queries/sql/query.ts
16
17
  /**
17
18
  * `em.query`: SQL-shaped queries as plain object literals.
@@ -27,10 +28,10 @@ import { buildExpr } from "./expressions/expression.js";
27
28
  * select: { name: a.first_name, n: bookStats.n },
28
29
  * orderBy: { n: "DESC" },
29
30
  * });
30
- * // rows: { name: string; n: number | null }[] (null because of the LEFT join)
31
+ * // rows: { name: string; n: number | undefined }[] (undefined because of the LEFT join)
31
32
  *
32
- * `em.query(pojo)` runs it. `select` decides the row type: a bare table returns entities, a
33
- * `{ key: expr }` object returns typed POJOs, a bare subquery returns that subquery's rows.
33
+ * `em.query(pojo)` runs it. `select` decides the row type: a bare table returns entities, an array of
34
+ * columns or a `{ key: expr }` object returns typed POJOs, and a bare subquery returns its rows.
34
35
  *
35
36
  * `query(pojo)` turns the *same* POJO into a value: a derived table with typed columns, a scalar
36
37
  * expression, or an entity list. It is the one non-POJO step, and the subquery analog of `table(Author)`:
@@ -50,24 +51,26 @@ const subqueryBrand = Symbol("joist.subquery");
50
51
  const entityQueryBrand = Symbol("joist.entityQuery");
51
52
  const scalarQueryBrand = Symbol("joist.scalarQuery");
52
53
  function query(q) {
53
- const handle = new SubqueryHandle(toQuery(q));
54
- const output = handle.output();
55
- if (output.kind === "entity") return { [entityQueryBrand]: handle };
56
- else if (output.kind === "scalar") return new SubqueryExpr(handle);
57
- else return newSubqueryProxy(handle);
54
+ return createQueryValue(q);
58
55
  }
59
56
  /**
60
- * Builds a query value only when its user-supplied `where` survives pruning.
57
+ * Builds a query value only when its user-supplied `where` or join-tree filters survive pruning.
61
58
  *
62
59
  * Missing or empty conditions prune the value, including nested groups and `pruneIfUndefined`.
63
- * Joins, HAVING, and implicit soft-delete/STI conditions do not keep the value alive.
60
+ * Alias-only joins, HAVING, and implicit soft-delete/STI conditions do not keep the value alive.
64
61
  *
65
62
  * I.e. `queryMaybe({ from: b, where: b.authorId.eq(enabled ? a.id : undefined), select: b.id })`
66
63
  * returns `undefined` when `enabled` is false, so an enclosing `notExists` also prunes.
67
64
  */
68
65
  function queryMaybe(q) {
69
- checkCondition(q.where);
70
- return isPrunedQueryCondition(q.where) ? void 0 : query(q);
66
+ return createMaybeQueryValue(q);
67
+ }
68
+ /** Applies queryMaybe's structural pruning before creating any kind of read-query value. */
69
+ function createMaybeQueryValue(q) {
70
+ const normalized = normalizeQueryJoins(q);
71
+ const where = normalizeWhere(normalized.where);
72
+ checkCondition(where);
73
+ return isPrunedQueryCondition(where) ? void 0 : createQueryValue(normalized);
71
74
  }
72
75
  /**
73
76
  * Declares a `WITH RECURSIVE` CTE from its two terms, and returns the CTE as a readable value.
@@ -122,9 +125,9 @@ function recursiveQuery(name, base, step, opts = {}) {
122
125
  * // Selecting this expression keeps the join to Book b.
123
126
  * sql.number`${b.order} * ${2}`
124
127
  *
125
- * // Reference an unmodeled column; it is untracked at the type level.
126
- * sql.ref<string>(a, "ts_search")
127
- * sql.condition`${sql.ref(a, "ts_search")} @@ plainto_tsquery(${term})`
128
+ * // Reference an unmodeled column; prefer the table's own `column`, which keeps its source key.
129
+ * a.column<string>("ts_search")
130
+ * sql.condition`${a.column("ts_search")} @@ plainto_tsquery(${term})`
128
131
  * ```
129
132
  */
130
133
  function sql(strings, ...values) {
@@ -158,10 +161,6 @@ sql.booleanArrayOrNull = typedSql;
158
161
  sql.condition = function condition(strings, ...values) {
159
162
  return deferredCondition((ctx) => new TemplateExpr(strings, values).toSql(ctx));
160
163
  };
161
- /** A column Joist does not model, on a source that is in the query. */
162
- sql.ref = function ref(source, column) {
163
- return new RefExpr(handleOf(source), column);
164
- };
165
164
  /**
166
165
  * Parses `arg` (a `Query` POJO or `query(...)` value) into a runnable `Plan`.
167
166
  *
@@ -188,8 +187,8 @@ function isReadQueryValue(arg) {
188
187
  * Compiles scalar or nonempty named expression projections, shared by SELECT and mutation RETURNING.
189
188
  * Source-shaped values need read hydration/source rules and are deliberately excluded here.
190
189
  */
191
- function projectionToSql(select, ctx) {
192
- const output = projectionOutput(select);
190
+ function projectionToSql(select, ctx, joins) {
191
+ const output = joinedOutput(projectionOutput(select), joins);
193
192
  if (output.kind === "scalar") {
194
193
  const expr = output.columns[0][1];
195
194
  const fragment = expr.toSql(ctx);
@@ -198,7 +197,7 @@ function projectionToSql(select, ctx) {
198
197
  ...fragment,
199
198
  sql: `${fragment.sql} AS value`
200
199
  }],
201
- decodeRows: (_, rows) => rows.map((row) => expr.decode(row.value)),
200
+ decodeRows: (_, rows) => rows.map((row) => decodeValue(expr, row.value)),
202
201
  output
203
202
  };
204
203
  }
@@ -271,6 +270,9 @@ var SubqueryColumnExpr = class extends BaseExpr {
271
270
  this.key = key;
272
271
  this.inner = inner;
273
272
  }
273
+ get [selectKeyBrand]() {
274
+ return this.key;
275
+ }
274
276
  toSql(ctx) {
275
277
  const alias = ctx.aliasFor(this.handle);
276
278
  return {
@@ -500,6 +502,7 @@ function queryOutput(q) {
500
502
  columns
501
503
  };
502
504
  }
505
+ q = normalizeQueryJoins(q);
503
506
  const { select } = q;
504
507
  if (isTable(select)) return {
505
508
  kind: "entity",
@@ -514,6 +517,20 @@ function projectionOutput(select) {
514
517
  kind: "scalar",
515
518
  columns: [["value", asExpr(select, "select")]]
516
519
  };
520
+ if (Array.isArray(select)) {
521
+ if (select.length === 0) fail("A column array projection must not be empty");
522
+ const columns = select.map((value, index) => {
523
+ const expr = asExpr(value, `select[${index}]`);
524
+ const key = expr[selectKeyBrand];
525
+ if (typeof key !== "string") fail(`select[${index}] must be a physical or subquery column`);
526
+ return [key, expr];
527
+ });
528
+ if (new Set(columns.map(([key]) => key)).size !== columns.length) fail("A column array projection must not contain duplicate field names");
529
+ return {
530
+ kind: "pojo",
531
+ columns
532
+ };
533
+ }
517
534
  if (isPlainSelect(select)) {
518
535
  const columns = [];
519
536
  for (const key of Reflect.ownKeys(select)) {
@@ -529,7 +546,7 @@ function projectionOutput(select) {
529
546
  return fail("Expected a scalar expression or a nonempty named expression projection");
530
547
  }
531
548
  /**
532
- * Adds physical NULL from unmatched LEFT joins without mutating shared expressions or their codecs.
549
+ * Adds SQL nullability from unmatched LEFT joins without mutating shared expressions or their codecs.
533
550
  * Only direct columns carry sqlSource; COUNT and COALESCE retain their own SQL nullability.
534
551
  * I.e. selecting Book.title from a LEFT-joined Book makes its output nullable, but COUNT(Book.id) stays NOT NULL.
535
552
  */
@@ -637,7 +654,7 @@ function parseSetQuery(q, parent, assigner, recursiveSelf) {
637
654
  bindings,
638
655
  outerRefs: [...new Set(outerRefs.filter((ref) => !cteAliases.has(ref)))],
639
656
  output,
640
- decodeRows: plans[0].decodeRows
657
+ decodeRows: (_, rows) => rows.map((row) => decodeRow(row, output.columns))
641
658
  };
642
659
  }
643
660
  /** Validate and quote only named set output sorts, including before a reusable value is parsed. */
@@ -776,6 +793,7 @@ function describeHandle(handle) {
776
793
  function parseQuery(q, parent, assigner, recursiveSelf) {
777
794
  validateReadQuery(q);
778
795
  if (isSetQuery(q)) return parseSetQuery(q, parent, assigner, recursiveSelf);
796
+ q = normalizeQueryJoins(q);
779
797
  const ctx = new Ctx(assigner, parent);
780
798
  if (recursiveSelf) ctx.setRecursiveSelf(recursiveSelf);
781
799
  const joinEntries = resolveReferenceJoins(q.join);
@@ -823,7 +841,8 @@ function parseQuery(q, parent, assigner, recursiveSelf) {
823
841
  const sti = stiEntityPlan(q, from);
824
842
  const { selects, decodeRows, output } = selectsToSql(q, ctx, from, cti.selects ?? sti.selects);
825
843
  const fromInjected = [...injectedConditions(from, softDeletes), ...sti.conditions];
826
- const where = conditionToSql(fromInjected.length > 0 ? { and: [q.where, ...fromInjected] } : q.where, ctx, true);
844
+ const predicate = normalizeWhere(q.where);
845
+ const where = conditionToSql(fromInjected.length > 0 ? { and: [predicate, ...fromInjected] } : predicate, ctx, true);
827
846
  const having = conditionToSql(q.having, ctx, true);
828
847
  const groupBys = (q.groupBy ?? []).map((g) => asExpr(g, "groupBy").toSql(ctx));
829
848
  const orderBys = orderBysToSql(q, ctx);
@@ -936,15 +955,17 @@ function registerSource(source, ctx, assigner) {
936
955
  };
937
956
  };
938
957
  } else {
939
- const meta = getTableMetadata(source);
940
- const alias = assigner.getAlias(meta.tableName);
958
+ const mgmt = getTableMgmt(source);
959
+ const { tableName } = mgmt;
960
+ const meta = "meta" in mgmt ? mgmt.meta : void 0;
961
+ const alias = assigner.getAlias(tableName);
941
962
  ctx.register(handle, alias);
942
963
  return () => {
943
- const entitySelects = meta.hasLazyColumns ? lazyExcludedSelects(meta, alias) : [kqStar(alias)];
964
+ const entitySelects = !meta ? [] : meta.hasLazyColumns ? lazyExcludedSelects(meta, alias) : [kqStar(alias)];
944
965
  return {
945
966
  handle,
946
967
  alias,
947
- sql: `${kq(meta.tableName)} AS ${kq(alias)}`,
968
+ sql: `${tableSqlName(mgmt)} AS ${kq(alias)}`,
948
969
  bindings: [],
949
970
  refs: [],
950
971
  entitySelects,
@@ -996,7 +1017,8 @@ function registerJoinTable(handle, ctx, assigner) {
996
1017
  /** Generates the `select` clause SQL and returns how to decode the resulting rows. */
997
1018
  function selectsToSql(q, ctx, from, entitySelects) {
998
1019
  const { select } = q;
999
- if (isTable(select)) {
1020
+ if (isTable(select) && !isEntityTable(select)) fail("A custom table cannot be selected as an entity; select its columns individually");
1021
+ if (isEntityTable(select)) {
1000
1022
  if (from.handle !== getTableMgmt(select)) fail("Selecting a joined table is not supported yet; select the from table, or select its columns individually");
1001
1023
  const alias = ctx.aliasFor(getTableMgmt(select));
1002
1024
  const meta = getTableMetadata(select);
@@ -1027,9 +1049,7 @@ function selectsToSql(q, ctx, from, entitySelects) {
1027
1049
  output
1028
1050
  };
1029
1051
  }
1030
- const projection = projectionToSql(select, ctx);
1031
- projection.output = joinedOutput(projection.output, q.join);
1032
- return projection;
1052
+ return projectionToSql(select, ctx, q.join);
1033
1053
  }
1034
1054
  /**
1035
1055
  * Builds the mandatory CTI joins and flat row projection needed by entity hydration.
@@ -1040,7 +1060,7 @@ function selectsToSql(q, ctx, from, entitySelects) {
1040
1060
  * `_sN` aliases can be made unique before their projection fragments are built.
1041
1061
  */
1042
1062
  function ctiEntityPlan(q, from, assigner) {
1043
- if (!isTable(q.select) || from.handle !== getTableMgmt(q.select)) return {
1063
+ if (!isEntityTable(q.select) || from.handle !== getTableMgmt(q.select)) return {
1044
1064
  joins: [],
1045
1065
  selects: void 0
1046
1066
  };
@@ -1102,7 +1122,7 @@ function ctiEntityPlan(q, from, assigner) {
1102
1122
  }
1103
1123
  /** Adds the discriminator projection and subtype filter required by STI entity hydration. */
1104
1124
  function stiEntityPlan(q, from) {
1105
- if (!isTable(q.select) || from.handle !== getTableMgmt(q.select)) return {
1125
+ if (!isEntityTable(q.select) || from.handle !== getTableMgmt(q.select)) return {
1106
1126
  selects: void 0,
1107
1127
  conditions: []
1108
1128
  };
@@ -1168,7 +1188,7 @@ function decodeRow(row, decoders) {
1168
1188
  const result = {};
1169
1189
  for (const [key, expr] of decoders) {
1170
1190
  const value = row[key];
1171
- const decoded = value === null || value === void 0 ? null : expr.decode(value);
1191
+ const decoded = decodeValue(expr, value);
1172
1192
  if (key === "__proto__") Object.defineProperty(result, key, {
1173
1193
  value: decoded,
1174
1194
  enumerable: true,
@@ -1179,6 +1199,10 @@ function decodeRow(row, decoders) {
1179
1199
  }
1180
1200
  return result;
1181
1201
  }
1202
+ /** Decodes a top-level projected SQL NULL using Joist's undefined convention. */
1203
+ function decodeValue(expr, value) {
1204
+ return value === null ? void 0 : expr.decode(value);
1205
+ }
1182
1206
  const ORDER_BY_DIRECTIONS = [
1183
1207
  "ASC",
1184
1208
  "DESC",
@@ -1188,7 +1212,7 @@ const ORDER_BY_DIRECTIONS = [
1188
1212
  "DESC NULLS LAST"
1189
1213
  ];
1190
1214
  /**
1191
- * Generates ORDER BY SQL in entry order for keyed/expression arrays or a single keyed object.
1215
+ * Generates ORDER BY SQL in entry order for keyed/expression arrays or a single entry.
1192
1216
  *
1193
1217
  * Expression entries retain bindings and alias references for join pruning. Undefined entries and
1194
1218
  * directions are omitted before expression references are collected.
@@ -1217,7 +1241,7 @@ function orderBysToSql(q, ctx) {
1217
1241
  });
1218
1242
  } else {
1219
1243
  if (isExpr(select)) return fail(`the keyed orderBy form needs a POJO or entity select`);
1220
- if (!(isSubqueryValue(select) ? select[subqueryBrand].columnKeys() : Object.keys(select)).includes(key)) return fail(`orderBy key '${key}' is not a key of select`);
1244
+ if (!(isSubqueryValue(select) ? select[subqueryBrand].columnKeys() : projectionOutput(select).columns.map(([key]) => key)).includes(key)) return fail(`orderBy key '${key}' is not a key of select`);
1221
1245
  result.push({
1222
1246
  sql: `${safeKq(key)} ${dir}`,
1223
1247
  bindings: [],
@@ -1260,6 +1284,10 @@ function conditionToSql(cond, ctx, topLevel) {
1260
1284
  function checkCondition(value) {
1261
1285
  if (isDeferredAliasCondition(value) || value && typeof value === "object" && predicateBrand in value && value[predicateBrand] === "domain") fail("Domain alias conditions are only supported by em.find; use table(...) predicates in SQL queries and mutations.");
1262
1286
  if (value === void 0 || value === skipCondition) return;
1287
+ if (isInQueryCondition(value)) {
1288
+ checkCondition(normalizeWhere(normalizeQueryJoins(toQuery(value[inQueryCondition].query)).where));
1289
+ return;
1290
+ }
1263
1291
  if (!value || typeof value !== "object" || Array.isArray(value) || isExpr(value)) fail("Query predicate must be a condition or an and/or group");
1264
1292
  const condition = value;
1265
1293
  if ("and" in condition || "or" in condition) {
@@ -1345,6 +1373,13 @@ function checkConditionKeys(value, allowed, description) {
1345
1373
  */
1346
1374
  function resolveQueryCondition(cond, ctx) {
1347
1375
  if (cond === void 0) return void 0;
1376
+ if (isInQueryCondition(cond)) {
1377
+ const { column, query } = cond[inQueryCondition];
1378
+ const value = createMaybeQueryValue(query);
1379
+ if (value === void 0) return void 0;
1380
+ if (!isExpr(value)) return fail("An in query literal must select one expression");
1381
+ return column.in(value);
1382
+ }
1348
1383
  if ("and" in cond && cond.and) return {
1349
1384
  and: cond.and.map((child) => resolveQueryCondition(child, ctx)),
1350
1385
  pruneIfUndefined: cond.pruneIfUndefined
@@ -1379,6 +1414,7 @@ function isFilter(cond) {
1379
1414
  */
1380
1415
  function isPrunedQueryCondition(cond) {
1381
1416
  if (cond === void 0 || cond === skipCondition) return true;
1417
+ if (isInQueryCondition(cond)) return isPrunedQueryCondition(normalizeWhere(normalizeQueryJoins(cond[inQueryCondition].query).where));
1382
1418
  if ("and" in cond || "or" in cond) {
1383
1419
  const children = (cond.and ?? cond.or ?? []).map(isPrunedQueryCondition);
1384
1420
  return children.every((pruned) => pruned) || "pruneIfUndefined" in cond && cond.pruneIfUndefined === "any" && children.some((pruned) => pruned);
@@ -1387,6 +1423,10 @@ function isPrunedQueryCondition(cond) {
1387
1423
  if ("notExists" in cond) return cond.notExists === void 0;
1388
1424
  return false;
1389
1425
  }
1426
+ /** Recognizes the direct query-literal condition created by EntityColumn.in. */
1427
+ function isInQueryCondition(value) {
1428
+ return typeof value === "object" && value !== null && inQueryCondition in value;
1429
+ }
1390
1430
  /** The physical source aliases a parsed condition tree references. */
1391
1431
  function refsOf(parsed) {
1392
1432
  return deepFindConditions(parsed, false).flatMap((c) => c.kind === "column" ? [c.alias] : c.kind === "raw" ? c.aliases : c.outerAliases);
@@ -1520,6 +1560,28 @@ function joinFragmentParts(parts, sep) {
1520
1560
  function isDefined(value) {
1521
1561
  return value !== void 0;
1522
1562
  }
1563
+ /** Lowers a relationship tree before SQL rendering or output nullability is calculated. */
1564
+ function normalizeQueryJoins(q) {
1565
+ if (q.join === void 0 || Array.isArray(q.join)) return q;
1566
+ const tree = compileJoinTree(q.from, q.join);
1567
+ return {
1568
+ ...q,
1569
+ join: tree.joins,
1570
+ where: { and: [tree.condition, normalizeWhere(q.where)] }
1571
+ };
1572
+ }
1573
+ /** Gives WHERE arrays the same pruning and validation as explicit AND groups. */
1574
+ function normalizeWhere(where) {
1575
+ return Array.isArray(where) ? { and: where } : where;
1576
+ }
1577
+ /** Creates the runtime query value after the public signature has checked its input. */
1578
+ function createQueryValue(q) {
1579
+ const handle = new SubqueryHandle(toQuery(q));
1580
+ const output = handle.output();
1581
+ if (output.kind === "entity") return { [entityQueryBrand]: handle };
1582
+ else if (output.kind === "scalar") return new SubqueryExpr(handle);
1583
+ else return newSubqueryProxy(handle);
1584
+ }
1523
1585
  /** Keeps shorthand declarations callable-only, without recursively copying sql's own shorthand properties. */
1524
1586
  function typedSql(strings, ...values) {
1525
1587
  return new TemplateExpr(strings, values);