uql-orm 0.29.0 → 0.31.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/README.md +11 -3
- package/dist/browser/querier/httpQuerier.d.ts +5 -5
- package/dist/browser/uql-browser.min.js.map +2 -2
- package/dist/dialect/abstractSqlDialect.d.ts +6 -4
- package/dist/dialect/abstractSqlDialect.js +29 -29
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +3 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +9 -0
- package/dist/dialect/vectorSqlDialect.js +8 -1
- package/dist/mongo/mongoDialect.d.ts +3 -3
- package/dist/mongo/mongoDialect.js +17 -9
- package/dist/mongo/mongodbQuerier.d.ts +4 -4
- package/dist/pglite/index.d.ts +3 -0
- package/dist/pglite/index.js +3 -0
- package/dist/pglite/pgliteDialect.d.ts +14 -0
- package/dist/pglite/pgliteDialect.js +14 -0
- package/dist/pglite/pgliteQuerier.d.ts +36 -0
- package/dist/pglite/pgliteQuerier.js +30 -0
- package/dist/pglite/pgliteQuerierPool.d.ts +36 -0
- package/dist/pglite/pgliteQuerierPool.js +36 -0
- package/dist/querier/abstractQuerier.d.ts +19 -8
- package/dist/querier/abstractQuerier.js +61 -25
- package/dist/querier/abstractQuerierPool.d.ts +7 -7
- package/dist/querier/abstractSharedHandleQuerierPool.d.ts +42 -0
- package/dist/querier/abstractSharedHandleQuerierPool.js +48 -0
- package/dist/querier/abstractSqlQuerier.d.ts +6 -4
- package/dist/querier/abstractSqlQuerier.js +31 -9
- package/dist/sqlite/localSqliteQuerierPool.d.ts +8 -11
- package/dist/sqlite/localSqliteQuerierPool.js +8 -15
- package/dist/turso/tursoLocalQuerierPool.d.ts +5 -11
- package/dist/turso/tursoLocalQuerierPool.js +4 -14
- package/dist/type/entity.d.ts +41 -16
- package/dist/type/querier.d.ts +14 -8
- package/dist/type/query.d.ts +27 -14
- package/dist/type/queryAggregate.d.ts +67 -24
- package/dist/type/queryWhere.d.ts +8 -5
- package/dist/type/universalQuerier.d.ts +15 -13
- package/dist/type/utility.d.ts +7 -0
- package/dist/util/dialect.util.d.ts +26 -4
- package/dist/util/dialect.util.js +42 -0
- package/package.json +13 -4
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
2
|
import type { ExtraOptions } from '../type/index.js';
|
|
3
3
|
import { TursoDialect } from './tursoDialect.js';
|
|
4
|
-
import { TursoLocalQuerier } from './tursoLocalQuerier.js';
|
|
4
|
+
import { type TursoDatabase, TursoLocalQuerier } from './tursoLocalQuerier.js';
|
|
5
5
|
/** Subset of `DatabaseOpts` from `@tursodatabase/database`, declared locally to avoid the coupling. */
|
|
6
6
|
export type TursoLocalOptions = {
|
|
7
7
|
readonly?: boolean;
|
|
@@ -17,16 +17,10 @@ export type TursoLocalOptions = {
|
|
|
17
17
|
* package ships native binaries that do not resolve on edge runtimes. Separating them guarantees a
|
|
18
18
|
* bundle targeting Workers never reaches the native import.
|
|
19
19
|
*/
|
|
20
|
-
export declare class TursoLocalQuerierPool extends
|
|
20
|
+
export declare class TursoLocalQuerierPool extends AbstractSharedHandleQuerierPool<TursoDatabase, TursoLocalQuerier, TursoDialect> {
|
|
21
21
|
readonly filename: string;
|
|
22
22
|
readonly opts?: TursoLocalOptions | undefined;
|
|
23
|
-
private db?;
|
|
24
23
|
constructor(filename?: string, opts?: TursoLocalOptions | undefined, extra?: ExtraOptions);
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
* so transaction state stays per unit of work.
|
|
28
|
-
*/
|
|
29
|
-
getQuerier(): Promise<TursoLocalQuerier>;
|
|
30
|
-
private openDb;
|
|
31
|
-
end(): Promise<void>;
|
|
24
|
+
protected openDb(): Promise<TursoDatabase>;
|
|
25
|
+
protected buildQuerier(db: TursoDatabase): TursoLocalQuerier;
|
|
32
26
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
2
|
import { TursoDialect } from './tursoDialect.js';
|
|
3
3
|
import { TursoLocalQuerier } from './tursoLocalQuerier.js';
|
|
4
4
|
/**
|
|
@@ -8,23 +8,14 @@ import { TursoLocalQuerier } from './tursoLocalQuerier.js';
|
|
|
8
8
|
* package ships native binaries that do not resolve on edge runtimes. Separating them guarantees a
|
|
9
9
|
* bundle targeting Workers never reaches the native import.
|
|
10
10
|
*/
|
|
11
|
-
export class TursoLocalQuerierPool extends
|
|
11
|
+
export class TursoLocalQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
12
12
|
filename;
|
|
13
13
|
opts;
|
|
14
|
-
db;
|
|
15
14
|
constructor(filename = ':memory:', opts, extra) {
|
|
16
15
|
super(new TursoDialect({ namingStrategy: extra?.namingStrategy }), extra);
|
|
17
16
|
this.filename = filename;
|
|
18
17
|
this.opts = opts;
|
|
19
18
|
}
|
|
20
|
-
/**
|
|
21
|
-
* The database handle is shared (single connection), but each acquisition gets its own querier
|
|
22
|
-
* so transaction state stays per unit of work.
|
|
23
|
-
*/
|
|
24
|
-
async getQuerier() {
|
|
25
|
-
this.db ??= await this.openDb();
|
|
26
|
-
return new TursoLocalQuerier(this.db, this.dialect, this.extra);
|
|
27
|
-
}
|
|
28
19
|
async openDb() {
|
|
29
20
|
const { connect } = await import('@tursodatabase/database');
|
|
30
21
|
// Annotated rather than cast, so the structural contract is checked against the real driver.
|
|
@@ -33,8 +24,7 @@ export class TursoLocalQuerierPool extends AbstractSqlQuerierPool {
|
|
|
33
24
|
await db.pragma('foreign_keys = ON');
|
|
34
25
|
return db;
|
|
35
26
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
this.db = undefined;
|
|
27
|
+
buildQuerier(db) {
|
|
28
|
+
return new TursoLocalQuerier(db, this.dialect, this.extra);
|
|
39
29
|
}
|
|
40
30
|
}
|
package/dist/type/entity.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ForeignKeyAction, IndexType } from '../schema/types.js';
|
|
2
2
|
import type { FilterOptions } from './query.js';
|
|
3
3
|
import type { QueryRaw } from './queryRaw.js';
|
|
4
|
-
import type { Except, Json, Scalar, Type, Unpacked } from './utility.js';
|
|
4
|
+
import type { Except, IsMany, Json, Scalar, Type, Unpacked } from './utility.js';
|
|
5
5
|
import type { VectorDistance, VectorIndexOptions, VectorIndexType } from './vector.js';
|
|
6
6
|
/**
|
|
7
7
|
* Allow to customize the name of the property that identifies an entity
|
|
@@ -16,16 +16,22 @@ export type Key<E> = keyof E & string;
|
|
|
16
16
|
* Includes scalar fields, JSON fields, and scalar arrays (e.g. vector `number[]`).
|
|
17
17
|
* The `-?` modifier strips optionality so the indexed access yields clean key unions
|
|
18
18
|
* (without it, optional properties leak `undefined` into the union).
|
|
19
|
+
*
|
|
20
|
+
* The check is bracketed so `any` resolves once rather than matching both this and
|
|
21
|
+
* {@link RelationKey}: an unbracketed `any extends X` satisfies either branch. It reads
|
|
22
|
+
* `readonly Scalar[]`, which every mutable one satisfies too, so declaring a vector or a scalar
|
|
23
|
+
* array `readonly` does not push the field over into {@link RelationKey}.
|
|
19
24
|
*/
|
|
20
25
|
export type FieldKey<E> = {
|
|
21
|
-
readonly [K in keyof E]-?: NonNullable<E[K]> extends Scalar | Scalar[] | Json ? K : never;
|
|
26
|
+
readonly [K in keyof E]-?: [NonNullable<E[K]>] extends [Scalar | readonly Scalar[] | Json] ? K : never;
|
|
22
27
|
}[Key<E>];
|
|
23
28
|
/**
|
|
24
|
-
* Infers the relation names of an entity
|
|
29
|
+
* Infers the relation names of an entity: whatever is left once its fields and its methods are
|
|
30
|
+
* taken out. Stated as the complement rather than as {@link FieldKey}'s test negated, so the two
|
|
31
|
+
* cannot drift; methods are subtracted because one is not a `Scalar` and would otherwise read as a
|
|
32
|
+
* relation.
|
|
25
33
|
*/
|
|
26
|
-
export type RelationKey<E> =
|
|
27
|
-
readonly [K in keyof E]-?: NonNullable<E[K]> extends Scalar | Scalar[] | Json ? never : K;
|
|
28
|
-
}[Key<E>];
|
|
34
|
+
export type RelationKey<E> = Exclude<Key<E>, FieldKey<E> | MethodKey<E>>;
|
|
29
35
|
/**
|
|
30
36
|
* Whether `T` carries the `Json` brand. Checks for the `__json` marker key explicitly:
|
|
31
37
|
* a bare `extends Json<infer T>` is not discriminating in check position (primitives match it,
|
|
@@ -75,7 +81,7 @@ export type JsonFieldPathValue<E, P extends string> = P extends `${infer F}.${in
|
|
|
75
81
|
* Used by `$push` and `$pull` to provide type-safe element targets.
|
|
76
82
|
*/
|
|
77
83
|
export type JsonArrayFields<T> = {
|
|
78
|
-
[K in keyof T as
|
|
84
|
+
[K in keyof T as IsMany<T[K]> extends true ? K & string : never]?: Unpacked<NonNullable<T[K]>>;
|
|
79
85
|
};
|
|
80
86
|
/**
|
|
81
87
|
* Operator shape accepted by JSON/JSONB fields in update payloads: `$set`/`$unset` target object
|
|
@@ -115,16 +121,31 @@ export type JsonUpdateOp<T = unknown> = {
|
|
|
115
121
|
* `JSON_SET(arr, '$.k', v)` is a no-op on MySQL and SQLite. Replace the whole value instead.
|
|
116
122
|
* `Json<unknown>` stays permissive, since `unknown` is not an array.
|
|
117
123
|
*/
|
|
118
|
-
type JsonUpdateOpFor<V, T = UnwrapJson<NonNullable<V>>> = [T] extends [never] ? never : T extends
|
|
124
|
+
type JsonUpdateOpFor<V, T = UnwrapJson<NonNullable<V>>> = [T] extends [never] ? never : IsMany<T> extends true ? never : JsonUpdateOp<T>;
|
|
119
125
|
/**
|
|
120
|
-
* Accepted value for a single field in an update payload: the value itself, `
|
|
121
|
-
* expression (e.g. `raw('NOW()')`), and - for JSON object
|
|
126
|
+
* Accepted value for a single field in an update payload: the value itself, `null` where the column
|
|
127
|
+
* is nullable, `QueryRaw` for a raw SQL expression (e.g. `raw('NOW()')`), and - for JSON object
|
|
128
|
+
* fields - the JSON operators.
|
|
129
|
+
*
|
|
130
|
+
* An optional property is a nullable column, and clearing one is what an update is for, so `null`
|
|
131
|
+
* belongs in the declared type rather than behind a cast.
|
|
122
132
|
*/
|
|
123
|
-
type UpdateFieldValue<V> = V | QueryRaw | JsonUpdateOpFor<V>;
|
|
133
|
+
type UpdateFieldValue<V> = V | (undefined extends V ? null : never) | QueryRaw | JsonUpdateOpFor<V>;
|
|
124
134
|
/**
|
|
125
|
-
*
|
|
126
|
-
*
|
|
127
|
-
*
|
|
135
|
+
* An entity's fields and relations, each keeping its declared optionality: what the whole-record
|
|
136
|
+
* writes (`insertOne`, `saveOne`, `upsertOne`, and their `*Many`) persist.
|
|
137
|
+
*
|
|
138
|
+
* Not `E`: that *demands* back every method the class declares, so on an entity carrying a
|
|
139
|
+
* lifecycle hook - `@BeforeInsert() generateSlug()` - a plain `{ title: 'Hello' }` was rejected as
|
|
140
|
+
* "missing the following properties". Method-free entities were unaffected, which is why the other
|
|
141
|
+
* examples worked. No runtime filter can help; the call never gets that far. `Pick` because it
|
|
142
|
+
* stays indexable by `IdKey<E>`, which the write path needs.
|
|
143
|
+
*/
|
|
144
|
+
export type EntityData<E> = Pick<E, FieldKey<E> | RelationKey<E>>;
|
|
145
|
+
/**
|
|
146
|
+
* Payload type for update operations: {@link EntityData} made partial, and widened per field to
|
|
147
|
+
* accept `QueryRaw` or `JsonUpdateOp` (for JSON fields), which gives IDE autocomplete for
|
|
148
|
+
* `$set`/`$push`/`$pull` keys via `Json<infer T>`.
|
|
128
149
|
*/
|
|
129
150
|
export type UpdatePayload<E> = {
|
|
130
151
|
[K in FieldKey<E>]?: UpdateFieldValue<E[K]>;
|
|
@@ -149,6 +170,10 @@ export type IdKey<E> = E extends {
|
|
|
149
170
|
} ? 'uuid' & FieldKey<E> : FieldKey<E>;
|
|
150
171
|
/**
|
|
151
172
|
* Infers the value of the key identifier on an entity.
|
|
173
|
+
*
|
|
174
|
+
* Nullable, because an entity declares its id optional - nothing has assigned one before the
|
|
175
|
+
* insert. That puts `undefined` inside every by-id method's parameter, where it would mean "no
|
|
176
|
+
* filter"; `assertIdValue` is what rejects it.
|
|
152
177
|
*/
|
|
153
178
|
export type IdValue<E> = E[IdKey<E>];
|
|
154
179
|
/**
|
|
@@ -346,8 +371,8 @@ export type RelationTarget<V> = NonNullable<Unpacked<NonNullable<V>>>;
|
|
|
346
371
|
*/
|
|
347
372
|
export type RelationOptionsFor<V> = Omit<RelationOptions<RelationTarget<V>>, 'entity' | 'cardinality'> & {
|
|
348
373
|
readonly entity: EntityGetter<RelationTarget<V>>;
|
|
349
|
-
readonly cardinality:
|
|
350
|
-
} & (
|
|
374
|
+
readonly cardinality: IsMany<V> extends true ? '1m' | 'mm' : '11' | 'm1';
|
|
375
|
+
} & (IsMany<V> extends true ? RelationJoin<RelationTarget<V>> : unknown);
|
|
351
376
|
/**
|
|
352
377
|
* The method names of an entity, so hook registrations name a method that exists.
|
|
353
378
|
*/
|
package/dist/type/querier.d.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { SqlDialectName } from './dialect.js';
|
|
|
4
4
|
import type { HookEvent } from './entity.js';
|
|
5
5
|
import type { LoggingOptions } from './logger.js';
|
|
6
6
|
import type { NamingStrategy } from './namingStrategy.js';
|
|
7
|
-
import type { Query, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
|
|
7
|
+
import type { Query, QueryFilter, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
|
|
8
8
|
import type { UniversalQuerier } from './universalQuerier.js';
|
|
9
9
|
import type { Type } from './utility.js';
|
|
10
10
|
/**
|
|
@@ -25,51 +25,57 @@ export type TransactionOptions = {
|
|
|
25
25
|
readonly isolationLevel?: IsolationLevel;
|
|
26
26
|
};
|
|
27
27
|
export type DialectName = SqlDialectName | 'mongodb';
|
|
28
|
+
/**
|
|
29
|
+
* The read and delete methods below take the entity as an argument or as the query's `$entity` key.
|
|
30
|
+
* In each pair the `$entity` overload comes **first** on purpose: when no overload matches,
|
|
31
|
+
* TypeScript reports the error from the *last* one, so keeping the entity-argument form last is
|
|
32
|
+
* what makes a typo'd query key report as itself rather than as a missing `$entity`.
|
|
33
|
+
*/
|
|
28
34
|
export interface Querier extends UniversalQuerier {
|
|
29
35
|
/**
|
|
30
36
|
* Find one record. Supports both entity-as-argument and entity-as-field patterns.
|
|
31
37
|
*/
|
|
32
|
-
findOne<E extends object>(entity: Type<E>, q: QueryOne<E>, opts?: QueryOptions): Promise<E | undefined>;
|
|
33
38
|
findOne<E extends object>(q: QueryOne<E> & {
|
|
34
39
|
$entity: Type<E>;
|
|
35
40
|
}, opts?: QueryOptions): Promise<E | undefined>;
|
|
41
|
+
findOne<E extends object>(entity: Type<E>, q: QueryOne<E>, opts?: QueryOptions): Promise<E | undefined>;
|
|
36
42
|
/**
|
|
37
43
|
* Find many records. Supports both entity-as-argument and entity-as-field patterns.
|
|
38
44
|
*/
|
|
39
|
-
findMany<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<E[]>;
|
|
40
45
|
findMany<E extends object>(q: Query<E> & {
|
|
41
46
|
$entity: Type<E>;
|
|
42
47
|
}, opts?: QueryOptions): Promise<E[]>;
|
|
48
|
+
findMany<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<E[]>;
|
|
43
49
|
/**
|
|
44
50
|
* Stream records as an async iterable. Supports both patterns.
|
|
45
51
|
* Does not fill relations or fire lifecycle hooks.
|
|
46
52
|
*/
|
|
47
|
-
findManyStream<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): AsyncIterable<E>;
|
|
48
53
|
findManyStream<E extends object>(q: Query<E> & {
|
|
49
54
|
$entity: Type<E>;
|
|
50
55
|
}, opts?: QueryOptions): AsyncIterable<E>;
|
|
56
|
+
findManyStream<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): AsyncIterable<E>;
|
|
51
57
|
/**
|
|
52
58
|
* Find many records and count. Supports both patterns.
|
|
53
59
|
*/
|
|
54
|
-
findManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
|
|
55
60
|
findManyAndCount<E extends object>(q: Query<E> & {
|
|
56
61
|
$entity: Type<E>;
|
|
57
62
|
}, opts?: QueryOptions): Promise<[E[], number]>;
|
|
63
|
+
findManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
|
|
58
64
|
/**
|
|
59
65
|
* Count records. Supports both patterns.
|
|
60
66
|
*/
|
|
61
|
-
count<E extends object>(
|
|
62
|
-
count<E extends object>(q: QuerySearch<E> & {
|
|
67
|
+
count<E extends object>(q: QueryFilter<E> & {
|
|
63
68
|
$entity: Type<E>;
|
|
64
69
|
}, opts?: QueryOptions): Promise<number>;
|
|
70
|
+
count<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: QueryOptions): Promise<number>;
|
|
65
71
|
/**
|
|
66
72
|
* Delete many records (soft-deletes when the entity has a soft-delete field, else removes them).
|
|
67
73
|
* Supports both entity-as-argument and entity-as-field patterns.
|
|
68
74
|
*/
|
|
69
|
-
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
70
75
|
deleteMany<E extends object>(q: QuerySearch<E> & {
|
|
71
76
|
$entity: Type<E>;
|
|
72
77
|
}, opts?: QueryOptions): Promise<number>;
|
|
78
|
+
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
73
79
|
/**
|
|
74
80
|
* whether this querier is in a transaction or not.
|
|
75
81
|
*/
|
package/dist/type/query.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type { FieldKey, JsonFieldPaths, RelationKey } from './entity.js';
|
|
1
|
+
import type { FieldKey, JsonFieldPaths, RelationKey, RelationTarget } from './entity.js';
|
|
2
2
|
import type { QueryLock } from './queryLock.js';
|
|
3
3
|
import type { QueryRaw } from './queryRaw.js';
|
|
4
4
|
import type { QueryWhere } from './queryWhere.js';
|
|
5
|
-
import type { BooleanLike, Except,
|
|
5
|
+
import type { BooleanLike, Except, IsMany, PrimaryKey } from './utility.js';
|
|
6
6
|
import type { QueryVectorSearch } from './vector.js';
|
|
7
7
|
export type QueryOptions = {
|
|
8
8
|
/**
|
|
@@ -64,9 +64,9 @@ export type QueryConflictPaths<E> = {
|
|
|
64
64
|
[K in FieldKey<E>]?: true;
|
|
65
65
|
};
|
|
66
66
|
/**
|
|
67
|
-
*
|
|
67
|
+
* Options to populate a relation declared as `V`, by its cardinality.
|
|
68
68
|
*/
|
|
69
|
-
export type QueryPopulateRelationOptions<
|
|
69
|
+
export type QueryPopulateRelationOptions<V> = (IsMany<V> extends true ? Except<Query<RelationTarget<V>>, '$lock'> : QueryUnique<RelationTarget<V>>) & {
|
|
70
70
|
$required?: boolean;
|
|
71
71
|
};
|
|
72
72
|
/**
|
|
@@ -126,7 +126,7 @@ export type QuerySortMap<E, Vector extends boolean = true> = {
|
|
|
126
126
|
} & {
|
|
127
127
|
[P in JsonFieldPaths<E>]?: QuerySortDirection;
|
|
128
128
|
} & {
|
|
129
|
-
[K in RelationKey<E> as
|
|
129
|
+
[K in RelationKey<E> as IsMany<E[K]> extends true ? never : K]?: QuerySortMap<RelationTarget<E[K]>, false>;
|
|
130
130
|
};
|
|
131
131
|
/**
|
|
132
132
|
* pager options.
|
|
@@ -142,21 +142,29 @@ export type QueryPager = {
|
|
|
142
142
|
$limit?: number;
|
|
143
143
|
};
|
|
144
144
|
/**
|
|
145
|
-
*
|
|
145
|
+
* Which rows a statement addresses. `count` takes exactly this: how many rows match is all a count
|
|
146
|
+
* can answer, so an ordering or a page on it is a clause it could only drop or choke on.
|
|
146
147
|
*/
|
|
147
|
-
export type
|
|
148
|
+
export type QueryFilter<E> = {
|
|
148
149
|
/**
|
|
149
150
|
* filtering options.
|
|
150
151
|
*/
|
|
151
152
|
$where?: QueryWhere<E>;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* A filter plus the ordering and page `updateMany`/`deleteMany` take. Both settle the rows they
|
|
156
|
+
* picked before writing, so the page is portable rather than MySQL-only.
|
|
157
|
+
*
|
|
158
|
+
* `$sort` excludes vector search here for the reason `$lock` is declared on {@link Query} instead:
|
|
159
|
+
* a vector search ranks rows into a projected distance column, and only a SELECT has a projection
|
|
160
|
+
* list to hold one. Passing `QuerySortMap` its `Vector = false` is what keeps it off these.
|
|
161
|
+
*/
|
|
162
|
+
export type QuerySearch<E> = QueryFilter<E> & {
|
|
152
163
|
/**
|
|
153
164
|
* sorting options.
|
|
154
165
|
*/
|
|
155
|
-
$sort?: QuerySortMap<E>;
|
|
166
|
+
$sort?: QuerySortMap<E, false>;
|
|
156
167
|
} & QueryPager;
|
|
157
|
-
/**
|
|
158
|
-
* criteria one options.
|
|
159
|
-
*/
|
|
160
168
|
/**
|
|
161
169
|
* query options.
|
|
162
170
|
*/
|
|
@@ -177,6 +185,11 @@ export type Query<E> = {
|
|
|
177
185
|
* regardless, since subtracting them would leave the relation unfilled.
|
|
178
186
|
*/
|
|
179
187
|
$exclude?: QueryExclude<E>;
|
|
188
|
+
/**
|
|
189
|
+
* sorting options, vector similarity search included: a SELECT is the one statement with a
|
|
190
|
+
* projection list to hold the distance such a search computes.
|
|
191
|
+
*/
|
|
192
|
+
$sort?: QuerySortMap<E>;
|
|
180
193
|
/**
|
|
181
194
|
* whether to return only distinct rows.
|
|
182
195
|
*/
|
|
@@ -187,11 +200,11 @@ export type Query<E> = {
|
|
|
187
200
|
* the rows, so it is rejected rather than emitted. Locks only the queried entity, never anything
|
|
188
201
|
* reached through `$populate`. SQL only; MongoDB and the SQLite family reject it.
|
|
189
202
|
*
|
|
190
|
-
*
|
|
191
|
-
*
|
|
203
|
+
* Declared here rather than on {@link QuerySearch}, which `update`/`delete` take: that placement
|
|
204
|
+
* is what keeps the clause off those statements at the type level.
|
|
192
205
|
*/
|
|
193
206
|
$lock?: QueryLock;
|
|
194
|
-
} &
|
|
207
|
+
} & QueryFilter<E> & QueryPager;
|
|
195
208
|
/**
|
|
196
209
|
* options to get a single record.
|
|
197
210
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { FieldKey } from './entity.js';
|
|
2
|
-
import type { QueryPager, QuerySortDirection
|
|
2
|
+
import type { QueryPager, QuerySortDirection } from './query.js';
|
|
3
3
|
import type { QueryWhere, QueryWhereFieldValue } from './queryWhere.js';
|
|
4
4
|
/**
|
|
5
5
|
* Maps the offending keys to `never`, turning an excess key into a compile error; resolves to
|
|
@@ -10,6 +10,22 @@ import type { QueryWhere, QueryWhereFieldValue } from './queryWhere.js';
|
|
|
10
10
|
* @internal
|
|
11
11
|
*/
|
|
12
12
|
type Reject<K> = [K] extends [never] ? unknown : Record<K & string, never>;
|
|
13
|
+
/**
|
|
14
|
+
* The columns `$group` actually names: keys whose value is literally `true`, not `keyof G`.
|
|
15
|
+
* Wherever `G` cannot be inferred - `$group` omitted, hoisted, or annotated - it *is* its own
|
|
16
|
+
* constraint, whose every value is `true | undefined`, and keying off values yields `never` there
|
|
17
|
+
* rather than every field of the entity.
|
|
18
|
+
* @internal
|
|
19
|
+
*/
|
|
20
|
+
type GroupedKeys<G> = {
|
|
21
|
+
[K in keyof G]: G[K] extends true ? K : never;
|
|
22
|
+
}[keyof G];
|
|
23
|
+
/**
|
|
24
|
+
* The keys `T` declares by name, or `never` when `T` is only an index signature - which is what an
|
|
25
|
+
* uninferred `$agg` is, and what would otherwise make every key look like a declared alias.
|
|
26
|
+
* @internal
|
|
27
|
+
*/
|
|
28
|
+
type NamedKeys<T> = string extends keyof T ? never : keyof T;
|
|
13
29
|
declare const QUERY_AGGREGATE_OPS: readonly ['$count', '$sum', '$avg', '$min', '$max'];
|
|
14
30
|
/**
|
|
15
31
|
* Supported aggregate operations.
|
|
@@ -44,10 +60,29 @@ export declare function resolveAggregateOp(key: string): {
|
|
|
44
60
|
};
|
|
45
61
|
/** The argument of an aggregate function: a field, or `'*'` (only meaningful for `COUNT(*)`). */
|
|
46
62
|
export type QueryAggregateArg<E> = FieldKey<E> | '*';
|
|
47
|
-
/**
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Fields `SUM`/`AVG` can total. Restricted to numeric columns because the result is declared
|
|
65
|
+
* `number`: totalling a text or date column is either an engine error or a coercion, and neither
|
|
66
|
+
* produces the value the signature promises.
|
|
67
|
+
*/
|
|
68
|
+
type NumericFieldKey<E> = {
|
|
69
|
+
readonly [K in FieldKey<E>]: [NonNullable<E[K]>] extends [number | bigint] ? K : never;
|
|
70
|
+
}[FieldKey<E>];
|
|
71
|
+
/** Every aggregate op, plain and DISTINCT-qualified. */
|
|
72
|
+
type AggregateOp = QueryAggregateOp | QueryAggregateDistinctOp;
|
|
73
|
+
/**
|
|
74
|
+
* Names a subset of {@link AggregateOp}. The constraint is the point, and why this is not `Extract`:
|
|
75
|
+
* renaming an op stops these literals satisfying it and breaks the subsets below at compile time,
|
|
76
|
+
* where `Extract` would quietly drop the renamed member and leave the subset wrong but valid.
|
|
77
|
+
*/
|
|
78
|
+
type OpsOf<K extends AggregateOp> = K;
|
|
79
|
+
/** Ops that total a column, so their argument has to be numeric. */
|
|
80
|
+
type TotallingOp = OpsOf<'$sum' | '$avg' | '$sumDistinct' | '$avgDistinct'>;
|
|
81
|
+
/**
|
|
82
|
+
* Every aggregate op mapped to the argument it accepts: `$count` a field or `'*'` (`COUNT(*)`),
|
|
83
|
+
* the totalling ops a numeric field, `$min`/`$max`/`$countDistinct` any field.
|
|
84
|
+
*/
|
|
85
|
+
type QueryAggregateArgMap<E> = Record<'$count', QueryAggregateArg<E>> & Record<TotallingOp, NumericFieldKey<E>> & Record<Exclude<AggregateOp, '$count' | TotallingOp>, FieldKey<E>>;
|
|
51
86
|
/** Exactly one key of `T`: the chosen op with its value; every other op key is forbidden (`never`). */
|
|
52
87
|
type ExactlyOne<T> = {
|
|
53
88
|
[K in keyof T]: Readonly<Record<K, T[K]>> & Partial<Readonly<Record<Exclude<keyof T, K>, never>>>;
|
|
@@ -64,17 +99,14 @@ type ExactlyOne<T> = {
|
|
|
64
99
|
* @example { $avg: 'age' } → AVG("age")
|
|
65
100
|
*/
|
|
66
101
|
export type QueryAggregateFn<E> = ExactlyOne<QueryAggregateArgMap<E>>;
|
|
67
|
-
/**
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
*/
|
|
71
|
-
type QueryAggregateNumericOp = QueryAggregateDistinctOp | (typeof QUERY_AGGREGATE_DISTINCT_OP_BASE)[QueryAggregateDistinctOp];
|
|
72
|
-
/** A single-key `{ [op]: unknown }` shape for each {@link QueryAggregateNumericOp}, matched to infer a `number` result. */
|
|
73
|
-
type QueryAggregateNumericFn = {
|
|
74
|
-
[K in QueryAggregateNumericOp]: {
|
|
102
|
+
/** A single-key `{ [op]: unknown }` shape for each op in `Ops`, matched to infer that op's result. */
|
|
103
|
+
type FnWithOp<Ops extends string> = {
|
|
104
|
+
[K in Ops]: {
|
|
75
105
|
readonly [P in K]: unknown;
|
|
76
106
|
};
|
|
77
|
-
}[
|
|
107
|
+
}[Ops];
|
|
108
|
+
/** Ops that count rows. Alone among the ops they answer `0`, never NULL, over an empty group. */
|
|
109
|
+
type CountingOp = OpsOf<'$count' | '$countDistinct'>;
|
|
78
110
|
/**
|
|
79
111
|
* Group-by columns: an object mapping entity field keys to `true`, exactly like {@link QuerySelect}.
|
|
80
112
|
* Typed against the entity, so a typo'd column is a compile error. Compute aggregate columns with
|
|
@@ -105,18 +137,22 @@ export type QueryAggMap<E> = {
|
|
|
105
137
|
/** The entity type of an aggregated field reference `F`, or `unknown` if it is not a known field. */
|
|
106
138
|
type FieldValueType<E, F> = F extends keyof E ? E[F] : unknown;
|
|
107
139
|
/**
|
|
108
|
-
* Resolves a single computed column's type from its aggregate function: `$count
|
|
109
|
-
*
|
|
140
|
+
* Resolves a single computed column's type from its aggregate function: `$count` is always
|
|
141
|
+
* `number`; `$sum`/`$avg` total to a `number` or to `null`; `$min`/`$max` keep the aggregated
|
|
142
|
+
* field's own type, likewise or `null`.
|
|
143
|
+
*
|
|
144
|
+
* Everything but `$count` is nullable: an aggregate over zero rows is NULL, and an ungrouped one
|
|
145
|
+
* still returns a row, so a `$where` matching nothing hands back a row of NULLs.
|
|
110
146
|
*
|
|
111
147
|
* `$sum`/`$avg` are exact to 2^53: Postgres widens a sum over BIGINT to NUMERIC, and decoding that
|
|
112
148
|
* text to satisfy this `number` drops the digits past that bound. Use `raw()` for a wider total.
|
|
113
149
|
* @internal
|
|
114
150
|
*/
|
|
115
|
-
type QueryAggregateFnResult<E, Fn> = Fn extends
|
|
151
|
+
type QueryAggregateFnResult<E, Fn> = Fn extends FnWithOp<CountingOp> ? number : Fn extends FnWithOp<TotallingOp> ? number | null : Fn extends {
|
|
116
152
|
readonly $min: infer F;
|
|
117
|
-
}
|
|
153
|
+
} | {
|
|
118
154
|
readonly $max: infer F;
|
|
119
|
-
} ? FieldValueType<E, F> : unknown;
|
|
155
|
+
} ? FieldValueType<E, F> | null : unknown;
|
|
120
156
|
/**
|
|
121
157
|
* Flattens an intersection into a single object literal for readable editor hovers.
|
|
122
158
|
* @internal
|
|
@@ -127,9 +163,12 @@ type Simplify<T> = {
|
|
|
127
163
|
/**
|
|
128
164
|
* Infers the aggregated result row: grouped columns (`G`) keep their entity type; computed columns
|
|
129
165
|
* (`A`) resolve from their aggregate function via {@link QueryAggregateFnResult}.
|
|
166
|
+
*
|
|
167
|
+
* Grouped columns come from {@link GroupedKeys}, not `keyof G`, so a `$group` the compiler could
|
|
168
|
+
* not read contributes none rather than all of them.
|
|
130
169
|
*/
|
|
131
170
|
export type QueryAggregateResult<E, G, A> = Simplify<{
|
|
132
|
-
-readonly [K in
|
|
171
|
+
-readonly [K in GroupedKeys<G> & FieldKey<E>]: E[K];
|
|
133
172
|
} & {
|
|
134
173
|
-readonly [K in keyof A]: QueryAggregateFnResult<E, A[K]>;
|
|
135
174
|
}>;
|
|
@@ -171,8 +210,11 @@ export type QueryAggregate<E, G extends QueryGroupMap<E> = QueryGroupMap<E>, A e
|
|
|
171
210
|
readonly $group?: G & Reject<Exclude<keyof G, FieldKey<E>>>;
|
|
172
211
|
/**
|
|
173
212
|
* Computed aggregate columns - `{ count: { $count: '*' }, avgAge: { $avg: 'age' } }`.
|
|
213
|
+
*
|
|
214
|
+
* An alias repeating a `$group` column is rejected: both would be emitted under that one name,
|
|
215
|
+
* leaving the driver to keep whichever it read last.
|
|
174
216
|
*/
|
|
175
|
-
readonly $agg?: A
|
|
217
|
+
readonly $agg?: A & Reject<NamedKeys<A> & GroupedKeys<G>>;
|
|
176
218
|
/**
|
|
177
219
|
* Post-aggregation filtering, applied after grouping (SQL `HAVING`, MongoDB post-group `$match`).
|
|
178
220
|
* Keyed by the result columns (grouped columns + computed aliases), and each value is typed to that
|
|
@@ -184,10 +226,11 @@ export type QueryAggregate<E, G extends QueryGroupMap<E> = QueryGroupMap<E>, A e
|
|
|
184
226
|
readonly [K in keyof QueryAggregateResult<E, G, A>]?: QueryWhereFieldValue<QueryAggregateResult<E, G, A>[K]>;
|
|
185
227
|
};
|
|
186
228
|
/**
|
|
187
|
-
* Sort the aggregated results by a grouped column
|
|
229
|
+
* Sort the aggregated results by a grouped column or a computed alias - an aggregate's rows are
|
|
230
|
+
* its groups, so any other entity field names a value the statement never produced.
|
|
188
231
|
*/
|
|
189
|
-
readonly $sort?:
|
|
190
|
-
readonly [K in
|
|
232
|
+
readonly $sort?: {
|
|
233
|
+
readonly [K in keyof QueryAggregateResult<E, G, A>]?: QuerySortDirection;
|
|
191
234
|
};
|
|
192
235
|
} & QueryPager;
|
|
193
236
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { FieldKey, IdValue, JsonFieldPaths, JsonFieldPathValue, RelationKey } from './entity.js';
|
|
1
|
+
import type { FieldKey, IdValue, JsonFieldPaths, JsonFieldPathValue, RelationKey, RelationTarget } from './entity.js';
|
|
2
2
|
import type { QueryRaw } from './queryRaw.js';
|
|
3
|
-
import type { ExpandScalar, QueryComparableScalar, Scalar
|
|
3
|
+
import type { ExpandScalar, IsMany, QueryComparableScalar, Scalar } from './utility.js';
|
|
4
4
|
/**
|
|
5
5
|
* options for full-text-search operator.
|
|
6
6
|
*/
|
|
@@ -35,7 +35,7 @@ export type QueryWhereFieldMap<E> = {
|
|
|
35
35
|
export type QueryWhereMap<E> = QueryWhereFieldMap<E> & QueryWhereRootOperator<E> & {
|
|
36
36
|
[P in JsonFieldPaths<E>]?: QueryWhereFieldValue<JsonFieldPathValue<E, P>>;
|
|
37
37
|
} & {
|
|
38
|
-
[K in RelationKey<E>]?: QueryWhereMap<
|
|
38
|
+
[K in RelationKey<E>]?: QueryWhereMap<RelationTarget<E[K]>> | QueryRelationSizeFilter;
|
|
39
39
|
};
|
|
40
40
|
/**
|
|
41
41
|
* Filter a to-many relation by its row count.
|
|
@@ -246,7 +246,7 @@ type QueryCommonOp = Exclude<keyof QueryWhereFieldOperatorMap<unknown>, QueryStr
|
|
|
246
246
|
* Operator keys applicable to a field of type `T`. Brackets prevent union distribution so an
|
|
247
247
|
* optional field (`string | undefined`) or a literal union (`'a' | 'b'`) gates as one type.
|
|
248
248
|
*/
|
|
249
|
-
type QueryAllowedOp<T> = QueryCommonOp | ([NonNullable<T>] extends [QueryComparableScalar] ? QueryOrderedOp : never) | ([NonNullable<T>] extends [string] ? QueryStringOp : never) | (
|
|
249
|
+
type QueryAllowedOp<T> = QueryCommonOp | ([NonNullable<T>] extends [QueryComparableScalar] ? QueryOrderedOp : never) | ([NonNullable<T>] extends [string] ? QueryStringOp : never) | (IsMany<T> extends true ? QueryArrayOp : never);
|
|
250
250
|
/**
|
|
251
251
|
* Operators applicable to a field of type `T`: string operators require string fields, ordering
|
|
252
252
|
* operators comparable fields, array operators array fields. `unknown` stays fully permissive
|
|
@@ -257,8 +257,11 @@ export type QueryWhereFieldOperators<T> = unknown extends T ? QueryWhereFieldOpe
|
|
|
257
257
|
* Value for a field comparison. A bare array is an implicit `$in` for scalar fields only:
|
|
258
258
|
* on array-typed fields (e.g. a vector `number[]`) an array of arrays is ambiguous, so
|
|
259
259
|
* membership there requires an explicit operator.
|
|
260
|
+
*
|
|
261
|
+
* `null` is accepted on a nullable field (an optional property is a nullable column), matching what
|
|
262
|
+
* `$eq: null` already took.
|
|
260
263
|
*/
|
|
261
|
-
export type QueryWhereFieldValue<T> = T | (
|
|
264
|
+
export type QueryWhereFieldValue<T> = T | (undefined extends T ? null : never) | (IsMany<T> extends true ? never : T[]) | QueryWhereFieldOperators<T> | QueryRaw;
|
|
262
265
|
/**
|
|
263
266
|
* query filter array - used for `$and`, `$or`, `$not`, `$nor` operators.
|
|
264
267
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { IdValue, UpdatePayload } from './entity.js';
|
|
2
|
-
import type { Query, QueryConflictPaths, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
|
|
1
|
+
import type { EntityData, IdValue, UpdatePayload } from './entity.js';
|
|
2
|
+
import type { Query, QueryConflictPaths, QueryFilter, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
|
|
3
3
|
import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
|
|
4
4
|
import type { Type } from './utility.js';
|
|
5
5
|
/**
|
|
@@ -46,22 +46,24 @@ export interface UniversalQuerier {
|
|
|
46
46
|
*/
|
|
47
47
|
findManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
|
|
48
48
|
/**
|
|
49
|
-
* counts the number of records matching the given
|
|
49
|
+
* counts the number of records matching the given filter.
|
|
50
50
|
* @param entity the target entity
|
|
51
|
-
* @param q the
|
|
51
|
+
* @param q the filter
|
|
52
52
|
* @return the count
|
|
53
53
|
*/
|
|
54
|
-
count<E extends object>(entity: Type<E>, q?:
|
|
54
|
+
count<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: QueryOptions): Promise<number>;
|
|
55
55
|
/**
|
|
56
56
|
* Insert a single record and return its ID (provided, `onInsert`-generated, or
|
|
57
57
|
* database-generated - see {@link UniversalQuerier.insertMany} for the exact semantics).
|
|
58
|
-
* Returns `undefined`
|
|
59
|
-
*
|
|
58
|
+
* Returns `undefined` only where the database cannot report one: MySQL, whose `LAST_INSERT_ID()`
|
|
59
|
+
* speaks for `AUTO_INCREMENT` columns alone and is left *stale* rather than cleared otherwise, so
|
|
60
|
+
* a non-auto-increment key the caller did not supply has no id to give and a header read would
|
|
61
|
+
* hand back an earlier row's. Every other backend uses `RETURNING` and is exact, SQLite included.
|
|
60
62
|
* @param entity the entity to persist on
|
|
61
63
|
* @param payload the data to be persisted
|
|
62
64
|
* @return the ID
|
|
63
65
|
*/
|
|
64
|
-
insertOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E> | undefined>;
|
|
66
|
+
insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E> | undefined>;
|
|
65
67
|
/**
|
|
66
68
|
* Insert multiple records in a single statement (auto-chunked when the batch exceeds the
|
|
67
69
|
* dialect's bind-parameter limit) and return their IDs in payload order.
|
|
@@ -75,7 +77,7 @@ export interface UniversalQuerier {
|
|
|
75
77
|
* @param payload the data to be persisted
|
|
76
78
|
* @return the IDs
|
|
77
79
|
*/
|
|
78
|
-
insertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
80
|
+
insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
79
81
|
/**
|
|
80
82
|
* updates a record partially.
|
|
81
83
|
* @param entity the entity to persist on
|
|
@@ -99,7 +101,7 @@ export interface UniversalQuerier {
|
|
|
99
101
|
* @param payload the data to be persisted
|
|
100
102
|
* @return operation metadata; see {@link QueryUpdateResult}
|
|
101
103
|
*/
|
|
102
|
-
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<QueryUpdateResult>;
|
|
104
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
103
105
|
/**
|
|
104
106
|
* Insert or update many records based on the conflict paths.
|
|
105
107
|
* @param entity the entity to persist on
|
|
@@ -107,21 +109,21 @@ export interface UniversalQuerier {
|
|
|
107
109
|
* @param payload the data to be persisted
|
|
108
110
|
* @return operation metadata; see {@link QueryUpdateResult}
|
|
109
111
|
*/
|
|
110
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<QueryUpdateResult>;
|
|
112
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
111
113
|
/**
|
|
112
114
|
* insert or update a record.
|
|
113
115
|
* @param entity the entity to persist on
|
|
114
116
|
* @param payload the data to be persisted
|
|
115
117
|
* @return the ID
|
|
116
118
|
*/
|
|
117
|
-
saveOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E>>;
|
|
119
|
+
saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E>>;
|
|
118
120
|
/**
|
|
119
121
|
* Insert or update records.
|
|
120
122
|
* @param entity the entity to persist on
|
|
121
123
|
* @param payload the data to be persisted
|
|
122
124
|
* @return the IDs
|
|
123
125
|
*/
|
|
124
|
-
saveMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
126
|
+
saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
125
127
|
/**
|
|
126
128
|
* delete or SoftDelete a record.
|
|
127
129
|
* @param entity the entity to persist on
|
package/dist/type/utility.d.ts
CHANGED
|
@@ -58,3 +58,10 @@ export type Except<T, K extends keyof T> = {
|
|
|
58
58
|
[P in keyof T as P extends K ? never : P]: T[P];
|
|
59
59
|
};
|
|
60
60
|
export type Unpacked<T> = T extends readonly (infer U)[] ? U : T extends (...args: unknown[]) => infer U ? U : T extends Promise<infer U> ? U : T;
|
|
61
|
+
/**
|
|
62
|
+
* Whether the value a property holds is many rather than one: a to-many relation, a scalar array, a
|
|
63
|
+
* vector. Every array test in the type layer goes through this, because writing one by hand gets some
|
|
64
|
+
* part of it wrong in ways nothing reports. `isMany.test-d.ts` has one case per part, and says what
|
|
65
|
+
* each is load-bearing for.
|
|
66
|
+
*/
|
|
67
|
+
export type IsMany<V> = [NonNullable<V>] extends [readonly unknown[]] ? true : false;
|