@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.
- package/dist/cjs/app/orm.js +48 -0
- package/dist/cjs/app/table.js +6 -0
- package/dist/esm/app/orm.js +48 -0
- package/dist/esm/app/table.js +6 -0
- package/dist/typings/app/orm.d.ts +8 -0
- package/dist/typings/app/table.d.ts +2 -0
- package/package.json +2 -2
- package/readme.md +77 -0
- package/src/app/orm.ts +68 -0
- package/src/app/table.ts +8 -0
- package/tests/test.js +111 -43
package/dist/cjs/app/orm.js
CHANGED
|
@@ -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;
|
package/dist/cjs/app/table.js
CHANGED
|
@@ -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()
|
package/dist/esm/app/orm.js
CHANGED
|
@@ -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
|
}
|
package/dist/esm/app/table.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
import
|
|
10
|
-
import
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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
|
+
})
|