@dbsp/cli 1.1.11 → 2.0.1

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.js CHANGED
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  createDbConnection,
4
- generateSchemaFile,
4
+ generateSchemaFileWithDiagnostics,
5
5
  redactDbUrl
6
- } from "./chunk-H2K46TNM.js";
6
+ } from "./chunk-VUNVYYOZ.js";
7
7
  import {
8
8
  loadSchema,
9
9
  loadSchemaFromCwd
10
- } from "./chunk-AQC34IO5.js";
10
+ } from "./chunk-EO3GHEFW.js";
11
11
  import {
12
12
  config,
13
13
  validateIdentifier
@@ -29,6 +29,14 @@ var generateCommand = new Command("generate").description("Generate code from sc
29
29
  ).action(
30
30
  async (target, options) => {
31
31
  try {
32
+ if (target === "manifest" || target === "kysely") {
33
+ throw new Error(
34
+ `Target '${target}' has been removed. Use 'ddl' for SQL generation, or the 'introspect' command to generate a schema file.`
35
+ );
36
+ }
37
+ if (target !== "ddl") {
38
+ throw new Error(`Unknown target: ${target}. Available targets: ddl`);
39
+ }
32
40
  let schema;
33
41
  let schemaPath;
34
42
  if (options.schema) {
@@ -51,9 +59,9 @@ var generateCommand = new Command("generate").description("Generate code from sc
51
59
  }
52
60
  switch (target) {
53
61
  case "ddl": {
54
- const casing = options.casing ?? "snake";
62
+ const dbCasing = mapGenerateCasingOption(options.casing) ?? schema.dbCasing ?? "snake_case";
63
+ const casingLabel = formatGenerateCasingLabel(dbCasing);
55
64
  const { createPgsqlCompileOnlyAdapter } = await import("@dbsp/adapter-pgsql");
56
- const dbCasing = casing === "snake" ? "snake_case" : "preserve";
57
65
  const adapter = createPgsqlCompileOnlyAdapter({
58
66
  dbCasing,
59
67
  ...options.schemaName ? { schemaName: options.schemaName } : {}
@@ -73,7 +81,7 @@ var generateCommand = new Command("generate").description("Generate code from sc
73
81
  console.log(`\u2705 Generated DDL: ${outPath}`);
74
82
  console.log(` Tables: ${schema.tableNames.length}`);
75
83
  console.log(` Statements: ${ddlStatements.length}`);
76
- console.log(` Casing: ${casing}`);
84
+ console.log(` Casing: ${casingLabel}`);
77
85
  if (options.drop) {
78
86
  console.log(` Includes DROP statements`);
79
87
  }
@@ -86,15 +94,6 @@ var generateCommand = new Command("generate").description("Generate code from sc
86
94
  }
87
95
  break;
88
96
  }
89
- case "manifest":
90
- case "kysely":
91
- throw new Error(
92
- `Target '${target}' has been removed in ARCH-005. These generators required the legacy defineSchema() format. Use 'ddl' target for SQL generation.`
93
- );
94
- default:
95
- throw new Error(
96
- `Unknown target: ${target}. Available targets: ddl`
97
- );
98
97
  }
99
98
  } catch (error) {
100
99
  const message = error instanceof Error ? error.message : String(error);
@@ -103,11 +102,48 @@ var generateCommand = new Command("generate").description("Generate code from sc
103
102
  }
104
103
  }
105
104
  );
105
+ function mapGenerateCasingOption(casing) {
106
+ switch (casing) {
107
+ case "snake":
108
+ return "snake_case";
109
+ case "camel":
110
+ return "camelCase";
111
+ case "none":
112
+ return "preserve";
113
+ default:
114
+ return void 0;
115
+ }
116
+ }
117
+ function formatGenerateCasingLabel(dbCasing) {
118
+ switch (dbCasing) {
119
+ case "snake_case":
120
+ return "snake";
121
+ case "camelCase":
122
+ return "camel";
123
+ case "preserve":
124
+ return "none";
125
+ }
126
+ }
106
127
 
107
128
  // src/commands/introspect.ts
108
129
  import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
109
130
  import { dirname as dirname2, resolve as resolve2 } from "path";
110
131
  import { Command as Command2 } from "commander";
132
+ function uniqueWarnings(warnings) {
133
+ const seen = /* @__PURE__ */ new Set();
134
+ const unique = [];
135
+ for (const warning of warnings) {
136
+ if (seen.has(warning)) continue;
137
+ seen.add(warning);
138
+ unique.push(warning);
139
+ }
140
+ return unique;
141
+ }
142
+ function reportWarnings(warnings) {
143
+ for (const warning of warnings) {
144
+ console.log(` \u26A0\uFE0F ${warning}`);
145
+ }
146
+ }
111
147
  var introspectCommand = new Command2("introspect").description("Generate schema.ts from database introspection").requiredOption("-d, --db <url>", "Database connection URL (required)").option("-o, --out <file>", "Output schema file", "./dbsp.schema.ts").option("--schema-name <name>", "Database schema name", "public").option(
112
148
  "--exclude <patterns>",
113
149
  "Tables to exclude (comma-separated glob patterns)",
@@ -128,10 +164,10 @@ var introspectCommand = new Command2("introspect").description("Generate schema.
128
164
  try {
129
165
  const { pool } = await createDbConnection(options.db);
130
166
  try {
131
- const { introspect: introspect4 } = await import("@dbsp/adapter-pgsql");
167
+ const { introspect } = await import("@dbsp/adapter-pgsql");
132
168
  const excludePatterns = options.exclude ? options.exclude.split(",").map((s) => s.trim()) : void 0;
133
169
  const includePatterns = options.include ? options.include.split(",").map((s) => s.trim()) : void 0;
134
- const model = await introspect4(pool, {
170
+ const model = await introspect(pool, {
135
171
  schema: options.schemaName,
136
172
  ...excludePatterns ? { exclude: excludePatterns } : {},
137
173
  ...includePatterns ? { include: includePatterns } : {}
@@ -142,23 +178,24 @@ var introspectCommand = new Command2("introspect").description("Generate schema.
142
178
  console.log(
143
179
  `\u{1F4CA} Found ${tableCount} tables, ${relationCount} relations, ${hierarchyCount} hierarchies`
144
180
  );
145
- if (model.warnings?.length) {
146
- for (const w of model.warnings) {
147
- console.log(` \u26A0\uFE0F ${w}`);
148
- }
149
- }
150
- console.log("");
151
181
  const codegenOptions = {
152
182
  sourceUrl: options.db,
153
183
  includeDbTypeComments: options.dbTypeComments,
154
- warnings: model.warnings,
155
184
  introspectedAt: model.introspectedAt,
156
185
  dbCasing: options.dbCasing
157
186
  };
158
- const schemaCode = generateSchemaFile(model, codegenOptions);
187
+ const generated = generateSchemaFileWithDiagnostics(
188
+ model,
189
+ codegenOptions
190
+ );
191
+ const warnings = uniqueWarnings(generated.warnings);
192
+ if (warnings.length > 0) {
193
+ reportWarnings(warnings);
194
+ }
195
+ console.log("");
159
196
  const outPath = resolve2(process.cwd(), options.out);
160
197
  mkdirSync2(dirname2(outPath), { recursive: true });
161
- writeFileSync2(outPath, schemaCode, "utf-8");
198
+ writeFileSync2(outPath, generated.code, "utf-8");
162
199
  console.log(`\u2705 Generated schema: ${outPath}`);
163
200
  console.log(` Tables: ${tableCount}`);
164
201
  console.log(` Relations: ${relationCount}`);
@@ -175,13 +212,13 @@ var introspectCommand = new Command2("introspect").description("Generate schema.
175
212
 
176
213
  // src/commands/migrate.ts
177
214
  import {
178
- compareSchemata,
215
+ comparePgsqlDatabaseSchema,
216
+ createPgsqlAdapter,
179
217
  ensureMigrationsTable,
180
218
  generateMigrationFile,
181
219
  generateMigrationSQL,
182
220
  getAppliedMigrations,
183
221
  getNextSchemaVersion,
184
- introspect,
185
222
  isDestructiveDown,
186
223
  parseMigrationFile,
187
224
  recordMigration,
@@ -318,10 +355,12 @@ var devCommand = new Command3("dev").description("Generate a migration from sche
318
355
  const loaded = await loadSchema(schemaPath);
319
356
  const schemaModel = loaded.model;
320
357
  await withMigratePool(options.db, async (pool) => {
321
- const dbModel = await introspect(pool, {
322
- ...options.schemaName ? { schema: options.schemaName } : {}
358
+ const adapter = createPgsqlAdapter(pool);
359
+ const diff = await comparePgsqlDatabaseSchema(adapter, schemaModel, {
360
+ ...options.schemaName ? { schema: options.schemaName } : {},
361
+ ...loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {},
362
+ onWarning: (message) => console.warn(`\u26A0\uFE0F ${message}`)
323
363
  });
324
- const diff = compareSchemata(schemaModel, dbModel);
325
364
  if (diff.changes.length === 0) {
326
365
  console.log("\u2705 No changes detected \u2014 database matches schema.");
327
366
  return;
@@ -640,10 +679,11 @@ var migrateCommand = new Command3("migrate").description("Database migration man
640
679
 
641
680
  // src/commands/push.ts
642
681
  import {
643
- compareSchemata as compareSchemata2,
682
+ comparePgsqlDatabaseSchema as comparePgsqlDatabaseSchema2,
683
+ createPgsqlAdapter as createPgsqlAdapter2,
644
684
  generateDDL,
645
685
  generateMigrationSQL as generateMigrationSQL2,
646
- introspect as introspect2
686
+ getNamingPluginForDbCasing
647
687
  } from "@dbsp/adapter-pgsql";
648
688
  import { Command as Command4 } from "commander";
649
689
 
@@ -706,16 +746,10 @@ var pushCommand = new Command4("push").description("Push schema to database (add
706
746
  if (options.drop) {
707
747
  const statements = generateDDL(schemaModel, {
708
748
  includeDropStatements: true,
709
- ...options.schemaName ? { schemaName: options.schemaName } : {}
749
+ ...options.schemaName ? { schemaName: options.schemaName } : {},
750
+ ...loaded.dbCasing !== void 0 ? { naming: getNamingPluginForDbCasing(loaded.dbCasing) } : {}
710
751
  });
711
- const escapedTable = MIGRATIONS_TABLE.replace(
712
- /[.*+?^${}()|[\]\\]/g,
713
- "\\$&"
714
- );
715
- const migrationsPattern = new RegExp(
716
- `DROP\\s+TABLE(?:\\s+IF\\s+EXISTS)?(?:\\s+"[^"]*"\\s*\\.)?\\s*"${escapedTable}"`,
717
- "i"
718
- );
752
+ const migrationsPattern = buildMigrationsTableDropPattern(MIGRATIONS_TABLE);
719
753
  const filtered = statements.filter(
720
754
  (stmt) => !migrationsPattern.test(stmt)
721
755
  );
@@ -724,10 +758,7 @@ var pushCommand = new Command4("push").description("Push schema to database (add
724
758
  ...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {}
725
759
  });
726
760
  if (options.json) {
727
- const droppedTables = statements.filter((s) => /DROP\s+TABLE/i.test(s)).filter((s) => !migrationsPattern.test(s)).map((s) => {
728
- const m = s.match(/"([^"]+)"\s*(?:CASCADE\s*)?;?\s*$/i);
729
- return m ? m[1] : s;
730
- }).filter((t) => t !== void 0);
761
+ const droppedTables = statements.filter((s) => /DROP\s+TABLE/i.test(s)).filter((s) => !migrationsPattern.test(s)).map(extractDroppedTableName).filter((t) => t !== void 0);
731
762
  console.log(
732
763
  JSON.stringify(
733
764
  {
@@ -747,10 +778,17 @@ var pushCommand = new Command4("push").description("Push schema to database (add
747
778
  );
748
779
  }
749
780
  } else {
750
- const dbModel = await introspect2(pool, {
751
- ...options.schemaName ? { schema: options.schemaName } : {}
752
- });
753
- const diff = compareSchemata2(schemaModel, dbModel);
781
+ const adapter = createPgsqlAdapter2(pool);
782
+ const compareOptions = {
783
+ ...options.schemaName ? { schema: options.schemaName } : {},
784
+ ...loaded.dbCasing ? { dbCasing: loaded.dbCasing } : {},
785
+ onWarning: (message) => console.warn(`\u26A0\uFE0F ${message}`)
786
+ };
787
+ const diff = await comparePgsqlDatabaseSchema2(
788
+ adapter,
789
+ schemaModel,
790
+ compareOptions
791
+ );
754
792
  const statements = generateMigrationSQL2(diff, {
755
793
  includeDestructive: false,
756
794
  ...options.schemaName ? { schemaName: options.schemaName } : {}
@@ -787,6 +825,17 @@ var pushCommand = new Command4("push").description("Push schema to database (add
787
825
  const result = await executeDdl(pool, statements, {
788
826
  ...options.dryRun !== void 0 ? { dryRun: options.dryRun } : {}
789
827
  });
828
+ if (!options.dryRun) {
829
+ const postApplyDiff = await comparePgsqlDatabaseSchema2(
830
+ adapter,
831
+ schemaModel,
832
+ {
833
+ ...compareOptions,
834
+ previouslyAppliedDiff: diff
835
+ }
836
+ );
837
+ assertNoUnexpectedResidualDrift(postApplyDiff, skippedChanges);
838
+ }
790
839
  if (options.json) {
791
840
  console.log(
792
841
  JSON.stringify(
@@ -822,6 +871,17 @@ var pushCommand = new Command4("push").description("Push schema to database (add
822
871
  }
823
872
  }
824
873
  );
874
+ function buildMigrationsTableDropPattern(tableName) {
875
+ const escaped = tableName.replace(/[.*+?^${}()|[\]\\]/gu, "\\$&");
876
+ return new RegExp(
877
+ `DROP\\s+TABLE(?:\\s+IF\\s+EXISTS)?(?:\\s+"[^"]*"\\s*\\.)?\\s*"${escaped}"`,
878
+ "i"
879
+ );
880
+ }
881
+ function extractDroppedTableName(statement) {
882
+ const match = statement.match(/"([^"]+)"\s*(?:CASCADE\s*)?;?\s*$/iu);
883
+ return match?.[1] ?? statement;
884
+ }
825
885
  function outputResult(statements, options) {
826
886
  if (options.dryRun && !options.json) {
827
887
  console.log(`-- Dry run: ${statements.length} statement(s)
@@ -832,6 +892,30 @@ function outputResult(statements, options) {
832
892
  }
833
893
  }
834
894
  }
895
+ function assertNoUnexpectedResidualDrift(postApplyDiff, skippedChanges) {
896
+ const skippedKeys = new Set(skippedChanges.map(changeIdentity));
897
+ const unexpected = postApplyDiff.changes.filter(
898
+ (change) => !(change.destructive && skippedKeys.has(changeIdentity(change)))
899
+ );
900
+ if (unexpected.length === 0) return;
901
+ throw new Error(
902
+ `Push did not converge after applying DDL; ${unexpected.length} outstanding change(s) remain:
903
+ ` + unexpected.map(formatOutstandingChange).join("\n")
904
+ );
905
+ }
906
+ function changeIdentity(change) {
907
+ return JSON.stringify([
908
+ change.kind,
909
+ change.table,
910
+ change.column ?? null,
911
+ change.details,
912
+ change.destructive
913
+ ]);
914
+ }
915
+ function formatOutstandingChange(change) {
916
+ const target = change.column ? `${change.table}.${change.column}` : change.table || "schema";
917
+ return ` - ${change.details} (${change.kind}: ${target})`;
918
+ }
835
919
 
836
920
  // src/commands/repl.ts
837
921
  import { readFileSync as readFileSync2 } from "fs";
@@ -888,7 +972,7 @@ var replCommand = new Command5("repl").description("Launch interactive REPL for
888
972
  if (options.use) {
889
973
  validateIdentifier(options.use, "schema");
890
974
  }
891
- const dbCasing = options.casing === "snake" ? "snake_case" : options.casing === "camel" ? "camelCase" : options.casing === "none" ? "preserve" : void 0;
975
+ const dbCasing = mapReplCasingOption(options.casing) ?? schema.dbCasing;
892
976
  if (options.eval || options.input) {
893
977
  const { runBatchMode } = await import("./repl/batch.js");
894
978
  const queries = [];
@@ -926,7 +1010,7 @@ var replCommand = new Command5("repl").description("Launch interactive REPL for
926
1010
  });
927
1011
  return;
928
1012
  }
929
- const { startRepl } = await import("./repl-G56OK3F7.js");
1013
+ const { startRepl } = await import("./repl-PIFVQAHC.js");
930
1014
  await startRepl({
931
1015
  schema,
932
1016
  schemaPath,
@@ -942,9 +1026,24 @@ var replCommand = new Command5("repl").description("Launch interactive REPL for
942
1026
  process.exit(1);
943
1027
  }
944
1028
  });
1029
+ function mapReplCasingOption(casing) {
1030
+ switch (casing) {
1031
+ case "snake":
1032
+ return "snake_case";
1033
+ case "camel":
1034
+ return "camelCase";
1035
+ case "none":
1036
+ return "preserve";
1037
+ default:
1038
+ return void 0;
1039
+ }
1040
+ }
945
1041
 
946
1042
  // src/commands/verify.ts
947
- import { compareSchemata as compareSchemata3, introspect as introspect3 } from "@dbsp/adapter-pgsql";
1043
+ import {
1044
+ comparePgsqlDatabaseSchema as comparePgsqlDatabaseSchema3,
1045
+ createPgsqlAdapter as createPgsqlAdapter3
1046
+ } from "@dbsp/adapter-pgsql";
948
1047
  import { Command as Command6 } from "commander";
949
1048
 
950
1049
  // src/verifier.ts
@@ -1089,10 +1188,15 @@ var verifyCommand = new Command6("verify").description("Compare schema vs real d
1089
1188
  const schemaModel = loaded.model;
1090
1189
  const { pool } = await createDbConnection(options.db);
1091
1190
  try {
1092
- const dbModel = await introspect3(pool, {
1191
+ const adapter = createPgsqlAdapter3(pool);
1192
+ const diff = await comparePgsqlDatabaseSchema3(adapter, schemaModel, {
1193
+ ...options.schemaName ? { schema: options.schemaName } : {},
1194
+ ...loaded.dbCasing !== void 0 ? { dbCasing: loaded.dbCasing } : {},
1195
+ onWarning: (message) => console.warn(`\u26A0\uFE0F ${message}`)
1196
+ });
1197
+ const dbModel = await adapter.introspect({
1093
1198
  ...options.schemaName ? { schema: options.schemaName } : {}
1094
1199
  });
1095
- const diff = compareSchemata3(schemaModel, dbModel);
1096
1200
  const schemaTables = Array.from(schemaModel.tables.keys());
1097
1201
  const dbTables = Array.from(dbModel.tables.keys());
1098
1202
  const result = verifyFromDiff(diff, schemaTables, dbTables);