@zerotal/orm 1.8.1 → 1.9.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/CHANGELOG.md +45 -0
- package/api-surface.md +0 -186
- package/package.json +3 -3
- package/src/commands/DbBackupCommand.ts +143 -0
- package/src/commands/_backup.ts +356 -0
- package/src/db/DB.ts +27 -2
- package/src/db/ReadWriteRouter.ts +2 -0
- package/src/db/dialects/MysqlDialect.ts +2 -0
- package/src/db/dialects/PostgresDialect.ts +2 -0
- package/src/db/dialects/SqliteDialect.ts +2 -0
- package/src/db/dialects/index.ts +2 -0
- package/src/db/dialects/types.ts +12 -2
- package/src/db/resolver.ts +4 -0
- package/src/diagnostics/pendingMigrationsCheck.ts +60 -0
- package/src/implicitBinding.ts +1 -0
- package/src/model/BaseModel.ts +6 -0
- package/src/model/ModelQueryBuilder.ts +1 -0
- package/src/model/OrmContext.ts +14 -2
- package/src/model/hooks/HookRegistry.ts +2 -0
- package/src/observability.ts +2 -0
- package/src/provider/DatabaseProvider.ts +17 -1
- package/src/schema/MigrationCodegen.ts +2 -0
- package/src/schema/ModelInspector.ts +4 -0
- package/src/schema/SchemaDiffer.ts +6 -0
- package/src/schema/SchemaInspector.ts +4 -0
- package/src/schema/autoMigrate.ts +14 -2
package/src/observability.ts
CHANGED
|
@@ -85,6 +85,8 @@ function _ctxPath(ctx: object): string {
|
|
|
85
85
|
/**
|
|
86
86
|
* Subscribe the ORM's events to every installed observer. Returns a disposer that
|
|
87
87
|
* removes every subscription; call it from the ORM provider's `onStopping()`.
|
|
88
|
+
*
|
|
89
|
+
* @internal
|
|
88
90
|
*/
|
|
89
91
|
export function installOrmObservability(app: Application): () => void {
|
|
90
92
|
const unsubs: Array<() => void> = [];
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { SQLInstance } from "../db/sql-types.ts";
|
|
2
2
|
import { ServiceProvider, registerErrorDiagnoser, isProdLike, deployEnv } from "@zerotal/core";
|
|
3
|
-
import type { AppEnvironment } from "@zerotal/core";
|
|
3
|
+
import type { AppEnvironment, DoctorCheck } from "@zerotal/core";
|
|
4
4
|
import type { ConfigManager } from "@zerotal/core/config";
|
|
5
5
|
import { SQL } from "bun";
|
|
6
6
|
import { DB, _getConnection } from "../db/DB.ts";
|
|
@@ -18,6 +18,7 @@ import { autoMigrateConcern } from "../schema/autoMigrate.ts";
|
|
|
18
18
|
import { registerImplicitBinding } from "../implicitBinding.ts";
|
|
19
19
|
import { installOrmObservability } from "../observability.ts";
|
|
20
20
|
import { diagnoseMissingRelation } from "../diagnostics/missingRelation.ts";
|
|
21
|
+
import { pendingMigrationsCheck } from "../diagnostics/pendingMigrationsCheck.ts";
|
|
21
22
|
import {
|
|
22
23
|
registerRunMigrationsEndpoint,
|
|
23
24
|
RUN_MIGRATIONS_PATH,
|
|
@@ -207,6 +208,15 @@ export class DatabaseProvider extends ServiceProvider {
|
|
|
207
208
|
}
|
|
208
209
|
}
|
|
209
210
|
|
|
211
|
+
/**
|
|
212
|
+
* The overlay answers "why did this fail" after something already has. This asks
|
|
213
|
+
* the same question before anything breaks, which is the cheaper moment to hear
|
|
214
|
+
* it — `doctor` already boots the app and already holds the connection.
|
|
215
|
+
*/
|
|
216
|
+
override doctorChecks(): DoctorCheck[] {
|
|
217
|
+
return [pendingMigrationsCheck];
|
|
218
|
+
}
|
|
219
|
+
|
|
210
220
|
override async onBooted(): Promise<void> {
|
|
211
221
|
// Forward the ORM's framework events to whatever observers are installed.
|
|
212
222
|
this._disposeObservability = installOrmObservability(this.app);
|
|
@@ -256,6 +266,12 @@ export class DatabaseProvider extends ServiceProvider {
|
|
|
256
266
|
runner.registerLazy("db:seed", () =>
|
|
257
267
|
import("../commands/DbSeedCommand.ts").then((m) => m.DbSeedCommand),
|
|
258
268
|
);
|
|
269
|
+
// The counterpart to every command above it. `migrate`, `migrate:fresh` and
|
|
270
|
+
// `db:seed` all assume the file will still be there; this is the one that
|
|
271
|
+
// makes that assumption survivable.
|
|
272
|
+
runner.registerLazy("db:backup", () =>
|
|
273
|
+
import("../commands/DbBackupCommand.ts").then((m) => m.DbBackupCommand),
|
|
274
|
+
);
|
|
259
275
|
runner.registerLazy("make:seeder", () =>
|
|
260
276
|
import("../commands/MakeSeederCommand.ts").then((m) => m.MakeSeederCommand),
|
|
261
277
|
);
|
|
@@ -111,6 +111,8 @@ function alterTableBlocks(newColumns: NewColumn[]): string {
|
|
|
111
111
|
* - Creates new tables with `Schema.create()`
|
|
112
112
|
* - Adds new columns with `Schema.table()` (ALTER TABLE ADD COLUMN)
|
|
113
113
|
* - Drops created tables in `down()` (column additions are left for manual rollback)
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
114
116
|
*/
|
|
115
117
|
export function generateMigrationContent(className: string, diff: DiffResult): string {
|
|
116
118
|
const upParts: string[] = [];
|
|
@@ -7,6 +7,7 @@ import type { ClassRef } from "../support/classRef.ts";
|
|
|
7
7
|
|
|
8
8
|
// ── Model schema descriptor ───────────────────────────────────────────────────
|
|
9
9
|
|
|
10
|
+
/** @internal */
|
|
10
11
|
export interface ModelColumn {
|
|
11
12
|
name: string;
|
|
12
13
|
type: ColumnOptions["type"]; // 'string' | 'text' | 'number' | 'boolean' | 'datetime' | 'json'
|
|
@@ -19,6 +20,7 @@ export interface ModelColumn {
|
|
|
19
20
|
index?: boolean;
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
/** @internal */
|
|
22
24
|
export interface ModelSchema {
|
|
23
25
|
table: string;
|
|
24
26
|
primaryKey: string;
|
|
@@ -109,6 +111,8 @@ function encryptedColumns(
|
|
|
109
111
|
* by child classes, matching normal TypeScript class semantics.
|
|
110
112
|
*
|
|
111
113
|
* Used by `migrate:generate` to compare model intent against the live DB.
|
|
114
|
+
*
|
|
115
|
+
* @internal
|
|
112
116
|
*/
|
|
113
117
|
export const ModelInspector = {
|
|
114
118
|
/**
|
|
@@ -3,20 +3,24 @@ import { columnDbName, type ModelSchema, type ModelColumn } from "./ModelInspect
|
|
|
3
3
|
|
|
4
4
|
// -- Diff result types ---------------------------------------------------------
|
|
5
5
|
|
|
6
|
+
/** @internal */
|
|
6
7
|
export interface NewTable {
|
|
7
8
|
schema: ModelSchema;
|
|
8
9
|
}
|
|
9
10
|
|
|
11
|
+
/** @internal */
|
|
10
12
|
export interface NewColumn {
|
|
11
13
|
table: string;
|
|
12
14
|
column: ModelColumn;
|
|
13
15
|
}
|
|
14
16
|
|
|
17
|
+
/** @internal */
|
|
15
18
|
export interface DroppedColumn {
|
|
16
19
|
table: string;
|
|
17
20
|
column: string;
|
|
18
21
|
}
|
|
19
22
|
|
|
23
|
+
/** @internal */
|
|
20
24
|
export interface DiffResult {
|
|
21
25
|
newTables: NewTable[];
|
|
22
26
|
newColumns: NewColumn[];
|
|
@@ -36,6 +40,8 @@ export interface DiffResult {
|
|
|
36
40
|
*
|
|
37
41
|
* Additive deltas (newTables, newColumns) are always safe to apply. Dropped
|
|
38
42
|
* columns are reported separately and only applied by a disruptive synchronize.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
39
45
|
*/
|
|
40
46
|
export const SchemaDiffer = {
|
|
41
47
|
async diff(schemas: ModelSchema[]): Promise<DiffResult> {
|
|
@@ -3,6 +3,7 @@ import { _getDialect } from "../model/BaseModel.ts";
|
|
|
3
3
|
|
|
4
4
|
// ── DB column descriptor (normalised across dialects) ─────────────────────────
|
|
5
5
|
|
|
6
|
+
/** @internal */
|
|
6
7
|
export interface LiveColumn {
|
|
7
8
|
name: string;
|
|
8
9
|
rawType: string; // original SQL type string from the DB (uppercase)
|
|
@@ -10,6 +11,7 @@ export interface LiveColumn {
|
|
|
10
11
|
primary: boolean;
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
/** @internal */
|
|
13
15
|
export interface LiveTable {
|
|
14
16
|
name: string;
|
|
15
17
|
columns: LiveColumn[];
|
|
@@ -38,6 +40,8 @@ async function queryParam<T>(sql: string, value: unknown): Promise<T[]> {
|
|
|
38
40
|
/**
|
|
39
41
|
* Queries the live database to enumerate tables and their column definitions.
|
|
40
42
|
* Used by `migrate:generate` to compute what has changed since the last migration.
|
|
43
|
+
*
|
|
44
|
+
* @internal
|
|
41
45
|
*/
|
|
42
46
|
export const SchemaInspector = {
|
|
43
47
|
/** Return all user-defined table names in the current DB. */
|
|
@@ -41,7 +41,11 @@ function applyColumn(table: TableBuilder, col: ModelColumn): void {
|
|
|
41
41
|
else if (col.index) table.index(dbName);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Options for {@link synchronizeSchema}.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
45
49
|
export interface SynchronizeOptions {
|
|
46
50
|
/**
|
|
47
51
|
* When true, also DROP columns that exist in the database but are no longer
|
|
@@ -61,6 +65,8 @@ export interface SynchronizeOptions {
|
|
|
61
65
|
*
|
|
62
66
|
* Returns the diff that was applied (additive deltas are empty when already in sync;
|
|
63
67
|
* `droppedColumns` is populated but only acted on when `disruptive` is set).
|
|
68
|
+
*
|
|
69
|
+
* @internal
|
|
64
70
|
*/
|
|
65
71
|
export async function synchronizeSchema(options: SynchronizeOptions = {}): Promise<DiffResult> {
|
|
66
72
|
const diff = await SchemaDiffer.diff(ModelInspector.all());
|
|
@@ -112,7 +118,11 @@ export async function synchronizeSchema(options: SynchronizeOptions = {}): Promi
|
|
|
112
118
|
return diff;
|
|
113
119
|
}
|
|
114
120
|
|
|
115
|
-
/**
|
|
121
|
+
/**
|
|
122
|
+
* Resolved, normalised form of the `database.synchronize` config value.
|
|
123
|
+
*
|
|
124
|
+
* @internal
|
|
125
|
+
*/
|
|
116
126
|
export interface ResolvedSyncOptions {
|
|
117
127
|
enabled: boolean;
|
|
118
128
|
disruptive: boolean;
|
|
@@ -124,6 +134,8 @@ export interface ResolvedSyncOptions {
|
|
|
124
134
|
* false / undefined -> { enabled: false, disruptive: false }
|
|
125
135
|
* true -> { enabled: true, disruptive: false } (additive)
|
|
126
136
|
* { enabled, disruptive? } -> as given (enabled defaults true when the object is present)
|
|
137
|
+
*
|
|
138
|
+
* @internal
|
|
127
139
|
*/
|
|
128
140
|
export function resolveSyncOptions(raw: unknown): ResolvedSyncOptions {
|
|
129
141
|
if (raw === true) return { enabled: true, disruptive: false };
|