@plyaz/db 0.1.1 → 0.2.0
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/adapters/sql/SQLAdapter.d.ts +1 -0
- package/dist/adapters/sql/SQLAdapter.d.ts.map +1 -1
- package/dist/cli/index.js +826 -45
- package/dist/cli/index.js.map +1 -1
- package/dist/index.cjs +300 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +300 -18
- package/dist/index.mjs.map +1 -1
- package/dist/migrations/MigrationManager.d.ts +22 -0
- package/dist/migrations/MigrationManager.d.ts.map +1 -1
- package/dist/seeds/SeedManager.d.ts +25 -1
- package/dist/seeds/SeedManager.d.ts.map +1 -1
- package/package.json +5 -5
package/dist/cli/index.js
CHANGED
|
@@ -193,6 +193,10 @@ function failure(error) {
|
|
|
193
193
|
return { success: false, error };
|
|
194
194
|
}
|
|
195
195
|
__name(failure, "failure");
|
|
196
|
+
var DESCRIPTION_MAX_LENGTH = 60;
|
|
197
|
+
var FALLBACK_DESCRIPTION_LENGTH = 50;
|
|
198
|
+
var PROGRESS_LOG_INTERVAL = 10;
|
|
199
|
+
var ERROR_MESSAGE_MAX_LENGTH = 300;
|
|
196
200
|
var MigrationManager = class {
|
|
197
201
|
static {
|
|
198
202
|
__name(this, "MigrationManager");
|
|
@@ -302,6 +306,116 @@ var MigrationManager = class {
|
|
|
302
306
|
}
|
|
303
307
|
return { upSQL: sql2.trim(), downSQL: null };
|
|
304
308
|
}
|
|
309
|
+
/**
|
|
310
|
+
* Process dollar-quoted string delimiters ($$ or $tag$)
|
|
311
|
+
* Returns updated state for tracking if we're inside a dollar block
|
|
312
|
+
*/
|
|
313
|
+
processDollarDelimiters(line, inDollarBlock, dollarTag) {
|
|
314
|
+
const dollarMatch = line.match(/\$([a-zA-Z_]*)\$/g);
|
|
315
|
+
if (!dollarMatch) return { inDollarBlock, dollarTag };
|
|
316
|
+
let currentInBlock = inDollarBlock;
|
|
317
|
+
let currentTag = dollarTag;
|
|
318
|
+
for (const match of dollarMatch) {
|
|
319
|
+
if (!currentInBlock) {
|
|
320
|
+
currentInBlock = true;
|
|
321
|
+
currentTag = match;
|
|
322
|
+
} else if (match === currentTag) {
|
|
323
|
+
currentInBlock = false;
|
|
324
|
+
currentTag = "";
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return { inDollarBlock: currentInBlock, dollarTag: currentTag };
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Filter out comment-only statements
|
|
331
|
+
*/
|
|
332
|
+
isNonCommentStatement(statement) {
|
|
333
|
+
const withoutComments = statement.replace(/--.*$/gm, "").trim();
|
|
334
|
+
return withoutComments.length > 0;
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Split SQL into individual statements for better error reporting
|
|
338
|
+
* Handles $$ delimited blocks (functions, triggers) correctly
|
|
339
|
+
*/
|
|
340
|
+
splitSqlStatements(sql2) {
|
|
341
|
+
const statements = [];
|
|
342
|
+
let current = "";
|
|
343
|
+
let inDollarBlock = false;
|
|
344
|
+
let dollarTag = "";
|
|
345
|
+
for (const line of sql2.split("\n")) {
|
|
346
|
+
const trimmedLine = line.trim();
|
|
347
|
+
const isEmptyOrComment = trimmedLine === "" || trimmedLine.startsWith("--");
|
|
348
|
+
current += line + "\n";
|
|
349
|
+
if (isEmptyOrComment) continue;
|
|
350
|
+
const dollarState = this.processDollarDelimiters(line, inDollarBlock, dollarTag);
|
|
351
|
+
inDollarBlock = dollarState.inDollarBlock;
|
|
352
|
+
dollarTag = dollarState.dollarTag;
|
|
353
|
+
const isEndOfStatement = !inDollarBlock && trimmedLine.endsWith(";");
|
|
354
|
+
if (isEndOfStatement && current.trim()) {
|
|
355
|
+
statements.push(current.trim());
|
|
356
|
+
current = "";
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
if (current.trim()) {
|
|
360
|
+
statements.push(current.trim());
|
|
361
|
+
}
|
|
362
|
+
return statements.filter((s) => this.isNonCommentStatement(s));
|
|
363
|
+
}
|
|
364
|
+
/**
|
|
365
|
+
* Extract a short description from a SQL statement for logging
|
|
366
|
+
*/
|
|
367
|
+
getStatementDescription(statement) {
|
|
368
|
+
const firstLine = statement.split("\n").find((l) => l.trim() && !l.trim().startsWith("--"))?.trim() ?? "";
|
|
369
|
+
const patterns = [
|
|
370
|
+
/^(CREATE\s+(?:OR\s+REPLACE\s+)?(?:TABLE|INDEX|UNIQUE\s+INDEX|TYPE|FUNCTION|TRIGGER|EXTENSION|SCHEMA|VIEW|POLICY))\s+(?:IF\s+NOT\s+EXISTS\s+)?([^\s(]+)/i,
|
|
371
|
+
/^(ALTER\s+TABLE)\s+([^\s]+)/i,
|
|
372
|
+
/^(DROP\s+(?:TABLE|INDEX|TYPE|FUNCTION|TRIGGER|EXTENSION|SCHEMA|VIEW|POLICY))\s+(?:IF\s+EXISTS\s+)?([^\s(;]+)/i,
|
|
373
|
+
/^(INSERT\s+INTO)\s+([^\s(]+)/i,
|
|
374
|
+
/^(COMMENT\s+ON\s+(?:TABLE|COLUMN|INDEX|FUNCTION|TYPE))\s+([^\s]+)/i,
|
|
375
|
+
/^(GRANT|REVOKE)\s+.+\s+ON\s+([^\s]+)/i
|
|
376
|
+
];
|
|
377
|
+
for (const pattern of patterns) {
|
|
378
|
+
const match = firstLine.match(pattern);
|
|
379
|
+
if (match) {
|
|
380
|
+
return `${match[1]} ${match[2]}`.slice(0, DESCRIPTION_MAX_LENGTH);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
const truncated = firstLine.slice(0, FALLBACK_DESCRIPTION_LENGTH);
|
|
384
|
+
const suffix = firstLine.length > FALLBACK_DESCRIPTION_LENGTH ? "..." : "";
|
|
385
|
+
return truncated + suffix;
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Execute SQL statements individually with better error reporting
|
|
389
|
+
*/
|
|
390
|
+
async executeSqlStatements(adapter, sql2, migrationVersion) {
|
|
391
|
+
const statements = this.splitSqlStatements(sql2);
|
|
392
|
+
const total = statements.length;
|
|
393
|
+
console.log(` → ${total} statements to execute`);
|
|
394
|
+
for (let i = 0; i < statements.length; i++) {
|
|
395
|
+
const statement = statements[i];
|
|
396
|
+
const description = this.getStatementDescription(statement);
|
|
397
|
+
try {
|
|
398
|
+
await adapter.query(statement);
|
|
399
|
+
const isInterval = (i + 1) % PROGRESS_LOG_INTERVAL === 0;
|
|
400
|
+
const isLast = i === total - 1;
|
|
401
|
+
const isSignificant = Boolean(description.match(/^(CREATE TABLE|CREATE FUNCTION|CREATE TRIGGER)/i));
|
|
402
|
+
if (isInterval || isLast || isSignificant) {
|
|
403
|
+
console.log(` ✓ [${i + 1}/${total}] ${description}`);
|
|
404
|
+
}
|
|
405
|
+
} catch (error) {
|
|
406
|
+
console.log(` ✗ [${i + 1}/${total}] ${description}`);
|
|
407
|
+
const rawMessage = error.message;
|
|
408
|
+
const errorMessage = rawMessage.replace(/^SQL Error:\s*/i, "").replace(/^Failed to execute query:.*?-\s*/i, "").slice(0, ERROR_MESSAGE_MAX_LENGTH);
|
|
409
|
+
throw new DatabaseError(
|
|
410
|
+
`Migration ${migrationVersion} failed at statement ${i + 1}/${total}:
|
|
411
|
+
Statement: ${description}
|
|
412
|
+
Error: ${errorMessage}`,
|
|
413
|
+
DATABASE_ERROR_CODES.QUERY_FAILED,
|
|
414
|
+
{ cause: error }
|
|
415
|
+
);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
305
419
|
/**
|
|
306
420
|
* Load SQL migration from file
|
|
307
421
|
*/
|
|
@@ -313,12 +427,20 @@ var MigrationManager = class {
|
|
|
313
427
|
name: migrationFile.name,
|
|
314
428
|
up: /* @__PURE__ */ __name(async (adapter) => {
|
|
315
429
|
if (typeof adapter.query === "function") {
|
|
316
|
-
await
|
|
430
|
+
await this.executeSqlStatements(
|
|
431
|
+
adapter,
|
|
432
|
+
upSQL,
|
|
433
|
+
migrationFile.version
|
|
434
|
+
);
|
|
317
435
|
}
|
|
318
436
|
}, "up"),
|
|
319
437
|
down: /* @__PURE__ */ __name(async (adapter) => {
|
|
320
438
|
if (downSQL && typeof adapter.query === "function") {
|
|
321
|
-
await
|
|
439
|
+
await this.executeSqlStatements(
|
|
440
|
+
adapter,
|
|
441
|
+
downSQL,
|
|
442
|
+
migrationFile.version
|
|
443
|
+
);
|
|
322
444
|
} else {
|
|
323
445
|
console.warn(
|
|
324
446
|
`[Migrations] No DOWN migration for ${migrationFile.version}`
|
|
@@ -425,6 +547,7 @@ var MigrationManager = class {
|
|
|
425
547
|
/**
|
|
426
548
|
* Run all pending migrations
|
|
427
549
|
*/
|
|
550
|
+
/* eslint-disable max-depth, complexity */
|
|
428
551
|
async up(targetVersion) {
|
|
429
552
|
try {
|
|
430
553
|
await this.initialize();
|
|
@@ -445,9 +568,15 @@ var MigrationManager = class {
|
|
|
445
568
|
const migration = await this.loadMigration(migrationFile);
|
|
446
569
|
const startTime = Date.now();
|
|
447
570
|
if (typeof this.adapter.transaction === "function") {
|
|
448
|
-
await this.adapter.transaction(async () => {
|
|
571
|
+
const txResult = await this.adapter.transaction(async () => {
|
|
449
572
|
await migration.up(this.adapter);
|
|
450
573
|
});
|
|
574
|
+
if (!txResult.success) {
|
|
575
|
+
throw txResult.error ?? new DatabaseError(
|
|
576
|
+
`Migration ${migration.version} failed`,
|
|
577
|
+
DATABASE_ERROR_CODES.QUERY_FAILED
|
|
578
|
+
);
|
|
579
|
+
}
|
|
451
580
|
} else {
|
|
452
581
|
await migration.up(this.adapter);
|
|
453
582
|
}
|
|
@@ -502,9 +631,15 @@ var MigrationManager = class {
|
|
|
502
631
|
const migration = await this.loadMigration(migrationFile);
|
|
503
632
|
const startTime = Date.now();
|
|
504
633
|
if (typeof this.adapter.transaction === "function") {
|
|
505
|
-
await this.adapter.transaction(async () => {
|
|
634
|
+
const txResult = await this.adapter.transaction(async () => {
|
|
506
635
|
await migration.down(this.adapter);
|
|
507
636
|
});
|
|
637
|
+
if (!txResult.success) {
|
|
638
|
+
throw txResult.error ?? new DatabaseError(
|
|
639
|
+
`Rollback ${appliedMigration.version} failed`,
|
|
640
|
+
DATABASE_ERROR_CODES.QUERY_FAILED
|
|
641
|
+
);
|
|
642
|
+
}
|
|
508
643
|
} else {
|
|
509
644
|
await migration.down(this.adapter);
|
|
510
645
|
}
|
|
@@ -563,6 +698,10 @@ var MigrationManager = class {
|
|
|
563
698
|
}
|
|
564
699
|
}
|
|
565
700
|
};
|
|
701
|
+
var DESCRIPTION_MAX_LENGTH2 = 60;
|
|
702
|
+
var FALLBACK_DESCRIPTION_LENGTH2 = 50;
|
|
703
|
+
var PROGRESS_LOG_INTERVAL2 = 10;
|
|
704
|
+
var ERROR_MESSAGE_MAX_LENGTH2 = 300;
|
|
566
705
|
var SeedManager = class {
|
|
567
706
|
static {
|
|
568
707
|
__name(this, "SeedManager");
|
|
@@ -631,7 +770,7 @@ var SeedManager = class {
|
|
|
631
770
|
const files = fs3.readdirSync(this.seedsPath);
|
|
632
771
|
const seeds = [];
|
|
633
772
|
for (const file of files) {
|
|
634
|
-
const match = file.match(/^(\d+)_(.+)\.(ts|js)$/);
|
|
773
|
+
const match = file.match(/^(\d+)_(.+)\.(ts|js|sql)$/);
|
|
635
774
|
if (match) {
|
|
636
775
|
const [, order, name] = match;
|
|
637
776
|
seeds.push({
|
|
@@ -644,9 +783,137 @@ var SeedManager = class {
|
|
|
644
783
|
return seeds.sort((a, b) => a.order - b.order);
|
|
645
784
|
}
|
|
646
785
|
/**
|
|
647
|
-
*
|
|
786
|
+
* Process dollar-quoted string delimiters ($$ or $tag$)
|
|
787
|
+
*/
|
|
788
|
+
processDollarDelimiters(line, inDollarBlock, dollarTag) {
|
|
789
|
+
const dollarMatch = line.match(/\$([a-zA-Z_]*)\$/g);
|
|
790
|
+
if (!dollarMatch) return { inDollarBlock, dollarTag };
|
|
791
|
+
let currentInBlock = inDollarBlock;
|
|
792
|
+
let currentTag = dollarTag;
|
|
793
|
+
for (const match of dollarMatch) {
|
|
794
|
+
if (!currentInBlock) {
|
|
795
|
+
currentInBlock = true;
|
|
796
|
+
currentTag = match;
|
|
797
|
+
} else if (match === currentTag) {
|
|
798
|
+
currentInBlock = false;
|
|
799
|
+
currentTag = "";
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
return { inDollarBlock: currentInBlock, dollarTag: currentTag };
|
|
803
|
+
}
|
|
804
|
+
/**
|
|
805
|
+
* Filter out comment-only statements
|
|
806
|
+
*/
|
|
807
|
+
isNonCommentStatement(statement) {
|
|
808
|
+
const withoutComments = statement.replace(/--.*$/gm, "").trim();
|
|
809
|
+
return withoutComments.length > 0;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Split SQL into individual statements for better error reporting
|
|
813
|
+
*/
|
|
814
|
+
splitSqlStatements(sql2) {
|
|
815
|
+
const statements = [];
|
|
816
|
+
let current = "";
|
|
817
|
+
let inDollarBlock = false;
|
|
818
|
+
let dollarTag = "";
|
|
819
|
+
for (const line of sql2.split("\n")) {
|
|
820
|
+
const trimmedLine = line.trim();
|
|
821
|
+
const isEmptyOrComment = trimmedLine === "" || trimmedLine.startsWith("--");
|
|
822
|
+
current += line + "\n";
|
|
823
|
+
if (isEmptyOrComment) continue;
|
|
824
|
+
const dollarState = this.processDollarDelimiters(line, inDollarBlock, dollarTag);
|
|
825
|
+
inDollarBlock = dollarState.inDollarBlock;
|
|
826
|
+
dollarTag = dollarState.dollarTag;
|
|
827
|
+
if (!inDollarBlock && trimmedLine.endsWith(";") && current.trim()) {
|
|
828
|
+
statements.push(current.trim());
|
|
829
|
+
current = "";
|
|
830
|
+
}
|
|
831
|
+
}
|
|
832
|
+
if (current.trim()) {
|
|
833
|
+
statements.push(current.trim());
|
|
834
|
+
}
|
|
835
|
+
return statements.filter((s) => this.isNonCommentStatement(s));
|
|
836
|
+
}
|
|
837
|
+
/**
|
|
838
|
+
* Extract a short description from a SQL statement for logging
|
|
648
839
|
*/
|
|
840
|
+
getStatementDescription(statement) {
|
|
841
|
+
const firstLine = statement.split("\n").find((l) => l.trim() && !l.trim().startsWith("--"))?.trim() ?? "";
|
|
842
|
+
const patterns = [
|
|
843
|
+
/^(CREATE\s+(?:OR\s+REPLACE\s+)?(?:TABLE|INDEX|UNIQUE\s+INDEX|TYPE|FUNCTION|TRIGGER|EXTENSION|SCHEMA|VIEW|POLICY))\s+(?:IF\s+NOT\s+EXISTS\s+)?([^\s(]+)/i,
|
|
844
|
+
/^(ALTER\s+TABLE)\s+([^\s]+)/i,
|
|
845
|
+
/^(DROP\s+(?:TABLE|INDEX|TYPE|FUNCTION|TRIGGER|EXTENSION|SCHEMA|VIEW|POLICY))\s+(?:IF\s+EXISTS\s+)?([^\s(;]+)/i,
|
|
846
|
+
/^(INSERT\s+INTO)\s+([^\s(]+)/i,
|
|
847
|
+
/^(COMMENT\s+ON\s+(?:TABLE|COLUMN|INDEX|FUNCTION|TYPE))\s+([^\s]+)/i,
|
|
848
|
+
/^(GRANT|REVOKE)\s+.+\s+ON\s+([^\s]+)/i
|
|
849
|
+
];
|
|
850
|
+
for (const pattern of patterns) {
|
|
851
|
+
const match = firstLine.match(pattern);
|
|
852
|
+
if (match) {
|
|
853
|
+
return `${match[1]} ${match[2]}`.slice(0, DESCRIPTION_MAX_LENGTH2);
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
const truncated = firstLine.slice(0, FALLBACK_DESCRIPTION_LENGTH2);
|
|
857
|
+
const suffix = firstLine.length > FALLBACK_DESCRIPTION_LENGTH2 ? "..." : "";
|
|
858
|
+
return truncated + suffix;
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Execute SQL statements individually with better error reporting
|
|
862
|
+
*/
|
|
863
|
+
async executeSqlStatements(sql2, seedName) {
|
|
864
|
+
const statements = this.splitSqlStatements(sql2);
|
|
865
|
+
const total = statements.length;
|
|
866
|
+
console.log(` → ${total} statements to execute`);
|
|
867
|
+
for (let i = 0; i < statements.length; i++) {
|
|
868
|
+
const statement = statements[i];
|
|
869
|
+
const description = this.getStatementDescription(statement);
|
|
870
|
+
try {
|
|
871
|
+
await this.adapter.query(statement);
|
|
872
|
+
const isInterval = (i + 1) % PROGRESS_LOG_INTERVAL2 === 0;
|
|
873
|
+
const isLast = i === total - 1;
|
|
874
|
+
const isSignificant = Boolean(description.match(/^(INSERT INTO)/i));
|
|
875
|
+
if (isInterval || isLast || isSignificant) {
|
|
876
|
+
console.log(` ✓ [${i + 1}/${total}] ${description}`);
|
|
877
|
+
}
|
|
878
|
+
} catch (error) {
|
|
879
|
+
console.log(` ✗ [${i + 1}/${total}] ${description}`);
|
|
880
|
+
const rawMessage = error.message;
|
|
881
|
+
const errorMessage = rawMessage.replace(/^SQL Error:\s*/i, "").replace(/^Failed to execute query:.*?-\s*/i, "").slice(0, ERROR_MESSAGE_MAX_LENGTH2);
|
|
882
|
+
throw new DatabaseError(
|
|
883
|
+
`Seed "${seedName}" failed at statement ${i + 1}/${total}:
|
|
884
|
+
Statement: ${description}
|
|
885
|
+
Error: ${errorMessage}`,
|
|
886
|
+
DATABASE_ERROR_CODES.QUERY_FAILED,
|
|
887
|
+
{ cause: error }
|
|
888
|
+
);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
/**
|
|
893
|
+
* Load SQL seed from file
|
|
894
|
+
*/
|
|
895
|
+
loadSqlSeed(seedFile) {
|
|
896
|
+
const sql2 = fs3.readFileSync(seedFile.filePath, "utf-8");
|
|
897
|
+
return {
|
|
898
|
+
name: seedFile.name,
|
|
899
|
+
run: /* @__PURE__ */ __name(async () => {
|
|
900
|
+
if (typeof this.adapter.query === "function") {
|
|
901
|
+
await this.executeSqlStatements(sql2, seedFile.name);
|
|
902
|
+
}
|
|
903
|
+
}, "run"),
|
|
904
|
+
// SQL seeds don't have cleanup by default
|
|
905
|
+
cleanup: void 0
|
|
906
|
+
};
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Load seed from file (supports .ts, .js, and .sql)
|
|
910
|
+
*/
|
|
911
|
+
// eslint-disable-next-line complexity
|
|
649
912
|
async loadSeed(seedFile) {
|
|
913
|
+
const ext = path4.extname(seedFile.filePath);
|
|
914
|
+
if (ext === ".sql") {
|
|
915
|
+
return this.loadSqlSeed(seedFile);
|
|
916
|
+
}
|
|
650
917
|
const importPath = seedFile.filePath.startsWith("/") ? seedFile.filePath : new URL(`file:///${seedFile.filePath.replace(/\\/g, "/")}`).href;
|
|
651
918
|
const seedModule = await import(importPath);
|
|
652
919
|
return {
|
|
@@ -700,9 +967,15 @@ var SeedManager = class {
|
|
|
700
967
|
*/
|
|
701
968
|
async executeSeed(seed) {
|
|
702
969
|
if (typeof this.adapter.transaction === "function") {
|
|
703
|
-
await this.adapter.transaction(async () => {
|
|
970
|
+
const txResult = await this.adapter.transaction(async () => {
|
|
704
971
|
await seed.run(this.adapter);
|
|
705
972
|
});
|
|
973
|
+
if (!txResult.success) {
|
|
974
|
+
throw txResult.error ?? new DatabaseError(
|
|
975
|
+
`Seed ${seed.name} failed`,
|
|
976
|
+
DATABASE_ERROR_CODES.QUERY_FAILED
|
|
977
|
+
);
|
|
978
|
+
}
|
|
706
979
|
} else {
|
|
707
980
|
await seed.run(this.adapter);
|
|
708
981
|
}
|
|
@@ -759,9 +1032,15 @@ var SeedManager = class {
|
|
|
759
1032
|
async executeCleanup(seed) {
|
|
760
1033
|
if (!seed.cleanup) return;
|
|
761
1034
|
if (typeof this.adapter.transaction === "function") {
|
|
762
|
-
await this.adapter.transaction(async () => {
|
|
1035
|
+
const txResult = await this.adapter.transaction(async () => {
|
|
763
1036
|
await seed.cleanup(this.adapter);
|
|
764
1037
|
});
|
|
1038
|
+
if (!txResult.success) {
|
|
1039
|
+
throw txResult.error ?? new DatabaseError(
|
|
1040
|
+
`Seed cleanup for ${seed.name} failed`,
|
|
1041
|
+
DATABASE_ERROR_CODES.QUERY_FAILED
|
|
1042
|
+
);
|
|
1043
|
+
}
|
|
765
1044
|
} else {
|
|
766
1045
|
await seed.cleanup(this.adapter);
|
|
767
1046
|
}
|
|
@@ -4395,6 +4674,7 @@ var SupabaseAdapter = class {
|
|
|
4395
4674
|
return ops[operator]();
|
|
4396
4675
|
}
|
|
4397
4676
|
};
|
|
4677
|
+
var SQL_ERROR_TRUNCATE_LENGTH = 500;
|
|
4398
4678
|
var SQLAdapter = class {
|
|
4399
4679
|
static {
|
|
4400
4680
|
__name(this, "SQLAdapter");
|
|
@@ -4405,6 +4685,7 @@ var SQLAdapter = class {
|
|
|
4405
4685
|
idColumnMap = /* @__PURE__ */ new Map();
|
|
4406
4686
|
configIdColumns;
|
|
4407
4687
|
defaultSchema;
|
|
4688
|
+
showSqlInErrors;
|
|
4408
4689
|
/**
|
|
4409
4690
|
* Creates a new SQLAdapter instance.
|
|
4410
4691
|
* @param {SQLAdapterConfig} config - Configuration for the SQL adapter.
|
|
@@ -4418,6 +4699,7 @@ var SQLAdapter = class {
|
|
|
4418
4699
|
constructor(config) {
|
|
4419
4700
|
this.config = config;
|
|
4420
4701
|
this.defaultSchema = config.schema ?? "public";
|
|
4702
|
+
this.showSqlInErrors = config.showSqlInErrors ?? true;
|
|
4421
4703
|
this.pool = new Pool({
|
|
4422
4704
|
connectionString: config.connectionString,
|
|
4423
4705
|
...config.pool
|
|
@@ -4536,16 +4818,16 @@ var SQLAdapter = class {
|
|
|
4536
4818
|
const result = await this.pool.query(sql2, params);
|
|
4537
4819
|
return result.rows;
|
|
4538
4820
|
} catch (error) {
|
|
4539
|
-
|
|
4540
|
-
|
|
4541
|
-
|
|
4542
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
4546
|
-
|
|
4547
|
-
|
|
4548
|
-
);
|
|
4821
|
+
const truncatedSql = sql2.slice(0, SQL_ERROR_TRUNCATE_LENGTH);
|
|
4822
|
+
const sqlSuffix = sql2.length > SQL_ERROR_TRUNCATE_LENGTH ? "..." : "";
|
|
4823
|
+
const errorMessage = this.showSqlInErrors ? `SQL Error: ${error.message}
|
|
4824
|
+
Query: ${truncatedSql}${sqlSuffix}` : `SQL Error: ${error.message}`;
|
|
4825
|
+
throw new DatabaseError(errorMessage, DATABASE_ERROR_CODES.QUERY_FAILED, {
|
|
4826
|
+
context: {
|
|
4827
|
+
source: "SQLAdapter.query"
|
|
4828
|
+
},
|
|
4829
|
+
cause: error
|
|
4830
|
+
});
|
|
4549
4831
|
}
|
|
4550
4832
|
}
|
|
4551
4833
|
/**
|
|
@@ -7975,20 +8257,48 @@ async function createDatabaseService(config) {
|
|
|
7975
8257
|
}
|
|
7976
8258
|
__name(createDatabaseService, "createDatabaseService");
|
|
7977
8259
|
var JSON_INDENT_SPACES = 2;
|
|
7978
|
-
async function
|
|
8260
|
+
async function getUserSchemas(adapter) {
|
|
7979
8261
|
const result = await adapter.query?.(`
|
|
7980
|
-
SELECT
|
|
7981
|
-
WHERE
|
|
7982
|
-
|
|
8262
|
+
SELECT schema_name FROM information_schema.schemata
|
|
8263
|
+
WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast', 'mysql', 'sys', 'performance_schema')
|
|
8264
|
+
AND schema_name NOT LIKE 'pg_%'
|
|
8265
|
+
AND schema_name NOT LIKE 'pg_temp_%'
|
|
8266
|
+
AND schema_name NOT LIKE 'pg_toast_temp_%'
|
|
7983
8267
|
`);
|
|
7984
|
-
const
|
|
7985
|
-
return
|
|
8268
|
+
const schemas = Array.isArray(result) ? result : result.rows || [];
|
|
8269
|
+
return schemas.map((s) => s.schema_name);
|
|
8270
|
+
}
|
|
8271
|
+
__name(getUserSchemas, "getUserSchemas");
|
|
8272
|
+
async function getTablesFromSchemas(adapter, schemas) {
|
|
8273
|
+
const tables = [];
|
|
8274
|
+
for (const schema of schemas) {
|
|
8275
|
+
const result = await adapter.query?.(
|
|
8276
|
+
`
|
|
8277
|
+
SELECT table_name FROM information_schema.tables
|
|
8278
|
+
WHERE table_schema = $1
|
|
8279
|
+
AND table_type = 'BASE TABLE'
|
|
8280
|
+
AND table_name NOT IN ('schema_migrations', 'seed_history')
|
|
8281
|
+
`,
|
|
8282
|
+
[schema]
|
|
8283
|
+
);
|
|
8284
|
+
const schemaResult = Array.isArray(result) ? result : result.rows || [];
|
|
8285
|
+
for (const row of schemaResult) {
|
|
8286
|
+
tables.push({ schema, table: row.table_name });
|
|
8287
|
+
}
|
|
8288
|
+
}
|
|
8289
|
+
return tables;
|
|
7986
8290
|
}
|
|
7987
|
-
__name(
|
|
8291
|
+
__name(getTablesFromSchemas, "getTablesFromSchemas");
|
|
7988
8292
|
async function clearTables(adapter, tables) {
|
|
7989
|
-
|
|
7990
|
-
|
|
7991
|
-
|
|
8293
|
+
let currentSchema = "";
|
|
8294
|
+
for (const { schema, table } of tables) {
|
|
8295
|
+
if (schema !== currentSchema) {
|
|
8296
|
+
console.log(`
|
|
8297
|
+
📁 Schema: ${schema}`);
|
|
8298
|
+
currentSchema = schema;
|
|
8299
|
+
}
|
|
8300
|
+
await adapter.query?.(`TRUNCATE TABLE "${schema}"."${table}" CASCADE`);
|
|
8301
|
+
console.log(` ✓ Cleared ${schema}.${table}`);
|
|
7992
8302
|
}
|
|
7993
8303
|
}
|
|
7994
8304
|
__name(clearTables, "clearTables");
|
|
@@ -8335,7 +8645,13 @@ seedCommand.command("reset").description("Reset seeds (run cleanup functions)").
|
|
|
8335
8645
|
process.exit(1);
|
|
8336
8646
|
}
|
|
8337
8647
|
});
|
|
8338
|
-
program.command("clear").description("Clear all data from database (keep schema)").option("--confirm", "Confirm clear operation").option(
|
|
8648
|
+
program.command("clear").description("Clear all data from database (keep schema)").option("--confirm", "Confirm clear operation").option(
|
|
8649
|
+
"-t, --tables <tables>",
|
|
8650
|
+
"Comma-separated list of tables to clear (format: schema.table or just table for public)"
|
|
8651
|
+
).option(
|
|
8652
|
+
"-s, --schemas <schemas>",
|
|
8653
|
+
"Comma-separated list of schemas to clear (default: all user schemas)"
|
|
8654
|
+
).action(async (options) => {
|
|
8339
8655
|
if (!options.confirm) {
|
|
8340
8656
|
console.error("❌ Please use --confirm flag to confirm clear operation");
|
|
8341
8657
|
process.exit(1);
|
|
@@ -8347,16 +8663,36 @@ program.command("clear").description("Clear all data from database (keep schema)
|
|
|
8347
8663
|
console.error("❌ Clear operation not supported by this adapter");
|
|
8348
8664
|
process.exit(1);
|
|
8349
8665
|
}
|
|
8350
|
-
|
|
8666
|
+
let tablesToClear;
|
|
8667
|
+
if (options.tables) {
|
|
8668
|
+
tablesToClear = options.tables.split(",").map((t) => {
|
|
8669
|
+
const trimmed = t.trim();
|
|
8670
|
+
if (trimmed.includes(".")) {
|
|
8671
|
+
const [schema, table] = trimmed.split(".");
|
|
8672
|
+
return { schema, table };
|
|
8673
|
+
}
|
|
8674
|
+
return { schema: "public", table: trimmed };
|
|
8675
|
+
});
|
|
8676
|
+
} else {
|
|
8677
|
+
const schemas = options.schemas ? options.schemas.split(",").map((s) => s.trim()) : await getUserSchemas(adapter);
|
|
8678
|
+
tablesToClear = await getTablesFromSchemas(adapter, schemas);
|
|
8679
|
+
}
|
|
8351
8680
|
await clearTables(adapter, tablesToClear);
|
|
8352
|
-
console.log(
|
|
8681
|
+
console.log(`
|
|
8682
|
+
✅ Cleared ${tablesToClear.length} tables`);
|
|
8353
8683
|
process.exit(0);
|
|
8354
8684
|
} catch (error) {
|
|
8355
8685
|
console.error("❌ Error:", error.message);
|
|
8356
8686
|
process.exit(1);
|
|
8357
8687
|
}
|
|
8358
8688
|
});
|
|
8359
|
-
program.command("drop").description("Drop all tables from database").option("--confirm", "Confirm drop operation").
|
|
8689
|
+
program.command("drop").description("Drop all tables from database (all schemas)").option("--confirm", "Confirm drop operation").option(
|
|
8690
|
+
"-s, --schemas <schemas>",
|
|
8691
|
+
"Comma-separated list of schemas to drop from (default: all user schemas)"
|
|
8692
|
+
).option(
|
|
8693
|
+
"--all",
|
|
8694
|
+
"Drop everything: tables, types, functions, views (not just tables)"
|
|
8695
|
+
).option("--drop-schemas", "Also drop the schemas themselves (except public)").action(async (options) => {
|
|
8360
8696
|
if (!options.confirm) {
|
|
8361
8697
|
console.error("❌ Please use --confirm flag to confirm drop operation");
|
|
8362
8698
|
process.exit(1);
|
|
@@ -8364,22 +8700,226 @@ program.command("drop").description("Drop all tables from database").option("--c
|
|
|
8364
8700
|
try {
|
|
8365
8701
|
const { adapter } = await initDatabase();
|
|
8366
8702
|
console.log("🔄 Dropping all tables...");
|
|
8367
|
-
if (typeof adapter.query
|
|
8368
|
-
const result = await adapter.query(`
|
|
8369
|
-
SELECT tablename FROM pg_tables
|
|
8370
|
-
WHERE schemaname = 'public'
|
|
8371
|
-
`);
|
|
8372
|
-
const tables = Array.isArray(result) ? result : result.rows || [];
|
|
8373
|
-
for (const { tablename } of tables) {
|
|
8374
|
-
await adapter.query(`DROP TABLE IF EXISTS ${tablename} CASCADE`);
|
|
8375
|
-
console.log(` ✓ Dropped ${tablename}`);
|
|
8376
|
-
}
|
|
8377
|
-
console.log("✅ All tables dropped");
|
|
8378
|
-
process.exit(0);
|
|
8379
|
-
} else {
|
|
8703
|
+
if (typeof adapter.query !== "function") {
|
|
8380
8704
|
console.error("❌ Drop operation not supported by this adapter");
|
|
8381
8705
|
process.exit(1);
|
|
8382
8706
|
}
|
|
8707
|
+
const schemas = options.schemas ? options.schemas.split(",").map((s) => s.trim()) : await getUserSchemas(adapter);
|
|
8708
|
+
let totalDropped = 0;
|
|
8709
|
+
for (const schema of schemas) {
|
|
8710
|
+
const result = await adapter.query(
|
|
8711
|
+
`
|
|
8712
|
+
SELECT table_name FROM information_schema.tables
|
|
8713
|
+
WHERE table_schema = $1
|
|
8714
|
+
AND table_type = 'BASE TABLE'
|
|
8715
|
+
`,
|
|
8716
|
+
[schema]
|
|
8717
|
+
);
|
|
8718
|
+
const tables = Array.isArray(result) ? result : result.rows || [];
|
|
8719
|
+
if (tables.length === 0) continue;
|
|
8720
|
+
console.log(`
|
|
8721
|
+
📁 Schema: ${schema}`);
|
|
8722
|
+
for (const { table_name } of tables) {
|
|
8723
|
+
await adapter.query(
|
|
8724
|
+
`DROP TABLE IF EXISTS "${schema}"."${table_name}" CASCADE`
|
|
8725
|
+
);
|
|
8726
|
+
console.log(` ✓ Dropped ${schema}.${table_name}`);
|
|
8727
|
+
totalDropped++;
|
|
8728
|
+
}
|
|
8729
|
+
}
|
|
8730
|
+
if (options.all) {
|
|
8731
|
+
console.log("\n🗑️ Dropping views...");
|
|
8732
|
+
for (const schema of schemas) {
|
|
8733
|
+
const viewResult = await adapter.query(
|
|
8734
|
+
`
|
|
8735
|
+
SELECT table_name FROM information_schema.views
|
|
8736
|
+
WHERE table_schema = $1
|
|
8737
|
+
`,
|
|
8738
|
+
[schema]
|
|
8739
|
+
);
|
|
8740
|
+
const views = Array.isArray(viewResult) ? viewResult : viewResult.rows || [];
|
|
8741
|
+
for (const { table_name } of views) {
|
|
8742
|
+
try {
|
|
8743
|
+
await adapter.query(
|
|
8744
|
+
`DROP VIEW IF EXISTS "${schema}"."${table_name}" CASCADE`
|
|
8745
|
+
);
|
|
8746
|
+
console.log(` ✓ Dropped view ${schema}.${table_name}`);
|
|
8747
|
+
} catch {
|
|
8748
|
+
}
|
|
8749
|
+
}
|
|
8750
|
+
}
|
|
8751
|
+
console.log("\n🗑️ Dropping triggers...");
|
|
8752
|
+
for (const schema of schemas) {
|
|
8753
|
+
try {
|
|
8754
|
+
const triggerResult = await adapter.query(
|
|
8755
|
+
`
|
|
8756
|
+
SELECT DISTINCT trigger_name, event_object_table
|
|
8757
|
+
FROM information_schema.triggers
|
|
8758
|
+
WHERE trigger_schema = $1
|
|
8759
|
+
`,
|
|
8760
|
+
[schema]
|
|
8761
|
+
);
|
|
8762
|
+
const triggers = Array.isArray(triggerResult) ? triggerResult : triggerResult.rows || [];
|
|
8763
|
+
for (const { trigger_name, event_object_table } of triggers) {
|
|
8764
|
+
try {
|
|
8765
|
+
await adapter.query(
|
|
8766
|
+
`DROP TRIGGER IF EXISTS "${trigger_name}" ON "${schema}"."${event_object_table}" CASCADE`
|
|
8767
|
+
);
|
|
8768
|
+
console.log(` ✓ Dropped trigger ${schema}.${trigger_name}`);
|
|
8769
|
+
} catch {
|
|
8770
|
+
}
|
|
8771
|
+
}
|
|
8772
|
+
} catch {
|
|
8773
|
+
}
|
|
8774
|
+
}
|
|
8775
|
+
console.log("\n🗑️ Dropping functions...");
|
|
8776
|
+
for (const schema of schemas) {
|
|
8777
|
+
try {
|
|
8778
|
+
const funcResult = await adapter.query(
|
|
8779
|
+
`
|
|
8780
|
+
SELECT p.proname, pg_get_function_identity_arguments(p.oid) as args
|
|
8781
|
+
FROM pg_proc p
|
|
8782
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
8783
|
+
WHERE n.nspname = $1
|
|
8784
|
+
AND p.prokind = 'f'
|
|
8785
|
+
`,
|
|
8786
|
+
[schema]
|
|
8787
|
+
);
|
|
8788
|
+
const funcs = Array.isArray(funcResult) ? funcResult : funcResult.rows || [];
|
|
8789
|
+
for (const { proname, args } of funcs) {
|
|
8790
|
+
try {
|
|
8791
|
+
await adapter.query(
|
|
8792
|
+
`DROP FUNCTION IF EXISTS "${schema}"."${proname}"(${args}) CASCADE`
|
|
8793
|
+
);
|
|
8794
|
+
console.log(` ✓ Dropped function ${schema}.${proname}`);
|
|
8795
|
+
} catch {
|
|
8796
|
+
}
|
|
8797
|
+
}
|
|
8798
|
+
} catch {
|
|
8799
|
+
console.log(
|
|
8800
|
+
` ⚠ Function dropping not supported for this database`
|
|
8801
|
+
);
|
|
8802
|
+
break;
|
|
8803
|
+
}
|
|
8804
|
+
}
|
|
8805
|
+
console.log("\n🗑️ Dropping procedures...");
|
|
8806
|
+
for (const schema of schemas) {
|
|
8807
|
+
try {
|
|
8808
|
+
const procResult = await adapter.query(
|
|
8809
|
+
`
|
|
8810
|
+
SELECT p.proname, pg_get_function_identity_arguments(p.oid) as args
|
|
8811
|
+
FROM pg_proc p
|
|
8812
|
+
JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
8813
|
+
WHERE n.nspname = $1
|
|
8814
|
+
AND p.prokind = 'p'
|
|
8815
|
+
`,
|
|
8816
|
+
[schema]
|
|
8817
|
+
);
|
|
8818
|
+
const procs = Array.isArray(procResult) ? procResult : procResult.rows || [];
|
|
8819
|
+
for (const { proname, args } of procs) {
|
|
8820
|
+
try {
|
|
8821
|
+
await adapter.query(
|
|
8822
|
+
`DROP PROCEDURE IF EXISTS "${schema}"."${proname}"(${args}) CASCADE`
|
|
8823
|
+
);
|
|
8824
|
+
console.log(` ✓ Dropped procedure ${schema}.${proname}`);
|
|
8825
|
+
} catch {
|
|
8826
|
+
}
|
|
8827
|
+
}
|
|
8828
|
+
} catch {
|
|
8829
|
+
}
|
|
8830
|
+
}
|
|
8831
|
+
console.log("\n🗑️ Dropping sequences...");
|
|
8832
|
+
for (const schema of schemas) {
|
|
8833
|
+
try {
|
|
8834
|
+
const seqResult = await adapter.query(
|
|
8835
|
+
`
|
|
8836
|
+
SELECT sequence_name FROM information_schema.sequences
|
|
8837
|
+
WHERE sequence_schema = $1
|
|
8838
|
+
`,
|
|
8839
|
+
[schema]
|
|
8840
|
+
);
|
|
8841
|
+
const sequences = Array.isArray(seqResult) ? seqResult : seqResult.rows || [];
|
|
8842
|
+
for (const { sequence_name } of sequences) {
|
|
8843
|
+
try {
|
|
8844
|
+
await adapter.query(
|
|
8845
|
+
`DROP SEQUENCE IF EXISTS "${schema}"."${sequence_name}" CASCADE`
|
|
8846
|
+
);
|
|
8847
|
+
console.log(` ✓ Dropped sequence ${schema}.${sequence_name}`);
|
|
8848
|
+
} catch {
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8851
|
+
} catch {
|
|
8852
|
+
}
|
|
8853
|
+
}
|
|
8854
|
+
console.log("\n🗑️ Dropping types...");
|
|
8855
|
+
for (const schema of schemas) {
|
|
8856
|
+
try {
|
|
8857
|
+
const typeResult = await adapter.query(
|
|
8858
|
+
`
|
|
8859
|
+
SELECT t.typname
|
|
8860
|
+
FROM pg_type t
|
|
8861
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
8862
|
+
WHERE n.nspname = $1
|
|
8863
|
+
AND t.typtype = 'e'
|
|
8864
|
+
`,
|
|
8865
|
+
[schema]
|
|
8866
|
+
);
|
|
8867
|
+
const types = Array.isArray(typeResult) ? typeResult : typeResult.rows || [];
|
|
8868
|
+
for (const { typname } of types) {
|
|
8869
|
+
try {
|
|
8870
|
+
await adapter.query(
|
|
8871
|
+
`DROP TYPE IF EXISTS "${schema}"."${typname}" CASCADE`
|
|
8872
|
+
);
|
|
8873
|
+
console.log(` ✓ Dropped type ${schema}.${typname}`);
|
|
8874
|
+
} catch {
|
|
8875
|
+
}
|
|
8876
|
+
}
|
|
8877
|
+
} catch {
|
|
8878
|
+
console.log(` ⚠ Type dropping not supported for this database`);
|
|
8879
|
+
break;
|
|
8880
|
+
}
|
|
8881
|
+
}
|
|
8882
|
+
console.log("\n🗑️ Dropping domains...");
|
|
8883
|
+
for (const schema of schemas) {
|
|
8884
|
+
try {
|
|
8885
|
+
const domainResult = await adapter.query(
|
|
8886
|
+
`
|
|
8887
|
+
SELECT domain_name FROM information_schema.domains
|
|
8888
|
+
WHERE domain_schema = $1
|
|
8889
|
+
`,
|
|
8890
|
+
[schema]
|
|
8891
|
+
);
|
|
8892
|
+
const domains = Array.isArray(domainResult) ? domainResult : domainResult.rows || [];
|
|
8893
|
+
for (const { domain_name } of domains) {
|
|
8894
|
+
try {
|
|
8895
|
+
await adapter.query(
|
|
8896
|
+
`DROP DOMAIN IF EXISTS "${schema}"."${domain_name}" CASCADE`
|
|
8897
|
+
);
|
|
8898
|
+
console.log(` ✓ Dropped domain ${schema}.${domain_name}`);
|
|
8899
|
+
} catch {
|
|
8900
|
+
}
|
|
8901
|
+
}
|
|
8902
|
+
} catch {
|
|
8903
|
+
}
|
|
8904
|
+
}
|
|
8905
|
+
}
|
|
8906
|
+
if (options.dropSchemas) {
|
|
8907
|
+
console.log("\n🗑️ Dropping schemas...");
|
|
8908
|
+
for (const schema of schemas) {
|
|
8909
|
+
if (schema === "public") continue;
|
|
8910
|
+
try {
|
|
8911
|
+
await adapter.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
8912
|
+
console.log(` ✓ Dropped schema ${schema}`);
|
|
8913
|
+
} catch {
|
|
8914
|
+
console.log(` ⚠ Could not drop schema ${schema}`);
|
|
8915
|
+
}
|
|
8916
|
+
}
|
|
8917
|
+
}
|
|
8918
|
+
console.log(
|
|
8919
|
+
`
|
|
8920
|
+
✅ Dropped ${totalDropped} tables${options.all ? " + types, functions, views" : ""}`
|
|
8921
|
+
);
|
|
8922
|
+
process.exit(0);
|
|
8383
8923
|
} catch (error) {
|
|
8384
8924
|
console.error("❌ Error:", error.message);
|
|
8385
8925
|
process.exit(1);
|
|
@@ -8415,6 +8955,247 @@ program.command("health").description("Check database health").action(async () =
|
|
|
8415
8955
|
process.exit(1);
|
|
8416
8956
|
}
|
|
8417
8957
|
});
|
|
8958
|
+
async function dropAllObjects(adapter, schemas, dropSchemas) {
|
|
8959
|
+
let totalTables = 0;
|
|
8960
|
+
let totalObjects = 0;
|
|
8961
|
+
console.log("\n🗑️ Dropping tables...");
|
|
8962
|
+
for (const schema of schemas) {
|
|
8963
|
+
const result = await adapter.query(
|
|
8964
|
+
`
|
|
8965
|
+
SELECT table_name FROM information_schema.tables
|
|
8966
|
+
WHERE table_schema = $1
|
|
8967
|
+
AND table_type = 'BASE TABLE'
|
|
8968
|
+
`,
|
|
8969
|
+
[schema]
|
|
8970
|
+
);
|
|
8971
|
+
const tables = Array.isArray(result) ? result : result.rows || [];
|
|
8972
|
+
if (tables.length === 0) continue;
|
|
8973
|
+
console.log(`
|
|
8974
|
+
📁 Schema: ${schema}`);
|
|
8975
|
+
for (const { table_name } of tables) {
|
|
8976
|
+
await adapter.query(
|
|
8977
|
+
`DROP TABLE IF EXISTS "${schema}"."${table_name}" CASCADE`
|
|
8978
|
+
);
|
|
8979
|
+
console.log(` ✓ Dropped table ${schema}.${table_name}`);
|
|
8980
|
+
totalTables++;
|
|
8981
|
+
}
|
|
8982
|
+
}
|
|
8983
|
+
console.log("\n🗑️ Dropping views...");
|
|
8984
|
+
for (const schema of schemas) {
|
|
8985
|
+
const viewResult = await adapter.query(
|
|
8986
|
+
`SELECT table_name FROM information_schema.views WHERE table_schema = $1`,
|
|
8987
|
+
[schema]
|
|
8988
|
+
);
|
|
8989
|
+
const views = Array.isArray(viewResult) ? viewResult : viewResult.rows || [];
|
|
8990
|
+
for (const { table_name } of views) {
|
|
8991
|
+
try {
|
|
8992
|
+
await adapter.query(
|
|
8993
|
+
`DROP VIEW IF EXISTS "${schema}"."${table_name}" CASCADE`
|
|
8994
|
+
);
|
|
8995
|
+
console.log(` ✓ Dropped view ${schema}.${table_name}`);
|
|
8996
|
+
totalObjects++;
|
|
8997
|
+
} catch {
|
|
8998
|
+
}
|
|
8999
|
+
}
|
|
9000
|
+
}
|
|
9001
|
+
console.log("\n🗑️ Dropping triggers...");
|
|
9002
|
+
for (const schema of schemas) {
|
|
9003
|
+
try {
|
|
9004
|
+
const triggerResult = await adapter.query(
|
|
9005
|
+
`SELECT DISTINCT trigger_name, event_object_table FROM information_schema.triggers WHERE trigger_schema = $1`,
|
|
9006
|
+
[schema]
|
|
9007
|
+
);
|
|
9008
|
+
const triggers = Array.isArray(triggerResult) ? triggerResult : triggerResult.rows || [];
|
|
9009
|
+
for (const { trigger_name, event_object_table } of triggers) {
|
|
9010
|
+
try {
|
|
9011
|
+
await adapter.query(
|
|
9012
|
+
`DROP TRIGGER IF EXISTS "${trigger_name}" ON "${schema}"."${event_object_table}" CASCADE`
|
|
9013
|
+
);
|
|
9014
|
+
console.log(` ✓ Dropped trigger ${schema}.${trigger_name}`);
|
|
9015
|
+
totalObjects++;
|
|
9016
|
+
} catch {
|
|
9017
|
+
}
|
|
9018
|
+
}
|
|
9019
|
+
} catch {
|
|
9020
|
+
}
|
|
9021
|
+
}
|
|
9022
|
+
console.log("\n🗑️ Dropping functions...");
|
|
9023
|
+
for (const schema of schemas) {
|
|
9024
|
+
try {
|
|
9025
|
+
const funcResult = await adapter.query(
|
|
9026
|
+
`SELECT p.proname, pg_get_function_identity_arguments(p.oid) as args
|
|
9027
|
+
FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
9028
|
+
WHERE n.nspname = $1 AND p.prokind = 'f'`,
|
|
9029
|
+
[schema]
|
|
9030
|
+
);
|
|
9031
|
+
const funcs = Array.isArray(funcResult) ? funcResult : funcResult.rows || [];
|
|
9032
|
+
for (const { proname, args } of funcs) {
|
|
9033
|
+
try {
|
|
9034
|
+
await adapter.query(
|
|
9035
|
+
`DROP FUNCTION IF EXISTS "${schema}"."${proname}"(${args}) CASCADE`
|
|
9036
|
+
);
|
|
9037
|
+
console.log(` ✓ Dropped function ${schema}.${proname}`);
|
|
9038
|
+
totalObjects++;
|
|
9039
|
+
} catch {
|
|
9040
|
+
}
|
|
9041
|
+
}
|
|
9042
|
+
} catch {
|
|
9043
|
+
}
|
|
9044
|
+
}
|
|
9045
|
+
console.log("\n🗑️ Dropping procedures...");
|
|
9046
|
+
for (const schema of schemas) {
|
|
9047
|
+
try {
|
|
9048
|
+
const procResult = await adapter.query(
|
|
9049
|
+
`SELECT p.proname, pg_get_function_identity_arguments(p.oid) as args
|
|
9050
|
+
FROM pg_proc p JOIN pg_namespace n ON p.pronamespace = n.oid
|
|
9051
|
+
WHERE n.nspname = $1 AND p.prokind = 'p'`,
|
|
9052
|
+
[schema]
|
|
9053
|
+
);
|
|
9054
|
+
const procs = Array.isArray(procResult) ? procResult : procResult.rows || [];
|
|
9055
|
+
for (const { proname, args } of procs) {
|
|
9056
|
+
try {
|
|
9057
|
+
await adapter.query(
|
|
9058
|
+
`DROP PROCEDURE IF EXISTS "${schema}"."${proname}"(${args}) CASCADE`
|
|
9059
|
+
);
|
|
9060
|
+
console.log(` ✓ Dropped procedure ${schema}.${proname}`);
|
|
9061
|
+
totalObjects++;
|
|
9062
|
+
} catch {
|
|
9063
|
+
}
|
|
9064
|
+
}
|
|
9065
|
+
} catch {
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
console.log("\n🗑️ Dropping sequences...");
|
|
9069
|
+
for (const schema of schemas) {
|
|
9070
|
+
try {
|
|
9071
|
+
const seqResult = await adapter.query(
|
|
9072
|
+
`SELECT sequence_name FROM information_schema.sequences WHERE sequence_schema = $1`,
|
|
9073
|
+
[schema]
|
|
9074
|
+
);
|
|
9075
|
+
const sequences = Array.isArray(seqResult) ? seqResult : seqResult.rows || [];
|
|
9076
|
+
for (const { sequence_name } of sequences) {
|
|
9077
|
+
try {
|
|
9078
|
+
await adapter.query(
|
|
9079
|
+
`DROP SEQUENCE IF EXISTS "${schema}"."${sequence_name}" CASCADE`
|
|
9080
|
+
);
|
|
9081
|
+
console.log(` ✓ Dropped sequence ${schema}.${sequence_name}`);
|
|
9082
|
+
totalObjects++;
|
|
9083
|
+
} catch {
|
|
9084
|
+
}
|
|
9085
|
+
}
|
|
9086
|
+
} catch {
|
|
9087
|
+
}
|
|
9088
|
+
}
|
|
9089
|
+
console.log("\n🗑️ Dropping types...");
|
|
9090
|
+
for (const schema of schemas) {
|
|
9091
|
+
try {
|
|
9092
|
+
const typeResult = await adapter.query(
|
|
9093
|
+
`SELECT t.typname FROM pg_type t
|
|
9094
|
+
JOIN pg_namespace n ON t.typnamespace = n.oid
|
|
9095
|
+
WHERE n.nspname = $1 AND t.typtype = 'e'`,
|
|
9096
|
+
[schema]
|
|
9097
|
+
);
|
|
9098
|
+
const types = Array.isArray(typeResult) ? typeResult : typeResult.rows || [];
|
|
9099
|
+
for (const { typname } of types) {
|
|
9100
|
+
try {
|
|
9101
|
+
await adapter.query(
|
|
9102
|
+
`DROP TYPE IF EXISTS "${schema}"."${typname}" CASCADE`
|
|
9103
|
+
);
|
|
9104
|
+
console.log(` ✓ Dropped type ${schema}.${typname}`);
|
|
9105
|
+
totalObjects++;
|
|
9106
|
+
} catch {
|
|
9107
|
+
}
|
|
9108
|
+
}
|
|
9109
|
+
} catch {
|
|
9110
|
+
}
|
|
9111
|
+
}
|
|
9112
|
+
console.log("\n🗑️ Dropping domains...");
|
|
9113
|
+
for (const schema of schemas) {
|
|
9114
|
+
try {
|
|
9115
|
+
const domainResult = await adapter.query(
|
|
9116
|
+
`SELECT domain_name FROM information_schema.domains WHERE domain_schema = $1`,
|
|
9117
|
+
[schema]
|
|
9118
|
+
);
|
|
9119
|
+
const domains = Array.isArray(domainResult) ? domainResult : domainResult.rows || [];
|
|
9120
|
+
for (const { domain_name } of domains) {
|
|
9121
|
+
try {
|
|
9122
|
+
await adapter.query(
|
|
9123
|
+
`DROP DOMAIN IF EXISTS "${schema}"."${domain_name}" CASCADE`
|
|
9124
|
+
);
|
|
9125
|
+
console.log(` ✓ Dropped domain ${schema}.${domain_name}`);
|
|
9126
|
+
totalObjects++;
|
|
9127
|
+
} catch {
|
|
9128
|
+
}
|
|
9129
|
+
}
|
|
9130
|
+
} catch {
|
|
9131
|
+
}
|
|
9132
|
+
}
|
|
9133
|
+
if (dropSchemas) {
|
|
9134
|
+
console.log("\n🗑️ Dropping schemas...");
|
|
9135
|
+
for (const schema of schemas) {
|
|
9136
|
+
if (schema === "public") continue;
|
|
9137
|
+
try {
|
|
9138
|
+
await adapter.query(`DROP SCHEMA IF EXISTS "${schema}" CASCADE`);
|
|
9139
|
+
console.log(` ✓ Dropped schema ${schema}`);
|
|
9140
|
+
} catch {
|
|
9141
|
+
console.log(` ⚠ Could not drop schema ${schema}`);
|
|
9142
|
+
}
|
|
9143
|
+
}
|
|
9144
|
+
}
|
|
9145
|
+
return { tables: totalTables, objects: totalObjects };
|
|
9146
|
+
}
|
|
9147
|
+
__name(dropAllObjects, "dropAllObjects");
|
|
9148
|
+
program.command("reset").description(
|
|
9149
|
+
"Full database reset: drop all objects from all schemas, optionally run migrations"
|
|
9150
|
+
).option("--confirm", "Confirm reset operation").option(
|
|
9151
|
+
"-s, --schemas <schemas>",
|
|
9152
|
+
"Comma-separated list of schemas (default: all user schemas)"
|
|
9153
|
+
).option("--drop-schemas", "Also drop schemas themselves (except public)").option("--migrate", "Run migrations after reset").action(async (options) => {
|
|
9154
|
+
if (!options.confirm) {
|
|
9155
|
+
console.error("❌ Please use --confirm flag to confirm reset operation");
|
|
9156
|
+
console.error(
|
|
9157
|
+
" This will DROP ALL database objects (tables, types, functions, etc.)"
|
|
9158
|
+
);
|
|
9159
|
+
process.exit(1);
|
|
9160
|
+
}
|
|
9161
|
+
try {
|
|
9162
|
+
const { adapter, config } = await initDatabase();
|
|
9163
|
+
if (typeof adapter.query !== "function") {
|
|
9164
|
+
console.error("❌ Reset operation not supported by this adapter");
|
|
9165
|
+
process.exit(1);
|
|
9166
|
+
}
|
|
9167
|
+
console.log("🔄 Resetting database (dropping all objects)...");
|
|
9168
|
+
const schemas = options.schemas ? options.schemas.split(",").map((s) => s.trim()) : await getUserSchemas(adapter);
|
|
9169
|
+
const { tables, objects } = await dropAllObjects(
|
|
9170
|
+
adapter,
|
|
9171
|
+
schemas,
|
|
9172
|
+
options.dropSchemas ?? false
|
|
9173
|
+
);
|
|
9174
|
+
console.log(
|
|
9175
|
+
`
|
|
9176
|
+
✅ Reset complete: dropped ${tables} tables, ${objects} other objects`
|
|
9177
|
+
);
|
|
9178
|
+
if (options.migrate) {
|
|
9179
|
+
console.log("\n🔄 Running migrations...");
|
|
9180
|
+
const migrationManager = new MigrationManager({
|
|
9181
|
+
adapter,
|
|
9182
|
+
migrationsPath: config.migrationsPath ?? "./migrations",
|
|
9183
|
+
tableName: config.migrationsTable ?? "schema_migrations"
|
|
9184
|
+
});
|
|
9185
|
+
const result = await migrationManager.up();
|
|
9186
|
+
if (result.success) {
|
|
9187
|
+
console.log(`✅ Applied ${result.value} migration(s)`);
|
|
9188
|
+
} else {
|
|
9189
|
+
console.error("❌ Migration failed:", result.error?.message);
|
|
9190
|
+
process.exit(1);
|
|
9191
|
+
}
|
|
9192
|
+
}
|
|
9193
|
+
process.exit(0);
|
|
9194
|
+
} catch (error) {
|
|
9195
|
+
console.error("❌ Error:", error.message);
|
|
9196
|
+
process.exit(1);
|
|
9197
|
+
}
|
|
9198
|
+
});
|
|
8418
9199
|
program.parse();
|
|
8419
9200
|
//# sourceMappingURL=index.js.map
|
|
8420
9201
|
//# sourceMappingURL=index.js.map
|