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.
Files changed (107) hide show
  1. package/README.md +4 -4
  2. package/dist/browser/uql-browser.min.js.map +3 -3
  3. package/dist/dialect/abstractSqlDialect.d.ts +14 -1
  4. package/dist/dialect/abstractSqlDialect.js +21 -14
  5. package/dist/dialect/mysqlLikeSqlDialect.d.ts +11 -1
  6. package/dist/dialect/mysqlLikeSqlDialect.js +25 -0
  7. package/dist/dialect/pgLikeSqlDialect.d.ts +4 -7
  8. package/dist/dialect/pgLikeSqlDialect.js +7 -12
  9. package/dist/entity/decorator/bag.d.ts +35 -0
  10. package/dist/entity/decorator/bag.js +54 -0
  11. package/dist/entity/decorator/entity.d.ts +28 -2
  12. package/dist/entity/decorator/entity.js +40 -2
  13. package/dist/entity/decorator/members.d.ts +51 -0
  14. package/dist/entity/decorator/members.js +51 -0
  15. package/dist/entity/index.d.ts +3 -1
  16. package/dist/entity/index.js +3 -1
  17. package/dist/entity/metadata/definition.d.ts +23 -9
  18. package/dist/entity/metadata/definition.js +55 -100
  19. package/dist/http/contract.js +1 -2
  20. package/dist/http/handler.js +5 -26
  21. package/dist/http/query.js +1 -1
  22. package/dist/migrate/acquireQuerierForMigrations.d.ts +13 -1
  23. package/dist/migrate/acquireQuerierForMigrations.js +28 -0
  24. package/dist/migrate/cli-config.js +24 -8
  25. package/dist/migrate/cli.js +5 -1
  26. package/dist/migrate/codegen/entityCodeGenerator.js +3 -4
  27. package/dist/migrate/codegen/index.d.ts +0 -1
  28. package/dist/migrate/codegen/index.js +0 -2
  29. package/dist/migrate/drift/driftDetector.d.ts +10 -1
  30. package/dist/migrate/drift/driftDetector.js +22 -5
  31. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +5 -1
  32. package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +17 -27
  33. package/dist/migrate/introspection/mongoIntrospector.js +7 -13
  34. package/dist/migrate/migrator.js +44 -81
  35. package/dist/migrate/storage/databaseStorage.d.ts +2 -0
  36. package/dist/migrate/storage/databaseStorage.js +8 -23
  37. package/dist/mongo/mongodbQuerier.js +171 -229
  38. package/dist/nestjs/uqlContextInterceptor.js +58 -22
  39. package/dist/nestjs/uqlModule.d.ts +2 -11
  40. package/dist/nestjs/uqlModule.js +93 -44
  41. package/dist/postgres/postgresDialect.js +2 -2
  42. package/dist/querier/abstractQuerier.d.ts +28 -0
  43. package/dist/querier/abstractQuerier.js +47 -22
  44. package/dist/querier/abstractQuerierPool.d.ts +21 -1
  45. package/dist/querier/abstractQuerierPool.js +113 -3
  46. package/dist/querier/abstractSqlQuerier.d.ts +1 -3
  47. package/dist/querier/abstractSqlQuerier.js +51 -96
  48. package/dist/querier/index.d.ts +3 -1
  49. package/dist/querier/index.js +3 -1
  50. package/dist/querier/querierContext.browser.d.ts +12 -0
  51. package/dist/querier/querierContext.browser.js +18 -0
  52. package/dist/querier/querierContext.d.ts +22 -0
  53. package/dist/querier/querierContext.js +42 -0
  54. package/dist/querier/queryError.d.ts +22 -0
  55. package/dist/querier/queryError.js +20 -0
  56. package/dist/querier/transactional.d.ts +26 -0
  57. package/dist/querier/transactional.js +43 -0
  58. package/dist/schema/index.d.ts +1 -1
  59. package/dist/schema/schemaASTBuilder.d.ts +1 -1
  60. package/dist/schema/schemaASTBuilder.js +2 -2
  61. package/dist/schema/types.d.ts +0 -21
  62. package/dist/sqlite/abstractSqliteQuerier.d.ts +9 -2
  63. package/dist/sqlite/hranaQuerier.js +20 -42
  64. package/dist/sqlite/index.d.ts +1 -0
  65. package/dist/sqlite/index.js +1 -0
  66. package/dist/sqlite/localSqliteQuerierPool.d.ts +31 -0
  67. package/dist/sqlite/localSqliteQuerierPool.js +34 -0
  68. package/dist/sqlite/nodeSqliteAdapter.d.ts +35 -0
  69. package/dist/sqlite/nodeSqliteAdapter.js +28 -0
  70. package/dist/sqlite/nodeSqliteQuerierPool.d.ts +28 -0
  71. package/dist/sqlite/nodeSqliteQuerierPool.js +29 -0
  72. package/dist/sqlite/sqliteDialect.d.ts +1 -2
  73. package/dist/sqlite/sqliteDialect.js +0 -13
  74. package/dist/sqlite/sqliteQuerierPool.d.ts +10 -23
  75. package/dist/sqlite/sqliteQuerierPool.js +9 -28
  76. package/dist/type/entity.d.ts +95 -28
  77. package/dist/type/querier.d.ts +8 -35
  78. package/dist/type/querierPool.d.ts +13 -12
  79. package/dist/type/universalQuerier.d.ts +23 -7
  80. package/dist/util/hook.util.js +1 -1
  81. package/package.json +7 -16
  82. package/dist/entity/decorator/field.d.ts +0 -2
  83. package/dist/entity/decorator/field.js +0 -7
  84. package/dist/entity/decorator/filter.d.ts +0 -7
  85. package/dist/entity/decorator/filter.js +0 -11
  86. package/dist/entity/decorator/hook.d.ts +0 -7
  87. package/dist/entity/decorator/hook.js +0 -14
  88. package/dist/entity/decorator/id.d.ts +0 -2
  89. package/dist/entity/decorator/id.js +0 -7
  90. package/dist/entity/decorator/index-decorator.d.ts +0 -22
  91. package/dist/entity/decorator/index-decorator.js +0 -26
  92. package/dist/entity/decorator/index.d.ts +0 -8
  93. package/dist/entity/decorator/index.js +0 -8
  94. package/dist/entity/decorator/relation.d.ts +0 -8
  95. package/dist/entity/decorator/relation.js +0 -19
  96. package/dist/migrate/codegen/smartRelationDetector.d.ts +0 -47
  97. package/dist/migrate/codegen/smartRelationDetector.js +0 -134
  98. package/dist/querier/decorator/index.d.ts +0 -4
  99. package/dist/querier/decorator/index.js +0 -4
  100. package/dist/querier/decorator/injectQuerier.d.ts +0 -3
  101. package/dist/querier/decorator/injectQuerier.js +0 -33
  102. package/dist/querier/decorator/log.d.ts +0 -32
  103. package/dist/querier/decorator/log.js +0 -52
  104. package/dist/querier/decorator/serialized.d.ts +0 -6
  105. package/dist/querier/decorator/serialized.js +0 -13
  106. package/dist/querier/decorator/transactional.d.ts +0 -6
  107. 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
