@stacksjs/database 0.70.240 → 0.70.241

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.
@@ -131,6 +131,21 @@ export declare function generateMigrations(options?: GenerateMigrationsOptions):
131
131
  */
132
132
  export declare function referencesUndefinedType(statement: string, dangling: string[]): boolean;
133
133
  export declare function findDanglingTypeReferences(statements: string[]): string[];
134
+ /**
135
+ * Define the enum types an ALTER needs but nothing in the batch creates.
136
+ *
137
+ * Postgres enum columns are backed by a named type, and bun-query-builder names
138
+ * it `<table>_<column>_type` when it creates the table. A column that becomes an
139
+ * enum *later* gets an `ALTER … TYPE "<table>_<column>_type"` naming a type that
140
+ * was never created, because the `CREATE TYPE` only ever accompanied a
141
+ * `CREATE TABLE`.
142
+ *
143
+ * The values are right there in the plan - the model declared them - so the type
144
+ * can be created instead of the statement being thrown away. Throwing it away
145
+ * left the column as it was, so a model change quietly did not happen and the
146
+ * next diff proposed the same thing again forever.
147
+ */
148
+ export declare function createMissingEnumTypes(dangling: string[], plan: MigrationPlanLike | undefined): { statements: string[], defined: string[] };
134
149
  /**
135
150
  * SQLite supports a nullable foreign key on `ADD COLUMN`, but not a later
136
151
  * `ADD CONSTRAINT`. bun-query-builder emits the new relation column without
@@ -192,6 +207,7 @@ declare interface MigrationPlanColumn {
192
207
  table?: string
193
208
  column?: string
194
209
  }
210
+ enumValues?: string[]
195
211
  }
196
212
  declare interface MigrationPlanTable {
197
213
  table?: string
@@ -751,9 +751,17 @@ export async function generateMigrations(options = {}) {
751
751
  if (result.hasChanges && sqlStatements.length > 0) {
752
752
  const dangling = findDanglingTypeReferences(sqlStatements);
753
753
  if (dangling.length > 0) {
754
- const before = sqlStatements.length;
755
- sqlStatements = sqlStatements.filter((statement) => !referencesUndefinedType(statement, dangling));
756
- log.warn(`[migration] Skipped ${before - sqlStatements.length} generated statement(s) referencing enum type(s) nothing creates (${dangling.slice(0, 3).join(", ")}${dangling.length > 3 ? ", \u2026" : ""}). This is a bug in the migration generator, not in your models.`);
754
+ const created = createMissingEnumTypes(dangling, result.plan);
755
+ if (created.statements.length > 0) {
756
+ sqlStatements = [...created.statements, ...sqlStatements];
757
+ log.debug(`[migration] Created ${created.statements.length} enum type(s) an ALTER needed: ${created.defined.join(", ")}`);
758
+ }
759
+ const unresolved = dangling.filter((name) => !created.defined.includes(name));
760
+ if (unresolved.length > 0) {
761
+ const before = sqlStatements.length;
762
+ sqlStatements = sqlStatements.filter((statement) => !referencesUndefinedType(statement, unresolved));
763
+ log.warn(`[migration] Skipped ${before - sqlStatements.length} generated statement(s) referencing enum type(s) nothing creates and no model defines values for (${unresolved.slice(0, 3).join(", ")}${unresolved.length > 3 ? ", \u2026" : ""}).`);
764
+ }
757
765
  }
758
766
  }
759
767
  if (result.hasChanges) {
@@ -787,6 +795,30 @@ export function findDanglingTypeReferences(statements) {
787
795
  }
788
796
  return [...referenced].filter((name) => !defined.has(name)).sort();
789
797
  }
798
+ export function createMissingEnumTypes(dangling, plan) {
799
+ if (dangling.length === 0)
800
+ return { statements: [], defined: [] };
801
+ const values = new Map;
802
+ for (const table of plan?.tables ?? []) {
803
+ if (!table.table)
804
+ continue;
805
+ for (const column of table.columns ?? []) {
806
+ if (!column.name || !column.enumValues?.length)
807
+ continue;
808
+ values.set(`${table.table}_${column.name}_type`, column.enumValues);
809
+ }
810
+ }
811
+ const statements = [], defined = [];
812
+ for (const name of dangling) {
813
+ const members = values.get(name);
814
+ if (!members?.length)
815
+ continue;
816
+ const literals = members.map((member) => `'${String(member).replaceAll("'", "''")}'`).join(", ");
817
+ statements.push(`DO $$ BEGIN CREATE TYPE "${name}" AS ENUM (${literals}); EXCEPTION WHEN duplicate_object THEN null; END $$;`);
818
+ defined.push(name);
819
+ }
820
+ return { statements, defined };
821
+ }
790
822
  export function inlineSqliteAddedColumnReferences(statements, plan) {
791
823
  const references = new Map;
792
824
  for (const table of plan?.tables ?? []) {
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@stacksjs/database",
3
3
  "type": "module",
4
4
  "sideEffects": false,
5
- "version": "0.70.240",
5
+ "version": "0.70.241",
6
6
  "description": "The Stacks database integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -54,19 +54,19 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@stacksjs/ts-validation": "^0.5.0",
57
- "bun-query-builder": "^0.2.2",
57
+ "bun-query-builder": "^0.2.3",
58
58
  "dynamodb-tooling": "^0.3.2"
59
59
  },
60
60
  "devDependencies": {
61
- "@stacksjs/cli": "0.70.240",
62
- "@stacksjs/config": "0.70.240",
63
- "@stacksjs/logging": "0.70.240",
64
- "@stacksjs/router": "0.70.240",
61
+ "@stacksjs/cli": "0.70.241",
62
+ "@stacksjs/config": "0.70.241",
63
+ "@stacksjs/logging": "0.70.241",
64
+ "@stacksjs/router": "0.70.241",
65
65
  "better-dx": "^0.2.17",
66
- "@stacksjs/path": "0.70.240",
67
- "@stacksjs/query-builder": "0.70.240",
68
- "@stacksjs/storage": "0.70.240",
69
- "@stacksjs/strings": "0.70.240",
70
- "@stacksjs/utils": "0.70.240"
66
+ "@stacksjs/path": "0.70.241",
67
+ "@stacksjs/query-builder": "0.70.241",
68
+ "@stacksjs/storage": "0.70.241",
69
+ "@stacksjs/strings": "0.70.241",
70
+ "@stacksjs/utils": "0.70.241"
71
71
  }
72
72
  }