linkgress-orm 0.1.16 → 0.1.18

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.
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.DatabaseContext = exports.DbEntityTable = exports.EntityInsertBuilder = exports.DataContext = exports.TableAccessor = exports.InsertBuilder = exports.QueryExecutor = exports.TimeTracer = void 0;
4
4
  const entity_base_1 = require("./entity-base");
5
5
  const model_config_1 = require("./model-config");
6
- const conditions_1 = require("../query/conditions");
7
6
  const query_builder_1 = require("../query/query-builder");
8
7
  const db_schema_manager_1 = require("../migration/db-schema-manager");
9
8
  const sequence_builder_1 = require("../schema/sequence-builder");
@@ -601,7 +600,12 @@ class TableAccessor {
601
600
  sql += '\n OVERRIDING SYSTEM VALUE';
602
601
  }
603
602
  sql += `
604
- VALUES ${valueClauses.join(', ')}
603
+ VALUES ${valueClauses.join(', ')}`;
604
+ // Add ON CONFLICT DO NOTHING if specified
605
+ if (insertConfig?.onConflictDoNothing) {
606
+ sql += '\n ON CONFLICT DO NOTHING';
607
+ }
608
+ sql += `
605
609
  RETURNING ${returningColumns}
606
610
  `;
607
611
  const result = this.executor
@@ -664,65 +668,6 @@ class TableAccessor {
664
668
  }
665
669
  return builder.execute();
666
670
  }
667
- /**
668
- * Bulk insert multiple rows (simple version, kept for compatibility)
669
- */
670
- async insertMany(dataArray) {
671
- if (dataArray.length === 0) {
672
- return [];
673
- }
674
- // Extract all unique column names from all data objects
675
- const columnSet = new Set();
676
- for (const data of dataArray) {
677
- for (const key of Object.keys(data)) {
678
- const column = this.schema.columns[key];
679
- if (column) {
680
- const config = column.build();
681
- if (!config.autoIncrement) {
682
- columnSet.add(key);
683
- }
684
- }
685
- }
686
- }
687
- const columns = Array.from(columnSet);
688
- const values = [];
689
- const valuePlaceholders = [];
690
- let paramIndex = 1;
691
- // Build placeholders for each row
692
- for (const data of dataArray) {
693
- const rowPlaceholders = [];
694
- for (const key of columns) {
695
- const value = data[key];
696
- const column = this.schema.columns[key];
697
- const config = column.build();
698
- // Apply toDriver mapper if present
699
- const mappedValue = config.mapper
700
- ? config.mapper.toDriver(value !== undefined ? value : null)
701
- : (value !== undefined ? value : null);
702
- values.push(mappedValue);
703
- rowPlaceholders.push(`$${paramIndex++}`);
704
- }
705
- valuePlaceholders.push(`(${rowPlaceholders.join(', ')})`);
706
- }
707
- const columnNames = columns.map(key => {
708
- const column = this.schema.columns[key];
709
- const config = column.build();
710
- return `"${config.name}"`;
711
- });
712
- const returningColumns = Object.entries(this.schema.columns)
713
- .map(([_, col]) => `"${col.build().name}"`)
714
- .join(', ');
715
- const qualifiedTableName = (0, sql_utils_1.getQualifiedTableName)(this.schema);
716
- const sql = `
717
- INSERT INTO ${qualifiedTableName} (${columnNames.join(', ')})
718
- VALUES ${valuePlaceholders.join(', ')}
719
- RETURNING ${returningColumns}
720
- `;
721
- const result = this.executor
722
- ? await this.executor.query(sql, values)
723
- : await this.client.query(sql, values);
724
- return result.rows;
725
- }
726
671
  /**
727
672
  * Insert with conflict resolution (upsert)
728
673
  */
@@ -856,10 +801,23 @@ class DataContext {
856
801
  return accessor;
857
802
  }
