uql-orm 0.23.0 → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/entity/metadata/definition.js +14 -6
- package/dist/querier/abstractQuerierPool.d.ts +18 -1
- package/dist/querier/abstractQuerierPool.js +108 -0
- package/dist/querier/abstractSqlQuerier.js +3 -0
- package/dist/type/entity.d.ts +16 -4
- package/dist/type/querier.d.ts +2 -35
- package/dist/type/querierPool.d.ts +13 -12
- package/dist/type/universalQuerier.d.ts +23 -7
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -32,8 +32,9 @@
|
|
|
32
32
|
npm install uql-orm pg # or mysql2, mariadb, better-sqlite3, mongodb, @tursodatabase/serverless, @libsql/client
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
Decorators
|
|
36
|
-
[
|
|
35
|
+
That is the whole install. Decorators are the standard TC39 ones, so there is no `reflect-metadata`
|
|
36
|
+
and no compiler flag to turn on ([setup](https://uql-orm.dev/getting-started)), and the
|
|
37
|
+
[imperative API](https://uql-orm.dev/entities/imperative) skips decorators altogether.
|
|
37
38
|
|
|
38
39
|
```ts
|
|
39
40
|
await querier.findMany(User, {
|
|
@@ -49,14 +50,13 @@ from the browser to the server. The same object runs on every supported database
|
|
|
49
50
|
|
|
50
51
|
## Why UQL?
|
|
51
52
|
|
|
52
|
-
- **The fastest.** Wins [all 8 categories](https://uql-orm.dev/benchmark) of our [open benchmark](https://github.com/rogerpadilla/ts-orm-benchmark), beating even query builders like Knex and Kysely: ~2.
|
|
53
|
+
- **The fastest.** Wins [all 8 categories](https://uql-orm.dev/benchmark) of our [open benchmark](https://github.com/rogerpadilla/ts-orm-benchmark), beating even query builders like Knex and Kysely: ~2.4× faster than the runner-up on average, over 4.6M ops/s on simple SELECTs.
|
|
53
54
|
- **Light.** Zero dependencies, under 1 MB installed, every dialect included.
|
|
54
55
|
- **Queries are data, not method chains.** Plain JSON in, typed rows out. There's no DSL to learn and nothing to compile.
|
|
55
56
|
- **Type-safe to the leaf.** Operators are gated per field type, and JSON/JSONB dot-paths resolve each path's value type, so `{ age: { $like: 'x' } }` or a typo'd path is a compile error instead of a runtime surprise.
|
|
56
57
|
- **No codegen.** Entities are TypeScript classes, so your code *is* the schema. No `.prisma` file to regenerate, no generated client to keep in sync.
|
|
57
58
|
- **One API everywhere.** PostgreSQL, CockroachDB, MySQL, MariaDB, SQLite, Turso, LibSQL, Neon, Cloudflare D1, Bun SQL, and MongoDB.
|
|
58
|
-
- **
|
|
59
|
-
- **The hard things are built in.** [Semantic and vector search](https://uql-orm.dev/ai-semantic-search), [non-bypassable multi-tenant filters](https://uql-orm.dev/multi-tenancy), [entity-first migrations](https://uql-orm.dev/migrations), [soft-delete with restore](https://uql-orm.dev/entities/soft-delete), [streaming](https://uql-orm.dev/querying/streaming), and [a REST API from your entities](https://uql-orm.dev/extensions-http).
|
|
59
|
+
- **The hard things are built in.** [Semantic and vector search](https://uql-orm.dev/ai-semantic-search), [non-bypassable multi-tenant filters](https://uql-orm.dev/multi-tenancy), [entity-first migrations](https://uql-orm.dev/migrations), [soft-delete with restore](https://uql-orm.dev/entities/soft-delete), [streaming](https://uql-orm.dev/querying/streaming), and [a REST API from your entities](https://uql-orm.dev/http).
|
|
60
60
|
|
|
61
61
|
## Get started
|
|
62
62
|
|
|
@@ -227,7 +227,14 @@ function fillRelations(meta) {
|
|
|
227
227
|
}
|
|
228
228
|
}
|
|
229
229
|
if (relOpts.through) {
|
|
230
|
-
|
|
230
|
+
const throughEntity = relOpts.through();
|
|
231
|
+
const throughMeta = fillThroughRelations(throughEntity);
|
|
232
|
+
for (const { local } of relOpts.references) {
|
|
233
|
+
if (throughMeta.fields[local])
|
|
234
|
+
continue;
|
|
235
|
+
throw new TypeError(`'${meta.entity.name}.${relKey}' joins through '${throughEntity.name}', which has no '${local}' ` +
|
|
236
|
+
"field. Declare it, or name the join columns yourself with 'references'.");
|
|
237
|
+
}
|
|
231
238
|
}
|
|
232
239
|
}
|
|
233
240
|
return meta;
|
|
@@ -254,8 +261,11 @@ function fillInverseSideRelations(relOpts) {
|
|
|
254
261
|
foreign: local,
|
|
255
262
|
}));
|
|
256
263
|
}
|
|
264
|
+
// `getMeta` rather than `ensureMeta`: a pivot that declares its sides as relations rather than as
|
|
265
|
+
// `@Field({ references })` columns gets those foreign-key columns auto-created there, and the caller
|
|
266
|
+
// checks its own derived join columns against them.
|
|
257
267
|
function fillThroughRelations(entity) {
|
|
258
|
-
const meta =
|
|
268
|
+
const meta = getMeta(entity);
|
|
259
269
|
meta.relations = getKeys(meta.fields).reduce((relations, key) => {
|
|
260
270
|
const field = meta.fields[key];
|
|
261
271
|
if (!field)
|
|
@@ -274,6 +284,7 @@ function fillThroughRelations(entity) {
|
|
|
274
284
|
}
|
|
275
285
|
return relations;
|
|
276
286
|
}, {});
|
|
287
|
+
return meta;
|
|
277
288
|
}
|
|
278
289
|
function getMappedByRelationKey(relOpts) {
|
|
279
290
|
if (typeof relOpts.mappedBy === 'function') {
|
|
@@ -286,10 +297,7 @@ function getMappedByRelationKey(relOpts) {
|
|
|
286
297
|
}
|
|
287
298
|
function getRelationKeyMap(meta) {
|
|
288
299
|
const keys = [...getKeys(meta.fields), ...getKeys(meta.relations)];
|
|
289
|
-
return keys.
|
|
290
|
-
acc[key] = key;
|
|
291
|
-
return acc;
|
|
292
|
-
}, {});
|
|
300
|
+
return Object.fromEntries(keys.map((key) => [key, key]));
|
|
293
301
|
}
|
|
294
302
|
function getIdKey(meta) {
|
|
295
303
|
const id = getKeys(meta.fields).find((key) => meta.fields[key]?.isId);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AbstractDialect } from '../dialect/index.js';
|
|
2
|
-
import type { ExtraOptions, IdValue, PoolRunOptions, Querier, QuerierPool, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap, QueryOne, QueryOptions, QuerySearch, TransactionOptions, Type } from '../type/index.js';
|
|
2
|
+
import type { ExtraOptions, IdValue, PoolRunOptions, Querier, QuerierPool, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryGroupMap, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Base pool: dialect id and behavior come only from the `dialect` instance (see {@link QuerierPool}).
|
|
5
5
|
*/
|
|
@@ -27,9 +27,26 @@ export declare abstract class AbstractQuerierPool<Q extends Querier, D extends A
|
|
|
27
27
|
findOneById<E extends object>(entity: Type<E>, id: IdValue<E>, q?: QueryOne<E>, opts?: QueryOptions): Promise<E | undefined>;
|
|
28
28
|
findOne<E extends object>(entity: Type<E>, q: QueryOne<E>, opts?: QueryOptions): Promise<E | undefined>;
|
|
29
29
|
findMany<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<E[]>;
|
|
30
|
+
/**
|
|
31
|
+
* The connection outlives the call here: it is held until the iterator is drained or closed by a
|
|
32
|
+
* `break`/`throw`. Abandoning the iterator instead leaks it until GC, so consume it in a `for await`.
|
|
33
|
+
*/
|
|
34
|
+
findManyStream<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): AsyncGenerator<E>;
|
|
30
35
|
findManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
|
|
31
36
|
count<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
32
37
|
aggregate<E extends object, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
38
|
+
insertOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E> | undefined>;
|
|
39
|
+
insertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
40
|
+
updateOneById<E extends object>(entity: Type<E>, id: IdValue<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
41
|
+
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
42
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<QueryUpdateResult>;
|
|
43
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<QueryUpdateResult>;
|
|
44
|
+
saveOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E>>;
|
|
45
|
+
saveMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
46
|
+
deleteOneById<E extends object>(entity: Type<E>, id: IdValue<E>, opts?: QueryOptions): Promise<number>;
|
|
47
|
+
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
48
|
+
restoreOneById<E extends object>(entity: Type<E>, id: IdValue<E>): Promise<number>;
|
|
49
|
+
restoreMany<E extends object>(entity: Type<E>, q: QuerySearch<E>): Promise<number>;
|
|
33
50
|
/**
|
|
34
51
|
* end the pool.
|
|
35
52
|
*/
|
|
@@ -1,3 +1,55 @@
|
|
|
1
|
+
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
+
if (value !== null && value !== void 0) {
|
|
3
|
+
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
+
var dispose, inner;
|
|
5
|
+
if (async) {
|
|
6
|
+
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
+
dispose = value[Symbol.asyncDispose];
|
|
8
|
+
}
|
|
9
|
+
if (dispose === void 0) {
|
|
10
|
+
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
+
dispose = value[Symbol.dispose];
|
|
12
|
+
if (async) inner = dispose;
|
|
13
|
+
}
|
|
14
|
+
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
+
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
+
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
+
}
|
|
18
|
+
else if (async) {
|
|
19
|
+
env.stack.push({ async: true });
|
|
20
|
+
}
|
|
21
|
+
return value;
|
|
22
|
+
};
|
|
23
|
+
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
+
return function (env) {
|
|
25
|
+
function fail(e) {
|
|
26
|
+
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
+
env.hasError = true;
|
|
28
|
+
}
|
|
29
|
+
var r, s = 0;
|
|
30
|
+
function next() {
|
|
31
|
+
while (r = env.stack.pop()) {
|
|
32
|
+
try {
|
|
33
|
+
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
+
if (r.dispose) {
|
|
35
|
+
var result = r.dispose.call(r.value);
|
|
36
|
+
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
+
}
|
|
38
|
+
else s |= 1;
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
fail(e);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
+
if (env.hasError) throw env.error;
|
|
46
|
+
}
|
|
47
|
+
return next();
|
|
48
|
+
};
|
|
49
|
+
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
+
var e = new Error(message);
|
|
51
|
+
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
+
});
|
|
1
53
|
import { withContext } from '../context/context.js';
|
|
2
54
|
/**
|
|
3
55
|
* Base pool: dialect id and behavior come only from the `dialect` instance (see {@link QuerierPool}).
|
|
@@ -43,6 +95,26 @@ export class AbstractQuerierPool {
|
|
|
43
95
|
findMany(entity, q, opts) {
|
|
44
96
|
return this.withQuerier((querier) => querier.findMany(entity, q, opts));
|
|
45
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* The connection outlives the call here: it is held until the iterator is drained or closed by a
|
|
100
|
+
* `break`/`throw`. Abandoning the iterator instead leaks it until GC, so consume it in a `for await`.
|
|
101
|
+
*/
|
|
102
|
+
async *findManyStream(entity, q, opts) {
|
|
103
|
+
const env_1 = { stack: [], error: void 0, hasError: false };
|
|
104
|
+
try {
|
|
105
|
+
const querier = __addDisposableResource(env_1, await this.getQuerier(), true);
|
|
106
|
+
yield* querier.findManyStream(entity, q, opts);
|
|
107
|
+
}
|
|
108
|
+
catch (e_1) {
|
|
109
|
+
env_1.error = e_1;
|
|
110
|
+
env_1.hasError = true;
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
const result_1 = __disposeResources(env_1);
|
|
114
|
+
if (result_1)
|
|
115
|
+
await result_1;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
46
118
|
findManyAndCount(entity, q, opts) {
|
|
47
119
|
return this.withQuerier((querier) => querier.findManyAndCount(entity, q, opts));
|
|
48
120
|
}
|
|
@@ -52,4 +124,40 @@ export class AbstractQuerierPool {
|
|
|
52
124
|
aggregate(entity, q, opts) {
|
|
53
125
|
return this.withQuerier((querier) => querier.aggregate(entity, q, opts));
|
|
54
126
|
}
|
|
127
|
+
insertOne(entity, payload) {
|
|
128
|
+
return this.withQuerier((querier) => querier.insertOne(entity, payload));
|
|
129
|
+
}
|
|
130
|
+
insertMany(entity, payload) {
|
|
131
|
+
return this.withQuerier((querier) => querier.insertMany(entity, payload));
|
|
132
|
+
}
|
|
133
|
+
updateOneById(entity, id, payload, opts) {
|
|
134
|
+
return this.withQuerier((querier) => querier.updateOneById(entity, id, payload, opts));
|
|
135
|
+
}
|
|
136
|
+
updateMany(entity, q, payload, opts) {
|
|
137
|
+
return this.withQuerier((querier) => querier.updateMany(entity, q, payload, opts));
|
|
138
|
+
}
|
|
139
|
+
upsertOne(entity, conflictPaths, payload) {
|
|
140
|
+
return this.withQuerier((querier) => querier.upsertOne(entity, conflictPaths, payload));
|
|
141
|
+
}
|
|
142
|
+
upsertMany(entity, conflictPaths, payload) {
|
|
143
|
+
return this.withQuerier((querier) => querier.upsertMany(entity, conflictPaths, payload));
|
|
144
|
+
}
|
|
145
|
+
saveOne(entity, payload) {
|
|
146
|
+
return this.withQuerier((querier) => querier.saveOne(entity, payload));
|
|
147
|
+
}
|
|
148
|
+
saveMany(entity, payload) {
|
|
149
|
+
return this.withQuerier((querier) => querier.saveMany(entity, payload));
|
|
150
|
+
}
|
|
151
|
+
deleteOneById(entity, id, opts) {
|
|
152
|
+
return this.withQuerier((querier) => querier.deleteOneById(entity, id, opts));
|
|
153
|
+
}
|
|
154
|
+
deleteMany(entity, q, opts) {
|
|
155
|
+
return this.withQuerier((querier) => querier.deleteMany(entity, q, opts));
|
|
156
|
+
}
|
|
157
|
+
restoreOneById(entity, id) {
|
|
158
|
+
return this.withQuerier((querier) => querier.restoreOneById(entity, id));
|
|
159
|
+
}
|
|
160
|
+
restoreMany(entity, q) {
|
|
161
|
+
return this.withQuerier((querier) => querier.restoreMany(entity, q));
|
|
162
|
+
}
|
|
55
163
|
}
|
|
@@ -64,6 +64,9 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
64
64
|
if (toManyKeys.length) {
|
|
65
65
|
throw new TypeError(`findManyStream does not load to-many relations (${toManyKeys.join(', ')}). Use findMany so fillToManyRelations can run, or omit those keys from the stream query.`);
|
|
66
66
|
}
|
|
67
|
+
// The one path that does not go through `all`/`run`, so it connects on its own: streaming first on
|
|
68
|
+
// a freshly acquired querier used to reach `getConn()` with nothing acquired.
|
|
69
|
+
await this.lazyConnect();
|
|
67
70
|
const ctx = this.dialect.createContext();
|
|
68
71
|
this.dialect.find(ctx, entity, q, opts);
|
|
69
72
|
const normalizedParams = this.dialect.normalizeValues(ctx.values);
|
package/dist/type/entity.d.ts
CHANGED
|
@@ -350,16 +350,28 @@ export type RelationOptions<E = any> = {
|
|
|
350
350
|
cardinality: RelationCardinality;
|
|
351
351
|
readonly cascade?: boolean | CascadeType;
|
|
352
352
|
mappedBy?: RelationMappedBy<E>;
|
|
353
|
-
|
|
353
|
+
/**
|
|
354
|
+
* The pivot entity of a many-to-many. Unconstrained by `E`: a pivot holds foreign keys to both
|
|
355
|
+
* sides and is not a relation value of the target, so nothing about it is derivable from `E`.
|
|
356
|
+
*/
|
|
357
|
+
through?: EntityGetter;
|
|
354
358
|
references?: RelationReferences;
|
|
355
359
|
};
|
|
356
360
|
type RelationOptionsOwner<E> = Pick<RelationOptions<E>, 'entity' | 'references' | 'cascade'>;
|
|
357
361
|
type RelationOptionsInverseSide<E> = Required<Pick<RelationOptions<E>, 'entity' | 'mappedBy'>> & Pick<RelationOptions<E>, 'cascade'>;
|
|
358
362
|
type RelationOptionsThroughOwner<E> = Required<Pick<RelationOptions<E>, 'entity'>> & Pick<RelationOptions<E>, 'through' | 'references' | 'cascade'>;
|
|
363
|
+
/**
|
|
364
|
+
* The key names of `E` as values, so `mappedBy` can be written as `(user) => user.company` instead of
|
|
365
|
+
* a string literal and survive a rename.
|
|
366
|
+
*
|
|
367
|
+
* Mapping over `Key<E>` rather than `keyof E` is what makes the callback usable: a homomorphic
|
|
368
|
+
* `[K in keyof E]` inherits the entity's optional modifiers, so `user.company` is
|
|
369
|
+
* `'company' | undefined` and {@link RelationKeyMapper} rejects it - every callback needed a `!`. The
|
|
370
|
+
* map is built from the target's metadata (see `getRelationKeyMap`), where every key is present and
|
|
371
|
+
* every key is a string.
|
|
372
|
+
*/
|
|
359
373
|
export type RelationKeyMap<E> = {
|
|
360
|
-
readonly [K in
|
|
361
|
-
} & {
|
|
362
|
-
readonly [key: string]: string;
|
|
374
|
+
readonly [K in Key<E>]: K;
|
|
363
375
|
};
|
|
364
376
|
export type RelationKeyMapper<E> = (keyMap: RelationKeyMap<E>) => Key<E>;
|
|
365
377
|
export type RelationReferences = {
|
package/dist/type/querier.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import type { Db } from 'mongodb';
|
|
2
2
|
import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
3
3
|
import type { SqlDialectName } from './dialect.js';
|
|
4
|
-
import type { HookEvent
|
|
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,
|
|
8
|
-
import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
|
|
7
|
+
import type { Query, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
|
|
9
8
|
import type { UniversalQuerier } from './universalQuerier.js';
|
|
10
9
|
import type { Type } from './utility.js';
|
|
11
10
|
/**
|
|
@@ -56,34 +55,6 @@ export interface Querier extends UniversalQuerier {
|
|
|
56
55
|
count<E extends object>(q: QuerySearch<E> & {
|
|
57
56
|
$entity: Type<E>;
|
|
58
57
|
}, opts?: QueryOptions): Promise<number>;
|
|
59
|
-
/**
|
|
60
|
-
* Insert a single record and return its ID (provided, `onInsert`-generated, or
|
|
61
|
-
* database-generated - see {@link Querier.insertMany} for the exact semantics).
|
|
62
|
-
* Returns `undefined` when the ID cannot be determined (e.g. MySQL/SQLite non-auto-increment
|
|
63
|
-
* keys in batches without explicit IDs).
|
|
64
|
-
*/
|
|
65
|
-
insertOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E> | undefined>;
|
|
66
|
-
/**
|
|
67
|
-
* Insert multiple records in a single statement (auto-chunked when the batch exceeds the
|
|
68
|
-
* dialect's bind-parameter limit) and return their IDs in payload order.
|
|
69
|
-
*
|
|
70
|
-
* Provided IDs and client-generated ones (`@Id({ onInsert })`) are always returned as-is.
|
|
71
|
-
* Database-generated IDs are exact on `'returning'` dialects (Postgres, MariaDB, MongoDB);
|
|
72
|
-
* on MySQL/SQLite they are inferred from the driver header, which is only reliable for
|
|
73
|
-
* auto-increment keys in batches without explicit IDs - otherwise those entries are
|
|
74
|
-
* `undefined` rather than potentially wrong values.
|
|
75
|
-
*/
|
|
76
|
-
insertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
77
|
-
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
78
|
-
/**
|
|
79
|
-
* Restore soft-deleted records (sets the soft-delete field back to `null`). Throws if the
|
|
80
|
-
* entity has no soft-delete field.
|
|
81
|
-
*/
|
|
82
|
-
restoreOneById<E extends object>(entity: Type<E>, id: IdValue<E>): Promise<number>;
|
|
83
|
-
restoreMany<E extends object>(entity: Type<E>, q: QuerySearch<E>): Promise<number>;
|
|
84
|
-
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<QueryUpdateResult>;
|
|
85
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<QueryUpdateResult>;
|
|
86
|
-
saveMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
87
58
|
/**
|
|
88
59
|
* Delete many records (soft-deletes when the entity has a soft-delete field, else removes them).
|
|
89
60
|
* Supports both entity-as-argument and entity-as-field patterns.
|
|
@@ -92,10 +63,6 @@ export interface Querier extends UniversalQuerier {
|
|
|
92
63
|
deleteMany<E extends object>(q: QuerySearch<E> & {
|
|
93
64
|
$entity: Type<E>;
|
|
94
65
|
}, opts?: QueryOptions): Promise<number>;
|
|
95
|
-
/**
|
|
96
|
-
* Run an aggregate query (GROUP BY with aggregate functions).
|
|
97
|
-
*/
|
|
98
|
-
aggregate<E extends object, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
99
66
|
/**
|
|
100
67
|
* whether this querier is in a transaction or not.
|
|
101
68
|
*/
|
|
@@ -15,24 +15,25 @@ export interface PoolRunOptions {
|
|
|
15
15
|
/**
|
|
16
16
|
* Querier pool. Read the dialect id via `pool.dialect.dialectName` (see {@link AbstractDialect.dialectName}); queriers expose the same on `querier.dialect`.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* `
|
|
21
|
-
*
|
|
22
|
-
* connection and serialize. Single-connection backends (better-sqlite3, Bun sqlite, D1) stay
|
|
23
|
-
* correct but always serialize on their one connection.
|
|
18
|
+
* A pool is a {@link UniversalQuerier} too, so a function that runs queries takes that type and the
|
|
19
|
+
* caller passes its own querier or the pool. `pool.op(...)` is exactly
|
|
20
|
+
* `pool.withQuerier((querier) => querier.op(...))`, so two pool calls are two units of work; when they
|
|
21
|
+
* must commit together, that is `transaction`.
|
|
24
22
|
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
23
|
+
* Acquiring per call is also what makes `Promise.all([pool.findMany(A, {}), pool.count(B, {})])` run on
|
|
24
|
+
* separate connections, while the same calls inside one `withQuerier`/`transaction` share a pinned
|
|
25
|
+
* connection and serialize. Single-connection backends (better-sqlite3, Bun sqlite, D1) stay correct
|
|
26
|
+
* but always serialize.
|
|
28
27
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
28
|
+
* An enclosing `withContext` scopes pool calls (`security` filters apply), which is why they take no
|
|
29
|
+
* per-call `context` option (unlike `withQuerier`/`transaction`).
|
|
30
|
+
*
|
|
31
|
+
* Pool calls take the entity-as-argument form only; the `{ $entity }` form needs a querier.
|
|
31
32
|
*
|
|
32
33
|
* @typeParam Q - Querier implementation returned from the pool.
|
|
33
34
|
* @typeParam D - Concrete dialect class held by the pool.
|
|
34
35
|
*/
|
|
35
|
-
export interface QuerierPool<Q extends Querier = Querier, D extends AbstractDialect = AbstractDialect> extends
|
|
36
|
+
export interface QuerierPool<Q extends Querier = Querier, D extends AbstractDialect = AbstractDialect> extends UniversalQuerier {
|
|
36
37
|
/**
|
|
37
38
|
* Database dialect instance (single source of truth for dialect id and SQL/NoSQL behavior).
|
|
38
39
|
*/
|
|
@@ -53,19 +53,29 @@ export interface UniversalQuerier {
|
|
|
53
53
|
*/
|
|
54
54
|
count<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
55
55
|
/**
|
|
56
|
-
*
|
|
56
|
+
* Insert a single record and return its ID (provided, `onInsert`-generated, or
|
|
57
|
+
* database-generated - see {@link UniversalQuerier.insertMany} for the exact semantics).
|
|
58
|
+
* Returns `undefined` when the ID cannot be determined (e.g. MySQL/SQLite non-auto-increment
|
|
59
|
+
* keys in batches without explicit IDs).
|
|
57
60
|
* @param entity the entity to persist on
|
|
58
61
|
* @param payload the data to be persisted
|
|
59
62
|
* @return the ID
|
|
60
63
|
*/
|
|
61
64
|
insertOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E> | undefined>;
|
|
62
65
|
/**
|
|
63
|
-
*
|
|
66
|
+
* Insert multiple records in a single statement (auto-chunked when the batch exceeds the
|
|
67
|
+
* dialect's bind-parameter limit) and return their IDs in payload order.
|
|
68
|
+
*
|
|
69
|
+
* Provided IDs and client-generated ones (`@Id({ onInsert })`) are always returned as-is.
|
|
70
|
+
* Database-generated IDs are exact on `'returning'` dialects (Postgres, MariaDB, MongoDB);
|
|
71
|
+
* on MySQL/SQLite they are inferred from the driver header, which is only reliable for
|
|
72
|
+
* auto-increment keys in batches without explicit IDs - otherwise those entries are
|
|
73
|
+
* `undefined` rather than potentially wrong values.
|
|
64
74
|
* @param entity the entity to persist on
|
|
65
75
|
* @param payload the data to be persisted
|
|
66
76
|
* @return the IDs
|
|
67
77
|
*/
|
|
68
|
-
insertMany
|
|
78
|
+
insertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
69
79
|
/**
|
|
70
80
|
* updates a record partially.
|
|
71
81
|
* @param entity the entity to persist on
|
|
@@ -81,7 +91,7 @@ export interface UniversalQuerier {
|
|
|
81
91
|
* @param payload the data to be persisted
|
|
82
92
|
* @return the number of affected records
|
|
83
93
|
*/
|
|
84
|
-
updateMany
|
|
94
|
+
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
85
95
|
/**
|
|
86
96
|
* Insert or update a record based on the conflict paths.
|
|
87
97
|
* @param entity the entity to persist on
|
|
@@ -89,7 +99,7 @@ export interface UniversalQuerier {
|
|
|
89
99
|
* @param payload the data to be persisted
|
|
90
100
|
* @return operation metadata; see {@link QueryUpdateResult}
|
|
91
101
|
*/
|
|
92
|
-
upsertOne
|
|
102
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<QueryUpdateResult>;
|
|
93
103
|
/**
|
|
94
104
|
* Insert or update many records based on the conflict paths.
|
|
95
105
|
* @param entity the entity to persist on
|
|
@@ -97,7 +107,7 @@ export interface UniversalQuerier {
|
|
|
97
107
|
* @param payload the data to be persisted
|
|
98
108
|
* @return operation metadata; see {@link QueryUpdateResult}
|
|
99
109
|
*/
|
|
100
|
-
upsertMany
|
|
110
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<QueryUpdateResult>;
|
|
101
111
|
/**
|
|
102
112
|
* insert or update a record.
|
|
103
113
|
* @param entity the entity to persist on
|
|
@@ -111,7 +121,7 @@ export interface UniversalQuerier {
|
|
|
111
121
|
* @param payload the data to be persisted
|
|
112
122
|
* @return the IDs
|
|
113
123
|
*/
|
|
114
|
-
saveMany
|
|
124
|
+
saveMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
115
125
|
/**
|
|
116
126
|
* delete or SoftDelete a record.
|
|
117
127
|
* @param entity the entity to persist on
|
|
@@ -126,6 +136,12 @@ export interface UniversalQuerier {
|
|
|
126
136
|
* @return the number of affected records
|
|
127
137
|
*/
|
|
128
138
|
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
139
|
+
/**
|
|
140
|
+
* Restore soft-deleted records (sets the soft-delete field back to `null`). Throws if the
|
|
141
|
+
* entity has no soft-delete field.
|
|
142
|
+
*/
|
|
143
|
+
restoreOneById<E extends object>(entity: Type<E>, id: IdValue<E>): Promise<number>;
|
|
144
|
+
restoreMany<E extends object>(entity: Type<E>, q: QuerySearch<E>): Promise<number>;
|
|
129
145
|
/**
|
|
130
146
|
* runs an aggregate query (GROUP BY with aggregate functions).
|
|
131
147
|
* @param entity the target entity
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "Extremely fast, type-safe TypeScript ORM - one API for every database",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.24.1",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -145,7 +145,6 @@
|
|
|
145
145
|
"pg-query-stream": "^4.16.0",
|
|
146
146
|
"rxjs": "^7.8.2",
|
|
147
147
|
"sqlite-vec": "^0.1.9",
|
|
148
|
-
"tsx": "^4.23.1",
|
|
149
148
|
"ws": "^8.21.1"
|
|
150
149
|
},
|
|
151
150
|
"author": "Roger Padilla",
|
|
@@ -199,5 +198,5 @@
|
|
|
199
198
|
"publishConfig": {
|
|
200
199
|
"access": "public"
|
|
201
200
|
},
|
|
202
|
-
"gitHead": "
|
|
201
|
+
"gitHead": "7f11b72301d647853c7a2778daaa09af8f4d5695"
|
|
203
202
|
}
|