relq 1.0.83 → 1.0.85

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.
Files changed (44) hide show
  1. package/dist/cjs/cli/utils/ast-codegen.cjs +14 -3
  2. package/dist/cjs/cli/utils/cockroachdb/introspect.cjs +1 -1
  3. package/dist/cjs/cli/utils/dsql/introspect.cjs +1 -1
  4. package/dist/cjs/cli/utils/nile/introspect.cjs +1 -1
  5. package/dist/cjs/cli/utils/postgres/introspect.cjs +1 -1
  6. package/dist/cjs/cli/utils/schema-to-ast.cjs +1 -1
  7. package/dist/cjs/core/helpers/ConnectedSelectBuilder.cjs +29 -4
  8. package/dist/cjs/core/helpers/select-joins.cjs +11 -1
  9. package/dist/cjs/core/shared/column-mapping.cjs +1 -1
  10. package/dist/cjs/core/shared/transform.cjs +6 -2
  11. package/dist/cjs/schema-definition/pg-schema/column-types/column-builder.cjs +5 -5
  12. package/dist/cjs/schema-definition/pg-schema/column-types/custom-types.cjs +2 -2
  13. package/dist/cjs/schema-definition/pg-schema/column-types/domain-types.cjs +4 -4
  14. package/dist/cjs/schema-definition/pg-schema/pg-enum.cjs +3 -1
  15. package/dist/cjs/schema-definition/pg-schema/schema-builder.cjs +1 -1
  16. package/dist/cjs/schema-definition/pg-schema/table-definition/sql-generation.cjs +1 -1
  17. package/dist/cjs/schema-definition/pg-schema/validate-schema/validators.cjs +1 -1
  18. package/dist/cjs/schema-definition/sqlite-schema/table-definition/ast-generation.cjs +1 -1
  19. package/dist/cjs/schema-definition/sqlite-schema/table-definition/sql-generation.cjs +1 -1
  20. package/dist/cjs/schema-definition/sqlite-schema/table-definition/table-core.cjs +1 -1
  21. package/dist/cjs/utils/type-coercion.cjs +4 -2
  22. package/dist/esm/cli/utils/ast-codegen.js +14 -3
  23. package/dist/esm/cli/utils/cockroachdb/introspect.js +1 -1
  24. package/dist/esm/cli/utils/dsql/introspect.js +1 -1
  25. package/dist/esm/cli/utils/nile/introspect.js +1 -1
  26. package/dist/esm/cli/utils/postgres/introspect.js +1 -1
  27. package/dist/esm/cli/utils/schema-to-ast.js +1 -1
  28. package/dist/esm/core/helpers/ConnectedSelectBuilder.js +29 -4
  29. package/dist/esm/core/helpers/select-joins.js +11 -1
  30. package/dist/esm/core/shared/column-mapping.js +1 -1
  31. package/dist/esm/core/shared/transform.js +6 -2
  32. package/dist/esm/schema-definition/pg-schema/column-types/column-builder.js +5 -5
  33. package/dist/esm/schema-definition/pg-schema/column-types/custom-types.js +2 -2
  34. package/dist/esm/schema-definition/pg-schema/column-types/domain-types.js +4 -4
  35. package/dist/esm/schema-definition/pg-schema/pg-enum.js +3 -1
  36. package/dist/esm/schema-definition/pg-schema/schema-builder.js +1 -1
  37. package/dist/esm/schema-definition/pg-schema/table-definition/sql-generation.js +1 -1
  38. package/dist/esm/schema-definition/pg-schema/validate-schema/validators.js +1 -1
  39. package/dist/esm/schema-definition/sqlite-schema/table-definition/ast-generation.js +1 -1
  40. package/dist/esm/schema-definition/sqlite-schema/table-definition/sql-generation.js +1 -1
  41. package/dist/esm/schema-definition/sqlite-schema/table-definition/table-core.js +1 -1
  42. package/dist/esm/utils/type-coercion.js +4 -2
  43. package/dist/index.d.ts +6 -0
  44. package/package.json +1 -1
