@zerotal/orm 1.7.3 → 1.7.5
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 +31 -2
- package/api-surface.md +9 -61
- package/package.json +3 -3
- package/src/config.ts +4 -1
- package/src/db/dialects/MysqlDialect.ts +6 -0
- package/src/db/dialects/PostgresDialect.ts +5 -0
- package/src/db/dialects/SqliteDialect.ts +5 -0
- package/src/db/dialects/types.ts +16 -0
- package/src/schema/Blueprint.ts +7 -5
- package/src/schema/ColumnDefinition.ts +14 -3
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,29 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
8
8
|
|
|
9
9
|
## [Unreleased]
|
|
10
10
|
|
|
11
|
+
## [1.7.4] — 2026-08-21
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **A string column could not carry an index on MySQL.** `table.string()` compiled to `TEXT`
|
|
16
|
+
on every engine and discarded its `length` argument — the parameter existed and was
|
|
17
|
+
documented as "accepted for multi-DB compatibility", wired to nothing. MySQL refuses to key
|
|
18
|
+
a TEXT column without a prefix length, so `table.string("email").unique()` failed at
|
|
19
|
+
`CREATE TABLE`:
|
|
20
|
+
|
|
21
|
+
```text
|
|
22
|
+
BLOB/TEXT column 'email' used in key specification without a key length
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
Any natural key — an email, a slug — was unusable on MySQL, and `index()` the same. The
|
|
26
|
+
storage type now comes from the dialect, beside `booleanType` and `autoIncrementColumn`:
|
|
27
|
+
MySQL gets `VARCHAR(length)`, while SQLite and PostgreSQL keep `TEXT`, which PostgreSQL
|
|
28
|
+
indexes happily. `char()` had the identical bug and the identical fix.
|
|
29
|
+
|
|
30
|
+
Found by the new MySQL smoke suite on its first run against a real server.
|
|
31
|
+
|
|
32
|
+
## [1.7.3] — 2026-08-20
|
|
33
|
+
|
|
11
34
|
### Fixed
|
|
12
35
|
|
|
13
36
|
- **A boolean column could not hold a boolean on PostgreSQL.** `table.boolean()` compiled to
|
|
@@ -19,6 +42,10 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
19
42
|
tables keep their integer columns until a migration alters them. Found by the new smoke suite
|
|
20
43
|
that runs the ORM against a real PostgreSQL in CI.
|
|
21
44
|
|
|
45
|
+
## [1.7.2] — 2026-08-18
|
|
46
|
+
|
|
47
|
+
### Fixed
|
|
48
|
+
|
|
22
49
|
- **A seeder that failed partway left its rows behind.** `Seeder.call()` has always wrapped
|
|
23
50
|
_composed_ seeders in a transaction, so a `DatabaseSeeder` that delegates was atomic and one that
|
|
24
51
|
does its work inline — which is most of them — was not. A failure on the fourth table committed
|
|
@@ -31,8 +58,6 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
31
58
|
no connection is bound, because a seeder is not obliged to touch the database and an app that has
|
|
32
59
|
not configured one should not fail to seed over a transaction it never needed.
|
|
33
60
|
|
|
34
|
-
### Fixed
|
|
35
|
-
|
|
36
61
|
- **`DatabaseProvider` now runs in `worker`, so `zt queue:work` can boot.** It did not, and the
|
|
37
62
|
consequence was total rather than partial: `QueueProvider` _does_ run in `worker`, the
|
|
38
63
|
queue's own default driver is `sqlite`, and so the worker asked for a connection this
|
|
@@ -51,6 +76,10 @@ follows the Zerotal monorepo's unified versioning.
|
|
|
51
76
|
|
|
52
77
|
Found building the first cookbook app, whose first queued job could not run.
|
|
53
78
|
|
|
79
|
+
## [1.7.1] — 2026-08-16
|
|
80
|
+
|
|
81
|
+
### Fixed
|
|
82
|
+
|
|
54
83
|
- **Relation keys now accept the JS spelling, like every other identifier.** The convention is
|
|
55
84
|
camelCase in the application and snake_case in the database, converted on the way through —
|
|
56
85
|
and relation keys were the one place it did not happen. `@hasMany(() => Issue, { foreignKey:
|
package/api-surface.md
CHANGED
|
@@ -116,7 +116,7 @@ class Blueprint = {
|
|
|
116
116
|
bigInteger: (name: string) => ColumnBuilder
|
|
117
117
|
binary: (name: string) => ColumnBuilder
|
|
118
118
|
boolean: (name: string) => ColumnBuilder
|
|
119
|
-
char: (name: string,
|
|
119
|
+
char: (name: string, length?: number) => ColumnBuilder
|
|
120
120
|
date: (name: string) => ColumnBuilder
|
|
121
121
|
dateTime: (name: string) => ColumnBuilder
|
|
122
122
|
datetime: (name: string) => ColumnBuilder
|
|
@@ -153,7 +153,7 @@ class Blueprint = {
|
|
|
153
153
|
smallInteger: (name: string) => ColumnBuilder
|
|
154
154
|
softDeletes: (column?: string) => void
|
|
155
155
|
spatialIndex: (columns: string | string[], name?: string) => Blueprint
|
|
156
|
-
string: (name: string,
|
|
156
|
+
string: (name: string, length?: number) => ColumnBuilder
|
|
157
157
|
text: (name: string) => ColumnBuilder
|
|
158
158
|
time: (name: string) => ColumnBuilder
|
|
159
159
|
timestamp: (name: string) => ColumnBuilder
|
|
@@ -182,7 +182,7 @@ class Cast = {
|
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
class ColumnBuilder = {
|
|
185
|
-
new <Locked extends string = never>(name: string, _sqlType: string, isPrimary?: boolean, isAutoIncrement?: boolean, _isBoolean?: boolean): ColumnBuilder<Locked>
|
|
185
|
+
new <Locked extends string = never>(name: string, _sqlType: string, isPrimary?: boolean, isAutoIncrement?: boolean, _isBoolean?: boolean, _stringLength?: number | undefined): ColumnBuilder<Locked>
|
|
186
186
|
after: (_column: string) => ColumnBuilder<Locked>
|
|
187
187
|
alter: () => ColumnBuilder<Locked>
|
|
188
188
|
before: (_column: string) => ColumnBuilder<Locked>
|
|
@@ -277,13 +277,6 @@ class ForeignKeyBuilder = {
|
|
|
277
277
|
toConstraintSQL: () => string
|
|
278
278
|
}
|
|
279
279
|
|
|
280
|
-
class HookRegistry = {
|
|
281
|
-
new (): HookRegistry
|
|
282
|
-
static onAfterRun: ((ModelClass: ClassRef, hook: HookName, model: unknown) => void) | undefined
|
|
283
|
-
static register: <T>(ModelClass: ClassRef, hook: HookName, fn: HookFn<T>) => void
|
|
284
|
-
static run: <T>(ModelClass: ClassRef, hook: HookName, model: T) => Promise<void>
|
|
285
|
-
}
|
|
286
|
-
|
|
287
280
|
class JsonCast = {
|
|
288
281
|
new <T = unknown>(mapper?: CastMapper<T> | undefined): JsonCast<T>
|
|
289
282
|
fields: () => CastField[]
|
|
@@ -574,6 +567,7 @@ class MysqlDialect = {
|
|
|
574
567
|
readonly name: 'mysql'
|
|
575
568
|
readonly supportsAdvisoryLocks: true
|
|
576
569
|
readonly supportsTransactionalDdl: false
|
|
570
|
+
stringType: (length: number) => string
|
|
577
571
|
}
|
|
578
572
|
|
|
579
573
|
class NPlusOneDetected = {
|
|
@@ -615,6 +609,7 @@ class PostgresDialect = {
|
|
|
615
609
|
readonly name: 'postgres'
|
|
616
610
|
readonly supportsAdvisoryLocks: true
|
|
617
611
|
readonly supportsTransactionalDdl: true
|
|
612
|
+
stringType: () => string
|
|
618
613
|
}
|
|
619
614
|
|
|
620
615
|
class QueryBuilder = {
|
|
@@ -754,6 +749,7 @@ class SqliteDialect = {
|
|
|
754
749
|
readonly name: 'sqlite'
|
|
755
750
|
readonly supportsAdvisoryLocks: false
|
|
756
751
|
readonly supportsTransactionalDdl: true
|
|
752
|
+
stringType: () => string
|
|
757
753
|
}
|
|
758
754
|
|
|
759
755
|
class StateError = {
|
|
@@ -798,14 +794,10 @@ class UnsupportedDialectError = {
|
|
|
798
794
|
readonly status: number
|
|
799
795
|
}
|
|
800
796
|
|
|
801
|
-
const columnRegistry = Map<ClassRef, Map<string, ColumnOptions>>
|
|
802
|
-
|
|
803
797
|
const DB = { table(tableName: string): QueryBuilder; raw<T = Record<string, unknown>>(sql: TemplateStringsArray | string, ...rest: unknown[]): Promise<T[]>; transaction<T>(callback: (tx?: SQLInstance) => Promise<T>, attempts?: number): Promise<T>; beginTransaction(): Promise<ManualTransaction>; onPrimary(): { table(name: string): QueryBuilder; }; currentTx(): unknown | undefined; advisoryLock<T>(key: number, callback: () => Promise<T>): Promise<T>; preventNPlusOne(options?: NPlusOneOptions): void; allowNPlusOne(pattern: string, options?: { once?: boolean; }): void;}
|
|
804
798
|
|
|
805
799
|
const ModelInspector = { load(pattern: string, cwd?: string): Promise<void>; all(): ModelSchema[]; fromClass(ctor: ClassRef): ModelSchema | null;}
|
|
806
800
|
|
|
807
|
-
const modelsByName = Map<string, ClassRef>
|
|
808
|
-
|
|
809
801
|
const relationRegistry = Map<ClassRef, Map<string, RelationMetadata>>
|
|
810
802
|
|
|
811
803
|
const Schema = { create(table: string, callback: (bp: Blueprint) => void): Promise<void>; createIfNotExists(table: string, callback: (bp: Blueprint) => void): Promise<void>; table(name: string, callback: (bp: Blueprint) => void): Promise<void>; alter(name: string, callback: (bp: Blueprint) => void): Promise<void>; drop(table: string): Promise<void>; dropIfExists(table: string): Promise<void>; rename(from: string, to: string): Promise<void>; hasTable(table: string): Promise<boolean>; hasColumn(table: string, column: string): Promise<boolean>;}
|
|
@@ -816,32 +808,14 @@ const SchemaInspector = { tables(): Promise<string[]>; columns(table: stri
|
|
|
816
808
|
|
|
817
809
|
const TransactionContext = AsyncLocalStorage<SQLInstance>
|
|
818
810
|
|
|
819
|
-
function _clearModelConnections = () => void
|
|
820
|
-
|
|
821
|
-
function _clearTransitionCallbacks = () => void
|
|
822
|
-
|
|
823
811
|
function _getConnection = () => SQLInstance
|
|
824
812
|
|
|
825
813
|
function _getDbConnectionOverride = () => SQLInstance | null
|
|
826
814
|
|
|
827
|
-
function _getDialect = () => 'sqlite' | 'postgres' | 'mysql'
|
|
828
|
-
|
|
829
|
-
function _getModelConnection = () => SQLInstance
|
|
830
|
-
|
|
831
815
|
function _globalScopeRegistry = () => Map<ClassRef, Map<string, GlobalScopeCallback>>
|
|
832
816
|
|
|
833
|
-
function _normaliseSqliteUrl = (raw: string) => string
|
|
834
|
-
|
|
835
|
-
function _resolveConn = (ModelClass?: typeof BaseModel) => SQLInstance
|
|
836
|
-
|
|
837
|
-
function _setBaseModelConnection = (conn: SQLInstance | null) => void
|
|
838
|
-
|
|
839
|
-
function _setBaseModelDialect = (dialect: 'sqlite' | 'postgres' | 'mysql') => void
|
|
840
|
-
|
|
841
817
|
function _setDbConnection = (conn: SQLInstance | null) => void
|
|
842
818
|
|
|
843
|
-
function _setQueryBuilderDialect = (d: Dialect) => void
|
|
844
|
-
|
|
845
819
|
function _setReadReplicas = (primary: SQLInstance, replicas: SQLInstance[]) => void
|
|
846
820
|
|
|
847
821
|
function _suppressHooks = <T>(fn: () => Promise<T>) => Promise<T>
|
|
@@ -854,8 +828,6 @@ function belongsTo = (related: () => unknown, options: BelongsToOptions) => (_va
|
|
|
854
828
|
|
|
855
829
|
function column = { (): ColumnDecorator; (type: ColumnShorthand): ColumnDecorator; (options: ColumnOptions): ColumnDecorator; (type: ColumnShorthand, options: Omit<ColumnOptions, 'type'>): ColumnDecorator;}
|
|
856
830
|
|
|
857
|
-
function columnsFor = (ctor: ClassRef) => Map<string, ColumnOptions> | null
|
|
858
|
-
|
|
859
831
|
function createReadWriteRouter = (primary: SQLInstance, replicas: SQLInstance[]) => SQLInstance
|
|
860
832
|
|
|
861
833
|
function currentOrmContext = () => OrmContext
|
|
@@ -882,10 +854,6 @@ function json = <T = unknown>(mapper?: CastMapper<T>) => JsonCast<T>
|
|
|
882
854
|
|
|
883
855
|
function manyToMany = (related: () => unknown, options: ManyToManyOptions) => (_value: unknown, context: ClassFieldDecoratorContext) => void
|
|
884
856
|
|
|
885
|
-
function modelByName = (name: string) => ClassRef | undefined
|
|
886
|
-
|
|
887
|
-
function modelForParam = (paramName: string) => ImplicitModel | undefined
|
|
888
|
-
|
|
889
857
|
function morphedByMany = (related: () => unknown, options: MorphedByManyOptions) => (_value: unknown, context: ClassFieldDecoratorContext) => void
|
|
890
858
|
|
|
891
859
|
function morphMany = (related: () => unknown, options: MorphManyOptions) => (_value: unknown, context: ClassFieldDecoratorContext) => void
|
|
@@ -900,14 +868,10 @@ function objectOf = <T = unknown>(mapper?: CastMapper<T>) => JsonCast<T>
|
|
|
900
868
|
|
|
901
869
|
function preventNPlusOne = (options?: NPlusOneOptions) => void
|
|
902
870
|
|
|
903
|
-
function registerColumn = (ctor: ClassRef, name: string, options: ColumnOptions) => void
|
|
904
|
-
|
|
905
871
|
function registerConnectionResolver = (fn: ContextConnectionResolver | null) => void
|
|
906
872
|
|
|
907
873
|
function registerImplicitBinding = () => void
|
|
908
874
|
|
|
909
|
-
function registerModel = (ctor: ClassRef) => void
|
|
910
|
-
|
|
911
875
|
function registerModelConnection = (name: string, conn: SQLInstance, dialect?: Dialect) => void
|
|
912
876
|
|
|
913
877
|
function resetOrmContext = () => void
|
|
@@ -1203,21 +1167,6 @@ interface PaginateResult = {
|
|
|
1203
1167
|
url: (page: number, baseUrl?: string, query?: Record<string, string>) => string
|
|
1204
1168
|
}
|
|
1205
1169
|
|
|
1206
|
-
interface QueryState = {
|
|
1207
|
-
distinct: boolean
|
|
1208
|
-
groupBys: string[]
|
|
1209
|
-
havings: HavingClause[]
|
|
1210
|
-
joins: JoinClause[]
|
|
1211
|
-
limit: number | undefined
|
|
1212
|
-
lock: string | undefined
|
|
1213
|
-
offset: number | undefined
|
|
1214
|
-
orders: OrderClause[]
|
|
1215
|
-
selects: string[]
|
|
1216
|
-
table: string
|
|
1217
|
-
unions: UnionClause[]
|
|
1218
|
-
wheres: WhereClause[]
|
|
1219
|
-
}
|
|
1220
|
-
|
|
1221
1170
|
interface RelationMetadata = {
|
|
1222
1171
|
firstKey?: string
|
|
1223
1172
|
foreignKey: string
|
|
@@ -1274,6 +1223,7 @@ interface SqlDialect = {
|
|
|
1274
1223
|
readonly name: DialectName
|
|
1275
1224
|
readonly supportsAdvisoryLocks: boolean
|
|
1276
1225
|
readonly supportsTransactionalDdl: boolean
|
|
1226
|
+
stringType: (length: number) => string
|
|
1277
1227
|
}
|
|
1278
1228
|
|
|
1279
1229
|
interface SQLInstance = {
|
|
@@ -1311,17 +1261,15 @@ interface TransitionContext = {
|
|
|
1311
1261
|
|
|
1312
1262
|
type CastMapper = ((raw: unknown) => T) | (new (...args: never[]) => T)
|
|
1313
1263
|
|
|
1314
|
-
type ClassRef = abstract new (...args: never[]) => unknown
|
|
1315
|
-
|
|
1316
1264
|
type Columns = { [K in keyof T & string]: K extends `_${string}` ? never : T[K] extends (...args: any[]) => any ? never : K; }[keyof T & string]
|
|
1317
1265
|
|
|
1318
|
-
type ColumnShorthand = 'string' | '
|
|
1266
|
+
type ColumnShorthand = 'string' | 'text' | 'integer' | 'number' | 'float' | 'boolean' | 'datetime' | 'date' | 'json' | 'array' | 'encrypted' | 'encrypted:json'
|
|
1319
1267
|
|
|
1320
1268
|
type Constructor = new (...args: any[]) => T
|
|
1321
1269
|
|
|
1322
1270
|
type ContextConnectionResolver = (ModelClass?: typeof BaseModel) => SQLInstance | null
|
|
1323
1271
|
|
|
1324
|
-
type DatePart = 'date' | 'time' | '
|
|
1272
|
+
type DatePart = 'date' | 'time' | 'day' | 'month' | 'year'
|
|
1325
1273
|
|
|
1326
1274
|
type DialectName = 'sqlite' | 'postgres' | 'mysql'
|
|
1327
1275
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zerotal/orm",
|
|
3
|
-
"version": "1.7.
|
|
3
|
+
"version": "1.7.5",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"maturity": "stable",
|
|
6
6
|
"private": false,
|
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
"typecheck": "tsc --noEmit"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@zerotal/core": "1.7.
|
|
35
|
-
"@zerotal/validator": "1.7.
|
|
34
|
+
"@zerotal/core": "1.7.5",
|
|
35
|
+
"@zerotal/validator": "1.7.5"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"typescript": "^5.8.0"
|
package/src/config.ts
CHANGED
|
@@ -19,11 +19,14 @@ export interface DatabaseConfigShape {
|
|
|
19
19
|
* mutating queries (INSERT, UPDATE, DELETE, DDL) plus transactions to the
|
|
20
20
|
* primary. No code changes are required in controllers or models.
|
|
21
21
|
*
|
|
22
|
+
* `env()` with no fallback is `string | undefined`, so an unset replica has to
|
|
23
|
+
* drop out rather than widen the array — this field is `string[]`.
|
|
24
|
+
*
|
|
22
25
|
* @example
|
|
23
26
|
* replicas: [
|
|
24
27
|
* env('REPLICA_1_URL'),
|
|
25
28
|
* env('REPLICA_2_URL'),
|
|
26
|
-
* ]
|
|
29
|
+
* ].filter((url) => url !== undefined)
|
|
27
30
|
*/
|
|
28
31
|
replicas?: string[];
|
|
29
32
|
/**
|
|
@@ -58,6 +58,12 @@ export class MysqlDialect implements SqlDialect {
|
|
|
58
58
|
return value ? "1" : "0";
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
// VARCHAR so the column can carry an index or a unique constraint; MySQL
|
|
62
|
+
// refuses to key a TEXT column without a prefix length.
|
|
63
|
+
stringType(length: number): string {
|
|
64
|
+
return `VARCHAR(${length})`;
|
|
65
|
+
}
|
|
66
|
+
|
|
61
67
|
advisoryLockSql(key: number): DialectQuery {
|
|
62
68
|
return { sql: `SELECT GET_LOCK(?, -1)`, params: [`zerotal_lock_${key}`] };
|
|
63
69
|
}
|
|
@@ -54,6 +54,11 @@ export class PostgresDialect implements SqlDialect {
|
|
|
54
54
|
return value ? "TRUE" : "FALSE";
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
+
// PostgreSQL indexes TEXT without a key length, so the portable type stands.
|
|
58
|
+
stringType(): string {
|
|
59
|
+
return "TEXT";
|
|
60
|
+
}
|
|
61
|
+
|
|
57
62
|
advisoryLockSql(key: number): DialectQuery {
|
|
58
63
|
return { sql: `SELECT pg_advisory_lock(?)`, params: [key] };
|
|
59
64
|
}
|
|
@@ -55,6 +55,11 @@ export class SqliteDialect implements SqlDialect {
|
|
|
55
55
|
return value ? "1" : "0";
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// SQLite has one string type and no length to honour.
|
|
59
|
+
stringType(): string {
|
|
60
|
+
return "TEXT";
|
|
61
|
+
}
|
|
62
|
+
|
|
58
63
|
advisoryLockSql(): DialectQuery | null {
|
|
59
64
|
return null;
|
|
60
65
|
}
|
package/src/db/dialects/types.ts
CHANGED
|
@@ -76,6 +76,22 @@ export interface SqlDialect {
|
|
|
76
76
|
/** A boolean as this engine spells it in a `DEFAULT` clause. */
|
|
77
77
|
booleanLiteral(value: boolean): string;
|
|
78
78
|
|
|
79
|
+
/**
|
|
80
|
+
* The column type a portable `table.string(name, length)` compiles to.
|
|
81
|
+
*
|
|
82
|
+
* SQLite has one string type and ignores the length, so the Blueprint emitted
|
|
83
|
+
* `TEXT` for every engine and threw the length away. MySQL cannot index a TEXT
|
|
84
|
+
* column without a key length, so `table.string("name").unique()` — an email, a
|
|
85
|
+
* slug, any natural key — failed at `CREATE TABLE`:
|
|
86
|
+
*
|
|
87
|
+
* BLOB/TEXT column 'name' used in key specification without a key length
|
|
88
|
+
*
|
|
89
|
+
* The length was already in the signature and already documented as accepted;
|
|
90
|
+
* it just had nowhere to go. PostgreSQL keeps `TEXT`, which it indexes happily
|
|
91
|
+
* and which is the idiomatic choice there.
|
|
92
|
+
*/
|
|
93
|
+
stringType(length: number): string;
|
|
94
|
+
|
|
79
95
|
/** Whether the engine supports application-level advisory locks. */
|
|
80
96
|
readonly supportsAdvisoryLocks: boolean;
|
|
81
97
|
|
package/src/schema/Blueprint.ts
CHANGED
|
@@ -202,19 +202,21 @@ export class Blueprint {
|
|
|
202
202
|
/**
|
|
203
203
|
* Variable-length string column (`VARCHAR`-style), stored as `TEXT`.
|
|
204
204
|
* @param name - Column name.
|
|
205
|
-
* @param
|
|
205
|
+
* @param length - Max length. Ignored on SQLite and PostgreSQL, which have one
|
|
206
|
+
* string type; on MySQL it becomes `VARCHAR(length)`, which is what lets the
|
|
207
|
+
* column carry an index or a unique constraint.
|
|
206
208
|
* @category Column types
|
|
207
209
|
*/
|
|
208
|
-
string(name: string,
|
|
209
|
-
return this._add(new ColumnBuilder(name, "TEXT"));
|
|
210
|
+
string(name: string, length = 255): ColumnBuilder {
|
|
211
|
+
return this._add(new ColumnBuilder(name, "TEXT", false, false, false, length));
|
|
210
212
|
}
|
|
211
213
|
|
|
212
214
|
/**
|
|
213
215
|
* Fixed-length `CHAR` column. Stored as `TEXT` on SQLite; `_length` is ignored.
|
|
214
216
|
* @category Column types
|
|
215
217
|
*/
|
|
216
|
-
char(name: string,
|
|
217
|
-
return this._add(new ColumnBuilder(name, "TEXT"));
|
|
218
|
+
char(name: string, length = 255): ColumnBuilder {
|
|
219
|
+
return this._add(new ColumnBuilder(name, "TEXT", false, false, false, length));
|
|
218
220
|
}
|
|
219
221
|
|
|
220
222
|
/**
|
|
@@ -69,6 +69,11 @@ export class ColumnBuilder<Locked extends string = never> {
|
|
|
69
69
|
* storage type at compile time — see {@link SqlDialect.booleanType}.
|
|
70
70
|
*/
|
|
71
71
|
private _isBoolean = false,
|
|
72
|
+
/**
|
|
73
|
+
* Declared max length for a portable string column. The engine decides
|
|
74
|
+
* whether it matters — see {@link SqlDialect.stringType}.
|
|
75
|
+
*/
|
|
76
|
+
private _stringLength?: number,
|
|
72
77
|
) {
|
|
73
78
|
this._isPrimary = isPrimary;
|
|
74
79
|
this._isAutoIncr = isAutoIncrement;
|
|
@@ -329,9 +334,15 @@ export class ColumnBuilder<Locked extends string = never> {
|
|
|
329
334
|
// PostgreSQL a syntax error and against MySQL a 1064.
|
|
330
335
|
if (this._isAutoIncr) return getDialect(dialect).autoIncrementColumn(this.name);
|
|
331
336
|
|
|
332
|
-
//
|
|
333
|
-
// INTEGER
|
|
334
|
-
|
|
337
|
+
// Storage type is the engine's to choose wherever the engines disagree:
|
|
338
|
+
// SQLite keeps 0/1 in an INTEGER where PostgreSQL has a real boolean, and
|
|
339
|
+
// MySQL needs a VARCHAR length before it will index a string at all.
|
|
340
|
+
const d = getDialect(dialect);
|
|
341
|
+
const sqlType = this._isBoolean
|
|
342
|
+
? d.booleanType
|
|
343
|
+
: this._stringLength !== undefined
|
|
344
|
+
? d.stringType(this._stringLength)
|
|
345
|
+
: this._sqlType;
|
|
335
346
|
const parts: string[] = [`${this.name} ${sqlType}`];
|
|
336
347
|
|
|
337
348
|
if (this._isPrimary) parts.push("PRIMARY KEY");
|