858
803
  /**
859
- * Execute raw SQL
804
+ * Execute raw SQL query with optional type parameter for results
805
+ *
806
+ * @example
807
+ * ```typescript
808
+ * // Untyped query
809
+ * const result = await db.query('SELECT * FROM users');
810
+ *
811
+ * // Typed query - returns T[]
812
+ * const users = await db.query<{ id: number; name: string }>('SELECT id, name FROM users');
813
+ *
814
+ * // With parameters
815
+ * const user = await db.query<{ id: number }>('SELECT id FROM users WHERE name = $1', ['alice']);
816
+ * ```
860
817
  */
861
818
  async query(sql, params) {
862
- return this.client.query(sql, params);
819
+ const result = await this.client.query(sql, params);
820
+ return result.rows;
863
821
  }
864
822
  /**
865
823
  * Execute in transaction
@@ -972,6 +930,89 @@ class DbEntityTable {
972
930
  _getCollectionStrategy() {
973
931
  return this.context.queryOptions?.collectionStrategy;
974
932
  }
933
+ /**
934
+ * Get information about all columns in this table.
935
+ * This method returns metadata about database columns only, excluding navigation properties.
936
+ *
937
+ * @param options - Optional configuration
938
+ * @param options.includeNavigation - If false (default), only returns database columns.
939
+ * Navigation properties are never included as they are not columns.
940
+ *
941
+ * @returns Array of column information objects
942
+ *
943
+ * @example
944
+ * ```typescript
945
+ * // Get all columns
946
+ * const columns = db.users.getColumns();
947
+ * // Returns: [
948
+ * // { propertyName: 'id', columnName: 'id', type: 'integer', isPrimaryKey: true, ... },
949
+ * // { propertyName: 'username', columnName: 'username', type: 'varchar', ... },
950
+ * // { propertyName: 'email', columnName: 'email', type: 'text', ... },
951
+ * // ]
952
+ *
953
+ * // Get column names only
954
+ * const columnNames = db.users.getColumns().map(c => c.propertyName);
955
+ * // Returns: ['id', 'username', 'email', ...]
956
+ *
957
+ * // Get database column names
958
+ * const dbColumnNames = db.users.getColumns().map(c => c.columnName);
959
+ * // Returns: ['id', 'username', 'email', ...]
960
+ * ```
961
+ */
962
+ getColumns() {
963
+ const schema = this._getSchema();
964
+ const columns = [];
965
+ for (const [propName, colBuilder] of Object.entries(schema.columns)) {
966
+ const config = colBuilder.build();
967
+ columns.push({
968
+ propertyName: propName,
969
+ columnName: config.name,
970
+ type: config.type,
971
+ isPrimaryKey: config.primaryKey || false,
972
+ isAutoIncrement: config.autoIncrement || !!config.identity,
973
+ isNullable: config.nullable !== false,
974
+ isUnique: config.unique || false,
975
+ defaultValue: config.default,
976
+ });
977
+ }
978
+ return columns;
979
+ }
980
+ /**
981
+ * Get an object containing all entity properties as DbColumn references.
982
+ * Useful for building dynamic queries or accessing column metadata.
983
+ *
984
+ * @param options - Optional configuration
985
+ * @param options.excludeNavigation - If true (default), excludes navigation properties.
986
+ * Set to false to include navigation properties.
987
+ *
988
+ * @returns Object with property names as keys and their DbColumn/navigation references as values
989
+ *
990
+ * @example
991
+ * ```typescript
992
+ * // Get all column properties (excludes navigation by default)
993
+ * const cols = db.users.props();
994
+ * // Use in select: db.users.select(u => ({ id: cols.id, name: cols.username }))
995
+ *
996
+ * // Include navigation properties
997
+ * const allProps = db.users.props({ excludeNavigation: false });
998
+ * ```
999
+ */
1000
+ props(options) {
1001
+ const schema = this._getSchema();
1002
+ const excludeNav = options?.excludeNavigation !== false; // Default true
1003
+ // Create a temporary QueryBuilder to reuse the createMockRow logic
1004
+ const qb = new query_builder_1.QueryBuilder(schema, this._getClient(), undefined, undefined, undefined, undefined, this._getExecutor(), undefined, undefined, this._getCollectionStrategy(), this.context.schemaRegistry);
1005
+ const mockRow = qb._createMockRow();
1006
+ if (excludeNav) {
1007
+ // Filter out navigation properties, keep only columns
1008
+ const result = {};
1009
+ for (const propName of Object.keys(schema.columns)) {
1010
+ result[propName] = mockRow[propName];
1011
+ }
1012
+ return result;
1013
+ }
1014
+ return mockRow;
1015
+ }
975
1016
  /**
976
1017
  * Get qualified table name with schema prefix if specified
977
1018
  * @internal
@@ -1264,77 +1305,22 @@ class DbEntityTable {
1264
1305
  * ```
1265
1306
  */
