@ghom/orm 2.0.0 → 2.1.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/biome.json +7 -0
- package/dist/app/migration.d.ts +258 -0
- package/dist/app/migration.js +229 -0
- package/dist/app/orm.d.ts +21 -1
- package/dist/app/orm.js +51 -1
- package/dist/app/table.d.ts +84 -15
- package/dist/app/table.js +107 -10
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/package.json +4 -5
- package/readme.md +91 -19
- package/tests/orm.test.ts +253 -1
- package/tests/tables/d.ts +0 -4
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +2 -2
package/dist/app/table.d.ts
CHANGED
|
@@ -1,16 +1,25 @@
|
|
|
1
1
|
import { CachedQuery } from "@ghom/query";
|
|
2
2
|
import type { Knex } from "knex";
|
|
3
3
|
import { type ColumnDef, type InferColumns } from "./column.js";
|
|
4
|
+
import type { FinalTableType, TypedMigration, TypedMigrationSequence } from "./migration.js";
|
|
4
5
|
import type { ORM } from "./orm.js";
|
|
6
|
+
/**
|
|
7
|
+
* A migration value is a TypedMigration or TypedMigrationSequence.
|
|
8
|
+
* Use migrate.sequence() to combine multiple migrations.
|
|
9
|
+
*/
|
|
10
|
+
export type MigrationValue = TypedMigration<any, any> | TypedMigrationSequence<any>;
|
|
5
11
|
export interface MigrationData {
|
|
6
12
|
table: string;
|
|
7
|
-
version:
|
|
13
|
+
version: string;
|
|
8
14
|
}
|
|
9
15
|
/**
|
|
10
|
-
* Table options with typed columns.
|
|
11
|
-
* Type is automatically inferred from the column definitions.
|
|
16
|
+
* Table options with typed columns and optional typed migrations.
|
|
17
|
+
* Type is automatically inferred from the column definitions and migrations.
|
|
18
|
+
*
|
|
19
|
+
* @template Columns - Record of column definitions
|
|
20
|
+
* @template Migrations - Record of migration definitions (optional)
|
|
12
21
|
*/
|
|
13
|
-
export interface TableOptions<Columns extends Record<string, ColumnDef<any, any
|
|
22
|
+
export interface TableOptions<Columns extends Record<string, ColumnDef<any, any>>, Migrations extends Record<string, MigrationValue> = {}> {
|
|
14
23
|
name: string;
|
|
15
24
|
description?: string;
|
|
16
25
|
priority?: number;
|
|
@@ -19,10 +28,37 @@ export interface TableOptions<Columns extends Record<string, ColumnDef<any, any>
|
|
|
19
28
|
* Default is `Infinity`.
|
|
20
29
|
*/
|
|
21
30
|
caching?: number;
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
31
|
+
/**
|
|
32
|
+
* Database migrations to apply using typed migrations.
|
|
33
|
+
*
|
|
34
|
+
* Supports three key patterns:
|
|
35
|
+
* - **Numeric keys** (`"1"`, `"2"`): Sorted numerically
|
|
36
|
+
* - **Numeric-prefixed keys** (`"001_init"`, `"002_add"`): Sorted by prefix
|
|
37
|
+
* - **Pure string keys** (`"init"`, `"add"`): Uses insertion order
|
|
38
|
+
*
|
|
39
|
+
* @example
|
|
40
|
+
* // Single migration
|
|
41
|
+
* migrations: {
|
|
42
|
+
* "001_add_email": migrate.addColumn("email", col.string()),
|
|
43
|
+
* }
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* // Multiple migrations in sequence
|
|
47
|
+
* migrations: {
|
|
48
|
+
* "002_add_fields": migrate.sequence(
|
|
49
|
+
* migrate.addColumn("phone", col.string()),
|
|
50
|
+
* migrate.addColumn("address", col.string().nullable()),
|
|
51
|
+
* ),
|
|
52
|
+
* }
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Raw migration for advanced use cases
|
|
56
|
+
* migrations: {
|
|
57
|
+
* "003_custom": migrate.raw((builder) => builder.dropColumn("oldField")),
|
|
58
|
+
* }
|
|
59
|
+
*/
|
|
60
|
+
migrations?: Migrations;
|
|
61
|
+
then?: (this: Table<Columns, Migrations>, table: Table<Columns, Migrations>) => unknown;
|
|
26
62
|
/**
|
|
27
63
|
* Typed columns definition with automatic type inference.
|
|
28
64
|
*
|
|
@@ -37,7 +73,7 @@ export interface TableOptions<Columns extends Record<string, ColumnDef<any, any>
|
|
|
37
73
|
columns: (col: typeof import("./column.js").col) => Columns;
|
|
38
74
|
}
|
|
39
75
|
/**
|
|
40
|
-
* Represents a database table with typed columns.
|
|
76
|
+
* Represents a database table with typed columns and migrations.
|
|
41
77
|
*
|
|
42
78
|
* @example
|
|
43
79
|
* const userTable = new Table({
|
|
@@ -49,15 +85,36 @@ export interface TableOptions<Columns extends Record<string, ColumnDef<any, any>
|
|
|
49
85
|
* }),
|
|
50
86
|
* })
|
|
51
87
|
* // Type is automatically inferred as { id: number; username: string; age: number | null }
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* // With typed migrations
|
|
91
|
+
* const userTable = new Table({
|
|
92
|
+
* name: "user",
|
|
93
|
+
* columns: (col) => ({
|
|
94
|
+
* id: col.increments(),
|
|
95
|
+
* name: col.string(),
|
|
96
|
+
* }),
|
|
97
|
+
* migrations: {
|
|
98
|
+
* "001_rename": migrate.renameColumn("name", "username"),
|
|
99
|
+
* "002_add_email": migrate.addColumn("email", col.string()),
|
|
100
|
+
* },
|
|
101
|
+
* })
|
|
102
|
+
* // Type includes migration transforms: { id: number; username: string; email: string }
|
|
52
103
|
*/
|
|
53
|
-
export declare class Table<Columns extends Record<string, ColumnDef<any, any>> = Record<string, ColumnDef<any, any
|
|
54
|
-
readonly options: TableOptions<Columns>;
|
|
104
|
+
export declare class Table<Columns extends Record<string, ColumnDef<any, any>> = Record<string, ColumnDef<any, any>>, Migrations extends Record<string, MigrationValue> = {}> {
|
|
105
|
+
readonly options: TableOptions<Columns, Migrations>;
|
|
55
106
|
orm?: ORM;
|
|
56
|
-
_whereCache?: CachedQuery<[
|
|
107
|
+
_whereCache?: CachedQuery<[
|
|
108
|
+
cb: (query: Table<Columns, Migrations>["query"]) => unknown
|
|
109
|
+
], unknown>;
|
|
57
110
|
_countCache?: CachedQuery<[where: string | null], number>;
|
|
58
|
-
constructor(options: TableOptions<Columns>);
|
|
59
|
-
/**
|
|
60
|
-
|
|
111
|
+
constructor(options: TableOptions<Columns, Migrations>);
|
|
112
|
+
/**
|
|
113
|
+
* The inferred TypeScript type for rows of this table.
|
|
114
|
+
* Includes base columns and all migration type transforms.
|
|
115
|
+
* Supports both single migrations and arrays of migrations.
|
|
116
|
+
*/
|
|
117
|
+
readonly $type: FinalTableType<Columns, Migrations>;
|
|
61
118
|
private requireOrm;
|
|
62
119
|
get client(): Knex;
|
|
63
120
|
get query(): Knex.QueryBuilder<InferColumns<Columns>, {
|
|
@@ -82,5 +139,17 @@ export declare class Table<Columns extends Record<string, ColumnDef<any, any>> =
|
|
|
82
139
|
getColumnNames(): Promise<Array<keyof InferColumns<Columns> & string>>;
|
|
83
140
|
isEmpty(): Promise<boolean>;
|
|
84
141
|
make(orm: ORM): Promise<this>;
|
|
142
|
+
/**
|
|
143
|
+
* Get sorted migration keys based on their pattern.
|
|
144
|
+
* - Pure numeric keys ("1", "2", "10") are sorted numerically
|
|
145
|
+
* - Numeric-prefixed keys ("001_add", "010_rename") are sorted by prefix
|
|
146
|
+
* - Pure string keys ("add_email", "rename") use insertion order or alphabetical
|
|
147
|
+
*/
|
|
148
|
+
private getMigrationKeys;
|
|
149
|
+
/**
|
|
150
|
+
* Compare migration keys for determining if one is greater than another.
|
|
151
|
+
* Handles both numeric and string comparisons appropriately.
|
|
152
|
+
*/
|
|
153
|
+
private compareMigrationKeys;
|
|
85
154
|
private migrate;
|
|
86
155
|
}
|
package/dist/app/table.js
CHANGED
|
@@ -2,7 +2,7 @@ import { CachedQuery } from "@ghom/query";
|
|
|
2
2
|
import { buildColumnsSchema, col } from "./column.js";
|
|
3
3
|
import { styled } from "./util.js";
|
|
4
4
|
/**
|
|
5
|
-
* Represents a database table with typed columns.
|
|
5
|
+
* Represents a database table with typed columns and migrations.
|
|
6
6
|
*
|
|
7
7
|
* @example
|
|
8
8
|
* const userTable = new Table({
|
|
@@ -14,6 +14,21 @@ import { styled } from "./util.js";
|
|
|
14
14
|
* }),
|
|
15
15
|
* })
|
|
16
16
|
* // Type is automatically inferred as { id: number; username: string; age: number | null }
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* // With typed migrations
|
|
20
|
+
* const userTable = new Table({
|
|
21
|
+
* name: "user",
|
|
22
|
+
* columns: (col) => ({
|
|
23
|
+
* id: col.increments(),
|
|
24
|
+
* name: col.string(),
|
|
25
|
+
* }),
|
|
26
|
+
* migrations: {
|
|
27
|
+
* "001_rename": migrate.renameColumn("name", "username"),
|
|
28
|
+
* "002_add_email": migrate.addColumn("email", col.string()),
|
|
29
|
+
* },
|
|
30
|
+
* })
|
|
31
|
+
* // Type includes migration transforms: { id: number; username: string; email: string }
|
|
17
32
|
*/
|
|
18
33
|
export class Table {
|
|
19
34
|
options;
|
|
@@ -120,27 +135,109 @@ export class Table {
|
|
|
120
135
|
}
|
|
121
136
|
return this;
|
|
122
137
|
}
|
|
138
|
+
/**
|
|
139
|
+
* Get sorted migration keys based on their pattern.
|
|
140
|
+
* - Pure numeric keys ("1", "2", "10") are sorted numerically
|
|
141
|
+
* - Numeric-prefixed keys ("001_add", "010_rename") are sorted by prefix
|
|
142
|
+
* - Pure string keys ("add_email", "rename") use insertion order or alphabetical
|
|
143
|
+
*/
|
|
144
|
+
getMigrationKeys() {
|
|
145
|
+
const keys = Object.keys(this.options.migrations ?? {});
|
|
146
|
+
if (keys.length === 0)
|
|
147
|
+
return [];
|
|
148
|
+
// Detect key type patterns
|
|
149
|
+
const allPureNumeric = keys.every((k) => /^\d+$/.test(k));
|
|
150
|
+
const allNumericPrefix = keys.every((k) => /^\d+/.test(k));
|
|
151
|
+
const allPureString = keys.every((k) => !/^\d/.test(k));
|
|
152
|
+
// Validate: no mixing allowed
|
|
153
|
+
if (!allPureNumeric && !allNumericPrefix && !allPureString) {
|
|
154
|
+
// Categorize keys for helpful error message
|
|
155
|
+
const numericKeys = keys.filter((k) => /^\d+$/.test(k));
|
|
156
|
+
const prefixedKeys = keys.filter((k) => /^\d+[_\-a-zA-Z]/.test(k));
|
|
157
|
+
const stringKeys = keys.filter((k) => !/^\d/.test(k));
|
|
158
|
+
const parts = [];
|
|
159
|
+
if (numericKeys.length > 0) {
|
|
160
|
+
parts.push(`numeric keys: ${numericKeys.map((k) => `"${k}"`).join(", ")}`);
|
|
161
|
+
}
|
|
162
|
+
if (prefixedKeys.length > 0) {
|
|
163
|
+
parts.push(`prefixed keys: ${prefixedKeys.map((k) => `"${k}"`).join(", ")}`);
|
|
164
|
+
}
|
|
165
|
+
if (stringKeys.length > 0) {
|
|
166
|
+
parts.push(`string keys: ${stringKeys.map((k) => `"${k}"`).join(", ")}`);
|
|
167
|
+
}
|
|
168
|
+
throw new Error(`Table "${this.options.name}": Migration keys use mixed patterns which prevents reliable ordering.\n\n` +
|
|
169
|
+
`Found: ${parts.join(" AND ")}\n\n` +
|
|
170
|
+
`Choose ONE pattern for all keys:\n` +
|
|
171
|
+
` - Pure numbers: "1", "2", "10" (sorted numerically)\n` +
|
|
172
|
+
` - Prefixed strings: "001_init", "002_add" (sorted by prefix)\n` +
|
|
173
|
+
` - Pure strings: "init", "add_email" (insertion order)`);
|
|
174
|
+
}
|
|
175
|
+
if (allPureNumeric) {
|
|
176
|
+
// Sort purely numeric keys numerically: 1, 2, 10, 20
|
|
177
|
+
return keys.sort((a, b) => Number(a) - Number(b));
|
|
178
|
+
}
|
|
179
|
+
if (allNumericPrefix) {
|
|
180
|
+
// Sort by numeric prefix: "001_x" < "002_y" < "010_z"
|
|
181
|
+
const getNumericPrefix = (key) => parseInt(key.match(/^(\d+)/)?.[1] ?? "0", 10);
|
|
182
|
+
return keys.sort((a, b) => getNumericPrefix(a) - getNumericPrefix(b));
|
|
183
|
+
}
|
|
184
|
+
// Pure strings: alphabetical order OR insertion order
|
|
185
|
+
// Get ORM config if available (and not false for unconnected ORM)
|
|
186
|
+
const ormConfig = this.orm?.config === false ? undefined : this.orm?.config;
|
|
187
|
+
if (ormConfig?.migrations?.alphabeticalOrder) {
|
|
188
|
+
return keys.sort((a, b) => a.localeCompare(b));
|
|
189
|
+
}
|
|
190
|
+
// Warning for insertion order (Git merge risks)
|
|
191
|
+
if (keys.length > 1 && ormConfig) {
|
|
192
|
+
ormConfig.logger?.warn?.(`Table "${this.options.name}": Using insertion order for string migration keys. ` +
|
|
193
|
+
`This may cause issues with Git merges. Consider using numeric prefixes (e.g., "001_init").`);
|
|
194
|
+
}
|
|
195
|
+
return keys; // Insertion order (ES2015+)
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Compare migration keys for determining if one is greater than another.
|
|
199
|
+
* Handles both numeric and string comparisons appropriately.
|
|
200
|
+
*/
|
|
201
|
+
compareMigrationKeys(a, b) {
|
|
202
|
+
const aIsNumeric = /^\d+$/.test(a);
|
|
203
|
+
const bIsNumeric = /^\d+$/.test(b);
|
|
204
|
+
if (aIsNumeric && bIsNumeric) {
|
|
205
|
+
return Number(a) - Number(b);
|
|
206
|
+
}
|
|
207
|
+
const aHasPrefix = /^\d+/.test(a);
|
|
208
|
+
const bHasPrefix = /^\d+/.test(b);
|
|
209
|
+
if (aHasPrefix && bHasPrefix) {
|
|
210
|
+
const aPrefix = parseInt(a.match(/^(\d+)/)?.[1] ?? "0", 10);
|
|
211
|
+
const bPrefix = parseInt(b.match(/^(\d+)/)?.[1] ?? "0", 10);
|
|
212
|
+
return aPrefix - bPrefix;
|
|
213
|
+
}
|
|
214
|
+
return a.localeCompare(b);
|
|
215
|
+
}
|
|
123
216
|
async migrate() {
|
|
124
217
|
if (!this.options.migrations)
|
|
125
218
|
return false;
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
219
|
+
const sortedKeys = this.getMigrationKeys();
|
|
220
|
+
if (sortedKeys.length === 0)
|
|
221
|
+
return false;
|
|
222
|
+
const migrations = this.options.migrations;
|
|
129
223
|
const fromDatabase = await this.client("migration")
|
|
130
224
|
.where("table", this.options.name)
|
|
131
225
|
.first();
|
|
132
226
|
const data = fromDatabase || {
|
|
133
227
|
table: this.options.name,
|
|
134
|
-
version:
|
|
228
|
+
version: "",
|
|
135
229
|
};
|
|
136
230
|
const baseVersion = data.version;
|
|
137
|
-
for (const
|
|
231
|
+
for (const key of sortedKeys) {
|
|
232
|
+
// Skip migrations that have already been applied
|
|
233
|
+
if (data.version !== "" && this.compareMigrationKeys(key, data.version) <= 0) {
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
const migration = migrations[key];
|
|
138
237
|
await this.client.schema.alterTable(this.options.name, (builder) => {
|
|
139
|
-
|
|
140
|
-
return;
|
|
141
|
-
migration(builder);
|
|
142
|
-
data.version = version;
|
|
238
|
+
migration.apply(builder);
|
|
143
239
|
});
|
|
240
|
+
data.version = key;
|
|
144
241
|
}
|
|
145
242
|
await this.client("migration").insert(data).onConflict("table").merge();
|
|
146
243
|
return baseVersion === data.version ? false : data.version;
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ghom/orm",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,10 +8,9 @@
|
|
|
8
8
|
"description": "TypeScript KnexJS ORM & handler",
|
|
9
9
|
"homepage": "https://github.com/GhomKrosmonaute/orm",
|
|
10
10
|
"scripts": {
|
|
11
|
-
"format": "biome format --write
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"build": "rimraf dist && tsc",
|
|
11
|
+
"format": "biome format --write .",
|
|
12
|
+
"check": "biome check --write . && tsc --noEmit",
|
|
13
|
+
"build": "rimraf dist && tsc -p tsconfig.build.json",
|
|
15
14
|
"test": "bun test",
|
|
16
15
|
"prepublishOnly": "npm run check && npm run build && npm test"
|
|
17
16
|
},
|
package/readme.md
CHANGED
|
@@ -62,46 +62,117 @@ orm.raw("SELECT 1") // throws Error
|
|
|
62
62
|
|
|
63
63
|
## Add tables
|
|
64
64
|
|
|
65
|
-
The tables are automatically loaded from the `
|
|
65
|
+
The tables are automatically loaded from the `tableLocation` directory. Types are automatically inferred from the column definitions.
|
|
66
66
|
|
|
67
67
|
```typescript
|
|
68
68
|
// tables/user.ts
|
|
69
69
|
|
|
70
|
-
import { Table } from "@ghom/orm"
|
|
70
|
+
import { Table, col } from "@ghom/orm"
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
username: string
|
|
74
|
-
password: string
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
export default new Table<User>({
|
|
72
|
+
export default new Table({
|
|
78
73
|
name: "user",
|
|
79
74
|
|
|
80
75
|
// the higher the priority, the earlier the table is compiled
|
|
81
76
|
priority: 0,
|
|
82
77
|
|
|
83
|
-
//
|
|
78
|
+
// typed columns definition with automatic type inference
|
|
79
|
+
columns: (col) => ({
|
|
80
|
+
id: col.increments(),
|
|
81
|
+
username: col.string().unique(),
|
|
82
|
+
password: col.string(),
|
|
83
|
+
age: col.integer().nullable(),
|
|
84
|
+
role: col.enum(["admin", "user"]).defaultTo("user"),
|
|
85
|
+
}),
|
|
86
|
+
|
|
87
|
+
// migrations are executed in order based on key pattern (see Migration Keys section)
|
|
84
88
|
migrations: {
|
|
85
|
-
1: (table) => {
|
|
89
|
+
"1": (table) => {
|
|
86
90
|
table.renameColumn("name", "username")
|
|
87
91
|
}
|
|
88
92
|
},
|
|
89
93
|
|
|
90
|
-
// the
|
|
91
|
-
setup: (table) => {
|
|
92
|
-
table.string("name").notNullable()
|
|
93
|
-
table.string("password").notNullable()
|
|
94
|
-
},
|
|
95
|
-
|
|
96
|
-
// the then is executed after the table is created and the migrations are runned
|
|
94
|
+
// then is executed after the table is created and the migrations are run (only if table is empty)
|
|
97
95
|
then: ({ query }) => {
|
|
98
|
-
query.insert({ username: "admin", password: "admin" })
|
|
96
|
+
query.insert({ username: "admin", password: "admin", role: "admin" })
|
|
99
97
|
},
|
|
100
98
|
|
|
101
99
|
caching: 10 * 60 * 1000 // The table cache. Default to the ORM cache or Infinity
|
|
102
100
|
})
|
|
101
|
+
|
|
102
|
+
// Type is automatically inferred:
|
|
103
|
+
// { id: number; username: string; password: string; age: number | null; role: "admin" | "user" }
|
|
104
|
+
type User = typeof userTable.$type
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Typed Migrations
|
|
108
|
+
|
|
109
|
+
You can also use typed migrations that automatically update the TypeScript type:
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { Table, col, migrate } from "@ghom/orm"
|
|
113
|
+
|
|
114
|
+
export default new Table({
|
|
115
|
+
name: "user",
|
|
116
|
+
columns: (col) => ({
|
|
117
|
+
id: col.increments(),
|
|
118
|
+
name: col.string(), // will be renamed to username
|
|
119
|
+
}),
|
|
120
|
+
migrations: {
|
|
121
|
+
"001_rename_name": migrate.renameColumn("name", "username"),
|
|
122
|
+
"002_add_email": migrate.addColumn("email", col.string()),
|
|
123
|
+
"003_add_age": migrate.addColumn("age", col.integer().nullable()),
|
|
124
|
+
},
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
// Final type: { id: number; username: string; email: string; age: number | null }
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Available migration helpers:
|
|
131
|
+
- `migrate.addColumn(name, columnDef)` - Add a new column
|
|
132
|
+
- `migrate.dropColumn(name)` - Remove a column
|
|
133
|
+
- `migrate.renameColumn(oldName, newName)` - Rename a column
|
|
134
|
+
- `migrate.alterColumn(name, newColumnDef)` - Change column type/constraints
|
|
135
|
+
- `migrate.addIndex(columns, name?)` - Add an index
|
|
136
|
+
- `migrate.dropIndex(name)` - Remove an index
|
|
137
|
+
- `migrate.addUnique(columns, name?)` - Add a unique constraint
|
|
138
|
+
- `migrate.dropUnique(name)` - Remove a unique constraint
|
|
139
|
+
- `migrate.raw(callback)` - Custom migration callback
|
|
140
|
+
|
|
141
|
+
## Migration Keys
|
|
142
|
+
|
|
143
|
+
The ORM supports three patterns for migration keys:
|
|
144
|
+
|
|
145
|
+
1. **Numeric keys** (`"1"`, `"2"`, `"10"`): Sorted numerically
|
|
146
|
+
2. **Numeric-prefixed keys** (`"001_init"`, `"002_add_users"`, `"010_fix"`): Sorted by numeric prefix
|
|
147
|
+
3. **Pure string keys** (`"init"`, `"add_users"`): Uses insertion order (ES2015+)
|
|
148
|
+
|
|
149
|
+
> **Warning**: Mixing key patterns is not allowed and will throw an error at runtime.
|
|
150
|
+
|
|
151
|
+
### Migration Configuration
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
const orm = new ORM({
|
|
155
|
+
tableLocation: "./tables",
|
|
156
|
+
migrations: {
|
|
157
|
+
/**
|
|
158
|
+
* NOT RECOMMENDED
|
|
159
|
+
* Force alphabetical sorting for string migration keys.
|
|
160
|
+
*
|
|
161
|
+
* If your keys start with numbers (e.g., "001_init"),
|
|
162
|
+
* they are automatically sorted by those numbers,
|
|
163
|
+
* not alphabetically.
|
|
164
|
+
*/
|
|
165
|
+
alphabeticalOrder: false // default
|
|
166
|
+
}
|
|
167
|
+
})
|
|
103
168
|
```
|
|
104
169
|
|
|
170
|
+
### ES2015+ Requirement
|
|
171
|
+
|
|
172
|
+
This ORM requires ES2015+ for guaranteed object key insertion order. Node.js 6+ and all modern browsers are supported.
|
|
173
|
+
|
|
174
|
+
The ORM performs a runtime check on initialization and will throw an error if the environment doesn't support ES2015+ key ordering.
|
|
175
|
+
|
|
105
176
|
## Launch a query
|
|
106
177
|
|
|
107
178
|
For more information about the query builder, see [knexjs.org](https://knexjs.org/).
|
|
@@ -192,8 +263,9 @@ The cache of the `<ORM>.cache.raw` method is automatically invalidated when the
|
|
|
192
263
|
|
|
193
264
|
- [x] Add timed caching system
|
|
194
265
|
- [x] Add backup option
|
|
266
|
+
- [x] Auto typings for tables from the column definitions
|
|
267
|
+
- [x] Typed migrations with automatic type inference
|
|
195
268
|
- [ ] Dependency management between tables
|
|
196
|
-
- [ ] Auto typings for tables from the column definitions
|
|
197
269
|
- [ ] Add specific methods for relations and joins
|
|
198
270
|
- [ ] Add admin panel
|
|
199
271
|
- [ ] Make possible to switch the data between all possible clients (pg, mysql, sqlite3)
|