relq 1.0.42 → 1.0.44

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.
@@ -43,6 +43,7 @@ const pool_defaults_1 = require("../utils/pool-defaults.cjs");
43
43
  const relq_errors_1 = require("../errors/relq-errors.cjs");
44
44
  const type_coercion_1 = require("../utils/type-coercion.cjs");
45
45
  const helpers_1 = require("./helpers/index.cjs");
46
+ const scalar_select_builder_1 = require("../select/scalar-select-builder.cjs");
46
47
  let PgPool = null;
47
48
  let PgClient = null;
48
49
  const activeRelqInstances = new Set();
@@ -720,6 +721,14 @@ class Relq {
720
721
  }
721
722
  return result.result.rows.map((r) => r['QUERY PLAN'] || Object.values(r)[0]).join('\n');
722
723
  }
724
+ scalar(scalars) {
725
+ const schema = this._getSchema();
726
+ return new scalar_select_builder_1.ConnectedScalarSelectBuilder(scalars, schema, {
727
+ executeSelectOne: this[helpers_1.INTERNAL].executeSelect.bind(this),
728
+ hasColumnMapping: this._hasColumnMapping.bind(this),
729
+ transformToDbColumns: this._transformToDbColumns.bind(this)
730
+ });
731
+ }
723
732
  }
724
733
  exports.Relq = Relq;
725
734
  exports.default = Relq;
