@c9up/atlas 0.3.4 → 0.3.6
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/db.win32-x64-msvc.node +0 -0
- package/dist/AtlasProvider.d.ts +53 -16
- package/dist/AtlasProvider.d.ts.map +1 -1
- package/dist/AtlasProvider.js +10 -3
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
- package/dist/adapters/NapiDbAdapter.js +34 -4
- package/dist/adapters/NapiDbAdapter.js.map +1 -1
- package/dist/configure.d.ts +1 -0
- package/dist/configure.d.ts.map +1 -1
- package/dist/configure.js +12 -1
- package/dist/configure.js.map +1 -1
- package/dist/console/index.d.ts +39 -0
- package/dist/console/index.d.ts.map +1 -0
- package/dist/console/index.js +144 -0
- package/dist/console/index.js.map +1 -0
- package/dist/console/migrationCommands.d.ts +12 -0
- package/dist/console/migrationCommands.d.ts.map +1 -1
- package/dist/console/migrationCommands.js +5 -0
- package/dist/console/migrationCommands.js.map +1 -1
- package/dist/console/schemaCheckCommand.d.ts +7 -5
- package/dist/console/schemaCheckCommand.d.ts.map +1 -1
- package/dist/console/schemaCheckCommand.js +16 -5
- package/dist/console/schemaCheckCommand.js.map +1 -1
- package/dist/services/db.d.ts +24 -1
- package/dist/services/db.d.ts.map +1 -1
- package/dist/services/db.js +66 -0
- package/dist/services/db.js.map +1 -1
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +5 -1
- package/src/AtlasProvider.ts +66 -19
- package/src/adapters/NapiDbAdapter.ts +37 -4
- package/src/configure.ts +13 -1
- package/src/console/index.ts +202 -0
- package/src/console/migrationCommands.ts +17 -0
- package/src/console/schemaCheckCommand.ts +19 -6
- package/src/services/db.ts +84 -1
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@c9up/atlas/commands` — every command atlas ships, configured from
|
|
3
|
+
* `config/database.ts`.
|
|
4
|
+
*
|
|
5
|
+
* // reamrc.ts, written by `configure()`
|
|
6
|
+
* commands: [() => import('@c9up/atlas/commands')]
|
|
7
|
+
*
|
|
8
|
+
* This is the shape Adonis Lucid ships (`@adonisjs/lucid/commands`, a module
|
|
9
|
+
* exporting `getMetaData` / `getCommand`), and it is the whole point: a package
|
|
10
|
+
* adds commands by shipping them, never by a change to the `ream` binary.
|
|
11
|
+
*
|
|
12
|
+
* Where the paths come from is Lucid's answer too — the connection's config
|
|
13
|
+
* (`migrations.paths`, `seeders.paths`, `schemaGeneration.outputPath`). Lucid
|
|
14
|
+
* reads it through the container; atlas reads it from its own locator, which
|
|
15
|
+
* the provider fills at boot, so nothing here imports `@c9up/ream`.
|
|
16
|
+
*
|
|
17
|
+
* The option objects below use getters on purpose. A command class is built
|
|
18
|
+
* when this module is imported — before the application boots — while every
|
|
19
|
+
* `options.x` is read inside `run()`, after it. A plain object would capture
|
|
20
|
+
* the config that did not exist yet.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
connectionConfigFor,
|
|
25
|
+
getDatabaseConfig,
|
|
26
|
+
primaryConnectionName,
|
|
27
|
+
} from "../services/db.js";
|
|
28
|
+
import type { AtlasCommandClass } from "./contract.js";
|
|
29
|
+
import {
|
|
30
|
+
type FactoryCommandOptions,
|
|
31
|
+
makeFactoryCommand,
|
|
32
|
+
} from "./factoryCommands.js";
|
|
33
|
+
import {
|
|
34
|
+
dbWipeCommand,
|
|
35
|
+
type MigrationCommandOptions,
|
|
36
|
+
makeMigrationCommand,
|
|
37
|
+
migrationFreshCommand,
|
|
38
|
+
migrationRefreshCommand,
|
|
39
|
+
migrationResetCommand,
|
|
40
|
+
migrationRollbackCommand,
|
|
41
|
+
migrationRunCommand,
|
|
42
|
+
migrationStatusCommand,
|
|
43
|
+
migrationUnlockCommand,
|
|
44
|
+
} from "./migrationCommands.js";
|
|
45
|
+
import { schemaCheckCommand } from "./schemaCheckCommand.js";
|
|
46
|
+
import {
|
|
47
|
+
type SchemaDumpCommandOptions,
|
|
48
|
+
schemaDumpCommand,
|
|
49
|
+
} from "./schemaDumpCommand.js";
|
|
50
|
+
import {
|
|
51
|
+
type SchemaGenerateOptions,
|
|
52
|
+
schemaGenerateCommand,
|
|
53
|
+
} from "./schemaGenerateCommand.js";
|
|
54
|
+
import {
|
|
55
|
+
dbSeedCommand,
|
|
56
|
+
makeSeederCommand,
|
|
57
|
+
type SeederCommandOptions,
|
|
58
|
+
} from "./seederCommands.js";
|
|
59
|
+
|
|
60
|
+
/** Lucid's defaults, for a config that leaves the paths out. */
|
|
61
|
+
const DEFAULT_MIGRATIONS_DIR = "database/migrations";
|
|
62
|
+
const DEFAULT_SEEDERS_DIR = "database/seeders";
|
|
63
|
+
const DEFAULT_FACTORIES_DIR = "database/factories";
|
|
64
|
+
|
|
65
|
+
/** The default connection's config — per-connection first, then the top level. */
|
|
66
|
+
function connection() {
|
|
67
|
+
return connectionConfigFor();
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function migrationsDir(): string {
|
|
71
|
+
const migrations = connection().migrations;
|
|
72
|
+
return migrations?.paths?.[0] ?? migrations?.path ?? DEFAULT_MIGRATIONS_DIR;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const migrationOptions: MigrationCommandOptions = {
|
|
76
|
+
get migrationsDir() {
|
|
77
|
+
return migrationsDir();
|
|
78
|
+
},
|
|
79
|
+
get naturalSort() {
|
|
80
|
+
return connection().migrations?.naturalSort;
|
|
81
|
+
},
|
|
82
|
+
get disableTransactions() {
|
|
83
|
+
return connection().migrations?.disableTransactions;
|
|
84
|
+
},
|
|
85
|
+
get tableName() {
|
|
86
|
+
return connection().migrations?.tableName ?? connection().migrations?.table;
|
|
87
|
+
},
|
|
88
|
+
get disableRollbacksInProduction() {
|
|
89
|
+
return connection().migrations?.disableRollbacksInProduction;
|
|
90
|
+
},
|
|
91
|
+
get schemaGeneration() {
|
|
92
|
+
return connection().schemaGeneration;
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const seederOptions: SeederCommandOptions = {
|
|
97
|
+
get seedersDir() {
|
|
98
|
+
return connection().seeders?.paths?.[0] ?? DEFAULT_SEEDERS_DIR;
|
|
99
|
+
},
|
|
100
|
+
get naturalSort() {
|
|
101
|
+
return connection().seeders?.naturalSort;
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
const factoryOptions: FactoryCommandOptions = {
|
|
106
|
+
get factoriesDir() {
|
|
107
|
+
return connection().factories?.path ?? DEFAULT_FACTORIES_DIR;
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const schemaDumpOptions: SchemaDumpCommandOptions = {
|
|
112
|
+
get migrationsDir() {
|
|
113
|
+
return migrationsDir();
|
|
114
|
+
},
|
|
115
|
+
get schemaTableName() {
|
|
116
|
+
return connection().migrations?.tableName ?? connection().migrations?.table;
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
// `outputPath` has no sensible default — a command that guessed one would write
|
|
121
|
+
// a generated file into a directory the project never chose. The command
|
|
122
|
+
// reports the missing key itself; an empty string is what it checks for.
|
|
123
|
+
const schemaGenerateOptions: SchemaGenerateOptions = {
|
|
124
|
+
get outputPath() {
|
|
125
|
+
return connection().schemaGeneration?.outputPath ?? "";
|
|
126
|
+
},
|
|
127
|
+
get excludeTables() {
|
|
128
|
+
return connection().schemaGeneration?.excludeTables;
|
|
129
|
+
},
|
|
130
|
+
get enabled() {
|
|
131
|
+
return connection().schemaGeneration?.enabled;
|
|
132
|
+
},
|
|
133
|
+
get rulesPaths() {
|
|
134
|
+
return connection().schemaGeneration?.rulesPaths;
|
|
135
|
+
},
|
|
136
|
+
get compact() {
|
|
137
|
+
return connection().schemaGeneration?.compact;
|
|
138
|
+
},
|
|
139
|
+
get schemas() {
|
|
140
|
+
return connection().schemaGeneration?.schemas;
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const COMMANDS: readonly AtlasCommandClass[] = [
|
|
145
|
+
migrationRunCommand(migrationOptions),
|
|
146
|
+
migrationRollbackCommand(migrationOptions),
|
|
147
|
+
migrationStatusCommand(migrationOptions),
|
|
148
|
+
migrationResetCommand(migrationOptions),
|
|
149
|
+
migrationRefreshCommand(migrationOptions),
|
|
150
|
+
migrationFreshCommand(migrationOptions),
|
|
151
|
+
migrationUnlockCommand(migrationOptions),
|
|
152
|
+
makeMigrationCommand(migrationOptions),
|
|
153
|
+
dbWipeCommand(migrationOptions),
|
|
154
|
+
dbSeedCommand(seederOptions),
|
|
155
|
+
makeSeederCommand(seederOptions),
|
|
156
|
+
makeFactoryCommand(factoryOptions),
|
|
157
|
+
schemaDumpCommand(schemaDumpOptions),
|
|
158
|
+
schemaGenerateCommand(schemaGenerateOptions),
|
|
159
|
+
schemaCheckCommand(() => getDatabaseConfig()?.verifySchema?.entities ?? []),
|
|
160
|
+
];
|
|
161
|
+
|
|
162
|
+
/** What the kernel reads to list a command without importing it. */
|
|
163
|
+
interface CommandMetaData {
|
|
164
|
+
commandName: string;
|
|
165
|
+
namespace: string | null;
|
|
166
|
+
description: string;
|
|
167
|
+
help?: string | string[];
|
|
168
|
+
aliases: string[];
|
|
169
|
+
options: Record<string, unknown>;
|
|
170
|
+
args: readonly unknown[];
|
|
171
|
+
flags: readonly unknown[];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function serialize(command: AtlasCommandClass): CommandMetaData {
|
|
175
|
+
const colon = command.commandName.indexOf(":");
|
|
176
|
+
return {
|
|
177
|
+
commandName: command.commandName,
|
|
178
|
+
namespace: colon === -1 ? null : command.commandName.slice(0, colon),
|
|
179
|
+
description: command.description,
|
|
180
|
+
help: command.help,
|
|
181
|
+
aliases: [],
|
|
182
|
+
options: { ...command.options },
|
|
183
|
+
args: command.args ?? [],
|
|
184
|
+
flags: command.flags ?? [],
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function getMetaData(): Promise<CommandMetaData[]> {
|
|
189
|
+
return COMMANDS.map(serialize);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
export async function getCommand(
|
|
193
|
+
metadata: CommandMetaData,
|
|
194
|
+
): Promise<AtlasCommandClass | null> {
|
|
195
|
+
return (
|
|
196
|
+
COMMANDS.find((command) => command.commandName === metadata.commandName) ??
|
|
197
|
+
null
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/** The connection these commands run against — Lucid `db.primaryConnectionName`. */
|
|
202
|
+
export { primaryConnectionName };
|
|
@@ -61,6 +61,18 @@ export interface MigrationCommandOptions {
|
|
|
61
61
|
* loads it once migrations exist.
|
|
62
62
|
*/
|
|
63
63
|
schemaPath?: string;
|
|
64
|
+
/**
|
|
65
|
+
* Migration bookkeeping table — Adonis Lucid `migrations.tableName`. Must
|
|
66
|
+
* match what the application boots with: a command tracking migrations in a
|
|
67
|
+
* different table than the app would re-apply every migration it cannot see.
|
|
68
|
+
* Defaults to the runner's `"ream_migrations"`.
|
|
69
|
+
*/
|
|
70
|
+
tableName?: string;
|
|
71
|
+
/**
|
|
72
|
+
* Refuse rollback/reset/refresh/fresh/wipe in production unless `--force`
|
|
73
|
+
* (Adonis Lucid `migrations.disableRollbacksInProduction`). On by default.
|
|
74
|
+
*/
|
|
75
|
+
disableRollbacksInProduction?: boolean;
|
|
64
76
|
}
|
|
65
77
|
|
|
66
78
|
/**
|
|
@@ -105,6 +117,11 @@ function resolveRunner(
|
|
|
105
117
|
dialect: db.dialect,
|
|
106
118
|
naturalSort: options.naturalSort,
|
|
107
119
|
disableTransactions: options.disableTransactions,
|
|
120
|
+
// Without the table name the runner falls back to its default while the
|
|
121
|
+
// application tracks migrations in the configured one — the command would
|
|
122
|
+
// then see an empty history and re-apply everything.
|
|
123
|
+
tableName: options.tableName,
|
|
124
|
+
disableRollbacksInProduction: options.disableRollbacksInProduction,
|
|
108
125
|
});
|
|
109
126
|
}
|
|
110
127
|
|
|
@@ -21,10 +21,12 @@ type Constructor = new (...args: unknown[]) => unknown;
|
|
|
21
21
|
export type { AtlasCommandClass } from "./contract.js";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
|
-
* Build the `atlas:check` command for the given models.
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
24
|
+
* Build the `atlas:check` command for the given models.
|
|
25
|
+
*
|
|
26
|
+
* Most applications do not call this: `@c9up/atlas/commands` ships the command
|
|
27
|
+
* already, reading the models from `verifySchema.entities`. Call it directly to
|
|
28
|
+
* verify a different set — pass a function when the list is only known once the
|
|
29
|
+
* application has booted.
|
|
28
30
|
*
|
|
29
31
|
* @example
|
|
30
32
|
* // commands/atlas-check.ts
|
|
@@ -35,7 +37,7 @@ export type { AtlasCommandClass } from "./contract.js";
|
|
|
35
37
|
* // run: ream atlas:check --warn
|
|
36
38
|
*/
|
|
37
39
|
export function schemaCheckCommand(
|
|
38
|
-
entities: readonly Constructor[],
|
|
40
|
+
entities: readonly Constructor[] | (() => readonly Constructor[]),
|
|
39
41
|
): AtlasCommandClass {
|
|
40
42
|
return class SchemaCheck {
|
|
41
43
|
static commandName = "atlas:check";
|
|
@@ -58,7 +60,18 @@ export function schemaCheckCommand(
|
|
|
58
60
|
process.exitCode = 1;
|
|
59
61
|
return;
|
|
60
62
|
}
|
|
61
|
-
|
|
63
|
+
// Resolved here, not at registration: the shipped command reads the
|
|
64
|
+
// models from `verifySchema.entities`, which only exists once the
|
|
65
|
+
// application has booted its config.
|
|
66
|
+
const models = typeof entities === "function" ? entities() : entities;
|
|
67
|
+
if (models.length === 0) {
|
|
68
|
+
console.error(
|
|
69
|
+
"[atlas:check] no models to verify — list them under `verifySchema.entities` in config/database.ts.",
|
|
70
|
+
);
|
|
71
|
+
process.exitCode = 1;
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
const code = await runSchemaCheck(models, db, getAtlasDialect());
|
|
62
75
|
// `--warn` downgrades drift to advisory (exit 0); default fails CI.
|
|
63
76
|
if (code !== 0 && !this.warn) process.exitCode = code;
|
|
64
77
|
}
|
package/src/services/db.ts
CHANGED
|
@@ -13,7 +13,10 @@
|
|
|
13
13
|
* container hooks).
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
import type {
|
|
16
|
+
import type {
|
|
17
|
+
AtlasDatabaseConfig,
|
|
18
|
+
ConnectionConfig,
|
|
19
|
+
} from "../AtlasProvider.js";
|
|
17
20
|
import type { AsyncDatabaseConnection } from "../adapters/NapiDbAdapter.js";
|
|
18
21
|
import { ConnectionManager } from "../ConnectionManager.js";
|
|
19
22
|
import {
|
|
@@ -171,6 +174,86 @@ export function getDb(): AsyncDatabaseConnection | undefined {
|
|
|
171
174
|
return instance;
|
|
172
175
|
}
|
|
173
176
|
|
|
177
|
+
// The config the provider booted with. Commands need it: `migration:run` has to
|
|
178
|
+
// know where the migration files are, and Lucid answers that from the config
|
|
179
|
+
// too (`connection.config.migrations.paths`, read through `lucid.db`). Atlas has
|
|
180
|
+
// no framework container to read, so the provider records it here.
|
|
181
|
+
let databaseConfig:
|
|
182
|
+
| { config: AtlasDatabaseConfig; defaultName: string }
|
|
183
|
+
| undefined;
|
|
184
|
+
|
|
185
|
+
/** @internal Record the booted config (called by AtlasProvider). */
|
|
186
|
+
export function setDatabaseConfig(
|
|
187
|
+
config: AtlasDatabaseConfig,
|
|
188
|
+
defaultName: string,
|
|
189
|
+
): void {
|
|
190
|
+
databaseConfig = { config, defaultName };
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @internal Forget the config IF it is still the one `config` describes.
|
|
195
|
+
* Ownership-guarded for the same reason {@link clearDb} is: a second provider
|
|
196
|
+
* may have rebound it, and the older provider's shutdown must not clear the
|
|
197
|
+
* newer binding.
|
|
198
|
+
*/
|
|
199
|
+
export function clearDatabaseConfig(config: AtlasDatabaseConfig): void {
|
|
200
|
+
if (databaseConfig?.config === config) databaseConfig = undefined;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** The config the application booted with, or `undefined` before boot. */
|
|
204
|
+
export function getDatabaseConfig(): AtlasDatabaseConfig | undefined {
|
|
205
|
+
return databaseConfig?.config;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** The default connection's name — Lucid `db.primaryConnectionName`. */
|
|
209
|
+
export function primaryConnectionName(): string {
|
|
210
|
+
return databaseConfig?.defaultName ?? "primary";
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* One connection's config, by name (default connection when unnamed).
|
|
215
|
+
*
|
|
216
|
+
* Read from the manager's node first — that is where Lucid keeps `migrations`,
|
|
217
|
+
* `seeders` and `schemaGeneration`, and the shape we mirror. The top level is
|
|
218
|
+
* the fallback, because the single-connection form IS the connection config,
|
|
219
|
+
* and because an app that set `migrations` there before these keys were
|
|
220
|
+
* per-connection must keep working.
|
|
221
|
+
*/
|
|
222
|
+
export function connectionConfigFor(name?: string): ConnectionConfig {
|
|
223
|
+
const resolved = name ?? primaryConnectionName();
|
|
224
|
+
const top = databaseConfig?.config;
|
|
225
|
+
// The manager knows a connection once the provider has registered it; before
|
|
226
|
+
// that — a command running against a config nobody opened yet — the declared
|
|
227
|
+
// `connections` entry is the same information.
|
|
228
|
+
const declared =
|
|
229
|
+
manager.get(resolved)?.config ?? top?.connections?.[resolved];
|
|
230
|
+
if (top === undefined) return declared ?? {};
|
|
231
|
+
if (declared === undefined) return top;
|
|
232
|
+
return {
|
|
233
|
+
...top,
|
|
234
|
+
...declared,
|
|
235
|
+
// These three are merged a level down rather than replaced: the top level
|
|
236
|
+
// is the single-connection form of the same config, so a connection that
|
|
237
|
+
// names only its paths must not drop a `tableName` set beside it.
|
|
238
|
+
migrations: mergeOrUndefined(top.migrations, declared.migrations),
|
|
239
|
+
seeders: mergeOrUndefined(top.seeders, declared.seeders),
|
|
240
|
+
schemaGeneration: mergeOrUndefined(
|
|
241
|
+
top.schemaGeneration,
|
|
242
|
+
declared.schemaGeneration,
|
|
243
|
+
),
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** `{...a, ...b}`, or `undefined` when neither side has anything to merge. */
|
|
248
|
+
function mergeOrUndefined<T extends object>(
|
|
249
|
+
base: T | undefined,
|
|
250
|
+
override: T | undefined,
|
|
251
|
+
): T | undefined {
|
|
252
|
+
if (base === undefined) return override;
|
|
253
|
+
if (override === undefined) return base;
|
|
254
|
+
return { ...base, ...override };
|
|
255
|
+
}
|
|
256
|
+
|
|
174
257
|
// The shared connection manager (Lucid `db.manager`) — the single owner of named
|
|
175
258
|
// connections. Backs `BaseModel.connection = 'analytics'` so a model resolves a
|
|
176
259
|
// non-default connection from a plain import (AdonisJS `static connection`).
|