@ghom/orm 1.4.1 → 1.5.1

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.
@@ -47,6 +47,7 @@ class ORM {
47
47
  });
48
48
  this.handler = new handler_1.Handler(config.location, {
49
49
  loader: (filepath) => Promise.resolve(`${isCJS ? filepath : url_1.default.pathToFileURL(filepath).href}`).then(s => __importStar(require(s))).then((file) => file.default),
50
+ pattern: /\.js$/,
50
51
  });
51
52
  }
52
53
  async init() {
@@ -71,5 +72,52 @@ class ORM {
71
72
  await table.make();
72
73
  }
73
74
  }
75
+ /**
76
+ * Extract the database to a CSV file.
77
+ */
78
+ async extract(dir = process.cwd()) {
79
+ const tables = [...this.handler.elements.values()];
80
+ for (const table of tables) {
81
+ await this.database
82
+ .select()
83
+ .from(table.options.name)
84
+ .then((rows) => {
85
+ const csv = rows.map((row) => Object.values(row).join(",")).join("\n");
86
+ fs_1.default.writeFileSync(path_1.default.join(dir, `${table.options.name}.csv`), csv, "utf8");
87
+ });
88
+ }
89
+ }
90
+ /**
91
+ * Import a CSV file to the database.
92
+ */
93
+ async import(dir = process.cwd()) {
94
+ const tables = [...this.handler.elements.values()].sort((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0));
95
+ for (const table of tables) {
96
+ const columnInfo = await table.getColumns();
97
+ let csv;
98
+ try {
99
+ csv = fs_1.default.readFileSync(path_1.default.join(dir, `${table.options.name}.csv`), "utf8");
100
+ }
101
+ catch (error) {
102
+ continue;
103
+ }
104
+ if (csv.trim().length === 0)
105
+ continue;
106
+ const rows = csv
107
+ .split("\n")
108
+ .map((row) => row.split(","))
109
+ .map((row) => {
110
+ const data = {};
111
+ let index = 0;
112
+ for (const [name, info] of Object.entries(columnInfo)) {
113
+ data[name] =
114
+ info.type === "integer" ? Number(row[index]) : row[index];
115
+ index++;
116
+ }
117
+ return data;
118
+ });
119
+ await this.database(table.options.name).insert(rows);
120
+ }
121
+ }
74
122
  }
75
123
  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()
@@ -21,6 +21,7 @@ export class ORM {
21
21
  });
22
22
  this.handler = new Handler(config.location, {
23
23
  loader: (filepath) => import(isCJS ? filepath : url.pathToFileURL(filepath).href).then((file) => file.default),
24
+ pattern: /\.js$/,
24
25
  });
25
26
  }
26
27
  async init() {
@@ -45,4 +46,51 @@ export class ORM {
45
46
  await table.make();
46
47
  }
47
48
  }
49
+ /**
50
+ * Extract the database to a CSV file.
51
+ */
52
+ async extract(dir = process.cwd()) {
53
+ const tables = [...this.handler.elements.values()];
54
+ for (const table of tables) {
55
+ await this.database
56
+ .select()
57
+ .from(table.options.name)
58
+ .then((rows) => {
59
+ const csv = rows.map((row) => Object.values(row).join(",")).join("\n");
60
+ fs.writeFileSync(path.join(dir, `${table.options.name}.csv`), csv, "utf8");
61
+ });
62
+ }
63
+ }
64
+ /**
65
+ * Import a CSV file to the database.
66
+ */
67
+ async import(dir = process.cwd()) {
68
+ const tables = [...this.handler.elements.values()].sort((a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0));
69
+ for (const table of tables) {
70
+ const columnInfo = await table.getColumns();
71
+ let csv;
72
+ try {
73
+ csv = fs.readFileSync(path.join(dir, `${table.options.name}.csv`), "utf8");
74
+ }
75
+ catch (error) {
76
+ continue;
77
+ }
78
+ if (csv.trim().length === 0)
79
+ continue;
80
+ const rows = csv
81
+ .split("\n")
82
+ .map((row) => row.split(","))
83
+ .map((row) => {
84
+ const data = {};
85
+ let index = 0;
86
+ for (const [name, info] of Object.entries(columnInfo)) {
87
+ data[name] =
88
+ info.type === "integer" ? Number(row[index]) : row[index];
89
+ index++;
90
+ }
91
+ return data;
92
+ });
93
+ await this.database(table.options.name).insert(rows);
94
+ }
95
+ }
48
96
  }
@@ -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()
@@ -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.1",
3
+ "version": "1.5.1",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/esm/index.js",
@@ -33,7 +33,7 @@
33
33
  "sqlite3": "^5.1.6"
34
34
  },
35
35
  "dependencies": {
36
- "@ghom/handler": "^1.2.0",
36
+ "@ghom/handler": "^1.3.0",
37
37
  "knex": "^2.4.2"
38
38
  }
39
39
  }
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
@@ -61,6 +61,7 @@ export class ORM {
61
61
  import(isCJS ? filepath : url.pathToFileURL(filepath).href).then(
62
62
  (file) => file.default
63
63
  ),
64
+ pattern: /\.js$/,
64
65
  })
65
66
  }
66
67
 
@@ -92,4 +93,71 @@ export class ORM {
92
93
  await table.make()
93
94
  }
94
95
  }
96
+
97
+ /**
98
+ * Extract the database to a CSV file.
99
+ */
100
+ async extract(dir = process.cwd()) {
101
+ const tables = [...this.handler.elements.values()]
102
+
103
+ for (const table of tables) {
104
+ await this.database
105
+ .select()
106
+ .from(table.options.name)
107
+ .then((rows) => {
108
+ const csv = rows.map((row) => Object.values(row).join(",")).join("\n")
109
+
110
+ fs.writeFileSync(
111
+ path.join(dir, `${table.options.name}.csv`),
112
+ csv,
113
+ "utf8"
114
+ )
115
+ })
116
+ }
117
+ }
118
+
119
+ /**
120
+ * Import a CSV file to the database.
121
+ */
122
+ async import(dir = process.cwd()) {
123
+ const tables = [...this.handler.elements.values()].sort(
124
+ (a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0)
125
+ )
126
+
127
+ for (const table of tables) {
128
+ const columnInfo = await table.getColumns()
129
+
130
+ let csv: string
131
+
132
+ try {
133
+ csv = fs.readFileSync(
134
+ path.join(dir, `${table.options.name}.csv`),
135
+ "utf8"
136
+ )
137
+ } catch (error) {
138
+ continue
139
+ }
140
+
141
+ if (csv.trim().length === 0) continue
142
+
143
+ const rows = csv
144
+ .split("\n")
145
+ .map((row) => row.split(","))
146
+ .map((row) => {
147
+ const data: any = {}
148
+
149
+ let index = 0
150
+
151
+ for (const [name, info] of Object.entries(columnInfo)) {
152
+ data[name] =
153
+ info.type === "integer" ? Number(row[index]) : row[index]
154
+ index++
155
+ }
156
+
157
+ return data
158
+ })
159
+
160
+ await this.database(table.options.name).insert(rows)
161
+ }
162
+ }
95
163
  }
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
+ })