relq 1.0.81 → 1.0.83

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.
@@ -36,10 +36,13 @@ class ConnectedInsertBuilder {
36
36
  });
37
37
  this.builder.setColumnTypeResolver((column) => {
38
38
  const columnDef = tableColumns[column];
39
- if (!columnDef || !columnDef.$type) {
39
+ if (!columnDef) {
40
40
  return undefined;
41
41
  }
42
42
  const type = columnDef.$type;
43
+ if (typeof type !== 'string') {
44
+ return undefined;
45
+ }
43
46
  const isArray = columnDef.$array === true;
44
47
  const baseType = type.replace(/\[\]$/, '').toLowerCase();
45
48
  return { type: baseType, isArray };
@@ -41,6 +41,52 @@ class ConnectedSelectBuilder {
41
41
  return column;
42
42
  });
43
43
  }
44
+ transformJoinResults(rows) {
45
+ const joins = this.builder.getStructuredJoins();
46
+ if (joins.length === 0) {
47
+ return rows;
48
+ }
49
+ const internal = this.relq[methods_1.INTERNAL];
50
+ return rows.map(row => {
51
+ if (!row || typeof row !== 'object')
52
+ return row;
53
+ const transformed = { ...row };
54
+ for (const join of joins) {
55
+ const alias = join.alias;
56
+ const nestedData = transformed[alias];
57
+ if (nestedData === null || nestedData === undefined) {
58
+ continue;
59
+ }
60
+ const joinedTableDef = internal.getTableDef(join.schemaKey || alias) || internal.getTableDef(join.table);
61
+ if (!joinedTableDef) {
62
+ continue;
63
+ }
64
+ const tableColumns = joinedTableDef.$columns || joinedTableDef;
65
+ const dbToProp = new Map();
66
+ for (const [propName, colDef] of Object.entries(tableColumns)) {
67
+ const dbColName = colDef?.$columnName ?? propName;
68
+ dbToProp.set(dbColName, propName);
69
+ }
70
+ if (Array.isArray(nestedData)) {
71
+ transformed[alias] = nestedData.map((item) => this.transformNestedObject(item, dbToProp));
72
+ }
73
+ else {
74
+ transformed[alias] = this.transformNestedObject(nestedData, dbToProp);
75
+ }
76
+ }
77
+ return transformed;
78
+ });
79
+ }
80
+ transformNestedObject(obj, dbToProp) {
81
+ if (!obj || typeof obj !== 'object')
82
+ return obj;
83
+ const result = {};
84
+ for (const [key, value] of Object.entries(obj)) {
85
+ const propName = dbToProp.get(key) ?? key;
86
+ result[propName] = value;
87
+ }
88
+ return result;
89
+ }
44
90
  where(callback) {
45
91
  this.builder.where(callback);
46
92
  return this;
@@ -158,19 +204,21 @@ class ConnectedSelectBuilder {
158
204
  async all(withMetadata, _asRequired) {
159
205
  const sql = this.builder.toString();
160
206
  const result = await this.relq[methods_1.INTERNAL].executeSelect(sql, this.tableName);
207
+ const transformedData = this.transformJoinResults(result.data);
161
208
  if (withMetadata) {
162
- return result;
209
+ return { data: transformedData, metadata: result.metadata };
163
210
  }
164
- return result.data;
211
+ return transformedData;
165
212
  }
166
213
  async get(withMetadata, _asRequired) {
167
214
  this.builder.limit(1);
168
215
  const sql = this.builder.toString();
169
216
  const result = await this.relq[methods_1.INTERNAL].executeSelectOne(sql, this.tableName);
217
+ const transformedData = result.data ? this.transformJoinResults([result.data])[0] : null;
170
218
  if (withMetadata) {
171
- return result;
219
+ return { data: transformedData, metadata: result.metadata };
172
220
  }
173
- return result.data;
221
+ return transformedData;
174
222
  }
175
223
  async value(column) {
176
224
  this.builder.limit(1);
@@ -35,10 +35,13 @@ class ConnectedUpdateBuilder {
35
35
  });
36
36
  this.builder.setColumnTypeResolver((column) => {
37
37
  const columnDef = tableColumns[column];
38
- if (!columnDef || !columnDef.$type) {
38
+ if (!columnDef) {
39
39
  return undefined;
40
40
  }
41
41
  const type = columnDef.$type;
42
+ if (typeof type !== 'string') {
43
+ return undefined;
44
+ }
42
45
  const isArray = columnDef.$array === true;
43
46
  const baseType = type.replace(/\[\]$/, '').toLowerCase();
44
47
  return { type: baseType, isArray };
@@ -54,6 +54,7 @@ function executeTypeSafeJoin(ctx, joinType, tableOrAlias, callback) {
54
54
  type: joinType,
55
55
  table: rightTableName,
56
56
  alias: rightAlias,
57
+ schemaKey: rightTableKey,
57
58
  onClause: conditionInternals.toSQL() || undefined,
58
59
  usingColumns: conditionInternals.getUsingColumns() || undefined,
59
60
  selectColumns
@@ -89,6 +90,7 @@ function executeTypeSafeJoinMany(ctx, joinType, tableOrAlias, callback) {
89
90
  type: lateralJoinType,
90
91
  table: rightTableName,
91
92
  alias: rightAlias,
93
+ schemaKey: rightTableKey,
92
94
  lateralSubquery: lateralSQL
93
95
  };
94
96
  ctx.builder.addStructuredJoin(joinClause);
@@ -184,34 +184,29 @@ class InsertBuilder {
184
184
  const placeholderTypes = [];
185
185
  for (const colName of originalColumns) {
186
186
  const value = row[colName];
187
- if (Array.isArray(value)) {
187
+ if (Array.isArray(value) || (value !== null && typeof value === 'object' && !(value instanceof Date))) {
188
188
  const typeInfo = this.columnTypeResolver?.(colName);
189
189
  if (typeInfo) {
190
- if (typeInfo.isArray) {
191
- processedValues.push(this.formatPostgresArray(value, typeInfo.type));
192
- }
193
- else if (typeInfo.type === 'jsonb' || typeInfo.type === 'json') {
194
- processedValues.push(this.formatJsonbValue(value));
190
+ if (Array.isArray(value)) {
191
+ if (typeInfo.isArray) {
192
+ processedValues.push(this.formatPostgresArray(value, typeInfo.type));
193
+ }
194
+ else if (typeInfo.type === 'jsonb' || typeInfo.type === 'json') {
195
+ processedValues.push(this.formatJsonbValue(value));
196
+ }
197
+ else {
198
+ processedValues.push(this.formatJsonbValue(value));
199
+ }
195
200
  }
196
201
  else {
197
- processedValues.push(this.formatPostgresArray(value));
202
+ processedValues.push(this.formatJsonbValue(value));
198
203
  }
199
204
  }
200
205
  else {
201
- const hasObjects = value.length > 0 && typeof value[0] === 'object' && value[0] !== null;
202
- if (hasObjects) {
203
- processedValues.push(this.formatJsonbValue(value));
204
- }
205
- else {
206
- processedValues.push(this.formatPostgresArray(value));
207
- }
206
+ processedValues.push(this.formatJsonbValue(value));
208
207
  }
209
208
  placeholderTypes.push('%s');
210
209
  }
211
- else if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
212
- processedValues.push(this.formatJsonbValue(value));
213
- placeholderTypes.push('%s');
214
- }
215
210
  else {
216
211
  processedValues.push(value);
217
212
  placeholderTypes.push('%L');
@@ -172,12 +172,11 @@ class UpdateBuilder {
172
172
  else if (typeInfo.type === 'jsonb' || typeInfo.type === 'json') {
173
173
  return this.formatJsonbValue(value);
174
174
  }
175
+ else {
176
+ return this.formatJsonbValue(value);
177
+ }
175
178
  }
176
- const hasObjects = value.length > 0 && typeof value[0] === 'object' && value[0] !== null;
177
- if (hasObjects) {
178
- return this.formatJsonbValue(value);
179
- }
180
- return this.formatPostgresArray(value);
179
+ return this.formatJsonbValue(value);
181
180
  }
182
181
  toString() {
183
182
  const processedPairs = Object.entries(this.updateData).map(([col, val]) => {
@@ -33,10 +33,13 @@ export class ConnectedInsertBuilder {
33
33
  });
34
34
  this.builder.setColumnTypeResolver((column) => {
35
35
  const columnDef = tableColumns[column];
36
- if (!columnDef || !columnDef.$type) {
36
+ if (!columnDef) {
37
37
  return undefined;
38
38
  }
39
39
  const type = columnDef.$type;
40
+ if (typeof type !== 'string') {
41
+ return undefined;
42
+ }
40
43
  const isArray = columnDef.$array === true;
41
44
  const baseType = type.replace(/\[\]$/, '').toLowerCase();
42
45
  return { type: baseType, isArray };
@@ -38,6 +38,52 @@ export class ConnectedSelectBuilder {
38
38
  return column;
39
39
  });
40
40
  }
41
+ transformJoinResults(rows) {
42
+ const joins = this.builder.getStructuredJoins();
43
+ if (joins.length === 0) {
44
+ return rows;
45
+ }
46
+ const internal = this.relq[INTERNAL];
47
+ return rows.map(row => {
48
+ if (!row || typeof row !== 'object')
49
+ return row;
50
+ const transformed = { ...row };
51
+ for (const join of joins) {
52
+ const alias = join.alias;
53
+ const nestedData = transformed[alias];
54
+ if (nestedData === null || nestedData === undefined) {
55
+ continue;
56
+ }
57
+ const joinedTableDef = internal.getTableDef(join.schemaKey || alias) || internal.getTableDef(join.table);
58
+ if (!joinedTableDef) {
59
+ continue;
60
+ }
61
+ const tableColumns = joinedTableDef.$columns || joinedTableDef;
62
+ const dbToProp = new Map();
63
+ for (const [propName, colDef] of Object.entries(tableColumns)) {
64
+ const dbColName = colDef?.$columnName ?? propName;
65
+ dbToProp.set(dbColName, propName);
66
+ }
67
+ if (Array.isArray(nestedData)) {
68
+ transformed[alias] = nestedData.map((item) => this.transformNestedObject(item, dbToProp));
69
+ }
70
+ else {
71
+ transformed[alias] = this.transformNestedObject(nestedData, dbToProp);
72
+ }
73
+ }
74
+ return transformed;
75
+ });
76
+ }
77
+ transformNestedObject(obj, dbToProp) {
78
+ if (!obj || typeof obj !== 'object')
79
+ return obj;
80
+ const result = {};
81
+ for (const [key, value] of Object.entries(obj)) {
82
+ const propName = dbToProp.get(key) ?? key;
83
+ result[propName] = value;
84
+ }
85
+ return result;
86
+ }
41
87
  where(callback) {
42
88
  this.builder.where(callback);
43
89
  return this;
@@ -155,19 +201,21 @@ export class ConnectedSelectBuilder {
155
201
  async all(withMetadata, _asRequired) {
156
202
  const sql = this.builder.toString();
157
203
  const result = await this.relq[INTERNAL].executeSelect(sql, this.tableName);
204
+ const transformedData = this.transformJoinResults(result.data);
158
205
  if (withMetadata) {
159
- return result;
206
+ return { data: transformedData, metadata: result.metadata };
160
207
  }
161
- return result.data;
208
+ return transformedData;
162
209
  }
163
210
  async get(withMetadata, _asRequired) {
164
211
  this.builder.limit(1);
165
212
  const sql = this.builder.toString();
166
213
  const result = await this.relq[INTERNAL].executeSelectOne(sql, this.tableName);
214
+ const transformedData = result.data ? this.transformJoinResults([result.data])[0] : null;
167
215
  if (withMetadata) {
168
- return result;
216
+ return { data: transformedData, metadata: result.metadata };
169
217
  }
170
- return result.data;
218
+ return transformedData;
171
219
  }
172
220
  async value(column) {
173
221
  this.builder.limit(1);
@@ -32,10 +32,13 @@ export class ConnectedUpdateBuilder {
32
32
  });
33
33
  this.builder.setColumnTypeResolver((column) => {
34
34
  const columnDef = tableColumns[column];
35
- if (!columnDef || !columnDef.$type) {
35
+ if (!columnDef) {
36
36
  return undefined;
37
37
  }
38
38
  const type = columnDef.$type;
39
+ if (typeof type !== 'string') {
40
+ return undefined;
41
+ }
39
42
  const isArray = columnDef.$array === true;
40
43
  const baseType = type.replace(/\[\]$/, '').toLowerCase();
41
44
  return { type: baseType, isArray };
@@ -50,6 +50,7 @@ export function executeTypeSafeJoin(ctx, joinType, tableOrAlias, callback) {
50
50
  type: joinType,
51
51
  table: rightTableName,
52
52
  alias: rightAlias,
53
+ schemaKey: rightTableKey,
53
54
  onClause: conditionInternals.toSQL() || undefined,
54
55
  usingColumns: conditionInternals.getUsingColumns() || undefined,
55
56
  selectColumns
@@ -85,6 +86,7 @@ export function executeTypeSafeJoinMany(ctx, joinType, tableOrAlias, callback) {
85
86
  type: lateralJoinType,
86
87
  table: rightTableName,
87
88
  alias: rightAlias,
89
+ schemaKey: rightTableKey,
88
90
  lateralSubquery: lateralSQL
89
91
  };
90
92
  ctx.builder.addStructuredJoin(joinClause);
@@ -178,34 +178,29 @@ export class InsertBuilder {
178
178
  const placeholderTypes = [];
179
179
  for (const colName of originalColumns) {
180
180
  const value = row[colName];
181
- if (Array.isArray(value)) {
181
+ if (Array.isArray(value) || (value !== null && typeof value === 'object' && !(value instanceof Date))) {
182
182
  const typeInfo = this.columnTypeResolver?.(colName);
183
183
  if (typeInfo) {
184
- if (typeInfo.isArray) {
185
- processedValues.push(this.formatPostgresArray(value, typeInfo.type));
186
- }
187
- else if (typeInfo.type === 'jsonb' || typeInfo.type === 'json') {
188
- processedValues.push(this.formatJsonbValue(value));
184
+ if (Array.isArray(value)) {
185
+ if (typeInfo.isArray) {
186
+ processedValues.push(this.formatPostgresArray(value, typeInfo.type));
187
+ }
188
+ else if (typeInfo.type === 'jsonb' || typeInfo.type === 'json') {
189
+ processedValues.push(this.formatJsonbValue(value));
190
+ }
191
+ else {
192
+ processedValues.push(this.formatJsonbValue(value));
193
+ }
189
194
  }
190
195
  else {
191
- processedValues.push(this.formatPostgresArray(value));
196
+ processedValues.push(this.formatJsonbValue(value));
192
197
  }
193
198
  }
194
199
  else {
195
- const hasObjects = value.length > 0 && typeof value[0] === 'object' && value[0] !== null;
196
- if (hasObjects) {
197
- processedValues.push(this.formatJsonbValue(value));
198
- }
199
- else {
200
- processedValues.push(this.formatPostgresArray(value));
201
- }
200
+ processedValues.push(this.formatJsonbValue(value));
202
201
  }
203
202
  placeholderTypes.push('%s');
204
203
  }
205
- else if (value !== null && typeof value === 'object' && !(value instanceof Date)) {
206
- processedValues.push(this.formatJsonbValue(value));
207
- placeholderTypes.push('%s');
208
- }
209
204
  else {
210
205
  processedValues.push(value);
211
206
  placeholderTypes.push('%L');
@@ -166,12 +166,11 @@ export class UpdateBuilder {
166
166
  else if (typeInfo.type === 'jsonb' || typeInfo.type === 'json') {
167
167
  return this.formatJsonbValue(value);
168
168
  }
169
+ else {
170
+ return this.formatJsonbValue(value);
171
+ }
169
172
  }
170
- const hasObjects = value.length > 0 && typeof value[0] === 'object' && value[0] !== null;
171
- if (hasObjects) {
172
- return this.formatJsonbValue(value);
173
- }
174
- return this.formatPostgresArray(value);
173
+ return this.formatJsonbValue(value);
175
174
  }
176
175
  toString() {
177
176
  const processedPairs = Object.entries(this.updateData).map(([col, val]) => {
package/dist/index.d.ts CHANGED
@@ -2037,6 +2037,8 @@ export interface JoinClause {
2037
2037
  table: string;
2038
2038
  /** Alias for the table (may be same as table) */
2039
2039
  alias: string;
2040
+ /** Schema key (for looking up table definition) - may differ from table/alias */
2041
+ schemaKey?: string;
2040
2042
  /** ON clause SQL (without "ON" keyword) */
2041
2043
  onClause?: string;
2042
2044
  /** USING columns (alternative to ON) */
@@ -2501,8 +2503,8 @@ export declare class UpdateBuilder {
2501
2503
  */
2502
2504
  private formatJsonbValue;
2503
2505
  /**
2504
- * Format an array value using schema type info if available.
2505
- * Falls back to heuristics if no schema info.
2506
+ * Format an array value using schema type info.
2507
+ * Schema is required for proper handling - defaults to JSONB if no schema.
2506
2508
  */
2507
2509
  private formatArrayValueWithType;
2508
2510
  toString(): string;
@@ -8731,6 +8733,17 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8731
8733
  * @internal
8732
8734
  */
8733
8735
  private setupColumnResolver;
8736
+ /**
8737
+ * Transform nested join results from DB column names to schema property names.
8738
+ * Uses schema metadata to resolve column names for each joined table.
8739
+ * @internal
8740
+ */
8741
+ private transformJoinResults;
8742
+ /**
8743
+ * Transform a single nested object using the column mapping.
8744
+ * @internal
8745
+ */
8746
+ private transformNestedObject;
8734
8747
  where(callback: (q: TypedConditionBuilder<TTable>) => TypedConditionBuilder<TTable>): this;
8735
8748
  orderBy(column: ColumnName<TTable>, direction?: "ASC" | "DESC"): this;
8736
8749
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.81",
3
+ "version": "1.0.83",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",