@ghom/orm 1.8.3 → 1.8.6

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.
@@ -20,10 +20,46 @@ jobs:
20
20
  # The type of runner that the job will run on
21
21
  runs-on: ubuntu-latest
22
22
 
23
+ services:
24
+ postgres:
25
+ # Docker command:
26
+ # docker run --name pg_orm -e POSTGRES_PASSWORD=postgres -e POSTGRES_USER=postgres -e POSTGRES_DB=postgres -p 5432:5432 -d postgres
27
+ image: postgres
28
+
29
+ env:
30
+ POSTGRES_PASSWORD: postgres
31
+ POSTGRES_USER: postgres
32
+ POSTGRES_DB: postgres
33
+
34
+ options: >-
35
+ --health-cmd pg_isready
36
+ --health-interval 10s
37
+ --health-timeout 5s
38
+ --health-retries 5
39
+ ports:
40
+ - 5432:5432
41
+ mysql:
42
+ # Docker command:
43
+ # docker run --name mysql_orm -e MYSQL_ROOT_PASSWORD=mysql -e MYSQL_DATABASE=mysql -p 3306:3306 -d mysql:5.7
44
+ image: mysql:5.7
45
+
46
+ env:
47
+ MYSQL_ROOT_PASSWORD: mysql
48
+ MYSQL_DATABASE: mysql
49
+
50
+ options: >-
51
+ --health-cmd "mysqladmin ping"
52
+ --health-interval 10s
53
+ --health-timeout 5s
54
+ --health-retries 5
55
+ ports:
56
+ - 3306:3306
57
+
23
58
  # Steps represent a sequence of tasks that will be executed as part of the job
24
59
  steps:
25
60
  # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
26
- - uses: actions/checkout@v2
61
+ - name: Checkout repository
62
+ uses: actions/checkout@v2
27
63
 
28
64
  - name: Setup Node
29
65
  uses: actions/setup-node@v3
@@ -36,5 +72,17 @@ jobs:
36
72
  - name: Build the source
37
73
  run: npm run build
38
74
 
39
- - name: Start the tests
75
+ - name: Start the tests on SQLite
76
+ run: npm run test
77
+
78
+ - name: Start the tests on PostgreSQL
79
+ run: npm run test
80
+ env:
81
+ DB_CLIENT: pg
82
+ DB_CONNECTION: postgres://postgres:postgres@localhost:5432/postgres
83
+
84
+ - name: Start the tests on MySQL
40
85
  run: npm run test
86
+ env:
87
+ DB_CLIENT: mysql2
88
+ DB_CONNECTION: mysql://root:mysql@localhost:3306/mysql
@@ -1,6 +1,7 @@
1
+ import { Knex } from "knex";
1
2
  import { ORM } from "./orm.js";
2
3
  import { Table } from "./table.js";
3
4
  export declare function backupTable(table: Table, dirname?: string): Promise<void>;
4
- export declare function restoreBackup(table: Table, dirname?: string): Promise<void>;
5
- export declare function disableForeignKeys(orm: ORM): Promise<void>;
6
- export declare function enableForeignKeys(orm: ORM): Promise<void>;
5
+ export declare function restoreBackup(table: Table, trx: Knex.Transaction | Knex, dirname?: string): Promise<void>;
6
+ export declare function enableForeignKeys(orm: ORM, trx?: Knex.Transaction | Knex): Promise<void>;
7
+ export declare function disableForeignKeys(orm: ORM, run: (trx: Knex.Transaction | Knex) => unknown): Promise<void>;
@@ -53,14 +53,14 @@ export async function backupTable(table, dirname) {
53
53
  console.error(`\nError while backing up table ${util.styleText(table.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT, table.options.name)}:`, error);
54
54
  }
55
55
  }
