uql-orm 0.28.2 → 0.30.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 +5 -5
- package/dist/browser/uql-browser.min.js +2 -2
- package/dist/browser/uql-browser.min.js.map +3 -3
- 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 +9 -4
- package/dist/mongo/mongodbQuerier.js +15 -7
- package/dist/mysql/mysql2Querier.d.ts +1 -1
- package/dist/mysql/mysql2Querier.js +7 -1
- package/dist/nestjs/uqlContextInterceptor.d.ts +2 -2
- package/dist/nestjs/uqlContextInterceptor.js +2 -2
- package/dist/nestjs/uqlModule.d.ts +3 -3
- package/dist/nestjs/uqlModule.js +5 -6
- package/dist/postgres/abstractPgQuerier.d.ts +3 -2
- package/dist/postgres/abstractPgQuerier.js +2 -2
- package/dist/querier/abstractPoolQuerier.d.ts +2 -2
- package/dist/querier/abstractPoolQuerier.js +7 -7
- package/dist/querier/abstractQuerier.d.ts +45 -16
- package/dist/querier/abstractQuerier.js +99 -40
- package/dist/querier/abstractQuerierPool.d.ts +7 -7
- package/dist/querier/abstractSqlQuerier.d.ts +17 -6
- package/dist/querier/abstractSqlQuerier.js +61 -34
- package/dist/querier/index.d.ts +0 -2
- package/dist/querier/index.js +0 -2
- package/dist/sqlite/abstractSqliteQuerier.d.ts +1 -2
- package/dist/sqlite/abstractSqliteQuerier.js +2 -8
- package/dist/sqlite/hranaQuerier.d.ts +5 -0
- package/dist/sqlite/hranaQuerier.js +10 -6
- package/dist/type/entity.d.ts +34 -11
- package/dist/type/querier.d.ts +26 -10
- package/dist/type/query.d.ts +22 -9
- package/dist/type/queryAggregate.d.ts +67 -24
- package/dist/type/queryWhere.d.ts +4 -1
- package/dist/type/universalQuerier.d.ts +11 -11
- package/dist/util/dialect.util.d.ts +26 -4
- package/dist/util/dialect.util.js +42 -0
- package/package.json +4 -5
- package/dist/querier/querierContext.browser.d.ts +0 -12
- package/dist/querier/querierContext.browser.js +0 -18
- package/dist/querier/querierContext.d.ts +0 -22
- package/dist/querier/querierContext.js +0 -42
- package/dist/querier/transactional.d.ts +0 -26
- package/dist/querier/transactional.js +0 -43
|
@@ -257,8 +257,11 @@ export type QueryWhereFieldOperators<T> = unknown extends T ? QueryWhereFieldOpe
|
|
|
257
257
|
* Value for a field comparison. A bare array is an implicit `$in` for scalar fields only:
|
|
258
258
|
* on array-typed fields (e.g. a vector `number[]`) an array of arrays is ambiguous, so
|
|
259
259
|
* membership there requires an explicit operator.
|
|
260
|
+
*
|
|
261
|
+
* `null` is accepted on a nullable field (an optional property is a nullable column), matching what
|
|
262
|
+
* `$eq: null` already took.
|
|
260
263
|
*/
|
|
261
|
-
export type QueryWhereFieldValue<T> = T | ([NonNullable<T>] extends [readonly unknown[]] ? never : T[]) | QueryWhereFieldOperators<T> | QueryRaw;
|
|
264
|
+
export type QueryWhereFieldValue<T> = T | (undefined extends T ? null : never) | ([NonNullable<T>] extends [readonly unknown[]] ? never : T[]) | QueryWhereFieldOperators<T> | QueryRaw;
|
|
262
265
|
/**
|
|
263
266
|
* query filter array - used for `$and`, `$or`, `$not`, `$nor` operators.
|
|
264
267
|
*/
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { IdValue, UpdatePayload } from './entity.js';
|
|
2
|
-
import type { Query, QueryConflictPaths, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
|
|
1
|
+
import type { EntityData, IdValue, UpdatePayload } from './entity.js';
|
|
2
|
+
import type { Query, QueryConflictPaths, QueryFilter, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
|
|
3
3
|
import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
|
|
4
4
|
import type { Type } from './utility.js';
|
|
5
5
|
/**
|
|
@@ -46,12 +46,12 @@ export interface UniversalQuerier {
|
|
|
46
46
|
*/
|
|
47
47
|
findManyAndCount<E extends object>(entity: Type<E>, q: Query<E>, opts?: QueryOptions): Promise<[E[], number]>;
|
|
48
48
|
/**
|
|
49
|
-
* counts the number of records matching the given
|
|
49
|
+
* counts the number of records matching the given filter.
|
|
50
50
|
* @param entity the target entity
|
|
51
|
-
* @param q the
|
|
51
|
+
* @param q the filter
|
|
52
52
|
* @return the count
|
|
53
53
|
*/
|
|
54
|
-
count<E extends object>(entity: Type<E>, q?:
|
|
54
|
+
count<E extends object>(entity: Type<E>, q?: QueryFilter<E>, opts?: QueryOptions): Promise<number>;
|
|
55
55
|
/**
|
|
56
56
|
* Insert a single record and return its ID (provided, `onInsert`-generated, or
|
|
57
57
|
* database-generated - see {@link UniversalQuerier.insertMany} for the exact semantics).
|
|
@@ -61,7 +61,7 @@ export interface UniversalQuerier {
|
|
|
61
61
|
* @param payload the data to be persisted
|
|
62
62
|
* @return the ID
|
|
63
63
|
*/
|
|
64
|
-
insertOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E> | undefined>;
|
|
64
|
+
insertOne<E extends object>(entity: Type<E>, payload: EntityData<E>): Promise<IdValue<E> | undefined>;
|
|
65
65
|
/**
|
|
66
66
|
* Insert multiple records in a single statement (auto-chunked when the batch exceeds the
|
|
67
67
|
* dialect's bind-parameter limit) and return their IDs in payload order.
|
|
@@ -75,7 +75,7 @@ export interface UniversalQuerier {
|
|
|
75
75
|
* @param payload the data to be persisted
|
|
76
76
|
* @return the IDs
|
|
77
77
|
*/
|
|
78
|
-
insertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
78
|
+
insertMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
79
79
|
/**
|
|
80
80
|
* updates a record partially.
|
|
81
81
|
* @param entity the entity to persist on
|
|
@@ -99,7 +99,7 @@ export interface UniversalQuerier {
|
|
|
99
99
|
* @param payload the data to be persisted
|
|
100
100
|
* @return operation metadata; see {@link QueryUpdateResult}
|
|
101
101
|
*/
|
|
102
|
-
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<QueryUpdateResult>;
|
|
102
|
+
upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>): Promise<QueryUpdateResult>;
|
|
103
103
|
/**
|
|
104
104
|
* Insert or update many records based on the conflict paths.
|
|
105
105
|
* @param entity the entity to persist on
|
|
@@ -107,21 +107,21 @@ export interface UniversalQuerier {
|
|
|
107
107
|
* @param payload the data to be persisted
|
|
108
108
|
* @return operation metadata; see {@link QueryUpdateResult}
|
|
109
109
|
*/
|
|
110
|
-
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<QueryUpdateResult>;
|
|
110
|
+
upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: EntityData<E>[]): Promise<QueryUpdateResult>;
|
|
111
111
|
/**
|
|
112
112
|
* insert or update a record.
|
|
113
113
|
* @param entity the entity to persist on
|
|
114
114
|
* @param payload the data to be persisted
|
|
115
115
|
* @return the ID
|
|
116
116
|
*/
|
|
117
|
-
saveOne<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
118
|
/**
|
|
119
119
|
* Insert or update records.
|
|
120
120
|
* @param entity the entity to persist on
|
|
121
121
|
* @param payload the data to be persisted
|
|
122
122
|
* @return the IDs
|
|
123
123
|
*/
|
|
124
|
-
saveMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
124
|
+
saveMany<E extends object>(entity: Type<E>, payload: EntityData<E>[]): Promise<IdValue<E>[]>;
|
|
125
125
|
/**
|
|
126
126
|
* delete or SoftDelete a record.
|
|
127
127
|
* @param entity the entity to persist on
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { type CascadeType, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QueryVectorSearch, type QueryWhere, type QueryWhereMap, type RelationKey } from '../type/index.js';
|
|
1
|
+
import { type CascadeType, type EntityData, type EntityMeta, type FieldKey, type FieldOptions, type JsonUpdateOp, type OnFieldCallback, type QueryAggMap, type QueryAggregateOp, type QueryExclude, type QueryGroupMap, type QueryOptions, QueryRaw, type QuerySelect, type QuerySelectValue, type QuerySizeComparisonOps, type QueryVectorSearch, type QueryWhere, type QueryWhereMap, type RelationKey } from '../type/index.js';
|
|
2
2
|
export type CallbackKey = keyof Pick<FieldOptions, 'onInsert' | 'onUpdate'>;
|
|
3
|
-
export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: E
|
|
3
|
+
export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: EntityData<E>, callbackKey: CallbackKey): FieldKey<E>[];
|
|
4
4
|
/**
|
|
5
5
|
* Resolves the columns of an INSERT statement: the union of the persistable fields provided by
|
|
6
6
|
* any record (in first-seen order), plus every `onInsert` field. Records missing one of these
|
|
@@ -13,7 +13,7 @@ export declare function filterFieldKeys<E>(meta: EntityMeta<E>, payload: E, call
|
|
|
13
13
|
* has run {@link fillOnFields} first (it stamps them on every record, but the querier's
|
|
14
14
|
* chunk-size estimate inspects the raw payload).
|
|
15
15
|
*/
|
|
16
|
-
export declare function getInsertFieldKeys<E>(meta: EntityMeta<E>, payloads: E[]): FieldKey<E>[];
|
|
16
|
+
export declare function getInsertFieldKeys<E>(meta: EntityMeta<E>, payloads: EntityData<E>[]): FieldKey<E>[];
|
|
17
17
|
export declare function getFieldCallbackValue(val: OnFieldCallback): string | number | bigint | boolean | Date | QueryRaw | readonly {
|
|
18
18
|
readonly __json?: never;
|
|
19
19
|
}[] | readonly number[] | Uint8Array<ArrayBufferLike> | {
|
|
@@ -28,7 +28,7 @@ export declare function getSoftDeleteValue(field: FieldOptions): string | number
|
|
|
28
28
|
}[] | readonly number[] | Uint8Array<ArrayBufferLike> | {
|
|
29
29
|
readonly __json?: never;
|
|
30
30
|
};
|
|
31
|
-
export declare function fillOnFields<E>(meta: EntityMeta<E>, payload: E | E[], callbackKey: CallbackKey): E[];
|
|
31
|
+
export declare function fillOnFields<E>(meta: EntityMeta<E>, payload: EntityData<E> | EntityData<E>[], callbackKey: CallbackKey): EntityData<E>[];
|
|
32
32
|
/**
|
|
33
33
|
* The relation keys present in `payload` whose cascade configuration allows `action`. Only
|
|
34
34
|
* `payload`'s keys are read, so any keys-bearing object works (an entity, an update payload,
|
|
@@ -100,3 +100,25 @@ export declare function parseRelationSize(val: unknown): number | QuerySizeCompa
|
|
|
100
100
|
* entries consumable by any dialect. Grouped columns come first, then computed columns.
|
|
101
101
|
*/
|
|
102
102
|
export declare function parseGroupMap<E>(group?: QueryGroupMap<E>, agg?: QueryAggMap<E>): ParsedGroupEntry[];
|
|
103
|
+
/**
|
|
104
|
+
* Whether `value` is a map of comparison operators rather than a value to compare against. Only a
|
|
105
|
+
* plain object qualifies: `Date`, `QueryRaw`, `Uint8Array` and arrays are all `typeof 'object'`, and
|
|
106
|
+
* reading their keys as operators drops the condition (a `Date` has none) or throws on an array's
|
|
107
|
+
* indices. Shared by the SQL and MongoDB builders, whose `$where` and `$having` all face this.
|
|
108
|
+
*/
|
|
109
|
+
export declare function isOperatorMap(value: unknown): value is Record<string, unknown>;
|
|
110
|
+
/**
|
|
111
|
+
* A page operand, checked before it reaches an engine. These arrive from page arithmetic and from
|
|
112
|
+
* REST query strings (`parseQueryParams` yields `NaN` for a non-numeric one), and each engine's own
|
|
113
|
+
* complaint names neither the clause nor the value - or, for `$limit: 0`, quietly means something
|
|
114
|
+
* else. Shared so every backend rejects the same input.
|
|
115
|
+
*/
|
|
116
|
+
export declare function assertNonNegativeInteger(value: number, clause: string): number;
|
|
117
|
+
/**
|
|
118
|
+
* Rejects a `$having`/`$sort` key naming something an aggregate does not emit. Its rows are its
|
|
119
|
+
* `$group` columns and its `$agg` aliases; anything else is a value that is not there. Shared so
|
|
120
|
+
* SQL and MongoDB refuse the same query with the same words.
|
|
121
|
+
*/
|
|
122
|
+
export declare function throwUnknownAggregateColumn(key: string, clause: string): never;
|
|
123
|
+
/** {@link throwUnknownAggregateColumn} over every key of a clause, for backends that check up front. */
|
|
124
|
+
export declare function assertAggregateColumns(clauseMap: object | undefined, emitted: ReadonlySet<string>, clause: string): void;
|
|
@@ -301,3 +301,45 @@ export function parseGroupMap(group, agg) {
|
|
|
301
301
|
}
|
|
302
302
|
return entries;
|
|
303
303
|
}
|
|
304
|
+
/**
|
|
305
|
+
* Whether `value` is a map of comparison operators rather than a value to compare against. Only a
|
|
306
|
+
* plain object qualifies: `Date`, `QueryRaw`, `Uint8Array` and arrays are all `typeof 'object'`, and
|
|
307
|
+
* reading their keys as operators drops the condition (a `Date` has none) or throws on an array's
|
|
308
|
+
* indices. Shared by the SQL and MongoDB builders, whose `$where` and `$having` all face this.
|
|
309
|
+
*/
|
|
310
|
+
export function isOperatorMap(value) {
|
|
311
|
+
return (typeof value === 'object' &&
|
|
312
|
+
value !== null &&
|
|
313
|
+
!Array.isArray(value) &&
|
|
314
|
+
!(value instanceof Date) &&
|
|
315
|
+
!(value instanceof Uint8Array) &&
|
|
316
|
+
!(value instanceof QueryRaw));
|
|
317
|
+
}
|
|
318
|
+
/**
|
|
319
|
+
* A page operand, checked before it reaches an engine. These arrive from page arithmetic and from
|
|
320
|
+
* REST query strings (`parseQueryParams` yields `NaN` for a non-numeric one), and each engine's own
|
|
321
|
+
* complaint names neither the clause nor the value - or, for `$limit: 0`, quietly means something
|
|
322
|
+
* else. Shared so every backend rejects the same input.
|
|
323
|
+
*/
|
|
324
|
+
export function assertNonNegativeInteger(value, clause) {
|
|
325
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
326
|
+
throw new TypeError(`${clause} must be a non-negative integer, got ${value}`);
|
|
327
|
+
}
|
|
328
|
+
return value;
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Rejects a `$having`/`$sort` key naming something an aggregate does not emit. Its rows are its
|
|
332
|
+
* `$group` columns and its `$agg` aliases; anything else is a value that is not there. Shared so
|
|
333
|
+
* SQL and MongoDB refuse the same query with the same words.
|
|
334
|
+
*/
|
|
335
|
+
export function throwUnknownAggregateColumn(key, clause) {
|
|
336
|
+
throw new TypeError(`cannot ${clause} by '${key}': it is neither a $group column nor an $agg alias`);
|
|
337
|
+
}
|
|
338
|
+
/** {@link throwUnknownAggregateColumn} over every key of a clause, for backends that check up front. */
|
|
339
|
+
export function assertAggregateColumns(clauseMap, emitted, clause) {
|
|
340
|
+
for (const key of getKeys(clauseMap ?? {})) {
|
|
341
|
+
if (!emitted.has(key)) {
|
|
342
|
+
throwUnknownAggregateColumn(key, clause);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"homepage": "https://uql-orm.dev",
|
|
4
4
|
"description": "JSON-native TypeScript ORM for Node.js, Bun and Deno. Supports PostgreSQL, MySQL, MariaDB, SQLite, CockroachDB, Turso, Neon, Cloudflare D1 and MongoDB. Queries are plain JSON, typed to the leaf.",
|
|
5
5
|
"license": "MIT",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.30.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
9
|
"node": ">=24"
|
|
@@ -12,8 +12,7 @@
|
|
|
12
12
|
"main": "./dist/index.js",
|
|
13
13
|
"types": "./dist/index.d.ts",
|
|
14
14
|
"browser": {
|
|
15
|
-
"./dist/context/context.js": "./dist/context/context.browser.js"
|
|
16
|
-
"./dist/querier/querierContext.js": "./dist/querier/querierContext.browser.js"
|
|
15
|
+
"./dist/context/context.js": "./dist/context/context.browser.js"
|
|
17
16
|
},
|
|
18
17
|
"bin": {
|
|
19
18
|
"uql-migrate": "./dist/migrate/bin.js"
|
|
@@ -134,13 +133,13 @@
|
|
|
134
133
|
"@tursodatabase/serverless": "^1.4.0",
|
|
135
134
|
"@types/better-sqlite3": "^9.6.0",
|
|
136
135
|
"@types/express": "^5.0.6",
|
|
137
|
-
"@types/pg": "^8.
|
|
136
|
+
"@types/pg": "^8.23.1",
|
|
138
137
|
"@types/ws": "^8.18.1",
|
|
139
138
|
"better-sqlite3": "^13.0.3",
|
|
140
139
|
"express": "^5.2.1",
|
|
141
140
|
"mariadb": "^3.5.3",
|
|
142
141
|
"mongodb": "^7.5.0",
|
|
143
|
-
"mysql2": "^3.23.
|
|
142
|
+
"mysql2": "^3.23.4",
|
|
144
143
|
"pg": "^8.23.0",
|
|
145
144
|
"pg-query-stream": "^4.17.0",
|
|
146
145
|
"rxjs": "^7.8.2",
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { Querier } from '../type/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Browser build of the transactional querier context: bundlers targeting the browser resolve
|
|
4
|
-
* `querierContext.ts` to this file (see the `browser` map in package.json), keeping the root entrypoint
|
|
5
|
-
* free of `node:async_hooks`.
|
|
6
|
-
*
|
|
7
|
-
* Nothing in a browser bundle opens a transaction. The browser querier serializes queries over HTTP and
|
|
8
|
-
* the server owns the transaction, so there is no ambient querier to hand out and no flow to track.
|
|
9
|
-
*/
|
|
10
|
-
export declare function withQuerierContext<T>(_querier: Querier, callback: () => T): T;
|
|
11
|
-
export declare function currentQuerier(): Querier;
|
|
12
|
-
export declare function currentQuerierIfAny(): Querier | undefined;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser build of the transactional querier context: bundlers targeting the browser resolve
|
|
3
|
-
* `querierContext.ts` to this file (see the `browser` map in package.json), keeping the root entrypoint
|
|
4
|
-
* free of `node:async_hooks`.
|
|
5
|
-
*
|
|
6
|
-
* Nothing in a browser bundle opens a transaction. The browser querier serializes queries over HTTP and
|
|
7
|
-
* the server owns the transaction, so there is no ambient querier to hand out and no flow to track.
|
|
8
|
-
*/
|
|
9
|
-
export function withQuerierContext(_querier, callback) {
|
|
10
|
-
return callback();
|
|
11
|
-
}
|
|
12
|
-
export function currentQuerier() {
|
|
13
|
-
throw new TypeError('currentQuerier() is server-only: transactions run on the server, and the browser querier sends each ' +
|
|
14
|
-
'request over HTTP. Call it from server code, or use the querier you already have.');
|
|
15
|
-
}
|
|
16
|
-
export function currentQuerierIfAny() {
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { Querier } from '../type/index.js';
|
|
2
|
-
/** Runs `callback` with `querier` as the ambient one, for the whole async flow beneath it. */
|
|
3
|
-
export declare function withQuerierContext<T>(querier: Querier, callback: () => T): T;
|
|
4
|
-
/**
|
|
5
|
-
* The querier of the enclosing `@Transactional()` method.
|
|
6
|
-
*
|
|
7
|
-
* This is what replaced `@InjectQuerier()`: the standard decorator spec has no parameter decorators, so
|
|
8
|
-
* the querier can no longer be injected into an argument and is read from the ambient flow instead.
|
|
9
|
-
*
|
|
10
|
-
* @example
|
|
11
|
-
* ```ts
|
|
12
|
-
* class UserService {
|
|
13
|
-
* @Transactional()
|
|
14
|
-
* async register(data: Partial<User>) {
|
|
15
|
-
* await currentQuerier().insertOne(User, data);
|
|
16
|
-
* }
|
|
17
|
-
* }
|
|
18
|
-
* ```
|
|
19
|
-
*/
|
|
20
|
-
export declare function currentQuerier(): Querier;
|
|
21
|
-
/** The ambient querier, or `undefined` outside a transaction. For callers that can work without one. */
|
|
22
|
-
export declare function currentQuerierIfAny(): Querier | undefined;
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
-
/**
|
|
3
|
-
* The querier `@Transactional()` opened for the current async flow.
|
|
4
|
-
*
|
|
5
|
-
* Separate from the `UqlContext` storage in `context/context.ts` on purpose: that one is remapped to a
|
|
6
|
-
* synchronous browser shim which cannot propagate across `await`, and a querier handle must. Nothing in
|
|
7
|
-
* a browser bundle opens a transaction, so this stays server-only and `verify-dist` keeps it out of the
|
|
8
|
-
* browser-facing graph.
|
|
9
|
-
*/
|
|
10
|
-
const querierStorage = new AsyncLocalStorage();
|
|
11
|
-
/** Runs `callback` with `querier` as the ambient one, for the whole async flow beneath it. */
|
|
12
|
-
export function withQuerierContext(querier, callback) {
|
|
13
|
-
return querierStorage.run(querier, callback);
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* The querier of the enclosing `@Transactional()` method.
|
|
17
|
-
*
|
|
18
|
-
* This is what replaced `@InjectQuerier()`: the standard decorator spec has no parameter decorators, so
|
|
19
|
-
* the querier can no longer be injected into an argument and is read from the ambient flow instead.
|
|
20
|
-
*
|
|
21
|
-
* @example
|
|
22
|
-
* ```ts
|
|
23
|
-
* class UserService {
|
|
24
|
-
* @Transactional()
|
|
25
|
-
* async register(data: Partial<User>) {
|
|
26
|
-
* await currentQuerier().insertOne(User, data);
|
|
27
|
-
* }
|
|
28
|
-
* }
|
|
29
|
-
* ```
|
|
30
|
-
*/
|
|
31
|
-
export function currentQuerier() {
|
|
32
|
-
const querier = querierStorage.getStore();
|
|
33
|
-
if (!querier) {
|
|
34
|
-
throw new TypeError('currentQuerier() found no active querier. Call it inside a @Transactional() method, or take a querier ' +
|
|
35
|
-
'from the pool yourself with `await using querier = await pool.getQuerier()`.');
|
|
36
|
-
}
|
|
37
|
-
return querier;
|
|
38
|
-
}
|
|
39
|
-
/** The ambient querier, or `undefined` outside a transaction. For callers that can work without one. */
|
|
40
|
-
export function currentQuerierIfAny() {
|
|
41
|
-
return querierStorage.getStore();
|
|
42
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import type { IsolationLevel, QuerierPool } from '../type/index.js';
|
|
2
|
-
export type TransactionalOptions = {
|
|
3
|
-
/** `required` opens a transaction when none is active; `supported` joins one but never starts one. */
|
|
4
|
-
readonly propagation?: 'supported' | 'required';
|
|
5
|
-
readonly pool?: QuerierPool;
|
|
6
|
-
readonly isolationLevel?: IsolationLevel;
|
|
7
|
-
};
|
|
8
|
-
/**
|
|
9
|
-
* Wraps the method in a transaction and publishes its querier for {@link currentQuerier} to pick up.
|
|
10
|
-
*
|
|
11
|
-
* @remarks Replaces the `@InjectQuerier()` parameter that used to receive the querier. The standard
|
|
12
|
-
* decorator spec has no parameter decorators, and the separate TC39 proposal for them is still Stage 1,
|
|
13
|
-
* so the querier travels through async-local storage instead. A nested call joins the transaction
|
|
14
|
-
* already in flight rather than opening a second one.
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* class UserService {
|
|
19
|
-
* @Transactional()
|
|
20
|
-
* async register(data: Partial<User>) {
|
|
21
|
-
* await currentQuerier().insertOne(User, data);
|
|
22
|
-
* }
|
|
23
|
-
* }
|
|
24
|
-
* ```
|
|
25
|
-
*/
|
|
26
|
-
export declare function Transactional({ propagation, pool, isolationLevel }?: TransactionalOptions): <This, Args extends unknown[], R>(original: (this: This, ...args: Args) => Promise<R>, context: ClassMethodDecoratorContext<This>) => (this: This, ...args: Args) => Promise<R>;
|
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { getQuerierPool } from '../options.js';
|
|
2
|
-
import { currentQuerierIfAny, withQuerierContext } from './querierContext.js';
|
|
3
|
-
/**
|
|
4
|
-
* Wraps the method in a transaction and publishes its querier for {@link currentQuerier} to pick up.
|
|
5
|
-
*
|
|
6
|
-
* @remarks Replaces the `@InjectQuerier()` parameter that used to receive the querier. The standard
|
|
7
|
-
* decorator spec has no parameter decorators, and the separate TC39 proposal for them is still Stage 1,
|
|
8
|
-
* so the querier travels through async-local storage instead. A nested call joins the transaction
|
|
9
|
-
* already in flight rather than opening a second one.
|
|
10
|
-
*
|
|
11
|
-
* @example
|
|
12
|
-
* ```ts
|
|
13
|
-
* class UserService {
|
|
14
|
-
* @Transactional()
|
|
15
|
-
* async register(data: Partial<User>) {
|
|
16
|
-
* await currentQuerier().insertOne(User, data);
|
|
17
|
-
* }
|
|
18
|
-
* }
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
export function Transactional({ propagation = 'required', pool, isolationLevel } = {}) {
|
|
22
|
-
return (original, context) => {
|
|
23
|
-
// Checked at decoration time rather than on the first call: a synchronous method cannot be wrapped in
|
|
24
|
-
// a transaction, and finding that out at startup beats finding out mid-request.
|
|
25
|
-
if (original.constructor.name !== 'AsyncFunction') {
|
|
26
|
-
throw new TypeError(`@Transactional() needs an async method, but '${String(context.name)}' is not one.`);
|
|
27
|
-
}
|
|
28
|
-
return async function (...args) {
|
|
29
|
-
// Already inside a transactional flow: join it and let the outermost call own commit and release.
|
|
30
|
-
if (currentQuerierIfAny()) {
|
|
31
|
-
return original.apply(this, args);
|
|
32
|
-
}
|
|
33
|
-
// `withQuerier` releases; `transaction` commits or rolls back. `supported` joins a transaction but
|
|
34
|
-
// never starts one, so it takes only the first half.
|
|
35
|
-
return (pool ?? getQuerierPool()).withQuerier((querier) => {
|
|
36
|
-
const run = () => original.apply(this, args);
|
|
37
|
-
return withQuerierContext(querier, () => propagation === 'supported'
|
|
38
|
-
? run()
|
|
39
|
-
: querier.transaction(run, isolationLevel ? { isolationLevel } : undefined));
|
|
40
|
-
});
|
|
41
|
-
};
|
|
42
|
-
};
|
|
43
|
-
}
|