@@ -0,0 +1,134 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ScalarQueryBuilderImpl = void 0;
7
+ exports.createScalarTableAccessor = createScalarTableAccessor;
8
+ const pg_format_1 = __importDefault(require("../addon/pg-format/index.cjs"));
9
+ const condition_collector_1 = require("../condition/condition-collector.cjs");
10
+ class ScalarResultImpl {
11
+ sql;
12
+ params;
13
+ $scalar = true;
14
+ $type;
15
+ constructor(sql, params = []) {
16
+ this.sql = sql;
17
+ this.params = params;
18
+ }
19
+ toSQL() {
20
+ return this.sql;
21
+ }
22
+ getParams() {
23
+ return this.params;
24
+ }
25
+ }
26
+ class ScalarQueryBuilderImpl {
27
+ tableName;
28
+ tableSchema;
29
+ columnResolver;
30
+ whereConditions = [];
31
+ constructor(tableName, tableSchema, columnResolver) {
32
+ this.tableName = tableName;
33
+ this.tableSchema = tableSchema;
34
+ this.columnResolver = columnResolver;
35
+ }
36
+ where(callback) {
37
+ const conditionBuilder = new condition_collector_1.ConditionCollector();
38
+ callback(conditionBuilder);
39
+ this.whereConditions.push(...conditionBuilder.getConditions());
40
+ return this;
41
+ }
42
+ count() {
43
+ const sql = this.buildSQL('COUNT(*)');
44
+ return new ScalarResultImpl(sql);
45
+ }
46
+ sum(column) {
47
+ const sqlColumn = this.resolveColumn(column);
48
+ const sql = this.buildSQL(`COALESCE(SUM(${pg_format_1.default.ident(sqlColumn)}), 0)`);
49
+ return new ScalarResultImpl(sql);
50
+ }
51
+ avg(column) {
52
+ const sqlColumn = this.resolveColumn(column);
53
+ const sql = this.buildSQL(`COALESCE(AVG(${pg_format_1.default.ident(sqlColumn)}), 0)`);
54
+ return new ScalarResultImpl(sql);
55
+ }
56
+ min(column) {
57
+ const sqlColumn = this.resolveColumn(column);
58
+ const sql = this.buildSQL(`MIN(${pg_format_1.default.ident(sqlColumn)})`);
59
+ return new ScalarResultImpl(sql);
60
+ }
61
+ max(column) {
62
+ const sqlColumn = this.resolveColumn(column);
63
+ const sql = this.buildSQL(`MAX(${pg_format_1.default.ident(sqlColumn)})`);
64
+ return new ScalarResultImpl(sql);
65
+ }
66
+ pick(column) {
67
+ const sqlColumn = this.resolveColumn(column);
68
+ const sql = this.buildSQL(pg_format_1.default.ident(sqlColumn), true);
69
+ return new ScalarResultImpl(sql);
70
+ }
71
+ exists() {
72
+ const whereClause = this.buildWhereClause();
73
+ const sql = `(EXISTS(SELECT 1 FROM ${pg_format_1.default.ident(this.tableName)}${whereClause}))`;
74
+ return new ScalarResultImpl(sql);
75
+ }
76
+ resolveColumn(column) {
77
+ if (this.columnResolver) {
78
+ return this.columnResolver(column);
79
+ }
80
+ const columns = this.tableSchema?.$columns || this.tableSchema;
81
+ const columnDef = columns?.[column];
82
+ if (columnDef?.$columnName) {
83
+ return columnDef.$columnName;
84
+ }
85
+ return column.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
86
+ }
87
+ transformConditions(conditions) {
88
+ if (!this.columnResolver && !this.tableSchema) {
89
+ return conditions;
90
+ }
91
+ return conditions.map(cond => {
92
+ if (!cond.column || cond.method === 'raw') {
93
+ return cond;
94
+ }
95
+ if (cond.method === 'or' || cond.method === 'and') {
96
+ return {
97
+ ...cond,
98
+ values: this.transformConditions(cond.values)
99
+ };
100
+ }
101
+ return {
102
+ ...cond,
103
+ column: this.resolveColumn(cond.column)
104
+ };
105
+ });
106
+ }
107
+ buildWhereClause() {
108
+ if (this.whereConditions.length === 0) {
109
+ return '';
110
+ }
111
+ const transformed = this.transformConditions(this.whereConditions);
112
+ return ` WHERE ${(0, condition_collector_1.buildConditionsSQL)(transformed)}`;
113
+ }
114
+ buildSQL(selectExpr, withLimit = false) {
115
+ const whereClause = this.buildWhereClause();
116
+ const limitClause = withLimit ? ' LIMIT 1' : '';
117
+ return `(SELECT ${selectExpr} FROM ${pg_format_1.default.ident(this.tableName)}${whereClause}${limitClause})`;
118
+ }
119
+ }
120
+ exports.ScalarQueryBuilderImpl = ScalarQueryBuilderImpl;
121
+ function createScalarTableAccessor(schema, getColumnResolver) {
122
+ return new Proxy({}, {
123
+ get(_, prop) {
124
+ if (typeof prop === 'symbol') {
125
+ return undefined;
126
+ }
127
+ const tables = schema?.tables || schema;
128
+ const tableDef = tables?.[prop];
129
+ const sqlTableName = tableDef?.$name || prop;
130
+ const columnResolver = getColumnResolver?.(sqlTableName);
131
+ return new ScalarQueryBuilderImpl(sqlTableName, tableDef, columnResolver);
132
+ }
133
+ });
134
+ }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConnectedScalarSelectBuilder = exports.ScalarSelectBuilder = void 0;
7
+ const pg_format_1 = __importDefault(require("../addon/pg-format/index.cjs"));
8
+ const scalar_query_builder_1 = require("./scalar-query-builder.cjs");
9
+ class ScalarSelectBuilder {
10
+ executeQuery;
11
+ scalarResults;
12
+ constructor(scalars, schema, executeQuery, getColumnResolver) {
13
+ this.executeQuery = executeQuery;
14
+ const tableAccessor = (0, scalar_query_builder_1.createScalarTableAccessor)(schema, getColumnResolver);
15
+ this.scalarResults = {};
16
+ for (const [key, callback] of Object.entries(scalars)) {
17
+ this.scalarResults[key] = callback(tableAccessor);
18
+ }
19
+ }
20
+ toString() {
21
+ const columns = Object.entries(this.scalarResults)
22
+ .map(([alias, scalar]) => `${scalar.toSQL()} AS ${pg_format_1.default.ident(alias)}`)
23
+ .join(', ');
24
+ return `SELECT ${columns}`;
25
+ }
26
+ async get(withMetadata) {
27
+ const sql = this.toString();
28
+ const { data, metadata } = await this.executeQuery(sql);
29
+ const row = Array.isArray(data) ? data[0] : data;
30
+ const result = this.transformResult(row);
31
+ if (withMetadata) {
32
+ return { data: result, metadata };
33
+ }
34
+ return result;
35
+ }
36
+ transformResult(row) {
37
+ if (!row) {
38
+ const result = {};
39
+ for (const key of Object.keys(this.scalarResults)) {
40
+ result[key] = null;
41
+ }
42
+ return result;
43
+ }
44
+ const result = {};
45
+ for (const [key, value] of Object.entries(row)) {
46
+ if (typeof value === 'string' && /^\d+$/.test(value)) {
47
+ result[key] = parseInt(value, 10);
48
+ }
49
+ else if (typeof value === 'string' && /^\d+\.\d+$/.test(value)) {
50
+ result[key] = parseFloat(value);
51
+ }
52
+ else {
53
+ result[key] = value;
54
+ }
55
+ }
56
+ return result;
57
+ }
58
+ }
59
+ exports.ScalarSelectBuilder = ScalarSelectBuilder;
60
+ class ConnectedScalarSelectBuilder extends ScalarSelectBuilder {
61
+ constructor(scalars, schema, internal) {
62
+ const executeQuery = async (sql) => {
63
+ return internal.executeSelectOne(sql);
64
+ };
65
+ const getColumnResolver = (tableName) => {
66
+ if (internal.hasColumnMapping()) {
67
+ return (column) => {
68
+ const transformed = internal.transformToDbColumns(tableName, { [column]: null });
69
+ const keys = Object.keys(transformed);
70
+ return keys.length > 0 ? keys[0] : column;
71
+ };
72
+ }
73
+ return undefined;
74
+ };
75
+ super(scalars, schema, executeQuery, getColumnResolver);
76
+ }
77
+ }
78
+ exports.ConnectedScalarSelectBuilder = ConnectedScalarSelectBuilder;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -16,6 +16,26 @@ class ForeignKeyResolutionError extends Error {
16
16
  }
