relq 1.0.80 → 1.0.82

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.
@@ -34,6 +34,19 @@ class ConnectedInsertBuilder {
34
34
  }
35
35
  return column;
36
36
  });
37
+ this.builder.setColumnTypeResolver((column) => {
38
+ const columnDef = tableColumns[column];
39
+ if (!columnDef) {
40
+ return undefined;
41
+ }
42
+ const type = columnDef.$type;
43
+ if (typeof type !== 'string') {
44
+ return undefined;
45
+ }
46
+ const isArray = columnDef.$array === true;
47
+ const baseType = type.replace(/\[\]$/, '').toLowerCase();
48
+ return { type: baseType, isArray };
49
+ });
37
50
  }
38
51
  addRow(row) {
39
52
  this.builder.addRow(row);
@@ -33,6 +33,19 @@ class ConnectedUpdateBuilder {
33
33
  }
34
34
  return column;
35
35
  });
36
+ this.builder.setColumnTypeResolver((column) => {
37
+ const columnDef = tableColumns[column];
38
+ if (!columnDef) {
39
+ return undefined;
40
+ }
41
+ const type = columnDef.$type;
42
+ if (typeof type !== 'string') {
43
+ return undefined;
44
+ }
45
+ const isArray = columnDef.$array === true;
46
+ const baseType = type.replace(/\[\]$/, '').toLowerCase();
47
+ return { type: baseType, isArray };
48
+ });
36
49
  }
