@bakery-framework/orm 1.0.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.
- package/LICENSE +19 -0
- package/README.md +88 -0
- package/package.json +59 -0
- package/src/adapters/base.ts +1128 -0
- package/src/adapters/mysql.ts +619 -0
- package/src/adapters/observe.ts +261 -0
- package/src/adapters/pgsql.ts +611 -0
- package/src/adapters/registry.ts +204 -0
- package/src/adapters/sqlite.ts +588 -0
- package/src/adapters.ts +72 -0
- package/src/backup.ts +37 -0
- package/src/connection.ts +69 -0
- package/src/define.ts +380 -0
- package/src/field.ts +595 -0
- package/src/globals.d.ts +22 -0
- package/src/index.ts +63 -0
- package/src/orm/index.ts +24 -0
- package/src/orm/mutation.ts +692 -0
- package/src/orm/query.ts +1680 -0
- package/src/pool.ts +83 -0
- package/src/schema-registry.ts +75 -0
- package/src/schema-util.ts +467 -0
- package/src/sync/builder.ts +618 -0
- package/src/sync/engine.ts +399 -0
- package/src/sync/helpers.ts +1119 -0
- package/src/sync/history.ts +169 -0
- package/src/sync/index.ts +113 -0
- package/src/sync/ledger.ts +335 -0
- package/src/sync/load.ts +368 -0
- package/src/sync/rollback.ts +200 -0
- package/src/sync/types.ts +101 -0
- package/src/sync/view-sql.ts +160 -0
- package/templates/schema.example.ts +94 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
import { Logger, messageLogger } from '@bakery-framework/core/logger'
|
|
2
|
+
import { Case } from '@bakery-framework/core/utils'
|
|
3
|
+
import type { SQLAdapter } from '../adapters/base'
|
|
4
|
+
import { SchemaBuilder } from './builder'
|
|
5
|
+
import {
|
|
6
|
+
buildSyncPlan,
|
|
7
|
+
calculateForeignKeyDiff,
|
|
8
|
+
calculateIndexDiff,
|
|
9
|
+
collectForeignKeys,
|
|
10
|
+
executeSyncPlan,
|
|
11
|
+
hasOldWrappers,
|
|
12
|
+
logPlannedChanges,
|
|
13
|
+
} from './helpers'
|
|
14
|
+
import { writeLedger } from './ledger'
|
|
15
|
+
import type { SchemaLayout } from './load'
|
|
16
|
+
import type * as SyncTypes from './types'
|
|
17
|
+
|
|
18
|
+
// prettier-ignore
|
|
19
|
+
export const syncMsgs = {
|
|
20
|
+
GEN_TYPES: 'I Generating types...',
|
|
21
|
+
SYNC_SUCCESS: 'I %gschema.ts successfully synced%* to Database!',
|
|
22
|
+
INVALID_SCHEMA: 'W %yschema.ts is invalid or corrupt. Treating as new.%*',
|
|
23
|
+
NO_DBINFO: 'W %yDBInfo namespace not found in schema.ts!%*',
|
|
24
|
+
PERFECT_SYNC: 'I %gschema.ts is perfectly synced%* with Database!',
|
|
25
|
+
DB_NEWER: 'I Database is newer than TS. Generating types...',
|
|
26
|
+
TS_NEWER: 'I %yschema.ts is newer! Syncing to the database...%*',
|
|
27
|
+
BACKUP_CREATED: 'I Created database backup: %y{file}%*',
|
|
28
|
+
SCHEMA_PRESERVED: 'I Preserved previous schema: %y{file}%*',
|
|
29
|
+
NO_CONSTRAINTS:
|
|
30
|
+
'E Could not find %rDBInfo.constraints%* in schema.ts to run the reverse sync!',
|
|
31
|
+
COL_MISMATCH:
|
|
32
|
+
"W Table '%y{table}%*' needs rebuild because of column '%y{column}%*' mismatch:",
|
|
33
|
+
COL_MISMATCH_TS:
|
|
34
|
+
'W - TS: type=%c{tsType}%*, nullable=%c{tsNullable}%*, default=%c{tsDefault}%*',
|
|
35
|
+
COL_MISMATCH_DB:
|
|
36
|
+
'W - DB: type=%c{dbType}%*, nullable=%c{dbNullable}%*, default=%c{dbDefault}%*',
|
|
37
|
+
DANGER_ZONE: 'W %rDANGER ZONE: Destructive or major changes detected!%*',
|
|
38
|
+
DROP_TABLES: 'W Tables to drop: %r{tables}%*',
|
|
39
|
+
RENAME_TABLES: 'I Tables to rename: %y{tables}%*',
|
|
40
|
+
DROP_COLS: 'W Columns to drop: %r{cols}%*',
|
|
41
|
+
RENAME_COLS: 'I Columns to rename: %y{cols}%*',
|
|
42
|
+
ADD_COLS: 'I Columns to add: %g{cols}%*',
|
|
43
|
+
REBUILD_TABLES: 'W Tables to rebuild (schema modified): %r{tables}%*',
|
|
44
|
+
UPDATE_VIEWS: 'I Views to update/recreate: %y{views}%*',
|
|
45
|
+
DROP_INDEXES: 'I Indexes to drop: %r{indexes}%*',
|
|
46
|
+
ADD_INDEXES: 'I Indexes to add: %g{indexes}%*',
|
|
47
|
+
REVIEW_WARNING: 'W %yThese changes may affect data. Review carefully.%*',
|
|
48
|
+
SYNC_ABORTED: 'I %ySync aborted. Your data is safe!%*',
|
|
49
|
+
EXEC_RENAME_TABLE: 'I Renaming table: %y{oldName}%* -> %y{newName}%*...',
|
|
50
|
+
EXEC_RENAME_COL:
|
|
51
|
+
'I Renaming column: %y{table}.{oldColumn}%* -> %y{newColumn}%*...',
|
|
52
|
+
EXEC_DROP_TABLE: 'I Dropping %y{type}%*: %r{table}%*...',
|
|
53
|
+
EXEC_DROP_COL: 'I Dropping column: %r{table}.{column}%*...',
|
|
54
|
+
EXEC_ADD_COL: 'I Adding column: %g{table}.{column}%*...',
|
|
55
|
+
EXEC_DROP_INDEX: 'I Dropping index: %r{idx}%*...',
|
|
56
|
+
EXEC_REBUILD:
|
|
57
|
+
'I Rebuilding table to apply schema modifications: %y{table}%*...',
|
|
58
|
+
EXEC_SYNC_VIEW: 'D Syncing view: %y{view}%*...',
|
|
59
|
+
EXEC_SYNC_CONS: 'D Syncing constraints for: %y{table}%*...',
|
|
60
|
+
EXEC_ADD_INDEX: 'I Creating %y{type}%* index: %g{name}%*...',
|
|
61
|
+
CATCH_UP_SUCCESS: 'I %gDatabase successfully caught up%*!',
|
|
62
|
+
PROD_FORCE_REQUIRED: 'E %rProduction requires %y--force-sync%* to proceed.%*',
|
|
63
|
+
BACKUP_REQUIRED:
|
|
64
|
+
'E %rAborting: this sync drops data but no backup was created. Fix the backup first.%*',
|
|
65
|
+
SCHEMA_GENERATED:
|
|
66
|
+
'I Database is empty. Generated %yschema.ts%* with empty boilerplate.',
|
|
67
|
+
NOTHING_TO_SYNC: 'I Database and %yschema.ts%* are empty. Nothing to sync!',
|
|
68
|
+
OVERRIDE_SCHEMA:
|
|
69
|
+
'I %yschema.ts contains _oldTable/_transform wrappers. Overriding file to match DB.%*',
|
|
70
|
+
FATAL_ERROR:
|
|
71
|
+
'E %rFATAL ERROR: Sync failed! All changes have been safely rolled back. Detail: {error}%*',
|
|
72
|
+
VIEWS_SEEDED:
|
|
73
|
+
'I Seeded %y{file}%* from the database. It is yours from now on — the generator will not overwrite it.',
|
|
74
|
+
VIEWS_KEPT:
|
|
75
|
+
'I Left %y{file}%* alone: view interfaces are hand-owned. Delete it and re-run to reseed.',
|
|
76
|
+
LEDGER_DRIFT:
|
|
77
|
+
'W %yDatabase has drifted from the last schema Bakery applied%*: {reason}. Diffing against live introspection instead of the ledger. Something changed this database outside Bakery — if that was not deliberate, check it before syncing.',
|
|
78
|
+
} as const
|
|
79
|
+
|
|
80
|
+
const logger = new Logger('db-sync')
|
|
81
|
+
export const MESSAGES = messageLogger(logger, syncMsgs)
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Whether a destructive sync in this process needs an explicit `--force-sync`.
|
|
85
|
+
*
|
|
86
|
+
* The second half used to read `process.env.PROD === 'true'`, which could
|
|
87
|
+
* never be true: `core/init.ts` installs `PROD` on `process.env` with
|
|
88
|
+
* `Object.defineProperties` as a getter returning a **boolean**, so the guard
|
|
89
|
+
* on the most destructive operation the framework performs rested entirely on
|
|
90
|
+
* `NODE_ENV`. `import.meta.env` is the same object as `process.env` and
|
|
91
|
+
* `import.meta.env.PROD` is the framework's idiom for reading these flags.
|
|
92
|
+
*
|
|
93
|
+
* The dead term was **deleted rather than repaired**, and that is the whole
|
|
94
|
+
* decision here. `import.meta.env.PROD` means only "`--dev` is absent", and
|
|
95
|
+
* `db:sync` is a separate CLI invocation that never passes `--dev` — so for
|
|
96
|
+
* this caller the flag is a constant `true`, carrying no information about the
|
|
97
|
+
* environment at all. Activating it would not have made the guard smarter; it
|
|
98
|
+
* would have made the `isProd` branch unconditional and left `handleSafetyChecks`'s
|
|
99
|
+
* interactive `Proceed with sync?` unreachable for the one workflow it exists
|
|
100
|
+
* for. That is dead code traded for different dead code, plus a silent UX
|
|
101
|
+
* change to the documented way of applying a schema.
|
|
102
|
+
*
|
|
103
|
+
* `NODE_ENV` is the only term that actually says "deployment", which is what
|
|
104
|
+
* the guard is protecting against. Set it, and a destructive plan requires
|
|
105
|
+
* `--force-sync`; otherwise a human at a terminal gets asked.
|
|
106
|
+
*
|
|
107
|
+
* Exported for the regression test: the value is process-wide ambient state,
|
|
108
|
+
* and the alternative is driving `SyncEngine.run` far enough to reach a
|
|
109
|
+
* `process.exit`.
|
|
110
|
+
*/
|
|
111
|
+
export function isProductionSync(): boolean {
|
|
112
|
+
return process.env.NODE_ENV === 'production'
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
class SyncSession implements AsyncDisposable {
|
|
116
|
+
constructor(private adapter: SQLAdapter) {}
|
|
117
|
+
async [Symbol.asyncDispose]() {
|
|
118
|
+
await (this.adapter as any).postSync?.(this.adapter)
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export class SyncEngine {
|
|
123
|
+
protected constructor() {}
|
|
124
|
+
|
|
125
|
+
private static async checkEmptyConstraints(
|
|
126
|
+
adapter: SQLAdapter,
|
|
127
|
+
constraints: SyncTypes.DBConstraints,
|
|
128
|
+
genLocal: (c?: any) => Promise<void>,
|
|
129
|
+
schemaPath: string,
|
|
130
|
+
): Promise<boolean> {
|
|
131
|
+
const schemaExists = await Bun.file(schemaPath).exists()
|
|
132
|
+
if (!schemaExists) {
|
|
133
|
+
await genLocal(constraints)
|
|
134
|
+
const dbConstraints = await adapter.getConstraints()
|
|
135
|
+
if (Object.keys(dbConstraints).length === 0) {
|
|
136
|
+
MESSAGES.SCHEMA_GENERATED()
|
|
137
|
+
}
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
if (Object.keys(constraints).length) return false
|
|
141
|
+
|
|
142
|
+
MESSAGES.NO_CONSTRAINTS()
|
|
143
|
+
const dbConstraints = await adapter.getConstraints()
|
|
144
|
+
|
|
145
|
+
if (Object.keys(dbConstraints).length) {
|
|
146
|
+
MESSAGES.DB_NEWER()
|
|
147
|
+
await genLocal(constraints)
|
|
148
|
+
} else {
|
|
149
|
+
MESSAGES.NOTHING_TO_SYNC()
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (process.env.DEV_WATCHER_ACTIVE && Object.keys(dbConstraints).length) {
|
|
153
|
+
process.exit(42)
|
|
154
|
+
}
|
|
155
|
+
return true
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
private static evaluateChanges(
|
|
159
|
+
plan: any,
|
|
160
|
+
indexesToDrop: any,
|
|
161
|
+
indexesToAdd: any,
|
|
162
|
+
) {
|
|
163
|
+
// Index and view changes are destructive too: any index present in the DB
|
|
164
|
+
// but absent from the schema is dropped, which silently removes indexes an
|
|
165
|
+
// operator added by hand in production.
|
|
166
|
+
// `viewsToUpdate` is deliberately not here, though it *is* a change below.
|
|
167
|
+
//
|
|
168
|
+
// Creating or recreating a view cannot lose anything: a view holds no data,
|
|
169
|
+
// and `createView` drops and recreates in one step. Counting it as
|
|
170
|
+
// destructive made a schema with a view demand `--force-sync` in production
|
|
171
|
+
// for no reason — and it matters more now that views are diffed at all,
|
|
172
|
+
// because MySQL and Postgres re-render a stored body, so a run that falls
|
|
173
|
+
// back to introspection sees one as changed every time.
|
|
174
|
+
//
|
|
175
|
+
// Dropping a view the schema no longer declares is a different act, and it
|
|
176
|
+
// lands in `tablesToDrop`, which is still dangerous.
|
|
177
|
+
const isDangerous = Boolean(
|
|
178
|
+
plan.tablesToDrop.length ||
|
|
179
|
+
plan.tablesToRename.length ||
|
|
180
|
+
plan.columnsToDrop.length ||
|
|
181
|
+
plan.columnsToRename.length ||
|
|
182
|
+
plan.tablesToRebuild.size ||
|
|
183
|
+
indexesToDrop.size,
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
const hasChanges = Boolean(
|
|
187
|
+
isDangerous ||
|
|
188
|
+
plan.unmappedTsTables.size ||
|
|
189
|
+
plan.columnsToAdd.length ||
|
|
190
|
+
plan.viewsToUpdate.length ||
|
|
191
|
+
indexesToDrop.size ||
|
|
192
|
+
indexesToAdd.size,
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
return { isDangerous, hasChanges }
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
private static handleSafetyChecks(
|
|
199
|
+
isDangerous: boolean,
|
|
200
|
+
argv: string[],
|
|
201
|
+
): void {
|
|
202
|
+
if (!isDangerous) return
|
|
203
|
+
|
|
204
|
+
const isProd = isProductionSync()
|
|
205
|
+
const force = argv.includes('--force-sync')
|
|
206
|
+
|
|
207
|
+
if (!force)
|
|
208
|
+
logger.log(
|
|
209
|
+
"Tip: use '--choose=db', '--choose=ts', or '--dry-run'.",
|
|
210
|
+
'info',
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if (isProd && !force) {
|
|
214
|
+
MESSAGES.PROD_FORCE_REQUIRED()
|
|
215
|
+
process.exit(1)
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!isProd && !force && !logger.confirm('Proceed with sync?')) {
|
|
219
|
+
MESSAGES.SYNC_ABORTED()
|
|
220
|
+
process.exit(0)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private static async executeSyncPipeline(
|
|
225
|
+
adapter: SQLAdapter,
|
|
226
|
+
plan: any,
|
|
227
|
+
constraints: SyncTypes.DBConstraints,
|
|
228
|
+
indexesToDrop: any,
|
|
229
|
+
indexesToAdd: any,
|
|
230
|
+
genLocal: (c?: any) => Promise<void>,
|
|
231
|
+
isDangerous = false,
|
|
232
|
+
tsFks: SyncTypes.DBForeignKeys = {},
|
|
233
|
+
fksToAdd: Map<string, SyncTypes.ForeignKeyInfo> = new Map(),
|
|
234
|
+
fksToDrop: Map<string, SyncTypes.ForeignKeyInfo> = new Map(),
|
|
235
|
+
tsIndexes: SyncTypes.DBIndexes = {},
|
|
236
|
+
): Promise<void> {
|
|
237
|
+
const { backupDatabase } = await import('../backup')
|
|
238
|
+
const backedUp = await backupDatabase(adapter)
|
|
239
|
+
|
|
240
|
+
// Never run an irreversible migration without a recoverable copy.
|
|
241
|
+
if (isDangerous && !backedUp) {
|
|
242
|
+
MESSAGES.BACKUP_REQUIRED()
|
|
243
|
+
process.exit(1)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
await (adapter as any).preSync?.(adapter)
|
|
247
|
+
|
|
248
|
+
{
|
|
249
|
+
await using _session = new SyncSession(adapter)
|
|
250
|
+
await adapter.transaction(tx =>
|
|
251
|
+
executeSyncPlan(
|
|
252
|
+
tx,
|
|
253
|
+
plan,
|
|
254
|
+
constraints,
|
|
255
|
+
indexesToDrop,
|
|
256
|
+
indexesToAdd,
|
|
257
|
+
MESSAGES,
|
|
258
|
+
tsFks,
|
|
259
|
+
fksToAdd,
|
|
260
|
+
fksToDrop,
|
|
261
|
+
),
|
|
262
|
+
)
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// After the transaction commits, never inside it: MySQL commits DDL
|
|
266
|
+
// implicitly, so there is no unit of work these two could share anyway, and
|
|
267
|
+
// writing the ledger first would claim a migration that had not happened.
|
|
268
|
+
// Best-effort by design — a sync that succeeded still succeeded, and a
|
|
269
|
+
// missing ledger only costs the next run its fast path.
|
|
270
|
+
// `tsIndexes`, not `adapter.getIndexes()`: the ledger records what was
|
|
271
|
+
// *applied*, and re-reading the database here would record whatever the
|
|
272
|
+
// dialect reports back — the exact normalisation problem the ledger exists
|
|
273
|
+
// to route around.
|
|
274
|
+
await writeLedger(adapter, constraints, tsIndexes)
|
|
275
|
+
|
|
276
|
+
MESSAGES.CATCH_UP_SUCCESS()
|
|
277
|
+
|
|
278
|
+
if (hasOldWrappers(constraints)) {
|
|
279
|
+
MESSAGES.OVERRIDE_SCHEMA()
|
|
280
|
+
await genLocal(constraints)
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
static async run(
|
|
285
|
+
adapter: SQLAdapter,
|
|
286
|
+
constraints: SyncTypes.DBConstraints,
|
|
287
|
+
tsIndexes: SyncTypes.DBIndexes,
|
|
288
|
+
schemaPath: string,
|
|
289
|
+
layout: SchemaLayout = 'file',
|
|
290
|
+
): Promise<void> {
|
|
291
|
+
const genLocal = (c: any = {}) =>
|
|
292
|
+
SchemaBuilder.generate(adapter, schemaPath, MESSAGES, c, layout)
|
|
293
|
+
const isEmpty = await SyncEngine.checkEmptyConstraints(
|
|
294
|
+
adapter,
|
|
295
|
+
constraints,
|
|
296
|
+
genLocal,
|
|
297
|
+
schemaPath,
|
|
298
|
+
)
|
|
299
|
+
if (isEmpty) return
|
|
300
|
+
// No SQLite special case here. `adjustSqlitePlan` used to rebuild every
|
|
301
|
+
// renamed table and then discard `columnsToRename` wholesale whenever any
|
|
302
|
+
// column rename existed, which dated from SQLite before 3.25 (2018) having
|
|
303
|
+
// no ALTER TABLE … RENAME COLUMN. Bun ships 3.53. Measured against a real
|
|
304
|
+
// database, the special case lost a rename in a non-renamed table silently
|
|
305
|
+
// — reporting a perfect sync — and threw outright in the two cases where it
|
|
306
|
+
// did fire. See sync/engine.test.ts, which pins all four.
|
|
307
|
+
const plan = await buildSyncPlan(adapter, constraints, logger, MESSAGES)
|
|
308
|
+
|
|
309
|
+
// `buildSyncPlan` has always recorded which state it diffed against and
|
|
310
|
+
// nothing ever read it. Falling back to introspection is *correct* — see
|
|
311
|
+
// sync/ledger.ts — but doing it silently means a column somebody added by
|
|
312
|
+
// hand in production looks exactly like an ordinary run. "No ledger yet" is
|
|
313
|
+
// the normal state of a fresh database and is not drift.
|
|
314
|
+
if (
|
|
315
|
+
plan.ledgerSource === 'introspection' &&
|
|
316
|
+
plan.ledgerReason &&
|
|
317
|
+
plan.ledgerReason !== 'no ledger yet'
|
|
318
|
+
) {
|
|
319
|
+
MESSAGES.LEDGER_DRIFT({ reason: plan.ledgerReason })
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const dbIndexes = await adapter.getIndexes()
|
|
323
|
+
const { indexesToDrop, indexesToAdd } = calculateIndexDiff(
|
|
324
|
+
dbIndexes,
|
|
325
|
+
tsIndexes,
|
|
326
|
+
plan.tablesToRebuild,
|
|
327
|
+
)
|
|
328
|
+
|
|
329
|
+
const tsFks = collectForeignKeys(tsIndexes)
|
|
330
|
+
const dbFks = await adapter.getForeignKeys()
|
|
331
|
+
// A table being created carries its foreign keys inline from CREATE TABLE,
|
|
332
|
+
// so counting them here would double up.
|
|
333
|
+
const beingCreated = new Set([...plan.unmappedTsTables].map(Case.snake))
|
|
334
|
+
const { fksToAdd, fksToDrop } = calculateForeignKeyDiff(
|
|
335
|
+
dbFks,
|
|
336
|
+
tsFks,
|
|
337
|
+
plan.tablesToRebuild,
|
|
338
|
+
beingCreated,
|
|
339
|
+
)
|
|
340
|
+
// SQLite cannot ALTER a foreign key in or out — the constraint is part of
|
|
341
|
+
// the table definition — so those become table rebuilds, which recreate the
|
|
342
|
+
// table with the keys inline. Decided here rather than in the adapter so
|
|
343
|
+
// the printed plan states the work that will actually happen.
|
|
344
|
+
if (!adapter.supportsAlterForeignKey) {
|
|
345
|
+
for (const fk of [...fksToAdd.values(), ...fksToDrop.values()]) {
|
|
346
|
+
plan.tablesToRebuild.add(Case.snake(fk.table))
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
const { isDangerous, hasChanges: planChanged } = SyncEngine.evaluateChanges(
|
|
351
|
+
plan,
|
|
352
|
+
indexesToDrop,
|
|
353
|
+
indexesToAdd,
|
|
354
|
+
)
|
|
355
|
+
const hasChanges = planChanged || fksToAdd.size > 0 || fksToDrop.size > 0
|
|
356
|
+
|
|
357
|
+
if (!hasChanges) {
|
|
358
|
+
MESSAGES.PERFECT_SYNC()
|
|
359
|
+
return
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
const argv = process.argv
|
|
363
|
+
if (argv.find(a => a.startsWith('--choose='))?.split('=')[1] === 'db') {
|
|
364
|
+
MESSAGES.GEN_TYPES()
|
|
365
|
+
return await genLocal(plan.dbConstraintsForDiff)
|
|
366
|
+
}
|
|
367
|
+
logPlannedChanges(plan, indexesToDrop, indexesToAdd, isDangerous, MESSAGES)
|
|
368
|
+
if (argv.includes('--dry-run')) {
|
|
369
|
+
// `logger.log` takes the level as its second argument. A leading 'D ' is
|
|
370
|
+
// `messageLogger` table syntax, and this is not a table — so the letter
|
|
371
|
+
// was printed verbatim and the level defaulted.
|
|
372
|
+
//
|
|
373
|
+
// `info`, not the `debug` the stray 'D' was reaching for: this line is
|
|
374
|
+
// the confirmation that nothing was written. Routing it to a level that
|
|
375
|
+
// is filtered by default would mean `--dry-run` could print a page of
|
|
376
|
+
// destructive-looking changes and never say it did not apply them.
|
|
377
|
+
logger.log(
|
|
378
|
+
'Dry-run enabled: planned changes shown above, not applying.',
|
|
379
|
+
'info',
|
|
380
|
+
)
|
|
381
|
+
return
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
SyncEngine.handleSafetyChecks(isDangerous, argv)
|
|
385
|
+
await SyncEngine.executeSyncPipeline(
|
|
386
|
+
adapter,
|
|
387
|
+
plan,
|
|
388
|
+
constraints,
|
|
389
|
+
indexesToDrop,
|
|
390
|
+
indexesToAdd,
|
|
391
|
+
genLocal,
|
|
392
|
+
isDangerous,
|
|
393
|
+
tsFks,
|
|
394
|
+
fksToAdd,
|
|
395
|
+
fksToDrop,
|
|
396
|
+
tsIndexes,
|
|
397
|
+
)
|
|
398
|
+
}
|
|
399
|
+
}
|