relq 1.0.4 → 1.0.6
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.
- package/dist/cjs/cli/commands/add.cjs +252 -12
- package/dist/cjs/cli/commands/commit.cjs +12 -1
- package/dist/cjs/cli/commands/export.cjs +25 -19
- package/dist/cjs/cli/commands/import.cjs +219 -100
- package/dist/cjs/cli/commands/init.cjs +86 -14
- package/dist/cjs/cli/commands/pull.cjs +104 -23
- package/dist/cjs/cli/commands/push.cjs +38 -3
- package/dist/cjs/cli/index.cjs +9 -1
- package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
- package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
- package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
- package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
- package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
- package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
- package/dist/cjs/cli/utils/ast/index.cjs +19 -0
- package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
- package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
- package/dist/cjs/cli/utils/ast/types.cjs +2 -0
- package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
- package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
- package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
- package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
- package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
- package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
- package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
- package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
- package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
- package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
- package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
- package/dist/cjs/cli/utils/type-generator.cjs +114 -15
- package/dist/cjs/core/relq-client.cjs +22 -6
- package/dist/cjs/schema-definition/column-types.cjs +150 -13
- package/dist/cjs/schema-definition/defaults.cjs +72 -0
- package/dist/cjs/schema-definition/index.cjs +15 -1
- package/dist/cjs/schema-definition/introspection.cjs +7 -3
- package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
- package/dist/cjs/schema-definition/pg-view.cjs +30 -0
- package/dist/cjs/schema-definition/table-definition.cjs +110 -4
- package/dist/cjs/types/config-types.cjs +13 -4
- package/dist/cjs/utils/aws-dsql.cjs +177 -0
- package/dist/config.d.ts +146 -1
- package/dist/esm/cli/commands/add.js +250 -13
- package/dist/esm/cli/commands/commit.js +12 -1
- package/dist/esm/cli/commands/export.js +25 -19
- package/dist/esm/cli/commands/import.js +221 -102
- package/dist/esm/cli/commands/init.js +86 -14
- package/dist/esm/cli/commands/pull.js +106 -25
- package/dist/esm/cli/commands/push.js +39 -4
- package/dist/esm/cli/index.js +9 -1
- package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
- package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
- package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
- package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
- package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
- package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
- package/dist/esm/cli/utils/ast/index.js +3 -0
- package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
- package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
- package/dist/esm/cli/utils/ast/types.js +1 -0
- package/dist/esm/cli/utils/ast-codegen.js +945 -0
- package/dist/esm/cli/utils/ast-transformer.js +907 -0
- package/dist/esm/cli/utils/change-tracker.js +50 -1
- package/dist/esm/cli/utils/cli-utils.js +147 -0
- package/dist/esm/cli/utils/fast-introspect.js +149 -23
- package/dist/esm/cli/utils/pg-parser.js +1 -0
- package/dist/esm/cli/utils/repo-manager.js +114 -4
- package/dist/esm/cli/utils/schema-comparator.js +98 -14
- package/dist/esm/cli/utils/schema-introspect.js +56 -19
- package/dist/esm/cli/utils/snapshot-manager.js +0 -1
- package/dist/esm/cli/utils/sql-generator.js +353 -64
- package/dist/esm/cli/utils/type-generator.js +114 -15
- package/dist/esm/core/relq-client.js +23 -7
- package/dist/esm/schema-definition/column-types.js +147 -12
- package/dist/esm/schema-definition/defaults.js +69 -0
- package/dist/esm/schema-definition/index.js +3 -0
- package/dist/esm/schema-definition/introspection.js +7 -3
- package/dist/esm/schema-definition/pg-relations.js +161 -0
- package/dist/esm/schema-definition/pg-view.js +24 -0
- package/dist/esm/schema-definition/table-definition.js +110 -4
- package/dist/esm/types/config-types.js +12 -4
- package/dist/esm/utils/aws-dsql.js +139 -0
- package/dist/index.d.ts +159 -1
- package/dist/schema-builder.d.ts +1314 -32
- package/package.json +1 -1
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
function createColumnRef(tableName, columnName, schemaKey) {
|
|
2
|
+
return {
|
|
3
|
+
$table: tableName,
|
|
4
|
+
$column: columnName,
|
|
5
|
+
$schemaKey: schemaKey,
|
|
6
|
+
};
|
|
7
|
+
}
|
|
8
|
+
function createTargetColumnRefs(schemaKey, table) {
|
|
9
|
+
const refs = {};
|
|
10
|
+
const actualTableName = table.$name;
|
|
11
|
+
for (const colName of Object.keys(table.$columns)) {
|
|
12
|
+
const colDef = table.$columns[colName];
|
|
13
|
+
const actualColName = colDef.$sqlName || colName;
|
|
14
|
+
refs[colName] = createColumnRef(actualTableName, actualColName, colName);
|
|
15
|
+
}
|
|
16
|
+
return refs;
|
|
17
|
+
}
|
|
18
|
+
function createReferenceToBuilder(schema, currentTableKey) {
|
|
19
|
+
return new Proxy({}, {
|
|
20
|
+
get(_, targetTableKey) {
|
|
21
|
+
const targetTable = schema[targetTableKey];
|
|
22
|
+
if (!targetTable) {
|
|
23
|
+
throw new Error(`Unknown table: ${targetTableKey}`);
|
|
24
|
+
}
|
|
25
|
+
return (callback) => {
|
|
26
|
+
const targetColRefs = createTargetColumnRefs(targetTableKey, targetTable);
|
|
27
|
+
const options = callback(targetColRefs);
|
|
28
|
+
const currentTable = schema[currentTableKey];
|
|
29
|
+
const currentTableName = currentTable?.$name || currentTableKey;
|
|
30
|
+
if ('columns' in options && Array.isArray(options.columns)) {
|
|
31
|
+
const compositeOpts = options;
|
|
32
|
+
return {
|
|
33
|
+
$type: 'foreignKey',
|
|
34
|
+
$targetTable: targetTable.$name,
|
|
35
|
+
$columns: compositeOpts.columns.map(c => ({
|
|
36
|
+
table: c.$table,
|
|
37
|
+
column: c.$column,
|
|
38
|
+
})),
|
|
39
|
+
$references: compositeOpts.references.map(r => ({
|
|
40
|
+
table: r.$table,
|
|
41
|
+
column: r.$column,
|
|
42
|
+
})),
|
|
43
|
+
$onDelete: compositeOpts.onDelete,
|
|
44
|
+
$onUpdate: compositeOpts.onUpdate,
|
|
45
|
+
$match: compositeOpts.match,
|
|
46
|
+
$deferrable: compositeOpts.deferrable,
|
|
47
|
+
$initiallyDeferred: compositeOpts.initiallyDeferred,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
const singleOpts = options;
|
|
51
|
+
const referencesCol = singleOpts.references;
|
|
52
|
+
return {
|
|
53
|
+
$type: 'foreignKey',
|
|
54
|
+
$targetTable: targetTable.$name,
|
|
55
|
+
$columns: [],
|
|
56
|
+
$references: referencesCol ? [{
|
|
57
|
+
table: referencesCol.$table,
|
|
58
|
+
column: referencesCol.$column,
|
|
59
|
+
}] : undefined,
|
|
60
|
+
$onDelete: singleOpts.onDelete,
|
|
61
|
+
$onUpdate: singleOpts.onUpdate,
|
|
62
|
+
$match: singleOpts.match,
|
|
63
|
+
$deferrable: singleOpts.deferrable,
|
|
64
|
+
$initiallyDeferred: singleOpts.initiallyDeferred,
|
|
65
|
+
};
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
function createSourceColumnRefs(schemaKey, table) {
|
|
71
|
+
const refs = {};
|
|
72
|
+
const actualTableName = table.$name;
|
|
73
|
+
for (const colName of Object.keys(table.$columns)) {
|
|
74
|
+
const colDef = table.$columns[colName];
|
|
75
|
+
const actualColName = colDef.$sqlName || colName;
|
|
76
|
+
refs[colName] = createColumnRef(actualTableName, actualColName, colName);
|
|
77
|
+
}
|
|
78
|
+
return refs;
|
|
79
|
+
}
|
|
80
|
+
function createFullBuilder(schema, currentTableKey) {
|
|
81
|
+
const builder = {
|
|
82
|
+
referenceTo: createReferenceToBuilder(schema, currentTableKey),
|
|
83
|
+
};
|
|
84
|
+
for (const [tableName, table] of Object.entries(schema)) {
|
|
85
|
+
builder[tableName] = createSourceColumnRefs(tableName, table);
|
|
86
|
+
}
|
|
87
|
+
return builder;
|
|
88
|
+
}
|
|
89
|
+
export function pgRelations(schema, builder) {
|
|
90
|
+
const tables = {};
|
|
91
|
+
for (const tableKey of Object.keys(schema)) {
|
|
92
|
+
tables[tableKey] = (defineRelations) => {
|
|
93
|
+
const fullBuilder = createFullBuilder(schema, tableKey);
|
|
94
|
+
return defineRelations(fullBuilder);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return builder(tables);
|
|
98
|
+
}
|
|
99
|
+
export function defineRelations(schema, relationDefs) {
|
|
100
|
+
const result = {};
|
|
101
|
+
for (const [tableKey, defFn] of Object.entries(relationDefs)) {
|
|
102
|
+
if (typeof defFn === 'function') {
|
|
103
|
+
const fullBuilder = createFullBuilder(schema, tableKey);
|
|
104
|
+
result[tableKey] = defFn(fullBuilder);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
export function actionCodeToString(code) {
|
|
110
|
+
switch (code) {
|
|
111
|
+
case 'a': return 'NO ACTION';
|
|
112
|
+
case 'r': return 'RESTRICT';
|
|
113
|
+
case 'c': return 'CASCADE';
|
|
114
|
+
case 'n': return 'SET NULL';
|
|
115
|
+
case 'd': return 'SET DEFAULT';
|
|
116
|
+
default: return 'NO ACTION';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
export function stringToActionCode(action) {
|
|
120
|
+
switch (action) {
|
|
121
|
+
case 'NO ACTION': return 'a';
|
|
122
|
+
case 'RESTRICT': return 'r';
|
|
123
|
+
case 'CASCADE': return 'c';
|
|
124
|
+
case 'SET NULL': return 'n';
|
|
125
|
+
case 'SET DEFAULT': return 'd';
|
|
126
|
+
default: return 'a';
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export function matchCodeToString(code) {
|
|
130
|
+
switch (code) {
|
|
131
|
+
case 'f': return 'FULL';
|
|
132
|
+
case 's':
|
|
133
|
+
default: return 'SIMPLE';
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
export function generateReferencesSQL(relation, columnName, columnType = 'uuid') {
|
|
137
|
+
const parts = [`${columnName} ${columnType}`];
|
|
138
|
+
if (relation.$references && relation.$references.length > 0) {
|
|
139
|
+
const refCols = relation.$references.map(r => r.column).join(', ');
|
|
140
|
+
parts.push(`REFERENCES ${relation.$targetTable}(${refCols})`);
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
parts.push(`REFERENCES ${relation.$targetTable}`);
|
|
144
|
+
}
|
|
145
|
+
if (relation.$onDelete && relation.$onDelete !== 'NO ACTION') {
|
|
146
|
+
parts.push(`ON DELETE ${relation.$onDelete}`);
|
|
147
|
+
}
|
|
148
|
+
if (relation.$onUpdate && relation.$onUpdate !== 'NO ACTION') {
|
|
149
|
+
parts.push(`ON UPDATE ${relation.$onUpdate}`);
|
|
150
|
+
}
|
|
151
|
+
if (relation.$match && relation.$match !== 'SIMPLE') {
|
|
152
|
+
parts.push(`MATCH ${relation.$match}`);
|
|
153
|
+
}
|
|
154
|
+
if (relation.$deferrable) {
|
|
155
|
+
parts.push('DEFERRABLE');
|
|
156
|
+
if (relation.$initiallyDeferred) {
|
|
157
|
+
parts.push('INITIALLY DEFERRED');
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return parts.join(' ');
|
|
161
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function pgView(name, definition) {
|
|
2
|
+
return {
|
|
3
|
+
_type: 'view',
|
|
4
|
+
name,
|
|
5
|
+
definition: definition.trim(),
|
|
6
|
+
isMaterialized: false,
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export function pgMaterializedView(name, definition, options) {
|
|
10
|
+
return {
|
|
11
|
+
_type: 'materialized_view',
|
|
12
|
+
name,
|
|
13
|
+
definition: definition.trim(),
|
|
14
|
+
isMaterialized: true,
|
|
15
|
+
withData: options?.withData,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
export function viewToSQL(view) {
|
|
19
|
+
return `CREATE OR REPLACE VIEW "${view.name}" AS\n${view.definition};`;
|
|
20
|
+
}
|
|
21
|
+
export function materializedViewToSQL(view) {
|
|
22
|
+
const withData = view.withData !== false ? ' WITH DATA' : ' WITH NO DATA';
|
|
23
|
+
return `CREATE MATERIALIZED VIEW IF NOT EXISTS "${view.name}" AS\n${view.definition}${withData};`;
|
|
24
|
+
}
|
|
@@ -37,6 +37,9 @@ function createColumnExpr(colName) {
|
|
|
37
37
|
neq(value) {
|
|
38
38
|
return whereCondition(`${this.$sql} != ${formatWhereValue(value)}`);
|
|
39
39
|
},
|
|
40
|
+
ne(value) {
|
|
41
|
+
return whereCondition(`${this.$sql} <> ${formatWhereValue(value)}`);
|
|
42
|
+
},
|
|
40
43
|
gt(value) {
|
|
41
44
|
return whereCondition(`${this.$sql} > ${formatWhereValue(value)}`);
|
|
42
45
|
},
|
|
@@ -171,6 +174,9 @@ function createCheckExpr(sql) {
|
|
|
171
174
|
neq(value) {
|
|
172
175
|
return createCheckWhereCondition(`${this.$sql} != ${formatVal(value)}`);
|
|
173
176
|
},
|
|
177
|
+
ne(value) {
|
|
178
|
+
return createCheckWhereCondition(`${this.$sql} <> ${formatVal(value)}`);
|
|
179
|
+
},
|
|
174
180
|
isNull() {
|
|
175
181
|
return createCheckWhereCondition(`${this.$sql} IS NULL`);
|
|
176
182
|
},
|
|
@@ -255,6 +261,53 @@ function createCheckConstraintBuilder() {
|
|
|
255
261
|
constraint(name, condition) {
|
|
256
262
|
return { name, expression: condition.$sql };
|
|
257
263
|
},
|
|
264
|
+
raw(expression) {
|
|
265
|
+
return createCheckWhereCondition(expression);
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function createConstraintBuilder() {
|
|
270
|
+
return {
|
|
271
|
+
primaryKey(...args) {
|
|
272
|
+
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && 'columns' in args[0]) {
|
|
273
|
+
const opts = args[0];
|
|
274
|
+
return {
|
|
275
|
+
$type: 'PRIMARY KEY',
|
|
276
|
+
$name: opts.name || '',
|
|
277
|
+
$columns: opts.columns.map(c => String(c)),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
const columns = args;
|
|
281
|
+
return {
|
|
282
|
+
$type: 'PRIMARY KEY',
|
|
283
|
+
$name: '',
|
|
284
|
+
$columns: columns.map(c => String(c)),
|
|
285
|
+
};
|
|
286
|
+
},
|
|
287
|
+
unique(...args) {
|
|
288
|
+
if (args.length === 1 && typeof args[0] === 'object' && args[0] !== null && 'columns' in args[0]) {
|
|
289
|
+
const opts = args[0];
|
|
290
|
+
return {
|
|
291
|
+
$type: 'UNIQUE',
|
|
292
|
+
$name: opts.name || '',
|
|
293
|
+
$columns: opts.columns.map(c => String(c)),
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
const columns = args;
|
|
297
|
+
return {
|
|
298
|
+
$type: 'UNIQUE',
|
|
299
|
+
$name: '',
|
|
300
|
+
$columns: columns.map(c => String(c)),
|
|
301
|
+
};
|
|
302
|
+
},
|
|
303
|
+
exclude(name, expression) {
|
|
304
|
+
return {
|
|
305
|
+
$type: 'EXCLUDE',
|
|
306
|
+
$name: name,
|
|
307
|
+
$columns: [],
|
|
308
|
+
$expression: expression,
|
|
309
|
+
};
|
|
310
|
+
},
|
|
258
311
|
};
|
|
259
312
|
}
|
|
260
313
|
function createColumnRefs(columns) {
|
|
@@ -266,6 +319,15 @@ function createColumnRefs(columns) {
|
|
|
266
319
|
}
|
|
267
320
|
return refs;
|
|
268
321
|
}
|
|
322
|
+
function createIndexTableRefs(columns) {
|
|
323
|
+
const refs = {};
|
|
324
|
+
for (const key of Object.keys(columns)) {
|
|
325
|
+
const col = columns[key];
|
|
326
|
+
const colName = col.$columnName || key;
|
|
327
|
+
refs[key] = createColumnExpr(colName);
|
|
328
|
+
}
|
|
329
|
+
return refs;
|
|
330
|
+
}
|
|
269
331
|
function createIndexFactory() {
|
|
270
332
|
const factory = (name) => {
|
|
271
333
|
const def = {
|
|
@@ -325,6 +387,22 @@ function createIndexFactory() {
|
|
|
325
387
|
def._opclass = opclass;
|
|
326
388
|
return Object.assign(this, { _opclass: opclass });
|
|
327
389
|
},
|
|
390
|
+
ifNotExists() {
|
|
391
|
+
def.ifNotExists = true;
|
|
392
|
+
return Object.assign(this, { ifNotExists: true });
|
|
393
|
+
},
|
|
394
|
+
tableOnly() {
|
|
395
|
+
def.tableOnly = true;
|
|
396
|
+
return Object.assign(this, { tableOnly: true });
|
|
397
|
+
},
|
|
398
|
+
comment(text) {
|
|
399
|
+
def.comment = text;
|
|
400
|
+
return Object.assign(this, { comment: text });
|
|
401
|
+
},
|
|
402
|
+
$id(trackingId) {
|
|
403
|
+
def.trackingId = trackingId;
|
|
404
|
+
return Object.assign(this, { trackingId: trackingId });
|
|
405
|
+
},
|
|
328
406
|
};
|
|
329
407
|
return {
|
|
330
408
|
on(...columns) {
|
|
@@ -348,10 +426,11 @@ function createIndexFactory() {
|
|
|
348
426
|
const indexFactory = createIndexFactory();
|
|
349
427
|
export function defineTable(name, columns, options) {
|
|
350
428
|
const columnRefs = createColumnRefs(columns);
|
|
429
|
+
const indexTableRefs = createIndexTableRefs(columns);
|
|
351
430
|
let resolvedIndexes;
|
|
352
431
|
if (options?.indexes) {
|
|
353
432
|
if (typeof options.indexes === 'function') {
|
|
354
|
-
const rawIndexes = options.indexes(
|
|
433
|
+
const rawIndexes = options.indexes(indexTableRefs, indexFactory, sqlFunctions);
|
|
355
434
|
resolvedIndexes = rawIndexes.map(normalizeIndexDef);
|
|
356
435
|
}
|
|
357
436
|
else {
|
|
@@ -369,6 +448,11 @@ export function defineTable(name, columns, options) {
|
|
|
369
448
|
const constraints = options.checkConstraints(checkTableRefs, checkBuilder);
|
|
370
449
|
resolvedCheckConstraints = constraints.map(c => ({ expression: c.expression, name: c.name }));
|
|
371
450
|
}
|
|
451
|
+
let resolvedConstraints;
|
|
452
|
+
if (options?.constraints) {
|
|
453
|
+
const constraintBuilder = createConstraintBuilder();
|
|
454
|
+
resolvedConstraints = options.constraints(columnRefs, constraintBuilder);
|
|
455
|
+
}
|
|
372
456
|
const definition = {
|
|
373
457
|
$name: name,
|
|
374
458
|
$schema: options?.schema,
|
|
@@ -376,12 +460,14 @@ export function defineTable(name, columns, options) {
|
|
|
376
460
|
$primaryKey: options?.primaryKey,
|
|
377
461
|
$uniqueConstraints: options?.uniqueConstraints,
|
|
378
462
|
$checkConstraints: resolvedCheckConstraints,
|
|
463
|
+
$constraints: resolvedConstraints,
|
|
379
464
|
$foreignKeys: options?.foreignKeys,
|
|
380
465
|
$indexes: resolvedIndexes,
|
|
381
466
|
$inherits: options?.inherits,
|
|
382
467
|
$partitionBy: resolvedPartitionBy,
|
|
383
468
|
$tablespace: options?.tablespace,
|
|
384
469
|
$withOptions: options?.withOptions,
|
|
470
|
+
$ifNotExists: options?.ifNotExists,
|
|
385
471
|
$inferSelect: {},
|
|
386
472
|
$inferInsert: {},
|
|
387
473
|
toSQL() {
|
|
@@ -410,13 +496,16 @@ function normalizeIndexDef(idx) {
|
|
|
410
496
|
nulls: idx.nulls,
|
|
411
497
|
order: idx.order,
|
|
412
498
|
include: idx.include,
|
|
499
|
+
ifNotExists: idx.ifNotExists,
|
|
500
|
+
tableOnly: idx.tableOnly,
|
|
413
501
|
};
|
|
414
502
|
}
|
|
415
503
|
function generateCreateTableSQL(def) {
|
|
416
504
|
const tableName = def.$schema
|
|
417
505
|
? `${format.ident(def.$schema)}.${format.ident(def.$name)}`
|
|
418
506
|
: format.ident(def.$name);
|
|
419
|
-
const
|
|
507
|
+
const ifNotExistsClause = def.$ifNotExists ? 'IF NOT EXISTS ' : '';
|
|
508
|
+
const parts = [`CREATE TABLE ${ifNotExistsClause}${tableName} (`];
|
|
420
509
|
const columnDefs = [];
|
|
421
510
|
const constraints = [];
|
|
422
511
|
for (const [colName, colConfig] of Object.entries(def.$columns)) {
|
|
@@ -450,6 +539,12 @@ function generateCreateTableSQL(def) {
|
|
|
450
539
|
}
|
|
451
540
|
}
|
|
452
541
|
}
|
|
542
|
+
if (def.$constraints) {
|
|
543
|
+
for (const c of def.$constraints) {
|
|
544
|
+
const cols = c.$columns.map(col => format.ident(col)).join(', ');
|
|
545
|
+
constraints.push(`CONSTRAINT ${format.ident(c.$name)} ${c.$type} (${cols})`);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
453
548
|
if (def.$foreignKeys) {
|
|
454
549
|
for (const fk of def.$foreignKeys) {
|
|
455
550
|
const cols = fk.columns.map(c => format.ident(c)).join(', ');
|
|
@@ -511,7 +606,10 @@ function generateColumnSQL(name, config) {
|
|
|
511
606
|
const defaultVal = typeof config.$default === 'function'
|
|
512
607
|
? config.$default()
|
|
513
608
|
: config.$default;
|
|
514
|
-
if (typeof defaultVal === '
|
|
609
|
+
if (typeof defaultVal === 'object' && defaultVal !== null && '$isDefault' in defaultVal && '$sql' in defaultVal) {
|
|
610
|
+
parts.push(`DEFAULT ${defaultVal.$sql}`);
|
|
611
|
+
}
|
|
612
|
+
else if (typeof defaultVal === 'symbol') {
|
|
515
613
|
const symDesc = defaultVal.description || String(defaultVal);
|
|
516
614
|
if (symDesc.includes('emptyObject')) {
|
|
517
615
|
parts.push(`DEFAULT '{}'::jsonb`);
|
|
@@ -587,7 +685,15 @@ function generateIndexSQL(def) {
|
|
|
587
685
|
if (idx.unique) {
|
|
588
686
|
sql += ' UNIQUE';
|
|
589
687
|
}
|
|
590
|
-
sql +=
|
|
688
|
+
sql += ' INDEX';
|
|
689
|
+
if (idx.ifNotExists) {
|
|
690
|
+
sql += ' IF NOT EXISTS';
|
|
691
|
+
}
|
|
692
|
+
sql += ` ${format.ident(indexName)} ON`;
|
|
693
|
+
if (idx.tableOnly) {
|
|
694
|
+
sql += ' ONLY';
|
|
695
|
+
}
|
|
696
|
+
sql += ` ${tableName}`;
|
|
591
697
|
if (idx.using) {
|
|
592
698
|
sql += ` USING ${idx.using}`;
|
|
593
699
|
}
|
|
@@ -6,18 +6,23 @@ export function toPoolConfig(config) {
|
|
|
6
6
|
const smartDefaults = config.disableSmartDefaults
|
|
7
7
|
? { min: 0, max: 10, idleTimeoutMillis: 30000, connectionTimeoutMillis: 0 }
|
|
8
8
|
: mergeWithDefaults(config.pool);
|
|
9
|
+
const isAws = !!config.aws;
|
|
10
|
+
const host = isAws ? config.aws.hostname : (config.host || 'localhost');
|
|
11
|
+
const port = config.aws?.port ?? config.port ?? 5432;
|
|
12
|
+
const user = config.aws?.user ?? config.user ?? (isAws ? 'admin' : undefined);
|
|
13
|
+
const ssl = isAws ? (config.aws.ssl ?? true) : config.ssl;
|
|
9
14
|
const poolConfig = {
|
|
10
|
-
host
|
|
11
|
-
port
|
|
15
|
+
host,
|
|
16
|
+
port,
|
|
12
17
|
database: config.database,
|
|
13
|
-
user
|
|
18
|
+
user,
|
|
14
19
|
password: config.password,
|
|
15
20
|
min: config.pool?.min ?? smartDefaults.min,
|
|
16
21
|
max: config.pool?.max ?? smartDefaults.max,
|
|
17
22
|
idleTimeoutMillis: config.pool?.idleTimeoutMillis ?? smartDefaults.idleTimeoutMillis,
|
|
18
23
|
connectionTimeoutMillis: config.pool?.connectionTimeoutMillis ?? smartDefaults.connectionTimeoutMillis,
|
|
19
24
|
application_name: config.pool?.application_name,
|
|
20
|
-
ssl: config.pool?.ssl,
|
|
25
|
+
ssl: config.pool?.ssl ?? ssl,
|
|
21
26
|
allowExitOnIdle: true
|
|
22
27
|
};
|
|
23
28
|
if (config.connectionString) {
|
|
@@ -34,3 +39,6 @@ export function toPoolConfig(config) {
|
|
|
34
39
|
}
|
|
35
40
|
return poolConfig;
|
|
36
41
|
}
|
|
42
|
+
export function isAwsDsqlConfig(config) {
|
|
43
|
+
return !!config.aws?.hostname && !!config.aws?.region;
|
|
44
|
+
}
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, accessSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { createHash } from 'node:crypto';
|
|
5
|
+
import { F_OK, W_OK, R_OK } from 'node:constants';
|
|
6
|
+
import { RelqConfigError } from "../errors/relq-errors.js";
|
|
7
|
+
const tokenCache = new Map();
|
|
8
|
+
function getCacheKey(config) {
|
|
9
|
+
const hash = createHash('md5')
|
|
10
|
+
.update(`${config.secretAccessKey ?? ''}-${config.accessKeyId ?? ''}-${config.region}-${config.hostname}`)
|
|
11
|
+
.digest('hex');
|
|
12
|
+
return hash;
|
|
13
|
+
}
|
|
14
|
+
function getTempFolder() {
|
|
15
|
+
return join(tmpdir(), '.dsql_');
|
|
16
|
+
}
|
|
17
|
+
function isTempFolderAvailable() {
|
|
18
|
+
const tempFolder = getTempFolder();
|
|
19
|
+
try {
|
|
20
|
+
mkdirSync(tempFolder, { recursive: true });
|
|
21
|
+
accessSync(tempFolder, F_OK | R_OK | W_OK);
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function getFromCache(cacheKey) {
|
|
29
|
+
const memoryToken = tokenCache.get(cacheKey);
|
|
30
|
+
if (memoryToken && memoryToken.expiresAt > Date.now()) {
|
|
31
|
+
return memoryToken;
|
|
32
|
+
}
|
|
33
|
+
const envName = `DSQL_TOKEN_${cacheKey}`;
|
|
34
|
+
const envToken = process.env[envName];
|
|
35
|
+
if (envToken) {
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(envToken);
|
|
38
|
+
if (parsed.expiresAt > Date.now()) {
|
|
39
|
+
tokenCache.set(cacheKey, parsed);
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
44
|
+
}
|
|
45
|
+
if (isTempFolderAvailable()) {
|
|
46
|
+
const tokenFile = join(getTempFolder(), `${cacheKey}.json`);
|
|
47
|
+
if (existsSync(tokenFile)) {
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(readFileSync(tokenFile, 'utf8'));
|
|
50
|
+
if (parsed.expiresAt > Date.now()) {
|
|
51
|
+
tokenCache.set(cacheKey, parsed);
|
|
52
|
+
process.env[envName] = JSON.stringify(parsed);
|
|
53
|
+
return parsed;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch { }
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
function saveToCache(cacheKey, token) {
|
|
62
|
+
tokenCache.set(cacheKey, token);
|
|
63
|
+
const envName = `DSQL_TOKEN_${cacheKey}`;
|
|
64
|
+
process.env[envName] = JSON.stringify(token);
|
|
65
|
+
if (isTempFolderAvailable()) {
|
|
66
|
+
const tokenFile = join(getTempFolder(), `${cacheKey}.json`);
|
|
67
|
+
try {
|
|
68
|
+
writeFileSync(tokenFile, JSON.stringify(token));
|
|
69
|
+
}
|
|
70
|
+
catch { }
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
let DsqlSigner = null;
|
|
74
|
+
async function loadAwsSdk() {
|
|
75
|
+
if (!DsqlSigner) {
|
|
76
|
+
try {
|
|
77
|
+
const sdk = await import('@aws-sdk/dsql-signer');
|
|
78
|
+
DsqlSigner = sdk.DsqlSigner;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
throw new RelqConfigError('AWS DSQL requires @aws-sdk/dsql-signer package.\n\n' +
|
|
82
|
+
'Install it with:\n' +
|
|
83
|
+
' npm install @aws-sdk/dsql-signer\n' +
|
|
84
|
+
' # or\n' +
|
|
85
|
+
' bun add @aws-sdk/dsql-signer', { field: '@aws-sdk/dsql-signer', value: 'not installed' });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return DsqlSigner;
|
|
89
|
+
}
|
|
90
|
+
export async function getAwsDsqlToken(config) {
|
|
91
|
+
const cacheKey = getCacheKey(config);
|
|
92
|
+
const cached = getFromCache(cacheKey);
|
|
93
|
+
if (cached) {
|
|
94
|
+
return cached.token;
|
|
95
|
+
}
|
|
96
|
+
if (!config.useDefaultCredentials && (!config.accessKeyId || !config.secretAccessKey)) {
|
|
97
|
+
throw new RelqConfigError('AWS DSQL requires credentials. Either provide accessKeyId + secretAccessKey, ' +
|
|
98
|
+
'or set useDefaultCredentials: true to use AWS credential chain.', { field: 'aws.credentials', value: 'missing' });
|
|
99
|
+
}
|
|
100
|
+
const SignerClass = await loadAwsSdk();
|
|
101
|
+
const expiresIn = config.tokenExpiresIn ?? 604800;
|
|
102
|
+
const signerConfig = {
|
|
103
|
+
hostname: config.hostname,
|
|
104
|
+
region: config.region,
|
|
105
|
+
expiresIn,
|
|
106
|
+
};
|
|
107
|
+
if (!config.useDefaultCredentials) {
|
|
108
|
+
signerConfig.credentials = {
|
|
109
|
+
accessKeyId: config.accessKeyId,
|
|
110
|
+
secretAccessKey: config.secretAccessKey,
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
const signer = new SignerClass(signerConfig);
|
|
114
|
+
const token = await signer.getDbConnectAdminAuthToken();
|
|
115
|
+
const cachedToken = {
|
|
116
|
+
token,
|
|
117
|
+
expiresAt: Date.now() + ((expiresIn - 30) * 1000)
|
|
118
|
+
};
|
|
119
|
+
saveToCache(cacheKey, cachedToken);
|
|
120
|
+
return token;
|
|
121
|
+
}
|
|
122
|
+
export function clearAwsDsqlToken(config) {
|
|
123
|
+
const cacheKey = getCacheKey(config);
|
|
124
|
+
tokenCache.delete(cacheKey);
|
|
125
|
+
const envName = `DSQL_TOKEN_${cacheKey}`;
|
|
126
|
+
delete process.env[envName];
|
|
127
|
+
if (isTempFolderAvailable()) {
|
|
128
|
+
const tokenFile = join(getTempFolder(), `${cacheKey}.json`);
|
|
129
|
+
try {
|
|
130
|
+
if (existsSync(tokenFile)) {
|
|
131
|
+
writeFileSync(tokenFile, '');
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
catch { }
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
export function isAwsDsql(config) {
|
|
138
|
+
return !!config.aws?.hostname && !!config.aws?.region;
|
|
139
|
+
}
|