@stacksjs/database 0.70.161 → 0.70.162
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/auth-tables.d.ts +3 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/managed-columns.d.ts +31 -0
- package/dist/managed-columns.js +46 -0
- package/dist/migrations.js +13 -3
- package/dist/types.js +1 -1
- package/package.json +10 -10
package/dist/auth-tables.d.ts
CHANGED
|
@@ -47,7 +47,9 @@ export declare function usersStripeIdSql(): string;
|
|
|
47
47
|
* password_changed_at, two_factor_secret, two_factor_enabled,
|
|
48
48
|
* stripe_id), each independently try/catch-swallowed so one
|
|
49
49
|
* already-existing column (or a not-yet-existing `users` table) never
|
|
50
|
-
* skips the others.
|
|
50
|
+
* skips the others. The schema-diff guard mirrors this set in
|
|
51
|
+
* `managed-columns.ts` (`USERS_GUARANTEED_COLUMNS`) so the differ never
|
|
52
|
+
* proposes dropping them (stacksjs/stacks#2075) — keep the two in sync. Exported so `buddy migrate`/`migrate:fresh` can
|
|
51
53
|
* call it a second time after the numbered model migrations run — see
|
|
52
54
|
* the call site in {@link migrateAuthTables} for why a single call
|
|
53
55
|
* isn't enough.
|
package/dist/index.d.ts
CHANGED
|
@@ -107,6 +107,8 @@ export * from './custom/index';
|
|
|
107
107
|
export * from './auth-tables';
|
|
108
108
|
// uuid column guarantee for `useUuid` models (stacksjs/status#1 Phase 9)
|
|
109
109
|
export * from './uuid-columns';
|
|
110
|
+
// Schema-diff guards so trait-managed columns aren't proposed for dropping (stacksjs/stacks#2075)
|
|
111
|
+
export * from './managed-columns';
|
|
110
112
|
// Notification tables migration (stacksjs/stacks#1937)
|
|
111
113
|
export { migrateNotificationTables } from './notification-tables';
|
|
112
114
|
// RBAC tables migration (stacksjs/stacks#1941 Phase A)
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ export * from "./drivers";
|
|
|
26
26
|
export * from "./custom";
|
|
27
27
|
export * from "./auth-tables";
|
|
28
28
|
export * from "./uuid-columns";
|
|
29
|
+
export * from "./managed-columns";
|
|
29
30
|
export { migrateNotificationTables } from "./notification-tables";
|
|
30
31
|
export { migrateRbacTables } from "./rbac-tables";
|
|
31
32
|
export * from "./sql-helpers";
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { MigrationOperation } from '@stacksjs/query-builder';
|
|
2
|
+
/**
|
|
3
|
+
* Resolve `table -> protected column names`: the `users` auth/billing columns
|
|
4
|
+
* plus `uuid` on every table backing a `useUuid` model. `findUuidTables` is
|
|
5
|
+
* lazy-imported so this module stays free of the model-walking ORM graph until
|
|
6
|
+
* actually resolving. Best-effort on the uuid side — if that walk fails we still
|
|
7
|
+
* guard the hardcoded `users` columns rather than losing the protection.
|
|
8
|
+
*/
|
|
9
|
+
export declare function frameworkManagedColumns(): Promise<Map<string, Set<string>>>;
|
|
10
|
+
/** True when `op` would drop a column the framework guarantees at runtime. */
|
|
11
|
+
export declare function isManagedColumnDrop(op: ColumnOp, managed: Map<string, Set<string>>): boolean;
|
|
12
|
+
/** Return `operations` without any drop of a framework-managed column. */
|
|
13
|
+
export declare function withoutManagedColumnDrops<T extends ColumnOp>(operations: T[], managed: Map<string, Set<string>>): T[];
|
|
14
|
+
/**
|
|
15
|
+
* Remove generated SQL statements that drop a framework-managed column, so the
|
|
16
|
+
* drop is never written to a migration file. Catches both the direct
|
|
17
|
+
* `ALTER TABLE ... DROP COLUMN` form and, by matching the structured
|
|
18
|
+
* operations' own `sql`, the SQLite table-rebuild form (which drops a column by
|
|
19
|
+
* recreating the table without it). Pass `operations` when available for the
|
|
20
|
+
* rebuild case; the regex alone still covers the common direct form.
|
|
21
|
+
*/
|
|
22
|
+
export declare function withoutManagedColumnDropSql(statements: string[], managed: Map<string, Set<string>>, operations?: MigrationOperation[]): { statements: string[], removed: string[] };
|
|
23
|
+
/**
|
|
24
|
+
* The `users` columns the framework guarantees via `ensureUsersAuthColumns`'s
|
|
25
|
+
* defensive ALTERs (auth-tables.ts) — created OUTSIDE any model's `attributes`.
|
|
26
|
+
* Kept here (a dependency-light module) so the differ guard doesn't drag in the
|
|
27
|
+
* ORM/query-builder graph. Keep in sync with `ensureUsersAuthColumns`.
|
|
28
|
+
*/
|
|
29
|
+
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'>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const USERS_GUARANTEED_COLUMNS = [
|
|
2
|
+
"email_verified_at",
|
|
3
|
+
"password_changed_at",
|
|
4
|
+
"two_factor_secret",
|
|
5
|
+
"two_factor_enabled",
|
|
6
|
+
"two_factor_last_used_step",
|
|
7
|
+
"stripe_id"
|
|
8
|
+
];
|
|
9
|
+
export async function frameworkManagedColumns() {
|
|
10
|
+
const managed = new Map;
|
|
11
|
+
managed.set("users", new Set(USERS_GUARANTEED_COLUMNS));
|
|
12
|
+
try {
|
|
13
|
+
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
|
+
} catch {}
|
|
20
|
+
return managed;
|
|
21
|
+
}
|
|
22
|
+
export function isManagedColumnDrop(op, managed) {
|
|
23
|
+
return op.kind === "drop_column" && op.column != null && (managed.get(op.table)?.has(op.column) ?? !1);
|
|
24
|
+
}
|
|
25
|
+
export function withoutManagedColumnDrops(operations, managed) {
|
|
26
|
+
return operations.filter((op) => !isManagedColumnDrop(op, managed));
|
|
27
|
+
}
|
|
28
|
+
function normalizeSql(sql) {
|
|
29
|
+
return sql.trim().replace(/\s+/g, " ").replace(/;+\s*$/, "");
|
|
30
|
+
}
|
|
31
|
+
const DROP_COLUMN_RE = /ALTER\s+TABLE\s+["'`]?(\w+)["'`]?\s+DROP\s+COLUMN\s+(?:IF\s+EXISTS\s+)?["'`]?(\w+)["'`]?/i;
|
|
32
|
+
export function withoutManagedColumnDropSql(statements, managed, operations = []) {
|
|
33
|
+
const protectedSql = new Set(operations.filter((op) => isManagedColumnDrop(op, managed)).map((op) => normalizeSql(op.sql))), removed = [];
|
|
34
|
+
return { statements: statements.filter((statement) => {
|
|
35
|
+
if (protectedSql.has(normalizeSql(statement))) {
|
|
36
|
+
removed.push(statement);
|
|
37
|
+
return !1;
|
|
38
|
+
}
|
|
39
|
+
const match = statement.match(DROP_COLUMN_RE);
|
|
40
|
+
if (match?.[1] && match[2] && (managed.get(match[1])?.has(match[2]) ?? !1)) {
|
|
41
|
+
removed.push(statement);
|
|
42
|
+
return !1;
|
|
43
|
+
}
|
|
44
|
+
return !0;
|
|
45
|
+
}), removed };
|
|
46
|
+
}
|
package/dist/migrations.js
CHANGED
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
setConfig
|
|
21
21
|
} from "@stacksjs/query-builder";
|
|
22
22
|
import { db } from "./utils";
|
|
23
|
+
import { frameworkManagedColumns, withoutManagedColumnDrops, withoutManagedColumnDropSql } from "./managed-columns";
|
|
23
24
|
import { acquireMigrationLock } from "./migration-lock";
|
|
24
25
|
import { env as envVars } from "@stacksjs/env";
|
|
25
26
|
import { getConnectionDefaults } from "./defaults";
|
|
@@ -454,8 +455,10 @@ export async function previewPendingMigrations(options = {}) {
|
|
|
454
455
|
const dialect = getDialect(), { modelsDir, skip } = prepareMigrationModelsDir();
|
|
455
456
|
if (skip)
|
|
456
457
|
return [];
|
|
457
|
-
const { applyRenames, fromDb } = resolveGenerateOptions(options);
|
|
458
|
-
|
|
458
|
+
const { applyRenames, fromDb } = resolveGenerateOptions(options), operations = (await qbGenerateMigration(modelsDir, { dialect: getQbDialect(), dryRun: !0, applyRenames, fromDb })).operations ?? [];
|
|
459
|
+
if (!operations.some((op) => op.kind === "drop_column"))
|
|
460
|
+
return operations;
|
|
461
|
+
return withoutManagedColumnDrops(operations, await frameworkManagedColumns());
|
|
459
462
|
} catch (error) {
|
|
460
463
|
log.debug(`[migration] preview failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
461
464
|
return [];
|
|
@@ -494,8 +497,15 @@ export async function generateMigrations(options = {}) {
|
|
|
494
497
|
const { applyRenames, fromDb } = resolveGenerateOptions(options);
|
|
495
498
|
log.debug(`[migration] Generating migrations for dialect: ${dialect}, models: ${modelsDir}`);
|
|
496
499
|
const result = await qbGenerateMigration(modelsDir, { dialect: getQbDialect(), dryRun: !0, applyRenames, fromDb });
|
|
500
|
+
let sqlStatements = result.sqlStatements ?? [];
|
|
501
|
+
if (result.hasChanges && sqlStatements.length > 0) {
|
|
502
|
+
const filtered = withoutManagedColumnDropSql(sqlStatements, await frameworkManagedColumns(), result.operations ?? []);
|
|
503
|
+
if (filtered.removed.length > 0)
|
|
504
|
+
log.debug(`[migration] Skipped ${filtered.removed.length} generated drop(s) of framework-managed column(s) (stacksjs/stacks#2075)`);
|
|
505
|
+
sqlStatements = filtered.statements;
|
|
506
|
+
}
|
|
497
507
|
if (result.hasChanges) {
|
|
498
|
-
const written = persistGeneratedMigrations(
|
|
508
|
+
const written = persistGeneratedMigrations(sqlStatements);
|
|
499
509
|
if (written > 0)
|
|
500
510
|
log.success(`Generated ${written} migration file${written === 1 ? "" : "s"}`);
|
|
501
511
|
else
|
package/dist/types.js
CHANGED
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.
|
|
5
|
+
"version": "0.70.162",
|
|
6
6
|
"description": "The Stacks database integration.",
|
|
7
7
|
"author": "Chris Breuer",
|
|
8
8
|
"contributors": [
|
|
@@ -60,15 +60,15 @@
|
|
|
60
60
|
"dynamodb-tooling": "^0.3.2"
|
|
61
61
|
},
|
|
62
62
|
"devDependencies": {
|
|
63
|
-
"@stacksjs/cli": "0.70.
|
|
64
|
-
"@stacksjs/config": "0.70.
|
|
65
|
-
"@stacksjs/logging": "0.70.
|
|
66
|
-
"@stacksjs/router": "0.70.
|
|
63
|
+
"@stacksjs/cli": "0.70.162",
|
|
64
|
+
"@stacksjs/config": "0.70.162",
|
|
65
|
+
"@stacksjs/logging": "0.70.162",
|
|
66
|
+
"@stacksjs/router": "0.70.162",
|
|
67
67
|
"better-dx": "^0.2.17",
|
|
68
|
-
"@stacksjs/path": "0.70.
|
|
69
|
-
"@stacksjs/query-builder": "0.70.
|
|
70
|
-
"@stacksjs/storage": "0.70.
|
|
71
|
-
"@stacksjs/strings": "0.70.
|
|
72
|
-
"@stacksjs/utils": "0.70.
|
|
68
|
+
"@stacksjs/path": "0.70.162",
|
|
69
|
+
"@stacksjs/query-builder": "0.70.162",
|
|
70
|
+
"@stacksjs/storage": "0.70.162",
|
|
71
|
+
"@stacksjs/strings": "0.70.162",
|
|
72
|
+
"@stacksjs/utils": "0.70.162"
|
|
73
73
|
}
|
|
74
74
|
}
|