1266
1307
  insert(data) {
1267
- const table = this;
1268
- const executeInsert = async (returning) => {
1269
- const schema = table._getSchema();
1270
- const executor = table._getExecutor();
1271
- const client = table._getClient();
1272
- // Build INSERT columns and values
1273
- const columns = [];
1274
- const values = [];
1275
- const placeholders = [];
1276
- let paramIndex = 1;
1277
- for (const [key, value] of Object.entries(data)) {
1278
- const column = schema.columns[key];
1279
- if (column) {
1280
- const config = column.build();
1281
- if (!config.autoIncrement) {
1282
- columns.push(`"${config.name}"`);
1283
- const mappedValue = config.mapper ? config.mapper.toDriver(value) : value;
1284
- values.push(mappedValue);
1285
- placeholders.push(`$${paramIndex++}`);
1286
- }
1287
- }
1288
- }
1289
- // Build RETURNING clause
1290
- const returningClause = table.buildReturningClause(returning);
1291
- const qualifiedTableName = table._getQualifiedTableName();
1292
- let sql = `INSERT INTO ${qualifiedTableName} (${columns.join(', ')}) VALUES (${placeholders.join(', ')})`;
1293
- if (returningClause) {
1294
- sql += ` RETURNING ${returningClause.sql}`;
1295
- }
1296
- const result = executor
1297
- ? await executor.query(sql, values)
1298
- : await client.query(sql, values);
1299
- if (!returningClause) {
1300
- return undefined;
1301
- }
1302
- const mappedResults = table.mapReturningResults(result.rows, returning);
1303
- return mappedResults[0];
1304
- };
1308
+ const bulkBuilder = this.insertBulk([data]);
1305
1309
  return {
1306
1310
  then(onfulfilled, onrejected) {
1307
- return executeInsert(undefined).then(onfulfilled, onrejected);
1311
+ return bulkBuilder.then(onfulfilled, onrejected);
1308
1312
  },
1309
1313
  returning(selector) {
1310
- const returningConfig = selector ?? true;
1314
+ const bulkReturning = selector ? bulkBuilder.returning(selector) : bulkBuilder.returning();
1311
1315
  return {
1312
1316
  then(onfulfilled, onrejected) {
1313
- return executeInsert(returningConfig).then(onfulfilled, onrejected);
1317
+ // Unwrap array to single item
1318
+ return bulkReturning.then((results) => results?.[0], undefined).then(onfulfilled, onrejected);
1314
1319
  }
1315
1320
  };
1316
1321
  }
1317
1322
  };
1318
1323
  }
1319
- /**
1320
- * Insert multiple records
1321
- * Returns a fluent builder that can be awaited directly or chained with .returning()
1322
- *
1323
- * @example
1324
- * ```typescript
1325
- * // No returning (default) - returns void
1326
- * await db.users.insertMany([{ username: 'alice' }, { username: 'bob' }]);
1327
- *
1328
- * // With returning() - returns full entities
1329
- * const users = await db.users.insertMany([{ username: 'alice' }]).returning();
1330
- *
1331
- * // With returning(selector) - returns selected columns
1332
- * const results = await db.users.insertMany([{ username: 'alice' }]).returning(u => ({ id: u.id }));
1333
- * ```
1334
- */
1335
- insertMany(data) {
1336
- return this.insertBulk(data);
1337
- }
1338
1324
  /**
1339
1325
  * Upsert (insert or update on conflict)
1340
1326
  * Returns a fluent builder that can be awaited directly or chained with .returning()
@@ -1367,8 +1353,11 @@ class DbEntityTable {
1367
1353
  * // With returning(selector) - returns selected columns
1368
1354
  * const results = await db.users.insertBulk([{ username: 'alice' }]).returning(u => ({ id: u.id }));
1369
1355
  *
1370
- * // With options
1356
+ * // With chunk size
1371
1357
  * await db.users.insertBulk([{ username: 'alice' }], { chunkSize: 100 });
1358
+ *
1359
+ * // Skip duplicates (ON CONFLICT DO NOTHING)
1360
+ * await db.users.insertBulk([{ username: 'alice' }], { onConflictDoNothing: true });
1372
1361
  * ```
1373
1362
  */
