uql-orm 0.36.1 → 0.37.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/dist/browser/http/http.d.ts +1 -1
- package/dist/browser/querier/httpQuerier.d.ts +12 -7
- package/dist/browser/querier/httpQuerier.js +6 -1
- package/dist/browser/type/clientQuerier.d.ts +10 -23
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +7 -7
- package/dist/cockroachdb/cockroachDialect.d.ts +8 -1
- package/dist/cockroachdb/cockroachDialect.js +12 -0
- package/dist/dialect/abstractSqlDialect.d.ts +31 -4
- package/dist/dialect/abstractSqlDialect.js +64 -15
- package/dist/dialect/aliases.d.ts +30 -0
- package/dist/dialect/aliases.js +32 -0
- package/dist/dialect/jsonSql.d.ts +0 -20
- package/dist/dialect/jsonSql.js +0 -20
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +6 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +20 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +2 -0
- package/dist/dialect/pgLikeSqlDialect.js +4 -1
- package/dist/dialect/queryJoins.d.ts +6 -0
- package/dist/dialect/queryJoins.js +13 -0
- package/dist/http/contract.d.ts +0 -7
- package/dist/http/handler.d.ts +2 -2
- package/dist/http/handler.js +1 -1
- package/dist/http/query.js +3 -2
- package/dist/mongo/mongoDialect.d.ts +15 -4
- package/dist/mongo/mongoDialect.js +65 -16
- package/dist/mongo/mongodbQuerier.d.ts +13 -2
- package/dist/mongo/mongodbQuerier.js +46 -2
- package/dist/postgres/postgresDialect.d.ts +7 -0
- package/dist/postgres/postgresDialect.js +13 -0
- package/dist/querier/abstractQuerier.d.ts +34 -18
- package/dist/querier/abstractQuerier.js +43 -30
- package/dist/querier/abstractQuerierPool.d.ts +9 -7
- package/dist/querier/abstractQuerierPool.js +6 -0
- package/dist/querier/abstractSqlQuerier.d.ts +30 -2
- package/dist/querier/abstractSqlQuerier.js +61 -8
- package/dist/querier/relationCount.d.ts +16 -0
- package/dist/querier/relationCount.js +104 -0
- package/dist/sqlite/sqliteDialect.d.ts +6 -1
- package/dist/sqlite/sqliteDialect.js +12 -1
- package/dist/type/dialect.d.ts +10 -3
- package/dist/type/index.d.ts +1 -0
- package/dist/type/index.js +1 -0
- package/dist/type/querier.d.ts +21 -14
- package/dist/type/query.d.ts +77 -16
- package/dist/type/query.js +17 -0
- package/dist/type/universalQuerier.d.ts +85 -48
- package/dist/type/wire.d.ts +37 -0
- package/dist/type/wire.js +1 -0
- package/dist/util/dialect.util.d.ts +6 -0
- package/dist/util/dialect.util.js +15 -0
- package/dist/util/relationQuery.util.d.ts +9 -0
- package/dist/util/relationQuery.util.js +13 -0
- package/package.json +4 -4
|
@@ -121,9 +121,15 @@ export class AbstractQuerierPool {
|
|
|
121
121
|
count(entity, q, opts) {
|
|
122
122
|
return this.withQuerier((querier) => querier.count(entity, q, opts));
|
|
123
123
|
}
|
|
124
|
+
exists(entity, q, opts) {
|
|
125
|
+
return this.withQuerier((querier) => querier.exists(entity, q, opts));
|
|
126
|
+
}
|
|
124
127
|
aggregate(entity, q, opts) {
|
|
125
128
|
return this.withQuerier((querier) => querier.aggregate(entity, q, opts));
|
|
126
129
|
}
|
|
130
|
+
estimatedCount(entity) {
|
|
131
|
+
return this.withQuerier((querier) => querier.estimatedCount(entity));
|
|
132
|
+
}
|
|
127
133
|
insertOne(entity, payload) {
|
|
128
134
|
return this.withQuerier((querier) => querier.insertOne(entity, payload));
|
|
129
135
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
2
|
-
import type { EntityData, ExtraOptions, IdValue, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryGroupMap, QueryOptions, QuerySearch, QueryUpdateResult, SqlQuerier, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
2
|
+
import type { EntityData, ExtraOptions, IdValue, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryFilter, QueryGroupMap, QueryOptions, QuerySearch, QueryUpdateResult, SqlQuerier, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
3
3
|
import type { BuildUpdateResultPayload } from '../util/sql.util.js';
|
|
4
4
|
import { AbstractQuerier } from './abstractQuerier.js';
|
|
5
5
|
export declare abstract class AbstractSqlQuerier extends AbstractQuerier implements SqlQuerier {
|
|
@@ -46,6 +46,27 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
|
|
|
46
46
|
*/
|
|
47
47
|
protected assertLockable<E>(entity: Type<E>, q: Query<E>): void;
|
|
48
48
|
protected internalFindMany<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<E[]>;
|
|
49
|
+
/**
|
|
50
|
+
* One statement for both: the page carries its own unpaged total in an extra column. An empty page
|
|
51
|
+
* has no row to carry it, which is the one case still needing a count of its own - a `$skip` past
|
|
52
|
+
* the end, or a filter nothing matched.
|
|
53
|
+
*
|
|
54
|
+
* A `$required` relation needs no special case: the window counts what the INNER JOIN left, which
|
|
55
|
+
* is exactly the total a caller of a filtered read is asking for. A `$lock` is the one clause an
|
|
56
|
+
* engine may refuse to have in the same statement, which {@link AbstractSqlDialect.supportsWindowWithRowLock}
|
|
57
|
+
* answers; where it does, the total comes from a count of its own. A `$distinct` read needs one
|
|
58
|
+
* too, and a deduplicating one: see {@link AbstractSqlDialect.countDistinct}.
|
|
59
|
+
*/
|
|
60
|
+
/**
|
|
61
|
+
* How to count when the total cannot ride along in the read's own `COUNT(*) OVER ()` column, or
|
|
62
|
+
* `undefined` when it can. Two clauses rule the window out: `$distinct`, because a window counts
|
|
63
|
+
* before the deduplication and so overstates the page, and `$lock` on an engine that refuses the
|
|
64
|
+
* pair outright. Both then cost a second statement; only the counting differs.
|
|
65
|
+
*/
|
|
66
|
+
private countedSeparately;
|
|
67
|
+
protected internalFindManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
|
|
68
|
+
private selectRows;
|
|
69
|
+
private hydrateRows;
|
|
49
70
|
protected internalFindManyStream<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): AsyncGenerator<Awaited<E>, void, unknown>;
|
|
50
71
|
/**
|
|
51
72
|
* Internal streaming query - returns an async iterable of raw rows.
|
|
@@ -64,7 +85,14 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
|
|
|
64
85
|
* which cost more than the decoding it guards, on a flat read.
|
|
65
86
|
*/
|
|
66
87
|
private hydrateFields;
|
|
67
|
-
|
|
88
|
+
/**
|
|
89
|
+
* Runs a statement whose one row carries a {@link COUNT_ALIAS} column. `Number` because `COUNT(*)` is BIGINT and
|
|
90
|
+
* a caller supplying their own `types` replaces the decoding the pools do at the wire; `?? 0` because
|
|
91
|
+
* a catalog that does not know the table answers with no row, which is nothing counted.
|
|
92
|
+
*/
|
|
93
|
+
private runCount;
|
|
94
|
+
protected internalCount<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: QueryOptions): Promise<number>;
|
|
95
|
+
estimatedCount<E extends object>(entity: Type<E>): Promise<number>;
|
|
68
96
|
protected internalAggregate<E extends object, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
69
97
|
internalInsertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
70
98
|
internalUpdateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { COUNT_ALIAS, TOTAL_ALIAS } from '../dialect/aliases.js';
|
|
1
2
|
import { decodeColumn } from '../dialect/hydrateColumn.js';
|
|
2
3
|
import { getMeta } from '../entity/index.js';
|
|
3
4
|
import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, getRelationRequestSummary, hasKeys, idOnlyQuery, isAutoIncrement, isPagedQuery, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
@@ -76,11 +77,54 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
76
77
|
}
|
|
77
78
|
}
|
|
78
79
|
async internalFindMany(entity, q, opts) {
|
|
80
|
+
return this.hydrateRows(entity, q, await this.selectRows(entity, q, opts));
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* One statement for both: the page carries its own unpaged total in an extra column. An empty page
|
|
84
|
+
* has no row to carry it, which is the one case still needing a count of its own - a `$skip` past
|
|
85
|
+
* the end, or a filter nothing matched.
|
|
86
|
+
*
|
|
87
|
+
* A `$required` relation needs no special case: the window counts what the INNER JOIN left, which
|
|
88
|
+
* is exactly the total a caller of a filtered read is asking for. A `$lock` is the one clause an
|
|
89
|
+
* engine may refuse to have in the same statement, which {@link AbstractSqlDialect.supportsWindowWithRowLock}
|
|
90
|
+
* answers; where it does, the total comes from a count of its own. A `$distinct` read needs one
|
|
91
|
+
* too, and a deduplicating one: see {@link AbstractSqlDialect.countDistinct}.
|
|
92
|
+
*/
|
|
93
|
+
/**
|
|
94
|
+
* How to count when the total cannot ride along in the read's own `COUNT(*) OVER ()` column, or
|
|
95
|
+
* `undefined` when it can. Two clauses rule the window out: `$distinct`, because a window counts
|
|
96
|
+
* before the deduplication and so overstates the page, and `$lock` on an engine that refuses the
|
|
97
|
+
* pair outright. Both then cost a second statement; only the counting differs.
|
|
98
|
+
*/
|
|
99
|
+
countedSeparately(entity, q, opts) {
|
|
100
|
+
if (q.$distinct) {
|
|
101
|
+
return (ctx) => this.dialect.countDistinct(ctx, entity, q, opts);
|
|
102
|
+
}
|
|
103
|
+
if (q.$lock && !this.dialect.supportsWindowWithRowLock) {
|
|
104
|
+
return (ctx) => this.dialect.count(ctx, entity, q, opts);
|
|
105
|
+
}
|
|
106
|
+
return undefined;
|
|
107
|
+
}
|
|
108
|
+
async internalFindManyAndCount(entity, q, opts) {
|
|
109
|
+
const separately = this.countedSeparately(entity, q, opts);
|
|
110
|
+
if (separately) {
|
|
111
|
+
return Promise.all([this.internalFindMany(entity, q, opts), this.runCount(separately)]);
|
|
112
|
+
}
|
|
113
|
+
const rows = await this.selectRows(entity, q, opts, TOTAL_ALIAS);
|
|
114
|
+
const total = rows.length ? Number(rows[0][TOTAL_ALIAS]) : await this.internalCount(entity, q, opts);
|
|
115
|
+
for (const row of rows) {
|
|
116
|
+
delete row[TOTAL_ALIAS];
|
|
117
|
+
}
|
|
118
|
+
return [await this.hydrateRows(entity, q, rows), total];
|
|
119
|
+
}
|
|
120
|
+
async selectRows(entity, q, opts, totalAlias) {
|
|
79
121
|
this.assertLockable(entity, q);
|
|
80
122
|
const ctx = this.dialect.createContext();
|
|
81
|
-
this.dialect.find(ctx, entity, q, opts);
|
|
82
|
-
|
|
83
|
-
|
|
123
|
+
this.dialect.find(ctx, entity, q, opts, totalAlias);
|
|
124
|
+
return this.all(ctx.sql, ctx.values);
|
|
125
|
+
}
|
|
126
|
+
async hydrateRows(entity, q, rows) {
|
|
127
|
+
const founds = unflatObjects(rows).map((row) => this.hydrateFields(entity, row));
|
|
84
128
|
await this.fillToManyRelations(entity, founds, q.$populate);
|
|
85
129
|
return founds;
|
|
86
130
|
}
|
|
@@ -166,13 +210,22 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
166
210
|
}
|
|
167
211
|
return dto;
|
|
168
212
|
}
|
|
169
|
-
|
|
213
|
+
/**
|
|
214
|
+
* Runs a statement whose one row carries a {@link COUNT_ALIAS} column. `Number` because `COUNT(*)` is BIGINT and
|
|
215
|
+
* a caller supplying their own `types` replaces the decoding the pools do at the wire; `?? 0` because
|
|
216
|
+
* a catalog that does not know the table answers with no row, which is nothing counted.
|
|
217
|
+
*/
|
|
218
|
+
async runCount(build) {
|
|
170
219
|
const ctx = this.dialect.createContext();
|
|
171
|
-
|
|
220
|
+
build(ctx);
|
|
172
221
|
const res = await this.all(ctx.sql, ctx.values);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
222
|
+
return Number(res[0]?.[COUNT_ALIAS] ?? 0);
|
|
223
|
+
}
|
|
224
|
+
async internalCount(entity, q = {}, opts) {
|
|
225
|
+
return this.runCount((ctx) => this.dialect.count(ctx, entity, q, opts));
|
|
226
|
+
}
|
|
227
|
+
async estimatedCount(entity) {
|
|
228
|
+
return this.runCount((ctx) => this.dialect.estimatedCount(ctx, entity));
|
|
176
229
|
}
|
|
177
230
|
async internalAggregate(entity, q, opts) {
|
|
178
231
|
const ctx = this.dialect.createContext();
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Querier, Query, QueryCount, Type } from '../type/index.js';
|
|
2
|
+
/** What counting asks of a querier, rather than the whole interface: two reads, both batched. */
|
|
3
|
+
type CountingQuerier = Pick<Querier, 'aggregate' | 'findMany'>;
|
|
4
|
+
/**
|
|
5
|
+
* A `$count` groups its tallies by the parent's id, so the id has to outlive the projection - the
|
|
6
|
+
* same reason populating a relation keeps it. A whitelisting `$select` gains the key and an
|
|
7
|
+
* `$exclude` loses it; the raw-array `$select` form has nothing to augment, so it is refused.
|
|
8
|
+
*/
|
|
9
|
+
export declare function withIdForCounts<E extends object>(entity: Type<E>, q: Query<E>): Query<E>;
|
|
10
|
+
/**
|
|
11
|
+
* How many rows each counted relation holds, under `_count` on every parent. One grouped aggregate
|
|
12
|
+
* per relation over every parent at once - the batching a populated to-many already gets - so the
|
|
13
|
+
* cost stays flat in the number of rows the read returned rather than one statement per row.
|
|
14
|
+
*/
|
|
15
|
+
export declare function fillRelationCounts<E>(querier: CountingQuerier, entity: Type<E>, payload: E[], count?: QueryCount<E>): Promise<void>;
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { COUNT_ALIAS } from '../dialect/aliases.js';
|
|
2
|
+
import { getMeta } from '../entity/index.js';
|
|
3
|
+
import { COUNT_RESULT_KEY } from '../type/index.js';
|
|
4
|
+
import { asSelectMap, getKeys, parentKeyColumn, targetKeyColumn } from '../util/index.js';
|
|
5
|
+
/**
|
|
6
|
+
* A `$count` groups its tallies by the parent's id, so the id has to outlive the projection - the
|
|
7
|
+
* same reason populating a relation keeps it. A whitelisting `$select` gains the key and an
|
|
8
|
+
* `$exclude` loses it; the raw-array `$select` form has nothing to augment, so it is refused.
|
|
9
|
+
*/
|
|
10
|
+
export function withIdForCounts(entity, q) {
|
|
11
|
+
const meta = getMeta(entity);
|
|
12
|
+
if (!q.$count || !meta.id) {
|
|
13
|
+
return q;
|
|
14
|
+
}
|
|
15
|
+
if (q.$distinct) {
|
|
16
|
+
// The id would have to join the projection for the tallies to group by, and that is the set
|
|
17
|
+
// `$distinct` collapses on: adding it silently stops the grouping collapsing anything at all.
|
|
18
|
+
throw new TypeError(`$count cannot be combined with $distinct: the tallies group by each row's '${String(meta.id)}', which the grouping does not keep.`);
|
|
19
|
+
}
|
|
20
|
+
if (Array.isArray(q.$select)) {
|
|
21
|
+
// Nothing to add the id to, and without it every tally would be looked up by `undefined` and
|
|
22
|
+
// come back zero. Refused rather than answered wrong, the way an unorderable clause is.
|
|
23
|
+
throw new TypeError(`$count needs the '${String(meta.id)}' of each row to group its tallies by, which a raw $select cannot carry. Use a $select map, or drop the $count.`);
|
|
24
|
+
}
|
|
25
|
+
const next = { ...q };
|
|
26
|
+
const select = asSelectMap(q.$select);
|
|
27
|
+
if (select && !select[meta.id]) {
|
|
28
|
+
next.$select = { ...select, [meta.id]: true };
|
|
29
|
+
}
|
|
30
|
+
if (q.$exclude?.[meta.id]) {
|
|
31
|
+
const kept = { ...q.$exclude };
|
|
32
|
+
delete kept[meta.id];
|
|
33
|
+
next.$exclude = kept;
|
|
34
|
+
}
|
|
35
|
+
return next;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* How many rows each counted relation holds, under `_count` on every parent. One grouped aggregate
|
|
39
|
+
* per relation over every parent at once - the batching a populated to-many already gets - so the
|
|
40
|
+
* cost stays flat in the number of rows the read returned rather than one statement per row.
|
|
41
|
+
*/
|
|
42
|
+
export async function fillRelationCounts(querier, entity, payload, count) {
|
|
43
|
+
if (!payload.length || !count) {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
const meta = getMeta(entity);
|
|
47
|
+
// The ids cross the same boundary the entity does: past here every column is named by metadata,
|
|
48
|
+
// so they are read as {@link CountedRow}'s values rather than as this entity's own id type.
|
|
49
|
+
const ids = payload.map((it) => it[meta.id]);
|
|
50
|
+
const counted = new Map();
|
|
51
|
+
for (const relKey of getKeys(count)) {
|
|
52
|
+
const value = count[relKey];
|
|
53
|
+
const relOpts = meta.relations[relKey];
|
|
54
|
+
if (!value || !relOpts) {
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
const where = typeof value === 'object' ? value.$where : undefined;
|
|
58
|
+
counted.set(relKey, await countPerParent(querier, relOpts, ids, where));
|
|
59
|
+
}
|
|
60
|
+
for (const parent of payload) {
|
|
61
|
+
const id = String(parent[meta.id]);
|
|
62
|
+
const row = {};
|
|
63
|
+
for (const [relKey, byParent] of counted) {
|
|
64
|
+
// A parent the grouped result has no row for matched nothing, which is a zero rather than a
|
|
65
|
+
// gap: `_count` names what the caller asked to count, so every key it asked for is present.
|
|
66
|
+
row[relKey] = byParent[id] ?? 0;
|
|
67
|
+
}
|
|
68
|
+
parent[COUNT_RESULT_KEY] = row;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
async function countPerParent(querier, relOpts, ids, where) {
|
|
72
|
+
const through = relOpts.through;
|
|
73
|
+
if (through) {
|
|
74
|
+
return countThroughPerParent(querier, relOpts, through(), ids, where);
|
|
75
|
+
}
|
|
76
|
+
const foreign = parentKeyColumn(relOpts);
|
|
77
|
+
return groupedCount(querier, relOpts.entity(), foreign, { ...where, [foreign]: ids });
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* A many-to-many counts its junction rows, one per pairing. A filter names the target's columns,
|
|
81
|
+
* which the junction does not have, so the matching targets are resolved first and the junction
|
|
82
|
+
* counted against them - the one shape here that costs a second statement, and only when filtered.
|
|
83
|
+
*/
|
|
84
|
+
async function countThroughPerParent(querier, relOpts, throughEntity, ids, where) {
|
|
85
|
+
const local = parentKeyColumn(relOpts);
|
|
86
|
+
const throughWhere = { [local]: ids };
|
|
87
|
+
if (where) {
|
|
88
|
+
const target = relOpts.entity();
|
|
89
|
+
const targetId = getMeta(target).id;
|
|
90
|
+
const targets = await querier.findMany(target, { $select: { [targetId]: true }, $where: where });
|
|
91
|
+
throughWhere[targetKeyColumn(relOpts)] = targets.map((it) => it[targetId]);
|
|
92
|
+
}
|
|
93
|
+
return groupedCount(querier, throughEntity, local, throughWhere);
|
|
94
|
+
}
|
|
95
|
+
/** `SELECT <key>, COUNT(*) ... GROUP BY <key>`, as a lookup from parent key to tally. */
|
|
96
|
+
async function groupedCount(querier, entity, groupKey, where) {
|
|
97
|
+
const $agg = { [COUNT_ALIAS]: { $count: '*' } };
|
|
98
|
+
const rows = await querier.aggregate(entity, { $group: { [groupKey]: true }, $agg, $where: where });
|
|
99
|
+
const byParent = {};
|
|
100
|
+
for (const row of rows) {
|
|
101
|
+
byParent[String(row[groupKey])] = Number(row[COUNT_ALIAS]);
|
|
102
|
+
}
|
|
103
|
+
return byParent;
|
|
104
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
|
|
2
|
-
import type { DialectFeatures, EntityMeta, FieldOptions, QueryContext, QuerySizeComparisonOps, QueryTextSearchOptions, Type, VectorDistance } from '../type/index.js';
|
|
2
|
+
import type { DialectFeatures, EntityMeta, FieldOptions, QueryContext, QueryPager, QuerySizeComparisonOps, QueryTextSearchOptions, Type, VectorDistance } from '../type/index.js';
|
|
3
3
|
export declare class SqliteDialect extends AbstractSqlDialect {
|
|
4
4
|
/** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
|
|
5
5
|
protected readonly featureDefaults: DialectFeatures;
|
|
@@ -30,6 +30,11 @@ export declare class SqliteDialect extends AbstractSqlDialect {
|
|
|
30
30
|
protected readonly caseInsensitiveMatch = "native";
|
|
31
31
|
protected get neOp(): string;
|
|
32
32
|
normalizeValue(value: unknown): unknown;
|
|
33
|
+
/**
|
|
34
|
+
* `OFFSET` is only legal after a `LIMIT` here too, so a bare `$skip` needs one - `-1` being
|
|
35
|
+
* SQLite's own spelling of "no limit", where the MySQL family uses its largest `BIGINT`.
|
|
36
|
+
*/
|
|
37
|
+
pager(ctx: QueryContext, opts: QueryPager): void;
|
|
33
38
|
/**
|
|
34
39
|
* FTS5 matches the table itself rather than its columns, so this only works when the table *is* an
|
|
35
40
|
* FTS5 virtual table (UQL does not create those; declare it outside your entities).
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
|
|
2
|
-
import { JSON_ELEM_ALIAS_PREFIX, JSON_PULL_ALIAS
|
|
2
|
+
import { JSON_ELEM_ALIAS_PREFIX, JSON_PULL_ALIAS } from '../dialect/aliases.js';
|
|
3
|
+
import { jsonAssignCall, jsonElemExists, jsonPath, jsonRemoveCall, jsonSetTarget } from '../dialect/jsonSql.js';
|
|
3
4
|
export class SqliteDialect extends AbstractSqlDialect {
|
|
4
5
|
/** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
|
|
5
6
|
featureDefaults = {
|
|
@@ -65,6 +66,16 @@ export class SqliteDialect extends AbstractSqlDialect {
|
|
|
65
66
|
return value.getTime();
|
|
66
67
|
return super.normalizeValue(value);
|
|
67
68
|
}
|
|
69
|
+
/**
|
|
70
|
+
* `OFFSET` is only legal after a `LIMIT` here too, so a bare `$skip` needs one - `-1` being
|
|
71
|
+
* SQLite's own spelling of "no limit", where the MySQL family uses its largest `BIGINT`.
|
|
72
|
+
*/
|
|
73
|
+
pager(ctx, opts) {
|
|
74
|
+
if (opts.$limit === undefined && opts.$skip !== undefined) {
|
|
75
|
+
ctx.append(' LIMIT -1');
|
|
76
|
+
}
|
|
77
|
+
super.pager(ctx, opts);
|
|
78
|
+
}
|
|
68
79
|
/**
|
|
69
80
|
* FTS5 matches the table itself rather than its columns, so this only works when the table *is* an
|
|
70
81
|
* FTS5 virtual table (UQL does not create those; declare it outside your entities).
|
package/dist/type/dialect.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { UpdatePayload } from './entity.js';
|
|
2
|
-
import type { Query, QueryConflictPaths, QueryOptions, QuerySearch } from './query.js';
|
|
2
|
+
import type { Query, QueryConflictPaths, QueryFilter, QueryOptions, QuerySearch } from './query.js';
|
|
3
3
|
import type { QueryAggMap, QueryAggregate, QueryGroupMap } from './queryAggregate.js';
|
|
4
4
|
import type { Type } from './utility.js';
|
|
5
5
|
/**
|
|
@@ -22,6 +22,12 @@ export type QueryWhereOptions = QueryComparisonOptions & {
|
|
|
22
22
|
*/
|
|
23
23
|
clause?: 'WHERE' | 'AND' | false;
|
|
24
24
|
};
|
|
25
|
+
/**
|
|
26
|
+
* Emits a statement, or a fragment of one, into the context it is handed. What a caller passes when
|
|
27
|
+
* it knows *what* to build but not *where*: the statement is assembled into whichever context the
|
|
28
|
+
* receiver opens, so the two ends cannot disagree about which one it went into.
|
|
29
|
+
*/
|
|
30
|
+
export type QueryBuildFn = (ctx: QueryContext) => void;
|
|
25
31
|
export interface QueryContext {
|
|
26
32
|
append(sql: string): this;
|
|
27
33
|
addValue(value: unknown): this;
|
|
@@ -113,8 +119,9 @@ export interface QueryDialect {
|
|
|
113
119
|
* @param entity the target entity
|
|
114
120
|
* @param q the criteria options
|
|
115
121
|
* @param opts the query options
|
|
122
|
+
* @param totalAlias when given, carry the unpaged match count on every row under this alias
|
|
116
123
|
*/
|
|
117
|
-
find<E>(ctx: QueryContext, entity: Type<E>, q: Query<E>, opts?: QueryOptions): void;
|
|
124
|
+
find<E>(ctx: QueryContext, entity: Type<E>, q: Query<E>, opts?: QueryOptions, totalAlias?: string): void;
|
|
118
125
|
/**
|
|
119
126
|
* counts the number of records matching the given search parameters.
|
|
120
127
|
* @param ctx the query context
|
|
@@ -122,7 +129,7 @@ export interface QueryDialect {
|
|
|
122
129
|
* @param q the criteria options
|
|
123
130
|
* @param opts the query options
|
|
124
131
|
*/
|
|
125
|
-
count<E>(ctx: QueryContext, entity: Type<E>, q:
|
|
132
|
+
count<E>(ctx: QueryContext, entity: Type<E>, q: QueryFilter<E>, opts?: QueryOptions): void;
|
|
126
133
|
/**
|
|
127
134
|
* insert records.
|
|
128
135
|
* @param ctx the query context
|
package/dist/type/index.d.ts
CHANGED
package/dist/type/index.js
CHANGED
package/dist/type/querier.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { SqlDialectName } from './dialect.js';
|
|
|
4
4
|
import type { FieldKey, HookEvent, RelationKey } from './entity.js';
|
|
5
5
|
import type { LoggingOptions } from './logger.js';
|
|
6
6
|
import type { NamingStrategy } from './namingStrategy.js';
|
|
7
|
-
import type { QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryProjected, QuerySearch, QueryUpdateResult } from './query.js';
|
|
7
|
+
import type { QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryStreamProjected, QueryUpdateResult } from './query.js';
|
|
8
8
|
import type { UniversalQuerier } from './universalQuerier.js';
|
|
9
9
|
import type { Type } from './utility.js';
|
|
10
10
|
/**
|
|
@@ -35,39 +35,46 @@ export interface Querier extends UniversalQuerier {
|
|
|
35
35
|
/**
|
|
36
36
|
* Find one record. Supports both entity-as-argument and entity-as-field patterns.
|
|
37
37
|
*/
|
|
38
|
-
findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(q: QueryOneProjected<E, S, V, X, P> & {
|
|
38
|
+
findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(q: QueryOneProjected<E, S, V, X, P, C> & {
|
|
39
39
|
$entity: Type<E>;
|
|
40
|
-
}, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P> | undefined>;
|
|
41
|
-
findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(entity: Type<E>, q: QueryOneProjected<E, S, V, X, P>, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P> | undefined>;
|
|
40
|
+
}, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P, C> | undefined>;
|
|
41
|
+
findOne<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryOneProjected<E, S, V, X, P, C>, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P, C> | undefined>;
|
|
42
42
|
/**
|
|
43
43
|
* Find many records. Supports both entity-as-argument and entity-as-field patterns.
|
|
44
44
|
*/
|
|
45
|
-
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(q: QueryProjected<E, S, V, X, P> & {
|
|
45
|
+
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(q: QueryProjected<E, S, V, X, P, C> & {
|
|
46
46
|
$entity: Type<E>;
|
|
47
|
-
}, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P>[]>;
|
|
48
|
-
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P>, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P>[]>;
|
|
47
|
+
}, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P, C>[]>;
|
|
48
|
+
findMany<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: QueryOptions): Promise<QueryFindResult<E, S, V, X, P, C>[]>;
|
|
49
49
|
/**
|
|
50
50
|
* Stream records as an async iterable. Supports both patterns.
|
|
51
51
|
* Does not fill relations or fire lifecycle hooks.
|
|
52
52
|
*/
|
|
53
|
-
findManyStream<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(q:
|
|
53
|
+
findManyStream<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(q: QueryStreamProjected<E, S, V, X, P> & {
|
|
54
54
|
$entity: Type<E>;
|
|
55
55
|
}, opts?: QueryOptions): AsyncIterable<QueryFindResult<E, S, V, X, P>>;
|
|
56
|
-
findManyStream<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(entity: Type<E>, q:
|
|
56
|
+
findManyStream<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(entity: Type<E>, q: QueryStreamProjected<E, S, V, X, P>, opts?: QueryOptions): AsyncIterable<QueryFindResult<E, S, V, X, P>>;
|
|
57
57
|
/**
|
|
58
58
|
* Find many records and count. Supports both patterns.
|
|
59
59
|
*/
|
|
60
|
-
findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(q: QueryProjected<E, S, V, X, P> & {
|
|
60
|
+
findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(q: QueryProjected<E, S, V, X, P, C> & {
|
|
61
61
|
$entity: Type<E>;
|
|
62
|
-
}, opts?: QueryOptions): Promise<[QueryFindResult<E, S, V, X, P>[], number]>;
|
|
63
|
-
findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P>, opts?: QueryOptions): Promise<[QueryFindResult<E, S, V, X, P>[], number]>;
|
|
62
|
+
}, opts?: QueryOptions): Promise<[QueryFindResult<E, S, V, X, P, C>[], number]>;
|
|
63
|
+
findManyAndCount<E extends object, const S extends FieldKey<E> = never, const V = true, const X extends FieldKey<E> = never, const P extends RelationKey<E> = never, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: QueryOptions): Promise<[QueryFindResult<E, S, V, X, P, C>[], number]>;
|
|
64
64
|
/**
|
|
65
65
|
* Count records. Supports both patterns.
|
|
66
66
|
*/
|
|
67
|
-
count<E extends object>(q:
|
|
67
|
+
count<E extends object>(q: QueryPage<E> & {
|
|
68
68
|
$entity: Type<E>;
|
|
69
69
|
}, opts?: QueryOptions): Promise<number>;
|
|
70
|
-
count<E extends object>(entity: Type<E>, q?:
|
|
70
|
+
count<E extends object>(entity: Type<E>, q?: QueryPage<E>, opts?: QueryOptions): Promise<number>;
|
|
71
|
+
/**
|
|
72
|
+
* Whether anything matches. Supports both patterns.
|
|
73
|
+
*/
|
|
74
|
+
exists<E extends object>(q: QueryFilter<E> & {
|
|
75
|
+
$entity: Type<E>;
|
|
76
|
+
}, opts?: QueryOptions): Promise<boolean>;
|
|
77
|
+
exists<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: QueryOptions): Promise<boolean>;
|
|
71
78
|
/**
|
|
72
79
|
* Delete many records (soft-deletes when the entity has a soft-delete field, else removes them).
|
|
73
80
|
* Supports both entity-as-argument and entity-as-field patterns.
|
package/dist/type/query.d.ts
CHANGED
|
@@ -59,6 +59,20 @@ export type QueryExclude<E> = QuerySelect<E>;
|
|
|
59
59
|
export type QueryPopulate<E> = {
|
|
60
60
|
[K in RelationKey<E>]?: BooleanLike | QueryPopulateRelationOptions<E[K]>;
|
|
61
61
|
};
|
|
62
|
+
/**
|
|
63
|
+
* How many rows each named relation holds per parent, `true` for all of them or a filter to narrow
|
|
64
|
+
* which ones count. One statement per relation named here, batched over every parent at once, so it
|
|
65
|
+
* stays flat however many rows the read returned. Comes back under `_count`, which keeps it clear of
|
|
66
|
+
* a relation of the same name that `$populate` filled with rows.
|
|
67
|
+
*/
|
|
68
|
+
/**
|
|
69
|
+
* The key a read carries its relation tallies under. One spelling for the type and the runtime that
|
|
70
|
+
* fills it: they sit in different modules, so a drift would type-check and answer `undefined`.
|
|
71
|
+
*/
|
|
72
|
+
export declare const COUNT_RESULT_KEY = "_count";
|
|
73
|
+
export type QueryCount<E> = {
|
|
74
|
+
[K in ToManyRelationKey<E>]?: BooleanLike | QueryFilter<RelationTarget<E[K]>>;
|
|
75
|
+
};
|
|
62
76
|
/**
|
|
63
77
|
* query conflict paths - subset of field keys used to detect upsert conflicts.
|
|
64
78
|
*/
|
|
@@ -139,8 +153,15 @@ type ToManyRelationKey<E> = Exclude<RelationKey<E>, ToOneRelationKey<E>>;
|
|
|
139
153
|
* against an intersection is repeated per constituent, which made this the single most expensive
|
|
140
154
|
* type in the package to check.
|
|
141
155
|
*/
|
|
156
|
+
/**
|
|
157
|
+
* Ordering parents by how many rows a to-many relation holds - "the ten users with the most posts".
|
|
158
|
+
* The tally is computed per parent as a correlated count, never by loading the rows.
|
|
159
|
+
*/
|
|
160
|
+
export type QuerySortByCount = {
|
|
161
|
+
$count: QuerySortDirection;
|
|
162
|
+
};
|
|
142
163
|
export type QuerySortMap<E, Vector extends boolean = true> = {
|
|
143
|
-
[K in FieldKey<E> | JsonFieldPaths<E> |
|
|
164
|
+
[K in FieldKey<E> | JsonFieldPaths<E> | RelationKey<E>]?: K extends RelationKey<E> ? IsMany<E[K]> extends true ? QuerySortByCount : QuerySortMap<RelationTarget<E[K]>, false> : K extends FieldKey<E> ? Vector extends true ? NonNullable<E[K]> extends readonly number[] ? QuerySortValue : QuerySortDirection : QuerySortDirection : QuerySortDirection;
|
|
144
165
|
};
|
|
145
166
|
/**
|
|
146
167
|
* pager options.
|
|
@@ -156,8 +177,7 @@ export type QueryPager = {
|
|
|
156
177
|
$limit?: number;
|
|
157
178
|
};
|
|
158
179
|
/**
|
|
159
|
-
* Which rows a statement addresses.
|
|
160
|
-
* can answer, so an ordering or a page on it is a clause it could only drop or choke on.
|
|
180
|
+
* Which rows a statement addresses.
|
|
161
181
|
*/
|
|
162
182
|
export type QueryFilter<E> = {
|
|
163
183
|
/**
|
|
@@ -166,18 +186,22 @@ export type QueryFilter<E> = {
|
|
|
166
186
|
$where?: QueryWhere<E>;
|
|
167
187
|
};
|
|
168
188
|
/**
|
|
169
|
-
* A filter plus the
|
|
170
|
-
*
|
|
189
|
+
* A filter plus the page `count` takes. No `$sort`: ordering picks *which* rows a page holds, never
|
|
190
|
+
* how many, so a count that accepted one would promise an influence it cannot have.
|
|
191
|
+
*/
|
|
192
|
+
export type QueryPage<E> = QueryFilter<E> & QueryPager;
|
|
193
|
+
/**
|
|
194
|
+
* A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the
|
|
195
|
+
* rows they address with a SELECT first, so the page is portable rather than MySQL-only, and a
|
|
171
196
|
* vector `$sort` is as valid here as on a read: it ranks the settle query's rows, which has the
|
|
172
|
-
* projection list to hold the distance. `$lock`
|
|
173
|
-
* {@link Query} instead.
|
|
197
|
+
* projection list to hold the distance. `$lock` stays off these, declared on {@link Query} instead.
|
|
174
198
|
*/
|
|
175
|
-
export type QuerySearch<E> =
|
|
199
|
+
export type QuerySearch<E> = QueryPage<E> & {
|
|
176
200
|
/**
|
|
177
201
|
* sorting options.
|
|
178
202
|
*/
|
|
179
203
|
$sort?: QuerySortMap<E>;
|
|
180
|
-
}
|
|
204
|
+
};
|
|
181
205
|
/**
|
|
182
206
|
* query options.
|
|
183
207
|
*/
|
|
@@ -192,6 +216,10 @@ export type Query<E> = {
|
|
|
192
216
|
* relation population options.
|
|
193
217
|
*/
|
|
194
218
|
$populate?: QueryPopulate<E>;
|
|
219
|
+
/**
|
|
220
|
+
* how many rows each named relation holds, under `_count` on every row. See {@link QueryCount}.
|
|
221
|
+
*/
|
|
222
|
+
$count?: QueryCount<E>;
|
|
195
223
|
/**
|
|
196
224
|
* field exclusion - `{ name: true }` blacklists fields. Mutually exclusive with positive `$select`.
|
|
197
225
|
* Keys a relation is assembled from (a joined row's primary key, a to-many's foreign key) are kept
|
|
@@ -240,6 +268,12 @@ export type Query<E> = {
|
|
|
240
268
|
* query accepts, so leaving it out is what excludes it from both.
|
|
241
269
|
*/
|
|
242
270
|
export declare const QUERY_OBJECT_CLAUSES: readonly ["$select", "$populate", "$exclude", "$where", "$sort"];
|
|
271
|
+
/**
|
|
272
|
+
* Object clauses only the statement itself takes, never a relation's own query - the mirror of
|
|
273
|
+
* `$lock`, which neither takes. Counting a relation is batched over the rows a read returned, and a
|
|
274
|
+
* populated relation's rows are assembled after that, so there is nothing for a nested one to count.
|
|
275
|
+
*/
|
|
276
|
+
export declare const QUERY_ROOT_OBJECT_CLAUSES: readonly ["$count"];
|
|
243
277
|
export declare const QUERY_NUMBER_CLAUSES: readonly ["$skip", "$limit"];
|
|
244
278
|
export declare const QUERY_BOOLEAN_CLAUSES: readonly ["$distinct"];
|
|
245
279
|
/**
|
|
@@ -263,7 +297,18 @@ export type QueryUnique<E> = Pick<QueryOne<E>, '$select' | '$exclude' | '$popula
|
|
|
263
297
|
* populated relation's own query - stays the concrete {@link Query} it is today.
|
|
264
298
|
* @internal
|
|
265
299
|
*/
|
|
266
|
-
type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = {
|
|
300
|
+
type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>> = QueryStreamProjection<E, S, V, X, P> & {
|
|
301
|
+
$count?: {
|
|
302
|
+
[K in C & keyof QueryCount<E>]?: QueryCount<E>[K];
|
|
303
|
+
};
|
|
304
|
+
};
|
|
305
|
+
/**
|
|
306
|
+
* {@link QueryProjection} without `$count`, which a stream cannot honor. Split out rather than
|
|
307
|
+
* subtracted afterwards: an optional key is a *known* key even when its value maps over `never`, so
|
|
308
|
+
* a statement that must not take the clause has to be built without it in the first place.
|
|
309
|
+
* @internal
|
|
310
|
+
*/
|
|
311
|
+
type QueryStreamProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = {
|
|
267
312
|
$select?: {
|
|
268
313
|
[K in S]?: V;
|
|
269
314
|
} | readonly QueryRaw[];
|
|
@@ -274,14 +319,19 @@ type QueryProjection<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P exten
|
|
|
274
319
|
[K in P]?: QueryPopulate<E>[K];
|
|
275
320
|
};
|
|
276
321
|
};
|
|
322
|
+
/**
|
|
323
|
+
* A {@link QueryProjected} a stream can honor: no `$count`, which is batched over a result set a
|
|
324
|
+
* stream never holds all of.
|
|
325
|
+
*/
|
|
326
|
+
export type QueryStreamProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>> = Except<Query<E>, '$count'> & QueryStreamProjection<E, S, V, X, P>;
|
|
277
327
|
/**
|
|
278
328
|
* A {@link Query} whose projection is captured, so {@link QueryFindResult} can shape the row.
|
|
279
329
|
*/
|
|
280
|
-
export type QueryProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E
|
|
330
|
+
export type QueryProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never> = Query<E> & QueryProjection<E, S, V, X, P, C>;
|
|
281
331
|
/**
|
|
282
332
|
* A {@link QueryOne} whose projection is captured, so {@link QueryFindResult} can shape the row.
|
|
283
333
|
*/
|
|
284
|
-
export type QueryOneProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E
|
|
334
|
+
export type QueryOneProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E> = never> = QueryOne<E> & QueryProjection<E, S, V, X, P, C>;
|
|
285
335
|
/**
|
|
286
336
|
* The keys a query comes back with, mirroring what the runtime projects: the fields a positive
|
|
287
337
|
* `$select` names, or every field minus what a falsy `$select` entry or a truthy `$exclude` entry
|
|
@@ -289,7 +339,7 @@ export type QueryOneProjected<E, S extends FieldKey<E>, V, X extends FieldKey<E>
|
|
|
289
339
|
* why `$exclude` is only read on the branch where there is none.
|
|
290
340
|
* @internal
|
|
291
341
|
*/
|
|
292
|
-
type ProjectedKeys<E, S, V, X, P> = ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S) | P | ([P] extends [never] ? never : NamedIdKey<E>);
|
|
342
|
+
type ProjectedKeys<E, S, V, X, P, C> = ([V] extends [false | 0] ? Exclude<FieldKey<E>, S> : [S] extends [never] ? Exclude<FieldKey<E>, X> : S) | P | ([P | C] extends [never] ? never : NamedIdKey<E>);
|
|
293
343
|
/**
|
|
294
344
|
* The id key when it can be named, and nothing when it cannot: {@link IdKey} widens to *every* field
|
|
295
345
|
* for an entity whose id is neither branded nor called `id`/`_id`/`uuid`, and adding that back would
|
|
@@ -315,11 +365,22 @@ type IsUniform<V> = [V] extends [true | 1] ? true : [V] extends [false | 0] ? tr
|
|
|
315
365
|
* keep their declared type: narrowing them means capturing their queries as maps, which costs those
|
|
316
366
|
* queries their own checks.
|
|
317
367
|
*/
|
|
318
|
-
export type QueryFindResult<E, S extends FieldKey<E> = never, V = true, X extends FieldKey<E> = never, P extends RelationKey<E> = never
|
|
319
|
-
|
|
368
|
+
export type QueryFindResult<E, S extends FieldKey<E> = never, V = true, X extends FieldKey<E> = never, P extends RelationKey<E> = never, C extends RelationKey<E> = never> = QueryProjectedRow<E, S, V, X, P, C> & CountedRelations<C>;
|
|
369
|
+
/**
|
|
370
|
+
* The `_count` a query asked for, or an inert intersection member when it asked for none - so a read
|
|
371
|
+
* without `$count` keeps exactly the row type it had.
|
|
372
|
+
*/
|
|
373
|
+
type CountedRelations<C extends PropertyKey> = [C] extends [never] ? unknown : {
|
|
374
|
+
[K in typeof COUNT_RESULT_KEY]: {
|
|
375
|
+
[R in C]: number;
|
|
376
|
+
};
|
|
377
|
+
};
|
|
378
|
+
/** @internal */
|
|
379
|
+
type QueryProjectedRow<E, S extends FieldKey<E>, V, X extends FieldKey<E>, P extends RelationKey<E>, C extends RelationKey<E>> = [S | X] extends [never] ? E : IsUniform<V> extends true ? [PopulatedToMany<E, P>] extends [never] ? {
|
|
380
|
+
[K in keyof E as K extends ProjectedKeys<E, S, V, X, P, C> ? K : never]: E[K];
|
|
320
381
|
} : // A populated to-many is always a list, empty where the parent has no children, so it maps
|
|
321
382
|
{
|
|
322
|
-
[K in keyof E as K extends Exclude<ProjectedKeys<E, S, V, X, P>, PopulatedToMany<E, P>> ? K : never]: E[K];
|
|
383
|
+
[K in keyof E as K extends Exclude<ProjectedKeys<E, S, V, X, P, C>, PopulatedToMany<E, P>> ? K : never]: E[K];
|
|
323
384
|
} & {
|
|
324
385
|
[K in PopulatedToMany<E, P>]-?: NonNullable<E[K]>;
|
|
325
386
|
} : E;
|