17
17
  }
18
18
  exports.ForeignKeyResolutionError = ForeignKeyResolutionError;
19
+ function isArrayFormat(tableRelations) {
20
+ return Array.isArray(tableRelations);
21
+ }
22
+ function* iterateForeignKeys(tableRelations) {
23
+ if (!tableRelations)
24
+ return;
25
+ if (isArrayFormat(tableRelations)) {
26
+ for (const relationDef of tableRelations) {
27
+ if (relationDef.$type === 'foreignKey' && relationDef.$columns?.length > 0) {
28
+ const columnKey = relationDef.$columns[0].column;
29
+ yield [columnKey, relationDef];
30
+ }
31
+ }
32
+ }
33
+ else {
34
+ for (const [columnKey, relationDef] of Object.entries(tableRelations)) {
35
+ yield [columnKey, relationDef];
36
+ }
37
+ }
38
+ }
19
39
  function resolveForeignKey(relations, schema, fromTableKey, toTableKey) {
20
40
  if (!relations) {
21
41
  return null;
@@ -7,6 +7,7 @@ import { validatePoolConfig, formatPoolConfig, getSmartPoolDefaults } from "../u
7
7
  import { RelqEnvironmentError, RelqConfigError, RelqQueryError, RelqConnectionError, parsePostgresError } from "../errors/relq-errors.js";
8
8
  import { deserializeValue, serializeValue } from "../utils/type-coercion.js";
9
9
  import { ConnectedCTEBuilder, ConnectedQueryBuilder, ConnectedRawQueryBuilder, ConnectedTransactionBuilder, INTERNAL, debugLog } from "./helpers/index.js";
10
+ import { ConnectedScalarSelectBuilder } from "../select/scalar-select-builder.js";
10
11
  let PgPool = null;
11
12
  let PgClient = null;
12
13
  const activeRelqInstances = new Set();
@@ -684,5 +685,13 @@ export class Relq {
684
685
  }
685
686
  return result.result.rows.map((r) => r['QUERY PLAN'] || Object.values(r)[0]).join('\n');
686
687
  }
688
+ scalar(scalars) {
689
+ const schema = this._getSchema();
690
+ return new ConnectedScalarSelectBuilder(scalars, schema, {
691
+ executeSelectOne: this[INTERNAL].executeSelect.bind(this),
692
+ hasColumnMapping: this._hasColumnMapping.bind(this),
693
+ transformToDbColumns: this._transformToDbColumns.bind(this)
694
+ });
695
+ }
687
696
  }