@@ -255,11 +255,21 @@ function generateColumnCode(col, useCamelCase, enumNames, domainNames, checkOver
255
255
  const normalizedType = col.type.toLowerCase();
256
256
  if (enumNames.has(normalizedType) || enumNames.has(col.type)) {
257
257
  const enumName = `${(0, utils_1.toCamelCase)(col.type)}Enum`;
258
- line = ` ${colName}: ${enumName}()`;
258
+ if (useCamelCase && colName !== col.name) {
259
+ line = ` ${colName}: ${enumName}('${col.name}')`;
260
+ }
261
+ else {
262
+ line = ` ${colName}: ${enumName}()`;
263
+ }
259
264
  }
260
265
  else if (domainNames.has(normalizedType) || domainNames.has(col.type)) {
261
266
  const domainName = `${(0, utils_1.toCamelCase)(col.type)}Domain`;
262
- line = ` ${colName}: ${domainName}()`;
267
+ if (useCamelCase && colName !== col.name) {
268
+ line = ` ${colName}: ${domainName}('${col.name}')`;
269
+ }
270
+ else {
271
+ line = ` ${colName}: ${domainName}()`;
272
+ }
263
273
  }
264
274
  else {
265
275
  let typeBuilder;
@@ -676,7 +686,8 @@ function generateTableCode(table, useCamelCase, enumNames, domainNames, columnTy
676
686
  function generateEnumCode(enumDef, useCamelCase) {
677
687
  const baseName = useCamelCase ? (0, utils_1.toCamelCase)(enumDef.name) : enumDef.name;
678
688
  const enumName = `${baseName}Enum`;
679
- const valuesStr = enumDef.values.map(v => `'${(0, utils_1.escapeString)(v)}'`).join(', ');
689
+ const values = Array.isArray(enumDef.values) ? enumDef.values : typeof enumDef.values === 'string' ? enumDef.values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [];
690
+ const valuesStr = values.map(v => `'${(0, utils_1.escapeString)(v)}'`).join(', ');
680
691
  const trackingId = enumDef.trackingId || generateTrackingId('e');
681
692
  return `export const ${enumName} = pgEnum('${enumDef.name}', [${valuesStr}]).$id('${trackingId}')`;
682
693
  }
@@ -212,7 +212,7 @@ async function introspectCockroachDB(connection, options) {
212
212
  const enums = enumsResult.rows.map(row => ({
213
213
  name: row.enum_name,
214
214
  schema: row.enum_schema,
215
- values: row.enum_values || [],
215
+ values: Array.isArray(row.enum_values) ? row.enum_values : typeof row.enum_values === 'string' ? row.enum_values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [],
216
216
  }));
217
217
  onProgress?.('processing');
218
218
  const columnsByTable = new Map();
@@ -196,7 +196,7 @@ async function introspectDsql(connection, options) {
196
196
  const enums = enumsResult.rows.map(row => ({
197
197
  name: row.enum_name,
198
198
  schema: row.enum_schema,
199
- values: row.enum_values || [],
199
+ values: Array.isArray(row.enum_values) ? row.enum_values : typeof row.enum_values === 'string' ? row.enum_values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [],
200
200
  }));
201
201
  onProgress?.('processing');
202
202
  const columnsByTable = new Map();
@@ -231,7 +231,7 @@ async function introspectNile(connection, options) {
231
231
  const enums = enumsResult.rows.map(row => ({
232
232
  name: row.enum_name,
233
233
  schema: row.enum_schema,
234
- values: row.enum_values || [],
234
+ values: Array.isArray(row.enum_values) ? row.enum_values : typeof row.enum_values === 'string' ? row.enum_values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [],
235
235
  }));
236
236
  onProgress?.('processing');
237
237
  const columnsByTable = new Map();
@@ -224,7 +224,7 @@ async function introspectPostgres(connection, options) {
224
224
  const enums = enumsResult.rows.map(row => ({
225
225
  name: row.enum_name,
226
226
  schema: row.enum_schema,
227
- values: row.enum_values || [],
227
+ values: Array.isArray(row.enum_values) ? row.enum_values : typeof row.enum_values === 'string' ? row.enum_values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [],
228
228
  }));
229
229
  onProgress?.('processing');
230
230
  const columnsByTable = new Map();
@@ -538,7 +538,7 @@ function compositeToAST(composite) {
538
538
  const config = fieldDef.$config || fieldDef;
539
539
  attributes.push({
540
540
  name: fieldName,
541
- type: config.$type || 'text',
541
+ type: config.$sqlType || (typeof config.$type === 'string' ? config.$type : null) || 'text',
542
542
  collation: config.$collation,
543
543
  });
544
544
  }
@@ -63,30 +63,55 @@ class ConnectedSelectBuilder {
63
63
  }
64
64
  const tableColumns = joinedTableDef.$columns || joinedTableDef;
65
65
  const dbToProp = new Map();
66
+ const propTypes = new Map();
66
67
  for (const [propName, colDef] of Object.entries(tableColumns)) {
67
68
  const dbColName = colDef?.$columnName ?? propName;
68
69
  dbToProp.set(dbColName, propName);
70
+ const snakeCase = propName.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
71
+ if (snakeCase !== propName && !dbToProp.has(snakeCase)) {
72
+ dbToProp.set(snakeCase, propName);
73
+ }
74
+ const sqlType = colDef?.$sqlType || (typeof colDef?.$type === 'string' ? colDef.$type : undefined);
75
+ if (sqlType) {
76
+ propTypes.set(propName, sqlType.toLowerCase());
77
+ }
69
78
  }
70
79
  if (Array.isArray(nestedData)) {
71
- transformed[alias] = nestedData.map((item) => this.transformNestedObject(item, dbToProp));
80
+ transformed[alias] = nestedData.map((item) => this.transformNestedObject(item, dbToProp, propTypes));
72
81
  }
73
82
  else {
74
- transformed[alias] = this.transformNestedObject(nestedData, dbToProp);
83
+ transformed[alias] = this.transformNestedObject(nestedData, dbToProp, propTypes);
75
84
  }
76
85
  }
77
86
  return transformed;
78
87
  });
79
88
  }
80
- transformNestedObject(obj, dbToProp) {
89
+ transformNestedObject(obj, dbToProp, propTypes) {
81
90
  if (!obj || typeof obj !== 'object')
82
91
  return obj;
83
92
  const result = {};
84
93
  for (const [key, value] of Object.entries(obj)) {
85
94
  const propName = dbToProp.get(key) ?? key;
86
- result[propName] = value;
95
+ result[propName] = propTypes ? this.coerceValue(value, propTypes.get(propName)) : value;
87
96
  }
88
97
  return result;
89
98
  }
99
+ coerceValue(value, sqlType) {
100
+ if (value === null || value === undefined || !sqlType)
101
+ return value;
102
+ switch (sqlType) {
103
+ case 'timestamp':
104
+ case 'timestamptz':
105
+ case 'timestamp without time zone':
106
+ case 'timestamp with time zone':
107
+ return typeof value === 'string' ? new Date(value) : value;
108
+ case 'bigint':
109
+ case 'int8':
110
+ return typeof value === 'number' ? BigInt(value) : typeof value === 'string' ? BigInt(value) : value;
111
+ default:
112
+ return value;
113
+ }
114
+ }
90
115
  where(callback) {
91
116
  this.builder.where(callback);
92
117
  return this;
@@ -42,14 +42,24 @@ function executeTypeSafeJoin(ctx, joinType, tableOrAlias, callback) {
42
42
  }
43
43
  const selectedProps = conditionInternals.getSelectedColumns();
44
44
  let selectColumns;
45
+ const toSnake = (s) => s.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`);
45
46
  if (selectedProps && selectedProps.length > 0) {
46
47
  const rightColumns = rightTableDef?.$columns || rightTableDef;
47
48
  selectColumns = selectedProps.map(prop => {
48
49
  const columnDef = rightColumns?.[prop];
49
- const sqlName = columnDef?.$columnName || prop;
50
+ const sqlName = columnDef?.$columnName || toSnake(prop);
50
51
  return { property: prop, sqlName };
51
52
  });
52
53
  }
54
+ else if (rightTableDef) {
55
+ const rightColumns = rightTableDef.$columns || rightTableDef;
56
+ selectColumns = Object.entries(rightColumns)
57
+ .filter(([_, colDef]) => colDef && typeof colDef === 'object' && '$type' in colDef)
58
+ .map(([propName, colDef]) => ({
59
+ property: propName,
60
+ sqlName: colDef.$columnName || toSnake(propName),
61
+ }));
62
+ }
53
63
  const joinClause = {
54
64
  type: joinType,
55
65
  table: rightTableName,
@@ -24,7 +24,7 @@ function buildColumnMappings(schema, mappings, debugLog) {
24
24
  const propToFields = new Map();
25
25
  for (const [propName, colDef] of Object.entries(columns)) {
26
26
  const dbColName = colDef?.$columnName ?? propName;
27
- const colType = colDef?.$type ?? 'TEXT';
27
+ const colType = colDef?.$sqlType || (typeof colDef?.$type === 'string' ? colDef.$type : undefined) || 'TEXT';
28
28
  propToDb.set(propName, dbColName);
29
29
  dbToProp.set(dbColName, propName);
30
30
  propToType.set(propName, colType);
@@ -23,8 +23,12 @@ function buildColumnTypeMap(tableDef) {
23
23
  const typeMap = new Map();
24
24
  const columns = tableDef.$columns || tableDef;
25
25
  for (const [key, colDef] of Object.entries(columns)) {
26
- if (colDef && typeof colDef === 'object' && '$type' in colDef) {
27
- typeMap.set(key, colDef.$type);
26
+ if (colDef && typeof colDef === 'object') {
27
+ const col = colDef;
28
+ const sqlType = col.$sqlType || (typeof col.$type === 'string' ? col.$type : undefined);
29
+ if (sqlType) {
30
+ typeMap.set(key, sqlType);
31
+ }
28
32
  }
29
33
  }
30
34
  return typeMap;
@@ -151,7 +151,7 @@ function createColumn(type) {
151
151
  config.$length = len;
152
152
  const baseType = config.$type.replace(/\(\d+\)/, '');
153
153
  config.$type = `${baseType}(${len})`;
154
- return Object.assign(this, { $length: len, $type: config.$type });
154
+ return Object.assign(this, { $length: len, $type: config.$type, $sqlType: config.$type });
155
155
  },
156
156
  precision(p) {
157
157
  config.$precision = p;
@@ -159,7 +159,7 @@ function createColumn(type) {
159
159
  config.$type = config.$scale !== undefined
160
160
  ? `${base}(${p}, ${config.$scale})`
161
161
  : `${base}(${p})`;
162
- return Object.assign(this, { $precision: p, $type: config.$type });
162
+ return Object.assign(this, { $precision: p, $type: config.$type, $sqlType: config.$type });
163
163
  },
164
164
  scale(s) {
165
165
  config.$scale = s;
@@ -167,7 +167,7 @@ function createColumn(type) {
167
167
  config.$type = config.$precision !== undefined
168
168
  ? `${base}(${config.$precision}, ${s})`
169
169
  : `${base}(38, ${s})`;
170
- return Object.assign(this, { $scale: s, $type: config.$type });
170
+ return Object.assign(this, { $scale: s, $type: config.$type, $sqlType: config.$type });
171
171
  },
172
172
  withTimezone() {
173
173
  config.$withTimezone = true;
@@ -177,12 +177,12 @@ function createColumn(type) {
177
177
  else if (config.$type === 'TIME') {
178
178
  config.$type = 'TIMETZ';
179
179
  }
180
- return Object.assign(this, { $withTimezone: true, $type: config.$type });
180
+ return Object.assign(this, { $withTimezone: true, $type: config.$type, $sqlType: config.$type });
181
181
  },
182
182
  dimensions(d) {
183
183
  config.$dimensions = d;
184
184
  config.$type = `VECTOR(${d})`;
185
- return Object.assign(this, { $dimensions: d, $type: config.$type });
185
+ return Object.assign(this, { $dimensions: d, $type: config.$type, $sqlType: config.$type });
186
186
  },
187
187
  comment(text) {
188
188
  config.$comment = text;
@@ -15,7 +15,7 @@ function pgComposite(name, fields) {
15
15
  const config = fieldConfig;
16
16
  return {
17
17
  name: fieldName,
18
- type: config.$type,
18
+ type: config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT'),
19
19
  collation: undefined,
20
20
  };
21
21
  });
@@ -43,7 +43,7 @@ function generateCompositeTypeSQL(composite) {
43
43
  const fieldDefs = [];
44
44
  for (const [fieldName, fieldConfig] of Object.entries(composite.$fields)) {
45
45
  const config = fieldConfig;
46
- let fieldDef = `"${fieldName}" ${config.$type}`;
46
+ let fieldDef = `"${fieldName}" ${config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT')}`;
47
47
  if (config.$nullable === false) {
48
48
  fieldDef += ' NOT NULL';
49
49
  }
@@ -139,8 +139,8 @@ function createDomainValueExpr() {
139
139
  };
140
140
  }
141
141
  function pgDomain(name, baseType, checks) {
142
- const config = baseType.$config;
143
- const baseTypeStr = config?.$type || 'TEXT';
142
+ const bt = baseType;
143
+ const baseTypeStr = bt.$sqlType || (typeof bt.$type === 'string' ? bt.$type : null) || 'TEXT';
144
144
  const constraints = [];
145
145
  let validateFn;
146
146
  if (checks) {
@@ -163,8 +163,8 @@ function pgDomain(name, baseType, checks) {
163
163
  }
164
164
  }
165
165
  }
166
- const notNull = config?.$nullable === false;
167
- const defaultValue = config?.$default;
166
+ const notNull = bt?.$nullable === false;
167
+ const defaultValue = bt?.$default;
168
168
  const domainFn = (columnName) => {
169
169
  const col = (0, column_builder_1.createColumnWithName)(name, columnName);
170
170
  if (validateFn) {
@@ -8,6 +8,7 @@ function pgEnum(name, values) {
8
8
  const config = function (columnName) {
9
9
  const col = {
10
10
  $type: name,
11
+ $sqlType: name,
11
12
  $enumName: name,
12
13
  $checkValues: values,
13
14
  };
@@ -38,7 +39,8 @@ function pgEnum(name, values) {
38
39
  array() {
39
40
  col.$array = true;
40
41
  col.$type = `${name}[]`;
41
- return Object.assign(this, { $array: true, $type: `${name}[]` });
42
+ col.$sqlType = `${name}[]`;
43
+ return Object.assign(this, { $array: true, $type: `${name}[]`, $sqlType: `${name}[]` });
42
44
  },
43
45
  $id(trackingId) {
44
46
  col.$trackingId = trackingId;
@@ -21,7 +21,7 @@ function createTableProxy(name, columns, tableDef) {
21
21
  for (const [colKey, colDef] of Object.entries(columns)) {
22
22
  const config = colDef.$config || colDef;
23
23
  const sqlName = config.$sqlName || config.$columnName || colKey;
24
- const colType = config.$sqlType || config.$type || 'unknown';
24
+ const colType = config.$sqlType || (typeof config.$type === 'string' ? config.$type : undefined) || 'unknown';
25
25
  proxy[colKey] = {
26
26
  $table: name,
27
27
  $column: sqlName,
@@ -116,7 +116,7 @@ function generateCreateTableSQL(def) {
116
116
  function generateColumnSQL(name, config) {
117
117
  const actualName = config.$columnName || name;
118
118
  const parts = [pg_format_1.default.ident(actualName)];
119
- let typeName = config.$type;
119
+ let typeName = config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT');
120
120
  if (config.$array) {
121
121
  const dims = config.$dimensions ?? 1;
122
122
  typeName += '[]'.repeat(dims);
@@ -76,7 +76,7 @@ function validateTable(table, tableName, features, errors, warnings, info, allow
76
76
  validateTableFeatures(table, tableName, features, errors, warnings);
77
77
  }
78
78
  function validateColumn(column, location, features, errors, warnings, allowedCustomTypes) {
79
- const colType = column.$type || column.$sqlType || 'unknown';
79
+ const colType = column.$sqlType || (typeof column.$type === 'string' ? column.$type : undefined) || 'unknown';
80
80
  if (allowedCustomTypes.includes(colType.toUpperCase())) {
81
81
  return;
82
82
  }
@@ -17,7 +17,7 @@ function extractDefaultValue(col) {
17
17
  return undefined;
18
18
  }
19
19
  function resolveColumnType(col) {
20
- const base = col.$sqlType || col.$type || 'TEXT';
20
+ const base = col.$sqlType || (typeof col.$type === 'string' ? col.$type : null) || 'TEXT';
21
21
  if (col.$length !== undefined) {
22
22
  return `${base}(${col.$length})`;
23
23
  }
@@ -32,7 +32,7 @@ function formatDefault(col) {
32
32
  }
33
33
  function generateColumnSQL(name, col) {
34
34
  const parts = [quoteSQLiteIdentifier(col.$columnName || name)];
35
- parts.push(col.$type || 'TEXT');
35
+ parts.push(col.$sqlType || (typeof col.$type === 'string' ? col.$type : null) || 'TEXT');
36
36
  if (col.$primaryKey) {
37
37
  parts.push('PRIMARY KEY');
38
38
  if (col.$autoincrement) {
@@ -14,7 +14,7 @@ function sqliteTable(name, columns, options) {
14
14
  for (const [colName, colConfig] of Object.entries(columns)) {
15
15
  const col = colConfig;
16
16
  if (col.$autoincrement) {
17
- const sqlType = (col.$sqlType || col.$type || '').toUpperCase();
17
+ const sqlType = (col.$sqlType || (typeof col.$type === 'string' ? col.$type : '') || '').toUpperCase();
18
18
  if (sqlType !== 'INTEGER' || !col.$primaryKey) {
19
19
  throw new Error(`[sqliteTable] AUTOINCREMENT on column "${colName}" in table "${name}" ` +
20
20
  `requires INTEGER PRIMARY KEY. Got type "${sqlType}", primaryKey=${col.$primaryKey}.`);
@@ -56,7 +56,8 @@ function deserializeRow(row, schema) {
56
56
  const columnMap = new Map();
57
57
  for (const [key, config] of Object.entries(schema)) {
58
58
  const dbColumnName = config.$columnName || key;
59
- columnMap.set(dbColumnName, { key, type: config.$type });
59
+ const sqlType = config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT');
60
+ columnMap.set(dbColumnName, { key, type: sqlType });
60
61
  }
61
62
  for (const [dbColumn, value] of Object.entries(row)) {
62
63
  const mapping = columnMap.get(dbColumn);
@@ -102,7 +103,8 @@ function serializeRow(row, schema) {
102
103
  for (const [key, value] of Object.entries(row)) {
103
104
  const config = schema[key];
104
105
  if (config) {
105
- result[key] = serializeValue(value, config.$type);
106
+ const sqlType = config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT');
107
+ result[key] = serializeValue(value, sqlType);
106
108
  }
107
109
  else {
108
110
  result[key] = value;
@@ -246,11 +246,21 @@ function generateColumnCode(col, useCamelCase, enumNames, domainNames, checkOver
246
246
  const normalizedType = col.type.toLowerCase();
247
247
  if (enumNames.has(normalizedType) || enumNames.has(col.type)) {
248
248
  const enumName = `${toCamelCase(col.type)}Enum`;
249
- line = ` ${colName}: ${enumName}()`;
249
+ if (useCamelCase && colName !== col.name) {
250
+ line = ` ${colName}: ${enumName}('${col.name}')`;
251
+ }
252
+ else {
253
+ line = ` ${colName}: ${enumName}()`;
254
+ }
250
255
  }
251
256
  else if (domainNames.has(normalizedType) || domainNames.has(col.type)) {
252
257
  const domainName = `${toCamelCase(col.type)}Domain`;
253
- line = ` ${colName}: ${domainName}()`;
258
+ if (useCamelCase && colName !== col.name) {
259
+ line = ` ${colName}: ${domainName}('${col.name}')`;
260
+ }
261
+ else {
262
+ line = ` ${colName}: ${domainName}()`;
263
+ }
254
264
  }
255
265
  else {
256
266
  let typeBuilder;
@@ -667,7 +677,8 @@ function generateTableCode(table, useCamelCase, enumNames, domainNames, columnTy
667
677
  function generateEnumCode(enumDef, useCamelCase) {
668
678
  const baseName = useCamelCase ? toCamelCase(enumDef.name) : enumDef.name;
669
679
  const enumName = `${baseName}Enum`;
670
- const valuesStr = enumDef.values.map(v => `'${escapeString(v)}'`).join(', ');
680
+ const values = Array.isArray(enumDef.values) ? enumDef.values : typeof enumDef.values === 'string' ? enumDef.values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [];
681
+ const valuesStr = values.map(v => `'${escapeString(v)}'`).join(', ');
671
682
  const trackingId = enumDef.trackingId || generateTrackingId('e');
672
683
  return `export const ${enumName} = pgEnum('${enumDef.name}', [${valuesStr}]).$id('${trackingId}')`;
673
684
  }
@@ -176,7 +176,7 @@ export async function introspectCockroachDB(connection, options) {
176
176
  const enums = enumsResult.rows.map(row => ({
177
177
  name: row.enum_name,
178
178
  schema: row.enum_schema,
179
- values: row.enum_values || [],
179
+ values: Array.isArray(row.enum_values) ? row.enum_values : typeof row.enum_values === 'string' ? row.enum_values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [],
180
180
  }));
181
181
  onProgress?.('processing');
182
182
  const columnsByTable = new Map();
@@ -160,7 +160,7 @@ export async function introspectDsql(connection, options) {
160
160
  const enums = enumsResult.rows.map(row => ({
161
161
  name: row.enum_name,
162
162
  schema: row.enum_schema,
163
- values: row.enum_values || [],
163
+ values: Array.isArray(row.enum_values) ? row.enum_values : typeof row.enum_values === 'string' ? row.enum_values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [],
164
164
  }));
165
165
  onProgress?.('processing');
166
166
  const columnsByTable = new Map();
@@ -195,7 +195,7 @@ export async function introspectNile(connection, options) {
195
195
  const enums = enumsResult.rows.map(row => ({
196
196
  name: row.enum_name,
197
197
  schema: row.enum_schema,
198
- values: row.enum_values || [],
198
+ values: Array.isArray(row.enum_values) ? row.enum_values : typeof row.enum_values === 'string' ? row.enum_values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [],
199
199
  }));
200
200
  onProgress?.('processing');
201
201
  const columnsByTable = new Map();
@@ -188,7 +188,7 @@ export async function introspectPostgres(connection, options) {
188
188
  const enums = enumsResult.rows.map(row => ({
189
189
  name: row.enum_name,
190
190
  schema: row.enum_schema,
191
- values: row.enum_values || [],
191
+ values: Array.isArray(row.enum_values) ? row.enum_values : typeof row.enum_values === 'string' ? row.enum_values.replace(/^\{|\}$/g, '').split(',').filter(Boolean) : [],
192
192
  }));
193
193
  onProgress?.('processing');
194
194
  const columnsByTable = new Map();
@@ -504,7 +504,7 @@ export function compositeToAST(composite) {
504
504
  const config = fieldDef.$config || fieldDef;
505
505
  attributes.push({
506
506
  name: fieldName,
507
- type: config.$type || 'text',
507
+ type: config.$sqlType || (typeof config.$type === 'string' ? config.$type : null) || 'text',
508
508
  collation: config.$collation,
509
509
  });
510
510
  }
@@ -60,30 +60,55 @@ export class ConnectedSelectBuilder {
60
60
  }
61
61
  const tableColumns = joinedTableDef.$columns || joinedTableDef;
62
62
  const dbToProp = new Map();
63
+ const propTypes = new Map();
63
64
  for (const [propName, colDef] of Object.entries(tableColumns)) {
64
65
  const dbColName = colDef?.$columnName ?? propName;
65
66
  dbToProp.set(dbColName, propName);
67
+ const snakeCase = propName.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
68
+ if (snakeCase !== propName && !dbToProp.has(snakeCase)) {
69
+ dbToProp.set(snakeCase, propName);
70
+ }
71
+ const sqlType = colDef?.$sqlType || (typeof colDef?.$type === 'string' ? colDef.$type : undefined);
72
+ if (sqlType) {
73
+ propTypes.set(propName, sqlType.toLowerCase());
74
+ }
66
75
  }
67
76
  if (Array.isArray(nestedData)) {
68
- transformed[alias] = nestedData.map((item) => this.transformNestedObject(item, dbToProp));
77
+ transformed[alias] = nestedData.map((item) => this.transformNestedObject(item, dbToProp, propTypes));
69
78
  }
70
79
  else {
71
- transformed[alias] = this.transformNestedObject(nestedData, dbToProp);
80
+ transformed[alias] = this.transformNestedObject(nestedData, dbToProp, propTypes);
72
81
  }
73
82
  }
74
83
  return transformed;
75
84
  });
76
85
  }
77
- transformNestedObject(obj, dbToProp) {
86
+ transformNestedObject(obj, dbToProp, propTypes) {
78
87
  if (!obj || typeof obj !== 'object')
79
88
  return obj;
80
89
  const result = {};
81
90
  for (const [key, value] of Object.entries(obj)) {
82
91
  const propName = dbToProp.get(key) ?? key;
83
- result[propName] = value;
92
+ result[propName] = propTypes ? this.coerceValue(value, propTypes.get(propName)) : value;
84
93
  }
85
94
  return result;
86
95
  }
96
+ coerceValue(value, sqlType) {
97
+ if (value === null || value === undefined || !sqlType)
98
+ return value;
99
+ switch (sqlType) {
100
+ case 'timestamp':
101
+ case 'timestamptz':
102
+ case 'timestamp without time zone':
103
+ case 'timestamp with time zone':
104
+ return typeof value === 'string' ? new Date(value) : value;
105
+ case 'bigint':
106
+ case 'int8':
107
+ return typeof value === 'number' ? BigInt(value) : typeof value === 'string' ? BigInt(value) : value;
108
+ default:
109
+ return value;
110
+ }
111
+ }
87
112
  where(callback) {
88
113
  this.builder.where(callback);
89
114
  return this;
@@ -38,14 +38,24 @@ export function executeTypeSafeJoin(ctx, joinType, tableOrAlias, callback) {
38
38
  }
39
39
  const selectedProps = conditionInternals.getSelectedColumns();
40
40
  let selectColumns;
41
+ const toSnake = (s) => s.replace(/[A-Z]/g, l => `_${l.toLowerCase()}`);
41
42
  if (selectedProps && selectedProps.length > 0) {
42
43
  const rightColumns = rightTableDef?.$columns || rightTableDef;
43
44
  selectColumns = selectedProps.map(prop => {
44
45
  const columnDef = rightColumns?.[prop];
45
- const sqlName = columnDef?.$columnName || prop;
46
+ const sqlName = columnDef?.$columnName || toSnake(prop);
46
47
  return { property: prop, sqlName };
47
48
  });
48
49
  }
50
+ else if (rightTableDef) {
51
+ const rightColumns = rightTableDef.$columns || rightTableDef;
52
+ selectColumns = Object.entries(rightColumns)
53
+ .filter(([_, colDef]) => colDef && typeof colDef === 'object' && '$type' in colDef)
54
+ .map(([propName, colDef]) => ({
55
+ property: propName,
56
+ sqlName: colDef.$columnName || toSnake(propName),
57
+ }));
58
+ }
49
59
  const joinClause = {
50
60
  type: joinType,
51
61
  table: rightTableName,
@@ -17,7 +17,7 @@ export function buildColumnMappings(schema, mappings, debugLog) {
17
17
  const propToFields = new Map();
18
18
  for (const [propName, colDef] of Object.entries(columns)) {
19
19
  const dbColName = colDef?.$columnName ?? propName;
20
- const colType = colDef?.$type ?? 'TEXT';
20
+ const colType = colDef?.$sqlType || (typeof colDef?.$type === 'string' ? colDef.$type : undefined) || 'TEXT';
21
21
  propToDb.set(propName, dbColName);
22
22
  dbToProp.set(dbColName, propName);
23
23
  propToType.set(propName, colType);
@@ -18,8 +18,12 @@ export function buildColumnTypeMap(tableDef) {
18
18
  const typeMap = new Map();
19
19
  const columns = tableDef.$columns || tableDef;
20
20
  for (const [key, colDef] of Object.entries(columns)) {
21
- if (colDef && typeof colDef === 'object' && '$type' in colDef) {
22
- typeMap.set(key, colDef.$type);
21
+ if (colDef && typeof colDef === 'object') {
22
+ const col = colDef;
23
+ const sqlType = col.$sqlType || (typeof col.$type === 'string' ? col.$type : undefined);
24
+ if (sqlType) {
25
+ typeMap.set(key, sqlType);
26
+ }
23
27
  }
24
28
  }
25
29
  return typeMap;
@@ -146,7 +146,7 @@ export function createColumn(type) {
146
146
  config.$length = len;
147
147
  const baseType = config.$type.replace(/\(\d+\)/, '');
148
148
  config.$type = `${baseType}(${len})`;
149
- return Object.assign(this, { $length: len, $type: config.$type });
149
+ return Object.assign(this, { $length: len, $type: config.$type, $sqlType: config.$type });
150
150
  },
151
151
  precision(p) {
152
152
  config.$precision = p;
@@ -154,7 +154,7 @@ export function createColumn(type) {
154
154
  config.$type = config.$scale !== undefined
155
155
  ? `${base}(${p}, ${config.$scale})`
156
156
  : `${base}(${p})`;
157
- return Object.assign(this, { $precision: p, $type: config.$type });
157
+ return Object.assign(this, { $precision: p, $type: config.$type, $sqlType: config.$type });
158
158
  },
159
159
  scale(s) {
160
160
  config.$scale = s;
@@ -162,7 +162,7 @@ export function createColumn(type) {
162
162
  config.$type = config.$precision !== undefined
163
163
  ? `${base}(${config.$precision}, ${s})`
164
164
  : `${base}(38, ${s})`;
165
- return Object.assign(this, { $scale: s, $type: config.$type });
165
+ return Object.assign(this, { $scale: s, $type: config.$type, $sqlType: config.$type });
166
166
  },
167
167
  withTimezone() {
168
168
  config.$withTimezone = true;
@@ -172,12 +172,12 @@ export function createColumn(type) {
172
172
  else if (config.$type === 'TIME') {
173
173
  config.$type = 'TIMETZ';
174
174
  }
175
- return Object.assign(this, { $withTimezone: true, $type: config.$type });
175
+ return Object.assign(this, { $withTimezone: true, $type: config.$type, $sqlType: config.$type });
176
176
  },
177
177
  dimensions(d) {
178
178
  config.$dimensions = d;
179
179
  config.$type = `VECTOR(${d})`;
180
- return Object.assign(this, { $dimensions: d, $type: config.$type });
180
+ return Object.assign(this, { $dimensions: d, $type: config.$type, $sqlType: config.$type });
181
181
  },
182
182
  comment(text) {
183
183
  config.$comment = text;
@@ -9,7 +9,7 @@ export function pgComposite(name, fields) {
9
9
  const config = fieldConfig;
10
10
  return {
11
11
  name: fieldName,
12
- type: config.$type,
12
+ type: config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT'),
13
13
  collation: undefined,
14
14
  };
15
15
  });
@@ -37,7 +37,7 @@ export function generateCompositeTypeSQL(composite) {
37
37
  const fieldDefs = [];
38
38
  for (const [fieldName, fieldConfig] of Object.entries(composite.$fields)) {
39
39
  const config = fieldConfig;
40
- let fieldDef = `"${fieldName}" ${config.$type}`;
40
+ let fieldDef = `"${fieldName}" ${config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT')}`;
41
41
  if (config.$nullable === false) {
42
42
  fieldDef += ' NOT NULL';
43
43
  }
@@ -135,8 +135,8 @@ function createDomainValueExpr() {
135
135
  };
136
136
  }
137
137
  export function pgDomain(name, baseType, checks) {
138
- const config = baseType.$config;
139
- const baseTypeStr = config?.$type || 'TEXT';
138
+ const bt = baseType;
139
+ const baseTypeStr = bt.$sqlType || (typeof bt.$type === 'string' ? bt.$type : null) || 'TEXT';
140
140
  const constraints = [];
141
141
  let validateFn;
142
142
  if (checks) {
@@ -159,8 +159,8 @@ export function pgDomain(name, baseType, checks) {
159
159
  }
160
160
  }
161
161
  }
162
- const notNull = config?.$nullable === false;
163
- const defaultValue = config?.$default;
162
+ const notNull = bt?.$nullable === false;
163
+ const defaultValue = bt?.$default;
164
164
  const domainFn = (columnName) => {
165
165
  const col = createColumnWithName(name, columnName);
166
166
  if (validateFn) {
@@ -2,6 +2,7 @@ export function pgEnum(name, values) {
2
2
  const config = function (columnName) {
3
3
  const col = {
4
4
  $type: name,
5
+ $sqlType: name,
5
6
  $enumName: name,
6
7
  $checkValues: values,
7
8
  };
@@ -32,7 +33,8 @@ export function pgEnum(name, values) {
32
33
  array() {
33
34
  col.$array = true;
34
35
  col.$type = `${name}[]`;
35
- return Object.assign(this, { $array: true, $type: `${name}[]` });
36
+ col.$sqlType = `${name}[]`;
37
+ return Object.assign(this, { $array: true, $type: `${name}[]`, $sqlType: `${name}[]` });
36
38
  },
37
39
  $id(trackingId) {
38
40
  col.$trackingId = trackingId;
@@ -17,7 +17,7 @@ function createTableProxy(name, columns, tableDef) {
17
17
  for (const [colKey, colDef] of Object.entries(columns)) {
18
18
  const config = colDef.$config || colDef;
19
19
  const sqlName = config.$sqlName || config.$columnName || colKey;
20
- const colType = config.$sqlType || config.$type || 'unknown';
20
+ const colType = config.$sqlType || (typeof config.$type === 'string' ? config.$type : undefined) || 'unknown';
21
21
  proxy[colKey] = {
22
22
  $table: name,
23
23
  $column: sqlName,
@@ -105,7 +105,7 @@ export function generateCreateTableSQL(def) {
105
105
  export function generateColumnSQL(name, config) {
106
106
  const actualName = config.$columnName || name;
107
107
  const parts = [format.ident(actualName)];
108
- let typeName = config.$type;
108
+ let typeName = config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT');
109
109
  if (config.$array) {
110
110
  const dims = config.$dimensions ?? 1;
111
111
  typeName += '[]'.repeat(dims);
@@ -73,7 +73,7 @@ function validateTable(table, tableName, features, errors, warnings, info, allow
73
73
  validateTableFeatures(table, tableName, features, errors, warnings);
74
74
  }
75
75
  function validateColumn(column, location, features, errors, warnings, allowedCustomTypes) {
76
- const colType = column.$type || column.$sqlType || 'unknown';
76
+ const colType = column.$sqlType || (typeof column.$type === 'string' ? column.$type : undefined) || 'unknown';
77
77
  if (allowedCustomTypes.includes(colType.toUpperCase())) {
78
78
  return;
79
79
  }
@@ -14,7 +14,7 @@ function extractDefaultValue(col) {
14
14
  return undefined;
15
15
  }
16
16
  function resolveColumnType(col) {
17
- const base = col.$sqlType || col.$type || 'TEXT';
17
+ const base = col.$sqlType || (typeof col.$type === 'string' ? col.$type : null) || 'TEXT';
18
18
  if (col.$length !== undefined) {
19
19
  return `${base}(${col.$length})`;
20
20
  }
@@ -27,7 +27,7 @@ function formatDefault(col) {
27
27
  }
28
28
  function generateColumnSQL(name, col) {
29
29
  const parts = [quoteSQLiteIdentifier(col.$columnName || name)];
30
- parts.push(col.$type || 'TEXT');
30
+ parts.push(col.$sqlType || (typeof col.$type === 'string' ? col.$type : null) || 'TEXT');
31
31
  if (col.$primaryKey) {
32
32
  parts.push('PRIMARY KEY');
33
33
  if (col.$autoincrement) {
@@ -11,7 +11,7 @@ export function sqliteTable(name, columns, options) {
11
11
  for (const [colName, colConfig] of Object.entries(columns)) {
12
12
  const col = colConfig;
13
13
  if (col.$autoincrement) {
14
- const sqlType = (col.$sqlType || col.$type || '').toUpperCase();
14
+ const sqlType = (col.$sqlType || (typeof col.$type === 'string' ? col.$type : '') || '').toUpperCase();
15
15
  if (sqlType !== 'INTEGER' || !col.$primaryKey) {
16
16
  throw new Error(`[sqliteTable] AUTOINCREMENT on column "${colName}" in table "${name}" ` +
17
17
  `requires INTEGER PRIMARY KEY. Got type "${sqlType}", primaryKey=${col.$primaryKey}.`);
@@ -48,7 +48,8 @@ export function deserializeRow(row, schema) {
48
48
  const columnMap = new Map();
49
49
  for (const [key, config] of Object.entries(schema)) {
50
50
  const dbColumnName = config.$columnName || key;
51
- columnMap.set(dbColumnName, { key, type: config.$type });
51
+ const sqlType = config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT');
52
+ columnMap.set(dbColumnName, { key, type: sqlType });
52
53
  }
53
54
  for (const [dbColumn, value] of Object.entries(row)) {
54
55
  const mapping = columnMap.get(dbColumn);
@@ -94,7 +95,8 @@ export function serializeRow(row, schema) {
94
95
  for (const [key, value] of Object.entries(row)) {
95
96
  const config = schema[key];
96
97
  if (config) {
97
- result[key] = serializeValue(value, config.$type);
98
+ const sqlType = config.$sqlType || (typeof config.$type === 'string' ? config.$type : 'TEXT');
99
+ result[key] = serializeValue(value, sqlType);
98
100
  }
99
101
  else {
100
102
  result[key] = value;
package/dist/index.d.ts CHANGED
@@ -8744,6 +8744,12 @@ declare class ConnectedSelectBuilder<TSchema = any, TTable = any, TColumns exten
8744
8744
  * @internal
8745
8745
  */
8746
8746
  private transformNestedObject;
8747
+ /**
8748
+ * Coerce a JSON-serialized value back to its proper JS type.
8749
+ * row_to_json() serializes timestamps as strings and bigints as numbers.
8750
+ * @internal
8751
+ */
8752
+ private coerceValue;
8747
8753
  where(callback: (q: TypedConditionBuilder<TTable>) => TypedConditionBuilder<TTable>): this;
8748
8754
  orderBy(column: ColumnName<TTable>, direction?: "ASC" | "DESC"): this;
8749
8755
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "relq",
3
- "version": "1.0.83",
3
+ "version": "1.0.85",
4
4
  "description": "The Fully-Typed PostgreSQL ORM for TypeScript",
5
5
  "author": "Olajide Mathew O. <olajide.mathew@yuniq.solutions>",
6
6
  "license": "MIT",