uql-orm 0.68.1 → 0.70.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/browser/querier/httpQuerier.d.ts +7 -7
- package/dist/browser/type/clientQuerier.d.ts +5 -5
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +6 -6
- package/dist/cockroachdb/cockroachDialect.js +2 -2
- package/dist/dialect/abstractDialect.d.ts +8 -2
- package/dist/dialect/abstractDialect.js +17 -1
- package/dist/dialect/abstractSqlDialect.d.ts +42 -4
- package/dist/dialect/abstractSqlDialect.js +164 -48
- package/dist/dialect/aliases.d.ts +15 -4
- package/dist/dialect/aliases.js +15 -4
- package/dist/dialect/mysqlLikeSqlDialect.js +3 -2
- package/dist/dialect/pgLikeSqlDialect.js +1 -0
- package/dist/dialect/queryJoins.d.ts +8 -1
- package/dist/dialect/queryJoins.js +33 -10
- package/dist/entity/decorator/members.d.ts +44 -2
- package/dist/entity/decorator/members.js +0 -5
- package/dist/entity/metadata/definition.d.ts +8 -3
- package/dist/entity/metadata/definition.js +10 -3
- package/dist/migrate/codegen/entityCodeGenerator.js +4 -2
- package/dist/migrate/introspection/postgresIntrospector.d.ts +6 -0
- package/dist/migrate/introspection/postgresIntrospector.js +7 -1
- package/dist/migrate/migrator.d.ts +2 -1
- package/dist/migrate/migrator.js +5 -3
- package/dist/mongo/mongoDialect.d.ts +49 -19
- package/dist/mongo/mongoDialect.js +238 -81
- package/dist/mongo/mongodbQuerier.d.ts +2 -4
- package/dist/mongo/mongodbQuerier.js +16 -14
- package/dist/mssql/mssqlDialect.js +3 -2
- package/dist/postgres/postgresDialect.js +2 -2
- package/dist/querier/abstractQuerier.d.ts +16 -11
- package/dist/querier/abstractQuerier.js +28 -8
- package/dist/querier/abstractQuerierPool.d.ts +9 -9
- package/dist/querier/abstractSqlQuerier.d.ts +1 -1
- package/dist/querier/abstractSqlQuerier.js +3 -3
- package/dist/sqlite/sqliteDialect.js +1 -0
- package/dist/turso/tursoDialect.d.ts +1 -1
- package/dist/turso/tursoDialect.js +6 -2
- package/dist/type/dialect.d.ts +35 -2
- package/dist/type/entity.d.ts +120 -4
- package/dist/type/migration.d.ts +7 -0
- package/dist/type/query.d.ts +7 -10
- package/dist/type/queryAggregate.d.ts +77 -42
- package/dist/type/queryAggregate.js +4 -21
- package/dist/type/queryRaw.d.ts +19 -1
- package/dist/type/queryRaw.js +18 -0
- package/dist/type/universalQuerier.d.ts +9 -9
- package/dist/util/dialect.util.d.ts +6 -2
- package/dist/util/dialect.util.js +21 -7
- package/dist/util/field.util.d.ts +15 -1
- package/dist/util/field.util.js +18 -1
- package/dist/util/object.util.d.ts +1 -4
- package/dist/util/object.util.js +0 -3
- package/dist/util/raw.d.ts +2 -2
- package/dist/util/raw.js +29 -2
- package/package.json +1 -1
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import type { FieldKey } from './entity.js';
|
|
1
|
+
import type { FieldKey, RelationKey, RelationTarget } from './entity.js';
|
|
2
2
|
import type { QueryPager, QuerySelect, QuerySortDirection } from './query.js';
|
|
3
|
+
import type { QueryRaw } from './queryRaw.js';
|
|
3
4
|
import type { QueryWhere, QueryWhereFieldValue } from './queryWhere.js';
|
|
4
|
-
import type { RejectKeys } from './utility.js';
|
|
5
|
+
import type { IsMany, RejectKeys } from './utility.js';
|
|
5
6
|
/** The columns `$group` names by a literal `true`, so an uninferred `$group`, its own constraint, names none. */
|
|
6
7
|
type GroupedKeys<G> = {
|
|
7
8
|
[K in keyof G]: G[K] extends true ? K : never;
|
|
@@ -23,22 +24,14 @@ export type QueryAggregateOp = (typeof QUERY_AGGREGATE_OPS)[number];
|
|
|
23
24
|
*/
|
|
24
25
|
export declare function isQueryAggregateOp(op: string): op is QueryAggregateOp;
|
|
25
26
|
/**
|
|
26
|
-
* DISTINCT
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
* are omitted: DISTINCT is a no-op for them.
|
|
27
|
+
* `$countDistinct` -> `COUNT(DISTINCT col)`. Flat (not a nested `{ $distinct }` argument) so the op is
|
|
28
|
+
* self-documenting and greppable. The other ops have no DISTINCT variant: `$sum`/`$avg` over deduplicated
|
|
29
|
+
* values is rarely what totalling or averaging means, and DISTINCT is a no-op for `$min`/`$max`.
|
|
30
30
|
*/
|
|
31
|
-
|
|
32
|
-
readonly $countDistinct: '$count';
|
|
33
|
-
readonly $sumDistinct: '$sum';
|
|
34
|
-
readonly $avgDistinct: '$avg';
|
|
35
|
-
};
|
|
36
|
-
/** DISTINCT-qualified aggregate operators (the keys of {@link QUERY_AGGREGATE_DISTINCT_OP_BASE}). */
|
|
37
|
-
export type QueryAggregateDistinctOp = keyof typeof QUERY_AGGREGATE_DISTINCT_OP_BASE;
|
|
31
|
+
export type QueryAggregateDistinctOp = '$countDistinct';
|
|
38
32
|
/**
|
|
39
|
-
* Resolve an aggregate op key into its base op and whether it is DISTINCT-qualified
|
|
40
|
-
*
|
|
41
|
-
* (`$min`/`$max` have no distinct variant).
|
|
33
|
+
* Resolve an aggregate op key into its base op and whether it is DISTINCT-qualified: `$countDistinct`
|
|
34
|
+
* resolves to `$count` with `distinct: true`, a plain op to itself with `distinct: false`. Throws otherwise.
|
|
42
35
|
*/
|
|
43
36
|
export declare function resolveAggregateOp(key: string): {
|
|
44
37
|
op: QueryAggregateOp;
|
|
@@ -59,9 +52,8 @@ export type QueryFieldRef<E, F extends keyof E = FieldKey<E>> = ExactlyOne<Requi
|
|
|
59
52
|
/** The argument of an aggregate function: a field, or `'*'` (only meaningful for `COUNT(*)`). */
|
|
60
53
|
export type QueryAggregateArg<E> = QueryFieldRef<E> | '*';
|
|
61
54
|
/**
|
|
62
|
-
* Fields `SUM`/`AVG` can total. Restricted to numeric columns because
|
|
63
|
-
*
|
|
64
|
-
* produces the value the signature promises.
|
|
55
|
+
* Fields `SUM`/`AVG` can total. Restricted to numeric columns because totalling a text or date one is
|
|
56
|
+
* either an engine error or a coercion, and neither produces the value the signature promises.
|
|
65
57
|
*/
|
|
66
58
|
type NumericFieldKey<E> = {
|
|
67
59
|
readonly [K in FieldKey<E>]: [NonNullable<E[K]>] extends [number | bigint] ? K : never;
|
|
@@ -74,8 +66,12 @@ type AggregateOp = QueryAggregateOp | QueryAggregateDistinctOp;
|
|
|
74
66
|
* where `Extract` would quietly drop the renamed member and leave the subset wrong but valid.
|
|
75
67
|
*/
|
|
76
68
|
type OpsOf<K extends AggregateOp> = K;
|
|
69
|
+
/** Ops that add a column up, which keeps the column's own type: a `bigint` column totals to a `bigint`. */
|
|
70
|
+
type SummingOp = OpsOf<'$sum'>;
|
|
71
|
+
/** Ops that mean a column, which the engine floats, so the result is a `number` however wide the column. */
|
|
72
|
+
type AveragingOp = OpsOf<'$avg'>;
|
|
77
73
|
/** Ops that total a column, so their argument has to be numeric. */
|
|
78
|
-
type TotallingOp =
|
|
74
|
+
type TotallingOp = SummingOp | AveragingOp;
|
|
79
75
|
/**
|
|
80
76
|
* Every aggregate op mapped to the argument it accepts: `$count` a field or `'*'` (`COUNT(*)`),
|
|
81
77
|
* the totalling ops a numeric field, `$min`/`$max`/`$countDistinct` any field.
|
|
@@ -83,19 +79,60 @@ type TotallingOp = OpsOf<'$sum' | '$avg' | '$sumDistinct' | '$avgDistinct'>;
|
|
|
83
79
|
type QueryAggregateArgMap<E> = Record<'$count', QueryAggregateArg<E>> & Record<TotallingOp, QueryFieldRef<E, NumericFieldKey<E>>> & Record<Exclude<AggregateOp, '$count' | TotallingOp>, QueryFieldRef<E>>;
|
|
84
80
|
/**
|
|
85
81
|
* An aggregate over one field, exactly one op per entry: `{ $sum: { amount: true } }` is `SUM("amount")`,
|
|
86
|
-
* `{ $countDistinct: { id: true } }` is `COUNT(DISTINCT "id")`, and only `$count` takes `'*'`.
|
|
82
|
+
* `{ $countDistinct: { id: true } }` is `COUNT(DISTINCT "id")`, and only `$count` takes `'*'`. Its own
|
|
83
|
+
* `$where` narrows the rows it reads to those matching, over the entity's own fields.
|
|
87
84
|
*/
|
|
88
|
-
export type QueryAggregateFn<E> = ExactlyOne<QueryAggregateArgMap<E
|
|
89
|
-
|
|
90
|
-
|
|
85
|
+
export type QueryAggregateFn<E> = ExactlyOne<QueryAggregateArgMap<E>> & {
|
|
86
|
+
readonly $where?: QueryAggregateWhere<E>;
|
|
87
|
+
};
|
|
88
|
+
/** What an aggregate's own `$where` reads: the entity's fields, since a relation there is a subquery inside it. */
|
|
89
|
+
type QueryAggregateWhere<E> = QueryWhere<E, QueryRaw, FieldKey<E>>;
|
|
90
|
+
/** An aggregate's `$where` naming a key it does not have, refused: a captured `$select` skips the excess-property check. */
|
|
91
|
+
type AggregateWhereKeys<E, Fn> = Fn extends {
|
|
92
|
+
readonly $where: infer W;
|
|
93
|
+
} ? {
|
|
94
|
+
readonly $where: RejectKeys<Exclude<keyof W, keyof QueryAggregateWhere<E>>>;
|
|
95
|
+
} : unknown;
|
|
96
|
+
/** A single-key `{ [op]: Arg }` shape for each op in `Ops`, matched to infer that op's result or its argument. */
|
|
97
|
+
type FnWithOp<Ops extends string, Arg = unknown> = {
|
|
91
98
|
[K in Ops]: {
|
|
92
|
-
readonly [P in K]:
|
|
99
|
+
readonly [P in K]: Arg;
|
|
93
100
|
};
|
|
94
101
|
}[Ops];
|
|
95
102
|
/** Ops that count rows. Alone among the ops they answer `0`, never NULL, over an empty group. */
|
|
96
103
|
type CountingOp = OpsOf<'$count' | '$countDistinct'>;
|
|
97
|
-
/**
|
|
98
|
-
|
|
104
|
+
/** Ops that read back as the column they aggregate, rather than widening or floating it. */
|
|
105
|
+
type ColumnTypedOp = SummingOp | OpsOf<'$min' | '$max'>;
|
|
106
|
+
/**
|
|
107
|
+
* A field a group key reads, by the path to it: `{ orderId: true }`, or through a to-one relation,
|
|
108
|
+
* `{ transaction: { orderId: true } }`. A to-many is `never`, since joining one multiplies the rows.
|
|
109
|
+
*/
|
|
110
|
+
export type QueryGroupRef<E, K extends keyof E = FieldKey<E> | RelationKey<E>> = ExactlyOne<Required<{
|
|
111
|
+
[P in K]: P extends RelationKey<E> ? IsMany<E[P]> extends true ? never : QueryGroupRef<RelationTarget<E[P]>> : true;
|
|
112
|
+
}>>;
|
|
113
|
+
/** The columns to group by: a field switched on, `{ status: true }`, or an alias for a field a path reads. */
|
|
114
|
+
export type QueryGroupMap<E> = {
|
|
115
|
+
readonly [key: string]: true | QueryGroupRef<E>;
|
|
116
|
+
};
|
|
117
|
+
/**
|
|
118
|
+
* A captured `$group` against its schema: a field key takes `true` and keeps its link, any other key is an
|
|
119
|
+
* alias for a path. A key switched on that is no field is refused by name, which the intersection alone lets by.
|
|
120
|
+
*/
|
|
121
|
+
type QueryGroupSchema<E, G> = Readonly<QuerySelect<E, FieldKey<E>, true>> & {
|
|
122
|
+
readonly [K in Exclude<NamedKeys<G>, FieldKey<E>>]: QueryGroupRef<E>;
|
|
123
|
+
} & RejectKeys<Exclude<GroupedKeys<G>, FieldKey<E>>>;
|
|
124
|
+
/**
|
|
125
|
+
* The value a path reads: the field's own type, or through a relation, `null` where the row points nowhere.
|
|
126
|
+
* `any` answers `unknown`: TypeScript checks a deferred type by instantiating it with `any`, which would
|
|
127
|
+
* walk every relation of the entity on every aggregate call, quadrupling what one costs to check.
|
|
128
|
+
*/
|
|
129
|
+
type GroupRefValue<E, Ref> = 0 extends 1 & Ref ? unknown : {
|
|
130
|
+
[K in keyof Ref & keyof E]: Ref[K] extends true ? E[K] : NonNullable<GroupRefLeaf<RelationTarget<E[K]>, Ref[K]>> | null;
|
|
131
|
+
}[keyof Ref & keyof E];
|
|
132
|
+
/** The field at the end of a path, whose type {@link GroupRefValue} widens with `null`; `any` as it does. */
|
|
133
|
+
type GroupRefLeaf<E, Ref> = 0 extends 1 & Ref ? unknown : {
|
|
134
|
+
[K in keyof Ref & keyof E]: Ref[K] extends true ? E[K] : GroupRefLeaf<RelationTarget<E[K]>, Ref[K]>;
|
|
135
|
+
}[keyof Ref & keyof E];
|
|
99
136
|
/** Computed columns by the alias each is read back under: `{ count: { $count: '*' }, avgAge: { $avg: { age: true } } }`. */
|
|
100
137
|
export type QueryAggMap<E> = {
|
|
101
138
|
readonly [alias: string]: QueryAggregateFn<E>;
|
|
@@ -103,14 +140,11 @@ export type QueryAggMap<E> = {
|
|
|
103
140
|
/** The entity type of an aggregated field reference `F`, or `unknown` if it is not a known field. */
|
|
104
141
|
type FieldValueType<E, F> = F extends keyof E ? E[F] : unknown;
|
|
105
142
|
/**
|
|
106
|
-
* A computed column's type: a count is a `number`; every other aggregate is `null` over no rows, a
|
|
107
|
-
*
|
|
143
|
+
* A computed column's type: a count is a `number`; every other aggregate is `null` over no rows, a mean
|
|
144
|
+
* a `number` whatever it read, and a total, a `$min` or a `$max` the column's own type - so a `bigint`
|
|
145
|
+
* column totals to a `bigint`, which is what the driver decodes rather than rounding through a float.
|
|
108
146
|
*/
|
|
109
|
-
type QueryAggregateFnResult<E, Fn> = Fn extends FnWithOp<CountingOp> ? number : Fn extends FnWithOp<
|
|
110
|
-
readonly $min: infer F;
|
|
111
|
-
} | {
|
|
112
|
-
readonly $max: infer F;
|
|
113
|
-
} ? FieldValueType<E, keyof F> | null : unknown;
|
|
147
|
+
type QueryAggregateFnResult<E, Fn> = Fn extends FnWithOp<CountingOp> ? number : Fn extends FnWithOp<AveragingOp> ? number | null : Fn extends FnWithOp<ColumnTypedOp, infer F> ? FieldValueType<E, keyof F> | null : unknown;
|
|
114
148
|
/**
|
|
115
149
|
* Flattens an intersection into a single object literal for readable editor hovers.
|
|
116
150
|
* @internal
|
|
@@ -118,8 +152,10 @@ type QueryAggregateFnResult<E, Fn> = Fn extends FnWithOp<CountingOp> ? number :
|
|
|
118
152
|
type Simplify<T> = {
|
|
119
153
|
[K in keyof T]: T[K];
|
|
120
154
|
} & {};
|
|
121
|
-
/** An aggregate's row: each grouped column with its entity type, each computed one with its aggregate's. */
|
|
155
|
+
/** An aggregate's row: each grouped column with its entity type, each alias with its path's, each computed one with its aggregate's. */
|
|
122
156
|
export type QueryAggregateResult<E, G, A> = Simplify<Pick<E, GroupedKeys<G> & FieldKey<E>> & {
|
|
157
|
+
-readonly [K in Exclude<NamedKeys<G>, FieldKey<E>>]: GroupRefValue<E, G[K]>;
|
|
158
|
+
} & {
|
|
123
159
|
-readonly [K in keyof A]: QueryAggregateFnResult<E, A[K]>;
|
|
124
160
|
}>;
|
|
125
161
|
/** A `HAVING` as the dialects read it, erased; {@link QueryAggregate.$having} is where it is typed. `{ count: { $gt: 5 } }` */
|
|
@@ -136,19 +172,18 @@ export type QueryAggregate<E, G extends QueryGroupMap<E> = QueryGroupMap<E>, A e
|
|
|
136
172
|
*/
|
|
137
173
|
readonly $where?: QueryWhere<E>;
|
|
138
174
|
/**
|
|
139
|
-
* Columns to group by
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
* map meets its schema, {@link QueryGroupMap}, so each key keeps its link to the entity property.
|
|
175
|
+
* Columns to group by: `{ status: true }`, or an alias for a to-one relation's field by the path to
|
|
176
|
+
* it, `{ orderId: { transaction: { orderId: true } } }`. The captured map meets its schema, so a field
|
|
177
|
+
* key keeps its link to the entity property, and a typo matches neither form.
|
|
143
178
|
*/
|
|
144
|
-
readonly $group?: G &
|
|
179
|
+
readonly $group?: G & QueryGroupSchema<E, G>;
|
|
145
180
|
/**
|
|
146
181
|
* The computed columns by alias, the captured map meeting its schema so field keys stay linked. An alias
|
|
147
182
|
* repeating a `$group` column is refused, since both would come back under one name.
|
|
148
183
|
*/
|
|
149
184
|
readonly $select?: A & {
|
|
150
|
-
readonly [K in keyof A]: QueryAggregateFn<E>;
|
|
151
|
-
} & RejectKeys<NamedKeys<A> &
|
|
185
|
+
readonly [K in keyof A]: QueryAggregateFn<E> & AggregateWhereKeys<E, A[K]>;
|
|
186
|
+
} & RejectKeys<NamedKeys<A> & NamedKeys<G>>;
|
|
152
187
|
/** Filtering after grouping, by a result column, each value typed as that column is. */
|
|
153
188
|
readonly $having?: {
|
|
154
189
|
readonly [K in keyof QueryAggregateResult<E, G, A>]?: QueryWhereFieldValue<QueryAggregateResult<E, G, A>[K]>;
|
|
@@ -7,29 +7,12 @@ export function isQueryAggregateOp(op) {
|
|
|
7
7
|
return QUERY_AGGREGATE_OPS.includes(op);
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* (not a nested `{ $distinct }` argument) so the op is self-documenting and greppable. `$min`/`$max`
|
|
13
|
-
* are omitted: DISTINCT is a no-op for them.
|
|
14
|
-
*/
|
|
15
|
-
const QUERY_AGGREGATE_DISTINCT_OP_BASE = {
|
|
16
|
-
$countDistinct: '$count',
|
|
17
|
-
$sumDistinct: '$sum',
|
|
18
|
-
$avgDistinct: '$avg',
|
|
19
|
-
};
|
|
20
|
-
/** Whether `key` is a DISTINCT-qualified aggregate operator (narrows for a cast-free base lookup). */
|
|
21
|
-
function isQueryAggregateDistinctOp(key) {
|
|
22
|
-
// `Object.hasOwn`, not `key in`: the latter matches inherited members like `toString`.
|
|
23
|
-
return Object.hasOwn(QUERY_AGGREGATE_DISTINCT_OP_BASE, key);
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* Resolve an aggregate op key into its base op and whether it is DISTINCT-qualified. A flat distinct
|
|
27
|
-
* op resolves to its base op with `distinct: true`; a plain op to `distinct: false`. Throws otherwise
|
|
28
|
-
* (`$min`/`$max` have no distinct variant).
|
|
10
|
+
* Resolve an aggregate op key into its base op and whether it is DISTINCT-qualified: `$countDistinct`
|
|
11
|
+
* resolves to `$count` with `distinct: true`, a plain op to itself with `distinct: false`. Throws otherwise.
|
|
29
12
|
*/
|
|
30
13
|
export function resolveAggregateOp(key) {
|
|
31
|
-
if (
|
|
32
|
-
return { op:
|
|
14
|
+
if (key === '$countDistinct') {
|
|
15
|
+
return { op: '$count', distinct: true };
|
|
33
16
|
}
|
|
34
17
|
if (isQueryAggregateOp(key)) {
|
|
35
18
|
return { op: key, distinct: false };
|
package/dist/type/queryRaw.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { QueryContext, SqlQueryDialect } from './dialect.js';
|
|
1
|
+
import type { QueryContext, RelationAggregateSpec, SqlQueryDialect } from './dialect.js';
|
|
2
2
|
import type { Type } from './utility.js';
|
|
3
3
|
/** What a `raw` callback receives. See {@link QueryRawFn}. */
|
|
4
4
|
export type QueryRawRenderOptions = {
|
|
@@ -44,3 +44,21 @@ export declare class ColumnRef<K extends string = string> extends QueryRaw {
|
|
|
44
44
|
readonly key: K;
|
|
45
45
|
constructor(key: K, value: QueryRawFn);
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* A relation aggregate as SQL, read off a `computed` field's refs: `(user) => user.resources.count()`.
|
|
49
|
+
* It renders as the correlated subquery a `$count` reads, so a field holding one is read, filtered and
|
|
50
|
+
* sorted like any other.
|
|
51
|
+
*
|
|
52
|
+
* `V` is the value it reads and `Storable` whether a trigger could keep it, both carried in phantom
|
|
53
|
+
* fields so the aggregate a field declares decides the property's type and refuses `stored: true` on
|
|
54
|
+
* one no delta can maintain.
|
|
55
|
+
*/
|
|
56
|
+
export declare class RelationAggregate<V = unknown, Storable extends boolean = boolean> extends QueryRaw {
|
|
57
|
+
/** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
|
|
58
|
+
readonly spec: RelationAggregateSpec;
|
|
59
|
+
private readonly __value;
|
|
60
|
+
private readonly __storable;
|
|
61
|
+
constructor(
|
|
62
|
+
/** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
|
|
63
|
+
spec: RelationAggregateSpec, value: QueryRawFn);
|
|
64
|
+
}
|
package/dist/type/queryRaw.js
CHANGED
|
@@ -30,3 +30,21 @@ export class ColumnRef extends QueryRaw {
|
|
|
30
30
|
this.key = key;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* A relation aggregate as SQL, read off a `computed` field's refs: `(user) => user.resources.count()`.
|
|
35
|
+
* It renders as the correlated subquery a `$count` reads, so a field holding one is read, filtered and
|
|
36
|
+
* sorted like any other.
|
|
37
|
+
*
|
|
38
|
+
* `V` is the value it reads and `Storable` whether a trigger could keep it, both carried in phantom
|
|
39
|
+
* fields so the aggregate a field declares decides the property's type and refuses `stored: true` on
|
|
40
|
+
* one no delta can maintain.
|
|
41
|
+
*/
|
|
42
|
+
export class RelationAggregate extends QueryRaw {
|
|
43
|
+
spec;
|
|
44
|
+
constructor(
|
|
45
|
+
/** What it reads, kept beside the SQL so a read decodes the value the way the target's field does. */
|
|
46
|
+
spec, value) {
|
|
47
|
+
super(value);
|
|
48
|
+
this.spec = spec;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { EntityId, EntityWrite, FieldKey, RelationKey, UpdateWrite, WrittenId } from './entity.js';
|
|
2
2
|
import type { QueryConflictPaths, QueryFilter, QueryFindResult, QueryOneProjected, QueryOptions, QueryPage, QueryProjected, QuerySearch, QueryUpsertOneResult, QueryUpsertManyResult } from './query.js';
|
|
3
3
|
import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
|
|
4
4
|
import type { Type } from './utility.js';
|
|
@@ -31,9 +31,9 @@ export interface SharedQuerier<W extends QuerierTransport, O, DO = O> {
|
|
|
31
31
|
/** Whether any record matches: a count capped at one row, so the engine stops at the first match. */
|
|
32
32
|
exists<E extends object>(entity: Type<E>, q?: QueryFilter<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, boolean>;
|
|
33
33
|
/** Update the record with the given primary key; resolves to the number of affected rows. */
|
|
34
|
-
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload:
|
|
34
|
+
updateOneById<E extends object>(entity: Type<E>, id: EntityId<E>, payload: UpdateWrite<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, number>;
|
|
35
35
|
/** Update the records matching the query; resolves to the number of affected rows. */
|
|
36
|
-
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E, QuerierRaw<W>>, payload:
|
|
36
|
+
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E, QuerierRaw<W>>, payload: UpdateWrite<E, QuerierRaw<W>>, opts?: O): QuerierResult<W, number>;
|
|
37
37
|
/**
|
|
38
38
|
* delete or SoftDelete a record.
|
|
39
39
|
* @param entity the entity to persist on
|
|
@@ -59,31 +59,31 @@ export interface UniversalQuerier extends SharedQuerier<'server', QueryOptions>
|
|
|
59
59
|
*/
|
|
60
60
|
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, const C extends RelationKey<E> = never>(entity: Type<E>, q: QueryProjected<E, S, V, X, P, C>, opts?: QueryOptions): AsyncIterable<QueryFindResult<E, S, V, X, P, C>>;
|
|
61
61
|
/** Insert a record and resolve to its id. See {@link UniversalQuerier.insertMany}. */
|
|
62
|
-
insertOne<E extends object>(entity: Type<E>, payload:
|
|
62
|
+
insertOne<E extends object>(entity: Type<E>, payload: EntityWrite<E>): Promise<WrittenId<E> | undefined>;
|
|
63
63
|
/**
|
|
64
64
|
* Insert records in as few statements as the bind limit allows, resolving to their ids in payload order.
|
|
65
65
|
* Ids are exact everywhere but MySQL, which infers them from its header and reports `undefined` rather
|
|
66
66
|
* than a guess where it cannot: a batch naming some keys, or a key that is not `AUTO_INCREMENT`.
|
|
67
67
|
*/
|
|
68
|
-
insertMany<E extends object>(entity: Type<E>, payload:
|
|
68
|
+
insertMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
69
69
|
/** Insert or update a record by its conflict paths; resolves to its id and whether it was created. */
|
|
70
|
-
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload:
|
|
70
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>): Promise<QueryUpsertOneResult<E>>;
|
|
71
71
|
/** Insert or update records by their conflict paths; resolves to their ids in payload order. */
|
|
72
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload:
|
|
72
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityWrite<E>[]): Promise<QueryUpsertManyResult<E>>;
|
|
73
73
|
/**
|
|
74
74
|
* insert or update a record.
|
|
75
75
|
* @param entity the entity to persist on
|
|
76
76
|
* @param payload the data to be persisted
|
|
77
77
|
* @return the ID
|
|
78
78
|
*/
|
|
79
|
-
saveOne<E extends object>(entity: Type<E>, payload:
|
|
79
|
+
saveOne<E extends object>(entity: Type<E>, payload: EntityWrite<E>): Promise<WrittenId<E> | undefined>;
|
|
80
80
|
/**
|
|
81
81
|
* Insert or update records.
|
|
82
82
|
* @param entity the entity to persist on
|
|
83
83
|
* @param payload the data to be persisted
|
|
84
84
|
* @return the IDs
|
|
85
85
|
*/
|
|
86
|
-
saveMany<E extends object>(entity: Type<E>, payload:
|
|
86
|
+
saveMany<E extends object>(entity: Type<E>, payload: EntityWrite<E>[]): Promise<(WrittenId<E> | undefined)[]>;
|
|
87
87
|
/**
|
|
88
88
|
* Restore soft-deleted records (sets the soft-delete field back to `null`). Throws if the
|
|
89
89
|
* entity has no soft-delete field.
|
|
@@ -104,9 +104,11 @@ export declare function applyFilters<E>(meta: EntityMeta<E>, whereMap: QueryWher
|
|
|
104
104
|
/**
|
|
105
105
|
* Parsed entry from a `$group` map - either a raw group key or an aggregate function call.
|
|
106
106
|
*/
|
|
107
|
-
export type ParsedGroupEntry = {
|
|
107
|
+
export type ParsedGroupEntry<E = object> = {
|
|
108
108
|
readonly kind: 'key';
|
|
109
109
|
readonly alias: string;
|
|
110
|
+
/** The field it reads, behind the to-one relations leading to it: `['transaction', 'orderId']`. */
|
|
111
|
+
readonly path: readonly string[];
|
|
110
112
|
} | {
|
|
111
113
|
readonly kind: 'fn';
|
|
112
114
|
readonly alias: string;
|
|
@@ -114,6 +116,8 @@ export type ParsedGroupEntry = {
|
|
|
114
116
|
readonly fieldRef: string;
|
|
115
117
|
/** `true` for a flat distinct op (`$countDistinct`, ...) -> `COUNT(DISTINCT field)`. */
|
|
116
118
|
readonly distinct: boolean;
|
|
119
|
+
/** The rows it reads, where not all of the statement's. */
|
|
120
|
+
readonly where?: QueryWhere<E>;
|
|
117
121
|
};
|
|
118
122
|
/**
|
|
119
123
|
* The `$size` of a relation condition, `{ comments: { $size: { $gte: 2 } } }`, or `undefined` where it
|
|
@@ -130,7 +134,7 @@ export declare function parseSortByCount(val: unknown): unknown;
|
|
|
130
134
|
* Parse the `$group` (grouped columns) and `$select` (computed aggregates) maps into structured
|
|
131
135
|
* entries consumable by any dialect. Grouped columns come first, then computed columns.
|
|
132
136
|
*/
|
|
133
|
-
export declare function parseGroupMap<E>(group?: QueryGroupMap<E>, select?: QueryAggMap<E>): ParsedGroupEntry[];
|
|
137
|
+
export declare function parseGroupMap<E>(group?: QueryGroupMap<E>, select?: QueryAggMap<E>): ParsedGroupEntry<E>[];
|
|
134
138
|
/**
|
|
135
139
|
* Whether `value` is a map of comparison operators rather than a value to compare against. Only a
|
|
136
140
|
* plain object qualifies: `Date`, `QueryRaw`, `Uint8Array` and arrays are all `typeof 'object'`, and
|
|
@@ -2,8 +2,8 @@ import { getContext, UqlSecurityError } from '../context/context.js';
|
|
|
2
2
|
import { soleIdOf } from '../entity/metadata/definition.js';
|
|
3
3
|
import { QueryRaw, resolveAggregateOp, SOFT_DELETE_FILTER, } from '../type/index.js';
|
|
4
4
|
import { VECTOR_INDEX_TYPES } from '../type/vector.js';
|
|
5
|
-
import { isDatabaseWritten } from './field.util.js';
|
|
6
|
-
import { entityName,
|
|
5
|
+
import { getFieldKeys, isDatabaseWritten } from './field.util.js';
|
|
6
|
+
import { entityName, getKeys, hasKeys, isOperatorObject, isScalarId, isRecord, isWhereMap, someKey, } from './object.util.js';
|
|
7
7
|
/** The keys of `payload` a write persists as columns. */
|
|
8
8
|
export function filterFieldKeys(meta, payload, callbackKey) {
|
|
9
9
|
return getKeys(payload).filter((key) => {
|
|
@@ -344,22 +344,36 @@ export function parseGroupMap(group, select) {
|
|
|
344
344
|
const entries = [];
|
|
345
345
|
const groupMap = group ?? {};
|
|
346
346
|
for (const alias of getKeys(groupMap)) {
|
|
347
|
-
|
|
348
|
-
|
|
347
|
+
const ref = groupMap[alias];
|
|
348
|
+
if (ref) {
|
|
349
|
+
entries.push({ kind: 'key', alias, path: ref === true ? [alias] : groupRefPath(alias, ref) });
|
|
349
350
|
}
|
|
350
351
|
}
|
|
351
352
|
if (!select) {
|
|
352
353
|
return entries;
|
|
353
354
|
}
|
|
354
355
|
for (const alias of getKeys(select)) {
|
|
355
|
-
const
|
|
356
|
-
const
|
|
356
|
+
const { $where: where } = select[alias];
|
|
357
|
+
const call = select[alias];
|
|
358
|
+
const key = getKeys(call).find((name) => name !== '$where');
|
|
359
|
+
if (key === undefined) {
|
|
360
|
+
throw new TypeError(`aggregate '${alias}' names no op, only a $where`);
|
|
361
|
+
}
|
|
357
362
|
// Flat DISTINCT ops (`$countDistinct`, ...) normalize to their base op + a `distinct` flag.
|
|
358
363
|
const { op, distinct } = resolveAggregateOp(key);
|
|
359
|
-
|
|
364
|
+
const fieldRef = aggregateFieldRef(alias, call[key]);
|
|
365
|
+
entries.push({ kind: 'fn', alias, op, fieldRef, distinct, ...(where && hasKeys(where) ? { where } : {}) });
|
|
360
366
|
}
|
|
361
367
|
return entries;
|
|
362
368
|
}
|
|
369
|
+
/** The path a group key's `{ transaction: { orderId: true } }` names, one key at each level. */
|
|
370
|
+
function groupRefPath(alias, ref) {
|
|
371
|
+
const [key, ...rest] = isRecord(ref) ? getKeys(ref) : [];
|
|
372
|
+
if (!isRecord(ref) || key === undefined || rest.length) {
|
|
373
|
+
throw new TypeError(`$group '${alias}' names one field by the path to it: got ${JSON.stringify(ref)}`);
|
|
374
|
+
}
|
|
375
|
+
return ref[key] === true ? [key] : [key, ...groupRefPath(alias, ref[key])];
|
|
376
|
+
}
|
|
363
377
|
/** The column an aggregate reads: `'*'`, or the one field its `{ field: true }` names. */
|
|
364
378
|
function aggregateFieldRef(alias, arg) {
|
|
365
379
|
const [field, ...rest] = arg === '*' ? [arg] : namedKeys(arg);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ColumnFamily, type EntityMeta, type FieldOptions } from '../type/index.js';
|
|
1
|
+
import { type ColumnFamily, type EntityMeta, type FieldKey, type FieldOptions, type RelationAggregateSpec } from '../type/index.js';
|
|
2
2
|
/** The family of a logical field type, or `undefined` where it names none. */
|
|
3
3
|
export declare function columnFamily(type: unknown): ColumnFamily | undefined;
|
|
4
4
|
/**
|
|
@@ -12,6 +12,12 @@ export declare function isIntegerColumn(field: Pick<FieldOptions, 'type' | 'colu
|
|
|
12
12
|
* because an inlined field has no column to name, while a stored one is read like any other.
|
|
13
13
|
*/
|
|
14
14
|
export declare function isInlinedExpression<F extends FieldOptions>(field: F): field is F & Required<Pick<F, 'computed'>>;
|
|
15
|
+
/**
|
|
16
|
+
* The relation aggregate a field computes, where it computes one rather than writing SQL: what it
|
|
17
|
+
* reads, off which relation, narrowed and capped how. Every engine renders it from this - a correlated
|
|
18
|
+
* subquery on SQL, a lookup on MongoDB - so both read the same declaration rather than parsing SQL.
|
|
19
|
+
*/
|
|
20
|
+
export declare function aggregateOf(field: FieldOptions | undefined): RelationAggregateSpec | undefined;
|
|
15
21
|
/**
|
|
16
22
|
* Whether the database supplies this field's value, so no insert or update may write it: a stored
|
|
17
23
|
* computed column *is* a real column, read like one, but writing to it is an error on every engine.
|
|
@@ -25,3 +31,11 @@ export declare function isSoleIdField<E>(meta: EntityMeta<E>, field: FieldOption
|
|
|
25
31
|
* states its width. The one answer the create statement and the diff both read.
|
|
26
32
|
*/
|
|
27
33
|
export declare function isAutoIncrement(field: FieldOptions, isPrimaryKey: boolean): boolean;
|
|
34
|
+
/**
|
|
35
|
+
* The fields a read answers with where it names none. A relation aggregate is left out unless it asks
|
|
36
|
+
* for `eager: true`: it reads the related rows, which is what a relation does, and a relation is loaded
|
|
37
|
+
* only when a query asks for it. Naming one in `$select` reads it, whatever the default.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getFieldKeys<E>(fields: {
|
|
40
|
+
[K in FieldKey<E>]?: FieldOptions;
|
|
41
|
+
}): FieldKey<E>[];
|
package/dist/util/field.util.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { COLUMN_TYPES, } from '../type/index.js';
|
|
1
|
+
import { COLUMN_TYPES, RelationAggregate, } from '../type/index.js';
|
|
2
2
|
import { getKeys } from './object.util.js';
|
|
3
3
|
// Constructors and type strings in one map: a logical type is either, and every caller asks the same
|
|
4
4
|
// question of both.
|
|
@@ -41,6 +41,15 @@ export function isIntegerColumn(field) {
|
|
|
41
41
|
export function isInlinedExpression(field) {
|
|
42
42
|
return field.computed !== undefined && field.stored !== true;
|
|
43
43
|
}
|
|
44
|
+
/**
|
|
45
|
+
* The relation aggregate a field computes, where it computes one rather than writing SQL: what it
|
|
46
|
+
* reads, off which relation, narrowed and capped how. Every engine renders it from this - a correlated
|
|
47
|
+
* subquery on SQL, a lookup on MongoDB - so both read the same declaration rather than parsing SQL.
|
|
48
|
+
*/
|
|
49
|
+
export function aggregateOf(field) {
|
|
50
|
+
const computed = field?.computed;
|
|
51
|
+
return computed instanceof RelationAggregate ? computed.spec : undefined;
|
|
52
|
+
}
|
|
44
53
|
/**
|
|
45
54
|
* Whether the database supplies this field's value, so no insert or update may write it: a stored
|
|
46
55
|
* computed column *is* a real column, read like one, but writing to it is an error on every engine.
|
|
@@ -62,3 +71,11 @@ export function isAutoIncrement(field, isPrimaryKey) {
|
|
|
62
71
|
return field.autoIncrement;
|
|
63
72
|
return isPrimaryKey && columnFamily(field.type) === 'numeric' && !field.onInsert && !field.references;
|
|
64
73
|
}
|
|
74
|
+
/**
|
|
75
|
+
* The fields a read answers with where it names none. A relation aggregate is left out unless it asks
|
|
76
|
+
* for `eager: true`: it reads the related rows, which is what a relation does, and a relation is loaded
|
|
77
|
+
* only when a query asks for it. Naming one in `$select` reads it, whatever the default.
|
|
78
|
+
*/
|
|
79
|
+
export function getFieldKeys(fields) {
|
|
80
|
+
return getKeys(fields).filter((field) => fields[field].eager ?? !aggregateOf(fields[field]));
|
|
81
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { EntityMeta
|
|
1
|
+
import type { EntityMeta } from '../type/index.js';
|
|
2
2
|
export declare function throwPendingTransaction(): never;
|
|
3
3
|
export declare function throwNoPendingTransaction(): never;
|
|
4
4
|
export declare function clone<T>(value: T): T;
|
|
@@ -24,9 +24,6 @@ export declare function definedEntries<K extends string, V>(record: Partial<Reco
|
|
|
24
24
|
* out reached for three different fallbacks, `?? ''` among them, and named nothing at all.
|
|
25
25
|
*/
|
|
26
26
|
export declare function entityName<E>(meta: EntityMeta<E>): string;
|
|
27
|
-
export declare function getFieldKeys<E>(fields: {
|
|
28
|
-
[K in FieldKey<E>]?: FieldOptions;
|
|
29
|
-
}): FieldKey<E>[];
|
|
30
27
|
/**
|
|
31
28
|
* Whether `value` addresses a row by itself rather than naming columns: every primitive, and the
|
|
32
29
|
* object ids a driver deals in (`ObjectId`, `Date`, bytes). Only a plain object names columns, which
|
package/dist/util/object.util.js
CHANGED
|
@@ -59,9 +59,6 @@ export function definedEntries(record) {
|
|
|
59
59
|
export function entityName(meta) {
|
|
60
60
|
return meta.name ?? meta.entity.name;
|
|
61
61
|
}
|
|
62
|
-
export function getFieldKeys(fields) {
|
|
63
|
-
return getKeys(fields).filter((field) => fields[field].eager ?? true);
|
|
64
|
-
}
|
|
65
62
|
/**
|
|
66
63
|
* Whether `value` addresses a row by itself rather than naming columns: every primitive, and the
|
|
67
64
|
* object ids a driver deals in (`ObjectId`, `Date`, bytes). Only a plain object names columns, which
|
package/dist/util/raw.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type EntitySql, type EntityWhere, type EntityWhereMeta, QueryRaw, type QueryRawFn, type RefMap, type Type } from '../type/index.js';
|
|
1
|
+
import { type EntitySql, type EntityWhere, type EntityWhereMeta, QueryRaw, type QueryRawFn, type ComputedRefs, type RefMap, type Type } from '../type/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* Raw SQL, where an interpolated value binds, a `refs` field renders its column, and a `raw` renders
|
|
4
4
|
* in place: `raw`GREATEST(0, ${user.credits} - ${amount})``. A callback writes whatever it writes, so
|
|
@@ -16,7 +16,7 @@ export declare function refs<E>(entity: Type<E>): RefMap<E>;
|
|
|
16
16
|
* The refs a definition's callbacks read: an index's, a check's, a computed field's. A member decorator
|
|
17
17
|
* sees no class, so these name no entity and resolve against the one rendering them.
|
|
18
18
|
*/
|
|
19
|
-
export declare function memberRefs<E>():
|
|
19
|
+
export declare function memberRefs<E>(): ComputedRefs<E>;
|
|
20
20
|
/** SQL a definition writes, a callback's refs read off {@link memberRefs}. */
|
|
21
21
|
export declare function entitySql<E>(sql: EntitySql<E>): QueryRaw;
|
|
22
22
|
/** A definition's predicate, its callback resolved the way {@link entitySql} resolves one. */
|
package/dist/util/raw.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { getMeta } from '../entity/metadata/definition.js';
|
|
2
|
-
import { ColumnRef, QueryRaw, } from '../type/index.js';
|
|
2
|
+
import { ColumnRef, QueryRaw, RelationAggregate, } from '../type/index.js';
|
|
3
3
|
import { isInlinedExpression } from './field.util.js';
|
|
4
4
|
export function raw(value, ...rest) {
|
|
5
5
|
if (!isTemplateStrings(value)) {
|
|
@@ -27,7 +27,7 @@ export function raw(value, ...rest) {
|
|
|
27
27
|
export function refs(entity) {
|
|
28
28
|
return new Proxy({}, { get: (_, key) => columnRef(entity, String(key)) });
|
|
29
29
|
}
|
|
30
|
-
const MEMBER_REFS = new Proxy({}, { get: (_, key) =>
|
|
30
|
+
const MEMBER_REFS = new Proxy({}, { get: (_, key) => memberRef(String(key)) });
|
|
31
31
|
/**
|
|
32
32
|
* The refs a definition's callbacks read: an index's, a check's, a computed field's. A member decorator
|
|
33
33
|
* sees no class, so these name no entity and resolve against the one rendering them.
|
|
@@ -43,6 +43,33 @@ export function entitySql(sql) {
|
|
|
43
43
|
export function entityWhere(where) {
|
|
44
44
|
return typeof where === 'function' ? where(memberRefs()) : where;
|
|
45
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* One member as a definition reads it: a {@link ColumnRef} where it names a field, and the same object
|
|
48
|
+
* answering `count`, `sum`, `min`, `max` and `avg` where it names a to-many. One runtime object, since
|
|
49
|
+
* a member decorator sees no class and so cannot know which the key is; the types keep them apart.
|
|
50
|
+
*/
|
|
51
|
+
function memberRef(relation) {
|
|
52
|
+
const over = (op) => (pick, q) => relationAggregate({ relation, op, field: pick(memberRefs()).key, ...(q && { query: q }) });
|
|
53
|
+
return Object.assign(columnRef(undefined, relation), {
|
|
54
|
+
count: (q) => relationAggregate({ relation, op: '$count', ...(q && { query: q }) }),
|
|
55
|
+
sum: over('$sum'),
|
|
56
|
+
min: over('$min'),
|
|
57
|
+
max: over('$max'),
|
|
58
|
+
avg: over('$avg'),
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* A relation aggregate as SQL: the dialect writes the same correlated subquery a `$count` reads,
|
|
63
|
+
* correlated to whichever alias the clause naming the field is rendering under.
|
|
64
|
+
*/
|
|
65
|
+
function relationAggregate(spec) {
|
|
66
|
+
return new RelationAggregate(spec, (opts) => {
|
|
67
|
+
if (!opts.entity) {
|
|
68
|
+
throw new TypeError(`'${spec.relation}' was read off a definition's refs, so it renders only inside its entity's SQL`);
|
|
69
|
+
}
|
|
70
|
+
opts.dialect.appendRelationAggregate(opts.ctx, opts.entity, spec, opts.prefix);
|
|
71
|
+
});
|
|
72
|
+
}
|
|
46
73
|
/** One field as SQL, against its own entity or, read off a definition, the entity rendering it. */
|
|
47
74
|
function columnRef(entity, key) {
|
|
48
75
|
return new ColumnRef(key, (opts) => {
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "The JSON-native TypeScript ORM for Bun, Browsers, Edge, Deno, Node, Workers. Supports PostgreSQL, PGlite, MySQL, MariaDB, SQLite, CockroachDB, SQL Server, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.70.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|