1374
1363
  insertBulk(value, options) {
@@ -1391,13 +1380,13 @@ class DbEntityTable {
1391
1380
  const allResults = [];
1392
1381
  for (let i = 0; i < dataArray.length; i += chunkSize) {
1393
1382
  const chunk = dataArray.slice(i, i + chunkSize);
1394
- const chunkResults = await table.insertBulkSingle(chunk, returning, options?.overridingSystemValue);
1383
+ const chunkResults = await table.insertBulkSingle(chunk, returning, options?.overridingSystemValue, options?.onConflictDoNothing);
1395
1384
  if (chunkResults)
1396
1385
  allResults.push(...chunkResults);
1397
1386
  }
1398
1387
  return returning === undefined ? undefined : allResults;
1399
1388
  }
1400
- return table.insertBulkSingle(dataArray, returning, options?.overridingSystemValue);
1389
+ return table.insertBulkSingle(dataArray, returning, options?.overridingSystemValue, options?.onConflictDoNothing);
1401
1390
  };
1402
1391
  return {
1403
1392
  then(onfulfilled, onrejected) {
@@ -1417,7 +1406,7 @@ class DbEntityTable {
1417
1406
  * Execute a single bulk insert batch
1418
1407
  * @internal
1419
1408
  */
1420
- async insertBulkSingle(data, returning, overridingSystemValue) {
1409
+ async insertBulkSingle(data, returning, overridingSystemValue, onConflictDoNothing) {
1421
1410
  const schema = this._getSchema();
1422
1411
  const executor = this._getExecutor();
1423
1412
  const client = this._getClient();
@@ -1460,6 +1449,9 @@ class DbEntityTable {
1460
1449
  sql += ' OVERRIDING SYSTEM VALUE';
1461
1450
  }
1462
1451
  sql += ` VALUES ${valuesClauses.join(', ')}`;
1452
+ if (onConflictDoNothing) {
1453
+ sql += ' ON CONFLICT DO NOTHING';
1454
+ }
1463
1455
  if (returningClause) {
1464
1456
  sql += ` RETURNING ${returningClause.sql}`;
1465
1457
  }
@@ -1730,23 +1722,15 @@ class DbEntityTable {
1730
1722
  return new EntityInsertBuilder(builder);
1731
1723
  }
1732
1724
  /**
1733
- * Update records matching condition
1725
+ * Update all records in the table
1734
1726
  * Returns a fluent builder that can be awaited directly or chained with .returning()
1735
1727
  *
1736
- * @example
1737
- * ```typescript
1738
- * // No returning (default) - returns void
1739
- * await db.users.update({ age: 30 }, u => eq(u.id, 1));
1740
- *
1741
- * // With returning() - returns full entities
1742
- * const users = await db.users.update({ age: 30 }, u => eq(u.id, 1)).returning();
1743
- *
1744
- * // With returning(selector) - returns selected columns
1745
- * const results = await db.users.update({ age: 30 }, u => eq(u.id, 1))
1746
- * .returning(u => ({ id: u.id, age: u.age }));
1747
- * ```
1728
+ * Usage:
1729
+ * await db.users.update({ age: 30 }) // Update all
1730
+ * await db.users.where(u => eq(u.id, 1)).update({ age: 30 }) // Update with condition
1731
+ * const updated = await db.users.where(u => eq(u.id, 1)).update({ age: 30 }).returning()
1748
1732
  */
1749
- update(data, condition) {
1733
+ update(data) {
1750
1734
  const table = this;
1751
1735
  const executeUpdate = async (returning) => {
1752
1736
  const schema = table._getSchema();
@@ -1767,16 +1751,11 @@ class DbEntityTable {
1767
1751
  if (setClauses.length === 0) {
1768
1752
  throw new Error('No valid columns to update');
1769
1753
  }
1770
- // Build WHERE clause
1771
- const mockEntity = table.createMockEntity();
1772
- const whereCondition = condition(mockEntity);
1773
- const condBuilder = new conditions_1.ConditionBuilder();
1774
- const { sql: whereSql, params: whereParams } = condBuilder.build(whereCondition, paramIndex);
1775
- values.push(...whereParams);
1754
+ // No WHERE clause - updates all records
1776
1755
  // Build RETURNING clause
1777
1756
  const returningClause = table.buildReturningClause(returning);
1778
1757
  const qualifiedTableName = table._getQualifiedTableName();
1779
- let sql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')} WHERE ${whereSql}`;
1758
+ let sql = `UPDATE ${qualifiedTableName} SET ${setClauses.join(', ')}`;
1780
1759
  if (returningClause) {
1781
1760
  sql += ` RETURNING ${returningClause.sql}`;
1782
1761
  }
@@ -1995,23 +1974,54 @@ WHERE ${whereClause}`.trim();
1995
1974
  return this.mapReturningResults(result.rows, returning);
1996
1975
  }
1997
1976
  /**
1998
- * Delete records matching condition
1999
- * Usage: db.users.delete(u => eq(u.id, 1))
1977
+ * Delete all records from the table
1978
+ * Returns a fluent builder that can be awaited directly or chained with .returning()
1979
+ *
1980
+ * Usage:
1981
+ * await db.users.delete() // Delete all
1982
+ * await db.users.where(u => eq(u.id, 1)).delete() // Delete with condition
1983
+ * const deleted = await db.users.where(u => eq(u.id, 1)).delete().returning()
2000
1984
  */
2001
- async delete(condition) {
1985
+ delete() {
2002
1986
  const schema = this._getSchema();
2003
1987
  const executor = this._getExecutor();
2004
1988
  const client = this._getClient();
2005
- // Build WHERE clause
2006
- const mockEntity = this.createMockEntity();
2007
- const whereCondition = condition(mockEntity);
2008
- const condBuilder = new conditions_1.ConditionBuilder();
2009
- const { sql: whereSql, params: whereParams } = condBuilder.build(whereCondition, 1);
2010
1989
  const qualifiedTableName = this._getQualifiedTableName();
2011
- const sql = `DELETE FROM ${qualifiedTableName} WHERE ${whereSql}`;
2012
- const result = executor
2013
- ? await executor.query(sql, whereParams)
2014
- : await client.query(sql, whereParams);
1990
+ // No WHERE condition - deletes all records
1991
+ const baseSql = `DELETE FROM ${qualifiedTableName}`;
1992
+ const table = this;
1993
+ const executeDelete = async (returningConfig) => {
1994
+ if (!returningConfig) {
1995
+ const sql = baseSql;
1996
+ const result = executor
1997
+ ? await executor.query(sql, [])
1998
+ : await client.query(sql, []);
1999
+ return;
2000
+ }
2001
+ // Build RETURNING clause
2002
+ const returningClause = table.buildReturningClause(returningConfig);
2003
+ const sql = returningClause ? `${baseSql} RETURNING ${returningClause.sql}` : baseSql;
2004
+ const result = executor
2005
+ ? await executor.query(sql, [])
2006
+ : await client.query(sql, []);
2007
+ if (returningClause && result.rows) {
2008
+ return table.mapReturningResults(result.rows, returningConfig);
2009
+ }
2010
+ };
2011
+ const fluent = {
2012
+ then: (resolve, reject) => {
2013
+ return executeDelete().then(resolve, reject);
2014
+ },
2015
+ returning: ((selector) => {
2016
+ const returningConfig = selector ?? true;
2017
+ return {
2018
+ then: (resolve, reject) => {
2019
+ return executeDelete(returningConfig).then(resolve, reject);
2020
+ }
2021
+ };
2022
+ })
2023
+ };
2024
+ return fluent;
2015
2025
  }
2016
2026
  /**
2017
2027
  * Create a mock entity for type inference in lambdas