@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
|
@@ -15,21 +15,94 @@ import {
|
|
|
15
15
|
import { RawSql } from "../query/QueryBuilder.js";
|
|
16
16
|
import { type DefaultValue, renderDefaultValue } from "./raw.js";
|
|
17
17
|
import {
|
|
18
|
+
type AlterOperation,
|
|
19
|
+
type CheckExpression,
|
|
20
|
+
type CheckOperator,
|
|
21
|
+
type CheckValue,
|
|
18
22
|
type ColumnDefinition,
|
|
19
23
|
type ColumnType,
|
|
24
|
+
type ForeignKeyReference,
|
|
20
25
|
type IndexDefinition,
|
|
26
|
+
type ReferentialAction,
|
|
27
|
+
type TableConstraintSpec,
|
|
28
|
+
type TableOptionsSpec,
|
|
29
|
+
type TextVariant,
|
|
21
30
|
TYPE_KIND_MAP,
|
|
22
31
|
} from "./types.js";
|
|
23
32
|
|
|
33
|
+
/**
|
|
34
|
+
* The chainable returned by `table.foreign([...])`, so the target reads in
|
|
35
|
+
* Knex order: `.references([...]).inTable('other').onDelete('cascade')`. It
|
|
36
|
+
* mutates the already-recorded constraint in place.
|
|
37
|
+
*/
|
|
38
|
+
export class ForeignKeyBuilder {
|
|
39
|
+
readonly #references: ForeignKeyReference;
|
|
40
|
+
|
|
41
|
+
constructor(references: ForeignKeyReference) {
|
|
42
|
+
this.#references = references;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Target column(s) on the referenced table. */
|
|
46
|
+
references(columns: string | readonly string[]): this {
|
|
47
|
+
this.#references.columns =
|
|
48
|
+
typeof columns === "string" ? [columns] : [...columns];
|
|
49
|
+
return this;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Referenced table. Keeps returning this builder so `.onDelete()` /
|
|
54
|
+
* `.onUpdate()` can follow, as in Knex — the constraint was already
|
|
55
|
+
* recorded on the table when `foreign()` was called, so there is nothing to
|
|
56
|
+
* hand back to.
|
|
57
|
+
*/
|
|
58
|
+
inTable(table: string): this {
|
|
59
|
+
this.#references.table = table;
|
|
60
|
+
return this;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
onDelete(action: ReferentialAction): this {
|
|
64
|
+
this.#references.onDelete = action;
|
|
65
|
+
return this;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
onUpdate(action: ReferentialAction): this {
|
|
69
|
+
this.#references.onUpdate = action;
|
|
70
|
+
return this;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Whether the builder is filling a `CREATE TABLE` or an `ALTER TABLE`. In
|
|
76
|
+
* `alter` mode a column-type method (`t.string('x')`) becomes `ADD COLUMN`,
|
|
77
|
+
* and `.alter()` turns the pending add into a type change (Lucid/Knex).
|
|
78
|
+
*/
|
|
79
|
+
export type TableBuilderMode = "create" | "alter";
|
|
80
|
+
|
|
24
81
|
/** Table builder — used inside `schema.createTable(name, callback)`. */
|
|
25
82
|
export class TableBuilder {
|
|
26
83
|
readonly tableName: string;
|
|
84
|
+
readonly mode: TableBuilderMode;
|
|
27
85
|
#columns: ColumnDefinition[] = [];
|
|
28
86
|
#indexes: IndexDefinition[] = [];
|
|
29
87
|
#currentColumn?: ColumnDefinition;
|
|
88
|
+
/** Ordered ALTER TABLE ops. Empty (and unused) in `create` mode. */
|
|
89
|
+
#operations: AlterOperation[] = [];
|
|
90
|
+
/** Table-level constraints. In `alter` mode these become `addConstraint` ops instead. */
|
|
91
|
+
#constraints: TableConstraintSpec[] = [];
|
|
92
|
+
#options: TableOptionsSpec = {};
|
|
93
|
+
/** The op the pending column modifiers apply to, in `alter` mode. */
|
|
94
|
+
#currentOp?: Extract<AlterOperation, { op: "addColumn" | "alterColumn" }>;
|
|
95
|
+
/**
|
|
96
|
+
* Whether `.nullable()` / `.notNullable()` was called on the current column.
|
|
97
|
+
* `ColumnDefinition.nullable` defaults to `true`, so without this flag
|
|
98
|
+
* `.alter()` could not tell "leave nullability alone" from "make it
|
|
99
|
+
* nullable" — see `AlterOperation.setNullable`.
|
|
100
|
+
*/
|
|
101
|
+
#nullabilityTouched = false;
|
|
30
102
|
|
|
31
|
-
constructor(tableName: string) {
|
|
103
|
+
constructor(tableName: string, mode: TableBuilderMode = "create") {
|
|
32
104
|
this.tableName = tableName;
|
|
105
|
+
this.mode = mode;
|
|
33
106
|
}
|
|
34
107
|
|
|
35
108
|
// ─── Column types ─────────────────────────────────────────
|
|
@@ -59,12 +132,29 @@ export class TableBuilder {
|
|
|
59
132
|
return this;
|
|
60
133
|
}
|
|
61
134
|
|
|
62
|
-
|
|
63
|
-
|
|
135
|
+
/**
|
|
136
|
+
* Text column (Lucid/Knex `text(name, textType)`). `textType` widens the
|
|
137
|
+
* MySQL type (`MEDIUMTEXT` / `LONGTEXT`); Postgres and SQLite have a single
|
|
138
|
+
* unbounded `TEXT` and ignore it.
|
|
139
|
+
*/
|
|
140
|
+
text(name: string, textType: TextVariant = "text"): this {
|
|
141
|
+
return this.#addColumn(name, textType);
|
|
64
142
|
}
|
|
65
143
|
integer(name: string): this {
|
|
66
144
|
return this.#addColumn(name, "integer");
|
|
67
145
|
}
|
|
146
|
+
/** 24-bit integer (Lucid/Knex `mediumint`). MySQL `MEDIUMINT`; pg/SQLite widen to `INTEGER`. */
|
|
147
|
+
mediumint(name: string): this {
|
|
148
|
+
return this.#addColumn(name, "mediumint");
|
|
149
|
+
}
|
|
150
|
+
/** 8-bit integer (Lucid `tinyint`). MySQL `TINYINT`; Postgres widens to `SMALLINT`; SQLite `INTEGER`. */
|
|
151
|
+
tinyint(name: string): this {
|
|
152
|
+
return this.#addColumn(name, "tinyint");
|
|
153
|
+
}
|
|
154
|
+
/** 16-bit integer (Lucid `smallint`). `SMALLINT` on pg/mysql, `INTEGER` on SQLite. */
|
|
155
|
+
smallint(name: string): this {
|
|
156
|
+
return this.#addColumn(name, "smallint");
|
|
157
|
+
}
|
|
68
158
|
bigInteger(name: string): this {
|
|
69
159
|
return this.#addColumn(name, "bigInteger");
|
|
70
160
|
}
|
|
@@ -78,14 +168,57 @@ export class TableBuilder {
|
|
|
78
168
|
return this;
|
|
79
169
|
}
|
|
80
170
|
|
|
171
|
+
/**
|
|
172
|
+
* Single-precision float (Lucid `float`). `REAL` on pg/sqlite, `FLOAT` on
|
|
173
|
+
* MySQL. `precision`/`scale` render `FLOAT(p, s)` on MySQL only — pg and
|
|
174
|
+
* SQLite have fixed-width floats and ignore them.
|
|
175
|
+
*/
|
|
176
|
+
float(name: string, precision?: number, scale?: number): this {
|
|
177
|
+
return this.#addFloat(name, "float", precision, scale);
|
|
178
|
+
}
|
|
179
|
+
/** Double-precision float (Lucid `double`). `DOUBLE PRECISION` on pg, `REAL` on SQLite, `DOUBLE` on MySQL. See {@link float} for precision/scale. */
|
|
180
|
+
double(name: string, precision?: number, scale?: number): this {
|
|
181
|
+
return this.#addFloat(name, "double", precision, scale);
|
|
182
|
+
}
|
|
183
|
+
|
|
81
184
|
boolean(name: string): this {
|
|
82
185
|
return this.#addColumn(name, "boolean");
|
|
83
186
|
}
|
|
84
187
|
date(name: string): this {
|
|
85
188
|
return this.#addColumn(name, "date");
|
|
86
189
|
}
|
|
87
|
-
|
|
88
|
-
|
|
190
|
+
/**
|
|
191
|
+
* Time of day (Lucid `time`). `TIME` on pg/mysql, `TEXT` on SQLite.
|
|
192
|
+
* `precision` renders `TIME(p)` fractional seconds (ignored on SQLite,
|
|
193
|
+
* which has no time type to carry it).
|
|
194
|
+
*/
|
|
195
|
+
time(name: string, precision?: number): this {
|
|
196
|
+
this.#addColumn(name, "time");
|
|
197
|
+
if (this.#currentColumn) this.#currentColumn.precision = precision;
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Timestamp column (Lucid `timestamp(name, options)`).
|
|
202
|
+
*
|
|
203
|
+
* `useTz: true` selects the tz-aware type — the same thing
|
|
204
|
+
* {@link timestamptz} does, exposed here for Lucid's option spelling.
|
|
205
|
+
* `precision` renders `TIMESTAMP(p)` (ignored on SQLite, which stores
|
|
206
|
+
* timestamps as TEXT).
|
|
207
|
+
*/
|
|
208
|
+
timestamp(
|
|
209
|
+
name: string,
|
|
210
|
+
options: { useTz?: boolean; precision?: number } = {},
|
|
211
|
+
): this {
|
|
212
|
+
this.#addColumn(name, options.useTz ? "timestamptz" : "timestamp");
|
|
213
|
+
if (this.#currentColumn) this.#currentColumn.precision = options.precision;
|
|
214
|
+
return this;
|
|
215
|
+
}
|
|
216
|
+
/** Alias of {@link timestamp} (Lucid `dateTime`). Use `{ useTz: true }` or {@link timestamptz} for a tz-aware column. */
|
|
217
|
+
dateTime(
|
|
218
|
+
name: string,
|
|
219
|
+
options: { useTz?: boolean; precision?: number } = {},
|
|
220
|
+
): this {
|
|
221
|
+
return this.timestamp(name, options);
|
|
89
222
|
}
|
|
90
223
|
/**
|
|
91
224
|
* `timestamp WITH time zone` — Postgres normalises every writer (atlas,
|
|
@@ -102,8 +235,54 @@ export class TableBuilder {
|
|
|
102
235
|
json(name: string): this {
|
|
103
236
|
return this.#addColumn(name, "json");
|
|
104
237
|
}
|
|
105
|
-
|
|
106
|
-
|
|
238
|
+
/**
|
|
239
|
+
* Binary JSON (Lucid/Knex `jsonb`). `JSONB` on pg, `JSON` on MySQL, `TEXT`
|
|
240
|
+
* on SQLite.
|
|
241
|
+
*
|
|
242
|
+
* Deviation, named: atlas's {@link json} already maps to `JSONB` on
|
|
243
|
+
* Postgres (it predates this method), where Lucid's `json()` maps to
|
|
244
|
+
* `json`. Leaving `json()` alone avoids silently rewriting the physical
|
|
245
|
+
* type of existing columns and desyncing `SchemaCheck`, so on Postgres the
|
|
246
|
+
* two spellings coincide.
|
|
247
|
+
*/
|
|
248
|
+
jsonb(name: string): this {
|
|
249
|
+
return this.#addColumn(name, "jsonb");
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Binary blob (Lucid/Knex `binary(name, length)`). `BYTEA` on pg, `BLOB` on
|
|
253
|
+
* SQLite; on MySQL `length` selects `VARBINARY(n)` over `BLOB`.
|
|
254
|
+
*/
|
|
255
|
+
binary(name: string, length?: number): this {
|
|
256
|
+
this.#addColumn(name, "binary");
|
|
257
|
+
if (this.#currentColumn) this.#currentColumn.length = length;
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* A column typed with a verbatim dialect type (Lucid/Knex `specificType`) —
|
|
263
|
+
* the escape hatch for types atlas has no method for (`inet`, `tsvector`,
|
|
264
|
+
* `geometry(Point, 4326)`…).
|
|
265
|
+
*
|
|
266
|
+
* Deviation, named: Knex passes the string straight through. Atlas cannot —
|
|
267
|
+
* it lands verbatim in DDL, so the Rust compiler validates it against a
|
|
268
|
+
* narrow grammar (letters, digits, spaces, `_`, and one parenthesised
|
|
269
|
+
* argument list) and rejects anything else with `E_UNSAFE_SQL`.
|
|
270
|
+
*/
|
|
271
|
+
specificType(name: string, type: string): this {
|
|
272
|
+
this.#addColumn(name, "specificType");
|
|
273
|
+
if (this.#currentColumn) this.#currentColumn.rawType = type;
|
|
274
|
+
return this;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Fixed value-set column (Lucid `enum`). MySQL renders a native `ENUM(...)`;
|
|
279
|
+
* Postgres and SQLite render `TEXT` plus a `CHECK (col IN (...))` that pins the
|
|
280
|
+
* value set. At least one value is required.
|
|
281
|
+
*/
|
|
282
|
+
enum(name: string, values: string[]): this {
|
|
283
|
+
this.#addColumn(name, "enum");
|
|
284
|
+
if (this.#currentColumn) this.#currentColumn.values = values;
|
|
285
|
+
return this;
|
|
107
286
|
}
|
|
108
287
|
|
|
109
288
|
// ─── Shortcuts ────────────────────────────────────────────
|
|
@@ -125,9 +304,8 @@ export class TableBuilder {
|
|
|
125
304
|
* t.uuid('id').primary() // no DEFAULT
|
|
126
305
|
* // and at insert: db.insert({ id: crypto.randomUUID(), ... })
|
|
127
306
|
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
* the helper dialect-aware.
|
|
307
|
+
* (A dialect-aware escape hatch can be added later if a future story needs
|
|
308
|
+
* per-dialect PK defaults.)
|
|
131
309
|
*/
|
|
132
310
|
id(): this {
|
|
133
311
|
return this.uuid("id").primary().defaultTo(new RawSql("gen_random_uuid()"));
|
|
@@ -152,35 +330,45 @@ export class TableBuilder {
|
|
|
152
330
|
* t.timestamp('updated_at').notNullable()
|
|
153
331
|
* // and at insert: db.insert({ created_at: new Date().toISOString(), ... })
|
|
154
332
|
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
157
|
-
* the helper dialect-aware.
|
|
333
|
+
* (A dialect-aware escape hatch can be added later if a future story needs
|
|
334
|
+
* per-dialect PK defaults.)
|
|
158
335
|
*/
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
336
|
+
/**
|
|
337
|
+
* Add `created_at` / `updated_at` columns (Adonis Lucid / Knex
|
|
338
|
+
* `timestamps(useTimestamps, defaultToNow)`).
|
|
339
|
+
*
|
|
340
|
+
* - `useTimestamps` (default `true`): `timestamp` type; `false` → `dateTime`.
|
|
341
|
+
* - `defaultToNow` (default `true`): `NOT NULL DEFAULT CURRENT_TIMESTAMP`;
|
|
342
|
+
* `false` → nullable, no default.
|
|
343
|
+
*
|
|
344
|
+
* The default uses `CURRENT_TIMESTAMP`, which every dialect understands —
|
|
345
|
+
* unlike `NOW()`, which SQLite rejects. So `timestamps()` and the Lucid
|
|
346
|
+
* `timestamps(true, true)` form both port to SQLite/Postgres/MySQL unchanged.
|
|
347
|
+
*/
|
|
348
|
+
timestamps(useTimestamps = true, defaultToNow = true): this {
|
|
349
|
+
const add = (name: string): void => {
|
|
350
|
+
if (useTimestamps) this.timestamp(name);
|
|
351
|
+
else this.dateTime(name);
|
|
352
|
+
if (defaultToNow) {
|
|
353
|
+
this.notNullable().defaultTo(new RawSql("CURRENT_TIMESTAMP"));
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
add("created_at");
|
|
357
|
+
add("updated_at");
|
|
162
358
|
return this;
|
|
163
359
|
}
|
|
164
360
|
|
|
165
361
|
// ─── Column modifiers ─────────────────────────────────────
|
|
166
362
|
|
|
167
|
-
primary(): this {
|
|
168
|
-
if (this.#currentColumn) this.#currentColumn.primary = true;
|
|
169
|
-
return this;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
363
|
notNullable(): this {
|
|
173
364
|
if (this.#currentColumn) this.#currentColumn.nullable = false;
|
|
365
|
+
this.#nullabilityTouched = true;
|
|
174
366
|
return this;
|
|
175
367
|
}
|
|
176
368
|
|
|
177
369
|
nullable(): this {
|
|
178
370
|
if (this.#currentColumn) this.#currentColumn.nullable = true;
|
|
179
|
-
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
unique(): this {
|
|
183
|
-
if (this.#currentColumn) this.#currentColumn.unique = true;
|
|
371
|
+
this.#nullabilityTouched = true;
|
|
184
372
|
return this;
|
|
185
373
|
}
|
|
186
374
|
|
|
@@ -196,8 +384,329 @@ export class TableBuilder {
|
|
|
196
384
|
return this;
|
|
197
385
|
}
|
|
198
386
|
|
|
199
|
-
|
|
200
|
-
|
|
387
|
+
/** MySQL `UNSIGNED` numeric modifier (Lucid `unsigned()`). No-op on pg/sqlite. */
|
|
388
|
+
unsigned(): this {
|
|
389
|
+
if (this.#currentColumn) this.#currentColumn.unsigned = true;
|
|
390
|
+
return this;
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Declare the current column a foreign key.
|
|
395
|
+
*
|
|
396
|
+
* - `references('users', 'id')` — atlas form `(table, column='id')`.
|
|
397
|
+
* - `references('users.id')` — Lucid/Knex dotted `'table.column'` shorthand, so
|
|
398
|
+
* a migration copied from Lucid resolves the target the same way. A single
|
|
399
|
+
* argument without a dot is treated as the table name (column defaults to
|
|
400
|
+
* `id`), preserving the atlas one-arg behaviour.
|
|
401
|
+
*/
|
|
402
|
+
references(tableOrPath: string, column?: string): this {
|
|
403
|
+
let table = tableOrPath;
|
|
404
|
+
let col = column ?? "id";
|
|
405
|
+
const dot = tableOrPath.indexOf(".");
|
|
406
|
+
// Dotted shorthand only when no explicit column was passed — an explicit
|
|
407
|
+
// second arg always wins, so `references('a.b', 'c')` stays (table 'a.b').
|
|
408
|
+
if (dot !== -1 && column === undefined) {
|
|
409
|
+
table = tableOrPath.slice(0, dot);
|
|
410
|
+
col = tableOrPath.slice(dot + 1);
|
|
411
|
+
}
|
|
412
|
+
if (this.#currentColumn) {
|
|
413
|
+
this.#currentColumn.references = { table, column: col };
|
|
414
|
+
}
|
|
415
|
+
return this;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Referential action for the current column's foreign key `ON DELETE`
|
|
420
|
+
* (Lucid parity). Must follow {@link references}.
|
|
421
|
+
*/
|
|
422
|
+
onDelete(action: ReferentialAction): this {
|
|
423
|
+
if (this.#currentColumn?.references) {
|
|
424
|
+
this.#currentColumn.references.onDelete = action;
|
|
425
|
+
}
|
|
426
|
+
return this;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/** Referential action for the current column's foreign key `ON UPDATE`. Must follow {@link references}. */
|
|
430
|
+
onUpdate(action: ReferentialAction): this {
|
|
431
|
+
if (this.#currentColumn?.references) {
|
|
432
|
+
this.#currentColumn.references.onUpdate = action;
|
|
433
|
+
}
|
|
434
|
+
return this;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/**
|
|
438
|
+
* Comment the current **column** (Lucid/Knex column `comment()`). Inline on
|
|
439
|
+
* MySQL, a separate `COMMENT ON COLUMN` on Postgres, dropped on SQLite.
|
|
440
|
+
*
|
|
441
|
+
* Deviation, named: Knex's `table.comment()` is the TABLE comment, because
|
|
442
|
+
* its column methods return a separate column builder. Atlas flattens the
|
|
443
|
+
* column modifiers onto the table builder (`.notNullable()`, `.unique()`,
|
|
444
|
+
* `.defaultTo()` all work this way), so `comment()` follows that same rule
|
|
445
|
+
* and the table comment is {@link tableComment}. Resolving it by "is a
|
|
446
|
+
* column pending?" would be exactly the kind of guessing that bites later.
|
|
447
|
+
*/
|
|
448
|
+
comment(text: string): this {
|
|
449
|
+
if (this.#currentColumn) this.#currentColumn.comment = text;
|
|
450
|
+
return this;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/** Collate the current **column** (Lucid/Knex column `collate()`). See {@link comment} for why the table form is {@link tableCollate}. */
|
|
454
|
+
collate(collation: string): this {
|
|
455
|
+
if (this.#currentColumn) this.#currentColumn.collate = collation;
|
|
456
|
+
return this;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Place an added column first (Lucid/Knex `first()`). MySQL-only —
|
|
461
|
+
* Postgres and SQLite always append, and the Rust compiler raises
|
|
462
|
+
* `E_UNSUPPORTED` rather than dropping the instruction silently.
|
|
463
|
+
*/
|
|
464
|
+
first(): this {
|
|
465
|
+
if (this.#currentColumn) this.#currentColumn.position = { at: "first" };
|
|
466
|
+
return this;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
/** Place an added column after `column` (Lucid/Knex `after()`). MySQL-only — see {@link first}. */
|
|
470
|
+
after(column: string): this {
|
|
471
|
+
if (this.#currentColumn) {
|
|
472
|
+
this.#currentColumn.position = { at: "after", column };
|
|
473
|
+
}
|
|
474
|
+
return this;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ─── CHECK constraints ────────────────────────────────────
|
|
478
|
+
|
|
479
|
+
/** `CHECK (col > 0)` on the current column (Lucid/Knex `checkPositive`). */
|
|
480
|
+
checkPositive(constraintName?: string): this {
|
|
481
|
+
return this.#addCheck(
|
|
482
|
+
(column) => ({ check: "positive", column }),
|
|
483
|
+
constraintName,
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/** `CHECK (col < 0)` on the current column (Lucid/Knex `checkNegative`). */
|
|
488
|
+
checkNegative(constraintName?: string): this {
|
|
489
|
+
return this.#addCheck(
|
|
490
|
+
(column) => ({ check: "negative", column }),
|
|
491
|
+
constraintName,
|
|
492
|
+
);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/** `CHECK (col IN (…))` on the current column (Lucid/Knex `checkIn`). Values are quoted, never interpolated raw. */
|
|
496
|
+
checkIn(values: readonly CheckValue[], constraintName?: string): this {
|
|
497
|
+
return this.#addCheck(
|
|
498
|
+
(column) => ({ check: "in", column, values: [...values] }),
|
|
499
|
+
constraintName,
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** `CHECK (col NOT IN (…))` on the current column (Lucid/Knex `checkNotIn`). */
|
|
504
|
+
checkNotIn(values: readonly CheckValue[], constraintName?: string): this {
|
|
505
|
+
return this.#addCheck(
|
|
506
|
+
(column) => ({ check: "notIn", column, values: [...values] }),
|
|
507
|
+
constraintName,
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* `CHECK (col BETWEEN lo AND hi)` on the current column (Lucid/Knex
|
|
513
|
+
* `checkBetween`). Accepts one `[min, max]` interval or a list of them —
|
|
514
|
+
* several intervals are OR'd together, as in Knex.
|
|
515
|
+
*/
|
|
516
|
+
checkBetween(
|
|
517
|
+
range: readonly CheckValue[] | readonly (readonly CheckValue[])[],
|
|
518
|
+
constraintName?: string,
|
|
519
|
+
): this {
|
|
520
|
+
// A single [min, max] vs a list of intervals: the first element of a
|
|
521
|
+
// list-of-intervals is itself an array.
|
|
522
|
+
const ranges = Array.isArray(range[0])
|
|
523
|
+
? (range as readonly (readonly CheckValue[])[]).map((r) => [...r])
|
|
524
|
+
: [[...(range as readonly CheckValue[])]];
|
|
525
|
+
return this.#addCheck(
|
|
526
|
+
(column) => ({ check: "between", column, ranges }),
|
|
527
|
+
constraintName,
|
|
528
|
+
);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/** `CHECK (LENGTH(col) <op> n)` on the current column (Lucid/Knex `checkLength`). The operator is allow-listed by the Rust compiler. */
|
|
532
|
+
checkLength(
|
|
533
|
+
operator: CheckOperator,
|
|
534
|
+
length: number,
|
|
535
|
+
constraintName?: string,
|
|
536
|
+
): this {
|
|
537
|
+
return this.#addCheck(
|
|
538
|
+
(column) => ({ check: "length", column, operator, length }),
|
|
539
|
+
constraintName,
|
|
540
|
+
);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* `CHECK (col ~ 'pattern')` on the current column (Lucid/Knex `checkRegex`).
|
|
545
|
+
* Postgres spells it `~`; MySQL and SQLite use `REGEXP`.
|
|
546
|
+
*
|
|
547
|
+
* SQLite parses `REGEXP` but ships no implementation — the constraint only
|
|
548
|
+
* works if the connection registers a `regexp` function. Knex behaves the
|
|
549
|
+
* same way, so this is parity rather than a new trap, but it is worth
|
|
550
|
+
* knowing before you rely on it there.
|
|
551
|
+
*/
|
|
552
|
+
checkRegex(pattern: string, constraintName?: string): this {
|
|
553
|
+
return this.#addCheck(
|
|
554
|
+
(column) => ({ check: "regex", column, pattern }),
|
|
555
|
+
constraintName,
|
|
556
|
+
);
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* A free-form `CHECK (predicate)` (Lucid/Knex `check`). The predicate is
|
|
561
|
+
* emitted verbatim — exactly as trusted as {@link Schema.raw}, so never
|
|
562
|
+
* build it from user input. Prefer the typed `check*` helpers, which are
|
|
563
|
+
* safe by construction.
|
|
564
|
+
*/
|
|
565
|
+
check(predicate: string, constraintName?: string): this {
|
|
566
|
+
this.#pushConstraint({
|
|
567
|
+
constraint: "check",
|
|
568
|
+
name: constraintName,
|
|
569
|
+
expr: { check: "raw", predicate },
|
|
570
|
+
});
|
|
571
|
+
return this;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** Drop named CHECK constraints (Lucid/Knex `dropChecks`). */
|
|
575
|
+
dropChecks(...constraintNames: string[]): this {
|
|
576
|
+
this.#assertAlterMode("dropChecks()");
|
|
577
|
+
for (const name of constraintNames) {
|
|
578
|
+
this.#pushStandaloneOp({ op: "dropConstraint", name });
|
|
579
|
+
}
|
|
580
|
+
return this;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// ─── Table-level constraints ──────────────────────────────
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* With no argument, mark the current column as the primary key (the
|
|
587
|
+
* existing column modifier). With a column list, declare a composite
|
|
588
|
+
* `PRIMARY KEY (…)` table constraint (Lucid/Knex `primary([...])`).
|
|
589
|
+
*/
|
|
590
|
+
primary(columns?: readonly string[], constraintName?: string): this {
|
|
591
|
+
if (columns === undefined) {
|
|
592
|
+
if (this.#currentColumn) this.#currentColumn.primary = true;
|
|
593
|
+
return this;
|
|
594
|
+
}
|
|
595
|
+
this.#pushConstraint({
|
|
596
|
+
constraint: "primary",
|
|
597
|
+
name: constraintName,
|
|
598
|
+
columns: [...columns],
|
|
599
|
+
});
|
|
600
|
+
return this;
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
/**
|
|
604
|
+
* With no argument, mark the current column `UNIQUE` (the existing column
|
|
605
|
+
* modifier). With a column list, declare a composite `UNIQUE (…)` table
|
|
606
|
+
* constraint (Lucid/Knex `unique([...])`).
|
|
607
|
+
*
|
|
608
|
+
* Note this is a real constraint, unlike {@link uniqueIndex}, which creates
|
|
609
|
+
* a separate `CREATE UNIQUE INDEX`.
|
|
610
|
+
*/
|
|
611
|
+
unique(columns?: readonly string[], constraintName?: string): this {
|
|
612
|
+
if (columns === undefined) {
|
|
613
|
+
if (this.#currentColumn) this.#currentColumn.unique = true;
|
|
614
|
+
return this;
|
|
615
|
+
}
|
|
616
|
+
this.#pushConstraint({
|
|
617
|
+
constraint: "unique",
|
|
618
|
+
name: constraintName ?? this.#constraintName(columns, "unique"),
|
|
619
|
+
columns: [...columns],
|
|
620
|
+
});
|
|
621
|
+
return this;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/**
|
|
625
|
+
* Declare a composite foreign key (Lucid/Knex
|
|
626
|
+
* `foreign([...]).references([...]).inTable(…)`). Returns a small chainable
|
|
627
|
+
* so the target reads in Knex order; the constraint is recorded up front
|
|
628
|
+
* and filled in as you chain.
|
|
629
|
+
*/
|
|
630
|
+
foreign(
|
|
631
|
+
columns: string | readonly string[],
|
|
632
|
+
constraintName?: string,
|
|
633
|
+
): ForeignKeyBuilder {
|
|
634
|
+
const cols = typeof columns === "string" ? [columns] : [...columns];
|
|
635
|
+
const references: ForeignKeyReference = { table: "", columns: [] };
|
|
636
|
+
this.#pushConstraint({
|
|
637
|
+
constraint: "foreign",
|
|
638
|
+
name: constraintName ?? this.#constraintName(cols, "foreign"),
|
|
639
|
+
columns: cols,
|
|
640
|
+
references,
|
|
641
|
+
});
|
|
642
|
+
// The constraint is already recorded; the builder fills `references` in
|
|
643
|
+
// place as the caller chains, so order of arrival doesn't matter.
|
|
644
|
+
return new ForeignKeyBuilder(references);
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
// ─── Dropping constraints ─────────────────────────────────
|
|
648
|
+
|
|
649
|
+
/** Drop the primary key (Lucid/Knex `dropPrimary`). MySQL drops it by keyword; Postgres by name (default `<table>_pkey`). */
|
|
650
|
+
dropPrimary(constraintName?: string): this {
|
|
651
|
+
this.#assertAlterMode("dropPrimary()");
|
|
652
|
+
this.#pushStandaloneOp({ op: "dropPrimary", name: constraintName });
|
|
653
|
+
return this;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/** Drop a unique constraint by columns (using the default name) or by explicit name (Lucid/Knex `dropUnique`). */
|
|
657
|
+
dropUnique(
|
|
658
|
+
columns: string | readonly string[],
|
|
659
|
+
constraintName?: string,
|
|
660
|
+
): this {
|
|
661
|
+
this.#assertAlterMode("dropUnique()");
|
|
662
|
+
this.#pushStandaloneOp({
|
|
663
|
+
op: "dropUnique",
|
|
664
|
+
name: constraintName ?? this.#constraintName(columns, "unique"),
|
|
665
|
+
});
|
|
666
|
+
return this;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/** Drop a foreign key by columns (using the default name) or by explicit name (Lucid/Knex `dropForeign`). */
|
|
670
|
+
dropForeign(
|
|
671
|
+
columns: string | readonly string[],
|
|
672
|
+
constraintName?: string,
|
|
673
|
+
): this {
|
|
674
|
+
this.#assertAlterMode("dropForeign()");
|
|
675
|
+
this.#pushStandaloneOp({
|
|
676
|
+
op: "dropForeign",
|
|
677
|
+
name: constraintName ?? this.#constraintName(columns, "foreign"),
|
|
678
|
+
});
|
|
679
|
+
return this;
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/** Drop `created_at` + `updated_at` (Lucid/Knex `dropTimestamps`). */
|
|
683
|
+
dropTimestamps(): this {
|
|
684
|
+
return this.dropColumns("created_at", "updated_at");
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// ─── Table options ────────────────────────────────────────
|
|
688
|
+
|
|
689
|
+
/** MySQL storage engine (Lucid/Knex `engine`). Ignored on pg/sqlite. */
|
|
690
|
+
engine(name: string): this {
|
|
691
|
+
this.#options.engine = name;
|
|
692
|
+
return this;
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** MySQL default charset (Lucid/Knex `charset`). Ignored on pg/sqlite. */
|
|
696
|
+
charset(name: string): this {
|
|
697
|
+
this.#options.charset = name;
|
|
698
|
+
return this;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
/** MySQL default collation for the table. Named `tableCollate` because {@link collate} is the column modifier — see {@link comment}. */
|
|
702
|
+
tableCollate(name: string): this {
|
|
703
|
+
this.#options.collate = name;
|
|
704
|
+
return this;
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
/** Table comment. Named `tableComment` because {@link comment} is the column modifier — see there for why. */
|
|
708
|
+
tableComment(text: string): this {
|
|
709
|
+
this.#options.comment = text;
|
|
201
710
|
return this;
|
|
202
711
|
}
|
|
203
712
|
|
|
@@ -223,6 +732,86 @@ export class TableBuilder {
|
|
|
223
732
|
return this;
|
|
224
733
|
}
|
|
225
734
|
|
|
735
|
+
// ─── ALTER TABLE operations ───────────────────────────────
|
|
736
|
+
|
|
737
|
+
/**
|
|
738
|
+
* Apply the pending column definition as a type change instead of an
|
|
739
|
+
* `ADD COLUMN` (Lucid/Knex `alter()`). Must follow a column-type method.
|
|
740
|
+
*
|
|
741
|
+
* Nullability moves only if `.nullable()` / `.notNullable()` was called
|
|
742
|
+
* before this — a bare `t.string('x').alter()` changes the type and leaves
|
|
743
|
+
* the NOT NULL constraint exactly as it is.
|
|
744
|
+
*
|
|
745
|
+
* SQLite cannot alter a column in place; the Rust compiler rejects it with
|
|
746
|
+
* `E_UNSUPPORTED` rather than emitting a table rebuild behind your back.
|
|
747
|
+
*/
|
|
748
|
+
alter(): this {
|
|
749
|
+
this.#assertAlterMode("alter()");
|
|
750
|
+
const pending = this.#currentOp;
|
|
751
|
+
if (!pending) {
|
|
752
|
+
throw new Error(
|
|
753
|
+
"E_ALTER_MISUSE: alter() must follow a column definition, e.g. table.string('email').alter()",
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
if (pending.op === "addColumn") {
|
|
757
|
+
const converted: AlterOperation = {
|
|
758
|
+
op: "alterColumn",
|
|
759
|
+
column: pending.column,
|
|
760
|
+
setNullable: this.#nullabilityTouched
|
|
761
|
+
? pending.column.nullable
|
|
762
|
+
: undefined,
|
|
763
|
+
};
|
|
764
|
+
this.#operations[this.#operations.indexOf(pending)] = converted;
|
|
765
|
+
this.#currentOp = converted;
|
|
766
|
+
}
|
|
767
|
+
return this;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/** Drop a column (Lucid/Knex `dropColumn`). */
|
|
771
|
+
dropColumn(name: string): this {
|
|
772
|
+
this.#assertAlterMode("dropColumn()");
|
|
773
|
+
this.#pushStandaloneOp({ op: "dropColumn", name });
|
|
774
|
+
return this;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/** Drop several columns in call order (Lucid/Knex `dropColumns`). */
|
|
778
|
+
dropColumns(...names: string[]): this {
|
|
779
|
+
for (const name of names) this.dropColumn(name);
|
|
780
|
+
return this;
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
/** Rename a column (Lucid/Knex `renameColumn`). */
|
|
784
|
+
renameColumn(from: string, to: string): this {
|
|
785
|
+
this.#assertAlterMode("renameColumn()");
|
|
786
|
+
this.#pushStandaloneOp({ op: "renameColumn", from, to });
|
|
787
|
+
return this;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
/**
|
|
791
|
+
* Make an existing column nullable — `DROP NOT NULL` (Lucid/Knex
|
|
792
|
+
* `setNullable`).
|
|
793
|
+
*
|
|
794
|
+
* **Deviation from Knex, named deliberately.** Knex supports this on every
|
|
795
|
+
* dialect by querying `columnInfo()` at runtime to recover the column's
|
|
796
|
+
* type. Atlas compiles SQL synchronously in Rust with no round-trip, so
|
|
797
|
+
* this is Postgres-only — Postgres is the one dialect whose syntax needs no
|
|
798
|
+
* type. On MySQL use `table.<type>('col').nullable().alter()`, which
|
|
799
|
+
* restates the type; SQLite cannot alter a column in place at all. Both
|
|
800
|
+
* raise `E_UNSUPPORTED` with the alternative spelled out.
|
|
801
|
+
*/
|
|
802
|
+
setNullable(name: string): this {
|
|
803
|
+
this.#assertAlterMode("setNullable()");
|
|
804
|
+
this.#pushStandaloneOp({ op: "setNullable", name, nullable: true });
|
|
805
|
+
return this;
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
/** Make an existing column `NOT NULL` (Lucid/Knex `dropNullable`). Postgres-only — see {@link setNullable}. */
|
|
809
|
+
dropNullable(name: string): this {
|
|
810
|
+
this.#assertAlterMode("dropNullable()");
|
|
811
|
+
this.#pushStandaloneOp({ op: "setNullable", name, nullable: false });
|
|
812
|
+
return this;
|
|
813
|
+
}
|
|
814
|
+
|
|
226
815
|
// ─── Accessors ────────────────────────────────────────────
|
|
227
816
|
|
|
228
817
|
getColumns(): ColumnDefinition[] {
|
|
@@ -231,33 +820,152 @@ export class TableBuilder {
|
|
|
231
820
|
getIndexes(): IndexDefinition[] {
|
|
232
821
|
return [...this.#indexes];
|
|
233
822
|
}
|
|
823
|
+
/** Ordered ALTER TABLE operations. Empty in `create` mode. */
|
|
824
|
+
getOperations(): AlterOperation[] {
|
|
825
|
+
return [...this.#operations];
|
|
826
|
+
}
|
|
234
827
|
|
|
235
828
|
/** Compile to SQL statements via the Rust compiler. */
|
|
236
|
-
toStatements(
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
829
|
+
toStatements(
|
|
830
|
+
dialect: AtlasDialect = getAtlasDialect(),
|
|
831
|
+
options: { ifNotExists?: boolean } = {},
|
|
832
|
+
): string[] {
|
|
833
|
+
const spec =
|
|
834
|
+
this.mode === "alter"
|
|
835
|
+
? {
|
|
836
|
+
kind: "alterTable",
|
|
837
|
+
table: this.tableName,
|
|
838
|
+
operations: this.#operations.map((op) =>
|
|
839
|
+
op.op === "addColumn"
|
|
840
|
+
? { op: op.op, column: this.#serializeColumn(op.column) }
|
|
841
|
+
: op.op === "alterColumn"
|
|
842
|
+
? {
|
|
843
|
+
op: op.op,
|
|
844
|
+
column: this.#serializeColumn(op.column),
|
|
845
|
+
setNullable: op.setNullable ?? null,
|
|
846
|
+
}
|
|
847
|
+
: op,
|
|
848
|
+
),
|
|
849
|
+
}
|
|
850
|
+
: {
|
|
851
|
+
kind: "createTable",
|
|
852
|
+
table: this.tableName,
|
|
853
|
+
columns: this.#columns.map((c) => this.#serializeColumn(c)),
|
|
854
|
+
indexes: this.#indexes.map((i) => ({
|
|
855
|
+
name: i.name,
|
|
856
|
+
columns: i.columns,
|
|
857
|
+
unique: i.unique,
|
|
858
|
+
})),
|
|
859
|
+
ifNotExists: options.ifNotExists ?? false,
|
|
860
|
+
constraints: this.#constraints,
|
|
861
|
+
options: this.#options,
|
|
862
|
+
};
|
|
863
|
+
const statements = compileStatementNative(spec, dialect).statements;
|
|
864
|
+
// `alterTable` carries no index list — an index added alongside an
|
|
865
|
+
// ALTER compiles to its own CREATE INDEX, appended in declaration order.
|
|
866
|
+
if (this.mode === "alter" && this.#indexes.length > 0) {
|
|
867
|
+
for (const idx of this.#indexes) {
|
|
868
|
+
statements.push(
|
|
869
|
+
...compileStatementNative(
|
|
870
|
+
{
|
|
871
|
+
kind: "createIndex",
|
|
872
|
+
table: this.tableName,
|
|
873
|
+
name: idx.name,
|
|
874
|
+
columns: idx.columns,
|
|
875
|
+
unique: idx.unique,
|
|
876
|
+
},
|
|
877
|
+
dialect,
|
|
878
|
+
).statements,
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
return statements;
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
/** Flatten a column into the wire shape the Rust `ColumnDef` deserialises. */
|
|
886
|
+
#serializeColumn(c: ColumnDefinition): Record<string, unknown> {
|
|
887
|
+
return {
|
|
888
|
+
name: c.name,
|
|
889
|
+
kind: TYPE_KIND_MAP[c.type],
|
|
890
|
+
length: c.length ?? null,
|
|
891
|
+
precision: c.precision ?? null,
|
|
892
|
+
scale: c.scale ?? null,
|
|
893
|
+
// Flattened into the Rust ColumnTypeSpec — each read for one kind only.
|
|
894
|
+
values: c.values ?? null,
|
|
895
|
+
rawType: c.rawType ?? null,
|
|
896
|
+
nullable: c.nullable,
|
|
897
|
+
primary: c.primary,
|
|
898
|
+
autoIncrement: c.autoIncrement ?? false,
|
|
899
|
+
unique: c.unique,
|
|
900
|
+
unsigned: c.unsigned ?? false,
|
|
901
|
+
default: c.defaultValue ?? null,
|
|
902
|
+
references: c.references ?? null,
|
|
903
|
+
comment: c.comment ?? null,
|
|
904
|
+
collate: c.collate ?? null,
|
|
905
|
+
position: c.position ?? null,
|
|
259
906
|
};
|
|
260
|
-
|
|
907
|
+
}
|
|
908
|
+
|
|
909
|
+
/**
|
|
910
|
+
* Record a constraint. In `create` mode it renders inside the
|
|
911
|
+
* `CREATE TABLE`; in `alter` mode it becomes an `ADD CONSTRAINT`, kept in
|
|
912
|
+
* call order with the surrounding column operations.
|
|
913
|
+
*/
|
|
914
|
+
#pushConstraint(spec: TableConstraintSpec): void {
|
|
915
|
+
if (this.mode === "alter") {
|
|
916
|
+
this.#operations.push({ op: "addConstraint", constraint: spec });
|
|
917
|
+
} else {
|
|
918
|
+
this.#constraints.push(spec);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
/** Build a CHECK against the pending column. */
|
|
923
|
+
#addCheck(
|
|
924
|
+
build: (column: string) => CheckExpression,
|
|
925
|
+
constraintName?: string,
|
|
926
|
+
): this {
|
|
927
|
+
const column = this.#currentColumn?.name;
|
|
928
|
+
if (!column) {
|
|
929
|
+
throw new Error(
|
|
930
|
+
"E_CHECK_MISUSE: a check* helper must follow a column definition, e.g. table.integer('qty').checkPositive()",
|
|
931
|
+
);
|
|
932
|
+
}
|
|
933
|
+
this.#pushConstraint({
|
|
934
|
+
constraint: "check",
|
|
935
|
+
name: constraintName,
|
|
936
|
+
expr: build(column),
|
|
937
|
+
});
|
|
938
|
+
return this;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
/**
|
|
942
|
+
* Default constraint name, following Knex's `<table>_<columns>_<suffix>`
|
|
943
|
+
* convention so `unique([...])` and `dropUnique([...])` agree without the
|
|
944
|
+
* caller naming anything. Distinct from {@link uniqueIndex}, which names a
|
|
945
|
+
* separate INDEX object `idx_…`.
|
|
946
|
+
*/
|
|
947
|
+
#constraintName(
|
|
948
|
+
columns: string | readonly string[],
|
|
949
|
+
suffix: "unique" | "foreign",
|
|
950
|
+
): string {
|
|
951
|
+
const cols = typeof columns === "string" ? [columns] : columns;
|
|
952
|
+
return `${this.tableName}_${cols.join("_")}_${suffix}`;
|
|
953
|
+
}
|
|
954
|
+
|
|
955
|
+
#assertAlterMode(method: string): void {
|
|
956
|
+
if (this.mode !== "alter") {
|
|
957
|
+
throw new Error(
|
|
958
|
+
`E_ALTER_MISUSE: ${method} is only available inside schema.alterTable() — a new table has nothing to alter`,
|
|
959
|
+
);
|
|
960
|
+
}
|
|
961
|
+
}
|
|
962
|
+
|
|
963
|
+
/** Record an op that takes no column modifiers, so `.nullable()` etc. can't silently attach to it. */
|
|
964
|
+
#pushStandaloneOp(op: AlterOperation): void {
|
|
965
|
+
this.#operations.push(op);
|
|
966
|
+
this.#currentColumn = undefined;
|
|
967
|
+
this.#currentOp = undefined;
|
|
968
|
+
this.#nullabilityTouched = false;
|
|
261
969
|
}
|
|
262
970
|
|
|
263
971
|
#addColumn(name: string, type: ColumnType): this {
|
|
@@ -270,6 +978,29 @@ export class TableBuilder {
|
|
|
270
978
|
};
|
|
271
979
|
this.#columns.push(col);
|
|
272
980
|
this.#currentColumn = col;
|
|
981
|
+
this.#nullabilityTouched = false;
|
|
982
|
+
if (this.mode === "alter") {
|
|
983
|
+
const op: Extract<AlterOperation, { op: "addColumn" }> = {
|
|
984
|
+
op: "addColumn",
|
|
985
|
+
column: col,
|
|
986
|
+
};
|
|
987
|
+
this.#operations.push(op);
|
|
988
|
+
this.#currentOp = op;
|
|
989
|
+
}
|
|
990
|
+
return this;
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
#addFloat(
|
|
994
|
+
name: string,
|
|
995
|
+
type: "float" | "double",
|
|
996
|
+
precision?: number,
|
|
997
|
+
scale?: number,
|
|
998
|
+
): this {
|
|
999
|
+
this.#addColumn(name, type);
|
|
1000
|
+
if (this.#currentColumn) {
|
|
1001
|
+
this.#currentColumn.precision = precision;
|
|
1002
|
+
this.#currentColumn.scale = scale;
|
|
1003
|
+
}
|
|
273
1004
|
return this;
|
|
274
1005
|
}
|
|
275
1006
|
|