uql-orm 0.22.0 → 0.24.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 +4 -4
- package/dist/browser/uql-browser.min.js.map +3 -3
- package/dist/dialect/abstractSqlDialect.d.ts +14 -1
- package/dist/dialect/abstractSqlDialect.js +21 -14
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +11 -1
- package/dist/dialect/mysqlLikeSqlDialect.js +25 -0
- package/dist/dialect/pgLikeSqlDialect.d.ts +4 -7
- package/dist/dialect/pgLikeSqlDialect.js +7 -12
- package/dist/entity/decorator/bag.d.ts +35 -0
- package/dist/entity/decorator/bag.js +54 -0
- package/dist/entity/decorator/entity.d.ts +28 -2
- package/dist/entity/decorator/entity.js +40 -2
- package/dist/entity/decorator/members.d.ts +51 -0
- package/dist/entity/decorator/members.js +51 -0
- package/dist/entity/index.d.ts +3 -1
- package/dist/entity/index.js +3 -1
- package/dist/entity/metadata/definition.d.ts +23 -9
- package/dist/entity/metadata/definition.js +55 -100
- package/dist/http/contract.js +1 -2
- package/dist/http/handler.js +5 -26
- package/dist/http/query.js +1 -1
- package/dist/migrate/acquireQuerierForMigrations.d.ts +13 -1
- package/dist/migrate/acquireQuerierForMigrations.js +28 -0
- package/dist/migrate/cli-config.js +24 -8
- package/dist/migrate/cli.js +5 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +3 -4
- package/dist/migrate/codegen/index.d.ts +0 -1
- package/dist/migrate/codegen/index.js +0 -2
- package/dist/migrate/drift/driftDetector.d.ts +10 -1
- package/dist/migrate/drift/driftDetector.js +22 -5
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +5 -1
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +17 -27
- package/dist/migrate/introspection/mongoIntrospector.js +7 -13
- package/dist/migrate/migrator.js +44 -81
- package/dist/migrate/storage/databaseStorage.d.ts +2 -0
- package/dist/migrate/storage/databaseStorage.js +8 -23
- package/dist/mongo/mongodbQuerier.js +171 -229
- package/dist/nestjs/uqlContextInterceptor.js +58 -22
- package/dist/nestjs/uqlModule.d.ts +2 -11
- package/dist/nestjs/uqlModule.js +93 -44
- package/dist/postgres/postgresDialect.js +2 -2
- package/dist/querier/abstractQuerier.d.ts +28 -0
- package/dist/querier/abstractQuerier.js +47 -22
- package/dist/querier/abstractQuerierPool.d.ts +21 -1
- package/dist/querier/abstractQuerierPool.js +113 -3
- package/dist/querier/abstractSqlQuerier.d.ts +1 -3
- package/dist/querier/abstractSqlQuerier.js +51 -96
- package/dist/querier/index.d.ts +3 -1
- package/dist/querier/index.js +3 -1
- package/dist/querier/querierContext.browser.d.ts +12 -0
- package/dist/querier/querierContext.browser.js +18 -0
- package/dist/querier/querierContext.d.ts +22 -0
- package/dist/querier/querierContext.js +42 -0
- package/dist/querier/queryError.d.ts +22 -0
- package/dist/querier/queryError.js +20 -0
- package/dist/querier/transactional.d.ts +26 -0
- package/dist/querier/transactional.js +43 -0
- package/dist/schema/index.d.ts +1 -1
- package/dist/schema/schemaASTBuilder.d.ts +1 -1
- package/dist/schema/schemaASTBuilder.js +2 -2
- package/dist/schema/types.d.ts +0 -21
- package/dist/sqlite/abstractSqliteQuerier.d.ts +9 -2
- package/dist/sqlite/hranaQuerier.js +20 -42
- package/dist/sqlite/index.d.ts +1 -0
- package/dist/sqlite/index.js +1 -0
- package/dist/sqlite/localSqliteQuerierPool.d.ts +31 -0
- package/dist/sqlite/localSqliteQuerierPool.js +34 -0
- package/dist/sqlite/nodeSqliteAdapter.d.ts +35 -0
- package/dist/sqlite/nodeSqliteAdapter.js +28 -0
- package/dist/sqlite/nodeSqliteQuerierPool.d.ts +28 -0
- package/dist/sqlite/nodeSqliteQuerierPool.js +29 -0
- package/dist/sqlite/sqliteDialect.d.ts +1 -2
- package/dist/sqlite/sqliteDialect.js +0 -13
- package/dist/sqlite/sqliteQuerierPool.d.ts +10 -23
- package/dist/sqlite/sqliteQuerierPool.js +9 -28
- package/dist/type/entity.d.ts +95 -28
- package/dist/type/querier.d.ts +8 -35
- package/dist/type/querierPool.d.ts +13 -12
- package/dist/type/universalQuerier.d.ts +23 -7
- package/dist/util/hook.util.js +1 -1
- package/package.json +7 -16
- package/dist/entity/decorator/field.d.ts +0 -2
- package/dist/entity/decorator/field.js +0 -7
- package/dist/entity/decorator/filter.d.ts +0 -7
- package/dist/entity/decorator/filter.js +0 -11
- package/dist/entity/decorator/hook.d.ts +0 -7
- package/dist/entity/decorator/hook.js +0 -14
- package/dist/entity/decorator/id.d.ts +0 -2
- package/dist/entity/decorator/id.js +0 -7
- package/dist/entity/decorator/index-decorator.d.ts +0 -22
- package/dist/entity/decorator/index-decorator.js +0 -26
- package/dist/entity/decorator/index.d.ts +0 -8
- package/dist/entity/decorator/index.js +0 -8
- package/dist/entity/decorator/relation.d.ts +0 -8
- package/dist/entity/decorator/relation.js +0 -19
- package/dist/migrate/codegen/smartRelationDetector.d.ts +0 -47
- package/dist/migrate/codegen/smartRelationDetector.js +0 -134
- package/dist/querier/decorator/index.d.ts +0 -4
- package/dist/querier/decorator/index.js +0 -4
- package/dist/querier/decorator/injectQuerier.d.ts +0 -3
- package/dist/querier/decorator/injectQuerier.js +0 -33
- package/dist/querier/decorator/log.d.ts +0 -32
- package/dist/querier/decorator/log.js +0 -52
- package/dist/querier/decorator/serialized.d.ts +0 -6
- package/dist/querier/decorator/serialized.js +0 -13
- package/dist/querier/decorator/transactional.d.ts +0 -6
- package/dist/querier/decorator/transactional.js +0 -48
|
@@ -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/dist/util/hook.util.js
CHANGED
package/package.json
CHANGED
|
@@ -3,16 +3,17 @@
|
|
|
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.0",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"engines": {
|
|
9
|
-
"node": ">=
|
|
9
|
+
"node": ">=24"
|
|
10
10
|
},
|
|
11
11
|
"sideEffects": false,
|
|
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"
|
|
15
|
+
"./dist/context/context.js": "./dist/context/context.browser.js",
|
|
16
|
+
"./dist/querier/querierContext.js": "./dist/querier/querierContext.browser.js"
|
|
16
17
|
},
|
|
17
18
|
"bin": {
|
|
18
19
|
"uql-migrate": "./dist/migrate/bin.js"
|
|
@@ -72,13 +73,11 @@
|
|
|
72
73
|
"@tursodatabase/serverless": ">=1.0.0",
|
|
73
74
|
"better-sqlite3": ">=9.0.0",
|
|
74
75
|
"express": ">=5.0.0",
|
|
75
|
-
"jiti": ">=2.0.0",
|
|
76
76
|
"mariadb": ">=3.0.0",
|
|
77
77
|
"mongodb": ">=6.0.0",
|
|
78
78
|
"mysql2": ">=3.0.0",
|
|
79
79
|
"pg": ">=8.0.0",
|
|
80
80
|
"pg-query-stream": ">=4.0.0",
|
|
81
|
-
"reflect-metadata": ">=0.2.0",
|
|
82
81
|
"rxjs": ">=7.0.0"
|
|
83
82
|
},
|
|
84
83
|
"peerDependenciesMeta": {
|
|
@@ -106,9 +105,6 @@
|
|
|
106
105
|
"express": {
|
|
107
106
|
"optional": true
|
|
108
107
|
},
|
|
109
|
-
"jiti": {
|
|
110
|
-
"optional": true
|
|
111
|
-
},
|
|
112
108
|
"mariadb": {
|
|
113
109
|
"optional": true
|
|
114
110
|
},
|
|
@@ -124,9 +120,6 @@
|
|
|
124
120
|
"pg-query-stream": {
|
|
125
121
|
"optional": true
|
|
126
122
|
},
|
|
127
|
-
"reflect-metadata": {
|
|
128
|
-
"optional": true
|
|
129
|
-
},
|
|
130
123
|
"rxjs": {
|
|
131
124
|
"optional": true
|
|
132
125
|
}
|
|
@@ -139,19 +132,17 @@
|
|
|
139
132
|
"@nestjs/testing": "^11.1.28",
|
|
140
133
|
"@tursodatabase/database": "^0.7.2",
|
|
141
134
|
"@tursodatabase/serverless": "^1.4.0",
|
|
142
|
-
"@types/better-sqlite3": "^
|
|
135
|
+
"@types/better-sqlite3": "^9.6.0",
|
|
143
136
|
"@types/express": "^5.0.6",
|
|
144
|
-
"@types/pg": "^8.20.
|
|
137
|
+
"@types/pg": "^8.20.3",
|
|
145
138
|
"@types/ws": "^8.18.1",
|
|
146
139
|
"better-sqlite3": "^13.0.2",
|
|
147
140
|
"express": "^5.2.1",
|
|
148
|
-
"jiti": "^2.7.0",
|
|
149
141
|
"mariadb": "^3.5.3",
|
|
150
142
|
"mongodb": "^7.5.0",
|
|
151
143
|
"mysql2": "^3.23.2",
|
|
152
144
|
"pg": "^8.22.0",
|
|
153
145
|
"pg-query-stream": "^4.16.0",
|
|
154
|
-
"reflect-metadata": "^0.2.2",
|
|
155
146
|
"rxjs": "^7.8.2",
|
|
156
147
|
"sqlite-vec": "^0.1.9",
|
|
157
148
|
"ws": "^8.21.1"
|
|
@@ -207,5 +198,5 @@
|
|
|
207
198
|
"publishConfig": {
|
|
208
199
|
"access": "public"
|
|
209
200
|
},
|
|
210
|
-
"gitHead": "
|
|
201
|
+
"gitHead": "d9499a4b8c5ee866950b397fb87f55643d86fc39"
|
|
211
202
|
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { FilterOptions, Type } from '../../type/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Registers a named, default-on `$where` filter on the entity, applied to every query
|
|
4
|
-
* unless bypassed via `QueryOptions.filters`.
|
|
5
|
-
* @example `@Filter('active', { condition: { status: 'active' }, default: false })`
|
|
6
|
-
*/
|
|
7
|
-
export declare function Filter<E>(name: string, opts: FilterOptions<E>): (entity: Type<E>) => void;
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { defineFilter } from '../metadata/definition.js';
|
|
2
|
-
/**
|
|
3
|
-
* Registers a named, default-on `$where` filter on the entity, applied to every query
|
|
4
|
-
* unless bypassed via `QueryOptions.filters`.
|
|
5
|
-
* @example `@Filter('active', { condition: { status: 'active' }, default: false })`
|
|
6
|
-
*/
|
|
7
|
-
export function Filter(name, opts) {
|
|
8
|
-
return (entity) => {
|
|
9
|
-
defineFilter(entity, name, opts);
|
|
10
|
-
};
|
|
11
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
export declare const BeforeInsert: () => MethodDecorator;
|
|
2
|
-
export declare const AfterInsert: () => MethodDecorator;
|
|
3
|
-
export declare const BeforeUpdate: () => MethodDecorator;
|
|
4
|
-
export declare const AfterUpdate: () => MethodDecorator;
|
|
5
|
-
export declare const BeforeDelete: () => MethodDecorator;
|
|
6
|
-
export declare const AfterDelete: () => MethodDecorator;
|
|
7
|
-
export declare const AfterLoad: () => MethodDecorator;
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { defineHook } from '../metadata/definition.js';
|
|
2
|
-
function createHookDecorator(event) {
|
|
3
|
-
return () => (target, key) => {
|
|
4
|
-
const entity = target.constructor;
|
|
5
|
-
defineHook(entity, String(key), event);
|
|
6
|
-
};
|
|
7
|
-
}
|
|
8
|
-
export const BeforeInsert = createHookDecorator('beforeInsert');
|
|
9
|
-
export const AfterInsert = createHookDecorator('afterInsert');
|
|
10
|
-
export const BeforeUpdate = createHookDecorator('beforeUpdate');
|
|
11
|
-
export const AfterUpdate = createHookDecorator('afterUpdate');
|
|
12
|
-
export const BeforeDelete = createHookDecorator('beforeDelete');
|
|
13
|
-
export const AfterDelete = createHookDecorator('afterDelete');
|
|
14
|
-
export const AfterLoad = createHookDecorator('afterLoad');
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import type { IndexColumnInput, IndexOptions, Type } from '../../type/index.js';
|
|
2
|
-
/**
|
|
3
|
-
* Define a composite index on an entity class.
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* ```ts
|
|
7
|
-
* @Index(['lastName', 'firstName'], { name: 'idx_users_fullname' })
|
|
8
|
-
* @Entity()
|
|
9
|
-
* export class User {
|
|
10
|
-
* @Id() id?: number;
|
|
11
|
-
* @Field() firstName?: string;
|
|
12
|
-
* @Field() lastName?: string;
|
|
13
|
-
* }
|
|
14
|
-
*
|
|
15
|
-
* // With unique and partial index
|
|
16
|
-
* @Index(['email'], { unique: true })
|
|
17
|
-
* @Index(['status'], { where: "status = 'active'" })
|
|
18
|
-
* @Entity()
|
|
19
|
-
* export class User { ... }
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
export declare function Index<E>(columns: readonly IndexColumnInput[], options?: IndexOptions): (target: Type<E>) => void;
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { defineIndex } from '../metadata/definition.js';
|
|
2
|
-
/**
|
|
3
|
-
* Define a composite index on an entity class.
|
|
4
|
-
*
|
|
5
|
-
* @example
|
|
6
|
-
* ```ts
|
|
7
|
-
* @Index(['lastName', 'firstName'], { name: 'idx_users_fullname' })
|
|
8
|
-
* @Entity()
|
|
9
|
-
* export class User {
|
|
10
|
-
* @Id() id?: number;
|
|
11
|
-
* @Field() firstName?: string;
|
|
12
|
-
* @Field() lastName?: string;
|
|
13
|
-
* }
|
|
14
|
-
*
|
|
15
|
-
* // With unique and partial index
|
|
16
|
-
* @Index(['email'], { unique: true })
|
|
17
|
-
* @Index(['status'], { where: "status = 'active'" })
|
|
18
|
-
* @Entity()
|
|
19
|
-
* export class User { ... }
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
export function Index(columns, options = {}) {
|
|
23
|
-
return (target) => {
|
|
24
|
-
defineIndex(target, { ...options, columns });
|
|
25
|
-
};
|
|
26
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, getEntities, getMeta, } from '../metadata/definition.js';
|
|
2
|
-
export * from './entity.js';
|
|
3
|
-
export * from './field.js';
|
|
4
|
-
export * from './filter.js';
|
|
5
|
-
export * from './hook.js';
|
|
6
|
-
export * from './id.js';
|
|
7
|
-
export * from './index-decorator.js';
|
|
8
|
-
export * from './relation.js';
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export { defineEntity, defineField, defineFilter, defineHook, defineId, defineIndex, defineRelation, getEntities, getMeta, } from '../metadata/definition.js';
|
|
2
|
-
export * from './entity.js';
|
|
3
|
-
export * from './field.js';
|
|
4
|
-
export * from './filter.js';
|
|
5
|
-
export * from './hook.js';
|
|
6
|
-
export * from './id.js';
|
|
7
|
-
export * from './index-decorator.js';
|
|
8
|
-
export * from './relation.js';
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import type { RelationManyToManyOptions, RelationManyToOneOptions, RelationOneToManyOptions, RelationOneToOneOptions, RelationOptions } from '../../type/index.js';
|
|
2
|
-
declare function Relation<E>(opts: RelationOptions<E>): (target: object, key: string) => void;
|
|
3
|
-
type RelationReturn = ReturnType<typeof Relation>;
|
|
4
|
-
export declare function OneToOne<E>(opts?: RelationOneToOneOptions<E>): RelationReturn;
|
|
5
|
-
export declare function ManyToOne<E>(opts?: RelationManyToOneOptions<E>): RelationReturn;
|
|
6
|
-
export declare function OneToMany<E>(opts: RelationOneToManyOptions<E>): RelationReturn;
|
|
7
|
-
export declare function ManyToMany<E>(opts: RelationManyToManyOptions<E>): RelationReturn;
|
|
8
|
-
export {};
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import { defineRelation } from '../metadata/definition.js';
|
|
2
|
-
function Relation(opts) {
|
|
3
|
-
return (target, key) => {
|
|
4
|
-
const entity = target.constructor;
|
|
5
|
-
defineRelation(entity, key, opts);
|
|
6
|
-
};
|
|
7
|
-
}
|
|
8
|
-
export function OneToOne(opts) {
|
|
9
|
-
return Relation({ cardinality: '11', ...opts });
|
|
10
|
-
}
|
|
11
|
-
export function ManyToOne(opts) {
|
|
12
|
-
return Relation({ cardinality: 'm1', ...opts });
|
|
13
|
-
}
|
|
14
|
-
export function OneToMany(opts) {
|
|
15
|
-
return Relation({ cardinality: '1m', ...opts });
|
|
16
|
-
}
|
|
17
|
-
export function ManyToMany(opts) {
|
|
18
|
-
return Relation({ cardinality: 'mm', ...opts });
|
|
19
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart Relation Detector
|
|
3
|
-
*
|
|
4
|
-
* Uses heuristics and schema analysis to detect relationships between tables:
|
|
5
|
-
* - Explicit foreign keys (highest confidence)
|
|
6
|
-
* - Junction tables for ManyToMany
|
|
7
|
-
* - Unique FK columns for OneToOne
|
|
8
|
-
*/
|
|
9
|
-
import type { SchemaAST } from '../../schema/schemaAST.js';
|
|
10
|
-
import type { DetectedRelation } from '../../schema/types.js';
|
|
11
|
-
/**
|
|
12
|
-
* Configuration for relation detection.
|
|
13
|
-
*/
|
|
14
|
-
export interface RelationDetectorOptions {
|
|
15
|
-
/** Minimum confidence threshold (0-1) to include in results */
|
|
16
|
-
minConfidence?: number;
|
|
17
|
-
/** Custom singularize function */
|
|
18
|
-
singularize?: (name: string) => string;
|
|
19
|
-
}
|
|
20
|
-
/**
|
|
21
|
-
* Detects relationships in a SchemaAST using multiple heuristics.
|
|
22
|
-
*/
|
|
23
|
-
export declare class SmartRelationDetector {
|
|
24
|
-
private readonly ast;
|
|
25
|
-
private readonly options;
|
|
26
|
-
constructor(ast: SchemaAST, options?: RelationDetectorOptions);
|
|
27
|
-
/**
|
|
28
|
-
* Detect all relationships in the schema.
|
|
29
|
-
*/
|
|
30
|
-
detectAll(): DetectedRelation[];
|
|
31
|
-
/**
|
|
32
|
-
* Detect junction tables that represent ManyToMany relationships.
|
|
33
|
-
*/
|
|
34
|
-
private detectJunctionTables;
|
|
35
|
-
/**
|
|
36
|
-
* Detect relations where unique FK should upgrade to OneToOne.
|
|
37
|
-
*/
|
|
38
|
-
private detectOneToOneRelations;
|
|
39
|
-
/**
|
|
40
|
-
* Default singularize function (delegates to shared utility).
|
|
41
|
-
*/
|
|
42
|
-
private defaultSingularize;
|
|
43
|
-
}
|
|
44
|
-
/**
|
|
45
|
-
* Create a SmartRelationDetector for the given AST.
|
|
46
|
-
*/
|
|
47
|
-
export declare function createRelationDetector(ast: SchemaAST, options?: RelationDetectorOptions): SmartRelationDetector;
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Smart Relation Detector
|
|
3
|
-
*
|
|
4
|
-
* Uses heuristics and schema analysis to detect relationships between tables:
|
|
5
|
-
* - Explicit foreign keys (highest confidence)
|
|
6
|
-
* - Junction tables for ManyToMany
|
|
7
|
-
* - Unique FK columns for OneToOne
|
|
8
|
-
*/
|
|
9
|
-
import { singularize } from '../../util/string.util.js';
|
|
10
|
-
/**
|
|
11
|
-
* Detects relationships in a SchemaAST using multiple heuristics.
|
|
12
|
-
*/
|
|
13
|
-
export class SmartRelationDetector {
|
|
14
|
-
ast;
|
|
15
|
-
options;
|
|
16
|
-
constructor(ast, options = {}) {
|
|
17
|
-
this.ast = ast;
|
|
18
|
-
this.options = {
|
|
19
|
-
minConfidence: options.minConfidence ?? 0.5,
|
|
20
|
-
singularize: options.singularize ?? this.defaultSingularize.bind(this),
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
/**
|
|
24
|
-
* Detect all relationships in the schema.
|
|
25
|
-
*/
|
|
26
|
-
detectAll() {
|
|
27
|
-
const relations = [];
|
|
28
|
-
// 1. Add explicit FK relationships (confidence: 1.0)
|
|
29
|
-
for (const rel of this.ast.relationships) {
|
|
30
|
-
relations.push({
|
|
31
|
-
type: rel.type,
|
|
32
|
-
from: {
|
|
33
|
-
table: rel.from.table,
|
|
34
|
-
columns: rel.from.columns,
|
|
35
|
-
},
|
|
36
|
-
to: {
|
|
37
|
-
table: rel.to.table,
|
|
38
|
-
columns: rel.to.columns,
|
|
39
|
-
},
|
|
40
|
-
through: rel.through,
|
|
41
|
-
confidence: 1.0,
|
|
42
|
-
source: 'explicit_fk',
|
|
43
|
-
});
|
|
44
|
-
}
|
|
45
|
-
// 2. Detect junction tables (ManyToMany)
|
|
46
|
-
const junctionRelations = this.detectJunctionTables();
|
|
47
|
-
relations.push(...junctionRelations);
|
|
48
|
-
// 3. Detect unique FK -> OneToOne upgrades
|
|
49
|
-
const oneToOneUpgrades = this.detectOneToOneRelations(relations);
|
|
50
|
-
relations.push(...oneToOneUpgrades);
|
|
51
|
-
// Filter by confidence
|
|
52
|
-
return relations.filter((r) => r.confidence >= this.options.minConfidence);
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* Detect junction tables that represent ManyToMany relationships.
|
|
56
|
-
*/
|
|
57
|
-
detectJunctionTables() {
|
|
58
|
-
const relations = [];
|
|
59
|
-
for (const table of this.ast.tables.values()) {
|
|
60
|
-
if (!this.ast.isJunctionTable(table))
|
|
61
|
-
continue;
|
|
62
|
-
const outgoingRels = table.outgoingRelations;
|
|
63
|
-
if (outgoingRels.length !== 2)
|
|
64
|
-
continue;
|
|
65
|
-
const [rel1, rel2] = outgoingRels;
|
|
66
|
-
// Create ManyToMany relation
|
|
67
|
-
relations.push({
|
|
68
|
-
type: 'ManyToMany',
|
|
69
|
-
from: {
|
|
70
|
-
table: rel1.to.table,
|
|
71
|
-
columns: rel1.to.columns,
|
|
72
|
-
},
|
|
73
|
-
to: {
|
|
74
|
-
table: rel2.to.table,
|
|
75
|
-
columns: rel2.to.columns,
|
|
76
|
-
},
|
|
77
|
-
through: table,
|
|
78
|
-
confidence: 0.95,
|
|
79
|
-
source: 'junction_table',
|
|
80
|
-
});
|
|
81
|
-
// Also create inverse relation
|
|
82
|
-
relations.push({
|
|
83
|
-
type: 'ManyToMany',
|
|
84
|
-
from: {
|
|
85
|
-
table: rel2.to.table,
|
|
86
|
-
columns: rel2.to.columns,
|
|
87
|
-
},
|
|
88
|
-
to: {
|
|
89
|
-
table: rel1.to.table,
|
|
90
|
-
columns: rel1.to.columns,
|
|
91
|
-
},
|
|
92
|
-
through: table,
|
|
93
|
-
confidence: 0.95,
|
|
94
|
-
source: 'junction_table',
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
return relations;
|
|
98
|
-
}
|
|
99
|
-
/**
|
|
100
|
-
* Detect relations where unique FK should upgrade to OneToOne.
|
|
101
|
-
*/
|
|
102
|
-
detectOneToOneRelations(existingRelations) {
|
|
103
|
-
const upgrades = [];
|
|
104
|
-
for (const rel of existingRelations) {
|
|
105
|
-
if (rel.source !== 'explicit_fk')
|
|
106
|
-
continue;
|
|
107
|
-
const fromCol = rel.from.columns[0];
|
|
108
|
-
if (fromCol?.isUnique && rel.type === 'ManyToOne') {
|
|
109
|
-
// Upgrade to OneToOne
|
|
110
|
-
upgrades.push({
|
|
111
|
-
type: 'OneToOne',
|
|
112
|
-
from: rel.from,
|
|
113
|
-
to: rel.to,
|
|
114
|
-
through: rel.through,
|
|
115
|
-
confidence: 0.9,
|
|
116
|
-
source: 'unique_fk',
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return upgrades;
|
|
121
|
-
}
|
|
122
|
-
/**
|
|
123
|
-
* Default singularize function (delegates to shared utility).
|
|
124
|
-
*/
|
|
125
|
-
defaultSingularize(name) {
|
|
126
|
-
return singularize(name);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Create a SmartRelationDetector for the given AST.
|
|
131
|
-
*/
|
|
132
|
-
export function createRelationDetector(ast, options) {
|
|
133
|
-
return new SmartRelationDetector(ast, options);
|
|
134
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
const metadataKey = Symbol('InjectQuerier');
|
|
2
|
-
export function InjectQuerier() {
|
|
3
|
-
// biome-ignore lint/suspicious/noExplicitAny: `any` is required - class prototypes don't satisfy Record<K, unknown>
|
|
4
|
-
return (proto, key, index) => {
|
|
5
|
-
if (!proto[metadataKey]) {
|
|
6
|
-
proto[metadataKey] = new WeakMap();
|
|
7
|
-
}
|
|
8
|
-
if (!proto[metadataKey].has(proto.constructor)) {
|
|
9
|
-
proto[metadataKey].set(proto.constructor, {});
|
|
10
|
-
}
|
|
11
|
-
const meta = proto[metadataKey].get(proto.constructor);
|
|
12
|
-
const isAlreadyInjected = key in meta;
|
|
13
|
-
if (isAlreadyInjected) {
|
|
14
|
-
throw new TypeError(`@InjectQuerier() can only appears once in '${proto.constructor.name}.${key}'}`);
|
|
15
|
-
}
|
|
16
|
-
meta[key] = index;
|
|
17
|
-
};
|
|
18
|
-
}
|
|
19
|
-
export function getInjectedQuerierIndex(service, key) {
|
|
20
|
-
let proto = service.prototype;
|
|
21
|
-
while (proto.constructor !== Object) {
|
|
22
|
-
const meta = proto[metadataKey]?.get(proto.constructor);
|
|
23
|
-
if (meta && key in meta) {
|
|
24
|
-
return meta[key];
|
|
25
|
-
}
|
|
26
|
-
const keys = Object.getOwnPropertyNames(proto);
|
|
27
|
-
const isOwnKey = keys.includes(key);
|
|
28
|
-
if (isOwnKey) {
|
|
29
|
-
return;
|
|
30
|
-
}
|
|
31
|
-
proto = Object.getPrototypeOf(proto);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import type { LoggerWrapper } from '../../util/logger.js';
|
|
2
|
-
/**
|
|
3
|
-
* A driver error enriched with context by {@link enrichError}. `query` is always attached; `values`
|
|
4
|
-
* only when the querier's logger is configured to surface them (see {@link enrichError}) - they can
|
|
5
|
-
* carry sensitive data (PII, tokens, etc.) and would otherwise leak into whatever error-tracking
|
|
6
|
-
* pipeline (Sentry, console.error, ...) serializes the error, without the developer opting in.
|
|
7
|
-
*/
|
|
8
|
-
export interface QueryError extends Error {
|
|
9
|
-
query?: string;
|
|
10
|
-
values?: unknown[];
|
|
11
|
-
}
|
|
12
|
-
/**
|
|
13
|
-
* Tags `err` with the query it failed on (as `QueryError`) and re-throws. `values` is only attached
|
|
14
|
-
* when `logger?.willLogValues()` is true - i.e. the app already has query values surfacing somewhere
|
|
15
|
-
* (query-level or slow-query logging), so attaching them here doesn't introduce a new leak surface.
|
|
16
|
-
* Shared by every query call site - `@Log()`, streams, transaction statements - so this logic lives
|
|
17
|
-
* in one place.
|
|
18
|
-
*
|
|
19
|
-
* Not typed `never`: an async `catch` that calls a `never`-returning function ahead of a `finally`
|
|
20
|
-
* containing an `if` trips a TS control-flow bug (`TS7027 Unreachable code`) unrelated to this logic.
|
|
21
|
-
*/
|
|
22
|
-
export declare function enrichError(err: unknown, logger: LoggerWrapper | undefined, query: string, values?: unknown[]): void;
|
|
23
|
-
/**
|
|
24
|
-
* Decorator that logs the execution of a query method.
|
|
25
|
-
* It tracks execution time and logs the query, parameters, and duration.
|
|
26
|
-
* The decorated class must have a `logger` property of type LoggerWrapper.
|
|
27
|
-
*
|
|
28
|
-
* On failure, also attaches the query (and values, when the logger surfaces them) to the thrown
|
|
29
|
-
* error via {@link enrichError} - so that context survives even when nothing gets logged, or is
|
|
30
|
-
* printed in full when the app already opted into it.
|
|
31
|
-
*/
|
|
32
|
-
export declare function Log(): (_target: object, _key: string, propDescriptor: PropertyDescriptor) => void;
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tags `err` with the query it failed on (as `QueryError`) and re-throws. `values` is only attached
|
|
3
|
-
* when `logger?.willLogValues()` is true - i.e. the app already has query values surfacing somewhere
|
|
4
|
-
* (query-level or slow-query logging), so attaching them here doesn't introduce a new leak surface.
|
|
5
|
-
* Shared by every query call site - `@Log()`, streams, transaction statements - so this logic lives
|
|
6
|
-
* in one place.
|
|
7
|
-
*
|
|
8
|
-
* Not typed `never`: an async `catch` that calls a `never`-returning function ahead of a `finally`
|
|
9
|
-
* containing an `if` trips a TS control-flow bug (`TS7027 Unreachable code`) unrelated to this logic.
|
|
10
|
-
*/
|
|
11
|
-
export function enrichError(err, logger, query, values) {
|
|
12
|
-
if (err instanceof Error) {
|
|
13
|
-
const queryError = err;
|
|
14
|
-
queryError.query ??= query;
|
|
15
|
-
if (values !== undefined && logger?.willLogValues()) {
|
|
16
|
-
queryError.values ??= values;
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
throw err;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Decorator that logs the execution of a query method.
|
|
23
|
-
* It tracks execution time and logs the query, parameters, and duration.
|
|
24
|
-
* The decorated class must have a `logger` property of type LoggerWrapper.
|
|
25
|
-
*
|
|
26
|
-
* On failure, also attaches the query (and values, when the logger surfaces them) to the thrown
|
|
27
|
-
* error via {@link enrichError} - so that context survives even when nothing gets logged, or is
|
|
28
|
-
* printed in full when the app already opted into it.
|
|
29
|
-
*/
|
|
30
|
-
export function Log() {
|
|
31
|
-
return (_target, _key, propDescriptor) => {
|
|
32
|
-
const originalMethod = propDescriptor.value;
|
|
33
|
-
propDescriptor.value = async function (...args) {
|
|
34
|
-
const startTime = performance.now();
|
|
35
|
-
const isSql = typeof args[0] === 'string';
|
|
36
|
-
const query = isSql ? args[0] : _key;
|
|
37
|
-
const values = isSql ? args[1] : args;
|
|
38
|
-
try {
|
|
39
|
-
return await originalMethod.apply(this, args);
|
|
40
|
-
}
|
|
41
|
-
catch (err) {
|
|
42
|
-
enrichError(err, this.logger, query, values);
|
|
43
|
-
}
|
|
44
|
-
finally {
|
|
45
|
-
if (this.logger) {
|
|
46
|
-
const duration = performance.now() - startTime;
|
|
47
|
-
this.logger.logQuery(query, values, Math.round(duration));
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
};
|
|
51
|
-
};
|
|
52
|
-
}
|