@zyno-io/dk-server-foundation 26.412.2137 → 26.509.2227

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 (113) hide show
  1. package/dist/resources/proto/generated/devconsole/devconsole.js +1 -1
  2. package/dist/src/app/config.d.ts +1 -0
  3. package/dist/src/app/config.d.ts.map +1 -1
  4. package/dist/src/app/config.js +1 -1
  5. package/dist/src/app/config.js.map +1 -1
  6. package/dist/src/database/common.d.ts +3 -0
  7. package/dist/src/database/common.d.ts.map +1 -1
  8. package/dist/src/database/common.js +46 -1
  9. package/dist/src/database/common.js.map +1 -1
  10. package/dist/src/database/factory.d.ts +17 -0
  11. package/dist/src/database/factory.d.ts.map +1 -0
  12. package/dist/src/database/factory.js +53 -0
  13. package/dist/src/database/factory.js.map +1 -0
  14. package/dist/src/database/index.d.ts +2 -0
  15. package/dist/src/database/index.d.ts.map +1 -1
  16. package/dist/src/database/index.js +2 -0
  17. package/dist/src/database/index.js.map +1 -1
  18. package/dist/src/database/migration/MigrationResetCommand.d.ts.map +1 -1
  19. package/dist/src/database/migration/MigrationResetCommand.js +6 -20
  20. package/dist/src/database/migration/MigrationResetCommand.js.map +1 -1
  21. package/dist/src/database/migration/MigrationRunCommand.d.ts.map +1 -1
  22. package/dist/src/database/migration/MigrationRunCommand.js +2 -0
  23. package/dist/src/database/migration/MigrationRunCommand.js.map +1 -1
  24. package/dist/src/database/migration/create/MigrationCreateCommand.d.ts +2 -0
  25. package/dist/src/database/migration/create/MigrationCreateCommand.d.ts.map +1 -1
  26. package/dist/src/database/migration/create/MigrationCreateCommand.js +33 -11
  27. package/dist/src/database/migration/create/MigrationCreateCommand.js.map +1 -1
  28. package/dist/src/database/migration/create/builder-regenerator.d.ts +12 -0
  29. package/dist/src/database/migration/create/builder-regenerator.d.ts.map +1 -0
  30. package/dist/src/database/migration/create/builder-regenerator.js +300 -0
  31. package/dist/src/database/migration/create/builder-regenerator.js.map +1 -0
  32. package/dist/src/database/migration/create/db-reader.js +47 -6
  33. package/dist/src/database/migration/create/db-reader.js.map +1 -1
  34. package/dist/src/database/migration/create/ddl-generator.d.ts.map +1 -1
  35. package/dist/src/database/migration/create/ddl-generator.js +36 -206
  36. package/dist/src/database/migration/create/ddl-generator.js.map +1 -1
  37. package/dist/src/database/migration/create/file-generator.d.ts +2 -0
  38. package/dist/src/database/migration/create/file-generator.d.ts.map +1 -1
  39. package/dist/src/database/migration/create/file-generator.js +7 -2
  40. package/dist/src/database/migration/create/file-generator.js.map +1 -1
  41. package/dist/src/database/migration/create/schema-model.d.ts +2 -0
  42. package/dist/src/database/migration/create/schema-model.d.ts.map +1 -1
  43. package/dist/src/database/migration/create/schema-model.js +1 -1
  44. package/dist/src/database/mysql.d.ts.map +1 -1
  45. package/dist/src/database/mysql.js +5 -11
  46. package/dist/src/database/mysql.js.map +1 -1
  47. package/dist/src/database/postgres.d.ts +1 -0
  48. package/dist/src/database/postgres.d.ts.map +1 -1
  49. package/dist/src/database/postgres.js +1 -13
  50. package/dist/src/database/postgres.js.map +1 -1
  51. package/dist/src/database/schema/AlterBlueprint.d.ts +44 -0
  52. package/dist/src/database/schema/AlterBlueprint.d.ts.map +1 -0
  53. package/dist/src/database/schema/AlterBlueprint.js +82 -0
  54. package/dist/src/database/schema/AlterBlueprint.js.map +1 -0
  55. package/dist/src/database/schema/Blueprint.d.ts +16 -0
  56. package/dist/src/database/schema/Blueprint.d.ts.map +1 -0
  57. package/dist/src/database/schema/Blueprint.js +51 -0
  58. package/dist/src/database/schema/Blueprint.js.map +1 -0
  59. package/dist/src/database/schema/BlueprintBase.d.ts +59 -0
  60. package/dist/src/database/schema/BlueprintBase.d.ts.map +1 -0
  61. package/dist/src/database/schema/BlueprintBase.js +349 -0
  62. package/dist/src/database/schema/BlueprintBase.js.map +1 -0
  63. package/dist/src/database/schema/ColumnDefinition.d.ts +43 -0
  64. package/dist/src/database/schema/ColumnDefinition.d.ts.map +1 -0
  65. package/dist/src/database/schema/ColumnDefinition.js +117 -0
  66. package/dist/src/database/schema/ColumnDefinition.js.map +1 -0
  67. package/dist/src/database/schema/Schema.d.ts +32 -0
  68. package/dist/src/database/schema/Schema.d.ts.map +1 -0
  69. package/dist/src/database/schema/Schema.js +148 -0
  70. package/dist/src/database/schema/Schema.js.map +1 -0
  71. package/dist/src/database/schema/grammar/Grammar.d.ts +34 -0
  72. package/dist/src/database/schema/grammar/Grammar.d.ts.map +1 -0
  73. package/dist/src/database/schema/grammar/Grammar.js +36 -0
  74. package/dist/src/database/schema/grammar/Grammar.js.map +1 -0
  75. package/dist/src/database/schema/grammar/MySQLGrammar.d.ts +28 -0
  76. package/dist/src/database/schema/grammar/MySQLGrammar.d.ts.map +1 -0
  77. package/dist/src/database/schema/grammar/MySQLGrammar.js +190 -0
  78. package/dist/src/database/schema/grammar/MySQLGrammar.js.map +1 -0
  79. package/dist/src/database/schema/grammar/PostgresGrammar.d.ts +28 -0
  80. package/dist/src/database/schema/grammar/PostgresGrammar.d.ts.map +1 -0
  81. package/dist/src/database/schema/grammar/PostgresGrammar.js +223 -0
  82. package/dist/src/database/schema/grammar/PostgresGrammar.js.map +1 -0
  83. package/dist/src/database/schema/index.d.ts +9 -0
  84. package/dist/src/database/schema/index.d.ts.map +1 -0
  85. package/dist/src/database/schema/index.js +12 -0
  86. package/dist/src/database/schema/index.js.map +1 -0
  87. package/docs/content/cli.md +4 -1
  88. package/docs/content/configuration.md +6 -0
  89. package/docs/content/database.md +128 -0
  90. package/package.json +1 -1
  91. package/src/app/config.ts +2 -0
  92. package/src/database/common.ts +51 -0
  93. package/src/database/factory.ts +59 -0
  94. package/src/database/index.ts +2 -0
  95. package/src/database/migration/MigrationResetCommand.ts +6 -22
  96. package/src/database/migration/MigrationRunCommand.ts +2 -0
  97. package/src/database/migration/create/MigrationCreateCommand.ts +40 -12
  98. package/src/database/migration/create/builder-regenerator.ts +287 -0
  99. package/src/database/migration/create/db-reader.ts +43 -6
  100. package/src/database/migration/create/ddl-generator.ts +35 -212
  101. package/src/database/migration/create/file-generator.ts +5 -1
  102. package/src/database/migration/create/schema-model.ts +2 -0
  103. package/src/database/mysql.ts +7 -15
  104. package/src/database/postgres.ts +3 -17
  105. package/src/database/schema/AlterBlueprint.ts +85 -0
  106. package/src/database/schema/Blueprint.ts +43 -0
  107. package/src/database/schema/BlueprintBase.ts +386 -0
  108. package/src/database/schema/ColumnDefinition.ts +131 -0
  109. package/src/database/schema/Schema.ts +164 -0
  110. package/src/database/schema/grammar/Grammar.ts +61 -0
  111. package/src/database/schema/grammar/MySQLGrammar.ts +196 -0
  112. package/src/database/schema/grammar/PostgresGrammar.ts +232 -0
  113. package/src/database/schema/index.ts +8 -0
