relq 1.0.5 → 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.
Files changed (84) hide show
  1. package/dist/cjs/cli/commands/add.cjs +252 -12
  2. package/dist/cjs/cli/commands/commit.cjs +12 -1
  3. package/dist/cjs/cli/commands/export.cjs +25 -19
  4. package/dist/cjs/cli/commands/import.cjs +219 -100
  5. package/dist/cjs/cli/commands/init.cjs +86 -14
  6. package/dist/cjs/cli/commands/pull.cjs +104 -23
  7. package/dist/cjs/cli/commands/push.cjs +38 -3
  8. package/dist/cjs/cli/index.cjs +9 -1
  9. package/dist/cjs/cli/utils/ast/codegen/builder.cjs +297 -0
  10. package/dist/cjs/cli/utils/ast/codegen/constraints.cjs +185 -0
  11. package/dist/cjs/cli/utils/ast/codegen/defaults.cjs +311 -0
  12. package/dist/cjs/cli/utils/ast/codegen/index.cjs +24 -0
  13. package/dist/cjs/cli/utils/ast/codegen/type-map.cjs +116 -0
  14. package/dist/cjs/cli/utils/ast/codegen/utils.cjs +69 -0
  15. package/dist/cjs/cli/utils/ast/index.cjs +19 -0
  16. package/dist/cjs/cli/utils/ast/transformer/helpers.cjs +154 -0
  17. package/dist/cjs/cli/utils/ast/transformer/index.cjs +25 -0
  18. package/dist/cjs/cli/utils/ast/types.cjs +2 -0
  19. package/dist/cjs/cli/utils/ast-codegen.cjs +949 -0
  20. package/dist/cjs/cli/utils/ast-transformer.cjs +916 -0
  21. package/dist/cjs/cli/utils/change-tracker.cjs +50 -1
  22. package/dist/cjs/cli/utils/cli-utils.cjs +151 -0
  23. package/dist/cjs/cli/utils/fast-introspect.cjs +149 -23
  24. package/dist/cjs/cli/utils/pg-parser.cjs +1 -0
  25. package/dist/cjs/cli/utils/repo-manager.cjs +121 -4
  26. package/dist/cjs/cli/utils/schema-comparator.cjs +98 -14
  27. package/dist/cjs/cli/utils/schema-introspect.cjs +56 -19
  28. package/dist/cjs/cli/utils/snapshot-manager.cjs +0 -1
  29. package/dist/cjs/cli/utils/sql-generator.cjs +353 -64
  30. package/dist/cjs/cli/utils/type-generator.cjs +114 -15
  31. package/dist/cjs/core/relq-client.cjs +22 -6
  32. package/dist/cjs/schema-definition/column-types.cjs +149 -13
  33. package/dist/cjs/schema-definition/defaults.cjs +72 -0
  34. package/dist/cjs/schema-definition/index.cjs +15 -1
  35. package/dist/cjs/schema-definition/introspection.cjs +7 -3
  36. package/dist/cjs/schema-definition/pg-relations.cjs +169 -0
  37. package/dist/cjs/schema-definition/pg-view.cjs +30 -0
  38. package/dist/cjs/schema-definition/table-definition.cjs +110 -4
  39. package/dist/cjs/types/config-types.cjs +13 -4
  40. package/dist/cjs/utils/aws-dsql.cjs +177 -0
  41. package/dist/config.d.ts +146 -1
  42. package/dist/esm/cli/commands/add.js +250 -13
  43. package/dist/esm/cli/commands/commit.js +12 -1
  44. package/dist/esm/cli/commands/export.js +25 -19
  45. package/dist/esm/cli/commands/import.js +221 -102
  46. package/dist/esm/cli/commands/init.js +86 -14
  47. package/dist/esm/cli/commands/pull.js +106 -25
  48. package/dist/esm/cli/commands/push.js +39 -4
  49. package/dist/esm/cli/index.js +9 -1
  50. package/dist/esm/cli/utils/ast/codegen/builder.js +291 -0
  51. package/dist/esm/cli/utils/ast/codegen/constraints.js +176 -0
  52. package/dist/esm/cli/utils/ast/codegen/defaults.js +305 -0
  53. package/dist/esm/cli/utils/ast/codegen/index.js +6 -0
  54. package/dist/esm/cli/utils/ast/codegen/type-map.js +111 -0
  55. package/dist/esm/cli/utils/ast/codegen/utils.js +60 -0
  56. package/dist/esm/cli/utils/ast/index.js +3 -0
  57. package/dist/esm/cli/utils/ast/transformer/helpers.js +141 -0
  58. package/dist/esm/cli/utils/ast/transformer/index.js +2 -0
  59. package/dist/esm/cli/utils/ast/types.js +1 -0
  60. package/dist/esm/cli/utils/ast-codegen.js +945 -0
  61. package/dist/esm/cli/utils/ast-transformer.js +907 -0
  62. package/dist/esm/cli/utils/change-tracker.js +50 -1
  63. package/dist/esm/cli/utils/cli-utils.js +147 -0
  64. package/dist/esm/cli/utils/fast-introspect.js +149 -23
  65. package/dist/esm/cli/utils/pg-parser.js +1 -0
  66. package/dist/esm/cli/utils/repo-manager.js +114 -4
  67. package/dist/esm/cli/utils/schema-comparator.js +98 -14
  68. package/dist/esm/cli/utils/schema-introspect.js +56 -19
  69. package/dist/esm/cli/utils/snapshot-manager.js +0 -1
  70. package/dist/esm/cli/utils/sql-generator.js +353 -64
  71. package/dist/esm/cli/utils/type-generator.js +114 -15
  72. package/dist/esm/core/relq-client.js +23 -7
  73. package/dist/esm/schema-definition/column-types.js +146 -12
  74. package/dist/esm/schema-definition/defaults.js +69 -0
  75. package/dist/esm/schema-definition/index.js +3 -0
  76. package/dist/esm/schema-definition/introspection.js +7 -3
  77. package/dist/esm/schema-definition/pg-relations.js +161 -0
  78. package/dist/esm/schema-definition/pg-view.js +24 -0
  79. package/dist/esm/schema-definition/table-definition.js +110 -4
  80. package/dist/esm/types/config-types.js +12 -4
  81. package/dist/esm/utils/aws-dsql.js +139 -0
  82. package/dist/index.d.ts +159 -1
  83. package/dist/schema-builder.d.ts +1314 -32
  84. 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(columnRefs, indexFactory, sqlFunctions);
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 parts = [`CREATE TABLE ${tableName} (`];
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 === 'symbol') {
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 += ` INDEX ${format.ident(indexName)} ON ${tableName}`;
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: config.host || 'localhost',
11
- port: config.port || 5432,
15
+ host,
16
+ port,
12
17
  database: config.database,
13
- user: config.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
+ }