@ghom/orm 1.4.0 → 1.5.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.
@@ -71,5 +71,52 @@ class ORM {
71
71
  await table.make();
72
72
  }
73
73
  }
74
+ /**
75
+ * Extract the database to a CSV file.
76
+ */
77
+ async extract(dir = process.cwd()) {
78
+ const tables = [...this.handler.elements.values()];
79
+ for (const table of tables) {
80
+ await this.database
81
+ .select()
82
+ .from(table.options.name)
83
+ .then((rows) => {
84
+ const csv = rows.map((row) => Object.values(row).join(",")).join("\n");
85
+ fs_1.default.writeFileSync(path_1.default.join(dir, `${table.options.name}.csv`), csv, "utf8");
86
+ });
87
+ }
88
+ }
89
+ /**
90
+ * Import a CSV file to the database.
91
+ */
92
+ async import(dir = process.cwd()) {
93
+ const tables = [...this.handler.elements.values()].sort((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0));
94
+ for (const table of tables) {
95
+ const columnInfo = await table.getColumns();
96
+ let csv;
97
+ try {
98
+ csv = fs_1.default.readFileSync(path_1.default.join(dir, `${table.options.name}.csv`), "utf8");
99
+ }
100
+ catch (error) {
101
+ continue;
102
+ }
103
+ if (csv.trim().length === 0)
104
+ continue;
105
+ const rows = csv
106
+ .split("\n")
107
+ .map((row) => row.split(","))
108
+ .map((row) => {
109
+ const data = {};
110
+ let index = 0;
111
+ for (const [name, info] of Object.entries(columnInfo)) {
112
+ data[name] =
113
+ info.type === "integer" ? Number(row[index]) : row[index];
114
+ index++;
115
+ }
116
+ return data;
117
+ });
118
+ await this.database(table.options.name).insert(rows);
119
+ }
120
+ }
74
121
  }
75
122
  exports.ORM = ORM;
@@ -20,6 +20,12 @@ class Table {
20
20
  async hasColumn(name) {
21
21
  return this.db.schema.hasColumn(this.options.name, name);
22
22
  }
23
+ async getColumns() {
24
+ return this.db(this.options.name).columnInfo();
25
+ }
26
+ async getColumnNames() {
27
+ return this.getColumns().then(Object.keys);
28
+ }
23
29
  async isEmpty() {
24
30
  return this.query
25
31
  .select()
@@ -45,4 +45,51 @@ export class ORM {
45
45
  await table.make();
46
46
  }
47
47
  }
48
+ /**
49
+ * Extract the database to a CSV file.
50
+ */
51
+ async extract(dir = process.cwd()) {
52
+ const tables = [...this.handler.elements.values()];
53
+ for (const table of tables) {
54
+ await this.database
55
+ .select()
56
+ .from(table.options.name)
57
+ .then((rows) => {
58
+ const csv = rows.map((row) => Object.values(row).join(",")).join("\n");
59
+ fs.writeFileSync(path.join(dir, `${table.options.name}.csv`), csv, "utf8");
60
+ });
61
+ }
62
+ }
63
+ /**
64
+ * Import a CSV file to the database.
65
+ */
66
+ async import(dir = process.cwd()) {
67
+ const tables = [...this.handler.elements.values()].sort((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0));
68
+ for (const table of tables) {
69
+ const columnInfo = await table.getColumns();
70
+ let csv;
71
+ try {
72
+ csv = fs.readFileSync(path.join(dir, `${table.options.name}.csv`), "utf8");
73
+ }
74
+ catch (error) {
75
+ continue;
76
+ }
77
+ if (csv.trim().length === 0)
78
+ continue;
79
+ const rows = csv
80
+ .split("\n")
81
+ .map((row) => row.split(","))
82
+ .map((row) => {
83
+ const data = {};
84
+ let index = 0;
85
+ for (const [name, info] of Object.entries(columnInfo)) {
86
+ data[name] =
87
+ info.type === "integer" ? Number(row[index]) : row[index];
88
+ index++;
89
+ }
90
+ return data;
91
+ });
92
+ await this.database(table.options.name).insert(rows);
93
+ }
94
+ }
48
95
  }
