@c9up/atlas 0.1.19 → 0.2.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/README.md +63 -14
- package/db.darwin-arm64.node +0 -0
- package/db.darwin-x64.node +0 -0
- package/db.linux-arm64-gnu.node +0 -0
- package/db.linux-x64-gnu.node +0 -0
- package/db.win32-x64-msvc.node +0 -0
- package/dist/AtlasProvider.d.ts +66 -7
- package/dist/AtlasProvider.d.ts.map +1 -1
- package/dist/AtlasProvider.js +205 -39
- package/dist/AtlasProvider.js.map +1 -1
- package/dist/BaseEntity.d.ts +181 -5
- package/dist/BaseEntity.d.ts.map +1 -1
- package/dist/BaseEntity.js +339 -33
- package/dist/BaseEntity.js.map +1 -1
- package/dist/BaseModel.d.ts +127 -0
- package/dist/BaseModel.d.ts.map +1 -0
- package/dist/BaseModel.js +250 -0
- package/dist/BaseModel.js.map +1 -0
- package/dist/BaseRepository.d.ts +81 -15
- package/dist/BaseRepository.d.ts.map +1 -1
- package/dist/BaseRepository.js +1433 -387
- package/dist/BaseRepository.js.map +1 -1
- package/dist/ConnectionManager.d.ts +106 -0
- package/dist/ConnectionManager.d.ts.map +1 -0
- package/dist/ConnectionManager.js +228 -0
- package/dist/ConnectionManager.js.map +1 -0
- package/dist/ModelQuery.d.ts +577 -41
- package/dist/ModelQuery.d.ts.map +1 -1
- package/dist/ModelQuery.js +2188 -291
- package/dist/ModelQuery.js.map +1 -1
- package/dist/Transaction.d.ts +71 -1
- package/dist/Transaction.d.ts.map +1 -1
- package/dist/Transaction.js +138 -24
- package/dist/Transaction.js.map +1 -1
- package/dist/adapters/NapiDbAdapter.d.ts +44 -4
- package/dist/adapters/NapiDbAdapter.d.ts.map +1 -1
- package/dist/adapters/NapiDbAdapter.js +131 -13
- package/dist/adapters/NapiDbAdapter.js.map +1 -1
- package/dist/console/contract.d.ts +62 -0
- package/dist/console/contract.d.ts.map +1 -0
- package/dist/console/contract.js +38 -0
- package/dist/console/contract.js.map +1 -0
- package/dist/console/factoryCommands.d.ts +23 -0
- package/dist/console/factoryCommands.d.ts.map +1 -0
- package/dist/console/factoryCommands.js +62 -0
- package/dist/console/factoryCommands.js.map +1 -0
- package/dist/console/migrationCommands.d.ts +78 -0
- package/dist/console/migrationCommands.d.ts.map +1 -0
- package/dist/console/migrationCommands.js +327 -0
- package/dist/console/migrationCommands.js.map +1 -0
- package/dist/console/schemaCheckCommand.d.ts +11 -17
- package/dist/console/schemaCheckCommand.d.ts.map +1 -1
- package/dist/console/schemaCheckCommand.js +21 -15
- package/dist/console/schemaCheckCommand.js.map +1 -1
- package/dist/console/schemaDumpCommand.d.ts +30 -0
- package/dist/console/schemaDumpCommand.d.ts.map +1 -0
- package/dist/console/schemaDumpCommand.js +69 -0
- package/dist/console/schemaDumpCommand.js.map +1 -0
- package/dist/console/schemaGenerateCommand.d.ts +100 -0
- package/dist/console/schemaGenerateCommand.d.ts.map +1 -0
- package/dist/console/schemaGenerateCommand.js +246 -0
- package/dist/console/schemaGenerateCommand.js.map +1 -0
- package/dist/console/seederCommands.d.ts +46 -0
- package/dist/console/seederCommands.d.ts.map +1 -0
- package/dist/console/seederCommands.js +136 -0
- package/dist/console/seederCommands.js.map +1 -0
- package/dist/decorators/entity.d.ts +50 -8
- package/dist/decorators/entity.d.ts.map +1 -1
- package/dist/decorators/entity.js +42 -2
- package/dist/decorators/entity.js.map +1 -1
- package/dist/decorators/hooks.d.ts +3 -3
- package/dist/decorators/hooks.d.ts.map +1 -1
- package/dist/decorators/hooks.js.map +1 -1
- package/dist/events.d.ts +69 -0
- package/dist/events.d.ts.map +1 -0
- package/dist/events.js +82 -0
- package/dist/events.js.map +1 -0
- package/dist/factories.d.ts +8 -0
- package/dist/factories.d.ts.map +1 -0
- package/dist/factories.js +8 -0
- package/dist/factories.js.map +1 -0
- package/dist/index.d.ts +15 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -3
- package/dist/index.js.map +1 -1
- package/dist/lucid-schema.d.ts +15 -0
- package/dist/lucid-schema.d.ts.map +1 -0
- package/dist/lucid-schema.js +16 -0
- package/dist/lucid-schema.js.map +1 -0
- package/dist/metadata-keys.d.ts +3 -2
- package/dist/metadata-keys.d.ts.map +1 -1
- package/dist/naming/NamingStrategy.d.ts +7 -0
- package/dist/naming/NamingStrategy.d.ts.map +1 -1
- package/dist/naming/NamingStrategy.js +16 -0
- package/dist/naming/NamingStrategy.js.map +1 -1
- package/dist/orm.d.ts +14 -0
- package/dist/orm.d.ts.map +1 -0
- package/dist/orm.js +14 -0
- package/dist/orm.js.map +1 -0
- package/dist/query/DatabaseQueryBuilder.d.ts +557 -0
- package/dist/query/DatabaseQueryBuilder.d.ts.map +1 -0
- package/dist/query/DatabaseQueryBuilder.js +1798 -0
- package/dist/query/DatabaseQueryBuilder.js.map +1 -0
- package/dist/query/DmlBuilder.d.ts +62 -0
- package/dist/query/DmlBuilder.d.ts.map +1 -0
- package/dist/query/DmlBuilder.js +84 -0
- package/dist/query/DmlBuilder.js.map +1 -0
- package/dist/query/QueryBuilder.d.ts +5 -0
- package/dist/query/QueryBuilder.d.ts.map +1 -1
- package/dist/query/QueryBuilder.js +7 -0
- package/dist/query/QueryBuilder.js.map +1 -1
- package/dist/query/RawQueryBuilder.d.ts +47 -0
- package/dist/query/RawQueryBuilder.d.ts.map +1 -0
- package/dist/query/RawQueryBuilder.js +138 -0
- package/dist/query/RawQueryBuilder.js.map +1 -0
- package/dist/query/interpolate.d.ts +45 -0
- package/dist/query/interpolate.d.ts.map +1 -0
- package/dist/query/interpolate.js +51 -0
- package/dist/query/interpolate.js.map +1 -0
- package/dist/query/native.d.ts +8 -0
- package/dist/query/native.d.ts.map +1 -1
- package/dist/query/native.js +14 -0
- package/dist/query/native.js.map +1 -1
- package/dist/query/operators.d.ts +4 -0
- package/dist/query/operators.d.ts.map +1 -0
- package/dist/query/operators.js +27 -0
- package/dist/query/operators.js.map +1 -0
- package/dist/schema/Migration.d.ts +50 -3
- package/dist/schema/Migration.d.ts.map +1 -1
- package/dist/schema/Migration.js +60 -24
- package/dist/schema/Migration.js.map +1 -1
- package/dist/schema/MigrationRunner.d.ts +95 -34
- package/dist/schema/MigrationRunner.d.ts.map +1 -1
- package/dist/schema/MigrationRunner.js +523 -68
- package/dist/schema/MigrationRunner.js.map +1 -1
- package/dist/schema/Schema.d.ts +114 -0
- package/dist/schema/Schema.d.ts.map +1 -1
- package/dist/schema/Schema.js +272 -3
- package/dist/schema/Schema.js.map +1 -1
- package/dist/schema/SchemaDumper.d.ts +121 -0
- package/dist/schema/SchemaDumper.d.ts.map +1 -0
- package/dist/schema/SchemaDumper.js +365 -0
- package/dist/schema/SchemaDumper.js.map +1 -0
- package/dist/schema/Seeder.d.ts +13 -0
- package/dist/schema/Seeder.d.ts.map +1 -1
- package/dist/schema/Seeder.js +28 -5
- package/dist/schema/Seeder.js.map +1 -1
- package/dist/schema/TableBuilder.d.ts +274 -16
- package/dist/schema/TableBuilder.d.ts.map +1 -1
- package/dist/schema/TableBuilder.js +656 -52
- package/dist/schema/TableBuilder.js.map +1 -1
- package/dist/schema/catalog.d.ts +71 -0
- package/dist/schema/catalog.d.ts.map +1 -0
- package/dist/schema/catalog.js +158 -0
- package/dist/schema/catalog.js.map +1 -0
- package/dist/schema/introspect.d.ts +1 -1
- package/dist/schema/introspect.d.ts.map +1 -1
- package/dist/schema/introspect.js +26 -11
- package/dist/schema/introspect.js.map +1 -1
- package/dist/schema/types.d.ts +150 -1
- package/dist/schema/types.d.ts.map +1 -1
- package/dist/schema/types.js +11 -0
- package/dist/schema/types.js.map +1 -1
- package/dist/seeders.d.ts +7 -0
- package/dist/seeders.d.ts.map +1 -0
- package/dist/seeders.js +7 -0
- package/dist/seeders.js.map +1 -0
- package/dist/services/db.d.ts +122 -5
- package/dist/services/db.d.ts.map +1 -1
- package/dist/services/db.js +187 -14
- package/dist/services/db.js.map +1 -1
- package/dist/testing/DatabaseCleanup.d.ts +33 -7
- package/dist/testing/DatabaseCleanup.d.ts.map +1 -1
- package/dist/testing/DatabaseCleanup.js +54 -24
- package/dist/testing/DatabaseCleanup.js.map +1 -1
- package/dist/testing/DbAssertions.d.ts +48 -0
- package/dist/testing/DbAssertions.d.ts.map +1 -0
- package/dist/testing/DbAssertions.js +80 -0
- package/dist/testing/DbAssertions.js.map +1 -0
- package/dist/testing/Factory.d.ts +166 -15
- package/dist/testing/Factory.d.ts.map +1 -1
- package/dist/testing/Factory.js +497 -23
- package/dist/testing/Factory.js.map +1 -1
- package/dist/testing/TestUtils.d.ts +49 -0
- package/dist/testing/TestUtils.d.ts.map +1 -0
- package/dist/testing/TestUtils.js +79 -0
- package/dist/testing/TestUtils.js.map +1 -0
- package/dist/testing/index.d.ts +3 -1
- package/dist/testing/index.d.ts.map +1 -1
- package/dist/testing/index.js +3 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types/relations.d.ts +31 -0
- package/dist/types/relations.d.ts.map +1 -0
- package/dist/types/relations.js +20 -0
- package/dist/types/relations.js.map +1 -0
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +32 -2
- package/scripts/guard-publish.mjs +15 -0
- package/src/AtlasProvider.ts +305 -52
- package/src/BaseEntity.ts +460 -41
- package/src/BaseModel.ts +461 -0
- package/src/BaseRepository.ts +1710 -421
- package/src/ConnectionManager.ts +298 -0
- package/src/ModelQuery.ts +2998 -335
- package/src/Transaction.ts +227 -30
- package/src/adapters/NapiDbAdapter.ts +224 -17
- package/src/console/contract.ts +96 -0
- package/src/console/factoryCommands.ts +73 -0
- package/src/console/migrationCommands.ts +448 -0
- package/src/console/schemaCheckCommand.ts +26 -22
- package/src/console/schemaDumpCommand.ts +96 -0
- package/src/console/schemaGenerateCommand.ts +380 -0
- package/src/console/seederCommands.ts +174 -0
- package/src/decorators/entity.ts +71 -8
- package/src/decorators/hooks.ts +5 -3
- package/src/events.ts +117 -0
- package/src/factories.ts +10 -0
- package/src/index.ts +69 -3
- package/src/lucid-schema.ts +22 -0
- package/src/metadata-keys.ts +3 -2
- package/src/naming/NamingStrategy.ts +23 -0
- package/src/orm.ts +13 -0
- package/src/query/DatabaseQueryBuilder.ts +2724 -0
- package/src/query/DmlBuilder.ts +131 -0
- package/src/query/QueryBuilder.ts +8 -0
- package/src/query/RawQueryBuilder.ts +173 -0
- package/src/query/interpolate.ts +73 -0
- package/src/query/native.ts +29 -0
- package/src/query/operators.ts +27 -0
- package/src/schema/Migration.ts +71 -3
- package/src/schema/MigrationRunner.ts +676 -69
- package/src/schema/Schema.ts +359 -3
- package/src/schema/SchemaDumper.ts +518 -0
- package/src/schema/Seeder.ts +47 -10
- package/src/schema/TableBuilder.ts +783 -52
- package/src/schema/catalog.ts +236 -0
- package/src/schema/introspect.ts +26 -9
- package/src/schema/types.ts +137 -2
- package/src/seeders.ts +16 -0
- package/src/services/db.ts +361 -21
- package/src/testing/DatabaseCleanup.ts +78 -25
- package/src/testing/DbAssertions.ts +127 -0
- package/src/testing/Factory.ts +837 -40
- package/src/testing/TestUtils.ts +123 -0
- package/src/testing/index.ts +12 -1
- package/src/types/relations.ts +43 -0
package/src/AtlasProvider.ts
CHANGED
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* }
|
|
18
18
|
*
|
|
19
19
|
* // Code
|
|
20
|
-
* const primary = app.container.resolve('db')
|
|
21
|
-
* const tenant1 = app.container.resolve('db:tenant1')
|
|
20
|
+
* const primary = await app.container.resolve('db') // default
|
|
21
|
+
* const tenant1 = await app.container.resolve('db:tenant1') // named
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import {
|
|
@@ -50,10 +50,30 @@ export interface AtlasAppContext {
|
|
|
50
50
|
token: string | symbol | (new (...args: never[]) => unknown),
|
|
51
51
|
factory: () => unknown,
|
|
52
52
|
): void;
|
|
53
|
+
/**
|
|
54
|
+
* Optional reader — present on ream's real container. Used to look up the
|
|
55
|
+
* app emitter (`events`) so query events are bridged onto it as `db:query`
|
|
56
|
+
* (AdonisJS parity: `emitter.on('db:query', …)`). Structural, async-capable.
|
|
57
|
+
*/
|
|
58
|
+
resolve?(
|
|
59
|
+
token: string | symbol | (new (...args: never[]) => unknown),
|
|
60
|
+
): unknown | Promise<unknown>;
|
|
53
61
|
};
|
|
54
62
|
config: { get<T = unknown>(key: string): T | undefined };
|
|
55
63
|
}
|
|
56
64
|
|
|
65
|
+
/** True when `x` looks like an event emitter with `emit(event, data)`. */
|
|
66
|
+
function hasEmit(
|
|
67
|
+
x: unknown,
|
|
68
|
+
): x is { emit: (event: string, data: unknown) => void } {
|
|
69
|
+
return (
|
|
70
|
+
typeof x === "object" &&
|
|
71
|
+
x !== null &&
|
|
72
|
+
"emit" in x &&
|
|
73
|
+
typeof x.emit === "function"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
57
77
|
/**
|
|
58
78
|
* Canonical sqlite production pragma recipe.
|
|
59
79
|
*
|
|
@@ -75,12 +95,32 @@ export const SQLITE_PROD_PRAGMAS = Object.freeze({
|
|
|
75
95
|
|
|
76
96
|
/** One connection's settings. */
|
|
77
97
|
export interface ConnectionConfig {
|
|
78
|
-
/**
|
|
79
|
-
|
|
80
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Connection URL: "sqlite:data/app.db", "postgres://...", "mysql://...".
|
|
100
|
+
* Lucid names this per-connection key `connection` — {@link connection} is
|
|
101
|
+
* accepted as an alias, so at least one of the two must be set.
|
|
102
|
+
*/
|
|
103
|
+
url?: string;
|
|
104
|
+
/**
|
|
105
|
+
* Lucid's per-connection URL key (`connections: { primary: { connection: '…' } }`).
|
|
106
|
+
* Alias of {@link url}; `url` wins when both are present.
|
|
107
|
+
*/
|
|
108
|
+
connection?: string;
|
|
109
|
+
/** Minimum pool connections (default: 1). Prefer the Lucid-shaped {@link pool}. */
|
|
81
110
|
poolMin?: number;
|
|
82
|
-
/** Maximum pool connections (default: 10) */
|
|
111
|
+
/** Maximum pool connections (default: 10). Prefer the Lucid-shaped {@link pool}. */
|
|
83
112
|
poolMax?: number;
|
|
113
|
+
/**
|
|
114
|
+
* Connection-pool sizing, Lucid-shaped — `pool: { min: 2, max: 10 }`. Takes
|
|
115
|
+
* precedence over the flat `poolMin`/`poolMax` when both are set.
|
|
116
|
+
*/
|
|
117
|
+
pool?: { min?: number; max?: number };
|
|
118
|
+
/**
|
|
119
|
+
* Emit a `db:query` event for every statement on this connection (Lucid's
|
|
120
|
+
* `debug` connection option). Off by default — subscribe with `onDbQuery`
|
|
121
|
+
* to receive them; with no subscriber nothing is emitted either way.
|
|
122
|
+
*/
|
|
123
|
+
debug?: boolean;
|
|
84
124
|
/**
|
|
85
125
|
* Connection-level pragmas (sqlite only). Each entry becomes a
|
|
86
126
|
* `PRAGMA <key> = <value>;` issued before the first query.
|
|
@@ -109,18 +149,48 @@ export interface ConnectionConfig {
|
|
|
109
149
|
|
|
110
150
|
/** Full database config — single-connection (legacy) OR multi-connection. */
|
|
111
151
|
export interface AtlasDatabaseConfig extends ConnectionConfig {
|
|
112
|
-
/**
|
|
152
|
+
/**
|
|
153
|
+
* Name of the default connection when `connections` is set (Lucid's top-level
|
|
154
|
+
* `connection`). `default` is a deprecated alias — `connection` wins when both
|
|
155
|
+
* are set. Defaults to `"primary"`.
|
|
156
|
+
*/
|
|
157
|
+
connection?: string;
|
|
158
|
+
/** @deprecated Use {@link connection} (the Lucid key). */
|
|
113
159
|
default?: string;
|
|
114
160
|
/** Named connections. When present, top-level `url` is treated as `connections[default].url`. */
|
|
115
161
|
connections?: Record<string, ConnectionConfig>;
|
|
116
162
|
migrations?: {
|
|
163
|
+
/** @deprecated Use the Lucid-shaped {@link paths}. */
|
|
117
164
|
path?: string;
|
|
165
|
+
/** Migration directories, Lucid-shaped — `paths: ['database/migrations']`. */
|
|
166
|
+
paths?: string[];
|
|
118
167
|
/**
|
|
119
168
|
* Custom name for the migrations tracking table. Defaults to `"ream_migrations"`.
|
|
120
169
|
* Must match `/^[A-Za-z_][A-Za-z0-9_]*$/` — the `MigrationRunner` constructor
|
|
121
170
|
* throws `AtlasError("MIGRATION_INVALID_TABLE_NAME")` otherwise.
|
|
122
171
|
*/
|
|
123
172
|
table?: string;
|
|
173
|
+
/**
|
|
174
|
+
* Allow the boot-time auto-migrate in production. OFF by default:
|
|
175
|
+
* starting the app should NOT silently mutate the schema in prod (Adonis
|
|
176
|
+
* Lucid runs migrations only via the explicit `migration:run` command).
|
|
177
|
+
* In non-production, boot auto-migrate stays on for dev convenience. The
|
|
178
|
+
* CLI's `REAM_SKIP_BOOT_MIGRATE=1` always wins.
|
|
179
|
+
*/
|
|
180
|
+
autoRunInProduction?: boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Sort migration files with a numeric-aware comparator (`2_x` before
|
|
183
|
+
* `10_x`). Adonis Lucid `migrations.naturalSort`. Defaults to `false`
|
|
184
|
+
* (plain lexicographic, correct for `make:migration`'s fixed-width prefix).
|
|
185
|
+
*/
|
|
186
|
+
naturalSort?: boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Run every migration OUTSIDE a transaction. Adonis Lucid
|
|
189
|
+
* `migrations.disableTransactions`. A single migration can also opt out with
|
|
190
|
+
* `static disableTransactions = true`; the effective value is their OR.
|
|
191
|
+
* Defaults to `false`.
|
|
192
|
+
*/
|
|
193
|
+
disableTransactions?: boolean;
|
|
124
194
|
};
|
|
125
195
|
/**
|
|
126
196
|
* Boot-time schema verification. When set, atlas reconciles each listed
|
|
@@ -140,11 +210,86 @@ export default class AtlasProvider {
|
|
|
140
210
|
/** Map of connection name → open connection. Populated at boot. */
|
|
141
211
|
#connections = new Map<string, AsyncDatabaseConnection>();
|
|
142
212
|
#defaultName = "primary";
|
|
213
|
+
/** Unsubscribe for the `db:query` → app-emitter bridge, torn down on shutdown. */
|
|
214
|
+
#dbQueryBridge?: () => void;
|
|
143
215
|
|
|
144
216
|
constructor(protected app: AtlasAppContext) {}
|
|
145
217
|
|
|
218
|
+
/** Resolve a live connection by name, or throw — never hand out a closed one
|
|
219
|
+
* (used by the `db`/`db:<name>` container factories so a failed-boot or
|
|
220
|
+
* post-shutdown lookup fails loudly instead of returning a dead handle). */
|
|
221
|
+
#requireConnection(name: string): AsyncDatabaseConnection {
|
|
222
|
+
const conn = this.#connections.get(name);
|
|
223
|
+
if (!conn) {
|
|
224
|
+
throw new Error(
|
|
225
|
+
`AtlasProvider: connection '${name}' is not available (boot failed or the provider was shut down).`,
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
return conn;
|
|
229
|
+
}
|
|
230
|
+
|
|
146
231
|
register() {}
|
|
147
232
|
|
|
233
|
+
/**
|
|
234
|
+
* Bridge atlas's `db:query` observability onto the app emitter so consumers
|
|
235
|
+
* use the AdonisJS API — `emitter.on('db:query', (query) => …)`. The agnostic
|
|
236
|
+
* `onDbQuery(listener)` primitive stays available for hosts without an emitter.
|
|
237
|
+
* No-op when the container exposes no `events` emitter.
|
|
238
|
+
*/
|
|
239
|
+
async #bridgeDbQueryEvents(): Promise<void> {
|
|
240
|
+
const resolve = this.app.container.resolve;
|
|
241
|
+
if (typeof resolve !== "function") return;
|
|
242
|
+
let emitter: unknown;
|
|
243
|
+
try {
|
|
244
|
+
emitter = await resolve.call(this.app.container, "events");
|
|
245
|
+
} catch {
|
|
246
|
+
return; // no emitter bound — `onDbQuery` remains the way to observe
|
|
247
|
+
}
|
|
248
|
+
if (!hasEmit(emitter)) return;
|
|
249
|
+
const { onDbQuery } = await import("./events.js");
|
|
250
|
+
this.#dbQueryBridge?.();
|
|
251
|
+
this.#dbQueryBridge = onDbQuery((event) => {
|
|
252
|
+
emitter.emit("db:query", event);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Teardown for the connection-lifecycle → app-emitter bridge. */
|
|
257
|
+
#connectionBridge?: () => void;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Bridge the connection manager's lifecycle events onto the app emitter as
|
|
261
|
+
* AdonisJS's `db:connection:connect` / `db:connection:disconnect` /
|
|
262
|
+
* `db:connection:error` (`[error, node]`). No-op without an `events` emitter.
|
|
263
|
+
*/
|
|
264
|
+
async #bridgeConnectionEvents(): Promise<void> {
|
|
265
|
+
const resolve = this.app.container.resolve;
|
|
266
|
+
if (typeof resolve !== "function") return;
|
|
267
|
+
let emitter: unknown;
|
|
268
|
+
try {
|
|
269
|
+
emitter = await resolve.call(this.app.container, "events");
|
|
270
|
+
} catch {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (!hasEmit(emitter)) return;
|
|
274
|
+
const { connectionManager } = await import("./services/db.js");
|
|
275
|
+
const mgr = connectionManager();
|
|
276
|
+
this.#connectionBridge?.();
|
|
277
|
+
const onConnect = (node: unknown) =>
|
|
278
|
+
emitter.emit("db:connection:connect", node);
|
|
279
|
+
const onDisconnect = (node: unknown) =>
|
|
280
|
+
emitter.emit("db:connection:disconnect", node);
|
|
281
|
+
const onError = (node: unknown, err?: unknown) =>
|
|
282
|
+
emitter.emit("db:connection:error", [err, node]);
|
|
283
|
+
mgr.on("connect", onConnect);
|
|
284
|
+
mgr.on("disconnect", onDisconnect);
|
|
285
|
+
mgr.on("error", onError);
|
|
286
|
+
this.#connectionBridge = () => {
|
|
287
|
+
mgr.off("connect", onConnect);
|
|
288
|
+
mgr.off("disconnect", onDisconnect);
|
|
289
|
+
mgr.off("error", onError);
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
148
293
|
async boot() {
|
|
149
294
|
const config = this.app.config.get<AtlasDatabaseConfig>("database");
|
|
150
295
|
if (!config) return;
|
|
@@ -153,6 +298,18 @@ export default class AtlasProvider {
|
|
|
153
298
|
const { connections, defaultName } = this.#resolveConnections(config);
|
|
154
299
|
this.#defaultName = defaultName;
|
|
155
300
|
|
|
301
|
+
// Bridge query observability onto the app emitter — AdonisJS parity, so
|
|
302
|
+
// consumers write `emitter.on('db:query', …)` (see #bridgeDbQueryEvents).
|
|
303
|
+
await this.#bridgeDbQueryEvents();
|
|
304
|
+
// Bridge connection lifecycle events too, BEFORE opening pools so the boot
|
|
305
|
+
// `connect` events reach the app emitter (`db:connection:connect`).
|
|
306
|
+
await this.#bridgeConnectionEvents();
|
|
307
|
+
|
|
308
|
+
// Import the services/db proxy BEFORE opening any pool: if this import ever
|
|
309
|
+
// failed (packaging/bundle issue), we must not have leaked open connections,
|
|
310
|
+
// and the cleanup catch below needs it available.
|
|
311
|
+
const dbServices = await import("./services/db.js");
|
|
312
|
+
|
|
156
313
|
// Open every connection in parallel — multi-database apps with slow-to-
|
|
157
314
|
// handshake drivers (Postgres over TLS, RDS proxies) previously paid the
|
|
158
315
|
// sum of the round-trip times on boot; now it's the max.
|
|
@@ -162,18 +319,24 @@ export default class AtlasProvider {
|
|
|
162
319
|
// close every successful one before rethrowing so a partial boot never
|
|
163
320
|
// leaks pools/sockets.
|
|
164
321
|
const entries = Object.entries(connections);
|
|
322
|
+
// Register every connection's config up front (Lucid `manager.add`), so the
|
|
323
|
+
// manager knows all connections — including any that fail to open below.
|
|
324
|
+
for (const [name, settings] of entries) {
|
|
325
|
+
dbServices.connectionManager().add(name, settings);
|
|
326
|
+
}
|
|
165
327
|
const results = await Promise.allSettled(
|
|
166
|
-
entries.map(([, settings]) =>
|
|
328
|
+
entries.map(([name, settings]) =>
|
|
167
329
|
createNapiConnection(
|
|
168
330
|
settings.url,
|
|
169
|
-
settings.poolMin ?? 1,
|
|
170
|
-
settings.poolMax ?? 10,
|
|
331
|
+
settings.pool?.min ?? settings.poolMin ?? 1,
|
|
332
|
+
settings.pool?.max ?? settings.poolMax ?? 10,
|
|
171
333
|
settings.pragmas,
|
|
172
334
|
{
|
|
173
335
|
retries: settings.connectRetries,
|
|
174
336
|
backoffMs: settings.connectBackoffMs,
|
|
175
337
|
timeoutMs: settings.connectTimeoutMs,
|
|
176
338
|
},
|
|
339
|
+
{ debug: settings.debug ?? false, connectionName: name },
|
|
177
340
|
),
|
|
178
341
|
),
|
|
179
342
|
);
|
|
@@ -186,6 +349,11 @@ export default class AtlasProvider {
|
|
|
186
349
|
else failures.push({ name, error: r.reason });
|
|
187
350
|
});
|
|
188
351
|
if (failures.length > 0) {
|
|
352
|
+
// Surface each failure through the manager → app emitter as
|
|
353
|
+
// `db:connection:error` ([error, node]) before rolling back.
|
|
354
|
+
for (const f of failures) {
|
|
355
|
+
dbServices.connectionManager().reportConnectError(f.name, f.error);
|
|
356
|
+
}
|
|
189
357
|
// Tear down the successes so we don't leak any pool that the runtime
|
|
190
358
|
// has already opened. Closures run in parallel with allSettled so a
|
|
191
359
|
// stuck close doesn't block the rollback path.
|
|
@@ -201,46 +369,85 @@ export default class AtlasProvider {
|
|
|
201
369
|
(others ? ` (also: ${others})` : ""),
|
|
202
370
|
);
|
|
203
371
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
372
|
+
// Everything past this point runs AFTER the pools are open — an invalid
|
|
373
|
+
// default connection, a boot-time migration crash, etc. must not leak the
|
|
374
|
+
// sockets/pools we just opened. On any failure, tear them all down and
|
|
375
|
+
// undo the registry/service bindings before rethrowing.
|
|
376
|
+
try {
|
|
377
|
+
// Validate the default connection opened BEFORE binding anything: a bad
|
|
378
|
+
// default must fail while the container/registry are still untouched, so
|
|
379
|
+
// we never leave handles pointing at connections we're about to close.
|
|
380
|
+
const defaultEntry = successes.find((s) => s.name === defaultName);
|
|
381
|
+
if (!defaultEntry) {
|
|
382
|
+
throw new Error(
|
|
383
|
+
`AtlasProvider: default connection '${defaultName}' is not defined in config.database.connections`,
|
|
384
|
+
);
|
|
385
|
+
}
|
|
386
|
+
const defaultConn = defaultEntry.conn;
|
|
208
387
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
388
|
+
// Bind connections into the container AND the named-connection registry
|
|
389
|
+
// (the latter is what `BaseModel.connection` / `Factory.connection()` /
|
|
390
|
+
// `getConnection(name)` read). The container factories resolve through
|
|
391
|
+
// `#connections` (not a captured handle) so that if boot fails after
|
|
392
|
+
// binding — the catch clears `#connections` — or after shutdown, resolving
|
|
393
|
+
// `db`/`db:<name>` throws instead of handing out a CLOSED connection.
|
|
394
|
+
for (const { name, conn } of successes) {
|
|
395
|
+
this.#connections.set(name, conn);
|
|
396
|
+
dbServices.registerConnection(name, conn, connections[name]);
|
|
397
|
+
this.app.container.singleton(`db:${name}`, () =>
|
|
398
|
+
this.#requireConnection(name),
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Expose the default under the short aliases `db` and `db.connection`.
|
|
403
|
+
this.app.container.singleton("db", () =>
|
|
404
|
+
this.#requireConnection(this.#defaultName),
|
|
405
|
+
);
|
|
406
|
+
this.app.container.singleton("db.connection", () =>
|
|
407
|
+
this.#requireConnection(this.#defaultName),
|
|
214
408
|
);
|
|
215
|
-
}
|
|
216
|
-
this.app.container.singleton("db", () => defaultConn);
|
|
217
|
-
this.app.container.singleton("db.connection", () => defaultConn);
|
|
218
409
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
// module at construction time when the provider is type-imported
|
|
223
|
-
// by `@c9up/ream`'s discovery scan.
|
|
224
|
-
const { setDb } = await import("./services/db.js");
|
|
225
|
-
setDb(defaultConn);
|
|
410
|
+
// Populate the `@c9up/atlas/services/db` proxy so apps can
|
|
411
|
+
// `import db from '@c9up/atlas/services/db'` from anywhere.
|
|
412
|
+
dbServices.setDb(defaultConn);
|
|
226
413
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
414
|
+
// The dialect set module-wide is the DEFAULT connection's dialect.
|
|
415
|
+
// Per-connection dialect (when a user hits a non-default) is read from
|
|
416
|
+
// the connection URL at query time by each call site that cares.
|
|
417
|
+
setAtlasDialect(dialectFromUrl(connections[defaultName]?.url));
|
|
231
418
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
config.migrations
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
419
|
+
// Auto-run migrations on boot — but NOT in production unless explicitly
|
|
420
|
+
// opted in: starting the app should not silently mutate the schema in
|
|
421
|
+
// prod (Adonis Lucid only migrates via `migration:run`). Skipped too
|
|
422
|
+
// when a CLI migration command booted us (`REAM_SKIP_BOOT_MIGRATE=1`),
|
|
423
|
+
// which drives migrations explicitly.
|
|
424
|
+
const inProduction = process.env.NODE_ENV === "production";
|
|
425
|
+
const autoMigrateAllowed =
|
|
426
|
+
!inProduction || config.migrations?.autoRunInProduction === true;
|
|
427
|
+
if (
|
|
428
|
+
(config.migrations?.paths?.[0] ?? config.migrations?.path) &&
|
|
429
|
+
process.env.REAM_SKIP_BOOT_MIGRATE !== "1" &&
|
|
430
|
+
autoMigrateAllowed
|
|
431
|
+
) {
|
|
432
|
+
await this.#runMigrations(
|
|
433
|
+
config.migrations?.paths?.[0] ?? config.migrations?.path ?? "",
|
|
434
|
+
connections[defaultName]?.url,
|
|
435
|
+
defaultConn,
|
|
436
|
+
config.migrations.table,
|
|
437
|
+
{
|
|
438
|
+
naturalSort: config.migrations.naturalSort,
|
|
439
|
+
disableTransactions: config.migrations.disableTransactions,
|
|
440
|
+
},
|
|
441
|
+
);
|
|
442
|
+
}
|
|
443
|
+
} catch (err) {
|
|
444
|
+
await Promise.allSettled(successes.map((s) => s.conn.close()));
|
|
445
|
+
for (const { name, conn } of successes) {
|
|
446
|
+
dbServices.clearDb(conn);
|
|
447
|
+
dbServices.unregisterConnection(name, conn);
|
|
448
|
+
}
|
|
449
|
+
this.#connections.clear();
|
|
450
|
+
throw err;
|
|
244
451
|
}
|
|
245
452
|
}
|
|
246
453
|
|
|
@@ -254,6 +461,12 @@ export default class AtlasProvider {
|
|
|
254
461
|
// - any rejection is aggregated into a single `AggregateError` thrown
|
|
255
462
|
// at the end so supervisors / health-checks see a non-zero exit
|
|
256
463
|
// signal instead of a silent "everything is fine" shutdown
|
|
464
|
+
// Detach the db:query → emitter bridge so a re-boot doesn't double-emit.
|
|
465
|
+
this.#dbQueryBridge?.();
|
|
466
|
+
this.#dbQueryBridge = undefined;
|
|
467
|
+
this.#connectionBridge?.();
|
|
468
|
+
this.#connectionBridge = undefined;
|
|
469
|
+
|
|
257
470
|
const named = [...this.#connections.entries()];
|
|
258
471
|
const results = await Promise.allSettled(named.map(([, c]) => c.close()));
|
|
259
472
|
this.#connections.clear();
|
|
@@ -261,9 +474,15 @@ export default class AtlasProvider {
|
|
|
261
474
|
// Release the module-level singletons this provider populated at boot so a
|
|
262
475
|
// re-boot starts clean and `db.*` can't dereference a now-closed handle.
|
|
263
476
|
// `clearDb` is ownership-guarded, so clearing every connection only unbinds
|
|
264
|
-
// the one still owning the singleton.
|
|
265
|
-
|
|
266
|
-
|
|
477
|
+
// the one still owning the singleton. Also drop the named-connection
|
|
478
|
+
// registry entries (boot registered them), so `getConnection(name)` /
|
|
479
|
+
// `BaseModel.connection` / `Factory.connection()` can't hand out a CLOSED
|
|
480
|
+
// handle after shutdown.
|
|
481
|
+
const { clearDb, unregisterConnection } = await import("./services/db.js");
|
|
482
|
+
for (const [name, conn] of named) {
|
|
483
|
+
clearDb(conn);
|
|
484
|
+
unregisterConnection(name, conn);
|
|
485
|
+
}
|
|
267
486
|
clearCastRegistry();
|
|
268
487
|
const errors = results
|
|
269
488
|
.map((r, i) =>
|
|
@@ -302,23 +521,51 @@ export default class AtlasProvider {
|
|
|
302
521
|
|
|
303
522
|
/** Normalize the config into a `{ name → ConnectionConfig }` map + default name. */
|
|
304
523
|
#resolveConnections(config: AtlasDatabaseConfig): {
|
|
305
|
-
connections: Record<string, ConnectionConfig>;
|
|
524
|
+
connections: Record<string, ConnectionConfig & { url: string }>;
|
|
306
525
|
defaultName: string;
|
|
307
526
|
} {
|
|
308
527
|
if (config.connections && Object.keys(config.connections).length > 0) {
|
|
528
|
+
// Normalize each block's URL from Lucid's `connection` key (alias of `url`).
|
|
529
|
+
const connections: Record<string, ConnectionConfig & { url: string }> =
|
|
530
|
+
{};
|
|
531
|
+
for (const [name, block] of Object.entries(config.connections)) {
|
|
532
|
+
const url = block.url ?? block.connection;
|
|
533
|
+
if (!url) {
|
|
534
|
+
throw new Error(
|
|
535
|
+
`[atlas] connection '${name}' has no URL — set 'url' (or Lucid's 'connection') in config.database.connections.${name}.`,
|
|
536
|
+
);
|
|
537
|
+
}
|
|
538
|
+
connections[name] = { ...block, url };
|
|
539
|
+
}
|
|
309
540
|
return {
|
|
310
|
-
connections
|
|
311
|
-
|
|
541
|
+
connections,
|
|
542
|
+
// Lucid's `connection` (default selector); `default` is the alias.
|
|
543
|
+
defaultName: config.connection ?? config.default ?? "primary",
|
|
312
544
|
};
|
|
313
545
|
}
|
|
314
|
-
// Legacy single-connection shape — promote to multi-connection under
|
|
546
|
+
// Legacy single-connection shape — promote to multi-connection under
|
|
547
|
+
// "primary". Carry EVERY top-level option through: dropping debug /
|
|
548
|
+
// connectRetries / connectBackoffMs / connectTimeoutMs here silently
|
|
549
|
+
// disabled retry/timeout/query-debug for anyone still on the flat config.
|
|
550
|
+
// (Top-level `connection` is the default-selector, so the flat form's URL
|
|
551
|
+
// comes from `url` only — Lucid's `connection`-as-URL is a nested-block key.)
|
|
552
|
+
if (!config.url) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
"[atlas] no database URL — set 'url' at the top level, or use 'connections: { … }' with a per-connection 'url'/'connection'.",
|
|
555
|
+
);
|
|
556
|
+
}
|
|
315
557
|
return {
|
|
316
558
|
connections: {
|
|
317
559
|
primary: {
|
|
318
560
|
url: config.url,
|
|
319
561
|
poolMin: config.poolMin,
|
|
320
562
|
poolMax: config.poolMax,
|
|
563
|
+
pool: config.pool,
|
|
321
564
|
pragmas: config.pragmas,
|
|
565
|
+
debug: config.debug,
|
|
566
|
+
connectRetries: config.connectRetries,
|
|
567
|
+
connectBackoffMs: config.connectBackoffMs,
|
|
568
|
+
connectTimeoutMs: config.connectTimeoutMs,
|
|
322
569
|
},
|
|
323
570
|
},
|
|
324
571
|
defaultName: "primary",
|
|
@@ -330,6 +577,7 @@ export default class AtlasProvider {
|
|
|
330
577
|
url: string,
|
|
331
578
|
db: AsyncDatabaseConnection,
|
|
332
579
|
tableName: string | undefined,
|
|
580
|
+
options?: { naturalSort?: boolean; disableTransactions?: boolean },
|
|
333
581
|
): Promise<void> {
|
|
334
582
|
const { existsSync } = await import("node:fs");
|
|
335
583
|
if (!existsSync(migrationsPath)) return;
|
|
@@ -346,11 +594,16 @@ export default class AtlasProvider {
|
|
|
346
594
|
// `ream_migrations` bookkeeping row together). Without this, the runner
|
|
347
595
|
// silently falls back to non-transactional execution.
|
|
348
596
|
runInTransaction: async (batch) => db.runInTransaction(batch),
|
|
597
|
+
// Managed interactive transaction, so this.defer() migrations run
|
|
598
|
+
// atomically (schema + deferred + tracking committed together).
|
|
599
|
+
transaction: db.transaction?.bind(db),
|
|
349
600
|
};
|
|
350
601
|
const runner = new MigrationRunner(adapter, {
|
|
351
602
|
migrationsDir: migrationsPath,
|
|
352
603
|
dialect: dialectFromUrl(url),
|
|
353
604
|
tableName,
|
|
605
|
+
naturalSort: options?.naturalSort,
|
|
606
|
+
disableTransactions: options?.disableTransactions,
|
|
354
607
|
});
|
|
355
608
|
await runner.migrate();
|
|
356
609
|
}
|