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
@@ -0,0 +1,34 @@
1
+ import { AbstractSqlQuerierPool } from '../querier/index.js';
2
+ import { SqliteDialect } from './sqliteDialect.js';
3
+ import { SqliteQuerier } from './sqliteQuerier.js';
4
+ /**
5
+ * Pool for a SQLite database opened in this process, whichever driver provides it.
6
+ *
7
+ * The handle is shared - SQLite gives one connection per file - but each acquisition gets its own
8
+ * querier, so transaction state stays per unit of work. Subclasses supply only {@link createDb}: the
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.
11
+ */
12
+ export class AbstractLocalSqliteQuerierPool extends AbstractSqlQuerierPool {
13
+ opts;
14
+ db;
15
+ constructor(opts, extra) {
16
+ super(new SqliteDialect({ namingStrategy: extra?.namingStrategy }), extra);
17
+ this.opts = opts;
18
+ }
19
+ async getQuerier() {
20
+ this.db ??= await this.openDb();
21
+ return new SqliteQuerier(this.db, this.dialect, this.extra);
22
+ }
23
+ async openDb() {
24
+ const db = await this.createDb();
25
+ for (const extension of this.opts?.extensions ?? []) {
26
+ db.loadExtension(extension);
27
+ }
28
+ return db;
29
+ }
30
+ async end() {
31
+ await this.db?.close();
32
+ this.db = undefined;
33
+ }
34
+ }
@@ -0,0 +1,35 @@
1
+ import type { SqliteBindValue } from './abstractSqliteQuerier.js';
2
+ import type { SqliteDatabase } from './sqliteQuerier.js';
3
+ /**
4
+ * A `node:sqlite` statement: better-sqlite3-shaped, except it describes columns instead of reporting
5
+ * `reader`, and types `changes` as possibly `bigint` where better-sqlite3 always answers a `number`.
6
+ */
7
+ type NodeSqliteStatement = {
8
+ columns(): readonly unknown[];
9
+ all(...values: SqliteBindValue[]): unknown[];
10
+ run(...values: SqliteBindValue[]): {
11
+ changes: number | bigint;
12
+ lastInsertRowid: number | bigint;
13
+ };
14
+ iterate(...values: SqliteBindValue[]): Iterable<unknown>;
15
+ };
16
+ /**
17
+ * Structural subset of `node:sqlite`'s `DatabaseSync`, declared locally for the same reason the rest
18
+ * of this folder declares its driver shapes: nothing here depends on `@types/node` being in scope.
19
+ */
20
+ export type NodeSqliteDatabase = {
21
+ prepare(sql: string): NodeSqliteStatement;
22
+ exec(sql: string): void;
23
+ loadExtension(path: string): void;
24
+ close(): void;
25
+ };
26
+ /**
27
+ * Presents a `node:sqlite` handle as a {@link SqliteDatabase}.
28
+ *
29
+ * @remarks Its statements expose no `reader`, so without deriving one every `RETURNING` statement
30
+ * would take the `run()` path, which discards returned rows, and inserts would report no ids.
31
+ * `columns()` is non-empty for exactly the statements better-sqlite3 marks as readers, including
32
+ * `INSERT ... RETURNING` and `DELETE ... RETURNING`.
33
+ */
34
+ export declare function adaptNodeSqlite(db: NodeSqliteDatabase): SqliteDatabase;
35
+ export {};
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Presents a `node:sqlite` handle as a {@link SqliteDatabase}.
3
+ *
4
+ * @remarks Its statements expose no `reader`, so without deriving one every `RETURNING` statement
5
+ * would take the `run()` path, which discards returned rows, and inserts would report no ids.
6
+ * `columns()` is non-empty for exactly the statements better-sqlite3 marks as readers, including
7
+ * `INSERT ... RETURNING` and `DELETE ... RETURNING`.
8
+ */
9
+ export function adaptNodeSqlite(db) {
10
+ return {
11
+ prepare: (sql) => {
12
+ const stmt = db.prepare(sql);
13
+ return {
14
+ reader: stmt.columns().length > 0,
15
+ all: (...values) => stmt.all(...values),
16
+ // `changes` is narrowed to `number` to match every other driver; a row count cannot exceed
17
+ // the safe-integer range, so nothing is lost. `lastInsertRowid` keeps its `bigint` arm.
18
+ run: (...values) => {
19
+ const { changes, lastInsertRowid } = stmt.run(...values);
20
+ return { changes: Number(changes), lastInsertRowid };
21
+ },
22
+ iterate: (...values) => stmt.iterate(...values),
23
+ };
24
+ },
25
+ loadExtension: (path) => db.loadExtension(path),
26
+ close: () => db.close(),
27
+ };
28
+ }
@@ -0,0 +1,28 @@
1
+ import type { ExtraOptions } from '../type/index.js';
2
+ import { AbstractLocalSqliteQuerierPool, type LocalSqlitePoolOptions } from './localSqliteQuerierPool.js';
3
+ import type { SqliteDatabase } from './sqliteQuerier.js';
4
+ /**
5
+ * The `DatabaseSync` options worth surfacing, plus the loadable extensions to install. Declared here
6
+ * rather than imported from `node:sqlite` so this module needs no ambient Node types; unknown keys
7
+ * are ignored by the driver, so the list only has to cover what callers actually set.
8
+ */
9
+ export type NodeSqlitePoolOptions = LocalSqlitePoolOptions & {
10
+ readonly readOnly?: boolean;
11
+ readonly enableForeignKeyConstraints?: boolean;
12
+ /** Milliseconds a locked database is retried before `SQLITE_BUSY`. */
13
+ readonly timeout?: number;
14
+ };
15
+ /**
16
+ * Pool backed by Node's built-in `node:sqlite`, so SQLite works with **no dependency at all** rather
17
+ * than requiring the `better-sqlite3` native build. Use {@link Sqlite3QuerierPool} instead when you
18
+ * want `better-sqlite3`, or are on Bun.
19
+ *
20
+ * @remarks `node:sqlite` needs no CLI flag from Node 22.13, and is still a release candidate
21
+ * (stability 1.2) as of Node 26, so `better-sqlite3` via {@link Sqlite3QuerierPool} remains the
22
+ * faster option for read-heavy work.
23
+ */
24
+ export declare class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool<NodeSqlitePoolOptions> {
25
+ readonly filename: string;
26
+ constructor(filename?: string, opts?: NodeSqlitePoolOptions, extra?: ExtraOptions);
27
+ protected createDb(): Promise<SqliteDatabase>;
28
+ }
@@ -0,0 +1,29 @@
1
+ import { AbstractLocalSqliteQuerierPool } from './localSqliteQuerierPool.js';
2
+ import { adaptNodeSqlite } from './nodeSqliteAdapter.js';
3
+ /**
4
+ * Pool backed by Node's built-in `node:sqlite`, so SQLite works with **no dependency at all** rather
5
+ * than requiring the `better-sqlite3` native build. Use {@link Sqlite3QuerierPool} instead when you
6
+ * want `better-sqlite3`, or are on Bun.
7
+ *
8
+ * @remarks `node:sqlite` needs no CLI flag from Node 22.13, and is still a release candidate
9
+ * (stability 1.2) as of Node 26, so `better-sqlite3` via {@link Sqlite3QuerierPool} remains the
10
+ * faster option for read-heavy work.
11
+ */
12
+ export class NodeSqliteQuerierPool extends AbstractLocalSqliteQuerierPool {
13
+ filename;
14
+ constructor(filename = ':memory:', opts, extra) {
15
+ super(opts, extra);
16
+ this.filename = filename;
17
+ }
18
+ async createDb() {
19
+ const { DatabaseSync } = await import('node:sqlite');
20
+ const { extensions, ...driverOpts } = this.opts ?? {};
21
+ const nodeDb = new DatabaseSync(this.filename, {
22
+ ...driverOpts,
23
+ // `node:sqlite` refuses `loadExtension` unless the database was opened with this on.
24
+ ...(extensions?.length ? { allowExtension: true } : undefined),
25
+ });
26
+ nodeDb.exec('PRAGMA journal_mode = WAL');
27
+ return adaptNodeSqlite(nodeDb);
28
+ }
29
+ }
@@ -1,5 +1,5 @@
1
1
  import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
