@stacksjs/database 0.70.229 → 0.70.230

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.
@@ -87,7 +87,12 @@ CREATE TABLE IF NOT EXISTS failed_jobs (
87
87
  queue TEXT NOT NULL,
88
88
  payload TEXT NOT NULL,
89
89
  exception TEXT NOT NULL,
90
- failed_at DATETIME DEFAULT CURRENT_TIMESTAMP
90
+ attempts INTEGER,
91
+ max_attempts INTEGER,
92
+ duration_ms INTEGER,
93
+ failed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
94
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
95
+ updated_at DATETIME
91
96
  );
92
97
 
93
98
  -- Create job_batches table
@@ -179,7 +184,12 @@ CREATE TABLE IF NOT EXISTS failed_jobs (
179
184
  queue VARCHAR(255) NOT NULL,
180
185
  payload LONGTEXT NOT NULL,
181
186
  exception LONGTEXT NOT NULL,
182
- failed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
187
+ attempts INT NULL,
188
+ max_attempts INT NULL,
189
+ duration_ms INT NULL,
190
+ failed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
191
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
192
+ updated_at TIMESTAMP NULL
183
193
  );
184
194
 
185
195
  -- Create job_batches table
@@ -256,7 +266,12 @@ CREATE TABLE IF NOT EXISTS failed_jobs (
256
266
  queue VARCHAR(255) NOT NULL,
257
267
  payload TEXT NOT NULL,
258
268
  exception TEXT NOT NULL,
259
- failed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
269
+ attempts INTEGER,
270
+ max_attempts INTEGER,
271
+ duration_ms INTEGER,
272
+ failed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
273
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
274
+ updated_at TIMESTAMP
260
275
  );
261
276
 
262
277
  -- Create job_batches table
@@ -342,7 +357,12 @@ CREATE TABLE IF NOT EXISTS queue_circuit_state (
342
357
  queue TEXT NOT NULL,
343
358
  payload TEXT NOT NULL,
344
359
  exception TEXT NOT NULL,
345
- failed_at DATETIME DEFAULT CURRENT_TIMESTAMP
360
+ attempts INTEGER,
361
+ max_attempts INTEGER,
362
+ duration_ms INTEGER,
363
+ failed_at DATETIME DEFAULT CURRENT_TIMESTAMP,
364
+ created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
365
+ updated_at DATETIME
346
366
  )`);
347
367
  sqlite.close();
348
368
  } else if (driver === "mysql") {
@@ -364,7 +384,12 @@ CREATE TABLE IF NOT EXISTS queue_circuit_state (
364
384
  queue VARCHAR(255) NOT NULL,
365
385
  payload LONGTEXT NOT NULL,
366
386
  exception LONGTEXT NOT NULL,
367
- failed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
387
+ attempts INT NULL,
388
+ max_attempts INT NULL,
389
+ duration_ms INT NULL,
390
+ failed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
391
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
392
+ updated_at TIMESTAMP NULL
368
393
  )`).execute();
