@ghom/orm 1.10.0 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/test.yml +9 -9
- package/biome.json +52 -0
- package/bun.lock +488 -0
- package/dist/app/backup.d.ts +3 -3
- package/dist/app/backup.js +10 -11
- package/dist/app/column.d.ts +225 -0
- package/dist/app/column.js +342 -0
- package/dist/app/migration.d.ts +171 -0
- package/dist/app/migration.js +198 -0
- package/dist/app/orm.d.ts +26 -5
- package/dist/app/orm.js +69 -15
- package/dist/app/table.d.ts +112 -22
- package/dist/app/table.js +130 -27
- package/dist/app/util.d.ts +1 -1
- package/dist/app/util.js +4 -4
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/package.json +42 -47
- package/readme.md +91 -19
- package/tests/orm.test.ts +431 -0
- package/tests/tables/a.ts +16 -0
- package/tests/tables/b.ts +16 -0
- package/tests/tables/c.ts +14 -0
- package/tests/tables/d.ts +28 -0
- package/tsconfig.json +1 -0
- package/tests/tables/a.js +0 -25
- package/tests/tables/b.js +0 -30
- package/tests/tables/c.js +0 -17
- package/tests/test.js +0 -229
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)
|
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test"
|
|
2
|
+
import fs from "fs"
|
|
3
|
+
import path from "path"
|
|
4
|
+
import { rimraf } from "rimraf"
|
|
5
|
+
|
|
6
|
+
import { col, migrate, ORM, type ORMConfig, Table } from "../src"
|
|
7
|
+
|
|
8
|
+
import a from "./tables/a"
|
|
9
|
+
import b from "./tables/b"
|
|
10
|
+
import c from "./tables/c"
|
|
11
|
+
import d from "./tables/d"
|
|
12
|
+
|
|
13
|
+
describe("typed columns", () => {
|
|
14
|
+
test("new Table() infers types correctly", () => {
|
|
15
|
+
const userTable = new Table({
|
|
16
|
+
name: "test_user",
|
|
17
|
+
columns: (col) => ({
|
|
18
|
+
id: col.increments(),
|
|
19
|
+
username: col.string().unique(),
|
|
20
|
+
age: col.integer().nullable(),
|
|
21
|
+
role: col.enum(["admin", "user"] as const),
|
|
22
|
+
isActive: col.boolean().defaultTo(true),
|
|
23
|
+
}),
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
expect(userTable).toBeInstanceOf(Table)
|
|
27
|
+
expect(userTable.options.name).toBe("test_user")
|
|
28
|
+
expect("columns" in userTable.options).toBe(true)
|
|
29
|
+
|
|
30
|
+
// Type inference check - this line would fail at compile time if types weren't inferred
|
|
31
|
+
type ExpectedType = typeof userTable.$type
|
|
32
|
+
const _typeCheck: ExpectedType = {
|
|
33
|
+
id: 1,
|
|
34
|
+
username: "test",
|
|
35
|
+
age: null,
|
|
36
|
+
role: "admin",
|
|
37
|
+
isActive: true,
|
|
38
|
+
}
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test("col factory creates column definitions", () => {
|
|
42
|
+
const idCol = col.increments()
|
|
43
|
+
const stringCol = col.string()
|
|
44
|
+
const nullableInt = col.integer().nullable()
|
|
45
|
+
const enumCol = col.enum(["a", "b", "c"] as const)
|
|
46
|
+
|
|
47
|
+
expect(idCol).toBeDefined()
|
|
48
|
+
expect(stringCol).toBeDefined()
|
|
49
|
+
expect(nullableInt._isNullable).toBe(true)
|
|
50
|
+
expect(enumCol).toBeDefined()
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
test("typed table d has correct options", () => {
|
|
54
|
+
expect(d).toBeInstanceOf(Table)
|
|
55
|
+
expect(d.options.name).toBe("d")
|
|
56
|
+
expect("columns" in d.options).toBe(true)
|
|
57
|
+
})
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
describe("typed migrations", () => {
|
|
61
|
+
test("migrate.addColumn creates TypedMigration", () => {
|
|
62
|
+
const migration = migrate.addColumn("email", col.string())
|
|
63
|
+
|
|
64
|
+
expect(migration).toBeDefined()
|
|
65
|
+
expect(migration.apply).toBeInstanceOf(Function)
|
|
66
|
+
expect("_from" in migration).toBe(true)
|
|
67
|
+
expect("_to" in migration).toBe(true)
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
test("migrate.dropColumn creates TypedMigration", () => {
|
|
71
|
+
const migration = migrate.dropColumn("oldField")
|
|
72
|
+
|
|
73
|
+
expect(migration).toBeDefined()
|
|
74
|
+
expect(migration.apply).toBeInstanceOf(Function)
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
test("migrate.renameColumn creates TypedMigration", () => {
|
|
78
|
+
const migration = migrate.renameColumn("name", "username")
|
|
79
|
+
|
|
80
|
+
expect(migration).toBeDefined()
|
|
81
|
+
expect(migration.apply).toBeInstanceOf(Function)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test("migrate.alterColumn creates TypedMigration", () => {
|
|
85
|
+
const migration = migrate.alterColumn("age", col.integer().nullable())
|
|
86
|
+
|
|
87
|
+
expect(migration).toBeDefined()
|
|
88
|
+
expect(migration.apply).toBeInstanceOf(Function)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
test("migrate.addIndex creates TypedMigration", () => {
|
|
92
|
+
const migration = migrate.addIndex(["email"], "idx_email")
|
|
93
|
+
|
|
94
|
+
expect(migration).toBeDefined()
|
|
95
|
+
expect(migration.apply).toBeInstanceOf(Function)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test("migrate.addUnique creates TypedMigration", () => {
|
|
99
|
+
const migration = migrate.addUnique(["email"], "uniq_email")
|
|
100
|
+
|
|
101
|
+
expect(migration).toBeDefined()
|
|
102
|
+
expect(migration.apply).toBeInstanceOf(Function)
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test("migrate.raw creates TypedMigration", () => {
|
|
106
|
+
const migration = migrate.raw((builder) => {
|
|
107
|
+
builder.dropColumn("temp")
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
expect(migration).toBeDefined()
|
|
111
|
+
expect(migration.apply).toBeInstanceOf(Function)
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
test("Table with typed migrations has correct options", () => {
|
|
115
|
+
const userTable = new Table({
|
|
116
|
+
name: "test_typed_migrations",
|
|
117
|
+
columns: (col) => ({
|
|
118
|
+
id: col.increments(),
|
|
119
|
+
name: col.string(),
|
|
120
|
+
}),
|
|
121
|
+
migrations: {
|
|
122
|
+
"001_add_email": migrate.addColumn("email", col.string()),
|
|
123
|
+
"002_add_age": migrate.addColumn("age", col.integer().nullable()),
|
|
124
|
+
"003_rename_name": migrate.renameColumn("name", "username"),
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
expect(userTable).toBeInstanceOf(Table)
|
|
129
|
+
expect(userTable.options.migrations).toBeDefined()
|
|
130
|
+
expect(Object.keys(userTable.options.migrations!).length).toBe(3)
|
|
131
|
+
|
|
132
|
+
// Type inference check - final type includes base columns + migrations
|
|
133
|
+
// "name" is removed by renameColumn, "username" is added
|
|
134
|
+
type ExpectedType = typeof userTable.$type
|
|
135
|
+
const _typeCheck: ExpectedType = {
|
|
136
|
+
id: 1,
|
|
137
|
+
username: "test", // renamed from "name"
|
|
138
|
+
// @ts-expect-error - name is removed by renameColumn
|
|
139
|
+
name: "test",
|
|
140
|
+
email: "test@example.com",
|
|
141
|
+
age: null,
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe("migration key patterns", () => {
|
|
147
|
+
test("Table accepts pure numeric keys", () => {
|
|
148
|
+
const table = new Table({
|
|
149
|
+
name: "test_numeric_keys",
|
|
150
|
+
columns: (col) => ({
|
|
151
|
+
id: col.increments(),
|
|
152
|
+
}),
|
|
153
|
+
migrations: {
|
|
154
|
+
"1": (_builder) => {},
|
|
155
|
+
"2": (_builder) => {},
|
|
156
|
+
"10": (_builder) => {},
|
|
157
|
+
},
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
expect(table.options.migrations).toBeDefined()
|
|
161
|
+
expect(Object.keys(table.options.migrations!)).toEqual(["1", "2", "10"])
|
|
162
|
+
})
|
|
163
|
+
|
|
164
|
+
test("Table accepts numeric-prefixed keys", () => {
|
|
165
|
+
const table = new Table({
|
|
166
|
+
name: "test_prefixed_keys",
|
|
167
|
+
columns: (col) => ({
|
|
168
|
+
id: col.increments(),
|
|
169
|
+
}),
|
|
170
|
+
migrations: {
|
|
171
|
+
"001_init": (_builder) => {},
|
|
172
|
+
"002_add_column": (_builder) => {},
|
|
173
|
+
"010_fix": (_builder) => {},
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
expect(table.options.migrations).toBeDefined()
|
|
178
|
+
expect(Object.keys(table.options.migrations!)).toEqual([
|
|
179
|
+
"001_init",
|
|
180
|
+
"002_add_column",
|
|
181
|
+
"010_fix",
|
|
182
|
+
])
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
test("Table accepts pure string keys", () => {
|
|
186
|
+
const table = new Table({
|
|
187
|
+
name: "test_string_keys",
|
|
188
|
+
columns: (col) => ({
|
|
189
|
+
id: col.increments(),
|
|
190
|
+
}),
|
|
191
|
+
migrations: {
|
|
192
|
+
init: (_builder) => {},
|
|
193
|
+
add_column: (_builder) => {},
|
|
194
|
+
fix: (_builder) => {},
|
|
195
|
+
},
|
|
196
|
+
})
|
|
197
|
+
|
|
198
|
+
expect(table.options.migrations).toBeDefined()
|
|
199
|
+
expect(Object.keys(table.options.migrations!)).toEqual(["init", "add_column", "fix"])
|
|
200
|
+
})
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
describe("unconnected ORM", () => {
|
|
204
|
+
test("can be initialized with false", () => {
|
|
205
|
+
const unconnectedOrm = new ORM(false)
|
|
206
|
+
|
|
207
|
+
expect(unconnectedOrm).toBeInstanceOf(ORM)
|
|
208
|
+
expect(unconnectedOrm.isConnected).toBe(false)
|
|
209
|
+
expect(unconnectedOrm.config).toBe(false)
|
|
210
|
+
expect(unconnectedOrm.handler).toBeUndefined()
|
|
211
|
+
expect(unconnectedOrm.cachedTables).toEqual([])
|
|
212
|
+
expect(unconnectedOrm.cachedTableNames).toEqual([])
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
test("throws when calling methods requiring client", async () => {
|
|
216
|
+
const unconnectedOrm = new ORM(false)
|
|
217
|
+
|
|
218
|
+
expect(() => unconnectedOrm.client).toThrow()
|
|
219
|
+
expect(unconnectedOrm.init()).rejects.toThrow()
|
|
220
|
+
expect(unconnectedOrm.hasTable("test")).rejects.toThrow()
|
|
221
|
+
expect(() => unconnectedOrm.raw("SELECT 1")).toThrow()
|
|
222
|
+
expect(unconnectedOrm.createBackup()).rejects.toThrow()
|
|
223
|
+
expect(unconnectedOrm.restoreBackup()).rejects.toThrow()
|
|
224
|
+
})
|
|
225
|
+
})
|
|
226
|
+
|
|
227
|
+
const orm = new ORM({
|
|
228
|
+
tableLocation: path.join(process.cwd(), "tests", "tables"),
|
|
229
|
+
backups: {
|
|
230
|
+
location: path.join(process.cwd(), "backups"),
|
|
231
|
+
},
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
beforeAll(async () => {
|
|
235
|
+
await orm.init()
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
describe("table management", () => {
|
|
239
|
+
test("tables created", async () => {
|
|
240
|
+
expect(await orm.hasTable("migration")).toBeTruthy()
|
|
241
|
+
expect(await orm.hasTable("a")).toBeTruthy()
|
|
242
|
+
expect(await orm.hasTable("b")).toBeTruthy()
|
|
243
|
+
expect(await orm.hasTable("c")).toBeTruthy()
|
|
244
|
+
expect(await orm.hasTable("d")).toBeTruthy()
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
test("table cached", () => {
|
|
248
|
+
expect(orm.hasCachedTable("migration")).toBeTruthy()
|
|
249
|
+
expect(orm.hasCachedTable("a")).toBeTruthy()
|
|
250
|
+
expect(orm.hasCachedTable("b")).toBeTruthy()
|
|
251
|
+
expect(orm.hasCachedTable("c")).toBeTruthy()
|
|
252
|
+
expect(orm.hasCachedTable("d")).toBeTruthy()
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
test("table migrations ran", async () => {
|
|
256
|
+
expect(await b.hasColumn("c_id")).toBeTruthy()
|
|
257
|
+
})
|
|
258
|
+
|
|
259
|
+
test("table then ran", async () => {
|
|
260
|
+
const rows = await a.query.select()
|
|
261
|
+
expect(rows.length).toBe(1)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
test("typed table d was created with correct columns", async () => {
|
|
265
|
+
expect(await d.hasColumn("id")).toBeTruthy()
|
|
266
|
+
expect(await d.hasColumn("name")).toBeTruthy()
|
|
267
|
+
expect(await d.hasColumn("email")).toBeTruthy()
|
|
268
|
+
expect(await d.hasColumn("age")).toBeTruthy()
|
|
269
|
+
expect(await d.hasColumn("role")).toBeTruthy()
|
|
270
|
+
expect(await d.hasColumn("isActive")).toBeTruthy()
|
|
271
|
+
expect(await d.hasColumn("metadata")).toBeTruthy()
|
|
272
|
+
expect(await d.hasColumn("createdAt")).toBeTruthy()
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
test("typed table d then ran", async () => {
|
|
276
|
+
const rows = await d.query.select()
|
|
277
|
+
expect(rows.length).toBe(1)
|
|
278
|
+
expect(rows[0].name).toBe("Test User")
|
|
279
|
+
expect(rows[0].role).toBe("admin")
|
|
280
|
+
})
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
describe("table column types", () => {
|
|
284
|
+
test("increments", async () => {
|
|
285
|
+
const info = await orm.client!("a").columnInfo("id")
|
|
286
|
+
expect(info.type).toMatch(/^int/)
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
test("integer", async () => {
|
|
290
|
+
const info = await orm.client!("a").columnInfo("b_id")
|
|
291
|
+
expect(info.type).toMatch(/^int/)
|
|
292
|
+
})
|
|
293
|
+
|
|
294
|
+
test("typed table d column types", async () => {
|
|
295
|
+
const nameInfo = await orm.client!("d").columnInfo("name")
|
|
296
|
+
expect(nameInfo.type).toMatch(/varchar|text|character/i)
|
|
297
|
+
|
|
298
|
+
const ageInfo = await orm.client!("d").columnInfo("age")
|
|
299
|
+
expect(ageInfo.type).toMatch(/^int/)
|
|
300
|
+
expect(ageInfo.nullable).toBe(true)
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
describe("database extraction", () => {
|
|
305
|
+
test("create backup", async () => {
|
|
306
|
+
await orm.createBackup()
|
|
307
|
+
|
|
308
|
+
const config = orm.config as ORMConfig
|
|
309
|
+
expect(fs.existsSync(path.join(config.backups!.location!, "a_chunk_0.csv"))).toBeTruthy()
|
|
310
|
+
expect(fs.existsSync(path.join(config.backups!.location!, "b_chunk_0.csv"))).toBeTruthy()
|
|
311
|
+
expect(fs.existsSync(path.join(config.backups!.location!, "c_chunk_0.csv"))).toBeTruthy()
|
|
312
|
+
expect(fs.existsSync(path.join(config.backups!.location!, "d_chunk_0.csv"))).toBeTruthy()
|
|
313
|
+
})
|
|
314
|
+
|
|
315
|
+
test("cascade delete", async () => {
|
|
316
|
+
expect(await a.isEmpty()).toBeFalsy()
|
|
317
|
+
await c.query.del()
|
|
318
|
+
expect(await a.isEmpty()).toBeTruthy()
|
|
319
|
+
})
|
|
320
|
+
|
|
321
|
+
test("restore backup", async () => {
|
|
322
|
+
await orm.restoreBackup()
|
|
323
|
+
|
|
324
|
+
expect(await a.isEmpty()).toBeFalsy()
|
|
325
|
+
expect(await b.isEmpty()).toBeFalsy()
|
|
326
|
+
expect(await c.isEmpty()).toBeFalsy()
|
|
327
|
+
expect(await d.isEmpty()).toBeFalsy()
|
|
328
|
+
})
|
|
329
|
+
|
|
330
|
+
afterAll(async () => {
|
|
331
|
+
const config = orm.config as ORMConfig
|
|
332
|
+
await rimraf(config.backups!.location!)
|
|
333
|
+
})
|
|
334
|
+
})
|
|
335
|
+
|
|
336
|
+
describe("table getters", () => {
|
|
337
|
+
test("table info", async () => {
|
|
338
|
+
expect(await a.getColumnNames()).toContain("id")
|
|
339
|
+
expect(await a.getColumnNames()).toContain("b_id")
|
|
340
|
+
|
|
341
|
+
expect(await b.getColumnNames()).toContain("id")
|
|
342
|
+
expect(await b.getColumnNames()).toContain("c_id")
|
|
343
|
+
|
|
344
|
+
expect(await c.getColumnNames()).toContain("id")
|
|
345
|
+
|
|
346
|
+
expect(await d.getColumnNames()).toContain("id")
|
|
347
|
+
expect(await d.getColumnNames()).toContain("name")
|
|
348
|
+
expect(await d.getColumnNames()).toContain("role")
|
|
349
|
+
})
|
|
350
|
+
|
|
351
|
+
test("table names", () => {
|
|
352
|
+
expect(orm.cachedTableNames).toContain("a")
|
|
353
|
+
expect(orm.cachedTableNames).toContain("b")
|
|
354
|
+
expect(orm.cachedTableNames).toContain("c")
|
|
355
|
+
expect(orm.cachedTableNames).toContain("d")
|
|
356
|
+
})
|
|
357
|
+
})
|
|
358
|
+
|
|
359
|
+
describe("data caching", () => {
|
|
360
|
+
beforeAll(async () => {
|
|
361
|
+
await c.query.del()
|
|
362
|
+
await c.query.insert([{ id: 1 }, { id: 2 }, { id: 3 }])
|
|
363
|
+
await b.query.insert([
|
|
364
|
+
{ id: 1, c_id: 1 },
|
|
365
|
+
{ id: 2, c_id: 2 },
|
|
366
|
+
{ id: 3, c_id: 3 },
|
|
367
|
+
])
|
|
368
|
+
await a.query.insert([
|
|
369
|
+
{ id: 1, b_id: 1 },
|
|
370
|
+
{ id: 2, b_id: 2 },
|
|
371
|
+
{ id: 3, b_id: 3 },
|
|
372
|
+
])
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
test("select with caching", async () => {
|
|
376
|
+
const rows = await a.cache.get("all a", (query) => {
|
|
377
|
+
return query.select("*")
|
|
378
|
+
})
|
|
379
|
+
|
|
380
|
+
expect(rows.length).toBe(3)
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
test("insert with caching", async () => {
|
|
384
|
+
await a.cache.set((query) => {
|
|
385
|
+
return query.insert({ id: 4, b_id: 1 })
|
|
386
|
+
})
|
|
387
|
+
|
|
388
|
+
expect(await a.cache.count()).toBe(4)
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
test("update with caching", async () => {
|
|
392
|
+
await a.cache.set((query) => {
|
|
393
|
+
return query.update({ b_id: 3 }).where({ id: 1 })
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
const row = await a.cache.get("a 1", (query) => {
|
|
397
|
+
return query.select("b_id").where({ id: 1 }).first()
|
|
398
|
+
})
|
|
399
|
+
|
|
400
|
+
expect(row!.b_id).toBe(3)
|
|
401
|
+
})
|
|
402
|
+
|
|
403
|
+
test("delete with caching", async () => {
|
|
404
|
+
await a.cache.set((query) => {
|
|
405
|
+
return query.delete().where({ id: 1 })
|
|
406
|
+
})
|
|
407
|
+
|
|
408
|
+
expect(await a.cache.count()).toBe(3)
|
|
409
|
+
})
|
|
410
|
+
|
|
411
|
+
test("cache invalidation", async () => {
|
|
412
|
+
expect(await a.cache.count()).toBe(3)
|
|
413
|
+
|
|
414
|
+
await a.query.insert({ id: 5, b_id: 1 })
|
|
415
|
+
|
|
416
|
+
expect(await a.cache.count()).toBe(3)
|
|
417
|
+
|
|
418
|
+
orm.cache.invalidate()
|
|
419
|
+
|
|
420
|
+
expect(await a.cache.count()).toBe(4)
|
|
421
|
+
})
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
afterAll(async () => {
|
|
425
|
+
await orm.client!.schema.dropTable("migration")
|
|
426
|
+
await orm.client!.schema.dropTable("a")
|
|
427
|
+
await orm.client!.schema.dropTable("b")
|
|
428
|
+
await orm.client!.schema.dropTable("c")
|
|
429
|
+
await orm.client!.schema.dropTable("d")
|
|
430
|
+
await orm.client!.destroy()
|
|
431
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Table } from "../../src"
|
|
2
|
+
|
|
3
|
+
export default new Table({
|
|
4
|
+
name: "a",
|
|
5
|
+
priority: 0,
|
|
6
|
+
columns: (col) => ({
|
|
7
|
+
id: col.increments(),
|
|
8
|
+
b_id: col.integer().unsigned().references("id").inTable("b").onDelete("CASCADE"),
|
|
9
|
+
}),
|
|
10
|
+
async then({ query }) {
|
|
11
|
+
await query.insert({
|
|
12
|
+
id: 1,
|
|
13
|
+
b_id: 1,
|
|
14
|
+
})
|
|
15
|
+
},
|
|
16
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Table } from "../../src"
|
|
2
|
+
|
|
3
|
+
export default new Table({
|
|
4
|
+
name: "b",
|
|
5
|
+
priority: 1,
|
|
6
|
+
columns: (col) => ({
|
|
7
|
+
id: col.increments(),
|
|
8
|
+
c_id: col.integer().unsigned().references("id").inTable("c").onDelete("CASCADE"),
|
|
9
|
+
}),
|
|
10
|
+
async then({ query }) {
|
|
11
|
+
await query.insert({
|
|
12
|
+
id: 1,
|
|
13
|
+
c_id: 1,
|
|
14
|
+
})
|
|
15
|
+
},
|
|
16
|
+
})
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Table } from "../../src"
|
|
2
|
+
|
|
3
|
+
export default new Table({
|
|
4
|
+
name: "d",
|
|
5
|
+
priority: 0,
|
|
6
|
+
columns: (col) => ({
|
|
7
|
+
id: col.increments(),
|
|
8
|
+
name: col.string(100).unique(),
|
|
9
|
+
email: col.string(255),
|
|
10
|
+
age: col.integer().nullable().unsigned(),
|
|
11
|
+
role: col.enum(["admin", "user", "guest"] as const).defaultTo("user"),
|
|
12
|
+
isActive: col.boolean().defaultTo(true),
|
|
13
|
+
metadata: col.json<{ tags: string[]; score: number }>().nullable(),
|
|
14
|
+
createdAt: col.timestamp().nullable(),
|
|
15
|
+
}),
|
|
16
|
+
async then({ query }) {
|
|
17
|
+
await query.insert({
|
|
18
|
+
id: 1,
|
|
19
|
+
name: "Test User",
|
|
20
|
+
email: "test@example.com",
|
|
21
|
+
age: 25,
|
|
22
|
+
role: "admin",
|
|
23
|
+
isActive: true,
|
|
24
|
+
metadata: { tags: ["test"], score: 100 },
|
|
25
|
+
createdAt: new Date(),
|
|
26
|
+
})
|
|
27
|
+
},
|
|
28
|
+
})
|
package/tsconfig.json
CHANGED
package/tests/tables/a.js
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import { Table } from "../.."
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @type {Table<{ id: number; b_id: number }>}
|
|
5
|
-
*/
|
|
6
|
-
export default new Table({
|
|
7
|
-
name: "a",
|
|
8
|
-
priority: 0,
|
|
9
|
-
setup(table) {
|
|
10
|
-
table.increments("id").primary().notNullable()
|
|
11
|
-
table
|
|
12
|
-
.integer("b_id")
|
|
13
|
-
.unsigned()
|
|
14
|
-
.references("id")
|
|
15
|
-
.inTable("b")
|
|
16
|
-
.onDelete("cascade")
|
|
17
|
-
.notNullable()
|
|
18
|
-
},
|
|
19
|
-
async then({ query }) {
|
|
20
|
-
await query.insert({
|
|
21
|
-
id: 1,
|
|
22
|
-
b_id: 1,
|
|
23
|
-
})
|
|
24
|
-
},
|
|
25
|
-
})
|