688
697
  export default Relq;
@@ -0,0 +1,126 @@
1
+ import format from "../addon/pg-format/index.js";
2
+ import { ConditionCollector, buildConditionsSQL } from "../condition/condition-collector.js";
3
+ class ScalarResultImpl {
4
+ sql;
5
+ params;
6
+ $scalar = true;
7
+ $type;
8
+ constructor(sql, params = []) {
9
+ this.sql = sql;
10
+ this.params = params;
11
+ }
12
+ toSQL() {
13
+ return this.sql;
14
+ }
15
+ getParams() {
16
+ return this.params;
17
+ }
18
+ }
19
+ export class ScalarQueryBuilderImpl {
20
+ tableName;
21
+ tableSchema;
22
+ columnResolver;
23
+ whereConditions = [];
24
+ constructor(tableName, tableSchema, columnResolver) {
25
+ this.tableName = tableName;
26
+ this.tableSchema = tableSchema;
27
+ this.columnResolver = columnResolver;
28
+ }
29
+ where(callback) {
30
+ const conditionBuilder = new ConditionCollector();
31
+ callback(conditionBuilder);
32
+ this.whereConditions.push(...conditionBuilder.getConditions());
33
+ return this;
34
+ }
35
+ count() {
36
+ const sql = this.buildSQL('COUNT(*)');
37
+ return new ScalarResultImpl(sql);
38
+ }
39
+ sum(column) {
40
+ const sqlColumn = this.resolveColumn(column);
41
+ const sql = this.buildSQL(`COALESCE(SUM(${format.ident(sqlColumn)}), 0)`);
42
+ return new ScalarResultImpl(sql);
43
+ }
44
+ avg(column) {
45
+ const sqlColumn = this.resolveColumn(column);
46
+ const sql = this.buildSQL(`COALESCE(AVG(${format.ident(sqlColumn)}), 0)`);
47
+ return new ScalarResultImpl(sql);
48
+ }
49
+ min(column) {
50
+ const sqlColumn = this.resolveColumn(column);
51
+ const sql = this.buildSQL(`MIN(${format.ident(sqlColumn)})`);
52
+ return new ScalarResultImpl(sql);
53
+ }
54
+ max(column) {
55
+ const sqlColumn = this.resolveColumn(column);
56
+ const sql = this.buildSQL(`MAX(${format.ident(sqlColumn)})`);
57
+ return new ScalarResultImpl(sql);
58
+ }
59
+ pick(column) {
60
+ const sqlColumn = this.resolveColumn(column);
61
+ const sql = this.buildSQL(format.ident(sqlColumn), true);
62
+ return new ScalarResultImpl(sql);
63
+ }
64
+ exists() {
65
+ const whereClause = this.buildWhereClause();
66
+ const sql = `(EXISTS(SELECT 1 FROM ${format.ident(this.tableName)}${whereClause}))`;
67
+ return new ScalarResultImpl(sql);
68
+ }
69
+ resolveColumn(column) {
70
+ if (this.columnResolver) {
71
+ return this.columnResolver(column);
72
+ }
73
+ const columns = this.tableSchema?.$columns || this.tableSchema;
74
+ const columnDef = columns?.[column];
75
+ if (columnDef?.$columnName) {
76
+ return columnDef.$columnName;
77
+ }
78
+ return column.replace(/([A-Z])/g, '_$1').toLowerCase().replace(/^_/, '');
79
+ }
80
+ transformConditions(conditions) {
81
+ if (!this.columnResolver && !this.tableSchema) {
82
+ return conditions;
83
+ }
84
+ return conditions.map(cond => {
85
+ if (!cond.column || cond.method === 'raw') {
86
+ return cond;
87
+ }
88
+ if (cond.method === 'or' || cond.method === 'and') {
89
+ return {
90
+ ...cond,
91
+ values: this.transformConditions(cond.values)
92
+ };
93
+ }
94
+ return {
95
+ ...cond,
96
+ column: this.resolveColumn(cond.column)
97
+ };
98
+ });
99
+ }
100
+ buildWhereClause() {
101
+ if (this.whereConditions.length === 0) {
102
+ return '';
103
+ }
104
+ const transformed = this.transformConditions(this.whereConditions);
105
+ return ` WHERE ${buildConditionsSQL(transformed)}`;
106
+ }
107
+ buildSQL(selectExpr, withLimit = false) {
108
+ const whereClause = this.buildWhereClause();
109
+ const limitClause = withLimit ? ' LIMIT 1' : '';
110
+ return `(SELECT ${selectExpr} FROM ${format.ident(this.tableName)}${whereClause}${limitClause})`;
111
+ }
112
+ }
113
+ export function createScalarTableAccessor(schema, getColumnResolver) {
114
+ return new Proxy({}, {
115
+ get(_, prop) {
116
+ if (typeof prop === 'symbol') {
117
+ return undefined;
118
+ }
119
+ const tables = schema?.tables || schema;
120
+ const tableDef = tables?.[prop];
121
+ const sqlTableName = tableDef?.$name || prop;
122
+ const columnResolver = getColumnResolver?.(sqlTableName);
123
+ return new ScalarQueryBuilderImpl(sqlTableName, tableDef, columnResolver);
124
+ }
125
+ });
126
+ }
@@ -0,0 +1,70 @@
1
+ import format from "../addon/pg-format/index.js";
2
+ import { createScalarTableAccessor } from "./scalar-query-builder.js";
3
+ export class ScalarSelectBuilder {
4
+ executeQuery;
5
+ scalarResults;
6
+ constructor(scalars, schema, executeQuery, getColumnResolver) {
7
+ this.executeQuery = executeQuery;
8
+ const tableAccessor = createScalarTableAccessor(schema, getColumnResolver);
9
+ this.scalarResults = {};
10
+ for (const [key, callback] of Object.entries(scalars)) {
11
+ this.scalarResults[key] = callback(tableAccessor);
12
+ }
13
+ }
14
+ toString() {
15
+ const columns = Object.entries(this.scalarResults)
16
+ .map(([alias, scalar]) => `${scalar.toSQL()} AS ${format.ident(alias)}`)
17
+ .join(', ');
18
+ return `SELECT ${columns}`;
19
+ }
20
+ async get(withMetadata) {
21
+ const sql = this.toString();
22
+ const { data, metadata } = await this.executeQuery(sql);
23
+ const row = Array.isArray(data) ? data[0] : data;
24
+ const result = this.transformResult(row);
25
+ if (withMetadata) {
26
+ return { data: result, metadata };
27
+ }
28
+ return result;
29
+ }
30
+ transformResult(row) {
31
+ if (!row) {
32
+ const result = {};
33
+ for (const key of Object.keys(this.scalarResults)) {
34
+ result[key] = null;
35
+ }
36
+ return result;
37
+ }
38
+ const result = {};
39
+ for (const [key, value] of Object.entries(row)) {
40
+ if (typeof value === 'string' && /^\d+$/.test(value)) {
41
+ result[key] = parseInt(value, 10);
42
+ }
43
+ else if (typeof value === 'string' && /^\d+\.\d+$/.test(value)) {
44
+ result[key] = parseFloat(value);
45
+ }
46
+ else {
47
+ result[key] = value;
48
+ }
49
+ }
50
+ return result;
51
+ }
52
+ }
53
+ export class ConnectedScalarSelectBuilder extends ScalarSelectBuilder {
54
+ constructor(scalars, schema, internal) {
55
+ const executeQuery = async (sql) => {
56
+ return internal.executeSelectOne(sql);
57
+ };
58
+ const getColumnResolver = (tableName) => {
59
+ if (internal.hasColumnMapping()) {
60
+ return (column) => {
61
+ const transformed = internal.transformToDbColumns(tableName, { [column]: null });
62
+ const keys = Object.keys(transformed);
63
+ return keys.length > 0 ? keys[0] : column;
64
+ };
65
+ }
66
+ return undefined;
67
+ };
68
+ super(scalars, schema, executeQuery, getColumnResolver);
69
+ }
70
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -8,6 +8,26 @@ export class ForeignKeyResolutionError extends Error {
8
8
  this.name = 'ForeignKeyResolutionError';
9
9
  }
10
10
  }