369
394
  } else if (driver === "postgres") {
370
395
  const { db } = await import("../utils");
@@ -385,7 +410,12 @@ CREATE TABLE IF NOT EXISTS queue_circuit_state (
385
410
  queue VARCHAR(255) NOT NULL,
386
411
  payload TEXT NOT NULL,
387
412
  exception TEXT NOT NULL,
388
- failed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
413
+ attempts INTEGER,
414
+ max_attempts INTEGER,
415
+ duration_ms INTEGER,
416
+ failed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
417
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
418
+ updated_at TIMESTAMP
389
419
  )`).execute();
390
420
  }
391
421
  console.log("\u2713 Jobs and failed_jobs tables created");
@@ -964,14 +964,26 @@ export async function createQueryLogsTable() {
964
964
  migrationContent += ` .addColumn('connection', 'varchar(255)')
965
965
  `;
966
966
  migrationContent += ` .addColumn('status', 'varchar(50)')
967
+ `;
968
+ migrationContent += ` .addColumn('error', 'text')
967
969
  `;
968
970
  migrationContent += ` .addColumn('executed_at', 'timestamp', col => col.notNull())
971
+ `;
972
+ migrationContent += ` .addColumn('trace', 'text')
969
973
  `;
970
974
  migrationContent += ` .addColumn('model', 'varchar(255)')
971
975
  `;
972
976
  migrationContent += ` .addColumn('method', 'varchar(255)')
977
+ `;
978
+ migrationContent += ` .addColumn('file', 'text')
979
+ `;
980
+ migrationContent += ` .addColumn('line', 'integer')
981
+ `;
982
+ migrationContent += ` .addColumn('memory_usage', 'real')
973
983
  `;
974
984
  migrationContent += ` .addColumn('rows_affected', 'integer')
985
+ `;
986
+ migrationContent += ` .addColumn('transaction_id', 'varchar(255)')
975
987
  `;
976
988
  migrationContent += ` .addColumn('optimization_suggestions', 'json')
977
989
  `;
@@ -980,6 +992,8 @@ export async function createQueryLogsTable() {
980
992
  migrationContent += ` .addColumn('indexes_used', 'json')
981
993
  `;
982
994
  migrationContent += ` .addColumn('missing_indexes', 'json')
995
+ `;
996
+ migrationContent += ` .addColumn('explain_plan', 'json')
983
997
  `;
984
998
  migrationContent += ` .addColumn('tags', 'json')
985
999
  `;
@@ -1056,14 +1070,26 @@ export async function createPostgresQueryLogsTable() {
1056
1070
  migrationContent += ` .addColumn('connection', 'varchar(255)')
1057
1071
  `;
1058
1072
  migrationContent += ` .addColumn('status', 'varchar(50)')
1073
+ `;
1074
+ migrationContent += ` .addColumn('error', 'text')
1059
1075
  `;
1060
1076
  migrationContent += ` .addColumn('executed_at', 'timestamp', col => col.notNull())
1077
+ `;
1078
+ migrationContent += ` .addColumn('trace', 'text')
1061
1079
  `;
1062
1080
  migrationContent += ` .addColumn('model', 'varchar(255)')
1063
1081
  `;
1064
1082
  migrationContent += ` .addColumn('method', 'varchar(255)')
1083
+ `;
1084
+ migrationContent += ` .addColumn('file', 'text')
1085
+ `;
1086
+ migrationContent += ` .addColumn('line', 'integer')
1087
+ `;
1088
+ migrationContent += ` .addColumn('memory_usage', 'double precision')
1065
1089
  `;
1066
1090
  migrationContent += ` .addColumn('rows_affected', 'integer')
1091
+ `;
1092
+ migrationContent += ` .addColumn('transaction_id', 'varchar(255)')
1067
1093
  `;
1068
1094
  migrationContent += ` .addColumn('optimization_suggestions', 'jsonb')
1069
1095
  `;
@@ -1072,6 +1098,8 @@ export async function createPostgresQueryLogsTable() {
1072
1098
  migrationContent += ` .addColumn('indexes_used', 'jsonb')
1073
1099
  `;
1074
1100
  migrationContent += ` .addColumn('missing_indexes', 'jsonb')
1101
+ `;
1102
+ migrationContent += ` .addColumn('explain_plan', 'jsonb')
1075
1103
  `;
1076
1104
  migrationContent += ` .addColumn('tags', 'jsonb')
1077
1105
  `;
package/dist/index.d.ts CHANGED
@@ -95,6 +95,8 @@ export * from './auth-tables';
95
95
  export * from './uuid-columns';
96
96
  // Schema-diff guards so trait-managed columns aren't proposed for dropping (stacksjs/stacks#2075)
97
97
  export * from './managed-columns';
98
+ // Foreign keys a model gets from `belongsTo` rather than from `attributes`
99
+ export * from './relation-columns';
98
100
  // Notification tables migration (stacksjs/stacks#1937)
99
101
  export { migrateNotificationTables } from './notification-tables';
100
102
  // RBAC tables migration (stacksjs/stacks#1941 Phase A)
package/dist/index.js CHANGED
@@ -24,6 +24,7 @@ export * from "./custom";
24
24
  export * from "./auth-tables";
25
25
  export * from "./uuid-columns";
26
26
  export * from "./managed-columns";
27
+ export * from "./relation-columns";
27
28
  export { migrateNotificationTables } from "./notification-tables";
28
29
  export { migrateRbacTables } from "./rbac-tables";
29
30
  export * from "./sql-helpers";
@@ -9,7 +9,25 @@ import type { MigrationOperation } from '@stacksjs/query-builder';
9
9
  export declare function frameworkManagedColumns(): Promise<Map<string, Set<string>>>;
10
10
  /** True when `op` would drop a column the framework guarantees at runtime. */
11
11
  export declare function isManagedColumnDrop(op: ColumnOp, managed: Map<string, Set<string>>): boolean;
12
- /** Return `operations` without any drop of a framework-managed column. */
12
+ /**
13
+ * Return `operations` without any drop of a framework-managed column, and
14
+ * without the operation that would have carried it out.
15
+ *
16
+ * SQLite has no `DROP COLUMN` before 3.35, so the differ drops one by
17
+ * rebuilding the table without it — and emits that single rebuild statement as
18
+ * TWO operations: a `drop_column` naming the casualty, and a `rebuild_table`
19
+ * whose `sql` is the very same statement. Filtering only the `drop_column`
20
+ * left the rebuild in the list, which is the worst of both: the confirmation
21
+ * gate still warns on every run, and answering yes still drops the column the
22
+ * guard exists to protect.
23
+ *
24
+ * So a rebuild that IS a suppressed drop goes with it. That can cost an
25
+ * unrelated constraint change riding along in the same rebuild, which is the
26
+ * right trade: a skipped constraint tweak is visible and repeatable, a
27
+ * silently dropped ownership column is neither. `withoutManagedColumnDropSql`
28
+ * has always removed this statement from the generated migration — this is the
29
+ * preview finally agreeing with what actually runs.
30
+ */
13
31
  export declare function withoutManagedColumnDrops<T extends ColumnOp>(operations: T[], managed: Map<string, Set<string>>): T[];
14
32
  /**
15
33
  * Remove generated SQL statements that drop a framework-managed column, so the
@@ -27,5 +45,10 @@ export declare function withoutManagedColumnDropSql(statements: string[], manage
27
45
  * ORM/query-builder graph. Keep in sync with `ensureUsersAuthColumns`.
28
46
  */
29
47
  export declare const USERS_GUARANTEED_COLUMNS: readonly string[];
30
- /** A minimal view of a migration operation — all these helpers need. */
31
- declare type ColumnOp = Pick<MigrationOperation, 'kind' | 'table' | 'column'>;
48
+ /**
49
+ * A minimal view of a migration operation — all these helpers need.
50
+ *
51
+ * `sql` is optional because the pairing check below degrades to a no-op
52
+ * without it, and several callers only have the classified operation.
53
+ */
54
+ declare type ColumnOp = Pick<MigrationOperation, 'kind' | 'table' | 'column'> & { sql?: string }
@@ -9,13 +9,21 @@ export const USERS_GUARANTEED_COLUMNS = [
9
9
  export async function frameworkManagedColumns() {
10
10
  const managed = new Map;
11
11
  managed.set("users", new Set(USERS_GUARANTEED_COLUMNS));
12
+ const add = (table, column) => {
13
+ const columns = managed.get(table) ?? new Set;
14
+ columns.add(column);
15
+ managed.set(table, columns);
16
+ };
12
17
  try {
13
18
  const { findUuidTables } = await import("./uuid-columns");
14
- for (const table of await findUuidTables()) {
15
- const columns = managed.get(table) ?? new Set;
16
- columns.add("uuid");
17
- managed.set(table, columns);
18
- }
19
+ for (const table of await findUuidTables())
20
+ add(table, "uuid");
21
+ } catch {}
22
+ try {
23
+ const { findRelationForeignKeys } = await import("./relation-columns");
24
+ for (const [table, columns] of await findRelationForeignKeys())
25
+ for (const column of columns)
26
+ add(table, column);
19
27
  } catch {}
20
28
  return managed;
21
29
  }
@@ -23,7 +31,12 @@ export function isManagedColumnDrop(op, managed) {
23
31
  return op.kind === "drop_column" && op.column != null && (managed.get(op.table)?.has(op.column) ?? !1);
24
32
  }
25
33
  export function withoutManagedColumnDrops(operations, managed) {
26
- return operations.filter((op) => !isManagedColumnDrop(op, managed));
34
+ const suppressed = new Set(operations.filter((op) => isManagedColumnDrop(op, managed)).map((op) => op.sql ? normalizeSql(op.sql) : "").filter((sql) => sql.length > 0));
35
+ return operations.filter((op) => {
36
+ if (isManagedColumnDrop(op, managed))
37
+ return !1;
38
+ return !(op.sql && suppressed.has(normalizeSql(op.sql)));
39
+ });
27
40
  }
28
41
  function normalizeSql(sql) {
29
42
  return sql.trim().replace(/\s+/g, " ").replace(/;+\s*$/, "");
@@ -1,6 +1,7 @@
1
- import type { MigrationOperation } from '@stacksjs/query-builder';
1
+ import type { MigrationOperation, MigrationPlan } from '@stacksjs/query-builder';
2
2
  import type { Result } from '@stacksjs/error-handling';
3
3
  export type { MigrationResult as MigrationResultType };
4
+ export declare function prepareMigrationModelsDir(): { modelsDir: string, skip: boolean };
4
5
  /**
5
6
  * SQLite compatibility preprocessing for migrations.
6
7
  *
@@ -24,11 +25,23 @@ export type { MigrationResult as MigrationResultType };
24
25
  * (stacksjs/stacks#1916)
25
26
  *
26
27
  * - **Drop-and-delete** (`deleteMigration`): the file is genuinely dead
27
- * — a duplicate CREATE TABLE created by `buddy generate:migrations`
28
- * regenerating against an already-modeled table, or a DROP COLUMN
29
- * migration whose target column never existed. Removing it keeps the
30
- * directory clean and prevents future runs from re-discovering it.
28
+ * — currently limited to an unrecorded duplicate CREATE TABLE created by
29
+ * `buddy generate:migrations`. Recorded files are immutable migration
30
+ * history, regardless of the current live schema.
31
31
  */
32
+ /**
33
+ * Split a migration file into its statements.
34
+ *
35
+ * Comments come out FIRST, before the split on `;`. Splitting first and then
36
+ * dropping the chunks that begin with `--` looks equivalent and is not: a
37
+ * file that opens with a comment header — which every hand-written migration
38
+ * in this repo does — glues that header to its first statement, so the chunk
39
+ * begins with `--` and the statement disappears with the comment. Everything
40
+ * downstream then reasons about a file it has only partly read, and the
41
+ * ADD COLUMN reconciliation below would happily record a file as fully
42
+ * applied while its first column had never been created.
43
+ */
44
+ export declare function sqlStatementsOf(content: string): string[];
32
45
  export declare function preprocessSqliteMigrations(): void;
33
46
  /**
34
47
  * Public bootstrap entry point.
@@ -45,6 +58,25 @@ export declare function preprocessSqliteMigrations(): void;
45
58
  export declare function ensureDatabaseReady(): Promise<void>;
46
59
  /** Test seam: forget that the bootstrap already ran. */
47
60
  export declare function resetDatabaseBootstrapCache(): void;
61
+ /**
62
+ * The table a single DDL statement acts on, or null when it names none.
63
+ *
64
+ * Only the forms the generator emits are recognised; anything unrecognised
65
+ * comes back null and is therefore kept, which is the safe direction.
66
+ */
67
+ export declare function statementTable(statement: string): string | null;
68
+ /**
69
+ * Drop the statements that act on a table a disabled feature owns.
70
+ *
71
+ * The generator emits a catch-all `auto-misc` migration holding stray alters
72
+ * for every table at once. Its filename names no table, so the file-level gate
73
+ * cannot classify it, and it ran in full: the first statement touching a gated
74
+ * table failed with "relation ... does not exist" and took the whole migration
75
+ * run with it. Gating has to reach inside a file that mixes them.
76
+ *
77
+ * A statement whose table cannot be identified is kept.
78
+ */
79
+ export declare function withoutGatedStatements(sql: string, gated: ReadonlySet<string>): string;
48
80
  /**
49
81
  * Count how many migrations have been recorded as applied in the
50
82
  * `migrations` table. Returns 0 when the table doesn't exist yet
@@ -78,6 +110,7 @@ export declare function resetDatabase(): Promise<Result<string, Error>>;
78
110
  * behind confirmation before spawning the non-interactive migrate action.
79
111
  */
80
112
  export declare function previewPendingMigrations(options?: GenerateMigrationsOptions): Promise<MigrationOperation[]>;
113
+ export declare function preserveMigrationPlanTableOrder(next: MigrationPlan, previous?: MigrationPlan): MigrationPlan;
81
114
  export declare function generateMigrations(options?: GenerateMigrationsOptions): Promise<Result<string, Error>>;
82
115
  /**
83
116
  * Write generated SQL to `database/migrations/` so the runner picks it up.
@@ -90,7 +123,22 @@ export declare function generateMigrations(options?: GenerateMigrationsOptions):
90
123
  * dangling reference is a guaranteed mid-migration failure rather than a
91
124
  * cosmetic problem.
92
125
  */
126
+ /**
127
+ * Whether a statement references one of the enum types nothing creates.
128
+ *
129
+ * Matches on the quoted name so a column or table that merely contains the same
130
+ * word is not caught.
131
+ */
132
+ export declare function referencesUndefinedType(statement: string, dangling: string[]): boolean;
93
133
  export declare function findDanglingTypeReferences(statements: string[]): string[];
134
+ /**
135
+ * SQLite supports a nullable foreign key on `ADD COLUMN`, but not a later
136
+ * `ADD CONSTRAINT`. bun-query-builder emits the new relation column without
137
+ * its reference on incremental SQLite diffs, leaving the live schema weaker
138
+ * than the model. Recover the reference from the generated current plan and
139
+ * declare it inline while the column is added.
140
+ */
141
+ export declare function inlineSqliteAddedColumnReferences(statements: string[], plan: MigrationPlanLike | undefined): string[];
94
142
  /**
95
143
  * Rebuild the whole migration corpus for one dialect, from the models.
96
144
  *
@@ -138,6 +186,20 @@ export declare interface GenerateMigrationsOptions {
138
186
  applyRenames?: boolean
139
187
  fromDb?: boolean
140
188
  }
189
+ declare interface MigrationPlanColumn {
190
+ name?: string
191
+ references?: {
192
+ table?: string
193
+ column?: string
194
+ }
195
+ }
196
+ declare interface MigrationPlanTable {
197
+ table?: string
198
+ columns?: MigrationPlanColumn[]
199
+ }
200
+ declare interface MigrationPlanLike {
201
+ tables?: MigrationPlanTable[]
202
+ }
141
203
  export declare interface RegeneratedCorpus {
142
204
  dialect: string
143
205
  models: number