@ghom/orm 1.10.0 → 2.0.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 +45 -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/orm.d.ts +5 -4
- package/dist/app/orm.js +19 -15
- package/dist/app/table.d.ts +51 -21
- package/dist/app/table.js +35 -18
- package/dist/app/util.d.ts +1 -1
- package/dist/app/util.js +4 -4
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -1
- package/package.json +43 -47
- package/tests/{test.js → orm.test.ts} +112 -53
- 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 +32 -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
|
@@ -1,20 +1,61 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { rimraf } from "rimraf"
|
|
3
|
-
import path from "path"
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "bun:test"
|
|
4
2
|
import fs from "fs"
|
|
3
|
+
import path from "path"
|
|
4
|
+
import { rimraf } from "rimraf"
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
// path: path.join(process.cwd(), "tests", ".pg.env"),
|
|
8
|
-
// path: path.join(process.cwd(), "tests", ".mysql2.env"),
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
process.env.DEBUG = "knex*"
|
|
12
|
-
|
|
13
|
-
import { ORM } from "../"
|
|
6
|
+
import { col, ORM, type ORMConfig, Table } from "../src"
|
|
14
7
|
|
|
15
8
|
import a from "./tables/a"
|
|
16
9
|
import b from "./tables/b"
|
|
17
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
|
+
})
|
|
18
59
|
|
|
19
60
|
describe("unconnected ORM", () => {
|
|
20
61
|
test("can be initialized with false", () => {
|
|
@@ -23,7 +64,6 @@ describe("unconnected ORM", () => {
|
|
|
23
64
|
expect(unconnectedOrm).toBeInstanceOf(ORM)
|
|
24
65
|
expect(unconnectedOrm.isConnected).toBe(false)
|
|
25
66
|
expect(unconnectedOrm.config).toBe(false)
|
|
26
|
-
expect(unconnectedOrm.client).toBeUndefined()
|
|
27
67
|
expect(unconnectedOrm.handler).toBeUndefined()
|
|
28
68
|
expect(unconnectedOrm.cachedTables).toEqual([])
|
|
29
69
|
expect(unconnectedOrm.cachedTableNames).toEqual([])
|
|
@@ -32,11 +72,12 @@ describe("unconnected ORM", () => {
|
|
|
32
72
|
test("throws when calling methods requiring client", async () => {
|
|
33
73
|
const unconnectedOrm = new ORM(false)
|
|
34
74
|
|
|
35
|
-
|
|
36
|
-
|
|
75
|
+
expect(() => unconnectedOrm.client).toThrow()
|
|
76
|
+
expect(unconnectedOrm.init()).rejects.toThrow()
|
|
77
|
+
expect(unconnectedOrm.hasTable("test")).rejects.toThrow()
|
|
37
78
|
expect(() => unconnectedOrm.raw("SELECT 1")).toThrow()
|
|
38
|
-
|
|
39
|
-
|
|
79
|
+
expect(unconnectedOrm.createBackup()).rejects.toThrow()
|
|
80
|
+
expect(unconnectedOrm.restoreBackup()).rejects.toThrow()
|
|
40
81
|
})
|
|
41
82
|
})
|
|
42
83
|
|
|
@@ -45,10 +86,6 @@ const orm = new ORM({
|
|
|
45
86
|
backups: {
|
|
46
87
|
location: path.join(process.cwd(), "backups"),
|
|
47
88
|
},
|
|
48
|
-
database: process.env.DB_CLIENT && {
|
|
49
|
-
client: process.env.DB_CLIENT,
|
|
50
|
-
connection: process.env.DB_CONNECTION,
|
|
51
|
-
},
|
|
52
89
|
})
|
|
53
90
|
|
|
54
91
|
beforeAll(async () => {
|
|
@@ -61,13 +98,15 @@ describe("table management", () => {
|
|
|
61
98
|
expect(await orm.hasTable("a")).toBeTruthy()
|
|
62
99
|
expect(await orm.hasTable("b")).toBeTruthy()
|
|
63
100
|
expect(await orm.hasTable("c")).toBeTruthy()
|
|
101
|
+
expect(await orm.hasTable("d")).toBeTruthy()
|
|
64
102
|
})
|
|
65
103
|
|
|
66
|
-
test("table cached",
|
|
104
|
+
test("table cached", () => {
|
|
67
105
|
expect(orm.hasCachedTable("migration")).toBeTruthy()
|
|
68
106
|
expect(orm.hasCachedTable("a")).toBeTruthy()
|
|
69
107
|
expect(orm.hasCachedTable("b")).toBeTruthy()
|
|
70
108
|
expect(orm.hasCachedTable("c")).toBeTruthy()
|
|
109
|
+
expect(orm.hasCachedTable("d")).toBeTruthy()
|
|
71
110
|
})
|
|
72
111
|
|
|
73
112
|
test("table migrations ran", async () => {
|
|
@@ -76,28 +115,46 @@ describe("table management", () => {
|
|
|
76
115
|
|
|
77
116
|
test("table then ran", async () => {
|
|
78
117
|
const rows = await a.query.select()
|
|
118
|
+
expect(rows.length).toBe(1)
|
|
119
|
+
})
|
|
120
|
+
|
|
121
|
+
test("typed table d was created with correct columns", async () => {
|
|
122
|
+
expect(await d.hasColumn("id")).toBeTruthy()
|
|
123
|
+
expect(await d.hasColumn("name")).toBeTruthy()
|
|
124
|
+
expect(await d.hasColumn("email")).toBeTruthy()
|
|
125
|
+
expect(await d.hasColumn("age")).toBeTruthy()
|
|
126
|
+
expect(await d.hasColumn("role")).toBeTruthy()
|
|
127
|
+
expect(await d.hasColumn("isActive")).toBeTruthy()
|
|
128
|
+
expect(await d.hasColumn("metadata")).toBeTruthy()
|
|
129
|
+
expect(await d.hasColumn("createdAt")).toBeTruthy()
|
|
130
|
+
})
|
|
79
131
|
|
|
132
|
+
test("typed table d then ran", async () => {
|
|
133
|
+
const rows = await d.query.select()
|
|
80
134
|
expect(rows.length).toBe(1)
|
|
135
|
+
expect(rows[0].name).toBe("Test User")
|
|
136
|
+
expect(rows[0].role).toBe("admin")
|
|
81
137
|
})
|
|
82
138
|
})
|
|
83
139
|
|
|
84
140
|
describe("table column types", () => {
|
|
85
141
|
test("increments", async () => {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
.client("a")
|
|
89
|
-
.columnInfo("id")
|
|
90
|
-
.then((info) => info.type),
|
|
91
|
-
).toMatch(/^int/)
|
|
142
|
+
const info = await orm.client!("a").columnInfo("id")
|
|
143
|
+
expect(info.type).toMatch(/^int/)
|
|
92
144
|
})
|
|
93
145
|
|
|
94
146
|
test("integer", async () => {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
).
|
|
147
|
+
const info = await orm.client!("a").columnInfo("b_id")
|
|
148
|
+
expect(info.type).toMatch(/^int/)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
test("typed table d column types", async () => {
|
|
152
|
+
const nameInfo = await orm.client!("d").columnInfo("name")
|
|
153
|
+
expect(nameInfo.type).toMatch(/varchar|text|character/i)
|
|
154
|
+
|
|
155
|
+
const ageInfo = await orm.client!("d").columnInfo("age")
|
|
156
|
+
expect(ageInfo.type).toMatch(/^int/)
|
|
157
|
+
expect(ageInfo.nullable).toBe(true)
|
|
101
158
|
})
|
|
102
159
|
})
|
|
103
160
|
|
|
@@ -105,22 +162,16 @@ describe("database extraction", () => {
|
|
|
105
162
|
test("create backup", async () => {
|
|
106
163
|
await orm.createBackup()
|
|
107
164
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
).toBeTruthy()
|
|
111
|
-
expect(
|
|
112
|
-
|
|
113
|
-
).toBeTruthy()
|
|
114
|
-
expect(
|
|
115
|
-
fs.existsSync(path.join(orm.config.backups.location, "c_chunk_0.csv")),
|
|
116
|
-
).toBeTruthy()
|
|
165
|
+
const config = orm.config as ORMConfig
|
|
166
|
+
expect(fs.existsSync(path.join(config.backups!.location!, "a_chunk_0.csv"))).toBeTruthy()
|
|
167
|
+
expect(fs.existsSync(path.join(config.backups!.location!, "b_chunk_0.csv"))).toBeTruthy()
|
|
168
|
+
expect(fs.existsSync(path.join(config.backups!.location!, "c_chunk_0.csv"))).toBeTruthy()
|
|
169
|
+
expect(fs.existsSync(path.join(config.backups!.location!, "d_chunk_0.csv"))).toBeTruthy()
|
|
117
170
|
})
|
|
118
171
|
|
|
119
172
|
test("cascade delete", async () => {
|
|
120
173
|
expect(await a.isEmpty()).toBeFalsy()
|
|
121
|
-
|
|
122
174
|
await c.query.del()
|
|
123
|
-
|
|
124
175
|
expect(await a.isEmpty()).toBeTruthy()
|
|
125
176
|
})
|
|
126
177
|
|
|
@@ -130,10 +181,12 @@ describe("database extraction", () => {
|
|
|
130
181
|
expect(await a.isEmpty()).toBeFalsy()
|
|
131
182
|
expect(await b.isEmpty()).toBeFalsy()
|
|
132
183
|
expect(await c.isEmpty()).toBeFalsy()
|
|
133
|
-
|
|
184
|
+
expect(await d.isEmpty()).toBeFalsy()
|
|
185
|
+
})
|
|
134
186
|
|
|
135
187
|
afterAll(async () => {
|
|
136
|
-
|
|
188
|
+
const config = orm.config as ORMConfig
|
|
189
|
+
await rimraf(config.backups!.location!)
|
|
137
190
|
})
|
|
138
191
|
})
|
|
139
192
|
|
|
@@ -146,12 +199,17 @@ describe("table getters", () => {
|
|
|
146
199
|
expect(await b.getColumnNames()).toContain("c_id")
|
|
147
200
|
|
|
148
201
|
expect(await c.getColumnNames()).toContain("id")
|
|
202
|
+
|
|
203
|
+
expect(await d.getColumnNames()).toContain("id")
|
|
204
|
+
expect(await d.getColumnNames()).toContain("name")
|
|
205
|
+
expect(await d.getColumnNames()).toContain("role")
|
|
149
206
|
})
|
|
150
207
|
|
|
151
|
-
test("table names",
|
|
208
|
+
test("table names", () => {
|
|
152
209
|
expect(orm.cachedTableNames).toContain("a")
|
|
153
210
|
expect(orm.cachedTableNames).toContain("b")
|
|
154
211
|
expect(orm.cachedTableNames).toContain("c")
|
|
212
|
+
expect(orm.cachedTableNames).toContain("d")
|
|
155
213
|
})
|
|
156
214
|
})
|
|
157
215
|
|
|
@@ -196,7 +254,7 @@ describe("data caching", () => {
|
|
|
196
254
|
return query.select("b_id").where({ id: 1 }).first()
|
|
197
255
|
})
|
|
198
256
|
|
|
199
|
-
expect(row
|
|
257
|
+
expect(row!.b_id).toBe(3)
|
|
200
258
|
})
|
|
201
259
|
|
|
202
260
|
test("delete with caching", async () => {
|
|
@@ -221,9 +279,10 @@ describe("data caching", () => {
|
|
|
221
279
|
})
|
|
222
280
|
|
|
223
281
|
afterAll(async () => {
|
|
224
|
-
await orm.client
|
|
225
|
-
await orm.client
|
|
226
|
-
await orm.client
|
|
227
|
-
await orm.client
|
|
228
|
-
await orm.client.
|
|
282
|
+
await orm.client!.schema.dropTable("migration")
|
|
283
|
+
await orm.client!.schema.dropTable("a")
|
|
284
|
+
await orm.client!.schema.dropTable("b")
|
|
285
|
+
await orm.client!.schema.dropTable("c")
|
|
286
|
+
await orm.client!.schema.dropTable("d")
|
|
287
|
+
await orm.client!.destroy()
|
|
229
288
|
})
|
|
@@ -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,32 @@
|
|
|
1
|
+
import { Table } from "../../src"
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Table using the new typed columns system.
|
|
5
|
+
* Type is automatically inferred from the column definitions.
|
|
6
|
+
*/
|
|
7
|
+
export default new Table({
|
|
8
|
+
name: "d",
|
|
9
|
+
priority: 0,
|
|
10
|
+
columns: (col) => ({
|
|
11
|
+
id: col.increments(),
|
|
12
|
+
name: col.string(100).unique(),
|
|
13
|
+
email: col.string(255),
|
|
14
|
+
age: col.integer().nullable().unsigned(),
|
|
15
|
+
role: col.enum(["admin", "user", "guest"] as const).defaultTo("user"),
|
|
16
|
+
isActive: col.boolean().defaultTo(true),
|
|
17
|
+
metadata: col.json<{ tags: string[]; score: number }>().nullable(),
|
|
18
|
+
createdAt: col.timestamp().nullable(),
|
|
19
|
+
}),
|
|
20
|
+
async then({ query }) {
|
|
21
|
+
await query.insert({
|
|
22
|
+
id: 1,
|
|
23
|
+
name: "Test User",
|
|
24
|
+
email: "test@example.com",
|
|
25
|
+
age: 25,
|
|
26
|
+
role: "admin",
|
|
27
|
+
isActive: true,
|
|
28
|
+
metadata: { tags: ["test"], score: 100 },
|
|
29
|
+
createdAt: new Date(),
|
|
30
|
+
})
|
|
31
|
+
},
|
|
32
|
+
})
|
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
|
-
})
|
package/tests/tables/b.js
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { Table } from "../.."
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @type {Table<{ id: number; c_id: number }>}
|
|
5
|
-
*/
|
|
6
|
-
export default new Table({
|
|
7
|
-
name: "b",
|
|
8
|
-
migrations: {
|
|
9
|
-
0: (table) => table.string("c_id"),
|
|
10
|
-
1: (table) => table.dropColumn("c_id"),
|
|
11
|
-
2: (table) =>
|
|
12
|
-
table
|
|
13
|
-
.integer("c_id")
|
|
14
|
-
.unsigned()
|
|
15
|
-
.references("id")
|
|
16
|
-
.inTable("c")
|
|
17
|
-
.onDelete("cascade")
|
|
18
|
-
.notNullable(),
|
|
19
|
-
},
|
|
20
|
-
priority: 1,
|
|
21
|
-
setup(table) {
|
|
22
|
-
table.increments("id").primary().notNullable()
|
|
23
|
-
},
|
|
24
|
-
async then({ query }) {
|
|
25
|
-
await query.insert({
|
|
26
|
-
id: 1,
|
|
27
|
-
c_id: 1,
|
|
28
|
-
})
|
|
29
|
-
},
|
|
30
|
-
})
|
package/tests/tables/c.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { Table } from "../.."
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @type {Table<{ id: number }>}
|
|
5
|
-
*/
|
|
6
|
-
export default new Table({
|
|
7
|
-
name: "c",
|
|
8
|
-
priority: 2,
|
|
9
|
-
setup(table) {
|
|
10
|
-
table.increments("id").primary().notNullable()
|
|
11
|
-
},
|
|
12
|
-
async then({ query }) {
|
|
13
|
-
await query.insert({
|
|
14
|
-
id: 1,
|
|
15
|
-
})
|
|
16
|
-
},
|
|
17
|
-
})
|