11
+ function isArrayFormat(tableRelations) {
12
+ return Array.isArray(tableRelations);
13
+ }
14
+ function* iterateForeignKeys(tableRelations) {
15
+ if (!tableRelations)
16
+ return;
17
+ if (isArrayFormat(tableRelations)) {
18
+ for (const relationDef of tableRelations) {
19
+ if (relationDef.$type === 'foreignKey' && relationDef.$columns?.length > 0) {
20
+ const columnKey = relationDef.$columns[0].column;
21
+ yield [columnKey, relationDef];
22
+ }
23
+ }
24
+ }
25
+ else {
26
+ for (const [columnKey, relationDef] of Object.entries(tableRelations)) {
27
+ yield [columnKey, relationDef];
28
+ }
29
+ }
30
+ }
11
31
  export function resolveForeignKey(relations, schema, fromTableKey, toTableKey) {
12
32
  if (!relations) {
13
33
  return null;
package/dist/index.d.ts CHANGED
@@ -5424,8 +5424,12 @@ export interface ForeignKeyRelationDef {
5424
5424
  }
5425
5425
  /** Relation definition type */
5426
5426
  export type RelationDef = ForeignKeyRelationDef;
5427
+ /** Table's relations - NEW: array of FK definitions */
5428
+ export type TableRelationsArray = ForeignKeyRelationDef[];
5427
5429
  /** Table's relations map (legacy format) */
5428
5430
  export type TableRelations = Record<string, RelationDef>;
5431
+ /** All relations for a schema - NEW: array per table */
5432
+ export type SchemaRelationsArray = Record<string, TableRelationsArray>;
5429
5433
  /** All relations for a schema (legacy) */
5430
5434
  export type SchemaRelations = Record<string, TableRelations>;
5431
5435
  /**
@@ -5735,7 +5739,7 @@ export interface RelqConfig {
5735
5739
  * )
5736
5740
  * ```
5737
5741
  */
5738
- relations?: SchemaRelations;
5742
+ relations?: SchemaRelations | SchemaRelationsArray;
5739
5743
  }
5740
5744
  /**
5741
5745
  * Unsubscribe function returned by `db.subscribe()`
@@ -7127,7 +7131,7 @@ export interface RelqInternal {
7127
7131
  /** Get the schema object for type-safe joins */
7128
7132
  getSchema(): Record<string, any> | undefined;
7129
7133
  /** Get the relations object for FK auto-resolution */
7130
- getRelations(): SchemaRelations | undefined;
7134
+ getRelations(): SchemaRelations | SchemaRelationsArray | undefined;
7131
7135
  /** Get table definition by schema key */
7132
7136
  getTableDef(tableKey: string): TableDefinition<any> | undefined;
7133
7137
  }
