@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.
@@ -1,20 +1,61 @@
1
- import dotenv from "dotenv"
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
- dotenv.config({
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
- await expect(unconnectedOrm.init()).rejects.toThrow()
36
- await expect(unconnectedOrm.hasTable("test")).rejects.toThrow()
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
- await expect(unconnectedOrm.createBackup()).rejects.toThrow()
39
- await expect(unconnectedOrm.restoreBackup()).rejects.toThrow()
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", async () => {
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
- expect(
87
- await orm
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
- expect(
96
- await orm
97
- .client("a")
98
- .columnInfo("b_id")
99
- .then((info) => info.type),
100
- ).toMatch(/^int/)
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
- expect(
109
- fs.existsSync(path.join(orm.config.backups.location, "a_chunk_0.csv")),
110
- ).toBeTruthy()
111
- expect(
112
- fs.existsSync(path.join(orm.config.backups.location, "b_chunk_0.csv")),
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
- }, 10_000)
184
+ expect(await d.isEmpty()).toBeFalsy()
185
+ })
134
186
 
135
187
  afterAll(async () => {
136
- await rimraf(orm.config.backups.location)
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", async () => {
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.b_id).toBe(3)
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.schema.dropTable("migration")
225
- await orm.client.schema.dropTable("a")
226
- await orm.client.schema.dropTable("b")
227
- await orm.client.schema.dropTable("c")
228
- await orm.client.destroy()
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,14 @@
1
+ import { Table } from "../../src"
2
+
3
+ export default new Table({
4
+ name: "c",
5
+ priority: 2,
6
+ columns: (col) => ({
7
+ id: col.increments(),
8
+ }),
9
+ async then({ query }) {
10
+ await query.insert({
11
+ id: 1,
12
+ })
13
+ },
14
+ })
@@ -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
@@ -9,6 +9,7 @@
9
9
  "moduleResolution": "NodeNext",
10
10
  "esModuleInterop": true,
11
11
  "declaration": true,
12
+ "skipLibCheck": true,
12
13
  "typeRoots": ["./node_modules/@types", "./dist/typings"]
13
14
  },
14
15
  "include": ["src/**/*", "dist/typings/**/*"]
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
- })