@@ -1,10 +1,28 @@
1
- import { quoteId } from '../../dialect';
1
+ import { Grammar, MySQLGrammar, PostgresGrammar } from '../../schema';
2
2
  import { ColumnModification, ColumnSchema, Dialect, ForeignKeySchema, IndexSchema, SchemaDiff, TableDiff, TableSchema } from './schema-model';
3
3
 
4
- const VALID_FK_ACTIONS = new Set(['RESTRICT', 'CASCADE', 'SET NULL', 'SET DEFAULT', 'NO ACTION']);
5
-
6
4
  export const COMMENT_PREFIX = '\x00comment:';
7
5
 
6
+ // --- Grammar delegation ---
7
+ //
8
+ // SQL emission lives in src/database/schema/grammar/. This module owns the high-level
9
+ // diff orchestration (statement ordering, deferred FKs, PG enum dedup, MySQL column
10
+ // reordering, etc.). The thin helpers below delegate to dialect-specific Grammars.
11
+
12
+ const grammarCache = new Map<string, Grammar>();
13
+ function getGrammar(dialect: Dialect, pgSchema?: string): Grammar {
14
+ const key = `${dialect}:${pgSchema ?? 'public'}`;
15
+ let g = grammarCache.get(key);
16
+ if (!g) {
17
+ g = dialect === 'mysql' ? new MySQLGrammar(pgSchema ?? 'public') : new PostgresGrammar(pgSchema ?? 'public');
18
+ grammarCache.set(key, g);
19
+ }
20
+ return g;
21
+ }
22
+ function getPgGrammar(pgSchema?: string): PostgresGrammar {
23
+ return getGrammar('postgres', pgSchema) as PostgresGrammar;
24
+ }
25
+
8
26
  export function generateDDL(diff: SchemaDiff): string[] {
9
27
  const statements: string[] = [];
10
28
  const { dialect, pgSchema } = diff;
@@ -485,216 +503,33 @@ function generatePostgresTableDDL(diff: TableDiff, pgSchema?: string, globalEnum
485
503
  // --- Shared helpers ---
486
504
 
487
505
  function createTable(table: TableSchema, dialect: Dialect, pgSchema?: string): string {
488
- const lines: string[] = [];
489
- const pkCols = table.columns.filter(c => c.isPrimaryKey).map(c => c.name);
490
-
491
- for (const col of table.columns) {
492
- if (dialect === 'mysql') {
493
- lines.push(` ${mysqlColumnDef(col)}`);
494
- } else {
495
- lines.push(` ${pgColumnDef(col, pgSchema)}`);
496
- }
497
- }
498
-
499
- if (pkCols.length > 0) {
500
- const quoted = pkCols.map(c => q(dialect, c)).join(', ');
501
- lines.push(` PRIMARY KEY (${quoted})`);
502
- }
503
-
504
- return `CREATE TABLE ${qTable(dialect, table.name, pgSchema)} (\n${lines.join(',\n')}\n)`;
506
+ return getGrammar(dialect, pgSchema).createTable(table);
505
507
  }
506
508
 
507
509
  function createIndex(tableName: string, idx: IndexSchema, dialect: Dialect, pgSchema?: string): string {
508
- const unique = idx.unique ? 'UNIQUE ' : '';
509
- const spatial = idx.spatial && dialect === 'mysql' ? 'SPATIAL ' : '';
510
- const cols = idx.columns.map(c => q(dialect, c)).join(', ');
511
- return `CREATE ${spatial}${unique}INDEX ${q(dialect, idx.name)} ON ${qTable(dialect, tableName, pgSchema)} (${cols})`;
510
+ return getGrammar(dialect, pgSchema).createIndex(tableName, idx);
512
511
  }
513
512
 
514
513
  function addForeignKey(tableName: string, fk: ForeignKeySchema, dialect: Dialect, pgSchema?: string): string {
515
- const cols = fk.columns.map(c => q(dialect, c)).join(', ');
516
- const refCols = fk.referencedColumns.map(c => q(dialect, c)).join(', ');
517
- const onDelete = validateFkAction(fk.onDelete, dialect);
518
- const onUpdate = validateFkAction(fk.onUpdate, dialect);
519
- return (
520
- `ALTER TABLE ${qTable(dialect, tableName, pgSchema)} ADD CONSTRAINT ${q(dialect, fk.name)} ` +
521
- `FOREIGN KEY (${cols}) REFERENCES ${qTable(dialect, fk.referencedTable, pgSchema)} (${refCols}) ` +
522
- `ON DELETE ${onDelete} ON UPDATE ${onUpdate}`
523
- );
524
- }
525
-
526
- function validateFkAction(action: string, dialect: Dialect): string {
527
- const upper = action.toUpperCase();
528
- if (!VALID_FK_ACTIONS.has(upper)) {
529
- throw new Error(`Invalid foreign key action: '${action}'`);
530
- }
531
- if (upper === 'SET DEFAULT' && dialect === 'mysql') {
532
- throw new Error(`Foreign key action 'SET DEFAULT' is not supported by MySQL/InnoDB`);
533
- }
534
- return upper;
514
+ return getGrammar(dialect, pgSchema).addForeignKey(tableName, fk);
535
515
  }
536
516
 
537
517
  function createEnumType(typeName: string, values: string[], pgSchema?: string): string[] {
538
- const qualifiedName = qType(typeName, pgSchema);
539
- const vals = values.map(v => `'${escapeStr(v)}'`).join(', ');
540
- const schemaFilter =
541
- pgSchema && pgSchema !== 'public' ? ` AND typnamespace = (SELECT oid FROM pg_namespace WHERE nspname = '${escapeStr(pgSchema)}')` : '';
542
- return [
543
- [
544
- `DO $$ BEGIN`,
545
- `IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = '${escapeStr(typeName)}'${schemaFilter}) THEN`,
546
- ` CREATE TYPE ${qualifiedName} AS ENUM (${vals});`,
547
- `END IF;`,
548
- `END $$`
549
- ].join('\n'),
550
- `CREATE CAST (text AS ${qualifiedName}) WITH INOUT AS IMPLICIT`
551
- ];
518
+ return getPgGrammar(pgSchema).createEnumType(typeName, values);
552
519
  }
553
520
 
554
- // --- MySQL column definition ---
521
+ // --- Column definition (delegated to Grammar) ---
555
522
 
556
523
  function mysqlColumnDef(col: ColumnSchema): string {
557
- let def = `${q('mysql', col.name)} ${mysqlTypeDef(col)}`;
558
-
559
- if (col.unsigned) def += ' UNSIGNED';
560
- if (!col.nullable) def += ' NOT NULL';
561
- if (col.autoIncrement) def += ' AUTO_INCREMENT';
562
-
563
- if (col.defaultExpression) {
564
- def += ` DEFAULT ${col.defaultExpression}`;
565
- } else if (col.defaultValue !== undefined) {
566
- def += ` DEFAULT '${escapeStr(String(col.defaultValue), 'mysql')}'`;
567
- }
568
-
569
- if (col.onUpdateExpression) {
570
- def += ` ON UPDATE ${col.onUpdateExpression}`;
571
- }
572
-
573
- return def;
574
- }
575
-
576
- function mysqlTypeDef(col: ColumnSchema): string {
577
- switch (col.type) {
578
- case 'varchar':
579
- return `VARCHAR(${col.size || 255})`;
580
- case 'char':
581
- return `CHAR(${col.size || 1})`;
582
- case 'tinyint':
583
- return col.size === 1 ? 'TINYINT(1)' : 'TINYINT';
584
- case 'smallint':
585
- return 'SMALLINT';
586
- case 'int':
587
- return 'INT';
588
- case 'bigint':
589
- return 'BIGINT';
590
- case 'float':
591
- return 'FLOAT';
592
- case 'double':
593
- return 'DOUBLE';
594
- case 'decimal':
595
- if (col.size === undefined) return 'DECIMAL';
596
- return col.scale !== undefined ? `DECIMAL(${col.size},${col.scale})` : `DECIMAL(${col.size})`;
597
- case 'boolean':
598
- return 'TINYINT(1)';
599
- case 'date':
600
- return 'DATE';
601
- case 'datetime':
602
- return 'DATETIME';
603
- case 'timestamp':
604
- return 'TIMESTAMP';
605
- case 'text':
606
- return 'TEXT';
607
- case 'binary':
608
- return `BINARY(${col.size || 16})`;
609
- case 'blob':
610
- return 'BLOB';
611
- case 'json':
612
- return 'JSON';
613
- case 'point':
614
- return 'POINT';
615
- case 'enum':
616
- if (col.enumValues) {
617
- const vals = col.enumValues.map(v => `'${escapeStr(v, 'mysql')}'`).join(',');
618
- return `ENUM(${vals})`;
619
- }
620
- return 'VARCHAR(255)';
621
- default:
622
- return col.type.toUpperCase();
623
- }
524
+ return getGrammar('mysql').columnDef(col);
624
525
  }
625
526
 
626
- // --- PostgreSQL column definition ---
627
-
628
527
  function pgColumnDef(col: ColumnSchema, pgSchema?: string): string {
629
- let typeDef: string;
630
- if (col.autoIncrement) {
631
- // Use SERIAL/BIGSERIAL for auto-increment
632
- typeDef = col.type === 'bigint' ? 'BIGSERIAL' : 'SERIAL';
633
- } else {
634
- typeDef = pgTypeDef(col, pgSchema);
635
- }
636
-
637
- let def = `${q('postgres', col.name)} ${typeDef}`;
638
-
639
- if (!col.nullable && !col.autoIncrement) def += ' NOT NULL';
640
-
641
- if (!col.autoIncrement) {
642
- if (col.defaultExpression) {
643
- def += ` DEFAULT ${col.defaultExpression}`;
644
- } else if (col.defaultValue !== undefined) {
645
- def += ` DEFAULT '${escapeStr(String(col.defaultValue))}'`;
646
- }
647
- }
648
-
649
- return def;
528
+ return getGrammar('postgres', pgSchema).columnDef(col);
650
529
  }
651
530
 
652
531
  function pgTypeDef(col: ColumnSchema, pgSchema?: string): string {
653
- switch (col.type) {
654
- case 'varchar':
655
- return col.size ? `VARCHAR(${col.size})` : 'VARCHAR';
656
- case 'char':
657
- return `CHAR(${col.size || 1})`;
658
- case 'smallint':
659
- return 'SMALLINT';
660
- case 'int':
661
- case 'integer':
662
- return 'INTEGER';
663
- case 'bigint':
664
- return 'BIGINT';
665
- case 'real':
666
- case 'float':
667
- return 'REAL';
668
- case 'double precision':
669
- case 'double':
670
- return 'DOUBLE PRECISION';
671
- case 'decimal':
672
- case 'numeric':
673
- if (col.size === undefined) return 'NUMERIC';
674
- return col.scale !== undefined ? `NUMERIC(${col.size},${col.scale})` : `NUMERIC(${col.size})`;
675
- case 'boolean':
676
- return 'BOOLEAN';
677
- case 'date':
678
- return 'DATE';
679
- case 'timestamp':
680
- return 'TIMESTAMP';
681
- case 'timestamptz':
682
- return 'TIMESTAMPTZ';
683
- case 'text':
684
- return 'TEXT';
685
- case 'bytea':
686
- return 'BYTEA';
687
- case 'json':
688
- return 'JSON';
689
- case 'jsonb':
690
- return 'JSONB';
691
- case 'uuid':
692
- return 'UUID';
693
- case 'enum':
694
- return col.enumTypeName ? qType(col.enumTypeName, pgSchema) : 'TEXT';
695
- default:
696
- return col.type.toUpperCase();
697
- }
532
+ return getPgGrammar(pgSchema).columnType(col);
698
533
  }
699
534
 
700
535
  // --- AFTER clause helpers (MySQL) ---
@@ -737,35 +572,23 @@ function findAfterClauseForExisting(colName: string, diff: TableDiff, dialect: D
737
572
  }
738
573
 
739
574
  function q(dialect: Dialect, name: string): string {
740
- return quoteId(dialect, name);
575
+ return getGrammar(dialect).quote(name);
741
576
  }
742
577
 
743
578
  function qTable(dialect: Dialect, name: string, pgSchema?: string): string {
744
- if (dialect === 'postgres' && pgSchema && pgSchema !== 'public') {
745
- return `${quoteId(dialect, pgSchema)}.${quoteId(dialect, name)}`;
746
- }
747
- return quoteId(dialect, name);
579
+ return getGrammar(dialect, pgSchema).qualifiedTable(name);
748
580
  }
749
581
 
750
582
  function qType(typeName: string, pgSchema?: string): string {
751
- if (pgSchema && pgSchema !== 'public') {
752
- return `${quoteId('postgres', pgSchema)}.${quoteId('postgres', typeName)}`;
753
- }
754
- return quoteId('postgres', typeName);
583
+ return getPgGrammar(pgSchema).qualifiedType(typeName);
755
584
  }
756
585
 
757
586
  function pgRegclass(name: string, pgSchema?: string): string {
758
- // Build a regclass-compatible identifier string with proper quoting for use inside SQL string literals
759
- if (pgSchema && pgSchema !== 'public') {
760
- return `${quoteId('postgres', pgSchema)}.${quoteId('postgres', name)}`;
761
- }
762
- return quoteId('postgres', name);
587
+ // Same form as qualifiedType used in SQL string literals (quoted identifiers).
588
+ return getPgGrammar(pgSchema).qualifiedType(name);
763
589
  }
764
590
 
765
591
  function escapeStr(s: string, dialect?: Dialect): string {
766
- // MySQL also treats backslashes as escape characters in string literals
767
- if (dialect === 'mysql') {
768
- s = s.replace(/\\/g, '\\\\');
769
- }
592
+ if (dialect === 'mysql') s = s.replace(/\\/g, '\\\\');
770
593
  return s.replace(/'/g, "''");
771
594
  }
@@ -5,6 +5,11 @@ import { getSourceMigrationsDir } from '../helpers';
5
5
  import { COMMENT_PREFIX } from './ddl-generator';
6
6
 
7
7
  export function generateMigrationFile(statements: string[], description: string): string {
8
+ return writeMigrationFile(buildFileContent(statements), description);
9
+ }
10
+
11
+ /** Write pre-rendered file content (e.g. builder-based) to a timestamped migration file. */
12
+ export function writeMigrationFile(content: string, description: string): string {
8
13
  const migrationsDir = getSourceMigrationsDir();
9
14
 
10
15
  if (!existsSync(migrationsDir)) {
@@ -16,7 +21,6 @@ export function generateMigrationFile(statements: string[], description: string)
16
21
  const filename = `${timestamp}_${slug}.ts`;
17
22
  const filePath = path.join(migrationsDir, filename);
18
23
 
19
- const content = buildFileContent(statements);
20
24
  writeFileSync(filePath, content, 'utf8');
21
25
 
22
26
  return filePath;
@@ -22,6 +22,8 @@ export interface ColumnSchema {
22
22
  isIdentity?: boolean; // PG only: true if GENERATED ... AS IDENTITY (vs sequence-backed serial)
23
23
  sequenceName?: string; // PG only: actual sequence name from pg_get_serial_sequence()
24
24
  ordinalPosition: number; // 1-based column order
25
+ /** MySQL only — positioning hint for ALTER TABLE ADD COLUMN. `null` = FIRST, string = AFTER <col>, undefined = no clause. */
26
+ afterColumn?: string | null;
25
27
  }
26
28
 
27
29
  export interface IndexSchema {
@@ -1,5 +1,4 @@
1
1
  import { ClassType, getClassName } from '@deepkit/core';
2
- import { Logger } from '@deepkit/logger';
3
2
  import { MySQLConnection, MySQLDatabaseAdapter as BaseMySQLDatabaseAdapter } from '@deepkit/mysql';
4
3
  import { Database, DatabaseSession } from '@deepkit/orm';
5
4
  import { isNonUndefined } from '@deepkit/sql';
@@ -7,7 +6,7 @@ import { databaseAnnotation, ReflectionKind, Type } from '@deepkit/type';
7
6
  import { PoolConfig } from 'mariadb';
8
7
 
9
8
  import { Coordinate } from '.';
10
- import { getAppConfig, r } from '../app/resolver';
9
+ import { getAppConfig } from '../app/resolver';
11
10
  import { globalState } from '../app/state';
12
11
  import { BaseDatabase } from './common';
13
12
 
@@ -120,21 +119,14 @@ export function createMySQLDatabase(
120
119
  entities.push(...globalState.additionalEntities);
121
120
  }
122
121
 
123
- super(adapter, entities);
124
-
122
+ // Mark the adapter so acquireSessionLock can lazy-init the _locks table on first use.
123
+ // Constructor-time fire-and-forget would race with pool teardown for short-lived CLI
124
+ // commands like migration:reset that don't issue any DB work.
125
125
  if (enableLocksTable) {
126
- this.rawExecute(
127
- `CREATE TABLE IF NOT EXISTS \`_locks\` (
128
- \`key\` VARCHAR(255) NOT NULL PRIMARY KEY,
129
- \`createdAt\` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
130
- \`lastTouched\` DATETIME
131
- )`
132
- )
133
- .then(() => this.rawExecute(`DELETE FROM _locks WHERE lastTouched < NOW() - INTERVAL 1 HOUR`))
134
- .catch(err => {
135
- r(Logger).error(`Could not create _locks table: %s`, err);
136
- });
126
+ (adapter as unknown as { _enableLocksTable: boolean })._enableLocksTable = true;
137
127
  }
128
+
129
+ super(adapter, entities);
138
130
  }
139
131
  };
140
132
  }
@@ -1,12 +1,11 @@
1
1
  import { ClassType } from '@deepkit/core';
2
- import { Logger } from '@deepkit/logger';
3
2
  import { Database, DatabaseSession } from '@deepkit/orm';
4
3
  import { PostgresConnection, PostgresDatabaseAdapter as BasePostgresDatabaseAdapter } from '@deepkit/postgres';
5
4
  import { isNonUndefined } from '@deepkit/sql';
6
5
  import { ReflectionKind, Type } from '@deepkit/type';
7
6
  import { PoolConfig } from 'pg';
8
7
 
9
- import { getAppConfig, r } from '../app/resolver';
8
+ import { getAppConfig } from '../app/resolver';
10
9
  import { globalState } from '../app/state';
11
10
  import { BaseDatabase } from './common';
12
11
 
@@ -46,6 +45,7 @@ export class PostgresDatabaseAdapter extends BasePostgresDatabaseAdapter {
46
45
  }
47
46
 
48
47
  export interface IPostgresDatabaseAdapterConfig extends PoolConfig {
48
+ /** Accepted for cross-dialect API parity with createMySQLDatabase. PG uses `pg_advisory_xact_lock` for session locks — no table needed. */
49
49
  enableLocksTable?: boolean;
50
50
  }
51
51
 
@@ -58,7 +58,7 @@ export function createPostgresDatabase(
58
58
  const appConfig = getAppConfig();
59
59
  const isProduction = appConfig.APP_ENV === 'production';
60
60
 
61
- const { enableLocksTable, ...otherConfig } = config;
61
+ const { enableLocksTable: _enableLocksTable, ...otherConfig } = config;
62
62
 
63
63
  const ssl = appConfig.PG_SSL ? { rejectUnauthorized: appConfig.PG_SSL_REJECT_UNAUTHORIZED ?? true } : undefined;
64
64
 
@@ -81,20 +81,6 @@ export function createPostgresDatabase(
81
81
  }
82
82
 
83
83
  super(adapter, entities);
84
-
85
- if (enableLocksTable) {
86
- this.rawExecute(
87
- `CREATE TABLE IF NOT EXISTS "_locks" (
88
- "key" VARCHAR(255) NOT NULL PRIMARY KEY,
89
- "createdAt" TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
90
- "lastTouched" TIMESTAMP
91
- )`
92
- )
93
- .then(() => this.rawExecute(`DELETE FROM "_locks" WHERE "lastTouched" < NOW() - INTERVAL '1 hour'`))
94
- .catch(err => {
95
- r(Logger).error(`Could not create _locks table: %s`, err);
96
- });
97
- }
98
84
  }
99
85
  };
100
86
  }
@@ -0,0 +1,85 @@
1
+ import { ColumnSchema, ForeignKeySchema, IndexSchema } from '../migration/create/schema-model';
2
+ import { BlueprintBase } from './BlueprintBase';
3
+ import { ColumnDefinition } from './ColumnDefinition';
4
+
5
+ /**
6
+ * Alter-table blueprint. Records discrete operations (add/drop/rename/change column,
7
+ * add/drop index/FK, set/drop primary key) for an existing table.
8
+ *
9
+ * Inherits all column-type methods from BlueprintBase — by default a column added via
10
+ * `t.string('email')` becomes an ADD COLUMN. Call `.change()` on the returned ColumnDefinition
11
+ * to mark it as a modification (MODIFY COLUMN / ALTER COLUMN) instead.
12
+ */
13
+ export class AlterBlueprint extends BlueprintBase {
14
+ public readonly addedColumns: ColumnSchema[] = [];
15
+ public readonly modifiedColumns: ColumnSchema[] = [];
16
+ public readonly droppedColumns: string[] = [];
17
+ public readonly renamedColumns: { from: string; to: string }[] = [];
18
+ public readonly addedIndexes: IndexSchema[] = [];
19
+ public readonly droppedIndexes: string[] = [];
20
+ public readonly addedForeignKeys: ForeignKeySchema[] = [];
21
+ public readonly droppedForeignKeys: string[] = [];
22
+ public newPrimaryKey?: string[];
23
+ public dropsPrimaryKey = false;
24
+
25
+ protected addColumnImpl(col: ColumnSchema): ColumnDefinition {
26
+ this.addedColumns.push(col);
27
+ return new ColumnDefinition(this, col);
28
+ }
29
+
30
+ protected addIndexImpl(idx: IndexSchema): void {
31
+ this.addedIndexes.push(idx);
32
+ }
33
+
34
+ protected addForeignKeyImpl(fk: ForeignKeySchema): void {
35
+ this.addedForeignKeys.push(fk);
36
+ }
37
+
38
+ dropColumn(name: string): this {
39
+ this.droppedColumns.push(name);
40
+ return this;
41
+ }
42
+
43
+ renameColumn(from: string, to: string): this {
44
+ this.renamedColumns.push({ from, to });
45
+ return this;
46
+ }
47
+
48
+ /** Drop an index by name. */
49
+ dropIndex(name: string): this {
50
+ this.droppedIndexes.push(name);
51
+ return this;
52
+ }
53
+
54
+ /** Alias of dropIndex — same op at the SQL level, kept for readability. */
55
+ dropUnique(name: string): this {
56
+ return this.dropIndex(name);
57
+ }
58
+
59
+ /** Drop a foreign key constraint by name. */
60
+ dropForeign(name: string): this {
61
+ this.droppedForeignKeys.push(name);
62
+ return this;
63
+ }
64
+
65
+ /** Set / replace the table's primary key. */
66
+ primary(columns: string[]): this {
67
+ this.newPrimaryKey = columns;
68
+ return this;
69
+ }
70
+
71
+ /** Drop the existing primary key. Combine with `.primary([...])` to replace. */
72
+ dropPrimary(): this {
73
+ this.dropsPrimaryKey = true;
74
+ return this;
75
+ }
76
+
77
+ /** Called by ColumnDefinition.change() — moves a column from added → modified. */
78
+ markColumnAsModified(col: ColumnSchema): void {
79
+ const idx = this.addedColumns.indexOf(col);
80
+ if (idx >= 0) {
81
+ this.addedColumns.splice(idx, 1);
82
+ this.modifiedColumns.push(col);
83
+ }
84
+ }
85
+ }
@@ -0,0 +1,43 @@
1
+ import { ColumnSchema, ForeignKeySchema, IndexSchema, TableSchema } from '../migration/create/schema-model';
2
+ import { BlueprintBase } from './BlueprintBase';
3
+ import { ColumnDefinition } from './ColumnDefinition';
4
+
5
+ /** Create-table blueprint. Records columns + indexes + FKs and renders a TableSchema. */
6
+ export class Blueprint extends BlueprintBase {
7
+ public readonly columns: ColumnSchema[] = [];
8
+ public readonly indexes: IndexSchema[] = [];
9
+ public readonly foreignKeys: ForeignKeySchema[] = [];
10
+
11
+ protected addColumnImpl(col: ColumnSchema): ColumnDefinition {
12
+ this.columns.push(col);
13
+ return new ColumnDefinition(this, col);
14
+ }
15
+
16
+ protected addIndexImpl(idx: IndexSchema): void {
17
+ this.indexes.push(idx);
18
+ }
19
+
20
+ protected addForeignKeyImpl(fk: ForeignKeySchema): void {
21
+ this.foreignKeys.push(fk);
22
+ }
23
+
24
+ /** Composite primary key — replaces single-column .primary() flag with a multi-column PK. */
25
+ primary(columns: string[]): this {
26
+ for (const col of this.columns) {
27
+ col.isPrimaryKey = columns.includes(col.name);
28
+ }
29
+ return this;
30
+ }
31
+
32
+ build(): TableSchema {
33
+ this.columns.forEach((col, i) => {
34
+ col.ordinalPosition = i + 1;
35
+ });
36
+ return {
37
+ name: this.tableName,
38
+ columns: this.columns,
39
+ indexes: this.indexes,
40
+ foreignKeys: this.foreignKeys
41
+ };
42
+ }
43
+ }