37
50
  where(callback) {
38
51
  this.builder.where(callback);
@@ -20,6 +20,7 @@ class InsertBuilder {
20
20
  _convertCase = '2snake';
21
21
  originalColumns;
22
22
  columnResolver;
23
+ columnTypeResolver;
23
24
  constructor(tableName, data) {
24
25
  this.tableName = tableName;
25
26
  const normalized = this.normalizeData(data);
@@ -37,6 +38,10 @@ class InsertBuilder {
37
38
  this.columnResolver = resolver;
38
39
  return this;
39
40
  }
41
+ setColumnTypeResolver(resolver) {
42
+ this.columnTypeResolver = resolver;
43
+ return this;
44
+ }
40
45
  resolveColumnName(name) {
41
46
  if (this.columnResolver) {
42
47
  return this.columnResolver(name);
@@ -151,19 +156,55 @@ class InsertBuilder {
151
156
  return (0, case_converter_1.convertCase)(name, this._convertCase);
152
157
  return name;
153
158
  }
154
- formatArrayValue(value) {
159
+ formatPostgresArray(value, baseType = 'jsonb') {
155
160
  if (value.length === 0)
156
- return 'ARRAY[]::jsonb[]';
157
- const jsonValues = value.map(v => (0, pg_format_1.default)('%L', JSON.stringify(v))).join(',');
158
- return `ARRAY[${jsonValues}]::jsonb[]`;
161
+ return `ARRAY[]::${baseType}[]`;
162
+ if (baseType === 'jsonb') {
163
+ const jsonValues = value.map(v => (0, pg_format_1.default)('%L', JSON.stringify(v))).join(',');
164
+ return `ARRAY[${jsonValues}]::jsonb[]`;
165
+ }
166
+ else if (baseType === 'text' || baseType === 'varchar') {
167
+ const textValues = value.map(v => (0, pg_format_1.default)('%L', String(v))).join(',');
168
+ return `ARRAY[${textValues}]::text[]`;
169
+ }
170
+ else if (baseType === 'integer' || baseType === 'int4' || baseType === 'bigint' || baseType === 'int8') {
171
+ return `ARRAY[${value.join(',')}]::${baseType}[]`;
172
+ }
173
+ else {
174
+ const jsonValues = value.map(v => (0, pg_format_1.default)('%L', JSON.stringify(v))).join(',');
175
+ return `ARRAY[${jsonValues}]::${baseType}[]`;
176
+ }
177
+ }
178
+ formatJsonbValue(value) {
179
+ const json = JSON.stringify(value).replace(/'/g, "''");
180
+ return `'${json}'::jsonb`;
159
181
  }
160
182
  processRowValues(row, originalColumns) {
161
183
  const processedValues = [];
162
184
  const placeholderTypes = [];
163
185
  for (const colName of originalColumns) {
164
186
  const value = row[colName];
165
- if (Array.isArray(value)) {
166
- processedValues.push(this.formatArrayValue(value));
187
+ if (Array.isArray(value) || (value !== null && typeof value === 'object' && !(value instanceof Date))) {
188
+ const typeInfo = this.columnTypeResolver?.(colName);
189
+ if (typeInfo) {
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
+ }
200
+ }
201
+ else {
202
+ processedValues.push(this.formatJsonbValue(value));
203
+ }
204
+ }
205
+ else {
206
+ processedValues.push(this.formatJsonbValue(value));
207
+ }
167
208
  placeholderTypes.push('%s');
168
209
  }
169
210
  else {
@@ -19,6 +19,7 @@ class UpdateBuilder {
19
19
  _case = 'keep-case';
20
20
  _convertCase = '2snake';
21
21
  columnResolver;
22
+ columnTypeResolver;
22
23
  constructor(tableName, data) {
23
24
  this.tableName = tableName;
24
25
  this.updateData = this.normalizeData(data);
@@ -34,6 +35,10 @@ class UpdateBuilder {
34
35
  this.columnResolver = resolver;
35
36
  return this;
36
37
  }
38
+ setColumnTypeResolver(resolver) {
39
+ this.columnTypeResolver = resolver;
40
+ return this;
41
+ }
37
42
  resolveColumnName(name) {
38
43
  if (this.columnResolver) {
39
44
  return this.columnResolver(name);
@@ -127,7 +132,7 @@ class UpdateBuilder {
127
132
  return (0, case_converter_1.convertCase)(name, this._convertCase);
128
133
  return name;
129
134
  }
130
- formatArrayValue(value) {
135
+ formatPostgresArray(value) {
131
136
  if (value.length === 0)
132
137
  return 'ARRAY[]';
133
138
  const firstElement = value[0];
@@ -154,6 +159,25 @@ class UpdateBuilder {
154
159
  const stringValues = value.map(v => (0, pg_format_1.default)('%L', v)).join(',');
155
160
  return `ARRAY[${stringValues}]`;
156
161
  }
162
+ formatJsonbValue(value) {
163
+ const json = JSON.stringify(value).replace(/'/g, "''");
164
+ return `'${json}'::jsonb`;
165
+ }
166
+ formatArrayValueWithType(colName, value) {
167
+ const typeInfo = this.columnTypeResolver?.(colName);
168
+ if (typeInfo) {
169
+ if (typeInfo.isArray) {
170
+ return this.formatPostgresArray(value);
171
+ }
172
+ else if (typeInfo.type === 'jsonb' || typeInfo.type === 'json') {
173
+ return this.formatJsonbValue(value);
174
+ }
175
+ else {
176
+ return this.formatJsonbValue(value);
177
+ }
178
+ }
179
+ return this.formatJsonbValue(value);
180
+ }
157
181
  toString() {
158
182
  const processedPairs = Object.entries(this.updateData).map(([col, val]) => {
159
183
  const convertedCol = this.resolveColumnName(col);
@@ -167,7 +191,10 @@ class UpdateBuilder {
167
191
  return (0, pg_format_1.default)('%I = %s', convertedCol, finalResult);
168
192
  }
169
193
  if (Array.isArray(val)) {
170
- return (0, pg_format_1.default)('%I = %s', convertedCol, this.formatArrayValue(val));
194
+ return (0, pg_format_1.default)('%I = %s', convertedCol, this.formatArrayValueWithType(col, val));
195
+ }
196
+ if (val !== null && typeof val === 'object' && !(val instanceof Date)) {
197
+ return (0, pg_format_1.default)('%I = %s', convertedCol, this.formatJsonbValue(val));
171
198
  }
172
199
  return (0, pg_format_1.default)('%I = %L', convertedCol, val);
173
200
  });
@@ -31,6 +31,19 @@ export class ConnectedInsertBuilder {
31
31
  }
32
32
  return column;
33
33
  });
34
+ this.builder.setColumnTypeResolver((column) => {
35
+ const columnDef = tableColumns[column];
36
+ if (!columnDef) {
37
+ return undefined;
38
+ }
39
+ const type = columnDef.$type;
40
+ if (typeof type !== 'string') {
41
+ return undefined;
42
+ }
43
+ const isArray = columnDef.$array === true;
44
+ const baseType = type.replace(/\[\]$/, '').toLowerCase();
45
+ return { type: baseType, isArray };
46
+ });
34
47
  }
35
48
  addRow(row) {
36
49
  this.builder.addRow(row);
@@ -30,6 +30,19 @@ export class ConnectedUpdateBuilder {
30
30
  }
31
31
  return column;
32
32
  });
33
+ this.builder.setColumnTypeResolver((column) => {
34
+ const columnDef = tableColumns[column];
35
+ if (!columnDef) {
36
+ return undefined;
37
+ }
38
+ const type = columnDef.$type;
39
+ if (typeof type !== 'string') {
40
+ return undefined;
41
+ }
42
+ const isArray = columnDef.$array === true;
43
+ const baseType = type.replace(/\[\]$/, '').toLowerCase();
44
+ return { type: baseType, isArray };
45
+ });
33
46
  }
34
47
  where(callback) {
35
48
  this.builder.where(callback);
@@ -14,6 +14,7 @@ export class InsertBuilder {
14
14
  _convertCase = '2snake';
15
15
  originalColumns;
16
16
  columnResolver;
17
+ columnTypeResolver;
17
18
  constructor(tableName, data) {
18
19
  this.tableName = tableName;
19
20
  const normalized = this.normalizeData(data);
@@ -31,6 +32,10 @@ export class InsertBuilder {
31
32
  this.columnResolver = resolver;
32
33
  return this;
33
34
  }
35
+ setColumnTypeResolver(resolver) {
36
+ this.columnTypeResolver = resolver;
37
+ return this;
38
+ }
34
39
  resolveColumnName(name) {
35
40
  if (this.columnResolver) {
36
41
  return this.columnResolver(name);
@@ -145,19 +150,55 @@ export class InsertBuilder {
145
150
  return convertCase(name, this._convertCase);
146
151
  return name;
147
152
  }
148
- formatArrayValue(value) {
153
+ formatPostgresArray(value, baseType = 'jsonb') {
149
154
  if (value.length === 0)
150
- return 'ARRAY[]::jsonb[]';
151
- const jsonValues = value.map(v => format('%L', JSON.stringify(v))).join(',');
152
- return `ARRAY[${jsonValues}]::jsonb[]`;
155
+ return `ARRAY[]::${baseType}[]`;
156
+ if (baseType === 'jsonb') {
157
+ const jsonValues = value.map(v => format('%L', JSON.stringify(v))).join(',');
158
+ return `ARRAY[${jsonValues}]::jsonb[]`;
159
+ }
160
+ else if (baseType === 'text' || baseType === 'varchar') {
161
+ const textValues = value.map(v => format('%L', String(v))).join(',');
162
+ return `ARRAY[${textValues}]::text[]`;
163
+ }
164
+ else if (baseType === 'integer' || baseType === 'int4' || baseType === 'bigint' || baseType === 'int8') {
165
+ return `ARRAY[${value.join(',')}]::${baseType}[]`;
166
+ }
167
+ else {
168
+ const jsonValues = value.map(v => format('%L', JSON.stringify(v))).join(',');
169
+ return `ARRAY[${jsonValues}]::${baseType}[]`;
170
+ }
171
+ }
172
+ formatJsonbValue(value) {
173
+ const json = JSON.stringify(value).replace(/'/g, "''");
174
+ return `'${json}'::jsonb`;
153
175
  }
154
176
  processRowValues(row, originalColumns) {
155
177
  const processedValues = [];
156
178
  const placeholderTypes = [];
157
179
  for (const colName of originalColumns) {
158
180
  const value = row[colName];
159
- if (Array.isArray(value)) {
160
- processedValues.push(this.formatArrayValue(value));
181
+ if (Array.isArray(value) || (value !== null && typeof value === 'object' && !(value instanceof Date))) {
182
+ const typeInfo = this.columnTypeResolver?.(colName);
183
+ if (typeInfo) {
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
+ }
194
+ }
195
+ else {
196
+ processedValues.push(this.formatJsonbValue(value));
197
+ }
198
+ }
199
+ else {
200
+ processedValues.push(this.formatJsonbValue(value));
201
+ }
161
202
  placeholderTypes.push('%s');
162
203
  }
163
204
  else {
@@ -13,6 +13,7 @@ export class UpdateBuilder {
13
13
  _case = 'keep-case';
14
14
  _convertCase = '2snake';
15
15
  columnResolver;
16
+ columnTypeResolver;
16
17
  constructor(tableName, data) {
17
18
  this.tableName = tableName;
18
19
  this.updateData = this.normalizeData(data);
@@ -28,6 +29,10 @@ export class UpdateBuilder {
28
29
  this.columnResolver = resolver;
29
30
  return this;
30
31
  }
32
+ setColumnTypeResolver(resolver) {
33
+ this.columnTypeResolver = resolver;
34
+ return this;
35
+ }
31
36
  resolveColumnName(name) {
32
37
  if (this.columnResolver) {
33
38
  return this.columnResolver(name);
@@ -121,7 +126,7 @@ export class UpdateBuilder {
121
126
  return convertCase(name, this._convertCase);
122
127
  return name;
123
128
  }
124
- formatArrayValue(value) {
129
+ formatPostgresArray(value) {
125
130
  if (value.length === 0)
126
131
  return 'ARRAY[]';
127
132
  const firstElement = value[0];
@@ -148,6 +153,25 @@ export class UpdateBuilder {
148
153
  const stringValues = value.map(v => format('%L', v)).join(',');
149
154
  return `ARRAY[${stringValues}]`;
150
155
  }
156
+ formatJsonbValue(value) {
157
+ const json = JSON.stringify(value).replace(/'/g, "''");
158
+ return `'${json}'::jsonb`;
159
+ }
160
+ formatArrayValueWithType(colName, value) {
161
+ const typeInfo = this.columnTypeResolver?.(colName);
162
+ if (typeInfo) {
163
+ if (typeInfo.isArray) {
164
+ return this.formatPostgresArray(value);
165
+ }
166
+ else if (typeInfo.type === 'jsonb' || typeInfo.type === 'json') {
167
+ return this.formatJsonbValue(value);
168
+ }
169
+ else {
170
+ return this.formatJsonbValue(value);
171
+ }
172
+ }
173
+ return this.formatJsonbValue(value);
174
+ }
151
175
  toString() {
152
176
  const processedPairs = Object.entries(this.updateData).map(([col, val]) => {
153
177
  const convertedCol = this.resolveColumnName(col);
@@ -161,7 +185,10 @@ export class UpdateBuilder {
161
185
  return format('%I = %s', convertedCol, finalResult);
162
186
  }
163
187
  if (Array.isArray(val)) {
164
- return format('%I = %s', convertedCol, this.formatArrayValue(val));
188
+ return format('%I = %s', convertedCol, this.formatArrayValueWithType(col, val));
189
+ }
190
+ if (val !== null && typeof val === 'object' && !(val instanceof Date)) {
191
+ return format('%I = %s', convertedCol, this.formatJsonbValue(val));
165
192
  }
166
193
  return format('%I = %L', convertedCol, val);
167
194
  });
package/dist/index.d.ts CHANGED
@@ -2358,6 +2358,15 @@ export type UpdateCallback = (builder: UpdateOperationsBuilder) => string;
2358
2358
  * Update value can be a direct value or a callback for array/jsonb operations
2359
2359
  */
2360
2360
  export type UpdateValue = QueryValue | UpdateCallback;
2361
+ /**
2362
+ * Column type info returned by the type resolver
2363
+ */
2364
+ export interface ColumnTypeInfo {
2365
+ /** Base PostgreSQL type (e.g., 'jsonb', 'text', 'integer') */
2366
+ type: string;
2367
+ /** Whether this is a PostgreSQL array type (text[], jsonb[], etc.) */
2368
+ isArray: boolean;
2369
+ }
2361
2370
  export declare class UpdateBuilder {
2362
2371
  tableName: string;
2363
2372
  updateData: Record<string, UpdateValue>;
@@ -2366,6 +2375,7 @@ export declare class UpdateBuilder {
2366
2375
  private _case;
2367
2376
  private _convertCase;
2368
2377
  private columnResolver?;
2378
+ private columnTypeResolver?;
2369
2379
  constructor(tableName: string, data: Record<string, UpdateValue>);
2370
2380
  /**
2371
2381
  * Normalize data by converting undefined values to null.
@@ -2379,6 +2389,12 @@ export declare class UpdateBuilder {
2379
2389
  * @internal Used by ConnectedUpdateBuilder for schema-aware column name mapping.
2380
2390
  */
2381
2391
  setColumnResolver(resolver: (column: string) => string): UpdateBuilder;
2392
+ /**
2393
+ * Set a column type resolver to get type info for proper value formatting.
2394
+ * This allows distinguishing between jsonb (single value) vs jsonb[] (PostgreSQL array).
2395
+ * @internal Used by ConnectedUpdateBuilder for schema-aware value serialization.
2396
+ */
2397
+ setColumnTypeResolver(resolver: (column: string) => ColumnTypeInfo | undefined): UpdateBuilder;
2382
2398
  /**
2383
2399
  * Resolve a column name using the column resolver or fallback to convertColumnName.
2384
2400
  * @internal
@@ -2475,10 +2491,20 @@ export declare class UpdateBuilder {
2475
2491
  returningCount(callback: (builder: CountBuilder) => CountBuilder | null): UpdateBuilder;
2476
2492
  private convertColumnName;
2477
2493
  /**
2478
- * Format array value with automatic type detection
2479
- * Uses array.every() to verify homogeneous arrays
2494
+ * Format a JavaScript array as a PostgreSQL array literal.
2495
+ * Used for actual PostgreSQL array columns (text[], integer[], jsonb[], etc.)
2496
+ */
2497
+ private formatPostgresArray;
2498
+ /**
2499
+ * Format a JavaScript value as a single JSONB value.
2500
+ * Used for jsonb columns that contain arrays or objects.
2501
+ */
2502
+ private formatJsonbValue;
2503
+ /**
2504
+ * Format an array value using schema type info.
2505
+ * Schema is required for proper handling - defaults to JSONB if no schema.
2480
2506
  */
2481
- private formatArrayValue;
2507
+ private formatArrayValueWithType;
2482
2508
  toString(): string;
2483
2509
  /**
2484
2510
  * Parse COUNT columns from SELECT clause
@@ -2910,6 +2936,12 @@ declare class ConflictBuilder<TTable = any> {
2910
2936
  get whereClause(): string | undefined;
2911
2937
  get tableName(): string;
2912
2938
  }
2939
+ interface ColumnTypeInfo$1 {
2940
+ /** Base PostgreSQL type (e.g., 'jsonb', 'text', 'integer') */
2941
+ type: string;
2942
+ /** Whether this is a PostgreSQL array type (text[], jsonb[], etc.) */
2943
+ isArray: boolean;
2944
+ }
2913
2945
  export declare class InsertBuilder {
2914
2946
  tableName: string;
2915
2947
  insertData: Record<string, QueryValue>[];
@@ -2920,6 +2952,7 @@ export declare class InsertBuilder {
2920
2952
  private _convertCase;
2921
2953
  private originalColumns;
2922
2954
  private columnResolver?;
2955
+ private columnTypeResolver?;
2923
2956
  constructor(tableName: string, data: Record<string, QueryValue>);
2924
2957
  /**
2925
2958
  * Normalize data by converting undefined values to null.
@@ -2933,6 +2966,12 @@ export declare class InsertBuilder {
2933
2966
  * @internal Used by ConnectedInsertBuilder for schema-aware column name mapping.
2934
2967
  */
2935
2968
  setColumnResolver(resolver: (column: string) => string): InsertBuilder;
2969
+ /**
2970
+ * Set a column type resolver to get type info for proper value formatting.
2971
+ * This allows distinguishing between jsonb (single value) vs jsonb[] (PostgreSQL array).
2972
+ * @internal Used by ConnectedInsertBuilder for schema-aware value serialization.
2973
+ */
2974
+ setColumnTypeResolver(resolver: (column: string) => ColumnTypeInfo$1 | undefined): InsertBuilder;
2936
2975
  /**
2937
2976
  * Resolve a column name using the column resolver or fallback to convertColumnName.
2938
2977
  * @internal
@@ -2971,7 +3010,16 @@ export declare class InsertBuilder {
2971
3010
  */
2972
3011
  returningCount(callback: (builder: CountBuilder) => CountBuilder | null): InsertBuilder;
2973
3012
  private convertColumnName;
2974
- private formatArrayValue;
3013
+ /**
3014
+ * Format a JavaScript array as a PostgreSQL array literal.
3015
+ * Used for actual PostgreSQL array columns (text[], integer[], jsonb[], etc.)
3016
+ */
3017
+ private formatPostgresArray;
3018
+ /**
3019
+ * Format a JavaScript value as a single JSONB value.
3020
+ * Used for jsonb columns that contain arrays or objects.
3021
+ */
3022
+ private formatJsonbValue;
2975
3023
  private processRowValues;
2976
3024
  private buildConflictClause;
2977
3025
  toString(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.80",
3
+ "version": "1.0.82",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",