@@ -7780,6 +7784,156 @@ declare class ConnectedRawQueryBuilder {
7780
7784
  count(): Promise<RelqCount>;
7781
7785
  }
7782
7786
  declare const INTERNAL: unique symbol;
7787
+ /**
7788
+ * Marker interface for scalar subquery results.
7789
+ * Contains type information and SQL generation capability.
7790
+ */
7791
+ export interface ScalarResult<T> {
7792
+ /** Marker to identify scalar results */
7793
+ readonly $scalar: true;
7794
+ /** Type marker for TypeScript inference (not used at runtime) */
7795
+ readonly $type: T;
7796
+ /** Generate SQL for this scalar subquery */
7797
+ toSQL(): string;
7798
+ /** Get bound parameters for this subquery */
7799
+ getParams(): unknown[];
7800
+ }
7801
+ /**
7802
+ * ScalarQueryBuilder interface for building scalar subqueries.
7803
+ * Provides filtering and terminal aggregate methods.
7804
+ */
7805
+ export interface ScalarQueryBuilder<TTable> {
7806
+ /**
7807
+ * Add WHERE conditions to the scalar subquery
7808
+ */
7809
+ where(callback: (q: TypedWhereCondition<TTable>) => any): this;
7810
+ /**
7811
+ * Count rows matching the conditions
7812
+ * @returns ScalarResult<number>
7813
+ */
7814
+ count(): ScalarResult<number>;
7815
+ /**
7816
+ * Sum a numeric column
7817
+ * @param column - Column to sum (must be numeric)
7818
+ * @returns ScalarResult<number>
7819
+ */
7820
+ sum<K extends ColumnName<TTable>>(column: K): ScalarResult<number>;
7821
+ /**
7822
+ * Average of a numeric column
7823
+ * @param column - Column to average (must be numeric)
7824
+ * @returns ScalarResult<number>
7825
+ */
7826
+ avg<K extends ColumnName<TTable>>(column: K): ScalarResult<number>;
7827
+ /**
7828
+ * Minimum value of a column
7829
+ * @param column - Column to get minimum
7830
+ * @returns ScalarResult of the column's type
7831
+ */
7832
+ min<K extends ColumnName<TTable>>(column: K): ScalarResult<TTable extends {
7833
+ $inferSelect: infer S;
7834
+ } ? K extends keyof S ? S[K] : any : any>;
7835
+ /**
7836
+ * Maximum value of a column
7837
+ * @param column - Column to get maximum
7838
+ * @returns ScalarResult of the column's type
7839
+ */
7840
+ max<K extends ColumnName<TTable>>(column: K): ScalarResult<TTable extends {
7841
+ $inferSelect: infer S;
7842
+ } ? K extends keyof S ? S[K] : any : any>;
7843
+ /**
7844
+ * Pick a single value from the first row
7845
+ * @param column - Column to pick
7846
+ * @returns ScalarResult of the column's type (nullable)
7847
+ */
7848
+ pick<K extends ColumnName<TTable>>(column: K): ScalarResult<(TTable extends {
7849
+ $inferSelect: infer S;
7850
+ } ? K extends keyof S ? S[K] : any : any) | null>;
7851
+ /**
7852
+ * Check if any rows exist
7853
+ * @returns ScalarResult<boolean>
7854
+ */
7855
+ exists(): ScalarResult<boolean>;
7856
+ }
7857
+ /**
7858
+ * ScalarTableAccessor provides access to tables for scalar subqueries.
7859
+ * Each property is a ScalarQueryBuilder for that table.
7860
+ *
7861
+ * @example
7862
+ * ```typescript
7863
+ * // In scalar callback:
7864
+ * q => q.orders.where(w => w.equal('status', 'active')).count()
7865
+ * // ^^^^^^
7866
+ * // This is ScalarTableAccessor providing .orders
7867
+ * ```
7868
+ */
7869
+ export type ScalarTableAccessor<TSchema> = {
7870
+ readonly [K in keyof TSchema & string]: ScalarQueryBuilder<TSchema[K]>;
7871
+ };
7872
+ /**
7873
+ * Callback function type for scalar definitions.
7874
+ * Takes a table accessor and returns a ScalarResult.
7875
+ */
7876
+ export type ScalarCallback<TSchema, TResult> = (tables: ScalarTableAccessor<TSchema>) => ScalarResult<TResult>;
7877
+ /**
7878
+ * Infer result type from an object of scalar callbacks.
7879
+ *
7880
+ * @example
7881
+ * ```typescript
7882
+ * type Input = {
7883
+ * count: (q: ScalarTableAccessor<Schema>) => ScalarResult<number>;
7884
+ * name: (q: ScalarTableAccessor<Schema>) => ScalarResult<string | null>;
7885
+ * };
7886
+ * type Result = InferScalars<Input>;
7887
+ * // { count: number; name: string | null }
7888
+ * ```
7889
+ */
7890
+ export type InferScalars<T extends Record<string, ScalarCallback<any, any>>> = Prettify<{
7891
+ [K in keyof T]: T[K] extends ScalarCallback<any, infer R> ? R : never;
7892
+ }>;
7893
+ /**
7894
+ * Type for the scalar definitions object passed to db.scalar()
7895
+ */
7896
+ export type ScalarDefinitions<TSchema> = Record<string, ScalarCallback<TSchema, any>>;
7897
+ /**
7898
+ * Executor function type for running queries
7899
+ */
7900
+ export type QueryExecutor = (sql: string) => Promise<{
7901
+ data: any;
7902
+ metadata: RelqMetadata;
7903
+ }>;
7904
+ /**
7905
+ * Column resolver getter type
7906
+ */
7907
+ export type ColumnResolverGetter = (tableName: string) => ((column: string) => string) | undefined;
7908
+ declare class ScalarSelectBuilder<TSchema, TScalars extends Record<string, ScalarCallback<TSchema, any>>> {
7909
+ private readonly executeQuery;
7910
+ private readonly scalarResults;
7911
+ constructor(scalars: TScalars, schema: TSchema, executeQuery: QueryExecutor, getColumnResolver?: ColumnResolverGetter);
7912
+ /**
7913
+ * Generate the SQL for this scalar select
7914
+ */
7915
+ toString(): string;
7916
+ /**
7917
+ * Execute the query and return the result
7918
+ * @param withMetadata - If true, returns { data, metadata } instead of just data
7919
+ */
7920
+ get(withMetadata?: false): Promise<InferScalars<TScalars>>;
7921
+ get(withMetadata: true): Promise<RelqResult<InferScalars<TScalars>>>;
7922
+ /**
7923
+ * Transform result values to correct types
7924
+ */
7925
+ private transformResult;
7926
+ }
7927
+ declare class ConnectedScalarSelectBuilder<TSchema, TScalars extends Record<string, ScalarCallback<TSchema, any>>> extends ScalarSelectBuilder<TSchema, TScalars> {
7928
+ constructor(scalars: TScalars, schema: TSchema, internal: {
7929
+ executeSelectOne: (sql: string) => Promise<{
7930
+ data: any;
7931
+ metadata: RelqMetadata;
7932
+ }>;
7933
+ hasColumnMapping: () => boolean;
7934
+ transformToDbColumns: (tableName: string, data: Record<string, any>) => Record<string, any>;
7935
+ });
7936
+ }
7783
7937
  /**
7784
7938
  * Main Relq client class with connection management
7785
7939
  * Supports both pooled and single connection modes
@@ -8174,6 +8328,26 @@ export declare class Relq<TSchema = any> {
8174
8328
  verbose?: boolean;
8175
8329
  format?: "text" | "json" | "xml" | "yaml";
8176
8330
  }): Promise<string | object[]>;
8331
+ /**
8332
+ * Execute multiple scalar subqueries in a single SELECT statement
8333
+ *
8334
+ * Use this for aggregating values from multiple tables without a base table.
8335
+ * Each callback receives a table accessor and must return a scalar result
8336
+ * (count, sum, avg, min, max, pick, or exists).
8337
+ *
8338
+ * @example
8339
+ * ```typescript
8340
+ * const stats = await db.scalar({
8341
+ * licenseCount: q => q.licenses.where(w => w.equal('productId', id)).count(),
8342
+ * totalSpent: q => q.orders.where(w => w.equal('customerId', id)).sum('amount'),
8343
+ * lastOrder: q => q.orders.where(w => w.equal('customerId', id)).max('createdAt'),
8344
+ * hasSubscription: q => q.subscriptions.where(w => w.equal('userId', id)).exists(),
8345
+ * }).get();
8346
+ *
8347
+ * // Type: { licenseCount: number, totalSpent: number, lastOrder: Date, hasSubscription: boolean }
8348
+ * ```
8349
+ */
8350
+ scalar<T extends Record<string, ScalarCallback<TSchema, any>>>(scalars: T): ConnectedScalarSelectBuilder<TSchema, T>;
8177
8351
  }
8178
8352
  /**
8179
8353
  * Type inference for aggregate functions, COUNT queries, and GROUP BY operations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.42",
3
+ "version": "1.0.44",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",