@@ -16,6 +16,12 @@ export class Table {
16
16
  async hasColumn(name) {
17
17
  return this.db.schema.hasColumn(this.options.name, name);
18
18
  }
19
+ async getColumns() {
20
+ return this.db(this.options.name).columnInfo();
21
+ }
22
+ async getColumnNames() {
23
+ return this.getColumns().then(Object.keys);
24
+ }
19
25
  async isEmpty() {
20
26
  return this.query
21
27
  .select()
@@ -4,7 +4,7 @@ import { Table } from "./table.js";
4
4
  import { Color } from "chalk";
5
5
  export interface ILogger {
6
6
  log: (...message: string[]) => void;
7
- error: (error: Error | string, ...message: string[]) => void;
7
+ error: (error: Error | string) => void;
8
8
  }
9
9
  export interface ORMConfig {
10
10
  /**
@@ -34,4 +34,12 @@ export declare class ORM {
34
34
  handler: Handler<Table<any>>;
35
35
  constructor(config: ORMConfig);
36
36
  init(): Promise<void>;
37
+ /**
38
+ * Extract the database to a CSV file.
39
+ */
40
+ extract(dir?: string): Promise<void>;
41
+ /**
42
+ * Import a CSV file to the database.
43
+ */
44
+ import(dir?: string): Promise<void>;
37
45
  }
@@ -28,6 +28,8 @@ export declare class Table<Type extends {}> {
28
28
  _unionProps: never;
29
29
  }[]>;
30
30
  hasColumn(name: keyof Type): Promise<boolean>;
31
+ getColumns(): Promise<Record<string | number | symbol, Knex.ColumnInfo>>;
32
+ getColumnNames(): Promise<string[]>;
31
33
  isEmpty(): Promise<boolean>;
32
34
  make(): Promise<this>;
33
35
  private migrate;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghom/orm",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/esm/index.js",
package/readme.md CHANGED
@@ -1 +1,78 @@
1
1
  # TypeScript KnexJS ORM & handler
2
+
3
+ ## Install
4
+
5
+ ```bash
6
+ npm install @ghom/orm
7
+ ```
8
+
9
+ ## Usage
10
+
11
+ ```typescript
12
+ import { ORM } from "@ghom/orm"
13
+
14
+ const orm = new ORM({
15
+ // tables directory
16
+ lcoation: "./tables",
17
+
18
+ // knex config (sqlite3 by default)
19
+ database: { ... },
20
+
21
+ // custom logger (console by default)
22
+ logger: console,
23
+ loggerColors: { ... }
24
+ })
25
+ ```
26
+
27
+ ## Tables
28
+
29
+ The tables are automatically loaded from the `location` directory.
30
+
31
+ ```typescript
32
+ // tables/user.ts
33
+
34
+ import { Table } from "@ghom/orm"
35
+
36
+ export default new Table({
37
+ name: "user",
38
+
39
+ // the higher the priority, the earlier the table is compiled
40
+ priority: 0,
41
+
42
+ // the migration are executed in order of version number
43
+ migrations: {
44
+ 1: (table) => {
45
+ table.renameColumn("name", "username")
46
+ }
47
+ },
48
+
49
+ // the setup is executed only once for table creation
50
+ setup: (table) => {
51
+ table.string("name").notNullable()
52
+ table.string("password").notNullable()
53
+ },
54
+
55
+ // the then is executed after the table is created and the migrations are runned
56
+ then: ({ query }) => {
57
+ query.insert({ username: "admin", password: "admin" })
58
+ }
59
+ })
60
+ ```
61
+
62
+ ## Query
63
+
64
+ For more information about the query builder, see [knexjs.org](https://knexjs.org/).
65
+
66
+ ## Import/extract
67
+
68
+ You can transfer the data from one instance of the ORM to another (between two database clients, for example between "pg" and "mysql2").
69
+
70
+ ```typescript
71
+ await orm1.extract()
72
+ ```
73
+
74
+ ```typescript
75
+ await orm2.import()
76
+ ```
77
+
78
+ The SQL structure isn't transferred, only the data. You must copy the table files to the other project.
package/src/app/orm.ts CHANGED
@@ -13,7 +13,7 @@ const isCJS = pack.type === "commonjs" || pack.type == void 0
13
13
 
14
14
  export interface ILogger {
15
15
  log: (...message: string[]) => void
16
- error: (error: Error | string, ...message: string[]) => void
16
+ error: (error: Error | string) => void
17
17
  }
18
18
 
19
19
  export interface ORMConfig {
@@ -92,4 +92,71 @@ export class ORM {
92
92
  await table.make()
93
93
  }
94
94
  }
95
+
96
+ /**
97
+ * Extract the database to a CSV file.
98
+ */
99
+ async extract(dir = process.cwd()) {
100
+ const tables = [...this.handler.elements.values()]
101
+
102
+ for (const table of tables) {
103
+ await this.database
104
+ .select()
105
+ .from(table.options.name)
106
+ .then((rows) => {
107
+ const csv = rows.map((row) => Object.values(row).join(",")).join("\n")
108
+
109
+ fs.writeFileSync(
110
+ path.join(dir, `${table.options.name}.csv`),
111
+ csv,
112
+ "utf8"
113
+ )
114
+ })
115
+ }
116
+ }
117
+
118
+ /**
119
+ * Import a CSV file to the database.
120
+ */
121
+ async import(dir = process.cwd()) {
122
+ const tables = [...this.handler.elements.values()].sort(
123
+ (a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0)
124
+ )
125
+
126
+ for (const table of tables) {
127
+ const columnInfo = await table.getColumns()
128
+
129
+ let csv: string
130
+
131
+ try {
132
+ csv = fs.readFileSync(
133
+ path.join(dir, `${table.options.name}.csv`),
134
+ "utf8"
135
+ )
136
+ } catch (error) {
137
+ continue
138
+ }
139
+
140
+ if (csv.trim().length === 0) continue
141
+
142
+ const rows = csv
143
+ .split("\n")
144
+ .map((row) => row.split(","))
145
+ .map((row) => {
146
+ const data: any = {}
147
+
148
+ let index = 0
149
+
150
+ for (const [name, info] of Object.entries(columnInfo)) {
151
+ data[name] =
152
+ info.type === "integer" ? Number(row[index]) : row[index]
153
+ index++
154
+ }
155
+
156
+ return data
157
+ })
158
+
159
+ await this.database(table.options.name).insert(rows)
160
+ }
161
+ }
95
162
  }
package/src/app/table.ts CHANGED
@@ -33,6 +33,14 @@ export class Table<Type extends {}> {
33
33
  return this.db.schema.hasColumn(this.options.name, name as string)
34
34
  }
35
35
 
36
+ async getColumns() {
37
+ return this.db(this.options.name).columnInfo()
38
+ }
39
+
40
+ async getColumnNames() {
41
+ return this.getColumns().then(Object.keys)
42
+ }
43
+
36
44
  async isEmpty(): Promise<boolean> {
37
45
  return this.query
38
46
  .select()
package/tests/test.js CHANGED
@@ -1,43 +1,111 @@
1
- import dotenv from "dotenv"
2
- import path from "path"
3
-
4
- dotenv.config({ path: "./.env" })
5
-
6
- import { ORM } from "../"
7
-
8
- import a from "./tables/a"
9
- import b from "./tables/b"
10
- import c from "./tables/c"
11
-
12
- const orm = new ORM({
13
- location: path.join("tests","tables")
14
- })
15
-
16
- beforeAll(async () => {
17
- await orm.init()
18
- })
19
-
20
- test("tables created", async () => {
21
- expect(await orm.database.schema.hasTable("migration")).toBeTruthy()
22
- expect(await orm.database.schema.hasTable("a")).toBeTruthy()
23
- expect(await orm.database.schema.hasTable("b")).toBeTruthy()
24
- expect(await orm.database.schema.hasTable("c")).toBeTruthy()
25
- })
26
-
27
- test("migrations ran", async () => {
28
- expect(await orm.database.schema.hasColumn("b", "c_id")).toBeTruthy()
29
- })
30
-
31
- test("then ran", async () => {
32
- const rows = await orm.database("a").select()
33
- expect(rows.length).toBe(1)
34
- })
35
-
36
- test("cascade delete", async () => {
37
- await c.query.del()
38
- expect(await a.isEmpty()).toBeTruthy()
39
- })
40
-
41
- afterAll(async () => {
42
- await orm.database.destroy()
43
- })
1
+ import dotenv from "dotenv"
2
+ import path from "path"
3
+ import fs from "fs"
4
+
5
+ dotenv.config({ path: "./.env" })
6
+
7
+ import { ORM } from "../"
8
+
9
+ import a from "./tables/a"
10
+ import b from "./tables/b"
11
+ import c from "./tables/c"
12
+
13
+ const orm = new ORM({
14
+ location: path.join("tests", "tables"),
15
+ })
16
+
17
+ beforeAll(async () => {
18
+ await orm.init()
19
+ })
20
+
21
+ describe("table management", () => {
22
+ test("tables created", async () => {
23
+ expect(await orm.database.schema.hasTable("migration")).toBeTruthy()
24
+ expect(await orm.database.schema.hasTable("a")).toBeTruthy()
25
+ expect(await orm.database.schema.hasTable("b")).toBeTruthy()
26
+ expect(await orm.database.schema.hasTable("c")).toBeTruthy()
27
+ })
28
+
29
+ test("migrations ran", async () => {
30
+ expect(await orm.database.schema.hasColumn("b", "c_id")).toBeTruthy()
31
+ })
32
+
33
+ test("then ran", async () => {
34
+ const rows = await orm.database("a").select()
35
+
36
+ expect(rows.length).toBe(1)
37
+ })
38
+
39
+ test("cascade delete", async () => {
40
+ await c.query.del()
41
+
42
+ expect(await a.isEmpty()).toBeTruthy()
43
+ })
44
+ })
45
+
46
+ describe("table column types", () => {
47
+ test("increments", async () => {
48
+ expect(
49
+ await orm
50
+ .database("a")
51
+ .columnInfo("id")
52
+ .then((info) => info.type)
53
+ ).toBe("integer")
54
+ })
55
+
56
+ test("integer", async () => {
57
+ expect(
58
+ await orm
59
+ .database("a")
60
+ .columnInfo("b_id")
61
+ .then((info) => info.type)
62
+ ).toBe("integer")
63
+ })
64
+ })
65
+
66
+ describe("database migration", () => {
67
+ beforeAll(async () => {
68
+ await c.query.insert({ id: 0 })
69
+ await b.query.insert({
70
+ id: 0,
71
+ c_id: 0,
72
+ })
73
+ await a.query.insert({
74
+ id: 0,
75
+ b_id: 0,
76
+ })
77
+ })
78
+
79
+ test("extract CSV", async () => {
80
+ await orm.extract()
81
+
82
+ expect(fs.existsSync("a.csv")).toBeTruthy()
83
+ expect(fs.existsSync("b.csv")).toBeTruthy()
84
+ expect(fs.existsSync("c.csv")).toBeTruthy()
85
+ })
86
+
87
+ test("empty tables", async () => {
88
+ await a.query.del()
89
+ await b.query.del()
90
+ await c.query.del()
91
+
92
+ expect(await a.isEmpty()).toBeTruthy()
93
+ expect(await b.isEmpty()).toBeTruthy()
94
+ expect(await c.isEmpty()).toBeTruthy()
95
+ })
96
+
97
+ test("import CSV", async () => {
98
+ await orm.import()
99
+
100
+ expect(await a.isEmpty()).toBeFalsy()
101
+ expect(await b.isEmpty()).toBeFalsy()
102
+ expect(await c.isEmpty()).toBeFalsy()
103
+ })
104
+ })
105
+
106
+ afterAll(async () => {
107
+ await orm.database.destroy()
108
+ fs.unlinkSync("a.csv")
109
+ fs.unlinkSync("b.csv")
110
+ fs.unlinkSync("c.csv")
111
+ })