uql-orm 0.24.6 → 0.24.7
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 +9 -8
- package/dist/cockroachdb/crdbQuerierPool.d.ts +1 -1
- package/dist/cockroachdb/crdbQuerierPool.js +5 -4
- package/dist/dialect/abstractSqlDialect.d.ts +78 -27
- package/dist/dialect/abstractSqlDialect.js +189 -129
- package/dist/dialect/hydrateColumn.d.ts +16 -0
- package/dist/dialect/hydrateColumn.js +66 -0
- package/dist/dialect/jsonSql.d.ts +24 -0
- package/dist/dialect/jsonSql.js +39 -0
- package/dist/dialect/mysqlLikeSqlDialect.d.ts +5 -0
- package/dist/dialect/mysqlLikeSqlDialect.js +10 -1
- package/dist/dialect/pgLikeSqlDialect.d.ts +3 -7
- package/dist/dialect/pgLikeSqlDialect.js +2 -14
- package/dist/dialect/vectorCast.d.ts +15 -0
- package/dist/dialect/vectorCast.js +58 -0
- package/dist/entity/metadata/definition.d.ts +0 -1
- package/dist/entity/metadata/definition.js +1 -1
- package/dist/maria/mariaDialect.d.ts +3 -2
- package/dist/maria/mariaDialect.js +3 -18
- package/dist/maria/mariadbQuerierPool.js +6 -1
- package/dist/migrate/builder/migrationBuilder.d.ts +12 -16
- package/dist/migrate/builder/migrationBuilder.js +24 -59
- package/dist/migrate/builder/tableBuilder.js +0 -12
- package/dist/migrate/cli.d.ts +0 -1
- package/dist/migrate/cli.js +1 -1
- package/dist/migrate/codegen/entityCodeGenerator.js +0 -3
- package/dist/migrate/drift/driftDetector.js +17 -15
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.d.ts +8 -2
- package/dist/migrate/introspection/abstractSqlSchemaIntrospector.js +10 -9
- package/dist/migrate/introspection/mysqlIntrospector.d.ts +0 -3
- package/dist/migrate/introspection/mysqlIntrospector.js +0 -9
- package/dist/migrate/introspection/postgresIntrospector.d.ts +0 -3
- package/dist/migrate/introspection/postgresIntrospector.js +0 -12
- package/dist/migrate/introspection/sqliteIntrospector.d.ts +1 -0
- package/dist/migrate/introspection/sqliteIntrospector.js +1 -9
- package/dist/migrate/migrator.d.ts +8 -0
- package/dist/migrate/migrator.js +19 -29
- package/dist/migrate/schemaGenerator.js +0 -12
- package/dist/neon/neonQuerierPool.d.ts +1 -1
- package/dist/neon/neonQuerierPool.js +6 -4
- package/dist/postgres/abstractPgQuerierPool.d.ts +6 -0
- package/dist/postgres/abstractPgQuerierPool.js +3 -0
- package/dist/postgres/pgNumericTypes.d.ts +41 -0
- package/dist/postgres/pgNumericTypes.js +35 -0
- package/dist/postgres/pgQuerierPool.d.ts +1 -1
- package/dist/postgres/pgQuerierPool.js +5 -4
- package/dist/querier/abstractSqlQuerier.d.ts +9 -2
- package/dist/querier/abstractSqlQuerier.js +31 -24
- package/dist/schema/canonicalType.js +2 -12
- package/dist/schema/schemaAST.js +0 -24
- package/dist/schema/schemaASTBuilder.js +0 -3
- package/dist/type/queryAggregate.d.ts +3 -0
- package/dist/util/field.util.d.ts +4 -0
- package/dist/util/field.util.js +12 -0
- package/dist/util/sqlLiteral.js +18 -15
- package/package.json +4 -4
|
@@ -3,9 +3,6 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
|
|
|
3
3
|
* PostgreSQL schema introspector
|
|
4
4
|
*/
|
|
5
5
|
export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
6
|
-
// ============================================================================
|
|
7
|
-
// SQL Queries (dialect-specific)
|
|
8
|
-
// ============================================================================
|
|
9
6
|
getTableNamesQuery() {
|
|
10
7
|
return /*sql*/ `
|
|
11
8
|
SELECT table_name
|
|
@@ -128,12 +125,6 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
128
125
|
ORDER BY kcu.ordinal_position
|
|
129
126
|
`;
|
|
130
127
|
}
|
|
131
|
-
// ============================================================================
|
|
132
|
-
// Internal Types
|
|
133
|
-
// ============================================================================
|
|
134
|
-
mapTableNameRow(row) {
|
|
135
|
-
return row.table_name;
|
|
136
|
-
}
|
|
137
128
|
async mapColumnsResult(_read, _tableName, results) {
|
|
138
129
|
return results.map((row) => ({
|
|
139
130
|
name: row.column_name,
|
|
@@ -166,9 +157,6 @@ export class PostgresSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
166
157
|
onUpdate: this.normalizeReferentialAction(row.update_rule),
|
|
167
158
|
}));
|
|
168
159
|
}
|
|
169
|
-
// ============================================================================
|
|
170
|
-
// PostgreSQL-specific helpers
|
|
171
|
-
// ============================================================================
|
|
172
160
|
normalizeType(dataType, udtName) {
|
|
173
161
|
// Handle user-defined types and arrays
|
|
174
162
|
if (dataType === 'USER-DEFINED') {
|
|
@@ -15,6 +15,7 @@ export declare class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospec
|
|
|
15
15
|
protected getIndexesParams(_tableName: string): unknown[];
|
|
16
16
|
protected getForeignKeysParams(_tableName: string): unknown[];
|
|
17
17
|
protected getPrimaryKeyParams(_tableName: string): unknown[];
|
|
18
|
+
/** `sqlite_master`, not `information_schema`, so the column is `name`. */
|
|
18
19
|
protected mapTableNameRow(row: {
|
|
19
20
|
name: string;
|
|
20
21
|
}): string;
|
|
@@ -3,9 +3,6 @@ import { AbstractSqlSchemaIntrospector } from './abstractSqlSchemaIntrospector.j
|
|
|
3
3
|
* SQLite schema introspector
|
|
4
4
|
*/
|
|
5
5
|
export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
6
|
-
// ============================================================================
|
|
7
|
-
// SQL Queries (dialect-specific)
|
|
8
|
-
// ============================================================================
|
|
9
6
|
getTableNamesQuery() {
|
|
10
7
|
return /*sql*/ `
|
|
11
8
|
SELECT name
|
|
@@ -55,9 +52,7 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
55
52
|
getPrimaryKeyParams(_tableName) {
|
|
56
53
|
return [];
|
|
57
54
|
}
|
|
58
|
-
|
|
59
|
-
// Row Mapping (dialect-specific)
|
|
60
|
-
// ============================================================================
|
|
55
|
+
/** `sqlite_master`, not `information_schema`, so the column is `name`. */
|
|
61
56
|
mapTableNameRow(row) {
|
|
62
57
|
return row.name;
|
|
63
58
|
}
|
|
@@ -128,9 +123,6 @@ export class SqliteSchemaIntrospector extends AbstractSqlSchemaIntrospector {
|
|
|
128
123
|
}
|
|
129
124
|
return pkColumns.map((r) => r.name);
|
|
130
125
|
}
|
|
131
|
-
// ============================================================================
|
|
132
|
-
// SQLite-specific helpers
|
|
133
|
-
// ============================================================================
|
|
134
126
|
async getUniqueColumns(read, tableName) {
|
|
135
127
|
const indexes = await read(this.getIndexesQuery(tableName));
|
|
136
128
|
const uniqueColumns = new Set();
|
|
@@ -53,6 +53,14 @@ export declare class Migrator {
|
|
|
53
53
|
to?: string;
|
|
54
54
|
step?: number;
|
|
55
55
|
}): Promise<MigrationResult[]>;
|
|
56
|
+
/**
|
|
57
|
+
* Narrow a run list by `to`/`step` and execute it, stopping at the first failure.
|
|
58
|
+
*
|
|
59
|
+
* Both directions do exactly this and differ only in the list they start from: `up` takes the
|
|
60
|
+
* pending migrations, `down` the executed ones reversed. Keeping the selection in one place is what
|
|
61
|
+
* makes `--to` and `--step` mean the same thing whichever way you are going.
|
|
62
|
+
*/
|
|
63
|
+
private runInOrder;
|
|
56
64
|
/**
|
|
57
65
|
* Run a single migration within a transaction
|
|
58
66
|
*/
|
package/dist/migrate/migrator.js
CHANGED
|
@@ -130,27 +130,7 @@ export class Migrator {
|
|
|
130
130
|
* Run all pending migrations
|
|
131
131
|
*/
|
|
132
132
|
async up(options = {}) {
|
|
133
|
-
|
|
134
|
-
const results = [];
|
|
135
|
-
let migrationsToRun = pendingMigrations;
|
|
136
|
-
if (options.to) {
|
|
137
|
-
const toIndex = migrationsToRun.findIndex((m) => m.name === options.to);
|
|
138
|
-
if (toIndex === -1) {
|
|
139
|
-
throw new Error(`Migration '${options.to}' not found`);
|
|
140
|
-
}
|
|
141
|
-
migrationsToRun = migrationsToRun.slice(0, toIndex + 1);
|
|
142
|
-
}
|
|
143
|
-
if (options.step !== undefined) {
|
|
144
|
-
migrationsToRun = migrationsToRun.slice(0, options.step);
|
|
145
|
-
}
|
|
146
|
-
for (const migration of migrationsToRun) {
|
|
147
|
-
const result = await this.runMigration(migration, 'up');
|
|
148
|
-
results.push(result);
|
|
149
|
-
if (!result.success) {
|
|
150
|
-
break; // Stop on first failure
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
return results;
|
|
133
|
+
return this.runInOrder(await this.pending(), 'up', options);
|
|
154
134
|
}
|
|
155
135
|
/**
|
|
156
136
|
* Rollback migrations
|
|
@@ -159,23 +139,33 @@ export class Migrator {
|
|
|
159
139
|
const [migrations, executed] = await Promise.all([this.getMigrations(), this.storage.executed()]);
|
|
160
140
|
const executedSet = new Set(executed);
|
|
161
141
|
const executedMigrations = migrations.filter((m) => executedSet.has(m.name)).reverse(); // Rollback in reverse order
|
|
162
|
-
|
|
163
|
-
|
|
142
|
+
return this.runInOrder(executedMigrations, 'down', options);
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Narrow a run list by `to`/`step` and execute it, stopping at the first failure.
|
|
146
|
+
*
|
|
147
|
+
* Both directions do exactly this and differ only in the list they start from: `up` takes the
|
|
148
|
+
* pending migrations, `down` the executed ones reversed. Keeping the selection in one place is what
|
|
149
|
+
* makes `--to` and `--step` mean the same thing whichever way you are going.
|
|
150
|
+
*/
|
|
151
|
+
async runInOrder(migrations, direction, options) {
|
|
152
|
+
let selected = migrations;
|
|
164
153
|
if (options.to) {
|
|
165
|
-
const toIndex =
|
|
154
|
+
const toIndex = selected.findIndex((m) => m.name === options.to);
|
|
166
155
|
if (toIndex === -1) {
|
|
167
156
|
throw new Error(`Migration '${options.to}' not found`);
|
|
168
157
|
}
|
|
169
|
-
|
|
158
|
+
selected = selected.slice(0, toIndex + 1);
|
|
170
159
|
}
|
|
171
160
|
if (options.step !== undefined) {
|
|
172
|
-
|
|
161
|
+
selected = selected.slice(0, options.step);
|
|
173
162
|
}
|
|
174
|
-
|
|
175
|
-
|
|
163
|
+
const results = [];
|
|
164
|
+
for (const migration of selected) {
|
|
165
|
+
const result = await this.runMigration(migration, direction);
|
|
176
166
|
results.push(result);
|
|
177
167
|
if (!result.success) {
|
|
178
|
-
break;
|
|
168
|
+
break;
|
|
179
169
|
}
|
|
180
170
|
}
|
|
181
171
|
return results;
|
|
@@ -40,9 +40,6 @@ export class SqlSchemaGenerator {
|
|
|
40
40
|
get serialPrimaryKeyType() {
|
|
41
41
|
return this.dialect.serialPrimaryKey;
|
|
42
42
|
}
|
|
43
|
-
// ============================================================================
|
|
44
|
-
// CanonicalType Integration (Unified Type System)
|
|
45
|
-
// ============================================================================
|
|
46
43
|
/**
|
|
47
44
|
* Convert FieldOptions to CanonicalType using the unified type system.
|
|
48
45
|
*/
|
|
@@ -52,9 +49,6 @@ export class SqlSchemaGenerator {
|
|
|
52
49
|
canonicalTypeToSql(type) {
|
|
53
50
|
return canonicalToSql(type, this.dialect);
|
|
54
51
|
}
|
|
55
|
-
// ============================================================================
|
|
56
|
-
// SchemaGenerator Implementation
|
|
57
|
-
// ============================================================================
|
|
58
52
|
generateCreateTable(entity, options = {}) {
|
|
59
53
|
const builder = new SchemaASTBuilder(this.dialect.namingStrategy);
|
|
60
54
|
const ast = builder.fromEntities([entity]);
|
|
@@ -390,9 +384,6 @@ export class SqlSchemaGenerator {
|
|
|
390
384
|
};
|
|
391
385
|
return normalize(current) === normalize(desired);
|
|
392
386
|
}
|
|
393
|
-
// ============================================================================
|
|
394
|
-
// SchemaAST Support Methods
|
|
395
|
-
// ============================================================================
|
|
396
387
|
generateCreateTableFromNode(table, options = {}) {
|
|
397
388
|
const columns = [];
|
|
398
389
|
const constraints = [];
|
|
@@ -468,9 +459,6 @@ export class SqlSchemaGenerator {
|
|
|
468
459
|
generateCreateIndexFromNode(index, options = { ifNotExists: false }) {
|
|
469
460
|
return this.generateCreateIndex(index.table.name, indexNodeToSchema(index), options);
|
|
470
461
|
}
|
|
471
|
-
// ============================================================================
|
|
472
|
-
// Phase 3: Builder Operation Methods (Moved forward for unification)
|
|
473
|
-
// ============================================================================
|
|
474
462
|
generateCreateTableFromDefinition(table, options = {}) {
|
|
475
463
|
const tableNode = this.tableDefinitionToNode(table);
|
|
476
464
|
return this.generateCreateTableFromNode(tableNode, options);
|
|
@@ -6,5 +6,5 @@ import { NeonQuerier } from './neonQuerier.js';
|
|
|
6
6
|
export declare class NeonQuerierPool extends AbstractPgQuerierPool<PoolClient, NeonQuerier, NeonDialect> {
|
|
7
7
|
readonly pool: Pool;
|
|
8
8
|
constructor(opts: PoolConfig, extra?: ExtraOptions);
|
|
9
|
-
|
|
9
|
+
protected buildQuerier(connect: () => Promise<PoolClient>): NeonQuerier;
|
|
10
10
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { Pool } from '@neondatabase/serverless';
|
|
1
|
+
import { Pool, types } from '@neondatabase/serverless';
|
|
2
2
|
import { AbstractPgQuerierPool } from '../postgres/abstractPgQuerierPool.js';
|
|
3
|
+
import { numericTypes } from '../postgres/pgNumericTypes.js';
|
|
3
4
|
import { NeonDialect } from './neonDialect.js';
|
|
4
5
|
import { NeonQuerier } from './neonQuerier.js';
|
|
5
6
|
export class NeonQuerierPool extends AbstractPgQuerierPool {
|
|
6
7
|
constructor(opts, extra) {
|
|
7
|
-
|
|
8
|
+
// Neon's own `types`, not `pg`'s: this entry has to load on an edge runtime where `pg` is absent.
|
|
9
|
+
super(new NeonDialect({ namingStrategy: extra?.namingStrategy }), new Pool({ types: numericTypes(types), ...opts }), extra);
|
|
8
10
|
}
|
|
9
|
-
|
|
10
|
-
return new NeonQuerier(
|
|
11
|
+
buildQuerier(connect) {
|
|
12
|
+
return new NeonQuerier(connect, this.dialect, this.extra);
|
|
11
13
|
}
|
|
12
14
|
}
|
|
@@ -17,5 +17,11 @@ export interface PgAnyPool<C extends PgAnyClient> extends ErrorEmittingPool {
|
|
|
17
17
|
export declare abstract class AbstractPgQuerierPool<C extends PgAnyClient, Q extends AbstractPgQuerier<C, D>, D extends AbstractSqlDialect> extends AbstractSqlQuerierPool<Q, D> {
|
|
18
18
|
readonly pool: PgAnyPool<C>;
|
|
19
19
|
constructor(dialect: D, pool: PgAnyPool<C>, extra?: ExtraOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Every pg-compatible pool acquires a client the same way, so only the querier class varies.
|
|
22
|
+
* Subclasses name that instead of restating the lazy `connect` the querier expects.
|
|
23
|
+
*/
|
|
24
|
+
protected abstract buildQuerier(connect: () => Promise<C>): Q;
|
|
25
|
+
getQuerier(): Promise<Q>;
|
|
20
26
|
end(): Promise<void>;
|
|
21
27
|
}
|
|
@@ -14,6 +14,9 @@ export class AbstractPgQuerierPool extends AbstractSqlQuerierPool {
|
|
|
14
14
|
this.pool = pool;
|
|
15
15
|
attachPoolErrorHandler(pool, 'Idle Postgres pool client encountered an error');
|
|
16
16
|
}
|
|
17
|
+
async getQuerier() {
|
|
18
|
+
return this.buildQuerier(() => this.pool.connect());
|
|
19
|
+
}
|
|
17
20
|
async end() {
|
|
18
21
|
await this.pool.end();
|
|
19
22
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { CustomTypesConfig } from 'pg';
|
|
2
|
+
/**
|
|
3
|
+
* The shape every pg-family driver exposes as `types`: `pg`'s own, and `@neondatabase/serverless`'s
|
|
4
|
+
* reimplementation of it. Taken as a parameter rather than imported, because `uql-orm/neon` must not
|
|
5
|
+
* pull `pg` into an edge bundle that has no such peer installed - the same reason
|
|
6
|
+
* `abstractPgQuerierPool.ts` keeps its `pg` imports type-only.
|
|
7
|
+
*/
|
|
8
|
+
type PgTypes = {
|
|
9
|
+
readonly builtins: Readonly<Record<string, number>>;
|
|
10
|
+
getTypeParser(oid: number, format?: 'text' | 'binary'): (value: string) => unknown;
|
|
11
|
+
};
|
|
12
|
+
/**
|
|
13
|
+
* Decode `INT8` and `FLOAT8` as JS numbers, leaving every other type to the driver.
|
|
14
|
+
*
|
|
15
|
+
* uql owes this to the caller because uql picks the column: `type: Number` maps to BIGINT (see
|
|
16
|
+
* `schema/canonicalType.ts`), so without it a field declared `number` read back as `'9'` - including
|
|
17
|
+
* every auto-increment primary key, on every entity. `FLOAT8` is a float64, which is exactly what a
|
|
18
|
+
* JS number is, so decoding it loses nothing at all.
|
|
19
|
+
*
|
|
20
|
+
* At the driver because everything crosses the wire decoder exactly once - entity reads, `RETURNING
|
|
21
|
+
* id`, raw SQL, counts, aggregates - while the ORM's hydration only ever sees entity reads. Which
|
|
22
|
+
* types belong here and which need the entity's declaration is settled in `hydratableFields`.
|
|
23
|
+
*
|
|
24
|
+
* `NUMERIC` is deliberately absent, and decoded in hydration instead: `type: BigInt` also maps to
|
|
25
|
+
* BIGINT, so a blanket decode here is already as far as a driver can go without the declaration. That
|
|
26
|
+
* split also covers mysql2, which returns DECIMAL as text and has no equivalent hook.
|
|
27
|
+
*
|
|
28
|
+
* Per pool, never global, which is the whole reason this takes `types` as an argument. TypeORM does
|
|
29
|
+
* the same job by assigning `postgres.defaults.parseInt8`, a module-wide flag every pool in the
|
|
30
|
+
* process then shares; MikroORM passes a per-pool `TypeOverrides`, as here. Two globals of exactly
|
|
31
|
+
* that shape have already been deleted from this repo - `test/pgTypeParsers.util.ts` and the
|
|
32
|
+
* `types.setTypeParser` calls in `neon/neonQuerier.test.ts` - and both made the suite pass on
|
|
33
|
+
* behaviour the library never shipped. Do not reintroduce one.
|
|
34
|
+
*
|
|
35
|
+
* Exact to 2^53, which covers any auto-increment id. A caller who needs more passes their own
|
|
36
|
+
* `types` in the pool options: it is spread after this one and therefore wins. For a decimal, the
|
|
37
|
+
* lighter escape hatch is the declaration itself: `@Field({ type: String, columnType: 'decimal' })`
|
|
38
|
+
* keeps the column DECIMAL while leaving the value as the exact text the driver returned.
|
|
39
|
+
*/
|
|
40
|
+
export declare function numericTypes(types: PgTypes): CustomTypesConfig;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Decode `INT8` and `FLOAT8` as JS numbers, leaving every other type to the driver.
|
|
3
|
+
*
|
|
4
|
+
* uql owes this to the caller because uql picks the column: `type: Number` maps to BIGINT (see
|
|
5
|
+
* `schema/canonicalType.ts`), so without it a field declared `number` read back as `'9'` - including
|
|
6
|
+
* every auto-increment primary key, on every entity. `FLOAT8` is a float64, which is exactly what a
|
|
7
|
+
* JS number is, so decoding it loses nothing at all.
|
|
8
|
+
*
|
|
9
|
+
* At the driver because everything crosses the wire decoder exactly once - entity reads, `RETURNING
|
|
10
|
+
* id`, raw SQL, counts, aggregates - while the ORM's hydration only ever sees entity reads. Which
|
|
11
|
+
* types belong here and which need the entity's declaration is settled in `hydratableFields`.
|
|
12
|
+
*
|
|
13
|
+
* `NUMERIC` is deliberately absent, and decoded in hydration instead: `type: BigInt` also maps to
|
|
14
|
+
* BIGINT, so a blanket decode here is already as far as a driver can go without the declaration. That
|
|
15
|
+
* split also covers mysql2, which returns DECIMAL as text and has no equivalent hook.
|
|
16
|
+
*
|
|
17
|
+
* Per pool, never global, which is the whole reason this takes `types` as an argument. TypeORM does
|
|
18
|
+
* the same job by assigning `postgres.defaults.parseInt8`, a module-wide flag every pool in the
|
|
19
|
+
* process then shares; MikroORM passes a per-pool `TypeOverrides`, as here. Two globals of exactly
|
|
20
|
+
* that shape have already been deleted from this repo - `test/pgTypeParsers.util.ts` and the
|
|
21
|
+
* `types.setTypeParser` calls in `neon/neonQuerier.test.ts` - and both made the suite pass on
|
|
22
|
+
* behaviour the library never shipped. Do not reintroduce one.
|
|
23
|
+
*
|
|
24
|
+
* Exact to 2^53, which covers any auto-increment id. A caller who needs more passes their own
|
|
25
|
+
* `types` in the pool options: it is spread after this one and therefore wins. For a decimal, the
|
|
26
|
+
* lighter escape hatch is the declaration itself: `@Field({ type: String, columnType: 'decimal' })`
|
|
27
|
+
* keeps the column DECIMAL while leaving the value as the exact text the driver returned.
|
|
28
|
+
*/
|
|
29
|
+
export function numericTypes(types) {
|
|
30
|
+
// Text only: in binary mode an INT8 arrives as an 8-byte Buffer, and `Number(buffer)` is `NaN`.
|
|
31
|
+
const textNumeric = new Set([types.builtins['INT8'], types.builtins['FLOAT8']]);
|
|
32
|
+
return {
|
|
33
|
+
getTypeParser: (oid, format) => format === 'text' && textNumeric.has(oid) ? Number : types.getTypeParser(oid, format),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -6,5 +6,5 @@ import { PgQuerier } from './pgQuerier.js';
|
|
|
6
6
|
export declare class PgQuerierPool extends AbstractPgQuerierPool<PoolClient, PgQuerier, PgDialect> {
|
|
7
7
|
readonly pool: Pool;
|
|
8
8
|
constructor(opts: PoolConfig, extra?: ExtraOptions);
|
|
9
|
-
|
|
9
|
+
protected buildQuerier(connect: () => Promise<PoolClient>): PgQuerier;
|
|
10
10
|
}
|
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { Pool } from 'pg';
|
|
1
|
+
import { Pool, types } from 'pg';
|
|
2
2
|
import { AbstractPgQuerierPool } from './abstractPgQuerierPool.js';
|
|
3
3
|
import { PgDialect } from './pgDialect.js';
|
|
4
|
+
import { numericTypes } from './pgNumericTypes.js';
|
|
4
5
|
import { PgQuerier } from './pgQuerier.js';
|
|
5
6
|
export class PgQuerierPool extends AbstractPgQuerierPool {
|
|
6
7
|
constructor(opts, extra) {
|
|
7
8
|
// keepAlive reduces (but can't eliminate) idle connections being silently
|
|
8
9
|
// dropped by NATs/firewalls on long-lived remote connections.
|
|
9
|
-
super(new PgDialect({ namingStrategy: extra?.namingStrategy }), new Pool({ keepAlive: true, ...opts }), extra);
|
|
10
|
+
super(new PgDialect({ namingStrategy: extra?.namingStrategy }), new Pool({ keepAlive: true, types: numericTypes(types), ...opts }), extra);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
return new PgQuerier(
|
|
12
|
+
buildQuerier(connect) {
|
|
13
|
+
return new PgQuerier(connect, this.dialect, this.extra);
|
|
13
14
|
}
|
|
14
15
|
}
|
|
@@ -41,8 +41,15 @@ export declare abstract class AbstractSqlQuerier extends AbstractQuerier impleme
|
|
|
41
41
|
* Drivers with native cursor/streaming APIs (SQLite, Pg) should override this.
|
|
42
42
|
*/
|
|
43
43
|
protected internalStream<T>(query: string, values?: unknown[]): AsyncIterable<T>;
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Turn what a driver returned back into the types the entity declares, for the row and everything
|
|
46
|
+
* populated under it. Which columns, and as what, is `hydratableFields`; the per-cell decode is
|
|
47
|
+
* `decodeColumn`. Both live with the dialect, because a `sparsevec` is only sparse on Postgres.
|
|
48
|
+
*
|
|
49
|
+
* `visited` guards a populated graph that points back at itself; it defaults rather than living in
|
|
50
|
+
* a separate entry-point wrapper, because the wrapper's whole body was seeding it.
|
|
51
|
+
*/
|
|
52
|
+
private hydrateFields;
|
|
46
53
|
protected internalCount<E extends object>(entity: Type<E>, q?: QuerySearch<E>, opts?: QueryOptions): Promise<number>;
|
|
47
54
|
protected internalAggregate<E extends object, G extends QueryGroupMap<E>, A extends QueryAggMap<E>>(entity: Type<E>, q: QueryAggregate<E, G, A>, opts?: QueryOptions): Promise<QueryAggregateResult<E, G, A>[]>;
|
|
48
55
|
internalInsertMany<E extends object>(entity: Type<E>, payload: E[]): Promise<IdValue<E>[]>;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { decodeColumn } from '../dialect/hydrateColumn.js';
|
|
1
2
|
import { getMeta } from '../entity/index.js';
|
|
2
3
|
import { buildUpdateResult, clone, getInsertFieldKeys, getRelationRequestSummary, isAutoIncrement, obtainAttrsPaths, throwNoPendingTransaction, throwPendingTransaction, unflatObject, unflatObjects, withoutSoftDeleteFilter, } from '../util/index.js';
|
|
3
4
|
import { AbstractQuerier } from './abstractQuerier.js';
|
|
@@ -54,7 +55,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
54
55
|
const ctx = this.dialect.createContext();
|
|
55
56
|
this.dialect.find(ctx, entity, q, opts);
|
|
56
57
|
const res = await this.all(ctx.sql, ctx.values);
|
|
57
|
-
const founds = unflatObjects(res).map((row) => this.
|
|
58
|
+
const founds = unflatObjects(res).map((row) => this.hydrateFields(entity, row));
|
|
58
59
|
await this.fillToManyRelations(entity, founds, q.$populate);
|
|
59
60
|
return founds;
|
|
60
61
|
}
|
|
@@ -74,7 +75,7 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
74
75
|
try {
|
|
75
76
|
for await (const row of this.internalStream(ctx.sql, normalizedParams)) {
|
|
76
77
|
attrsPaths ??= obtainAttrsPaths(row);
|
|
77
|
-
yield this.
|
|
78
|
+
yield this.hydrateFields(entity, unflatObject(row, attrsPaths));
|
|
78
79
|
}
|
|
79
80
|
}
|
|
80
81
|
catch (err) {
|
|
@@ -90,31 +91,25 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
90
91
|
const rows = await this.internalAll(query, this.dialect.normalizeValues(values));
|
|
91
92
|
yield* rows;
|
|
92
93
|
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
94
|
+
/**
|
|
95
|
+
* Turn what a driver returned back into the types the entity declares, for the row and everything
|
|
96
|
+
* populated under it. Which columns, and as what, is `hydratableFields`; the per-cell decode is
|
|
97
|
+
* `decodeColumn`. Both live with the dialect, because a `sparsevec` is only sparse on Postgres.
|
|
98
|
+
*
|
|
99
|
+
* `visited` guards a populated graph that points back at itself; it defaults rather than living in
|
|
100
|
+
* a separate entry-point wrapper, because the wrapper's whole body was seeding it.
|
|
101
|
+
*/
|
|
102
|
+
hydrateFields(entity, dto, visited = new WeakSet()) {
|
|
98
103
|
if (!dto || typeof dto !== 'object' || visited.has(dto)) {
|
|
99
|
-
return;
|
|
104
|
+
return dto;
|
|
100
105
|
}
|
|
101
106
|
visited.add(dto);
|
|
102
107
|
const meta = getMeta(entity);
|
|
103
108
|
const row = dto;
|
|
104
|
-
for (const key
|
|
105
|
-
const field = meta.fields[key];
|
|
106
|
-
if (!field || (field.type !== 'json' && field.type !== 'jsonb')) {
|
|
107
|
-
continue;
|
|
108
|
-
}
|
|
109
|
+
for (const [key, kind] of this.dialect.hydratableFields(entity)) {
|
|
109
110
|
const value = row[key];
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
try {
|
|
114
|
-
row[key] = JSON.parse(value);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Keep the original value when the driver returns non-JSON text.
|
|
111
|
+
if (value != null) {
|
|
112
|
+
row[key] = decodeColumn(value, kind);
|
|
118
113
|
}
|
|
119
114
|
}
|
|
120
115
|
for (const key in meta.relations) {
|
|
@@ -125,26 +120,38 @@ export class AbstractSqlQuerier extends AbstractQuerier {
|
|
|
125
120
|
const value = row[key];
|
|
126
121
|
if (Array.isArray(value)) {
|
|
127
122
|
for (const it of value) {
|
|
128
|
-
this.
|
|
123
|
+
this.hydrateFields(relEntity, it, visited);
|
|
129
124
|
}
|
|
130
125
|
continue;
|
|
131
126
|
}
|
|
132
127
|
if (value && typeof value === 'object') {
|
|
133
|
-
this.
|
|
128
|
+
this.hydrateFields(relEntity, value, visited);
|
|
134
129
|
}
|
|
135
130
|
}
|
|
131
|
+
return dto;
|
|
136
132
|
}
|
|
137
133
|
async internalCount(entity, q = {}, opts) {
|
|
138
134
|
const ctx = this.dialect.createContext();
|
|
139
135
|
this.dialect.count(ctx, entity, q, opts);
|
|
140
136
|
const res = await this.all(ctx.sql, ctx.values);
|
|
137
|
+
// `COUNT(*)` is BIGINT, which the pools decode at the wire - but a caller who supplies their own
|
|
138
|
+
// `types` replaces that, and the signature promises a number here regardless.
|
|
141
139
|
return Number(res[0].count);
|
|
142
140
|
}
|
|
143
141
|
async internalAggregate(entity, q, opts) {
|
|
144
142
|
const ctx = this.dialect.createContext();
|
|
145
143
|
this.dialect.aggregate(ctx, entity, q, opts);
|
|
146
144
|
// biome-ignore lint/suspicious/noExplicitAny: raw DB rows satisfy QueryAggregateResult at runtime but TS can't verify
|
|
147
|
-
|
|
145
|
+
const res = await this.all(ctx.sql, ctx.values);
|
|
146
|
+
const hydratable = this.dialect.hydratableAggregates(entity, q);
|
|
147
|
+
for (const row of res) {
|
|
148
|
+
for (const [alias, kind] of hydratable) {
|
|
149
|
+
if (row[alias] != null) {
|
|
150
|
+
row[alias] = decodeColumn(row[alias], kind);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
return res;
|
|
148
155
|
}
|
|
149
156
|
async internalInsertMany(entity, payload) {
|
|
150
157
|
if (!payload?.length) {
|
|
@@ -6,15 +6,10 @@
|
|
|
6
6
|
* - Canonical types (dialect-agnostic)
|
|
7
7
|
* - TypeScript types (for entity generation)
|
|
8
8
|
*/
|
|
9
|
-
// ============================================================================
|
|
10
|
-
// Vector Category Helpers
|
|
11
|
-
// ============================================================================
|
|
12
9
|
/** Whether a category is one of the vector types, narrowing it to the cast pgvector names use. */
|
|
13
10
|
export function isVectorCategory(category) {
|
|
14
11
|
return category === 'vector' || category === 'halfvec' || category === 'sparsevec';
|
|
15
12
|
}
|
|
16
|
-
// Type Mapping Tables
|
|
17
|
-
// ============================================================================
|
|
18
13
|
/**
|
|
19
14
|
* Maps SQL type strings to canonical type categories.
|
|
20
15
|
* Handles variations across dialects (PostgreSQL, MySQL, SQLite).
|
|
@@ -250,9 +245,6 @@ const CANONICAL_TO_TS = {
|
|
|
250
245
|
halfvec: 'number[]',
|
|
251
246
|
sparsevec: 'number[]',
|
|
252
247
|
};
|
|
253
|
-
// ============================================================================
|
|
254
|
-
// Type Conversion Functions
|
|
255
|
-
// ============================================================================
|
|
256
248
|
/**
|
|
257
249
|
* Parse a SQL type string into a canonical type.
|
|
258
250
|
* Handles complex types like VARCHAR(255), DECIMAL(10,2), etc.
|
|
@@ -426,10 +418,8 @@ export function fieldOptionsToCanonical(options, tsType) {
|
|
|
426
418
|
scale: options.scale,
|
|
427
419
|
};
|
|
428
420
|
}
|
|
429
|
-
//
|
|
430
|
-
|
|
431
|
-
return { category: 'integer', size: 'big' };
|
|
432
|
-
}
|
|
421
|
+
// BIGINT for every `Number`, key or not: a 32-bit column is a migration waiting to happen, and
|
|
422
|
+
// the pools decode it back to a JS number at the wire (see `pgNumericTypes`).
|
|
433
423
|
return { category: 'integer', size: 'big' };
|
|
434
424
|
}
|
|
435
425
|
if (type === Boolean) {
|
package/dist/schema/schemaAST.js
CHANGED
|
@@ -18,9 +18,6 @@ export class SchemaAST {
|
|
|
18
18
|
tables = new Map();
|
|
19
19
|
relationships = [];
|
|
20
20
|
indexes = [];
|
|
21
|
-
// ============================================================================
|
|
22
|
-
// Table Operations
|
|
23
|
-
// ============================================================================
|
|
24
21
|
/**
|
|
25
22
|
* Get a table by name.
|
|
26
23
|
*/
|
|
@@ -68,9 +65,6 @@ export class SchemaAST {
|
|
|
68
65
|
getTableNames() {
|
|
69
66
|
return Array.from(this.tables.keys());
|
|
70
67
|
}
|
|
71
|
-
// ============================================================================
|
|
72
|
-
// Graph Navigation
|
|
73
|
-
// ============================================================================
|
|
74
68
|
/**
|
|
75
69
|
* Get all tables that depend on this table (have FKs pointing to it).
|
|
76
70
|
* These are tables that reference this table's primary key.
|
|
@@ -103,9 +97,6 @@ export class SchemaAST {
|
|
|
103
97
|
getReferencedColumn(fkColumn) {
|
|
104
98
|
return fkColumn.references?.to.columns[0];
|
|
105
99
|
}
|
|
106
|
-
// ============================================================================
|
|
107
|
-
// Graph Analysis
|
|
108
|
-
// ============================================================================
|
|
109
100
|
/**
|
|
110
101
|
* Detect circular foreign key dependencies.
|
|
111
102
|
* Returns arrays of tables that form cycles.
|
|
@@ -179,9 +170,6 @@ export class SchemaAST {
|
|
|
179
170
|
}
|
|
180
171
|
return result;
|
|
181
172
|
}
|
|
182
|
-
// ============================================================================
|
|
183
|
-
// Validation
|
|
184
|
-
// ============================================================================
|
|
185
173
|
/**
|
|
186
174
|
* Validate schema integrity.
|
|
187
175
|
* Checks for:
|
|
@@ -233,9 +221,6 @@ export class SchemaAST {
|
|
|
233
221
|
isValid() {
|
|
234
222
|
return this.validate().length === 0;
|
|
235
223
|
}
|
|
236
|
-
// ============================================================================
|
|
237
|
-
// Smart Relation Detection
|
|
238
|
-
// ============================================================================
|
|
239
224
|
/**
|
|
240
225
|
* Check if a table looks like a junction table (ManyToMany through).
|
|
241
226
|
* Junction tables typically have:
|
|
@@ -292,9 +277,6 @@ export class SchemaAST {
|
|
|
292
277
|
return 'ManyToMany';
|
|
293
278
|
}
|
|
294
279
|
}
|
|
295
|
-
// ============================================================================
|
|
296
|
-
// Index Operations
|
|
297
|
-
// ============================================================================
|
|
298
280
|
/**
|
|
299
281
|
* Add an index to the schema.
|
|
300
282
|
*/
|
|
@@ -317,9 +299,6 @@ export class SchemaAST {
|
|
|
317
299
|
getIndex(name) {
|
|
318
300
|
return this.indexes.find((i) => i.name === name);
|
|
319
301
|
}
|
|
320
|
-
// ============================================================================
|
|
321
|
-
// Relationship Operations
|
|
322
|
-
// ============================================================================
|
|
323
302
|
/**
|
|
324
303
|
* Add a relationship to the schema.
|
|
325
304
|
*/
|
|
@@ -365,9 +344,6 @@ export class SchemaAST {
|
|
|
365
344
|
this.relationships.splice(index, 1);
|
|
366
345
|
return true;
|
|
367
346
|
}
|
|
368
|
-
// ============================================================================
|
|
369
|
-
// Utility Methods
|
|
370
|
-
// ============================================================================
|
|
371
347
|
/**
|
|
372
348
|
* Create a deep clone of this schema.
|
|
373
349
|
*/
|
|
@@ -34,9 +34,6 @@ export class SchemaASTBuilder {
|
|
|
34
34
|
getAST() {
|
|
35
35
|
return this.ast;
|
|
36
36
|
}
|
|
37
|
-
// ============================================================================
|
|
38
|
-
// Build from Entities
|
|
39
|
-
// ============================================================================
|
|
40
37
|
/**
|
|
41
38
|
* Build AST from entity classes (decorated with @Entity, @Field, etc.)
|
|
42
39
|
*/
|
|
@@ -107,6 +107,9 @@ type FieldValueType<E, F> = F extends keyof E ? E[F] : unknown;
|
|
|
107
107
|
/**
|
|
108
108
|
* Resolves a single computed column's type from its aggregate function: `$count`/`$sum`/`$avg` are
|
|
109
109
|
* always `number`; `$min`/`$max` keep the aggregated field's own type.
|
|
110
|
+
*
|
|
111
|
+
* `$sum`/`$avg` are exact to 2^53: Postgres widens a sum over BIGINT to NUMERIC, and decoding that
|
|
112
|
+
* text to satisfy this `number` drops the digits past that bound. Use `raw()` for a wider total.
|
|
110
113
|
* @internal
|
|
111
114
|
*/
|
|
112
115
|
type QueryAggregateFnResult<E, Fn> = Fn extends QueryAggregateNumericFn ? number : Fn extends {
|
|
@@ -3,6 +3,10 @@ import type { FieldOptions } from '../type/index.js';
|
|
|
3
3
|
* Checks if a field type is numeric (Number, BigInt, or explicit numeric logical types)
|
|
4
4
|
*/
|
|
5
5
|
export declare function isNumericType(type: unknown): boolean;
|
|
6
|
+
/**
|
|
7
|
+
* Checks if a field type is boolean (Boolean, or an explicit boolean logical type)
|
|
8
|
+
*/
|
|
9
|
+
export declare function isBooleanType(type: unknown): boolean;
|
|
6
10
|
/**
|
|
7
11
|
* Checks if a field type is JSON
|
|
8
12
|
*/
|