@memberjunction/cli 5.37.0 → 5.38.0

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.
Files changed (150) hide show
  1. package/README.md +74 -0
  2. package/dist/baseline/cli-helpers.d.ts +12 -0
  3. package/dist/baseline/cli-helpers.d.ts.map +1 -0
  4. package/dist/baseline/cli-helpers.js +40 -0
  5. package/dist/baseline/cli-helpers.js.map +1 -0
  6. package/dist/baseline/comparator.d.ts +26 -0
  7. package/dist/baseline/comparator.d.ts.map +1 -0
  8. package/dist/baseline/comparator.js +527 -0
  9. package/dist/baseline/comparator.js.map +1 -0
  10. package/dist/baseline/connection.d.ts +39 -0
  11. package/dist/baseline/connection.d.ts.map +1 -0
  12. package/dist/baseline/connection.js +104 -0
  13. package/dist/baseline/connection.js.map +1 -0
  14. package/dist/baseline/data-dumper.d.ts +23 -0
  15. package/dist/baseline/data-dumper.d.ts.map +1 -0
  16. package/dist/baseline/data-dumper.js +94 -0
  17. package/dist/baseline/data-dumper.js.map +1 -0
  18. package/dist/baseline/emitter.d.ts +36 -0
  19. package/dist/baseline/emitter.d.ts.map +1 -0
  20. package/dist/baseline/emitter.js +512 -0
  21. package/dist/baseline/emitter.js.map +1 -0
  22. package/dist/baseline/index.d.ts +18 -0
  23. package/dist/baseline/index.d.ts.map +1 -0
  24. package/dist/baseline/index.js +14 -0
  25. package/dist/baseline/index.js.map +1 -0
  26. package/dist/baseline/introspector-mssql.d.ts +15 -0
  27. package/dist/baseline/introspector-mssql.d.ts.map +1 -0
  28. package/dist/baseline/introspector-mssql.js +673 -0
  29. package/dist/baseline/introspector-mssql.js.map +1 -0
  30. package/dist/baseline/introspector-postgres.d.ts +14 -0
  31. package/dist/baseline/introspector-postgres.d.ts.map +1 -0
  32. package/dist/baseline/introspector-postgres.js +277 -0
  33. package/dist/baseline/introspector-postgres.js.map +1 -0
  34. package/dist/baseline/report.d.ts +7 -0
  35. package/dist/baseline/report.d.ts.map +1 -0
  36. package/dist/baseline/report.js +105 -0
  37. package/dist/baseline/report.js.map +1 -0
  38. package/dist/baseline/types.d.ts +286 -0
  39. package/dist/baseline/types.d.ts.map +1 -0
  40. package/dist/baseline/types.js +10 -0
  41. package/dist/baseline/types.js.map +1 -0
  42. package/dist/baseline/util.d.ts +112 -0
  43. package/dist/baseline/util.d.ts.map +1 -0
  44. package/dist/baseline/util.js +325 -0
  45. package/dist/baseline/util.js.map +1 -0
  46. package/dist/commands/baseline/build.d.ts +29 -0
  47. package/dist/commands/baseline/build.d.ts.map +1 -0
  48. package/dist/commands/baseline/build.js +188 -0
  49. package/dist/commands/baseline/build.js.map +1 -0
  50. package/dist/commands/baseline/compare.d.ts +19 -0
  51. package/dist/commands/baseline/compare.d.ts.map +1 -0
  52. package/dist/commands/baseline/compare.js +133 -0
  53. package/dist/commands/baseline/compare.js.map +1 -0
  54. package/dist/commands/baseline/roundtrip.d.ts +23 -0
  55. package/dist/commands/baseline/roundtrip.d.ts.map +1 -0
  56. package/dist/commands/baseline/roundtrip.js +245 -0
  57. package/dist/commands/baseline/roundtrip.js.map +1 -0
  58. package/dist/commands/test/compare.d.ts +3 -0
  59. package/dist/commands/test/compare.d.ts.map +1 -1
  60. package/dist/commands/test/compare.js +23 -3
  61. package/dist/commands/test/compare.js.map +1 -1
  62. package/dist/commands/test/regression/build.d.ts +14 -0
  63. package/dist/commands/test/regression/build.d.ts.map +1 -0
  64. package/dist/commands/test/regression/build.js +48 -0
  65. package/dist/commands/test/regression/build.js.map +1 -0
  66. package/dist/commands/test/regression/compare.d.ts +15 -0
  67. package/dist/commands/test/regression/compare.d.ts.map +1 -0
  68. package/dist/commands/test/regression/compare.js +66 -0
  69. package/dist/commands/test/regression/compare.js.map +1 -0
  70. package/dist/commands/test/regression/down.d.ts +10 -0
  71. package/dist/commands/test/regression/down.d.ts.map +1 -0
  72. package/dist/commands/test/regression/down.js +29 -0
  73. package/dist/commands/test/regression/down.js.map +1 -0
  74. package/dist/commands/test/regression/export.d.ts +11 -0
  75. package/dist/commands/test/regression/export.d.ts.map +1 -0
  76. package/dist/commands/test/regression/export.js +56 -0
  77. package/dist/commands/test/regression/export.js.map +1 -0
  78. package/dist/commands/test/regression/gen-forms.d.ts +7 -0
  79. package/dist/commands/test/regression/gen-forms.d.ts.map +1 -0
  80. package/dist/commands/test/regression/gen-forms.js +18 -0
  81. package/dist/commands/test/regression/gen-forms.js.map +1 -0
  82. package/dist/commands/test/regression/index.d.ts +7 -0
  83. package/dist/commands/test/regression/index.d.ts.map +1 -0
  84. package/dist/commands/test/regression/index.js +33 -0
  85. package/dist/commands/test/regression/index.js.map +1 -0
  86. package/dist/commands/test/regression/init.d.ts +21 -0
  87. package/dist/commands/test/regression/init.d.ts.map +1 -0
  88. package/dist/commands/test/regression/init.js +111 -0
  89. package/dist/commands/test/regression/init.js.map +1 -0
  90. package/dist/commands/test/regression/remote.d.ts +23 -0
  91. package/dist/commands/test/regression/remote.d.ts.map +1 -0
  92. package/dist/commands/test/regression/remote.js +140 -0
  93. package/dist/commands/test/regression/remote.js.map +1 -0
  94. package/dist/commands/test/regression/up.d.ts +21 -0
  95. package/dist/commands/test/regression/up.d.ts.map +1 -0
  96. package/dist/commands/test/regression/up.js +111 -0
  97. package/dist/commands/test/regression/up.js.map +1 -0
  98. package/dist/commands/test/run.d.ts +1 -0
  99. package/dist/commands/test/run.d.ts.map +1 -1
  100. package/dist/commands/test/run.js +6 -0
  101. package/dist/commands/test/run.js.map +1 -1
  102. package/dist/commands/test/suite.d.ts +5 -0
  103. package/dist/commands/test/suite.d.ts.map +1 -1
  104. package/dist/commands/test/suite.js +27 -0
  105. package/dist/commands/test/suite.js.map +1 -1
  106. package/dist/commands/translate-sql/index.d.ts.map +1 -1
  107. package/dist/commands/translate-sql/index.js +3 -2
  108. package/dist/commands/translate-sql/index.js.map +1 -1
  109. package/dist/init-templates/bring-your-own-app/README.md +74 -0
  110. package/dist/init-templates/bring-your-own-app/byo-metadata/.mj-sync.json +10 -0
  111. package/dist/init-templates/bring-your-own-app/byo-metadata/test-suites/.byo-suite.json +51 -0
  112. package/dist/init-templates/bring-your-own-app/byo-metadata/test-suites/.mj-sync.json +12 -0
  113. package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.T01-counter-increment.json +63 -0
  114. package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.T02-contact-form-submit.json +63 -0
  115. package/dist/init-templates/bring-your-own-app/byo-metadata/tests/.mj-sync.json +12 -0
  116. package/dist/init-templates/bring-your-own-app/docker-compose.app.yml +76 -0
  117. package/dist/init-templates/bring-your-own-app/target.json +23 -0
  118. package/dist/init-templates/generic-web/README.md +115 -0
  119. package/dist/init-templates/generic-web/metadata/.mj-sync.json +10 -0
  120. package/dist/init-templates/generic-web/metadata/test-suites/.generic-web-suite.json +48 -0
  121. package/dist/init-templates/generic-web/metadata/test-suites/.mj-sync.json +12 -0
  122. package/dist/init-templates/generic-web/metadata/tests/.GW01-home-loads.json +74 -0
  123. package/dist/init-templates/generic-web/metadata/tests/.GW02-search-array-map.json +82 -0
  124. package/dist/init-templates/generic-web/metadata/tests/.mj-sync.json +12 -0
  125. package/dist/init-templates/generic-web/oracles/mdn-oracles.cjs +121 -0
  126. package/dist/init-templates/generic-web/target.json +25 -0
  127. package/dist/init-templates/github-actions/README.md +26 -0
  128. package/dist/init-templates/github-actions/regression.yml +52 -0
  129. package/dist/init-templates/remote-mj/README.md +95 -0
  130. package/dist/init-templates/remote-mj/target.json +22 -0
  131. package/dist/init-templates/static-file-server/Dockerfile +23 -0
  132. package/dist/init-templates/static-file-server/README.md +88 -0
  133. package/dist/init-templates/static-file-server/docker-compose.app.yml +44 -0
  134. package/dist/init-templates/static-file-server/metadata/.mj-sync.json +10 -0
  135. package/dist/init-templates/static-file-server/metadata/test-suites/.mj-sync.json +12 -0
  136. package/dist/init-templates/static-file-server/metadata/test-suites/.static-server-suite.json +27 -0
  137. package/dist/init-templates/static-file-server/metadata/tests/.SS01-home-loads.json +45 -0
  138. package/dist/init-templates/static-file-server/metadata/tests/.SS02-navigate-about.json +45 -0
  139. package/dist/init-templates/static-file-server/metadata/tests/.mj-sync.json +12 -0
  140. package/dist/init-templates/static-file-server/public/about.html +12 -0
  141. package/dist/init-templates/static-file-server/public/index.html +27 -0
  142. package/dist/init-templates/static-file-server/target.json +16 -0
  143. package/dist/lib/regression/docker-helpers.d.ts +98 -0
  144. package/dist/lib/regression/docker-helpers.d.ts.map +1 -0
  145. package/dist/lib/regression/docker-helpers.js +184 -0
  146. package/dist/lib/regression/docker-helpers.js.map +1 -0
  147. package/oclif.manifest.json +1356 -509
  148. package/package.json +22 -17
  149. package/regression-compose/docker-compose.bacpac-standalone.yml +109 -0
  150. package/regression-compose/docker-compose.standalone.yml +50 -0
