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
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { AbstractSqlQuerier } from '../querier/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Querier for PGlite, Postgres compiled to WASM and run in this process.
|
|
4
|
+
*
|
|
5
|
+
* @remarks Extends {@link AbstractSqlQuerier} rather than `AbstractPgQuerier`, whose `internalStream`
|
|
6
|
+
* hands a `pg-query-stream` object to `query()`: PGlite has no cursor API, so streaming falls back to
|
|
7
|
+
* the base class buffering the whole result. `BEGIN`/`COMMIT` are plain statements on the single
|
|
8
|
+
* connection, leaving transactions to the base class.
|
|
9
|
+
*/
|
|
10
|
+
export class PgliteQuerier extends AbstractSqlQuerier {
|
|
11
|
+
db;
|
|
12
|
+
extra;
|
|
13
|
+
constructor(db, dialect, extra) {
|
|
14
|
+
super(dialect, extra);
|
|
15
|
+
this.db = db;
|
|
16
|
+
this.extra = extra;
|
|
17
|
+
}
|
|
18
|
+
async internalAll(query, values) {
|
|
19
|
+
const res = await this.db.query(query, values);
|
|
20
|
+
return res.rows;
|
|
21
|
+
}
|
|
22
|
+
async internalRun(query, values) {
|
|
23
|
+
const res = await this.db.query(query, values);
|
|
24
|
+
// `affectedRows`, not `rowCount`: PGlite derives the former from the command tag of a write only,
|
|
25
|
+
// where the latter also counts a `SELECT`'s rows and is absent altogether from a DDL tag.
|
|
26
|
+
return this.buildUpdateResult({ rows: res.rows, changes: res.affectedRows ?? 0 });
|
|
27
|
+
}
|
|
28
|
+
/** The handle belongs to the pool, which hands out one querier per unit of work over it. */
|
|
29
|
+
async internalRelease() { }
|
|
30
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { PGliteOptions } from '@electric-sql/pglite';
|
|
2
|
+
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
3
|
+
import type { ExtraOptions } from '../type/index.js';
|
|
4
|
+
import { PgliteDialect } from './pgliteDialect.js';
|
|
5
|
+
import { type PgliteDatabase, PgliteQuerier } from './pgliteQuerier.js';
|
|
6
|
+
/**
|
|
7
|
+
* The driver's own options, minus the `dataDir` this pool takes as its first argument.
|
|
8
|
+
*
|
|
9
|
+
* @remarks Imported rather than restated so extensions and the filesystem hooks keep their real types:
|
|
10
|
+
* `extensions: { vector }` from `@electric-sql/pglite-pgvector` is how a vector column becomes usable,
|
|
11
|
+
* mirroring `LocalSqlitePoolOptions.extensions` for `sqlite-vec`. Type-only, like the `pg` imports in
|
|
12
|
+
* `abstractPgQuerierPool.ts`, so nothing here reaches a runtime without the peer installed.
|
|
13
|
+
*/
|
|
14
|
+
export type PglitePoolOptions = Omit<PGliteOptions, 'dataDir'>;
|
|
15
|
+
/**
|
|
16
|
+
* Pool for PGlite, Postgres compiled to WASM and run in this process. No server, no container.
|
|
17
|
+
*
|
|
18
|
+
* PGlite is single connection, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
|
|
19
|
+
* Where PGlite differs from the two SQLite-family pools there is that it does not refuse a second
|
|
20
|
+
* `BEGIN`: a querier that opens a transaction while another already has one silently joins it, and that
|
|
21
|
+
* one's `ROLLBACK` then discards both queriers' writes. Nothing reports it, so a unit of work that needs
|
|
22
|
+
* a transaction of its own needs its own pool, and therefore its own database.
|
|
23
|
+
*
|
|
24
|
+
* @remarks Transactions are plain `BEGIN`/`COMMIT` statements rather than `db.transaction()`, whose
|
|
25
|
+
* callback holds PGlite's transaction mutex and would block every other querier's reads until commit.
|
|
26
|
+
* The cost is that PGlite cannot see the transaction, so it flushes to the filesystem after each
|
|
27
|
+
* statement within one: pass `relaxedDurability: true` on a persistent `dataDir` to skip waiting on
|
|
28
|
+
* those flushes.
|
|
29
|
+
*/
|
|
30
|
+
export declare class PgliteQuerierPool extends AbstractSharedHandleQuerierPool<PgliteDatabase, PgliteQuerier, PgliteDialect> {
|
|
31
|
+
readonly dataDir: string;
|
|
32
|
+
readonly opts?: PglitePoolOptions | undefined;
|
|
33
|
+
constructor(dataDir?: string, opts?: PglitePoolOptions | undefined, extra?: ExtraOptions);
|
|
34
|
+
protected openDb(): Promise<PgliteDatabase>;
|
|
35
|
+
protected buildQuerier(db: PgliteDatabase): PgliteQuerier;
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
|
+
import { PgliteDialect } from './pgliteDialect.js';
|
|
3
|
+
import { PgliteQuerier } from './pgliteQuerier.js';
|
|
4
|
+
/**
|
|
5
|
+
* Pool for PGlite, Postgres compiled to WASM and run in this process. No server, no container.
|
|
6
|
+
*
|
|
7
|
+
* PGlite is single connection, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
|
|
8
|
+
* Where PGlite differs from the two SQLite-family pools there is that it does not refuse a second
|
|
9
|
+
* `BEGIN`: a querier that opens a transaction while another already has one silently joins it, and that
|
|
10
|
+
* one's `ROLLBACK` then discards both queriers' writes. Nothing reports it, so a unit of work that needs
|
|
11
|
+
* a transaction of its own needs its own pool, and therefore its own database.
|
|
12
|
+
*
|
|
13
|
+
* @remarks Transactions are plain `BEGIN`/`COMMIT` statements rather than `db.transaction()`, whose
|
|
14
|
+
* callback holds PGlite's transaction mutex and would block every other querier's reads until commit.
|
|
15
|
+
* The cost is that PGlite cannot see the transaction, so it flushes to the filesystem after each
|
|
16
|
+
* statement within one: pass `relaxedDurability: true` on a persistent `dataDir` to skip waiting on
|
|
17
|
+
* those flushes.
|
|
18
|
+
*/
|
|
19
|
+
export class PgliteQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
20
|
+
dataDir;
|
|
21
|
+
opts;
|
|
22
|
+
constructor(dataDir = 'memory://', opts, extra) {
|
|
23
|
+
super(new PgliteDialect({ namingStrategy: extra?.namingStrategy }), extra);
|
|
24
|
+
this.dataDir = dataDir;
|
|
25
|
+
this.opts = opts;
|
|
26
|
+
}
|
|
27
|
+
async openDb() {
|
|
28
|
+
const { PGlite } = await import('@electric-sql/pglite');
|
|
29
|
+
// The declared return type is what checks {@link PgliteDatabase} against the real driver, so no
|
|
30
|
+
// cast is needed here or anywhere below it.
|
|
31
|
+
return PGlite.create(this.dataDir, this.opts);
|
|
32
|
+
}
|
|
33
|
+
buildQuerier(db) {
|
|
34
|
+
return new PgliteQuerier(db, this.dialect, this.extra);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ExtraOptions, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryGroupMap, QueryOne, QueryOptions, QueryPopulate, QuerySearch, QueryUpdateResult, RawRow, RelationKey, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
1
|
+
import type { EntityData, ExtraOptions, IdValue, Querier, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryGroupMap, QueryOne, QueryOptions, QueryPopulate, QuerySearch, QueryUpdateResult, RawRow, RelationKey, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
2
2
|
import { LoggerWrapper } from '../util/index.js';
|
|
3
3
|
/**
|
|
4
4
|
* Base class for all database queriers.
|
|
@@ -85,16 +85,16 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
85
85
|
*/
|
|
86
86
|
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>[]>;
|
|
87
87
|
protected abstract internalAggregate<E extends object, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
88
|
-
insertOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E> | undefined>;
|
|
89
|
-
insertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
90
|
-
protected abstract internalInsertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
88
|
+
insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E> | undefined>;
|
|
89
|
+
insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
90
|
+
protected abstract internalInsertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
91
91
|
updateOneById<E extends object>(entity: Type<E>, id: IdValue<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
92
92
|
updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
93
93
|
protected abstract internalUpdateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
94
94
|
restoreOneById<E extends object>(entity: Type<E>, id: IdValue<E>): Promise<number>;
|
|
95
95
|
restoreMany<E extends object>(entity: Type<E>, q: QuerySearch<E>): Promise<number>;
|
|
96
|
-
abstract upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<QueryUpdateResult>;
|
|
97
|
-
abstract upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<QueryUpdateResult>;
|
|
96
|
+
abstract upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
97
|
+
abstract upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
98
98
|
deleteOneById<E extends object>(entity: Type<E>, id: IdValue<E>, opts?: QueryOptions): Promise<number>;
|
|
99
99
|
/**
|
|
100
100
|
* Delete records matching the query. Soft-deletes when the entity has a soft-delete field (unless
|
|
@@ -104,9 +104,18 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
104
104
|
deleteMany<E extends object>(q: QuerySearch<E> & {
|
|
105
105
|
$entity: Type<E>;
|
|
106
106
|
}, opts?: QueryOptions): Promise<number>;
|
|
107
|
+
/**
|
|
108
|
+
* The rows a delete is about to take, loaded only when a hook or listener is there to receive
|
|
109
|
+
* them: the round trip is pure overhead for the (common) delete nobody is watching, and
|
|
110
|
+
* `internalDeleteMany` has its own fast path that never reads the rows at all.
|
|
111
|
+
*
|
|
112
|
+
* `undefined` means nobody was watching, which is not the same as the empty array meaning nothing
|
|
113
|
+
* matched - the caller deletes by `q` for the first and skips the statement entirely for the second.
|
|
114
|
+
*/
|
|
115
|
+
private findDoomed;
|
|
107
116
|
protected abstract internalDeleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
108
|
-
saveOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E>>;
|
|
109
|
-
saveMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
117
|
+
saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E>>;
|
|
118
|
+
saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
110
119
|
protected fillToManyRelations<E>(entity: Type<E>, payload: E[], populate?: QueryPopulate<E>): Promise<void>;
|
|
111
120
|
private fillToManyThroughRelation;
|
|
112
121
|
private fillToManyOneToMany;
|
|
@@ -137,6 +146,8 @@ export declare abstract class AbstractQuerier implements Querier {
|
|
|
137
146
|
* one afterwards.
|
|
138
147
|
*/
|
|
139
148
|
transaction<T>(callback: () => Promise<T>, opts?: TransactionOptions): Promise<T>;
|
|
149
|
+
/** Whether anything at all - a global listener or the entity itself - handles `event`. */
|
|
150
|
+
private hasHook;
|
|
140
151
|
/**
|
|
141
152
|
* Emit a lifecycle hook event for the given entity.
|
|
142
153
|
* Fires global listeners first, then entity-level hooks.
|
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
import { getMeta } from '../entity/index.js';
|
|
2
|
-
import { asSelectMap, augmentWhere, clone, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, LoggerWrapper, parseRelationAtKey, parseRelationQueryValue, runHooks, } from '../util/index.js';
|
|
2
|
+
import { asSelectMap, augmentWhere, clone, filterPersistableRelationKeys, forEachRequestedRelation, getKeys, getRelationRequestSummary, LoggerWrapper, parseRelationAtKey, parseRelationQueryValue, runHooks, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
3
3
|
import { enrichError } from './queryError.js';
|
|
4
|
+
/**
|
|
5
|
+
* Rejects a nullish primary key before it reaches a statement. The by-id methods reduce to
|
|
6
|
+
* `{ $where: id }`, and a nullish `$where` is *no filter*, so an unchecked one addresses the whole
|
|
7
|
+
* table. An entity declares its id optional, which puts `undefined` inside `IdValue<E>`, and the
|
|
8
|
+
* HTTP layer reaches these methods with parsed JSON regardless, so the guard belongs at runtime.
|
|
9
|
+
*
|
|
10
|
+
* Its callers are all `async` so this surfaces as a rejection on every one of them: a guard that
|
|
11
|
+
* threw synchronously from some and rejected from others would escape a caller's `.catch()`.
|
|
12
|
+
*/
|
|
13
|
+
function assertIdValue(entity, id) {
|
|
14
|
+
if (id === undefined || id === null) {
|
|
15
|
+
throw new TypeError(`'${entity.name}' was addressed by id, but the id is ${String(id)}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
4
18
|
/**
|
|
5
19
|
* Base class for all database queriers.
|
|
6
20
|
* It provides a standardized way to execute tasks serially to prevent race conditions on database connections.
|
|
@@ -65,6 +79,7 @@ export class AbstractQuerier {
|
|
|
65
79
|
return [$entity, query, maybeQueryOrOpts];
|
|
66
80
|
}
|
|
67
81
|
async findOneById(entity, id, q = {}, opts) {
|
|
82
|
+
assertIdValue(entity, id);
|
|
68
83
|
return this.findOne(entity, { ...q, $where: augmentWhere(getMeta(entity), q.$where, id) }, opts);
|
|
69
84
|
}
|
|
70
85
|
async findOne(entityOrQuery, maybeQueryOrOpts, maybeOpts) {
|
|
@@ -115,7 +130,8 @@ export class AbstractQuerier {
|
|
|
115
130
|
await this.emitHook(entity, 'afterInsert', payload);
|
|
116
131
|
return ids;
|
|
117
132
|
}
|
|
118
|
-
updateOneById(entity, id, payload, opts) {
|
|
133
|
+
async updateOneById(entity, id, payload, opts) {
|
|
134
|
+
assertIdValue(entity, id);
|
|
119
135
|
return this.updateMany(entity, { $where: id }, payload, opts);
|
|
120
136
|
}
|
|
121
137
|
async updateMany(entity, q, payload, opts) {
|
|
@@ -124,7 +140,8 @@ export class AbstractQuerier {
|
|
|
124
140
|
await this.emitHook(entity, 'afterUpdate', [payload]);
|
|
125
141
|
return changes;
|
|
126
142
|
}
|
|
127
|
-
restoreOneById(entity, id) {
|
|
143
|
+
async restoreOneById(entity, id) {
|
|
144
|
+
assertIdValue(entity, id);
|
|
128
145
|
return this.restoreMany(entity, { $where: id });
|
|
129
146
|
}
|
|
130
147
|
async restoreMany(entity, q) {
|
|
@@ -137,16 +154,41 @@ export class AbstractQuerier {
|
|
|
137
154
|
filters: { softDelete: false },
|
|
138
155
|
});
|
|
139
156
|
}
|
|
140
|
-
deleteOneById(entity, id, opts) {
|
|
157
|
+
async deleteOneById(entity, id, opts) {
|
|
158
|
+
assertIdValue(entity, id);
|
|
141
159
|
return this.deleteMany(entity, { $where: id }, opts);
|
|
142
160
|
}
|
|
143
161
|
async deleteMany(entityOrQuery, qOrOpts, maybeOpts) {
|
|
144
162
|
const [entity, q, opts] = this.resolveEntityQuery(entityOrQuery, qOrOpts, maybeOpts);
|
|
145
|
-
await this.
|
|
146
|
-
|
|
147
|
-
|
|
163
|
+
const doomed = await this.findDoomed(entity, q, opts);
|
|
164
|
+
if (doomed?.length === 0) {
|
|
165
|
+
return 0;
|
|
166
|
+
}
|
|
167
|
+
// Where a snapshot was taken, the statement names those rows rather than resolving `q` a second
|
|
168
|
+
// time. Two reads of one `$limit` with no total order are free to disagree, which would fire the
|
|
169
|
+
// hooks for one row and delete another; naming them also spares the second read.
|
|
170
|
+
const target = doomed ? { $where: doomed.map((it) => it[getMeta(entity).id]) } : q;
|
|
171
|
+
await this.emitHook(entity, 'beforeDelete', doomed ?? []);
|
|
172
|
+
const changes = await this.internalDeleteMany(entity, target, opts);
|
|
173
|
+
await this.emitHook(entity, 'afterDelete', doomed ?? []);
|
|
148
174
|
return changes;
|
|
149
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* The rows a delete is about to take, loaded only when a hook or listener is there to receive
|
|
178
|
+
* them: the round trip is pure overhead for the (common) delete nobody is watching, and
|
|
179
|
+
* `internalDeleteMany` has its own fast path that never reads the rows at all.
|
|
180
|
+
*
|
|
181
|
+
* `undefined` means nobody was watching, which is not the same as the empty array meaning nothing
|
|
182
|
+
* matched - the caller deletes by `q` for the first and skips the statement entirely for the second.
|
|
183
|
+
*/
|
|
184
|
+
async findDoomed(entity, q, opts) {
|
|
185
|
+
if (!this.hasHook(entity, 'beforeDelete') && !this.hasHook(entity, 'afterDelete')) {
|
|
186
|
+
return undefined;
|
|
187
|
+
}
|
|
188
|
+
// A hard delete takes already-soft-deleted rows too, so reading them back has to see them.
|
|
189
|
+
const findOpts = opts?.hardDelete ? { ...opts, filters: withoutSoftDeleteFilter(opts.filters) } : opts;
|
|
190
|
+
return this.internalFindMany(entity, q, findOpts);
|
|
191
|
+
}
|
|
150
192
|
async saveOne(entity, payload) {
|
|
151
193
|
const [id] = await this.saveMany(entity, [payload]);
|
|
152
194
|
return id;
|
|
@@ -400,32 +442,26 @@ export class AbstractQuerier {
|
|
|
400
442
|
throw err;
|
|
401
443
|
}
|
|
402
444
|
}
|
|
445
|
+
/** Whether anything at all - a global listener or the entity itself - handles `event`. */
|
|
446
|
+
hasHook(entity, event) {
|
|
447
|
+
return (this.extra?.listeners?.some((listener) => listener[event]) || (getMeta(entity).hooks?.[event]?.length ?? 0) > 0);
|
|
448
|
+
}
|
|
403
449
|
/**
|
|
404
450
|
* Emit a lifecycle hook event for the given entity.
|
|
405
451
|
* Fires global listeners first, then entity-level hooks.
|
|
406
452
|
*/
|
|
407
453
|
async emitHook(entity, event, payloads) {
|
|
408
|
-
|
|
409
|
-
const meta = getMeta(entity);
|
|
410
|
-
const registrations = meta.hooks?.[event];
|
|
411
|
-
// Fast bail-out: skip if no listeners and no entity hooks
|
|
412
|
-
if (!listeners?.length && !registrations?.length)
|
|
454
|
+
if (!this.hasHook(entity, event))
|
|
413
455
|
return;
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
const
|
|
418
|
-
if (
|
|
419
|
-
|
|
420
|
-
if (result instanceof Promise)
|
|
421
|
-
await result;
|
|
422
|
-
}
|
|
456
|
+
for (const listener of this.extra?.listeners ?? []) {
|
|
457
|
+
const fn = listener[event];
|
|
458
|
+
if (fn) {
|
|
459
|
+
const result = fn({ entity, querier: this, payloads, event });
|
|
460
|
+
if (result instanceof Promise)
|
|
461
|
+
await result;
|
|
423
462
|
}
|
|
424
463
|
}
|
|
425
|
-
|
|
426
|
-
if (registrations?.length) {
|
|
427
|
-
await runHooks(entity, event, payloads, { querier: this });
|
|
428
|
-
}
|
|
464
|
+
await runHooks(entity, event, payloads, { querier: this });
|
|
429
465
|
}
|
|
430
466
|
/**
|
|
431
467
|
* Runs `task` after everything already queued on this querier, one at a time.
|
|
@@ -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, QueryConflictPaths, QueryGroupMap, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
2
|
+
import type { EntityData, 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
|
*/
|
|
@@ -35,14 +35,14 @@ export declare abstract class AbstractQuerierPool<Q extends Querier, D extends A
|
|
|
35
35
|
findManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
|
|
36
36
|
count<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
37
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>[]>;
|
|
38
|
+
insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E> | undefined>;
|
|
39
|
+
insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
40
40
|
updateOneById<E extends object>(entity: Type<E>, id: IdValue<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
41
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>[]>;
|
|
42
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
43
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
44
|
+
saveOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E>>;
|
|
45
|
+
saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
46
46
|
deleteOneById<E extends object>(entity: Type<E>, id: IdValue<E>, opts?: QueryOptions): Promise<number>;
|
|
47
47
|
deleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
48
48
|
restoreOneById<E extends object>(entity: Type<E>, id: IdValue<E>): Promise<number>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
2
|
+
import type { SqlQuerier } from '../type/index.js';
|
|
3
|
+
import { AbstractSqlQuerierPool } from './abstractSqlQuerierPool.js';
|
|
4
|
+
/**
|
|
5
|
+
* Base pool for an engine that gives one connection per database and keeps it open for the pool's
|
|
6
|
+
* lifetime: every local SQLite driver, the embedded Turso engine, and PGlite.
|
|
7
|
+
*
|
|
8
|
+
* The handle is shared, but each acquisition gets its own querier, so transaction state stays per unit
|
|
9
|
+
* of work. That state is not *isolated*, which is the one way these differ from a real pool: there is a
|
|
10
|
+
* single connection under every querier, so two of them cannot hold independent transactions, and a
|
|
11
|
+
* unit of work that needs one needs its own pool and therefore its own database.
|
|
12
|
+
*
|
|
13
|
+
* What a second `BEGIN` then does is the engine's, not this class's: SQLite and the embedded Turso
|
|
14
|
+
* engine both refuse it ("cannot start a transaction within a transaction"), while PGlite accepts it
|
|
15
|
+
* into the transaction already open - see {@link PgliteQuerierPool}, which is why that one is worth
|
|
16
|
+
* saying out loud.
|
|
17
|
+
*
|
|
18
|
+
* Subclasses supply only how to open the handle and how to wrap it, the way {@link AbstractPgQuerierPool}
|
|
19
|
+
* takes `buildQuerier` alone. The lazy open and the close were written out once per pool before, along
|
|
20
|
+
* with three partial copies of the paragraph above.
|
|
21
|
+
*
|
|
22
|
+
* @remarks Deliberately not re-exported from `querier/index.ts`, which the root entry point re-exports:
|
|
23
|
+
* only the three driver entries need this, and each imports it by path, as `postgres/abstractPgQuerier.ts`
|
|
24
|
+
* is imported.
|
|
25
|
+
*/
|
|
26
|
+
export declare abstract class AbstractSharedHandleQuerierPool<DB extends {
|
|
27
|
+
close(): unknown;
|
|
28
|
+
}, Q extends SqlQuerier, D extends AbstractSqlDialect> extends AbstractSqlQuerierPool<Q, D> {
|
|
29
|
+
/**
|
|
30
|
+
* The open, not the handle: `db ??= await openDb()` reads before the await and assigns after, so
|
|
31
|
+
* callers arriving while the first open is in flight each start one of their own. The extra handles
|
|
32
|
+
* are then unreachable and never closed, and on an in-memory database they are separate databases,
|
|
33
|
+
* so a querier built on one writes where nothing else will ever read.
|
|
34
|
+
*/
|
|
35
|
+
private opening?;
|
|
36
|
+
/** Opens the one connection. Called on the first acquisition, and again after an {@link end}. */
|
|
37
|
+
protected abstract openDb(): Promise<DB>;
|
|
38
|
+
/** Wraps the shared handle in a querier: the only thing that varies between these pools. */
|
|
39
|
+
protected abstract buildQuerier(db: DB): Q;
|
|
40
|
+
getQuerier(): Promise<Q>;
|
|
41
|
+
end(): Promise<void>;
|
|
42
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AbstractSqlQuerierPool } from './abstractSqlQuerierPool.js';
|
|
2
|
+
/**
|
|
3
|
+
* Base pool for an engine that gives one connection per database and keeps it open for the pool's
|
|
4
|
+
* lifetime: every local SQLite driver, the embedded Turso engine, and PGlite.
|
|
5
|
+
*
|
|
6
|
+
* The handle is shared, but each acquisition gets its own querier, so transaction state stays per unit
|
|
7
|
+
* of work. That state is not *isolated*, which is the one way these differ from a real pool: there is a
|
|
8
|
+
* single connection under every querier, so two of them cannot hold independent transactions, and a
|
|
9
|
+
* unit of work that needs one needs its own pool and therefore its own database.
|
|
10
|
+
*
|
|
11
|
+
* What a second `BEGIN` then does is the engine's, not this class's: SQLite and the embedded Turso
|
|
12
|
+
* engine both refuse it ("cannot start a transaction within a transaction"), while PGlite accepts it
|
|
13
|
+
* into the transaction already open - see {@link PgliteQuerierPool}, which is why that one is worth
|
|
14
|
+
* saying out loud.
|
|
15
|
+
*
|
|
16
|
+
* Subclasses supply only how to open the handle and how to wrap it, the way {@link AbstractPgQuerierPool}
|
|
17
|
+
* takes `buildQuerier` alone. The lazy open and the close were written out once per pool before, along
|
|
18
|
+
* with three partial copies of the paragraph above.
|
|
19
|
+
*
|
|
20
|
+
* @remarks Deliberately not re-exported from `querier/index.ts`, which the root entry point re-exports:
|
|
21
|
+
* only the three driver entries need this, and each imports it by path, as `postgres/abstractPgQuerier.ts`
|
|
22
|
+
* is imported.
|
|
23
|
+
*/
|
|
24
|
+
export class AbstractSharedHandleQuerierPool extends AbstractSqlQuerierPool {
|
|
25
|
+
/**
|
|
26
|
+
* The open, not the handle: `db ??= await openDb()` reads before the await and assigns after, so
|
|
27
|
+
* callers arriving while the first open is in flight each start one of their own. The extra handles
|
|
28
|
+
* are then unreachable and never closed, and on an in-memory database they are separate databases,
|
|
29
|
+
* so a querier built on one writes where nothing else will ever read.
|
|
30
|
+
*/
|
|
31
|
+
opening;
|
|
32
|
+
async getQuerier() {
|
|
33
|
+
// Cleared on failure, so a driver that could not start once is retried rather than refused forever.
|
|
34
|
+
this.opening ??= this.openDb().catch((err) => {
|
|
35
|
+
this.opening = undefined;
|
|
36
|
+
throw err;
|
|
37
|
+
});
|
|
38
|
+
return this.buildQuerier(await this.opening);
|
|
39
|
+
}
|
|
40
|
+
async end() {
|
|
41
|
+
const opening = this.opening;
|
|
42
|
+
this.opening = undefined;
|
|
43
|
+
// An open still in flight is awaited rather than abandoned: closing is what releases its file or port.
|
|
44
|
+
// One that failed leaves nothing to close, and `getQuerier` already reported it to its own caller.
|
|
45
|
+
const db = await opening?.catch(() => undefined);
|
|
46
|
+
await db?.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AbstractSqlDialect } from '../dialect/index.js';
|
|
2
|
-
import type { ExtraOptions, IdValue, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryGroupMap, QueryOptions, QuerySearch, QueryUpdateResult, SqlQuerier, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
2
|
+
import type { EntityData, ExtraOptions, IdValue, Query, QueryAggMap, QueryAggregate, QueryAggregateResult, QueryConflictPaths, QueryGroupMap, QueryOptions, QuerySearch, QueryUpdateResult, SqlQuerier, TransactionOptions, Type, UpdatePayload } from '../type/index.js';
|
|
3
3
|
import type { BuildUpdateResultPayload } from '../util/sql.util.js';
|
|
4
4
|
import { AbstractQuerier } from './abstractQuerier.js';
|
|
5
5
|
export declare abstract class AbstractSqlQuerier extends AbstractQuerier implements SqlQuerier {
|
|
@@ -64,10 +64,12 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
|
|
|
64
64
|
private hydrateFields;
|
|
65
65
|
protected internalCount<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
66
66
|
protected internalAggregate<E extends object, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
67
|
-
internalInsertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
67
|
+
internalInsertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
68
68
|
internalUpdateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
/** The ids matching `q`, in `q`'s own order and page, so a write can name the rows it settled on. */
|
|
70
|
+
private settleIds;
|
|
71
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
72
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
71
73
|
protected internalDeleteMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
72
74
|
get hasOpenTransaction(): boolean;
|
|
73
75
|
beginTransaction(opts?: TransactionOptions): Promise<void>;
|
|
@@ -3,6 +3,14 @@ import { getMeta } from '../entity/index.js';
|
|
|
3
3
|
import { buildUpdateResult, cascadesOnDelete, clone, getInsertFieldKeys, getRelationRequestSummary, isAutoIncrement, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
4
4
|
import { AbstractQuerier } from './abstractQuerier.js';
|
|
5
5
|
import { enrichError } from './queryError.js';
|
|
6
|
+
/**
|
|
7
|
+
* Whether `q` picks a specific slice of the matching rows rather than all of them. A write that
|
|
8
|
+
* does has to settle those rows and name them: `ORDER BY`/`LIMIT` on an UPDATE or DELETE is MySQL's
|
|
9
|
+
* alone.
|
|
10
|
+
*/
|
|
11
|
+
function isPaged(q) {
|
|
12
|
+
return q.$sort !== undefined || q.$limit !== undefined || q.$skip !== undefined;
|
|
13
|
+
}
|
|
6
14
|
export class AbstractSqlQuerier extends AbstractQuerier {
|
|
7
15
|
dialect;
|
|
8
16
|
extra;
|
|
@@ -219,12 +227,30 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
219
227
|
}
|
|
220
228
|
async internalUpdateMany(entity, q, payload, opts) {
|
|
221
229
|
payload = clone(payload);
|
|
230
|
+
// Settled first for the reason `internalDeleteMany` settles: `ORDER BY`/`LIMIT` on an UPDATE is
|
|
231
|
+
// MySQL's alone, so a paged update has to name the rows it picked.
|
|
232
|
+
let target = q;
|
|
233
|
+
if (isPaged(q)) {
|
|
234
|
+
const ids = await this.settleIds(entity, q, opts);
|
|
235
|
+
if (!ids.length) {
|
|
236
|
+
return 0;
|
|
237
|
+
}
|
|
238
|
+
target = { $where: ids };
|
|
239
|
+
}
|
|
222
240
|
const ctx = this.dialect.createContext();
|
|
223
|
-
this.dialect.update(ctx, entity,
|
|
241
|
+
this.dialect.update(ctx, entity, target, payload, opts);
|
|
224
242
|
const { changes = 0 } = await this.run(ctx.sql, ctx.values);
|
|
225
|
-
await this.updateRelations(entity,
|
|
243
|
+
await this.updateRelations(entity, target, payload, opts);
|
|
226
244
|
return changes;
|
|
227
245
|
}
|
|
246
|
+
/** The ids matching `q`, in `q`'s own order and page, so a write can name the rows it settled on. */
|
|
247
|
+
async settleIds(entity, q, opts) {
|
|
248
|
+
const meta = getMeta(entity);
|
|
249
|
+
const ctx = this.dialect.createContext();
|
|
250
|
+
this.dialect.find(ctx, entity, { ...q, $select: { [meta.id]: true } }, opts);
|
|
251
|
+
const founds = await this.all(ctx.sql, ctx.values);
|
|
252
|
+
return founds.map((it) => it[meta.id]);
|
|
253
|
+
}
|
|
228
254
|
async upsertOne(entity, conflictPaths, payload) {
|
|
229
255
|
return this.upsertMany(entity, conflictPaths, [payload]);
|
|
230
256
|
}
|
|
@@ -252,8 +278,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
252
278
|
// parents' ids to find their children, and no engine but MySQL accepts `ORDER BY`/`LIMIT` on a
|
|
253
279
|
// DELETE, so a paged delete has to name the rows it settled on. A plain predicate needs neither,
|
|
254
280
|
// and there the round trip buys nothing: the statement can say what the caller already said.
|
|
255
|
-
|
|
256
|
-
if (!hasPagination && !cascadesOnDelete(meta)) {
|
|
281
|
+
if (!isPaged(q) && !cascadesOnDelete(meta)) {
|
|
257
282
|
const ctx = this.dialect.createContext();
|
|
258
283
|
this.dialect.delete(ctx, entity, q, opts);
|
|
259
284
|
const { changes = 0 } = await this.run(ctx.sql, ctx.values);
|
|
@@ -261,13 +286,10 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
261
286
|
}
|
|
262
287
|
// A hard delete also targets already-soft-deleted rows, so drop the soft-delete filter when finding ids.
|
|
263
288
|
const findOpts = opts?.hardDelete ? { ...opts, filters: withoutSoftDeleteFilter(opts.filters) } : opts;
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
const founds = await this.all(findCtx.sql, findCtx.values);
|
|
267
|
-
if (!founds.length) {
|
|
289
|
+
const ids = await this.settleIds(entity, q, findOpts);
|
|
290
|
+
if (!ids.length) {
|
|
268
291
|
return 0;
|
|
269
292
|
}
|
|
270
|
-
const ids = founds.map((it) => it[meta.id]);
|
|
271
293
|
// Children first: they hold the foreign key, so deleting the parent ahead of them is rejected
|
|
272
294
|
// outright by any schema that declares the constraint without `ON DELETE CASCADE`.
|
|
273
295
|
await this.deleteRelations(entity, ids, opts);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
2
|
import type { ExtraOptions } from '../type/index.js';
|
|
3
3
|
import { SqliteDialect } from './sqliteDialect.js';
|
|
4
4
|
import { type SqliteDatabase, SqliteQuerier } from './sqliteQuerier.js';
|
|
@@ -12,20 +12,17 @@ export type LocalSqlitePoolOptions = {
|
|
|
12
12
|
extensions?: readonly string[];
|
|
13
13
|
};
|
|
14
14
|
/**
|
|
15
|
-
* Pool for a SQLite database opened in this process, whichever driver provides it.
|
|
15
|
+
* Pool for a SQLite database opened in this process, whichever driver provides it. SQLite gives one
|
|
16
|
+
* connection per file, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
* lifecycle, and loading the extensions on the way up, are the same for `better-sqlite3`, `bun:sqlite`
|
|
20
|
-
* and `node:sqlite`, and were written out once per pool before.
|
|
18
|
+
* Subclasses supply only {@link createDb}: loading the extensions on the way up is the same for
|
|
19
|
+
* `better-sqlite3`, `bun:sqlite` and `node:sqlite`, and was written out once per pool before.
|
|
21
20
|
*/
|
|
22
|
-
export declare abstract class AbstractLocalSqliteQuerierPool<O extends LocalSqlitePoolOptions> extends
|
|
21
|
+
export declare abstract class AbstractLocalSqliteQuerierPool<O extends LocalSqlitePoolOptions> extends AbstractSharedHandleQuerierPool<SqliteDatabase, SqliteQuerier, SqliteDialect> {
|
|
23
22
|
readonly opts?: O | undefined;
|
|
24
|
-
private db?;
|
|
25
23
|
constructor(opts?: O | undefined, extra?: ExtraOptions);
|
|
26
24
|
/** Opens the driver's database. Extensions are loaded by the caller, not here. */
|
|
27
25
|
protected abstract createDb(): Promise<SqliteDatabase>;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end(): Promise<void>;
|
|
26
|
+
protected openDb(): Promise<SqliteDatabase>;
|
|
27
|
+
protected buildQuerier(db: SqliteDatabase): SqliteQuerier;
|
|
31
28
|
}
|
|
@@ -1,25 +1,19 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { AbstractSharedHandleQuerierPool } from '../querier/abstractSharedHandleQuerierPool.js';
|
|
2
2
|
import { SqliteDialect } from './sqliteDialect.js';
|
|
3
3
|
import { SqliteQuerier } from './sqliteQuerier.js';
|
|
4
4
|
/**
|
|
5
|
-
* Pool for a SQLite database opened in this process, whichever driver provides it.
|
|
5
|
+
* Pool for a SQLite database opened in this process, whichever driver provides it. SQLite gives one
|
|
6
|
+
* connection per file, so the shared-handle lifecycle is {@link AbstractSharedHandleQuerierPool}'s.
|
|
6
7
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* lifecycle, and loading the extensions on the way up, are the same for `better-sqlite3`, `bun:sqlite`
|
|
10
|
-
* and `node:sqlite`, and were written out once per pool before.
|
|
8
|
+
* Subclasses supply only {@link createDb}: loading the extensions on the way up is the same for
|
|
9
|
+
* `better-sqlite3`, `bun:sqlite` and `node:sqlite`, and was written out once per pool before.
|
|
11
10
|
*/
|
|
12
|
-
export class AbstractLocalSqliteQuerierPool extends
|
|
11
|
+
export class AbstractLocalSqliteQuerierPool extends AbstractSharedHandleQuerierPool {
|
|
13
12
|
opts;
|
|
14
|
-
db;
|
|
15
13
|
constructor(opts, extra) {
|
|
16
14
|
super(new SqliteDialect({ namingStrategy: extra?.namingStrategy }), extra);
|
|
17
15
|
this.opts = opts;
|
|
18
16
|
}
|
|
19
|
-
async getQuerier() {
|
|
20
|
-
this.db ??= await this.openDb();
|
|
21
|
-
return new SqliteQuerier(this.db, this.dialect, this.extra);
|
|
22
|
-
}
|
|
23
17
|
async openDb() {
|
|
24
18
|
const db = await this.createDb();
|
|
25
19
|
for (const extension of this.opts?.extensions ?? []) {
|
|
@@ -27,8 +21,7 @@ export class AbstractLocalSqliteQuerierPool extends AbstractSqlQuerierPool {
|
|
|
27
21
|
}
|
|
28
22
|
return db;
|
|
29
23
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
this.db = undefined;
|
|
24
|
+
buildQuerier(db) {
|
|
25
|
+
return new SqliteQuerier(db, this.dialect, this.extra);
|
|
33
26
|
}
|
|
34
27
|
}
|