turbine-orm 0.30.0 → 0.32.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.
- package/dist/cjs/client.js +14 -3
- package/dist/cjs/mssql.js +24 -3
- package/dist/cjs/powdb.js +197 -50
- package/dist/cjs/powql.js +45 -2
- package/dist/cjs/query/batched-loader.js +34 -0
- package/dist/cjs/query/builder.js +758 -139
- package/dist/cjs/query/filters.js +77 -2
- package/dist/client.d.ts +13 -0
- package/dist/client.js +14 -3
- package/dist/index.d.ts +1 -1
- package/dist/mssql.js +24 -3
- package/dist/powdb.d.ts +33 -0
- package/dist/powdb.js +197 -50
- package/dist/powql.js +45 -2
- package/dist/query/batched-loader.d.ts +12 -0
- package/dist/query/batched-loader.js +33 -0
- package/dist/query/builder.d.ts +171 -5
- package/dist/query/builder.js +760 -141
- package/dist/query/filters.d.ts +40 -1
- package/dist/query/filters.js +73 -1
- package/dist/query/index.d.ts +1 -1
- package/dist/query/types.d.ts +213 -22
- package/package.json +1 -1
package/dist/query/filters.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* compiler. Kept out of builder.ts so the class file stays about SQL assembly
|
|
6
6
|
* and execution rather than filter-shape bookkeeping.
|
|
7
7
|
*/
|
|
8
|
-
import type { ArrayFilter, JsonFilter, OrderBySpec, OrderDirection, TextSearchFilter, VectorFilter, VectorOrderBy, WhereOperator } from './types.js';
|
|
8
|
+
import type { ArrayFilter, ColumnRef, JsonFilter, JsonPathOrderBy, OrderBySpec, OrderDirection, RelationPickOrderBy, TextSearchFilter, VectorFilter, VectorOrderBy, WhereOperator } from './types.js';
|
|
9
9
|
/** Check if a value is a where operator object (has at least one known operator key) */
|
|
10
10
|
export declare function isWhereOperator(value: unknown): value is WhereOperator;
|
|
11
11
|
/**
|
|
@@ -15,11 +15,30 @@ export declare function isWhereOperator(value: unknown): value is WhereOperator;
|
|
|
15
15
|
* bind values and return false, as do arrays and Dates.
|
|
16
16
|
*/
|
|
17
17
|
export declare function isUnmatchedPlainObject(value: unknown): boolean;
|
|
18
|
+
/**
|
|
19
|
+
* Operator keys that accept a {@link ColumnRef} (`{ col: 'otherField' }`)
|
|
20
|
+
* value for column-to-column comparison. `in`/`notIn` and the LIKE operators
|
|
21
|
+
* take values only.
|
|
22
|
+
*/
|
|
23
|
+
export declare const COLUMN_REF_OPERATORS: Set<string>;
|
|
24
|
+
/**
|
|
25
|
+
* Check if an operator value is a column reference: a plain object whose ONLY
|
|
26
|
+
* key is `col` with a string value. Anything else (extra keys, non-string
|
|
27
|
+
* `col`) is treated as a plain value so JSON payloads that merely contain a
|
|
28
|
+
* `col` property keep their equality meaning.
|
|
29
|
+
*/
|
|
30
|
+
export declare function isColumnRef(value: unknown): value is ColumnRef;
|
|
18
31
|
/**
|
|
19
32
|
* Fingerprint the SHAPE of a where-operator object. Null-valued `equals` /
|
|
20
33
|
* `not` compile to parameterless `IS NULL` / `IS NOT NULL` (different SQL, no
|
|
21
34
|
* param pushed), so null-ness is part of the shape — without it a cache entry
|
|
22
35
|
* warmed by `{ not: 5 }` would serve `{ not: null }` with a desynced param list.
|
|
36
|
+
*
|
|
37
|
+
* Column references ({@link ColumnRef}) compile the referenced column into the
|
|
38
|
+
* SQL TEXT (no param bound), so the referenced field name is part of the shape
|
|
39
|
+
*: `{ equals: { col: 'a' } }` and `{ equals: { col: 'b' } }` must never share
|
|
40
|
+
* a cache entry. The name is JSON-encoded so exotic field names cannot collide
|
|
41
|
+
* with other fingerprint tokens.
|
|
23
42
|
*/
|
|
24
43
|
export declare function fingerprintOperatorShape(value: WhereOperator): string;
|
|
25
44
|
/**
|
|
@@ -127,6 +146,26 @@ export declare function isVectorFilter(value: unknown): value is VectorFilter;
|
|
|
127
146
|
export declare function isVectorOrderBy(value: unknown): value is VectorOrderBy;
|
|
128
147
|
/** Check if an orderBy value is an explicit `{ sort, nulls? }` spec. */
|
|
129
148
|
export declare function isOrderBySpec(value: unknown): value is OrderBySpec;
|
|
149
|
+
/**
|
|
150
|
+
* Check if an orderBy value is a JSON-path ordering: `{ path: [...] }` with an
|
|
151
|
+
* ARRAY path. The array requirement disambiguates from relation orderBy values
|
|
152
|
+
* (whose entries are directions/specs keyed by target column: a target column
|
|
153
|
+
* literally named `path` maps to a string direction, never an array), and the
|
|
154
|
+
* `distance`/`sort` exclusions keep vector and spec shapes out.
|
|
155
|
+
*/
|
|
156
|
+
export declare function isJsonPathOrderBy(value: unknown): value is JsonPathOrderBy;
|
|
157
|
+
/**
|
|
158
|
+
* Check if an orderBy value is a pick-row relation ordering:
|
|
159
|
+
* `{ pick: { orderBy, ... }, by, direction?, nulls? }`. The full shape is
|
|
160
|
+
* required — `pick` must be an object carrying `orderBy`, `by` must be
|
|
161
|
+
* present, and no keys outside `{ pick, by, direction, nulls }` — so a to-one
|
|
162
|
+
* relation whose target has real columns literally named `pick` and `by`
|
|
163
|
+
* (whose values are direction strings or `{ sort, nulls }` specs, never an
|
|
164
|
+
* object with `orderBy`) still falls through to column ordering. `distance`
|
|
165
|
+
* (vector), `sort` (OrderBySpec), and a top-level array `path` (JSON-path
|
|
166
|
+
* ordering) are excluded up front.
|
|
167
|
+
*/
|
|
168
|
+
export declare function isRelationPickOrderBy(value: unknown): value is RelationPickOrderBy;
|
|
130
169
|
/**
|
|
131
170
|
* Normalize an orderBy value into `{ direction, nulls }`. Accepts a plain
|
|
132
171
|
* direction string or an {@link OrderBySpec}. Used by every ORDER BY compile
|
package/dist/query/filters.js
CHANGED
|
@@ -36,17 +36,48 @@ export function isUnmatchedPlainObject(value) {
|
|
|
36
36
|
const proto = Object.getPrototypeOf(value);
|
|
37
37
|
return proto === Object.prototype || proto === null;
|
|
38
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Operator keys that accept a {@link ColumnRef} (`{ col: 'otherField' }`)
|
|
41
|
+
* value for column-to-column comparison. `in`/`notIn` and the LIKE operators
|
|
42
|
+
* take values only.
|
|
43
|
+
*/
|
|
44
|
+
export const COLUMN_REF_OPERATORS = new Set(['equals', 'not', 'gt', 'gte', 'lt', 'lte']);
|
|
45
|
+
/**
|
|
46
|
+
* Check if an operator value is a column reference: a plain object whose ONLY
|
|
47
|
+
* key is `col` with a string value. Anything else (extra keys, non-string
|
|
48
|
+
* `col`) is treated as a plain value so JSON payloads that merely contain a
|
|
49
|
+
* `col` property keep their equality meaning.
|
|
50
|
+
*/
|
|
51
|
+
export function isColumnRef(value) {
|
|
52
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value) || value instanceof Date)
|
|
53
|
+
return false;
|
|
54
|
+
const keys = Object.keys(value);
|
|
55
|
+
return keys.length === 1 && keys[0] === 'col' && typeof value.col === 'string';
|
|
56
|
+
}
|
|
39
57
|
/**
|
|
40
58
|
* Fingerprint the SHAPE of a where-operator object. Null-valued `equals` /
|
|
41
59
|
* `not` compile to parameterless `IS NULL` / `IS NOT NULL` (different SQL, no
|
|
42
60
|
* param pushed), so null-ness is part of the shape — without it a cache entry
|
|
43
61
|
* warmed by `{ not: 5 }` would serve `{ not: null }` with a desynced param list.
|
|
62
|
+
*
|
|
63
|
+
* Column references ({@link ColumnRef}) compile the referenced column into the
|
|
64
|
+
* SQL TEXT (no param bound), so the referenced field name is part of the shape
|
|
65
|
+
*: `{ equals: { col: 'a' } }` and `{ equals: { col: 'b' } }` must never share
|
|
66
|
+
* a cache entry. The name is JSON-encoded so exotic field names cannot collide
|
|
67
|
+
* with other fingerprint tokens.
|
|
44
68
|
*/
|
|
45
69
|
export function fingerprintOperatorShape(value) {
|
|
46
70
|
const obj = value;
|
|
47
71
|
const opKeys = Object.keys(obj)
|
|
48
72
|
.filter((k) => k !== 'mode')
|
|
49
|
-
.map((k) =>
|
|
73
|
+
.map((k) => {
|
|
74
|
+
const v = obj[k];
|
|
75
|
+
if ((k === 'equals' || k === 'not') && v === null)
|
|
76
|
+
return `${k}:null`;
|
|
77
|
+
if (COLUMN_REF_OPERATORS.has(k) && isColumnRef(v))
|
|
78
|
+
return `${k}:col(${JSON.stringify(v.col)})`;
|
|
79
|
+
return k;
|
|
80
|
+
})
|
|
50
81
|
.sort();
|
|
51
82
|
const modeStr = value.mode === 'insensitive' ? ':i' : '';
|
|
52
83
|
return `op(${opKeys.join(',')}${modeStr})`;
|
|
@@ -249,6 +280,47 @@ export function isVectorOrderBy(value) {
|
|
|
249
280
|
export function isOrderBySpec(value) {
|
|
250
281
|
return typeof value === 'object' && value !== null && !Array.isArray(value) && 'sort' in value;
|
|
251
282
|
}
|
|
283
|
+
/**
|
|
284
|
+
* Check if an orderBy value is a JSON-path ordering: `{ path: [...] }` with an
|
|
285
|
+
* ARRAY path. The array requirement disambiguates from relation orderBy values
|
|
286
|
+
* (whose entries are directions/specs keyed by target column: a target column
|
|
287
|
+
* literally named `path` maps to a string direction, never an array), and the
|
|
288
|
+
* `distance`/`sort` exclusions keep vector and spec shapes out.
|
|
289
|
+
*/
|
|
290
|
+
export function isJsonPathOrderBy(value) {
|
|
291
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
292
|
+
return false;
|
|
293
|
+
if ('distance' in value || 'sort' in value)
|
|
294
|
+
return false;
|
|
295
|
+
return Array.isArray(value.path);
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Check if an orderBy value is a pick-row relation ordering:
|
|
299
|
+
* `{ pick: { orderBy, ... }, by, direction?, nulls? }`. The full shape is
|
|
300
|
+
* required — `pick` must be an object carrying `orderBy`, `by` must be
|
|
301
|
+
* present, and no keys outside `{ pick, by, direction, nulls }` — so a to-one
|
|
302
|
+
* relation whose target has real columns literally named `pick` and `by`
|
|
303
|
+
* (whose values are direction strings or `{ sort, nulls }` specs, never an
|
|
304
|
+
* object with `orderBy`) still falls through to column ordering. `distance`
|
|
305
|
+
* (vector), `sort` (OrderBySpec), and a top-level array `path` (JSON-path
|
|
306
|
+
* ordering) are excluded up front.
|
|
307
|
+
*/
|
|
308
|
+
export function isRelationPickOrderBy(value) {
|
|
309
|
+
if (typeof value !== 'object' || value === null || Array.isArray(value))
|
|
310
|
+
return false;
|
|
311
|
+
if ('distance' in value || 'sort' in value || Array.isArray(value.path))
|
|
312
|
+
return false;
|
|
313
|
+
const v = value;
|
|
314
|
+
if (!('pick' in v) || !('by' in v))
|
|
315
|
+
return false;
|
|
316
|
+
if (typeof v.pick !== 'object' || v.pick === null || Array.isArray(v.pick) || !('orderBy' in v.pick))
|
|
317
|
+
return false;
|
|
318
|
+
for (const key of Object.keys(v)) {
|
|
319
|
+
if (key !== 'pick' && key !== 'by' && key !== 'direction' && key !== 'nulls')
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
252
324
|
/**
|
|
253
325
|
* Normalize an orderBy value into `{ direction, nulls }`. Accepts a plain
|
|
254
326
|
* direction string or an {@link OrderBySpec}. Used by every ORDER BY compile
|
package/dist/query/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* `import { … } from './query/index.js'` is a drop-in replacement for the
|
|
6
6
|
* former monolithic `import { … } from './query.js'`.
|
|
7
7
|
*/
|
|
8
|
-
export type { AggregateArgs, AggregateResult, ArrayFilter, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByArgs, HavingClause, JsonFilter, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithResult, } from './types.js';
|
|
8
|
+
export type { AggregateArgs, AggregateResult, ArrayFilter, ColumnRef, ConnectOrCreateOp, CountArgs, CreateArgs, CreateDataInput, CreateManyArgs, DeleteArgs, DeleteManyArgs, FieldResult, FindManyArgs, FindManyStreamArgs, FindUniqueArgs, GlobalFilters, GroupByAggregateSpec, GroupByArgs, GroupByDistinctOn, HavingClause, JsonFilter, JsonPathAggregateTarget, JsonPathGroupKey, JsonPathOrderBy, NestedCreateOp, NestedUpdateOp, NestedUpdateOpItem, NestedUpsertOpItem, OmitResult, OrderByClause, OrderDirection, QueryResult, RelationDescriptor, RelationFilter, RelationLoadStrategy, RelationPickBy, RelationPickOrderBy, SelectResult, SkipGlobalFilters, TextSearchFilter, TypedWithClause, UpdateArgs, UpdateDataInput, UpdateInput, UpdateManyArgs, UpdateOperatorInput, UpsertArgs, VectorDistanceFilter, VectorFilter, VectorMetric, VectorOrderBy, VectorOrderByDistance, WhereClause, WhereOperator, WhereValue, WithClause, WithOptions, WithResult, } from './types.js';
|
|
9
9
|
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, InsertStatementInput, UpsertStatementInput, } from '../dialect.js';
|
|
10
10
|
export { postgresDialect } from '../dialect.js';
|
|
11
11
|
export type { SqlCacheEntry } from './utils.js';
|
package/dist/query/types.d.ts
CHANGED
|
@@ -18,20 +18,48 @@ export type OrderDirection = 'asc' | 'desc';
|
|
|
18
18
|
* Precedence: per-query arg > client `relationLoadStrategy` config > `'join'`.
|
|
19
19
|
*/
|
|
20
20
|
export type RelationLoadStrategy = 'join' | 'batched';
|
|
21
|
+
/**
|
|
22
|
+
* Reference to ANOTHER COLUMN of the same table inside a where operator,
|
|
23
|
+
* enabling column-to-column comparison:
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* where: { currentVersionId: { equals: { col: 'publishedVersionId' } } }
|
|
27
|
+
* // → WHERE "current_version_id" = "published_version_id"
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* Accepted by `equals`, `not`, `gt`, `gte`, `lt`, and `lte`. The referenced
|
|
31
|
+
* field resolves through the table's columnMap (camelCase accepted, same as a
|
|
32
|
+
* where key) and compiles to a quoted identifier: NO parameter is bound.
|
|
33
|
+
* An unknown referenced field throws {@link ValidationError} (E003).
|
|
34
|
+
*
|
|
35
|
+
* Notes:
|
|
36
|
+
* - `mode: 'insensitive'` cannot be combined with a column reference: it
|
|
37
|
+
* throws E003 (use `client.sql` for `lower(a) = lower(b)`).
|
|
38
|
+
* - On json/jsonb columns `equals` routes to the JSONB containment filter
|
|
39
|
+
* first, so `{ equals: { col } }` there is treated as a JSON value, not a
|
|
40
|
+
* column reference.
|
|
41
|
+
*
|
|
42
|
+
* `F` narrows the referenced name to the table's field names when the
|
|
43
|
+
* surrounding {@link WhereClause} knows the entity type.
|
|
44
|
+
*/
|
|
45
|
+
export interface ColumnRef<F extends string = string> {
|
|
46
|
+
col: F;
|
|
47
|
+
}
|
|
21
48
|
/** Operator object for advanced where filtering */
|
|
22
|
-
export interface WhereOperator<V = unknown> {
|
|
49
|
+
export interface WhereOperator<V = unknown, F extends string = string> {
|
|
23
50
|
/**
|
|
24
51
|
* Explicit equality: `{ equals: value }` → `column = $n`.
|
|
25
52
|
* `{ equals: null }` → `column IS NULL`.
|
|
53
|
+
* `{ equals: { col: 'otherField' } }` → `column = "other_field"` ({@link ColumnRef}).
|
|
26
54
|
* On json/jsonb columns `equals` routes to the JSONB containment filter
|
|
27
55
|
* ({@link JsonFilter}) instead.
|
|
28
56
|
*/
|
|
29
|
-
equals?: V | null;
|
|
30
|
-
gt?: V
|
|
31
|
-
gte?: V
|
|
32
|
-
lt?: V
|
|
33
|
-
lte?: V
|
|
34
|
-
not?: V | null;
|
|
57
|
+
equals?: V | ColumnRef<F> | null;
|
|
58
|
+
gt?: V | ColumnRef<F>;
|
|
59
|
+
gte?: V | ColumnRef<F>;
|
|
60
|
+
lt?: V | ColumnRef<F>;
|
|
61
|
+
lte?: V | ColumnRef<F>;
|
|
62
|
+
not?: V | ColumnRef<F> | null;
|
|
35
63
|
in?: V[];
|
|
36
64
|
notIn?: V[];
|
|
37
65
|
contains?: string;
|
|
@@ -50,7 +78,7 @@ export interface WhereOperator<V = unknown> {
|
|
|
50
78
|
* - A text search filter object ({ search, config? })
|
|
51
79
|
* - A vector distance filter object ({ distance: { to, metric, lt } }) for pgvector columns
|
|
52
80
|
*/
|
|
53
|
-
export type WhereValue<V = unknown> = (V extends Array<infer U> ? TypedRelationFilter<U> : V extends Date ? V : V extends object ? V | TypedToOneFilter<V> | WhereClause<V> : V) | WhereOperator<V> | JsonFilter | ArrayFilter | TextSearchFilter | VectorFilter | null;
|
|
81
|
+
export type WhereValue<V = unknown, F extends string = string> = (V extends Array<infer U> ? TypedRelationFilter<U> : V extends Date ? V : V extends object ? V | TypedToOneFilter<V> | WhereClause<V> : V) | WhereOperator<V, F> | JsonFilter | ArrayFilter | TextSearchFilter | VectorFilter | null;
|
|
54
82
|
/** Relation filter on a to-many relation property. */
|
|
55
83
|
export interface TypedRelationFilter<U> {
|
|
56
84
|
some?: WhereClause<U>;
|
|
@@ -71,7 +99,7 @@ export interface TypedToOneFilter<V> {
|
|
|
71
99
|
* Relation names can be used with some/every/none sub-filters.
|
|
72
100
|
*/
|
|
73
101
|
export type WhereClause<T> = {
|
|
74
|
-
[K in keyof T]?: WhereValue<T[K]
|
|
102
|
+
[K in keyof T]?: WhereValue<T[K], Extract<keyof T, string>>;
|
|
75
103
|
} & {
|
|
76
104
|
OR?: WhereClause<T>[];
|
|
77
105
|
AND?: WhereClause<T>[];
|
|
@@ -148,7 +176,7 @@ export type TypedWithClause<R extends object = {}> = [keyof R] extends [never] ?
|
|
|
148
176
|
export interface WithOptions<NestedR extends object = {}> {
|
|
149
177
|
with?: TypedWithClause<NestedR>;
|
|
150
178
|
where?: Record<string, unknown>;
|
|
151
|
-
orderBy?: Record<string, OrderDirection | OrderBySpec>;
|
|
179
|
+
orderBy?: Record<string, OrderDirection | OrderBySpec | JsonPathOrderBy | RelationOrderBy>;
|
|
152
180
|
limit?: number;
|
|
153
181
|
/** Only include these fields from the relation */
|
|
154
182
|
select?: Record<string, boolean>;
|
|
@@ -568,20 +596,87 @@ export type HavingClause<T> = {
|
|
|
568
596
|
} & {
|
|
569
597
|
[K in keyof T & string]?: HavingAggregateFilter;
|
|
570
598
|
};
|
|
599
|
+
/**
|
|
600
|
+
* A JSON-path group key in {@link GroupByArgs.by}: groups by the value
|
|
601
|
+
* extracted at `path` from a json/jsonb column. Emits
|
|
602
|
+
* `(col #>> $n::text[]) AS "alias"` in SELECT and the same expression in
|
|
603
|
+
* GROUP BY (the path is bound as one text[] param, never interpolated).
|
|
604
|
+
* Result rows key by `alias` (default: the last path segment; a collision
|
|
605
|
+
* with another result key throws {@link ValidationError} E003).
|
|
606
|
+
*/
|
|
607
|
+
export interface JsonPathGroupKey {
|
|
608
|
+
/** json/jsonb column (camelCase field name, columnMap-resolved). */
|
|
609
|
+
field: string;
|
|
610
|
+
/** JSON path into the column (each element a key or array index). Bound as one text[] param. */
|
|
611
|
+
path: (string | number)[];
|
|
612
|
+
/** Result key for the group value. Defaults to the last path segment. */
|
|
613
|
+
alias?: string;
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* A JSON-path aggregate target inside `_sum` / `_avg` / `_min` / `_max` of
|
|
617
|
+
* {@link GroupByArgs}: aggregates the value extracted at `path` from a
|
|
618
|
+
* json/jsonb column, e.g. `SUM((col #>> $n::text[])::numeric)`. The arg key
|
|
619
|
+
* is the result alias. `_sum`/`_avg` always cast numeric (a text sum is
|
|
620
|
+
* meaningless); `_min`/`_max` compare as text unless `type: 'numeric'`.
|
|
621
|
+
*/
|
|
622
|
+
export interface JsonPathAggregateTarget {
|
|
623
|
+
/** json/jsonb column (camelCase field name, columnMap-resolved). */
|
|
624
|
+
field: string;
|
|
625
|
+
/** JSON path into the column (each element a key or array index). Bound as one text[] param. */
|
|
626
|
+
path: (string | number)[];
|
|
627
|
+
/** Comparison/aggregation kind. `_sum`/`_avg` are always numeric; `_min`/`_max` default to text. */
|
|
628
|
+
type?: 'numeric' | 'text';
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* Per-aggregate spec map for `_sum` / `_avg` / `_min` / `_max` in
|
|
632
|
+
* {@link GroupByArgs}: `true` keeps the existing plain-column behavior (the
|
|
633
|
+
* key is a column field name); a {@link JsonPathAggregateTarget} aggregates a
|
|
634
|
+
* JSON path (the key doubles as the result alias).
|
|
635
|
+
*/
|
|
636
|
+
export type GroupByAggregateSpec<T> = Partial<Record<keyof T & string, boolean>> | Record<string, boolean | JsonPathAggregateTarget>;
|
|
637
|
+
/**
|
|
638
|
+
* DISTINCT ON row source for {@link GroupByArgs} (PostgreSQL only: other
|
|
639
|
+
* engines throw {@link UnsupportedFeatureError} E017): the groupBy runs over
|
|
640
|
+
* one representative row per `columns` combination instead of the raw table.
|
|
641
|
+
*
|
|
642
|
+
* ```sql
|
|
643
|
+
* FROM (
|
|
644
|
+
* SELECT DISTINCT ON ("instance_id") * FROM "versions"
|
|
645
|
+
* WHERE <args.where> ORDER BY "instance_id", "created_at" DESC
|
|
646
|
+
* ) AS "versions"
|
|
647
|
+
* ```
|
|
648
|
+
*
|
|
649
|
+
* `orderBy` is REQUIRED (it decides which row survives per combination:
|
|
650
|
+
* without it the picked row would be nondeterministic); the wrapper ORDER BY
|
|
651
|
+
* is `columns` first, then this orderBy. `args.where` applies INSIDE the
|
|
652
|
+
* wrapper (filter before picking).
|
|
653
|
+
*/
|
|
654
|
+
export interface GroupByDistinctOn<T> {
|
|
655
|
+
/** DISTINCT ON columns: one surviving row per combination. */
|
|
656
|
+
columns: (keyof T & string)[];
|
|
657
|
+
/** Which row survives per combination (required for determinism). */
|
|
658
|
+
orderBy: Record<string, OrderDirection | OrderBySpec | JsonPathOrderBy>;
|
|
659
|
+
}
|
|
571
660
|
export interface GroupByArgs<T> {
|
|
572
|
-
|
|
661
|
+
/** Group keys: plain column field names and/or JSON-path keys ({@link JsonPathGroupKey}). */
|
|
662
|
+
by: ((keyof T & string) | JsonPathGroupKey)[];
|
|
573
663
|
where?: WhereClause<T>;
|
|
664
|
+
/**
|
|
665
|
+
* PostgreSQL only: group over one representative row per column combination
|
|
666
|
+
* (`SELECT DISTINCT ON … ORDER BY …` row source). See {@link GroupByDistinctOn}.
|
|
667
|
+
*/
|
|
668
|
+
distinctOn?: GroupByDistinctOn<T>;
|
|
574
669
|
/** Include count of each group */
|
|
575
670
|
_count?: true;
|
|
576
|
-
/** Sum of numeric fields in each group */
|
|
577
|
-
_sum?:
|
|
578
|
-
/** Average of numeric fields in each group */
|
|
579
|
-
_avg?:
|
|
580
|
-
/** Minimum value of fields in each group */
|
|
581
|
-
_min?:
|
|
582
|
-
/** Maximum value of fields in each group */
|
|
583
|
-
_max?:
|
|
584
|
-
/** Filter whole groups by their aggregate values (SQL HAVING). */
|
|
671
|
+
/** Sum of numeric fields (or JSON paths: see {@link JsonPathAggregateTarget}) in each group */
|
|
672
|
+
_sum?: GroupByAggregateSpec<T>;
|
|
673
|
+
/** Average of numeric fields (or JSON paths) in each group */
|
|
674
|
+
_avg?: GroupByAggregateSpec<T>;
|
|
675
|
+
/** Minimum value of fields (or JSON paths) in each group */
|
|
676
|
+
_min?: GroupByAggregateSpec<T>;
|
|
677
|
+
/** Maximum value of fields (or JSON paths) in each group */
|
|
678
|
+
_max?: GroupByAggregateSpec<T>;
|
|
679
|
+
/** Filter whole groups by their aggregate values (SQL HAVING). JSON-path aggregates key by their alias. */
|
|
585
680
|
having?: HavingClause<T>;
|
|
586
681
|
/** Order groups (supports {@link OrderBySpec} for NULLS placement). */
|
|
587
682
|
orderBy?: Record<string, OrderDirection | OrderBySpec>;
|
|
@@ -741,6 +836,32 @@ export interface OrderBySpec {
|
|
|
741
836
|
sort: OrderDirection;
|
|
742
837
|
nulls?: 'first' | 'last';
|
|
743
838
|
}
|
|
839
|
+
/**
|
|
840
|
+
* Ordering by a JSON path on a json/jsonb column of the SAME table:
|
|
841
|
+
*
|
|
842
|
+
* ```ts
|
|
843
|
+
* orderBy: { data: { path: ['weight'], direction: 'asc', type: 'numeric' } }
|
|
844
|
+
* // → ORDER BY ("data" #>> $n::text[])::numeric ASC
|
|
845
|
+
* ```
|
|
846
|
+
*
|
|
847
|
+
* The path is bound as a single text[] parameter (never interpolated).
|
|
848
|
+
* Comparison rule: values extracted from the path compare as TEXT by default;
|
|
849
|
+
* pass `type: 'numeric'` to cast for numeric comparison (`::numeric` on
|
|
850
|
+
* PostgreSQL). The column must be json/jsonb: anything else throws
|
|
851
|
+
* {@link ValidationError} (E003). Non-Postgres engines route through the same
|
|
852
|
+
* dialect JSON-extract hook the JSON where-filters use. Cross-relation
|
|
853
|
+
* (lateral) JSON ordering is NOT supported: same-table columns only.
|
|
854
|
+
*/
|
|
855
|
+
export interface JsonPathOrderBy {
|
|
856
|
+
/** JSON path into the column (each element a key or array index). Bound as one text[] param. */
|
|
857
|
+
path: (string | number)[];
|
|
858
|
+
/** Sort direction. Defaults to `'asc'`. */
|
|
859
|
+
direction?: OrderDirection;
|
|
860
|
+
/** Comparison kind for the extracted value. Defaults to `'text'`; `'numeric'` adds a numeric cast. */
|
|
861
|
+
type?: 'numeric' | 'text';
|
|
862
|
+
/** NULLS placement (PostgreSQL / SQLite only: see {@link OrderBySpec}). */
|
|
863
|
+
nulls?: 'first' | 'last';
|
|
864
|
+
}
|
|
744
865
|
/**
|
|
745
866
|
* Ordering by a relation, keyed by the relation name in an {@link OrderByClause}:
|
|
746
867
|
*
|
|
@@ -753,13 +874,83 @@ export interface OrderBySpec {
|
|
|
753
874
|
export type RelationOrderBy = {
|
|
754
875
|
_count: OrderDirection;
|
|
755
876
|
} | Record<string, OrderDirection | OrderBySpec>;
|
|
877
|
+
/**
|
|
878
|
+
* The ordering value extracted from the picked row in a
|
|
879
|
+
* {@link RelationPickOrderBy}: either a plain target column name (camelCase,
|
|
880
|
+
* columnMap-resolved) or a JSON path into a json/jsonb target column.
|
|
881
|
+
* Values extracted from a JSON path compare as TEXT by default; pass
|
|
882
|
+
* `type: 'numeric'` to add a numeric cast.
|
|
883
|
+
*/
|
|
884
|
+
export type RelationPickBy = string | {
|
|
885
|
+
/** json/jsonb column on the relation target. */
|
|
886
|
+
field: string;
|
|
887
|
+
/**
|
|
888
|
+
* JSON path into the column (each element a key or array index). Bound
|
|
889
|
+
* as ONE param: a text[] on PostgreSQL, a `'$'`-rooted JSONPath string
|
|
890
|
+
* on engines whose JSON functions take one (SQLite/MySQL/SQL Server).
|
|
891
|
+
*/
|
|
892
|
+
path: (string | number)[];
|
|
893
|
+
/** Comparison kind for the extracted value. Defaults to `'text'`. */
|
|
894
|
+
type?: 'numeric' | 'text';
|
|
895
|
+
};
|
|
896
|
+
/**
|
|
897
|
+
* Pick-row relation ordering: order a parent query by a value read from ONE
|
|
898
|
+
* related row of a hasMany relation, keyed by the relation name in an
|
|
899
|
+
* {@link OrderByClause}. Compiles to a correlated scalar subquery in ORDER BY:
|
|
900
|
+
*
|
|
901
|
+
* ```ts
|
|
902
|
+
* orderBy: {
|
|
903
|
+
* versions: {
|
|
904
|
+
* pick: { orderBy: { createdAt: 'desc' } }, // which related row
|
|
905
|
+
* by: { field: 'data', path: ['title'] }, // value to sort the parents by
|
|
906
|
+
* direction: 'asc',
|
|
907
|
+
* nulls: 'last',
|
|
908
|
+
* },
|
|
909
|
+
* }
|
|
910
|
+
* // → ORDER BY (SELECT ord0."data" #>> $n::text[] FROM "versions" ord0
|
|
911
|
+
* // WHERE ord0."instance_id" = "instances"."id"
|
|
912
|
+
* // ORDER BY ord0."created_at" DESC LIMIT 1) ASC NULLS LAST
|
|
913
|
+
* ```
|
|
914
|
+
*
|
|
915
|
+
* `pick.orderBy` is REQUIRED (it makes the picked row deterministic) and
|
|
916
|
+
* supports the same surface as a relation `with` orderBy on the target (plain
|
|
917
|
+
* columns, {@link OrderBySpec} nulls, {@link JsonPathOrderBy}). `pick.where`
|
|
918
|
+
* optionally filters the candidate rows before picking. hasMany relations
|
|
919
|
+
* only; top-level findMany orderBy only (manyToMany, to-one relations, and
|
|
920
|
+
* nested `with` orderBy throw {@link ValidationError} E003).
|
|
921
|
+
*/
|
|
922
|
+
export interface RelationPickOrderBy {
|
|
923
|
+
/** Which related row supplies the value. */
|
|
924
|
+
pick: {
|
|
925
|
+
/** Inner ORDER BY choosing the row (required for determinism). */
|
|
926
|
+
orderBy: Record<string, OrderDirection | OrderBySpec | JsonPathOrderBy>;
|
|
927
|
+
/** Optional filter on the candidate rows before picking. */
|
|
928
|
+
where?: Record<string, unknown>;
|
|
929
|
+
};
|
|
930
|
+
/** The value on the picked row to order the parents by. */
|
|
931
|
+
by: RelationPickBy;
|
|
932
|
+
/** Sort direction for the parents. Defaults to `'asc'`. */
|
|
933
|
+
direction?: OrderDirection;
|
|
934
|
+
/**
|
|
935
|
+
* NULLS placement (PostgreSQL / SQLite only: see {@link OrderBySpec}).
|
|
936
|
+
*
|
|
937
|
+
* A parent with NO related rows sorts by NULL. When `nulls` is not set,
|
|
938
|
+
* pick ordering defaults to `NULLS LAST` in BOTH directions, so parents
|
|
939
|
+
* with zero related rows always come last (Postgres's own DESC default is
|
|
940
|
+
* NULLS FIRST, which would put every childless parent at the top of a
|
|
941
|
+
* "highest first" sort). Set `nulls` explicitly to override.
|
|
942
|
+
*/
|
|
943
|
+
nulls?: 'first' | 'last';
|
|
944
|
+
}
|
|
756
945
|
/**
|
|
757
946
|
* An orderBy clause maps each key to one of:
|
|
758
947
|
* - a plain direction (`'asc'` / `'desc'`),
|
|
759
948
|
* - an {@link OrderBySpec} (`{ sort, nulls }`) for NULLS placement,
|
|
949
|
+
* - for json/jsonb columns, a JSON-path ordering ({@link JsonPathOrderBy}),
|
|
760
950
|
* - for pgvector columns, a KNN distance ordering ({@link VectorOrderBy}),
|
|
761
951
|
* - for a relation name, a {@link RelationOrderBy} (`_count` for to-many, a
|
|
762
|
-
* target column for to-one)
|
|
952
|
+
* target column for to-one) or a pick-row ordering
|
|
953
|
+
* ({@link RelationPickOrderBy}, hasMany only).
|
|
763
954
|
*/
|
|
764
|
-
export type OrderByClause = Record<string, OrderDirection | OrderBySpec | VectorOrderBy | RelationOrderBy>;
|
|
955
|
+
export type OrderByClause = Record<string, OrderDirection | OrderBySpec | JsonPathOrderBy | VectorOrderBy | RelationOrderBy | RelationPickOrderBy>;
|
|
765
956
|
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "turbine-orm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.32.0",
|
|
4
4
|
"description": "Postgres-native TypeScript ORM — runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. One dependency, no WASM engine",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|