- * inserts a record.
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
- * Inserts many records.
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?<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
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?<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
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?<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: 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?<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: 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?<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
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
@@ -1,4 +1,4 @@
1
- import { getMeta } from '../entity/decorator/index.js';
1
+ import { getMeta } from '../entity/index.js';
2
2
  /**
3
3
  * Run all registered hooks for the given event on each payload.
4
4
  * Hooks are invoked with `this` bound to the payload via `call`,
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.22.0",
6
+ "version": "0.24.0",
7
7
  "type": "module",
8
8
  "engines": {
9
- "node": ">=20"
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": "^7.6.13",
135
+ "@types/better-sqlite3": "^9.6.0",
143
136
  "@types/express": "^5.0.6",
144
- "@types/pg": "^8.20.1",
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": "0804fc0205c1f629e497df020835a17b63aed0e8"
201
+ "gitHead": "d9499a4b8c5ee866950b397fb87f55643d86fc39"
211
202
  }
@@ -1,2 +0,0 @@
1
- import type { FieldOptions } from '../../type/index.js';
2
- export declare function Field<E>(opts?: FieldOptions): (target: object, key: string) => void;
@@ -1,7 +0,0 @@
1
- import { defineField } from '../metadata/definition.js';
2
- export function Field(opts) {
3
- return (target, key) => {
4
- const entity = target.constructor;
5
- defineField(entity, key, opts);
6
- };
7
- }
@@ -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,2 +0,0 @@
1
- import type { FieldOptions } from '../../type/index.js';
2
- export declare function Id<E>(opts?: FieldOptions): (target: object, key: string) => void;
@@ -1,7 +0,0 @@
1
- import { defineId } from '../metadata/definition.js';
2
- export function Id(opts = {}) {
3
- return (target, key) => {
4
- const entity = target.constructor;
5
- defineId(entity, key, opts);
6
- };
7
- }
@@ -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,4 +0,0 @@
1
- export * from './injectQuerier.js';
2
- export * from './log.js';
3
- export * from './serialized.js';
4
- export * from './transactional.js';
@@ -1,4 +0,0 @@
1
- export * from './injectQuerier.js';
2
- export * from './log.js';
3
- export * from './serialized.js';
4
- export * from './transactional.js';
@@ -1,3 +0,0 @@
1
- import type { Key, Type } from '../../type/index.js';
2
- export declare function InjectQuerier(): (proto: Record<PropertyKey, any>, key: string, index: number) => void;
3
- export declare function getInjectedQuerierIndex<S>(service: Type<S>, key: Key<S>): any;
@@ -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
- }