56
- export async function restoreBackup(table, dirname) {
56
+ export async function restoreBackup(table, trx, dirname) {
57
57
  if (!table.orm)
58
58
  throw new Error("missing ORM");
59
59
  const chunkDir = path.join(table.orm.config.backups?.location ?? DEFAULT_BACKUP_LOCATION, dirname ?? "");
60
60
  const chunkFiles = fs
61
61
  .readdirSync(chunkDir)
62
62
  .filter((file) => file.split("_chunk_")[0] === table.options.name);
63
- await table.query.truncate();
63
+ await trx(table.options.name).del();
64
64
  try {
65
65
  const limit = 1000; // Limite par requête
66
66
  for (let chunkFile of chunkFiles) {
@@ -74,13 +74,13 @@ export async function restoreBackup(table, dirname) {
74
74
  if (rows.length > limit) {
75
75
  const rowsCopy = rows.slice();
76
76
  rows = [];
77
- await table.query.insert(rowsCopy);
77
+ await trx(table.options.name).insert(rowsCopy);
78
78
  }
79
79
  })
80
80
  .on("end", async () => {
81
81
  // Insérer les données dans la table une fois le fichier entièrement lu
82
82
  if (rows.length > 0)
83
- await table.query.insert(rows);
83
+ await trx(table.options.name).insert(rows);
84
84
  console.log(`Restored chunk ${util.styleText(table.orm.config.loggerStyles?.highlight ??
85
85
  DEFAULT_LOGGER_HIGHLIGHT, chunkFile)} into table ${util.styleText(table.orm.config.loggerStyles?.highlight ??
86
86
  DEFAULT_LOGGER_HIGHLIGHT, table.options.name)}.`);
@@ -95,25 +95,53 @@ export async function restoreBackup(table, dirname) {
95
95
  }
96
96
  console.log(`Backup of table ${util.styleText(table.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT, table.options.name)} restored.`);
97
97
  }
98
- export async function disableForeignKeys(orm) {
99
- const result = await Promise.allSettled([
100
- orm.raw("SET session_replication_role = replica;"),
101
- orm.raw("PRAGMA foreign_keys = OFF;"),
102
- orm.raw("SET FOREIGN_KEY_CHECKS = 0;"), // for mysql2
103
- ]);
104
- const errors = result.filter((r) => r.status === "rejected");
105
- if (errors.length === 3) {
106
- throw new Error("Failed to disable foreign key constraints.");
107
- }
98
+ export async function enableForeignKeys(orm, trx) {
99
+ const ctx = trx ?? orm;
100
+ await orm.clientBasedOperation({
101
+ mysql2: () => ctx.raw("SET FOREIGN_KEY_CHECKS = 1;"),
102
+ sqlite3: () => ctx.raw("PRAGMA foreign_keys = 1;"),
103
+ pg: () => ctx.raw("SET session_replication_role = DEFAULT;"),
104
+ });
105
+ console.log("Foreign key constraints enabled.");
108
106
  }
109
- export async function enableForeignKeys(orm) {
110
- const result = await Promise.allSettled([
111
- orm.raw("SET session_replication_role = DEFAULT;"),
112
- orm.raw("PRAGMA foreign_keys = ON;"),
113
- orm.raw("SET FOREIGN_KEY_CHECKS = 1;"), // for mysql2
114
- ]);
115
- const errors = result.filter((r) => r.status === "rejected");
116
- if (errors.length === 3) {
117
- throw new Error("Failed to enable foreign key constraints.");
107
+ export async function disableForeignKeys(orm, run) {
108
+ const trx = orm.clientBasedOperation({
109
+ sqlite3: () => orm.database,
110
+ }) ?? (await orm.database.transaction());
111
+ const ran = await orm.clientBasedOperation({
112
+ mysql2: async () => {
113
+ const result = await trx.raw("SELECT @@FOREIGN_KEY_CHECKS;");
114
+ const check = result?.[0] && result[0]["@@FOREIGN_KEY_CHECKS"] != 0;
115
+ if (check)
116
+ await trx.raw("SET FOREIGN_KEY_CHECKS = 0;");
117
+ return check;
118
+ },
119
+ sqlite3: async () => {
120
+ const result = await trx.raw("PRAGMA foreign_keys;");
121
+ const check = result?.[0] && result[0].foreign_keys != 0;
122
+ if (check)
123
+ await trx.raw("PRAGMA foreign_keys = 0;");
124
+ return check;
125
+ },
126
+ pg: async () => {
127
+ const result = await trx.raw("SHOW session_replication_role;");
128
+ const check = result?.rows?.[0] &&
129
+ result.rows[0].session_replication_role !== "replica";
130
+ if (check)
131
+ await trx.raw("SET session_replication_role = replica;");
132
+ return check;
133
+ },
134
+ });
135
+ console.log(`Foreign key constraints ${ran ? "" : "already "}disabled.`);
136
+ try {
137
+ await run(trx);
138
+ await enableForeignKeys(orm, trx);
139
+ if (trx.isTransaction)
140
+ await trx.commit();
141
+ }
142
+ catch (error) {
143
+ if (trx.isTransaction)
144
+ await trx.rollback();
145
+ throw error;
118
146
  }
119
147
  }
package/dist/app/orm.d.ts CHANGED
@@ -63,6 +63,7 @@ export declare class ORM {
63
63
  raw: (sql: string, anyDataUpdated?: boolean) => Knex.Raw;
64
64
  invalidate: () => void;
65
65
  };
66
+ clientBasedOperation<Return>(operation: Partial<Record<"pg" | "mysql2" | "sqlite3", () => Return>>): Return | undefined;
66
67
  /**
67
68
  * Create a backup of the database. <br>
68
69
  * The backup will be saved in the location specified in the config.
package/dist/app/orm.js CHANGED
@@ -43,7 +43,10 @@ export class ORM {
43
43
  */
44
44
  async init() {
45
45
  await this.handler.init();
46
- await enableForeignKeys(this);
46
+ try {
47
+ await enableForeignKeys(this);
48
+ }
49
+ catch { }
47
50
  this.handler.elements.set("migration", new Table({
48
51
  name: "migration",
49
52
  priority: Infinity,
@@ -52,7 +55,8 @@ export class ORM {
52
55
  table.integer("version").notNullable();
53
56
  },
54
57
  }));
55
- for (const table of this.cachedTables.sort((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0))) {
58
+ const sortedTables = this.cachedTables.toSorted((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0));
59
+ for (const table of sortedTables) {
56
60
  table.orm = this;
57
61
  await table.make();
58
62
  }
@@ -74,36 +78,30 @@ export class ORM {
74
78
  this.cachedTables.forEach((table) => table.cache.invalidate());
75
79
  },
76
80
  };
81
+ clientBasedOperation(operation) {
82
+ const client = (this.config.database?.client ?? "sqlite3");
83
+ return operation[client]?.();
84
+ }
77
85
  /**
78
86
  * Create a backup of the database. <br>
79
87
  * The backup will be saved in the location specified in the config.
80
88
  */
81
89
  async createBackup(dirname) {
82
- try {
83
- for (let table of this.cachedTables) {
84
- await backupTable(table, dirname);
85
- }
86
- console.log("Database backup created.");
87
- }
88
- catch (error) {
89
- console.error("Error while creating backup of the database.", error);
90
+ for (let table of this.cachedTables) {
91
+ await backupTable(table, dirname);
90
92
  }
93
+ console.log("Database backup created.");
91
94
  }
92
95
  /**
93
96
  * Restore the database from the backup. <br>
94
97
  * @warning This will delete all the data in the tables.
95
98
  */
96
99
  async restoreBackup(dirname) {
97
- try {
98
- await disableForeignKeys(this);
100
+ await disableForeignKeys(this, async (trx) => {
99
101
  for (let table of this.cachedTables) {
100
- await restoreBackup(table, dirname);
102
+ await restoreBackup(table, trx, dirname);
101
103
  }
102
- await enableForeignKeys(this);
103
- console.log("Database restored from backup.");
104
- }
105
- catch (error) {
106
- console.error("Error while restoring backup of the database.", error);
107
- }
104
+ });
105
+ console.log("Database restored from backup.");
108
106
  }
109
107
  }
package/dist/app/table.js CHANGED
@@ -45,7 +45,7 @@ export class Table {
45
45
  return this.query
46
46
  .select(this.db.raw("count(*) as total"))
47
47
  .whereRaw(where ?? "1=1")
48
- .then((rows) => rows[0].total);
48
+ .then((rows) => +(rows?.[0] ?? { total: 0 }).total);
49
49
  }
50
50
  async hasColumn(name) {
51
51
  return this.db.schema.hasColumn(this.options.name, name);
@@ -60,7 +60,7 @@ export class Table {
60
60
  return this.getColumns().then(Object.keys);
61
61
  }
62
62
  async isEmpty() {
63
- return this.count().then((count) => +count === 0);
63
+ return this.count().then((count) => count === 0);
64
64
  }
65
65
  async make() {
66
66
  if (!this.orm)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghom/orm",
3
- "version": "1.8.3",
3
+ "version": "1.8.6",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/readme.md CHANGED
@@ -1,7 +1,11 @@
1
- # TypeScript KnexJS ORM & handler
1
+ # @ghom/orm
2
+
3
+ ### TypeScript Knex ORM & table handler including a backup system and a cache.
2
4
 
3
5
  ![test workflow badge](https://github.com/GhomKrosmonaute/orm/actions/workflows/test.yml/badge.svg)
4
6
 
7
+ > Compatible with [PostgreSQL](https://www.npmjs.com/package/pg), [MySQL](https://www.npmjs.com/package/mysql2), and [SQLite](https://www.npmjs.com/package/sqlite3).
8
+
5
9
  ## Install
6
10
 
7
11
  ```bash
@@ -0,0 +1,2 @@
1
+ DB_CLIENT=mysql2
2
+ DB_CONNECTION=mysql2://root:mysql@localhost:3306/mysql
package/tests/.pg.env ADDED
@@ -0,0 +1,2 @@
1
+ DB_CLIENT=pg
2
+ DB_CONNECTION=postgres://postgres:postgres@localhost:5432/postgres
package/tests/tables/a.js CHANGED
@@ -10,6 +10,7 @@ export default new Table({
10
10
  table.increments("id").primary().notNullable()
11
11
  table
12
12
  .integer("b_id")
13
+ .unsigned()
13
14
  .references("id")
14
15
  .inTable("b")
15
16
  .onDelete("cascade")
@@ -17,8 +18,8 @@ export default new Table({
17
18
  },
18
19
  async then({ query }) {
19
20
  await query.insert({
20
- id: 0,
21
- b_id: 0,
21
+ id: 1,
22
+ b_id: 1,
22
23
  })
23
24
  },
24
25
  })
package/tests/tables/b.js CHANGED
@@ -9,6 +9,7 @@ export default new Table({
9
9
  0: (table) =>
10
10
  table
11
11
  .integer("c_id")
12
+ .unsigned()
12
13
  .references("id")
13
14
  .inTable("c")
14
15
  .onDelete("cascade")
@@ -20,8 +21,8 @@ export default new Table({
20
21
  },
21
22
  async then({ query }) {
22
23
  await query.insert({
23
- id: 0,
24
- c_id: 0,
24
+ id: 1,
25
+ c_id: 1,
25
26
  })
26
27
  },
27
28
  })
package/tests/tables/c.js CHANGED
@@ -10,6 +10,8 @@ export default new Table({
10
10
  table.increments("id").primary().notNullable()
11
11
  },
12
12
  async then({ query }) {
13
- await query.insert({ id: 0 })
13
+ await query.insert({
14
+ id: 1,
15
+ })
14
16
  },
15
17
  })
package/tests/test.js CHANGED
@@ -3,7 +3,12 @@ import { rimraf } from "rimraf"
3
3
  import path from "path"
4
4
  import fs from "fs"
5
5
 
6
- dotenv.config({ path: "./.env" })
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*"
7
12
 
8
13
  import { ORM } from "../"
9
14
 
@@ -16,6 +21,10 @@ const orm = new ORM({
16
21
  backups: {
17
22
  location: path.join(process.cwd(), "backups"),
18
23
  },
24
+ database: process.env.DB_CLIENT && {
25
+ client: process.env.DB_CLIENT,
26
+ connection: process.env.DB_CONNECTION,
27
+ },
19
28
  })
20
29
 
21
30
  beforeAll(async () => {
@@ -37,23 +46,15 @@ describe("table management", () => {
37
46
  expect(orm.hasCachedTable("c")).toBeTruthy()
38
47
  })
39
48
 
40
- test("migrations ran", async () => {
49
+ test("table migrations ran", async () => {
41
50
  expect(await b.hasColumn("c_id")).toBeTruthy()
42
51
  })
43
52
 
44
- test("then ran", async () => {
53
+ test("table then ran", async () => {
45
54
  const rows = await a.query.select()
46
55
 
47
56
  expect(rows.length).toBe(1)
48
57
  })
49
-
50
- test("cascade delete", async () => {
51
- expect(await a.isEmpty()).toBeFalsy()
52
-
53
- await c.query.del()
54
-
55
- expect(await a.isEmpty()).toBeTruthy()
56
- })
57
58
  })
58
59
 
59
60
  describe("table column types", () => {
@@ -63,7 +64,7 @@ describe("table column types", () => {
63
64
  .database("a")
64
65
  .columnInfo("id")
65
66
  .then((info) => info.type),
66
- ).toBe("integer")
67
+ ).toMatch(/^int/)
67
68
  })
68
69
 
69
70
  test("integer", async () => {
@@ -72,24 +73,12 @@ describe("table column types", () => {
72
73
  .database("a")
73
74
  .columnInfo("b_id")
74
75
  .then((info) => info.type),
75
- ).toBe("integer")
76
+ ).toMatch(/^int/)
76
77
  })
77
78
  })
78
79
 
79
80
  describe("database extraction", () => {
80
- beforeAll(async () => {
81
- await c.query.insert({ id: 0 })
82
- await b.query.insert({
83
- id: 0,
84
- c_id: 0,
85
- })
86
- await a.query.insert({
87
- id: 0,
88
- b_id: 0,
89
- })
90
- })
91
-
92
- test("extract CSV", async () => {
81
+ test("create backup", async () => {
93
82
  await orm.createBackup()
94
83
 
95
84
  expect(
@@ -103,22 +92,24 @@ describe("database extraction", () => {
103
92
  ).toBeTruthy()
104
93
  })
105
94
 
106
- test("empty tables", async () => {
107
- await a.query.del()
108
- await b.query.del()
95
+ test("cascade delete", async () => {
96
+ expect(await a.isEmpty()).toBeFalsy()
97
+
109
98
  await c.query.del()
110
99
 
111
100
  expect(await a.isEmpty()).toBeTruthy()
112
- expect(await b.isEmpty()).toBeTruthy()
113
- expect(await c.isEmpty()).toBeTruthy()
114
101
  })
115
102
 
116
- test("import CSV", async () => {
103
+ test("restore backup", async () => {
117
104
  await orm.restoreBackup()
118
105
 
119
106
  expect(await a.isEmpty()).toBeFalsy()
120
107
  expect(await b.isEmpty()).toBeFalsy()
121
108
  expect(await c.isEmpty()).toBeFalsy()
109
+ }, 10_000)
110
+
111
+ afterAll(async () => {
112
+ await rimraf(orm.config.backups.location)
122
113
  })
123
114
  })
124
115
 
@@ -206,6 +197,9 @@ describe("data caching", () => {
206
197
  })
207
198
 
208
199
  afterAll(async () => {
200
+ await orm.database.schema.dropTable("migration")
201
+ await orm.database.schema.dropTable("a")
202
+ await orm.database.schema.dropTable("b")
203
+ await orm.database.schema.dropTable("c")
209
204
  await orm.database.destroy()
210
- await rimraf(orm.config.backups.location)
211
205
  })
package/tsconfig.json CHANGED
@@ -3,10 +3,10 @@
3
3
  "strict": true,
4
4
  "rootDir": "src",
5
5
  "outDir": "dist",
6
- "module": "esnext",
7
- "target": "esnext",
8
- "lib": ["es2020", "dom"],
9
- "moduleResolution": "Node",
6
+ "module": "NodeNext",
7
+ "target": "ESNext",
8
+ "lib": ["ESNext"],
9
+ "moduleResolution": "NodeNext",
10
10
  "esModuleInterop": true,
11
11
  "declaration": true,
12
12
  "typeRoots": ["./node_modules/@types", "./dist/typings"]
package/tests/.env DELETED
@@ -1 +0,0 @@
1
- DEBUG=knex*