@stacksjs/database 0.70.376 → 0.70.378

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/index.d.ts CHANGED
@@ -10,6 +10,7 @@ export type {
10
10
  PostgresConfig,
11
11
  SqliteConfig,
12
12
  } from './driver-config';
13
+ export type { SchemaDriftColumn, SchemaDriftReport } from './schema-drift';
13
14
  export type { DeclaredFK, FkAuditResult, FkOrphan, FkOrphanReport, LiveFK } from './fk-audit';
14
15
  export type { DeclaredUnique, LiveUniqueIndex, UniqueAuditResult } from './unique-audit';
15
16
  export type {
@@ -143,6 +144,10 @@ export * from './ensure-database';
143
144
  // Foreign-key audit (stacksjs/stacks#1916) — compare declared
144
145
  // `belongsTo` relationships against live FKs.
145
146
  export { auditForeignKeys, findFkOrphans, getDeclaredFKs, getLiveFKs } from './fk-audit';
147
+ // Schema drift audit — compare live column types against what the models
148
+ // declare. `migrate` only tracks which files have run, so a database built from
149
+ // a wrong migration set reports "up to date" forever while its columns differ.
150
+ export { auditSchemaDrift, formatSchemaDrift } from './schema-drift';
146
151
  // Unique-index drift audit (stacksjs/stacks#1952) — compare declared
147
152
  // `unique: true` attributes / indexes against live UNIQUE indexes.
148
153
  export { auditUniqueIndexes, getDeclaredUniques, getLiveUniqueIndexes } from './unique-audit';
package/dist/index.js CHANGED
@@ -1 +1 @@
1
- export{Database,createDatabase,createMysqlDatabase,createPostgresDatabase,createSqliteDatabase}from"./database";export{detectDriver,driverDefaults,getConfigFromEnv,getConnectionString,mergeWithDefaults,validateDriverConfig}from"./driver-config";export*from"./utils";export*from"./types";export*from"./migrations";export{setQueryTracker,logQuery}from"./query-logger";export{addColumnSafely,backfillInBatches,renameColumnSafely}from"./safe-migrations";export*from"./seeder";export*from"./drivers";export*from"./custom";export*from"./auth-tables";export*from"./uuid-columns";export*from"./managed-columns";export*from"./relation-columns";export{ensureNotificationForeignKeys,migrateNotificationTables}from"./notification-tables";export{migrateRbacTables}from"./rbac-tables";export*from"./trait-tables";export*from"./datetime-columns";export*from"./dialect";export*from"./replicas";export*from"./ddl-constraints";export*from"./vschema";export*from"./sql-helpers";export*from"./defaults";export*from"./migration-dialect";export*from"./migration-path";export*from"./migration-ledger";export*from"./model-sources";export*from"./shadowed-models";export*from"./ensure-database";export{auditForeignKeys,findFkOrphans,getDeclaredFKs,getLiveFKs}from"./fk-audit";export{auditUniqueIndexes,getDeclaredUniques,getLiveUniqueIndexes}from"./unique-audit";export{__flushAfterCommitNow,__pendingAfterCommitCount,enqueueAfterCommit,isInTransaction,runInTransactionScope}from"./transaction-context";export{createQueryBuilder,setConfig}from"@stacksjs/query-builder";export{createDynamo,dynamo,EntityQueryBuilder,generateKeyPattern,parseKeyPattern,buildKey,marshall,unmarshall}from"./drivers/dynamodb";
1
+ export{Database,createDatabase,createMysqlDatabase,createPostgresDatabase,createSqliteDatabase}from"./database";export{detectDriver,driverDefaults,getConfigFromEnv,getConnectionString,mergeWithDefaults,validateDriverConfig}from"./driver-config";export*from"./utils";export*from"./types";export*from"./migrations";export{setQueryTracker,logQuery}from"./query-logger";export{addColumnSafely,backfillInBatches,renameColumnSafely}from"./safe-migrations";export*from"./seeder";export*from"./drivers";export*from"./custom";export*from"./auth-tables";export*from"./uuid-columns";export*from"./managed-columns";export*from"./relation-columns";export{ensureNotificationForeignKeys,migrateNotificationTables}from"./notification-tables";export{migrateRbacTables}from"./rbac-tables";export*from"./trait-tables";export*from"./datetime-columns";export*from"./dialect";export*from"./replicas";export*from"./ddl-constraints";export*from"./vschema";export*from"./sql-helpers";export*from"./defaults";export*from"./migration-dialect";export*from"./migration-path";export*from"./migration-ledger";export*from"./model-sources";export*from"./shadowed-models";export*from"./ensure-database";export{auditForeignKeys,findFkOrphans,getDeclaredFKs,getLiveFKs}from"./fk-audit";export{auditSchemaDrift,formatSchemaDrift}from"./schema-drift";export{auditUniqueIndexes,getDeclaredUniques,getLiveUniqueIndexes}from"./unique-audit";export{__flushAfterCommitNow,__pendingAfterCommitCount,enqueueAfterCommit,isInTransaction,runInTransactionScope}from"./transaction-context";export{createQueryBuilder,setConfig}from"@stacksjs/query-builder";export{createDynamo,dynamo,EntityQueryBuilder,generateKeyPattern,parseKeyPattern,buildKey,marshall,unmarshall}from"./drivers/dynamodb";
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Compare the live schema against the application's models.
3
+ *
4
+ * Only the application's own models under `app/Models/`. The framework defaults
5
+ * are deliberately excluded: an application that does not use the commerce or
6
+ * CMS models has no tables for them, and reporting sixty absent tables would
7
+ * bury the one column that is actually wrong.
8
+ *
9
+ * Never throws. A drift audit that breaks `migrate` is worse than one that does
10
+ * not run, so an introspection failure, a missing permission or an
11
+ * unrecognisable model returns `skipped` and the caller carries on.
12
+ */
13
+ export declare function auditSchemaDrift(): Promise<SchemaDriftReport>;
14
+ /** Re-exported so callers format one way. */
15
+ export declare function formatSchemaDrift(report: SchemaDriftReport): Promise<string>;
16
+ /**
17
+ * Does the live database still look like the models?
18
+ *
19
+ * The companion to `fk-audit.ts`, and the one that catches the quieter failure.
20
+ * `migrate` decides what to do by asking which migration FILES have not
21
+ * executed. It never asks whether the database resembles the models. So a
22
+ * database created from an older, wrong migration set reports "nothing to
23
+ * migrate — your database is already up to date" on every run, forever, while
24
+ * its columns are wrong, and a corrected `CREATE TABLE IF NOT EXISTS` deploys
25
+ * as a no-op against it.
26
+ *
27
+ * Two of those reached production on one app: a JSON column left in
28
+ * varchar(255), which refused a large payload, and a money column left as an
29
+ * integer, which stored 99.5 as 100. Every layer reported success. The only
30
+ * symptom was a number that came back different from the one written.
31
+ *
32
+ * The comparison itself lives in bun-query-builder, which owns the schema plan
33
+ * and the introspection. This file's job is the part only the framework knows:
34
+ * where an application's models actually are.
35
+ */
36
+ /** One column whose live type does not match what its model declares. */
37
+ export declare interface SchemaDriftColumn {
38
+ table: string
39
+ column: string
40
+ expected: string
41
+ actual: string
42
+ actualSqlType: string
43
+ }
44
+ export declare interface SchemaDriftReport {
45
+ missingTables: string[]
46
+ missingColumns: Array<{ table: string, column: string, expected: string }>
47
+ typeMismatches: SchemaDriftColumn[]
48
+ clean: boolean
49
+ skipped: boolean
50
+ }
@@ -0,0 +1 @@
1
+ import fs from"node:fs";import{path}from"@stacksjs/path";async function loadModelsFrom(dir){const out=[];if(!fs.existsSync(dir))return out;for(const entry of fs.readdirSync(dir,{withFileTypes:!0})){const fullPath=path.join(dir,entry.name);if(entry.isDirectory()){out.push(...await loadModelsFrom(fullPath));continue}if(!entry.name.endsWith(".ts")||entry.name.startsWith("_")||entry.name.startsWith("index"))continue;try{const imported=(await import(fullPath)).default;if(imported?.name||imported?.table)out.push(imported)}catch{}}return out}export async function auditSchemaDrift(){const empty={missingTables:[],missingColumns:[],typeMismatches:[],clean:!0,skipped:!0};try{const models=await loadModelsFrom(path.appPath("Models"));if(models.length===0)return empty;const qb=await import("bun-query-builder");if(typeof qb.auditSchemaDrift!=="function"||typeof qb.buildMigrationPlan!=="function")return empty;const defined=qb.defineModels(Object.fromEntries(models.map((model)=>[model.name??model.table,model]))),plan=qb.buildMigrationPlan(defined,{});return{...await qb.auditSchemaDrift(plan,{}),skipped:!1}}catch{return empty}}export async function formatSchemaDrift(report){if(report.skipped||report.clean)return"";const{formatSchemaDrift:format}=await import("bun-query-builder");return typeof format==="function"?format(report):""}
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.376",
5
+ "version": "0.70.378",
6
6
  "description": "The Stacks database integration.",
7
7
  "author": "Chris Breuer",
8
8
  "contributors": [
@@ -61,19 +61,19 @@
61
61
  },
62
62
  "dependencies": {
63
63
  "@stacksjs/ts-validation": "^0.5.4",
64
- "bun-query-builder": "^0.2.30",
64
+ "bun-query-builder": "^0.2.36",
65
65
  "dynamodb-tooling": "^0.3.2"
66
66
  },
67
67
  "devDependencies": {
68
- "@stacksjs/cli": "0.70.376",
69
- "@stacksjs/config": "0.70.376",
70
- "@stacksjs/logging": "0.70.376",
71
- "@stacksjs/router": "0.70.376",
68
+ "@stacksjs/cli": "0.70.378",
69
+ "@stacksjs/config": "0.70.378",
70
+ "@stacksjs/logging": "0.70.378",
71
+ "@stacksjs/router": "0.70.378",
72
72
  "better-dx": "^0.2.17",
73
- "@stacksjs/path": "0.70.376",
74
- "@stacksjs/query-builder": "0.70.376",
75
- "@stacksjs/storage": "0.70.376",
76
- "@stacksjs/strings": "0.70.376",
77
- "@stacksjs/utils": "0.70.376"
73
+ "@stacksjs/path": "0.70.378",
74
+ "@stacksjs/query-builder": "0.70.378",
75
+ "@stacksjs/storage": "0.70.378",
76
+ "@stacksjs/strings": "0.70.378",
77
+ "@stacksjs/utils": "0.70.378"
78
78
  }
79
79
  }