2
- import type { DialectFeatures, EntityMeta, FieldOptions, QueryConflictPaths, QueryContext, QuerySizeComparisonOps, QueryTextSearchOptions, Type, VectorDistance } from '../type/index.js';
2
+ import type { DialectFeatures, EntityMeta, FieldOptions, QueryContext, QuerySizeComparisonOps, QueryTextSearchOptions, Type, VectorDistance } from '../type/index.js';
3
3
  export declare class SqliteDialect extends AbstractSqlDialect {
4
4
  /** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
5
5
  protected readonly featureDefaults: DialectFeatures;
@@ -52,7 +52,6 @@ export declare class SqliteDialect extends AbstractSqlDialect {
52
52
  protected jsonElemRef(alias: string, field?: string, asJson?: boolean): string;
53
53
  protected getJsonPathScalarExpr(escapedColumn: string, jsonPathStr: string): string;
54
54
  protected numericCast(expr: string): string;
55
- upsert<E>(ctx: QueryContext, entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E | E[]): void;
56
55
  protected jsonCast(operand: string): string;
57
56
  /**
58
57
  * `json_replace` leaves an absent key (and a NULL column) untouched. Elements are read back
@@ -1,6 +1,5 @@
1
1
  import { AbstractSqlDialect } from '../dialect/abstractSqlDialect.js';
2
2
  import { JSON_ELEM_ALIAS_PREFIX, JSON_PULL_ALIAS, jsonAssignCall, jsonElemExists, jsonPath, jsonRemoveCall, jsonSetTarget, } from '../dialect/jsonSql.js';
3
- import { getMeta } from '../entity/index.js';
4
3
  export class SqliteDialect extends AbstractSqlDialect {
5
4
  /** Default {@link DialectFeatures} for SQLite and SQLite-derived dialects. */
6
5
  featureDefaults = {
@@ -109,18 +108,6 @@ export class SqliteDialect extends AbstractSqlDialect {
109
108
  numericCast(expr) {
110
109
  return `CAST(${expr} AS REAL)`;
111
110
  }
112
- upsert(ctx, entity, conflictPaths, payload) {
113
- const meta = getMeta(entity);
114
- const updateCtx = this.createContext();
115
- const update = this.getUpsertUpdateAssignments(updateCtx, meta, conflictPaths, payload, (name) => `EXCLUDED.${name}`);
116
- const keysStr = this.getUpsertConflictPathsStr(meta, conflictPaths);
117
- const onConflict = update ? `DO UPDATE SET ${update}` : 'DO NOTHING';
118
- // Use the base (non-RETURNING) insert here: the appended RETURNING below would otherwise
119
- // be doubled by `this.insert`'s own.
120
- this.appendInsertValues(ctx, entity, payload);
121
- ctx.append(` ON CONFLICT (${keysStr}) ${onConflict} ${this.returningId(entity)}`);
122
- ctx.pushValue(...updateCtx.values);
123
- }
124
111
  jsonCast(operand) {
125
112
  return `json(${operand})`;
126
113
  }
@@ -1,28 +1,15 @@
1
1
  import type { Options } from 'better-sqlite3';
2
- import { AbstractSqlQuerierPool } from '../querier/index.js';
3
2
  import type { ExtraOptions } from '../type/index.js';
4
- import { SqliteDialect } from './sqliteDialect.js';
5
- import { SqliteQuerier } from './sqliteQuerier.js';
3
+ import { AbstractLocalSqliteQuerierPool, type LocalSqlitePoolOptions } from './localSqliteQuerierPool.js';
4
+ import type { SqliteDatabase } from './sqliteQuerier.js';
6
5
  /** Driver options, plus the loadable extensions to install on the connection. */
7
- export type Sqlite3PoolOptions = Options & {
8
- /**
9
- * Paths of loadable extensions to install when the connection opens - e.g. what `sqlite-vec`'s
10
- * `getLoadablePath()` returns, which vector search needs because SQLite itself has no vector
11
- * functions.
12
- */
13
- extensions?: readonly string[];
14
- };
15
- export declare class Sqlite3QuerierPool extends AbstractSqlQuerierPool<SqliteQuerier, SqliteDialect> {
6
+ export type Sqlite3PoolOptions = Options & LocalSqlitePoolOptions;
7
+ /**
8
+ * Pool for `better-sqlite3`, or `bun:sqlite` when running under Bun - the same file, through whichever
9
+ * driver the runtime provides.
10
+ */
11
+ export declare class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool<Sqlite3PoolOptions> {
16
12
  readonly filename: string | Buffer;
17
- readonly opts?: Sqlite3PoolOptions | undefined;
18
- private db?;
19
- constructor(filename?: string | Buffer, opts?: Sqlite3PoolOptions | undefined, extra?: ExtraOptions);
20
- /**
21
- * The database handle is shared (single connection), but each acquisition gets its own querier
22
- * so transaction state stays per unit of work.
23
- */
24
- getQuerier(): Promise<SqliteQuerier>;
25
- private openDb;
26
- private openDriverDb;
27
- end(): Promise<void>;
13
+ constructor(filename?: string | Buffer, opts?: Sqlite3PoolOptions, extra?: ExtraOptions);
14
+ protected createDb(): Promise<SqliteDatabase>;
28
15
  }
@@ -1,34 +1,19 @@
1
- import { AbstractSqlQuerierPool } from '../querier/index.js';
2
- import { SqliteDialect } from './sqliteDialect.js';
3
- import { SqliteQuerier } from './sqliteQuerier.js';
4
- export class Sqlite3QuerierPool extends AbstractSqlQuerierPool {
1
+ import { AbstractLocalSqliteQuerierPool } from './localSqliteQuerierPool.js';
2
+ /**
3
+ * Pool for `better-sqlite3`, or `bun:sqlite` when running under Bun - the same file, through whichever
4
+ * driver the runtime provides.
5
+ */
6
+ export class Sqlite3QuerierPool extends AbstractLocalSqliteQuerierPool {
5
7
  filename;
6
- opts;
7
- db;
8
8
  constructor(filename = ':memory:', opts, extra) {
9
- super(new SqliteDialect({ namingStrategy: extra?.namingStrategy }), extra);
9
+ super(opts, extra);
10
10
  this.filename = filename;
11
- this.opts = opts;
12
11
  }
13
- /**
14
- * The database handle is shared (single connection), but each acquisition gets its own querier
15
- * so transaction state stays per unit of work.
16
- */
17
- async getQuerier() {
18
- this.db ??= await this.openDb();
19
- return new SqliteQuerier(this.db, this.dialect, this.extra);
20
- }
21
- async openDb() {
12
+ async createDb() {
22
13
  // `bun:sqlite` rejects option keys it does not know, and rejects an options object carrying no
23
14
  // open flags, so `extensions` is stripped out and what remains of it collapses back to nothing.
24
15
  const { extensions, ...driverOpts } = this.opts ?? {};
25
- const db = await this.openDriverDb(Object.keys(driverOpts).length > 0 ? driverOpts : undefined);
26
- for (const extension of extensions ?? []) {
27
- db.loadExtension(extension);
28
- }
29
- return db;
30
- }
31
- async openDriverDb(opts) {
16
+ const opts = Object.keys(driverOpts).length > 0 ? driverOpts : undefined;
32
17
  if (typeof Bun !== 'undefined') {
33
18
  const { Database: BunDatabase } = await import('bun:sqlite');
34
19
  const { adaptBunSqlite } = await import('./bunSqliteAdapter.bun.js');
@@ -41,8 +26,4 @@ export class Sqlite3QuerierPool extends AbstractSqlQuerierPool {
41
26
  db.pragma('journal_mode = WAL');
42
27
  return db;
43
28
  }
44
- async end() {
45
- await this.db?.close();
46
- this.db = undefined;
47
- }
48
29
  }
@@ -175,14 +175,36 @@ export type JsonColumnType = 'json' | 'jsonb';
175
175
  * SQL binary/blob column types
176
176
  */
177
177
  export type BlobColumnType = 'blob' | 'bytea';
178
+ /**
179
+ * SQL boolean column types
180
+ */
181
+ export type BooleanColumnType = 'bool' | 'boolean';
182
+ /**
183
+ * SQL vector column types
184
+ */
185
+ export type VectorColumnType = 'vector' | 'halfvec' | 'sparsevec';
178
186
  /**
179
187
  * SQL column types supported by uql migrations
180
188
  */
181
- export type ColumnType = NumericColumnType | StringColumnType | DateColumnType | JsonColumnType | BlobColumnType | 'bool' | 'boolean' | 'vector' | 'halfvec' | 'sparsevec';
189
+ export type ColumnType = NumericColumnType | StringColumnType | DateColumnType | JsonColumnType | BlobColumnType | BooleanColumnType | VectorColumnType;
182
190
  /**
183
191
  * Logical types for a field
184
192
  */
185
193
  export type FieldType = StringConstructor | NumberConstructor | BooleanConstructor | DateConstructor | BigIntConstructor | ColumnType;
194
+ /**
195
+ * The {@link FieldType} values legal for a field declared as `V`.
196
+ *
197
+ * This is what makes an explicit `type` an improvement over the reflected one it replaces: the
198
+ * annotation is checked against the property's real TypeScript type, so `@Field({ type: String })` on
199
+ * a `number` no longer compiles into a silent TEXT column. `unknown` shapes fall through to the full
200
+ * {@link FieldType}, keeping genuinely untyped fields usable.
201
+ *
202
+ * JSON is matched on the `__json` brand rather than structurally, because {@link Json} intersects its
203
+ * payload (`Json<string>` really does extend `string`) and would otherwise land on the string arm.
204
+ * Both `Json<T>` and `Json<T>[]` have to be recognised, and the array check has to precede the scalar
205
+ * arms so a `number[]` vector is not read as a `number`.
206
+ */
207
+ export type TypeFor<V, T = NonNullable<V>> = IsJson<T> extends true ? JsonColumnType : IsJson<NonNullable<Unpacked<T>>> extends true ? JsonColumnType : T extends readonly number[] ? VectorColumnType : T extends string ? StringConstructor | StringColumnType : T extends number ? NumberConstructor | NumericColumnType : T extends bigint ? BigIntConstructor | NumericColumnType : T extends boolean ? BooleanConstructor | BooleanColumnType : T extends Date ? DateConstructor | DateColumnType : T extends Uint8Array ? BlobColumnType : FieldType;
186
208
  /**
187
209
  * Configurable options for a field
188
210
  */
@@ -191,11 +213,13 @@ export type FieldOptions = {
191
213
  readonly isId?: true;
192
214
  readonly type?: FieldType;
193
215
  /**
194
- * Set by `defineField` when `type` was inferred via reflection rather than
195
- * given explicitly. Internal bookkeeping - do not set this from a decorator.
216
+ * Set by `defineField` when the field gave `references` but no `type`, so schema generation resolves
217
+ * the column from the referenced primary key rather than from whatever ended up in `type`. That is
218
+ * what keeps a `uuid` primary key from becoming TEXT on every foreign key pointing at it.
219
+ * Internal bookkeeping - do not set this from a decorator.
196
220
  * @internal
197
221
  */
198
- readonly typeInferred?: boolean;
222
+ readonly typeFromReference?: boolean;
199
223
  /**
200
224
  * Dimensions for vector fields. Used in schema generation.
201
225
  * @example `@Field({ type: 'vector', dimensions: 1536 })`
@@ -270,6 +294,55 @@ export type FieldOptions = {
270
294
  readonly comment?: string;
271
295
  };
272
296
  export type OnFieldCallback = Scalar | QueryRaw | (() => Scalar | QueryRaw);
297
+ /**
298
+ * The TypeScript types a field may be declared as, given the `type` it registers: the inverse of
299
+ * {@link TypeFor}.
300
+ *
301
+ * Both directions are needed because they are consumed at opposite ends. `defineEntity` keys its bulk
302
+ * `fields` by property name, so the property's type is already known and {@link TypeFor} narrows the
303
+ * `type` allowed. A decorator has it the other way round: `@Field({ type: String })` is checked before
304
+ * the class exists, so the only way to reach the property is to state what `type: String` implies and
305
+ * let the decorator's context position compare it against the real field. Neither can be derived from
306
+ * the other by inference, so `entityOptions.type-test.ts` asserts they agree instead.
307
+ */
308
+ export type TsTypeOf<T> = T extends StringConstructor ? string : T extends NumberConstructor ? number : T extends BigIntConstructor ? bigint : T extends BooleanConstructor ? boolean : T extends DateConstructor ? Date : T extends StringColumnType ? string : T extends NumericColumnType ? number | bigint : T extends BooleanColumnType ? boolean : T extends DateColumnType ? Date : T extends JsonColumnType ? Json<unknown> | readonly Json<unknown>[] : T extends BlobColumnType ? Uint8Array : T extends VectorColumnType ? readonly number[] : unknown;
309
+ /**
310
+ * {@link FieldOptions} for a field declared as `V`, with `type` required and checked by
311
+ * {@link TypeFor}.
312
+ *
313
+ * The second arm is load-bearing rather than a convenience: a foreign-key column may omit `type` so
314
+ * that schema generation resolves it from the referenced primary key instead, picking up that key's
315
+ * `columnType`, length and chained references. Forcing `type: Number` onto
316
+ * `@Field({ references: () => Company })` would silently downgrade a `uuid` key to TEXT on every
317
+ * column pointing at it.
318
+ */
319
+ export type FieldOptionsFor<V> = (FieldOptions & {
320
+ readonly type: TypeFor<V>;
321
+ }) | (FieldOptions & {
322
+ readonly references: EntityGetter;
323
+ readonly type?: TypeFor<V>;
324
+ });
325
+ /**
326
+ * The entity a relation field points at: `Company` for both `company?: Company` and
327
+ * `companies?: Company[]`.
328
+ */
329
+ export type RelationTarget<V> = NonNullable<Unpacked<NonNullable<V>>>;
330
+ /**
331
+ * {@link RelationOptions} for a relation field declared as `V`, with `entity` required and pinned to
332
+ * `V`'s own type, and the cardinality restricted to the ones that field shape can hold. Together those
333
+ * reject `@ManyToOne({ entity: () => Other })` on a `Company` field, and any to-many cardinality on a
334
+ * field that is not an array.
335
+ */
336
+ export type RelationOptionsFor<V> = Omit<RelationOptions<RelationTarget<V>>, 'entity' | 'cardinality'> & {
337
+ readonly entity: EntityGetter<RelationTarget<V>>;
338
+ readonly cardinality: NonNullable<V> extends readonly unknown[] ? '1m' | 'mm' : '11' | 'm1';
339
+ };
340
+ /**
341
+ * The method names of an entity, so hook registrations name a method that exists.
342
+ */
343
+ export type MethodKey<E> = {
344
+ readonly [K in keyof E]-?: NonNullable<E[K]> extends (...args: never[]) => unknown ? K : never;
345
+ }[Key<E>];
273
346
  export type EntityGetter<E = any> = () => Type<E>;
274
347
  export type CascadeType = 'persist' | 'delete';
275
348
  export type RelationOptions<E = any> = {
@@ -299,20 +372,6 @@ export type RelationOneToOneOptions<E> = RelationOptionsOwner<E> | RelationOptio
299
372
  export type RelationOneToManyOptions<E> = RelationOptionsInverseSide<E> | RelationOptionsThroughOwner<E>;
300
373
  export type RelationManyToOneOptions<E> = RelationOptionsOwner<E>;
301
374
  export type RelationManyToManyOptions<E> = RelationOptionsThroughOwner<E> | RelationOptionsInverseSide<E>;
302
- /**
303
- * Wrapper type for relation type definitions in entities.
304
- * Used to circumvent ESM modules circular dependency issue caused by reflection metadata saving the type of the property.
305
- *
306
- * Usage example:
307
- * @Entity()
308
- * export default class User {
309
- *
310
- * @OneToOne(() => Profile, profile => profile.user)
311
- * profile: Relation<Profile>;
312
- *
313
- * }
314
- */
315
- export type Relation<T> = T;
316
375
  /**
317
376
  * Lifecycle hook event names.
318
377
  */
@@ -354,8 +413,12 @@ export type IndexTypeOptions = {
354
413
  * @Index([{ column: 'body', length: 64 }]) // MySQL needs a prefix on TEXT
355
414
  * @Index(['data'], { type: 'gin' }) // JSONB containment
356
415
  * ```
416
+ *
417
+ * `C` is the entity's `FieldKey` on the `@Index`/`defineEntity` paths, where the decorated class says
418
+ * which columns exist. It defaults to `string` for the migration builder's `table.index(...)`, which
419
+ * names raw table columns with no entity in scope.
357
420
  */
358
- export type IndexColumnInput = string | QueryRaw | IndexColumnOptions;
421
+ export type IndexColumnInput<C extends string = string> = C | QueryRaw | IndexColumnOptions<C>;
359
422
  /**
360
423
  * What an index entry can carry besides the thing being indexed. Shared with the normalized
361
424
  * `IndexColumnSchema`, so the authored and internal shapes cannot drift apart.
@@ -373,9 +436,9 @@ export type IndexColumnModifiers = {
373
436
  /** Operator class, e.g. `jsonb_path_ops` for a smaller GIN index. Postgres only. */
374
437
  readonly opsClass?: string;
375
438
  };
376
- export type IndexColumnOptions = IndexColumnModifiers & {
439
+ export type IndexColumnOptions<C extends string = string> = IndexColumnModifiers & {
377
440
  /** The column to index, or `raw(...)` for an expression. */
378
- readonly column: string | QueryRaw;
441
+ readonly column: C | QueryRaw;
379
442
  };
380
443
  /**
381
444
  * One index entry, normalized: {@link IndexColumnInput}'s three authored shapes all reduce to this
@@ -439,11 +502,15 @@ export type EntityOptions<E = unknown> = {
439
502
  /** Named, default-on `$where` filters (soft-delete is auto-registered from `@Field({ softDelete })`). */
440
503
  readonly filters?: Record<string, FilterOptions<E>>;
441
504
  /** Scalar fields; use `isId: true` on exactly one field for the primary key. */
442
- readonly fields?: Record<string, FieldOptions>;
443
- readonly relations?: Record<string, RelationOptions>;
444
- readonly indexes?: readonly EntityIndexInput[];
505
+ readonly fields?: {
506
+ readonly [K in FieldKey<E>]?: FieldOptionsFor<E[K]>;
507
+ };
508
+ readonly relations?: {
509
+ readonly [K in RelationKey<E>]?: RelationOptionsFor<E[K]>;
510
+ };
511
+ readonly indexes?: readonly EntityIndexInput<FieldKey<E>>[];
445
512
  /** Map hook events to method names on the entity class. */
446
- readonly hooks?: Partial<Record<HookEvent, readonly string[]>>;
513
+ readonly hooks?: Partial<Record<HookEvent, readonly MethodKey<E>[]>>;
447
514
  };
448
515
  /**
449
516
  * Everything an index carries beyond its columns, shared by `@Index`, `defineEntity` and the
@@ -452,9 +519,9 @@ export type EntityOptions<E = unknown> = {
452
519
  */
453
520
  export type IndexOptions = DistributiveOmit<EntityIndexMeta, 'columns'>;
454
521
  /**
455
- * An index as authored, before {@link appendEntityIndex} normalizes its columns.
522
+ * An index as authored, before `defineIndex` normalizes its columns.
456
523
  */
457
- export type EntityIndexInput = IndexOptions & {
458
- readonly columns: readonly IndexColumnInput[];
524
+ export type EntityIndexInput<C extends string = string> = IndexOptions & {
525
+ readonly columns: readonly IndexColumnInput<C>[];
459
526
  };
460
527
  export {};
@@ -1,11 +1,10 @@
1
1
  import type { Db } from 'mongodb';
2
2
  import type { AbstractSqlDialect } from '../dialect/index.js';
3
3
  import type { SqlDialectName } from './dialect.js';
4
- import type { HookEvent, IdValue, UpdatePayload } from './entity.js';
4
+ import type { HookEvent } from './entity.js';
5
5
  import type { LoggingOptions } from './logger.js';
6
6
  import type { NamingStrategy } from './namingStrategy.js';
7
- import type { Query, QueryConflictPaths, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
8
- import type { QueryAggMap, QueryAggregate, QueryAggregateResult, QueryGroupMap } from './queryAggregate.js';
7
+ import type { Query, QueryOne, QueryOptions, QuerySearch, QueryUpdateResult } from './query.js';
9
8
  import type { UniversalQuerier } from './universalQuerier.js';
10
9
  import type { Type } from './utility.js';
11
10
  /**
@@ -56,34 +55,6 @@ export interface Querier extends UniversalQuerier {
56
55
  count<E extends object>(q: QuerySearch<E> & {
57
56
  $entity: Type<E>;
58
57
  }, opts?: QueryOptions): Promise<number>;
59
- /**
60
- * Insert a single record and return its ID (provided, `onInsert`-generated, or
61
- * database-generated - see {@link Querier.insertMany} for the exact semantics).
62
- * Returns `undefined` when the ID cannot be determined (e.g. MySQL/SQLite non-auto-increment
63
- * keys in batches without explicit IDs).
64
- */
65
- insertOne<E extends object>(entity: Type<E>, payload: E): Promise<IdValue<E> | undefined>;
66
- /**
67
- * Insert multiple records in a single statement (auto-chunked when the batch exceeds the
68
- * dialect's bind-parameter limit) and return their IDs in payload order.
69
- *
70
- * Provided IDs and client-generated ones (`@Id({ onInsert })`) are always returned as-is.
71
- * Database-generated IDs are exact on `'returning'` dialects (Postgres, MariaDB, MongoDB);
72
- * on MySQL/SQLite they are inferred from the driver header, which is only reliable for
73
- * auto-increment keys in batches without explicit IDs - otherwise those entries are
74
- * `undefined` rather than potentially wrong values.
75
- */
76
- insertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
77
- updateMany<E extends object>(entity: Type<E>, q: QuerySearch<E>, payload: UpdatePayload<E>, opts?: QueryOptions): Promise<number>;
78
- /**
79
- * Restore soft-deleted records (sets the soft-delete field back to `null`). Throws if the
80
- * entity has no soft-delete field.
81
- */
82
- restoreOneById<E extends object>(entity: Type<E>, id: IdValue<E>): Promise<number>;
83
- restoreMany<E extends object>(entity: Type<E>, q: QuerySearch<E>): Promise<number>;
84
- upsertOne<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E): Promise<QueryUpdateResult>;
85
- upsertMany<E extends object>(entity: Type<E>, conflictPaths: QueryConflictPaths<E>, payload: E[]): Promise<QueryUpdateResult>;
86
- saveMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
87
58
  /**
88
59
  * Delete many records (soft-deletes when the entity has a soft-delete field, else removes them).
89
60
  * Supports both entity-as-argument and entity-as-field patterns.
@@ -92,10 +63,6 @@ export interface Querier extends UniversalQuerier {
92
63
  deleteMany<E extends object>(q: QuerySearch<E> & {
93
64
  $entity: Type<E>;
94
65
  }, opts?: QueryOptions): Promise<number>;
95
- /**
96
- * Run an aggregate query (GROUP BY with aggregate functions).
97
- */
98
- aggregate<E extends object, const G extends QueryGroupMap<E>, const A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
99
66
  /**
100
67
  * whether this querier is in a transaction or not.
101
68
  */
@@ -120,6 +87,12 @@ export interface Querier extends UniversalQuerier {
120
87
  * release the querier to the pool.
121
88
  */
122
89
  release(): Promise<void>;
90
+ /**
91
+ * Releases the querier when an `await using` binding goes out of scope, so a unit of work cannot
92
+ * leak a connection on an early return or a throw.
93
+ * @example `await using querier = await pool.getQuerier();`
94
+ */
95
+ [Symbol.asyncDispose](): Promise<void>;
123
96
  }
124
97
  export interface SqlQuerier extends Querier {
125
98
  /**
@@ -15,24 +15,25 @@ export interface PoolRunOptions {
15
15
  /**
16
16
  * Querier pool. Read the dialect id via `pool.dialect.dialectName` (see {@link AbstractDialect.dialectName}); queriers expose the same on `querier.dialect`.
17
17
  *
18
- * The read methods of {@link UniversalQuerier} are available directly on the pool. Each call
19
- * acquires its own querier, runs the single read, and releases it - so
20
- * `Promise.all([pool.findMany(A, {}), pool.count(B, {})])` runs on separate connections in
21
- * parallel, while the same calls inside one `withQuerier`/`transaction` callback share a pinned
22
- * connection and serialize. Single-connection backends (better-sqlite3, Bun sqlite, D1) stay
23
- * correct but always serialize on their one connection.
18
+ * A pool is a {@link UniversalQuerier} too, so a function that runs queries takes that type and the
19
+ * caller passes its own querier or the pool. `pool.op(...)` is exactly
20
+ * `pool.withQuerier((querier) => querier.op(...))`, so two pool calls are two units of work; when they
21
+ * must commit together, that is `transaction`.
24
22
  *
25
- * An enclosing `withContext` scopes the pool reads (`security` filters apply); one wrapper covers a
26
- * whole parallel fan-out, which is why the reads take no per-call `context` option (unlike
27
- * `withQuerier`/`transaction`).
23
+ * Acquiring per call is also what makes `Promise.all([pool.findMany(A, {}), pool.count(B, {})])` run on
24
+ * separate connections, while the same calls inside one `withQuerier`/`transaction` share a pinned
25
+ * connection and serialize. Single-connection backends (better-sqlite3, Bun sqlite, D1) stay correct
26
+ * but always serialize.
28
27
  *
29
- * Pool reads take the entity-as-argument form only. For the `{ $entity }` form, streaming, or
30
- * writes (they need a unit of work), use `withQuerier`/`transaction`.
28
+ * An enclosing `withContext` scopes pool calls (`security` filters apply), which is why they take no
29
+ * per-call `context` option (unlike `withQuerier`/`transaction`).
30
+ *
31
+ * Pool calls take the entity-as-argument form only; the `{ $entity }` form needs a querier.
31
32
  *
32
33
  * @typeParam Q - Querier implementation returned from the pool.
33
34
  * @typeParam D - Concrete dialect class held by the pool.
34
35
  */
35
- export interface QuerierPool<Q extends Querier = Querier, D extends AbstractDialect = AbstractDialect> extends Pick<UniversalQuerier, 'findOneById' | 'findOne' | 'findMany' | 'findManyAndCount' | 'count' | 'aggregate'> {
36
+ export interface QuerierPool<Q extends Querier = Querier, D extends AbstractDialect = AbstractDialect> extends UniversalQuerier {
36
37
  /**
37
38
  * Database dialect instance (single source of truth for dialect id and SQL/NoSQL behavior).
38
39
  */