@@ -0,0 +1,673 @@
1
+ /**
2
+ * MSSQL introspector.
3
+ *
4
+ * Returns a normalized SchemaSnapshot. All object bodies (views, procs,
5
+ * functions, triggers) are pulled via OBJECT_DEFINITION() so they are
6
+ * byte-faithful to what the database actually has — no AST round-trip.
7
+ */
8
+ import { isExcludedTable, stableSortBy } from './util.js';
9
+ export async function introspectMssql(db, progress = {}) {
10
+ progress.onPhase?.('schemas');
11
+ const schemas = await db.query(`
12
+ SELECT s.name
13
+ FROM sys.schemas s
14
+ WHERE s.name NOT IN ('sys', 'INFORMATION_SCHEMA', 'guest', 'db_owner', 'db_accessadmin',
15
+ 'db_securityadmin', 'db_ddladmin', 'db_backupoperator', 'db_datareader',
16
+ 'db_datawriter', 'db_denydatareader', 'db_denydatawriter')
17
+ `);
18
+ progress.onPhase?.('tables');
19
+ const tables = await readTables(db);
20
+ progress.onPhase?.('views');
21
+ const views = await readViews(db);
22
+ progress.onPhase?.('routines');
23
+ const procedures = await readRoutines(db, 'P');
24
+ const functions = await readRoutines(db, 'FN_TF_IF');
25
+ progress.onPhase?.('triggers');
26
+ const triggers = await readTriggers(db);
27
+ progress.onPhase?.('sequences');
28
+ const sequences = await readSequences(db);
29
+ progress.onPhase?.('userDefinedTypes');
30
+ const userDefinedTypes = await readUserDefinedTypes(db);
31
+ progress.onPhase?.('extendedProperties');
32
+ const extendedProperties = await readExtendedProperties(db);
33
+ progress.onPhase?.('principals');
34
+ const principals = await readPrincipals(db);
35
+ progress.onPhase?.('roleMemberships');
36
+ const roleMemberships = await readRoleMemberships(db);
37
+ progress.onPhase?.('permissions');
38
+ const permissions = await readPermissions(db);
39
+ return {
40
+ dialect: 'mssql',
41
+ schemas: stableSortBy(schemas, (s) => s.name.toLowerCase()),
42
+ tables: stableSortBy(tables, (t) => `${t.schema}.${t.name}`.toLowerCase()),
43
+ views: stableSortBy(views, (v) => `${v.schema}.${v.name}`.toLowerCase()),
44
+ procedures: stableSortBy(procedures, (r) => `${r.schema}.${r.name}`.toLowerCase()),
45
+ functions: stableSortBy(functions, (r) => `${r.schema}.${r.name}`.toLowerCase()),
46
+ triggers: stableSortBy(triggers, (t) => `${t.schema}.${t.name}`.toLowerCase()),
47
+ sequences: stableSortBy(sequences, (s) => `${s.schema}.${s.name}`.toLowerCase()),
48
+ userDefinedTypes: stableSortBy(userDefinedTypes, (u) => `${u.schema}.${u.name}`.toLowerCase()),
49
+ extendedProperties: stableSortBy(extendedProperties, (p) => extPropSortKey(p)),
50
+ principals: stableSortBy(principals, (p) => `${p.kind}|${p.name}`.toLowerCase()),
51
+ roleMemberships: stableSortBy(roleMemberships, (m) => `${m.role}|${m.member}`.toLowerCase()),
52
+ permissions: stableSortBy(permissions, (p) => permissionSortKey(p)),
53
+ };
54
+ }
55
+ /** Canonical sort key for permissions so the emitted GRANT block is byte-deterministic. */
56
+ function permissionSortKey(p) {
57
+ return [
58
+ p.grantee.toLowerCase(),
59
+ p.targetClass,
60
+ (p.targetSchema || '').toLowerCase(),
61
+ (p.targetObject || p.targetType || '').toLowerCase(),
62
+ (p.targetColumn || '').toLowerCase(),
63
+ p.permission.toLowerCase(),
64
+ p.state,
65
+ ].join('|');
66
+ }
67
+ /** Canonical sort key for extended properties so output stays byte-deterministic. */
68
+ function extPropSortKey(p) {
69
+ return [
70
+ p.schemaName.toLowerCase(),
71
+ (p.level1Type || '').toLowerCase(),
72
+ (p.level1Name || '').toLowerCase(),
73
+ (p.level2Type || '').toLowerCase(),
74
+ (p.level2Name || '').toLowerCase(),
75
+ p.name.toLowerCase(),
76
+ ].join('|');
77
+ }
78
+ async function readTables(db) {
79
+ // Pull tables in one round-trip then enrich with columns/PKs/indexes/FKs/checks.
80
+ const tableRows = await db.query(`
81
+ SELECT s.name AS schema_name,
82
+ t.name AS table_name,
83
+ CAST(CASE WHEN EXISTS (
84
+ SELECT 1 FROM sys.columns c WHERE c.object_id = t.object_id AND c.is_identity = 1
85
+ ) THEN 1 ELSE 0 END AS INT) AS has_identity
86
+ FROM sys.tables t
87
+ JOIN sys.schemas s ON s.schema_id = t.schema_id
88
+ WHERE t.is_ms_shipped = 0
89
+ `);
90
+ const columnRows = await db.query(`
91
+ SELECT s.name AS schema_name, t.name AS table_name, c.name AS column_name,
92
+ c.column_id AS ordinal,
93
+ ty.name AS data_type, c.max_length, c.precision, c.scale,
94
+ c.is_nullable, c.is_identity, c.is_computed,
95
+ cc.definition AS computed_definition,
96
+ CAST(cc.is_persisted AS INT) AS computed_is_persisted,
97
+ dc.definition AS default_definition,
98
+ dc.name AS default_name,
99
+ c.collation_name AS collation
100
+ FROM sys.tables t
101
+ JOIN sys.schemas s ON s.schema_id = t.schema_id
102
+ JOIN sys.columns c ON c.object_id = t.object_id
103
+ JOIN sys.types ty ON ty.user_type_id = c.user_type_id
104
+ LEFT JOIN sys.computed_columns cc ON cc.object_id = c.object_id AND cc.column_id = c.column_id
105
+ LEFT JOIN sys.default_constraints dc ON dc.parent_object_id = c.object_id AND dc.parent_column_id = c.column_id
106
+ WHERE t.is_ms_shipped = 0
107
+ `);
108
+ const pkRows = await db.query(`
109
+ SELECT s.name AS schema_name, t.name AS table_name, kc.name AS constraint_name,
110
+ c.name AS column_name, ic.key_ordinal,
111
+ CAST(CASE WHEN i.type = 1 THEN 1 ELSE 0 END AS INT) AS is_clustered,
112
+ CAST(kc.type_desc AS VARCHAR(64)) AS type_desc_raw,
113
+ CAST(CASE WHEN kc.type = 'PK' THEN 1 ELSE 0 END AS INT) AS is_primary,
114
+ CAST(CASE WHEN kc.type = 'UQ' THEN 1 ELSE 0 END AS INT) AS is_unique
115
+ FROM sys.key_constraints kc
116
+ JOIN sys.tables t ON t.object_id = kc.parent_object_id
117
+ JOIN sys.schemas s ON s.schema_id = t.schema_id
118
+ JOIN sys.indexes i ON i.object_id = kc.parent_object_id AND i.index_id = kc.unique_index_id
119
+ JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
120
+ JOIN sys.columns c ON c.object_id = i.object_id AND c.column_id = ic.column_id
121
+ WHERE t.is_ms_shipped = 0
122
+ `);
123
+ const indexRows = await db.query(`
124
+ SELECT s.name AS schema_name, t.name AS table_name, i.name AS index_name,
125
+ CAST(i.is_unique AS INT) AS is_unique,
126
+ CAST(CASE WHEN i.type = 1 THEN 1 ELSE 0 END AS INT) AS is_clustered,
127
+ CAST(CASE WHEN i.is_primary_key = 1 OR i.is_unique_constraint = 1 THEN 1 ELSE 0 END AS INT) AS is_constraint,
128
+ c.name AS column_name, ic.key_ordinal,
129
+ CAST(ic.is_included_column AS INT) AS is_included,
130
+ i.filter_definition
131
+ FROM sys.indexes i
132
+ JOIN sys.tables t ON t.object_id = i.object_id
133
+ JOIN sys.schemas s ON s.schema_id = t.schema_id
134
+ JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
135
+ JOIN sys.columns c ON c.object_id = ic.object_id AND c.column_id = ic.column_id
136
+ WHERE t.is_ms_shipped = 0 AND i.name IS NOT NULL
137
+ `);
138
+ const fkRows = await db.query(`
139
+ SELECT ps.name AS schema_name, pt.name AS table_name, fk.name AS constraint_name,
140
+ pc.name AS column_name,
141
+ rs.name AS ref_schema, rt.name AS ref_table, rc.name AS ref_column,
142
+ fk.delete_referential_action_desc AS on_delete,
143
+ fk.update_referential_action_desc AS on_update,
144
+ fkc.constraint_column_id AS key_index
145
+ FROM sys.foreign_keys fk
146
+ JOIN sys.tables pt ON pt.object_id = fk.parent_object_id
147
+ JOIN sys.schemas ps ON ps.schema_id = pt.schema_id
148
+ JOIN sys.tables rt ON rt.object_id = fk.referenced_object_id
149
+ JOIN sys.schemas rs ON rs.schema_id = rt.schema_id
150
+ JOIN sys.foreign_key_columns fkc ON fkc.constraint_object_id = fk.object_id
151
+ JOIN sys.columns pc ON pc.object_id = fkc.parent_object_id AND pc.column_id = fkc.parent_column_id
152
+ JOIN sys.columns rc ON rc.object_id = fkc.referenced_object_id AND rc.column_id = fkc.referenced_column_id
153
+ `);
154
+ const checkRows = await db.query(`
155
+ SELECT s.name AS schema_name, t.name AS table_name, cc.name AS constraint_name, cc.definition
156
+ FROM sys.check_constraints cc
157
+ JOIN sys.tables t ON t.object_id = cc.parent_object_id
158
+ JOIN sys.schemas s ON s.schema_id = t.schema_id
159
+ WHERE t.is_ms_shipped = 0
160
+ `);
161
+ const tables = [];
162
+ for (const t of tableRows) {
163
+ // Skip Skyway/Flyway's own bookkeeping table — the migrator creates it
164
+ // automatically before running any migration, so emitting CREATE TABLE for
165
+ // it in the baseline collides ("There is already an object named ...").
166
+ // Filtering at the introspector level removes the table AND all of its
167
+ // nested constraints/indexes/data from the snapshot in one pass.
168
+ if (isExcludedTable(t.table_name))
169
+ continue;
170
+ const tableKey = `${t.schema_name}.${t.table_name}`;
171
+ const columns = stableSortBy(columnRows.filter((c) => c.schema_name === t.schema_name && c.table_name === t.table_name), (c) => String(c.ordinal).padStart(6, '0')).map((c) => ({
172
+ name: c.column_name,
173
+ ordinal: c.ordinal,
174
+ dataType: formatMssqlType(c),
175
+ isNullable: !!c.is_nullable,
176
+ isIdentity: !!c.is_identity,
177
+ isComputed: !!c.is_computed,
178
+ computedExpression: c.computed_definition ?? undefined,
179
+ isComputedPersisted: c.computed_is_persisted == null ? undefined : !!c.computed_is_persisted,
180
+ defaultExpression: c.default_definition ?? undefined,
181
+ defaultConstraintName: c.default_name ?? undefined,
182
+ collation: c.collation ?? undefined,
183
+ }));
184
+ const pkCols = pkRows.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name && r.is_primary === 1);
185
+ const primaryKey = pkCols.length
186
+ ? {
187
+ name: pkCols[0].constraint_name,
188
+ clustered: !!pkCols[0].is_clustered,
189
+ columns: stableSortBy(pkCols, (r) => String(r.key_ordinal).padStart(6, '0')).map((r) => r.column_name),
190
+ }
191
+ : undefined;
192
+ const uniqueGroups = groupBy(pkRows.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name && r.is_unique === 1), (r) => r.constraint_name);
193
+ const uniqueConstraints = stableSortBy([...uniqueGroups.entries()].map(([name, rows]) => ({
194
+ name,
195
+ clustered: !!rows[0].is_clustered,
196
+ columns: stableSortBy(rows, (r) => String(r.key_ordinal).padStart(6, '0')).map((r) => r.column_name),
197
+ })), (u) => u.name.toLowerCase());
198
+ const indexGroups = groupBy(indexRows.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name && r.is_constraint === 0), (r) => r.index_name);
199
+ const indexes = stableSortBy([...indexGroups.entries()].map(([name, rows]) => {
200
+ const keyCols = stableSortBy(rows.filter((r) => r.is_included === 0), (r) => String(r.key_ordinal).padStart(6, '0'));
201
+ const inclCols = rows.filter((r) => r.is_included === 1).map((r) => r.column_name);
202
+ return {
203
+ name,
204
+ isUnique: !!rows[0].is_unique,
205
+ isClustered: !!rows[0].is_clustered,
206
+ columns: keyCols.map((r) => r.column_name),
207
+ includes: stableSortBy(inclCols, (n) => n.toLowerCase()),
208
+ filter: rows[0].filter_definition ?? undefined,
209
+ };
210
+ }), (i) => i.name.toLowerCase());
211
+ const fkGroups = groupBy(fkRows.filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name), (r) => r.constraint_name);
212
+ const foreignKeys = stableSortBy([...fkGroups.entries()].map(([name, rows]) => {
213
+ const ordered = stableSortBy(rows, (r) => String(r.key_index).padStart(6, '0'));
214
+ return {
215
+ name,
216
+ columns: ordered.map((r) => r.column_name),
217
+ referencedSchema: ordered[0].ref_schema,
218
+ referencedTable: ordered[0].ref_table,
219
+ referencedColumns: ordered.map((r) => r.ref_column),
220
+ onDelete: normalizeAction(ordered[0].on_delete),
221
+ onUpdate: normalizeAction(ordered[0].on_update),
222
+ };
223
+ }), (f) => f.name.toLowerCase());
224
+ const checks = stableSortBy(checkRows
225
+ .filter((r) => r.schema_name === t.schema_name && r.table_name === t.table_name)
226
+ .map((r) => ({ name: r.constraint_name, expression: r.definition })), (c) => c.name.toLowerCase());
227
+ tables.push({
228
+ schema: t.schema_name,
229
+ name: t.table_name,
230
+ hasIdentity: !!t.has_identity,
231
+ columns,
232
+ primaryKey,
233
+ uniqueConstraints,
234
+ indexes,
235
+ foreignKeys,
236
+ checks,
237
+ });
238
+ }
239
+ return tables;
240
+ }
241
+ async function readViews(db) {
242
+ const rows = await db.query(`
243
+ SELECT s.name AS schema_name, v.name AS view_name,
244
+ OBJECT_DEFINITION(v.object_id) AS definition
245
+ FROM sys.views v
246
+ JOIN sys.schemas s ON s.schema_id = v.schema_id
247
+ WHERE v.is_ms_shipped = 0
248
+ `);
249
+ return rows.map((r) => ({ schema: r.schema_name, name: r.view_name, definition: r.definition ?? '' }));
250
+ }
251
+ async function readRoutines(db, kind) {
252
+ const typePredicate = kind === 'P' ? `o.type = 'P'` : `o.type IN ('FN','TF','IF')`;
253
+ const rows = await db.query(`
254
+ SELECT s.name AS schema_name, o.name AS object_name,
255
+ OBJECT_DEFINITION(o.object_id) AS definition,
256
+ CAST(o.type AS VARCHAR(8)) AS routine_kind
257
+ FROM sys.objects o
258
+ JOIN sys.schemas s ON s.schema_id = o.schema_id
259
+ WHERE ${typePredicate}
260
+ AND o.is_ms_shipped = 0
261
+ `);
262
+ return rows.map((r) => ({
263
+ schema: r.schema_name,
264
+ name: r.object_name,
265
+ definition: r.definition ?? '',
266
+ kind: kind === 'P' ? 'procedure' : 'function',
267
+ }));
268
+ }
269
+ async function readTriggers(db) {
270
+ const rows = await db.query(`
271
+ SELECT s.name AS schema_name, tr.name AS trigger_name, t.name AS table_name,
272
+ OBJECT_DEFINITION(tr.object_id) AS definition
273
+ FROM sys.triggers tr
274
+ JOIN sys.tables t ON t.object_id = tr.parent_id
275
+ JOIN sys.schemas s ON s.schema_id = t.schema_id
276
+ WHERE tr.is_ms_shipped = 0
277
+ AND t.name <> 'flyway_schema_history' -- defensive: never emit triggers on the migrator's own table
278
+ `);
279
+ return rows.map((r) => ({
280
+ schema: r.schema_name,
281
+ name: r.trigger_name,
282
+ table: r.table_name,
283
+ definition: r.definition ?? '',
284
+ }));
285
+ }
286
+ /**
287
+ * Map `sys.database_principals.type` to our normalized kind.
288
+ * Skipped types: A (application role) is kept; X/E/G handle AAD/Windows; everything
289
+ * not in this table is filtered out at the WHERE level below so we never see it.
290
+ */
291
+ const PRINCIPAL_TYPE_MAP = {
292
+ S: 'sql_user',
293
+ U: 'windows_user',
294
+ R: 'database_role',
295
+ A: 'application_role',
296
+ X: 'aad_group',
297
+ E: 'aad_user',
298
+ };
299
+ async function readPrincipals(db) {
300
+ // Filter out:
301
+ // - fixed roles (`is_fixed_role = 1`)
302
+ // - principal_id <= 4 (dbo / guest / INFORMATION_SCHEMA / sys)
303
+ // - `public` role
304
+ // - Anything starting with `db_` (defensive — covers fixed roles in older SQL versions)
305
+ const rows = await db.query(`
306
+ SELECT p.name,
307
+ CAST(p.type AS NVARCHAR(2)) AS type,
308
+ o.name AS owner_name,
309
+ p.default_schema_name
310
+ FROM sys.database_principals p
311
+ LEFT JOIN sys.database_principals o ON o.principal_id = p.owning_principal_id
312
+ WHERE p.is_fixed_role = 0
313
+ AND p.principal_id > 4
314
+ AND p.name NOT IN ('dbo','guest','public','sys','INFORMATION_SCHEMA')
315
+ AND p.name NOT LIKE 'db[_]%'
316
+ AND p.type IN ('S','U','R','A','X','E')
317
+ `);
318
+ return rows
319
+ .map((r) => {
320
+ const kind = PRINCIPAL_TYPE_MAP[r.type];
321
+ if (!kind)
322
+ return null;
323
+ const def = { name: r.name, kind };
324
+ if (r.owner_name)
325
+ def.owner = r.owner_name;
326
+ if (r.default_schema_name)
327
+ def.defaultSchema = r.default_schema_name;
328
+ return def;
329
+ })
330
+ .filter((p) => p !== null);
331
+ }
332
+ async function readRoleMemberships(db) {
333
+ // Skip the built-in `db_owner <- dbo` membership (SQL Server creates it
334
+ // automatically). Other memberships involving fixed roles (e.g.
335
+ // `db_datareader <- MJ_Connect`) ARE meaningful and need to be emitted.
336
+ const rows = await db.query(`
337
+ SELECT rp.name AS role_name, mp.name AS member_name
338
+ FROM sys.database_role_members rm
339
+ JOIN sys.database_principals rp ON rp.principal_id = rm.role_principal_id
340
+ JOIN sys.database_principals mp ON mp.principal_id = rm.member_principal_id
341
+ WHERE NOT (rp.name = 'db_owner' AND mp.name = 'dbo')
342
+ AND mp.name NOT IN ('public')
343
+ `);
344
+ return rows.map((r) => ({ role: r.role_name, member: r.member_name }));
345
+ }
346
+ async function readPermissions(db) {
347
+ // We support four classes: DATABASE (0), OBJECT_OR_COLUMN (1), SCHEMA (3), TYPE (6).
348
+ // Anything else (DATABASE_PRINCIPAL, ENDPOINT, ASSEMBLY, etc.) is intentionally skipped.
349
+ // Filtering: skip grants TO public (default), TO dbo (always has full access),
350
+ // and anything on system schemas / objects.
351
+ const rows = await db.query(`
352
+ SELECT 1 AS class_id, 'OBJECT_OR_COLUMN' AS class_desc,
353
+ CAST(p.state_desc AS NVARCHAR(40)) AS state,
354
+ CAST(p.permission_name AS NVARCHAR(64)) AS permission_name,
355
+ USER_NAME(p.grantee_principal_id) AS grantee_name,
356
+ OBJECT_SCHEMA_NAME(p.major_id) AS schema_name,
357
+ OBJECT_NAME(p.major_id) AS object_name,
358
+ CASE WHEN p.minor_id > 0
359
+ THEN (SELECT c.name FROM sys.columns c WHERE c.object_id = p.major_id AND c.column_id = p.minor_id)
360
+ ELSE NULL END AS column_name,
361
+ NULL AS type_name
362
+ FROM sys.database_permissions p
363
+ WHERE p.class = 1
364
+ AND OBJECT_SCHEMA_NAME(p.major_id) IS NOT NULL
365
+ AND USER_NAME(p.grantee_principal_id) NOT IN ('public','dbo','guest')
366
+ AND USER_NAME(p.grantee_principal_id) NOT LIKE 'db[_]%'
367
+
368
+ UNION ALL
369
+
370
+ SELECT 0 AS class_id, 'DATABASE' AS class_desc,
371
+ CAST(p.state_desc AS NVARCHAR(40)) AS state,
372
+ CAST(p.permission_name AS NVARCHAR(64)) AS permission_name,
373
+ USER_NAME(p.grantee_principal_id) AS grantee_name,
374
+ NULL AS schema_name, NULL AS object_name, NULL AS column_name, NULL AS type_name
375
+ FROM sys.database_permissions p
376
+ WHERE p.class = 0
377
+ AND USER_NAME(p.grantee_principal_id) NOT IN ('public','dbo','guest')
378
+ AND USER_NAME(p.grantee_principal_id) NOT LIKE 'db[_]%'
379
+
380
+ UNION ALL
381
+
382
+ SELECT 3 AS class_id, 'SCHEMA' AS class_desc,
383
+ CAST(p.state_desc AS NVARCHAR(40)) AS state,
384
+ CAST(p.permission_name AS NVARCHAR(64)) AS permission_name,
385
+ USER_NAME(p.grantee_principal_id) AS grantee_name,
386
+ s.name AS schema_name, NULL AS object_name, NULL AS column_name, NULL AS type_name
387
+ FROM sys.database_permissions p
388
+ JOIN sys.schemas s ON s.schema_id = p.major_id
389
+ WHERE p.class = 3
390
+ AND USER_NAME(p.grantee_principal_id) NOT IN ('public','dbo','guest')
391
+ AND USER_NAME(p.grantee_principal_id) NOT LIKE 'db[_]%'
392
+
393
+ UNION ALL
394
+
395
+ SELECT 6 AS class_id, 'TYPE' AS class_desc,
396
+ CAST(p.state_desc AS NVARCHAR(40)) AS state,
397
+ CAST(p.permission_name AS NVARCHAR(64)) AS permission_name,
398
+ USER_NAME(p.grantee_principal_id) AS grantee_name,
399
+ SCHEMA_NAME(t.schema_id) AS schema_name, NULL AS object_name, NULL AS column_name, t.name AS type_name
400
+ FROM sys.database_permissions p
401
+ JOIN sys.types t ON t.user_type_id = p.major_id
402
+ WHERE p.class = 6
403
+ AND USER_NAME(p.grantee_principal_id) NOT IN ('public','dbo','guest')
404
+ AND USER_NAME(p.grantee_principal_id) NOT LIKE 'db[_]%'
405
+ `);
406
+ return rows.map((r) => ({
407
+ grantee: r.grantee_name,
408
+ state: r.state.toUpperCase(),
409
+ permission: r.permission_name.toUpperCase(),
410
+ targetClass: r.class_id === 0 ? 'database' :
411
+ r.class_id === 1 ? 'object' :
412
+ r.class_id === 3 ? 'schema' : 'type',
413
+ targetSchema: r.schema_name ?? undefined,
414
+ targetObject: r.object_name ?? undefined,
415
+ targetType: r.type_name ?? undefined,
416
+ targetColumn: r.column_name ?? undefined,
417
+ }));
418
+ }
419
+ async function readSequences(db) {
420
+ const rows = await db.query(`
421
+ SELECT s.name AS schema_name, sq.name AS seq_name,
422
+ CAST(sq.start_value AS NVARCHAR(64)) AS start_value,
423
+ CAST(sq.increment AS NVARCHAR(64)) AS increment,
424
+ CAST(sq.minimum_value AS NVARCHAR(64)) AS minimum_value,
425
+ CAST(sq.maximum_value AS NVARCHAR(64)) AS maximum_value,
426
+ CAST(sq.is_cycling AS INT) AS is_cycling,
427
+ CAST(sq.current_value AS NVARCHAR(64)) AS current_value
428
+ FROM sys.sequences sq
429
+ JOIN sys.schemas s ON s.schema_id = sq.schema_id
430
+ `);
431
+ return rows.map((r) => ({
432
+ schema: r.schema_name,
433
+ name: r.seq_name,
434
+ startValue: r.start_value,
435
+ increment: r.increment,
436
+ minValue: r.minimum_value ?? undefined,
437
+ maxValue: r.maximum_value ?? undefined,
438
+ cycle: !!r.is_cycling,
439
+ currentValue: r.current_value ?? undefined,
440
+ }));
441
+ }
442
+ async function readUserDefinedTypes(db) {
443
+ // Table types live in sys.table_types. We only support user-defined ones.
444
+ // Note: tt.type_table_object_id is the synthetic object whose columns live in sys.columns.
445
+ const typeRows = await db.query(`
446
+ SELECT s.name AS schema_name,
447
+ tt.name AS type_name,
448
+ tt.type_table_object_id,
449
+ CAST(tt.is_memory_optimized AS INT) AS is_memory_optimized
450
+ FROM sys.table_types tt
451
+ JOIN sys.schemas s ON s.schema_id = tt.schema_id
452
+ WHERE tt.is_user_defined = 1
453
+ `);
454
+ if (typeRows.length === 0)
455
+ return [];
456
+ const columnRows = await db.query(`
457
+ SELECT tt.type_table_object_id,
458
+ c.name AS column_name,
459
+ c.column_id AS ordinal,
460
+ ty.name AS data_type, c.max_length, c.precision, c.scale,
461
+ CAST(c.is_nullable AS INT) AS is_nullable,
462
+ c.collation_name AS collation
463
+ FROM sys.table_types tt
464
+ JOIN sys.columns c ON c.object_id = tt.type_table_object_id
465
+ JOIN sys.types ty ON ty.user_type_id = c.user_type_id
466
+ WHERE tt.is_user_defined = 1
467
+ `);
468
+ // Read primary keys defined inline on table types. Distinct query because the
469
+ // sys.indexes object_id matches type_table_object_id (not sys.tables).
470
+ const pkRows = await db.query(`
471
+ SELECT tt.type_table_object_id, kc.name AS constraint_name,
472
+ c.name AS column_name, ic.key_ordinal,
473
+ CAST(CASE WHEN i.type = 1 THEN 1 ELSE 0 END AS INT) AS is_clustered
474
+ FROM sys.table_types tt
475
+ JOIN sys.key_constraints kc ON kc.parent_object_id = tt.type_table_object_id AND kc.type = 'PK'
476
+ JOIN sys.indexes i ON i.object_id = kc.parent_object_id AND i.index_id = kc.unique_index_id
477
+ JOIN sys.index_columns ic ON ic.object_id = i.object_id AND ic.index_id = i.index_id
478
+ JOIN sys.columns c ON c.object_id = i.object_id AND c.column_id = ic.column_id
479
+ WHERE tt.is_user_defined = 1
480
+ `);
481
+ const out = [];
482
+ for (const t of typeRows) {
483
+ const cols = stableSortBy(columnRows.filter((c) => c.type_table_object_id === t.type_table_object_id), (c) => String(c.ordinal).padStart(6, '0')).map((c) => ({
484
+ name: c.column_name,
485
+ ordinal: c.ordinal,
486
+ dataType: formatMssqlType(c),
487
+ isNullable: !!c.is_nullable,
488
+ collation: c.collation ?? undefined,
489
+ }));
490
+ const pkCols = pkRows.filter((r) => r.type_table_object_id === t.type_table_object_id);
491
+ const primaryKey = pkCols.length
492
+ ? {
493
+ name: pkCols[0].constraint_name,
494
+ clustered: !!pkCols[0].is_clustered,
495
+ columns: stableSortBy(pkCols, (r) => String(r.key_ordinal).padStart(6, '0')).map((r) => r.column_name),
496
+ }
497
+ : undefined;
498
+ out.push({
499
+ schema: t.schema_name,
500
+ name: t.type_name,
501
+ kind: 'table',
502
+ isMemoryOptimized: !!t.is_memory_optimized,
503
+ columns: cols,
504
+ primaryKey,
505
+ });
506
+ }
507
+ return out;
508
+ }
509
+ async function readExtendedProperties(db) {
510
+ // Three classes of extended properties matter for MJ:
511
+ // class=1 OBJECT_OR_COLUMN — table, view, proc, function, trigger, type, and any column thereof
512
+ // class=3 SCHEMA
513
+ // class=7 INDEX — rare in MJ but emit for completeness
514
+ // Everything else (DB-level, assemblies, etc.) is skipped intentionally.
515
+ //
516
+ // For class=1 we have to look up the object's type (sys.objects.type) and
517
+ // figure out the level1Type (TABLE/VIEW/PROCEDURE/FUNCTION/TYPE) plus the
518
+ // optional level2 column. Table types route through sys.table_types since
519
+ // their object_id matches type_table_object_id, not sys.objects.
520
+ const rows = await db.query(`
521
+ -- class 1: object (or column)
522
+ SELECT 1 AS class_id,
523
+ ep.name AS prop_name,
524
+ CAST(ep.value AS NVARCHAR(MAX)) AS prop_value,
525
+ s.name AS schema_name,
526
+ CASE
527
+ WHEN o.type IN ('U') THEN 'TABLE'
528
+ WHEN o.type IN ('V') THEN 'VIEW'
529
+ WHEN o.type IN ('P') THEN 'PROCEDURE'
530
+ WHEN o.type IN ('FN','TF','IF') THEN 'FUNCTION'
531
+ WHEN o.type IN ('SO') THEN 'SEQUENCE'
532
+ WHEN o.type IN ('TR') THEN NULL -- triggers get re-routed below to TABLE/TRIGGER
533
+ ELSE NULL
534
+ END AS level1_type,
535
+ o.name AS level1_name,
536
+ CASE WHEN ep.minor_id > 0 THEN 'COLUMN' ELSE NULL END AS level2_type,
537
+ c.name AS level2_name
538
+ FROM sys.extended_properties ep
539
+ JOIN sys.objects o ON o.object_id = ep.major_id
540
+ JOIN sys.schemas s ON s.schema_id = o.schema_id
541
+ LEFT JOIN sys.columns c ON c.object_id = ep.major_id AND c.column_id = ep.minor_id AND ep.minor_id > 0
542
+ WHERE ep.class = 1
543
+ AND o.is_ms_shipped = 0
544
+ AND o.type IN ('U','V','P','FN','TF','IF','SO') -- triggers handled below
545
+
546
+ UNION ALL
547
+
548
+ -- class 1 (triggers): level1=TABLE (parent), level2=TRIGGER (the trigger itself)
549
+ SELECT 1 AS class_id,
550
+ ep.name AS prop_name,
551
+ CAST(ep.value AS NVARCHAR(MAX)) AS prop_value,
552
+ ps.name AS schema_name,
553
+ 'TABLE' AS level1_type,
554
+ pt.name AS level1_name,
555
+ 'TRIGGER' AS level2_type,
556
+ tr.name AS level2_name
557
+ FROM sys.extended_properties ep
558
+ JOIN sys.triggers tr ON tr.object_id = ep.major_id
559
+ JOIN sys.tables pt ON pt.object_id = tr.parent_id
560
+ JOIN sys.schemas ps ON ps.schema_id = pt.schema_id
561
+ WHERE ep.class = 1
562
+ AND tr.is_ms_shipped = 0
563
+
564
+ UNION ALL
565
+
566
+ -- class 1 (table types): level1=TYPE, level2 = COLUMN if applicable
567
+ SELECT 1 AS class_id,
568
+ ep.name AS prop_name,
569
+ CAST(ep.value AS NVARCHAR(MAX)) AS prop_value,
570
+ s.name AS schema_name,
571
+ 'TYPE' AS level1_type,
572
+ tt.name AS level1_name,
573
+ CASE WHEN ep.minor_id > 0 THEN 'COLUMN' ELSE NULL END AS level2_type,
574
+ c.name AS level2_name
575
+ FROM sys.extended_properties ep
576
+ JOIN sys.table_types tt ON tt.type_table_object_id = ep.major_id
577
+ JOIN sys.schemas s ON s.schema_id = tt.schema_id
578
+ LEFT JOIN sys.columns c ON c.object_id = ep.major_id AND c.column_id = ep.minor_id AND ep.minor_id > 0
579
+ WHERE ep.class = 1
580
+ AND tt.is_user_defined = 1
581
+
582
+ UNION ALL
583
+
584
+ -- class 3: schema-level
585
+ SELECT 3 AS class_id,
586
+ ep.name AS prop_name,
587
+ CAST(ep.value AS NVARCHAR(MAX)) AS prop_value,
588
+ s.name AS schema_name,
589
+ NULL AS level1_type,
590
+ NULL AS level1_name,
591
+ NULL AS level2_type,
592
+ NULL AS level2_name
593
+ FROM sys.extended_properties ep
594
+ JOIN sys.schemas s ON s.schema_id = ep.major_id
595
+ WHERE ep.class = 3
596
+ AND s.name NOT IN ('sys', 'INFORMATION_SCHEMA', 'guest', 'db_owner', 'db_accessadmin',
597
+ 'db_securityadmin', 'db_ddladmin', 'db_backupoperator', 'db_datareader',
598
+ 'db_datawriter', 'db_denydatareader', 'db_denydatawriter')
599
+ `);
600
+ const out = [];
601
+ for (const r of rows) {
602
+ // Skip rows we couldn't classify (level1_type=NULL on class=1 means an
603
+ // object kind we don't emit baselines for, e.g. CLR assemblies).
604
+ if (r.class_id === 1 && !r.level1_type)
605
+ continue;
606
+ if (!r.schema_name)
607
+ continue;
608
+ // Mirror the table-level filter for any property attached to flyway_schema_history.
609
+ if (r.level1_name && isExcludedTable(r.level1_name))
610
+ continue;
611
+ out.push({
612
+ name: r.prop_name,
613
+ value: r.prop_value ?? '',
614
+ schemaName: r.schema_name,
615
+ level1Type: r.level1_type ?? undefined,
616
+ level1Name: r.level1_name ?? undefined,
617
+ level2Type: r.level2_type ?? undefined,
618
+ level2Name: r.level2_name ?? undefined,
619
+ });
620
+ }
621
+ return out;
622
+ }
623
+ function formatMssqlType(c) {
624
+ const type = c.data_type.toLowerCase();
625
+ switch (type) {
626
+ case 'nvarchar':
627
+ case 'nchar': {
628
+ const length = c.max_length === -1 ? 'max' : String(c.max_length / 2);
629
+ return `${type}(${length})`;
630
+ }
631
+ case 'varchar':
632
+ case 'char':
633
+ case 'binary':
634
+ case 'varbinary': {
635
+ const length = c.max_length === -1 ? 'max' : String(c.max_length);
636
+ return `${type}(${length})`;
637
+ }
638
+ case 'decimal':
639
+ case 'numeric':
640
+ return `${type}(${c.precision},${c.scale})`;
641
+ case 'datetime2':
642
+ case 'datetimeoffset':
643
+ case 'time':
644
+ return `${type}(${c.scale})`;
645
+ case 'float':
646
+ return c.precision === 53 ? 'float' : `float(${c.precision})`;
647
+ default:
648
+ return type;
649
+ }
650
+ }
651
+ function normalizeAction(raw) {
652
+ const r = (raw || '').toUpperCase().replace(/_/g, '');
653
+ if (r.includes('CASCADE'))
654
+ return 'CASCADE';
655
+ if (r.includes('SETNULL'))
656
+ return 'SET_NULL';
657
+ if (r.includes('SETDEFAULT'))
658
+ return 'SET_DEFAULT';
659
+ return 'NO_ACTION';
660
+ }
661
+ function groupBy(items, key) {
662
+ const out = new Map();
663
+ for (const item of items) {
664
+ const k = key(item);
665
+ const list = out.get(k);
666
+ if (list)
667
+ list.push(item);
668
+ else
669
+ out.set(k, [item]);
670
+ }
671
+ return out;
672
+ }
673
+ //# sourceMappingURL=introspector-mssql.js.map