@ghom/orm 1.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.
@@ -0,0 +1,20 @@
1
+ import { Handler } from "@ghom/handler";
2
+ import { Knex } from "knex";
3
+ /**
4
+ * @property tablePath - path to directory that contains js files of tables
5
+ * @property verbose - show console logs or not
6
+ */
7
+ export interface ORMConfig {
8
+ verbose?: boolean;
9
+ tablePath: string;
10
+ }
11
+ export declare class ORM extends Handler {
12
+ db: Knex;
13
+ ormConfig: ORMConfig;
14
+ /**
15
+ * @param ormConfig configuration for table handler or just tablePath (path to directory that contains js files of tables)
16
+ * @param knexConfig configuration for connect to database
17
+ */
18
+ constructor(ormConfig: ORMConfig | string, knexConfig?: Knex.Config);
19
+ init(): Promise<void>;
20
+ }
@@ -0,0 +1,69 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ var __importDefault = (this && this.__importDefault) || function (mod) {
22
+ return (mod && mod.__esModule) ? mod : { "default": mod };
23
+ };
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.ORM = void 0;
26
+ const handler_1 = require("@ghom/handler");
27
+ const knex_1 = __importDefault(require("knex"));
28
+ const table_1 = require("./table");
29
+ class ORM extends handler_1.Handler {
30
+ /**
31
+ * @param ormConfig configuration for table handler or just tablePath (path to directory that contains js files of tables)
32
+ * @param knexConfig configuration for connect to database
33
+ */
34
+ constructor(ormConfig, knexConfig = {
35
+ client: "sqlite3",
36
+ useNullAsDefault: true,
37
+ connection: {
38
+ filename: ":memory:",
39
+ },
40
+ }) {
41
+ super(typeof ormConfig === "string" ? ormConfig : ormConfig.tablePath);
42
+ this.ormConfig =
43
+ typeof ormConfig === "string" ? { tablePath: ormConfig } : ormConfig;
44
+ this.db = (0, knex_1.default)(knexConfig);
45
+ }
46
+ async init() {
47
+ this.once("finish", async (pathList) => {
48
+ const tables = await Promise.all(pathList.map(async (filepath) => {
49
+ return Promise.resolve().then(() => __importStar(require(filepath))).then((file) => file.default);
50
+ }));
51
+ tables.unshift(new table_1.Table({
52
+ name: "migration",
53
+ priority: Infinity,
54
+ setup: (table) => {
55
+ table.string("table").unique().notNullable();
56
+ table.integer("version").notNullable();
57
+ },
58
+ }));
59
+ tables.forEach((table) => (table.orm = this));
60
+ return Promise.all(tables
61
+ .sort((a, b) => {
62
+ return (b.options.priority ?? 0) - (a.options.priority ?? 0);
63
+ })
64
+ .map((table) => table.make()));
65
+ });
66
+ await this.load();
67
+ }
68
+ }
69
+ exports.ORM = ORM;
@@ -0,0 +1,32 @@
1
+ import { Knex } from "knex";
2
+ import { ORM } from "./orm.js";
3
+ export interface MigrationData {
4
+ table: string;
5
+ version: number;
6
+ }
7
+ export interface TableOptions<Type> {
8
+ name: string;
9
+ priority?: number;
10
+ migrations?: {
11
+ [version: number]: (table: Knex.CreateTableBuilder) => void;
12
+ };
13
+ setup: (table: Knex.CreateTableBuilder) => void;
14
+ }
15
+ export declare class Table<Type> {
16
+ readonly options: TableOptions<Type>;
17
+ orm?: ORM;
18
+ constructor(options: TableOptions<Type>);
19
+ private get verbose();
20
+ get db(): Knex<any, Record<string, any>[]>;
21
+ get query(): Knex.QueryBuilder<Type, {
22
+ _base: Type;
23
+ _hasSelection: false;
24
+ _keys: never;
25
+ _aliases: {};
26
+ _single: false;
27
+ _intersectProps: {};
28
+ _unionProps: never;
29
+ }[]>;
30
+ make(): Promise<this>;
31
+ private migrate;
32
+ }
@@ -0,0 +1,80 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Table = void 0;
4
+ class Table {
5
+ constructor(options) {
6
+ this.options = options;
7
+ }
8
+ get verbose() {
9
+ if (!this.orm)
10
+ throw new Error("missing ORM");
11
+ return this.orm.ormConfig.verbose;
12
+ }
13
+ get db() {
14
+ if (!this.orm)
15
+ throw new Error("missing ORM");
16
+ return this.orm.db;
17
+ }
18
+ get query() {
19
+ return this.db(this.options.name);
20
+ }
21
+ async make() {
22
+ try {
23
+ await this.db.schema.createTable(this.options.name, this.options.setup);
24
+ if (this.verbose)
25
+ console.log(`created table ${this.options.name}`);
26
+ }
27
+ catch (error) {
28
+ if (error.toString().includes("syntax error")) {
29
+ if (this.verbose)
30
+ console.error(`you need to implement the "setup" method in options of your ${this.options.name} table!`);
31
+ throw error;
32
+ }
33
+ else {
34
+ if (this.verbose)
35
+ console.log(`loaded table ${this.options.name}`);
36
+ }
37
+ }
38
+ try {
39
+ const migrated = await this.migrate();
40
+ if (migrated !== false) {
41
+ if (this.verbose)
42
+ console.log(`migrated table ${this.options.name} to version ${migrated}`);
43
+ }
44
+ }
45
+ catch (error) {
46
+ if (this.verbose)
47
+ console.error(error);
48
+ }
49
+ return this;
50
+ }
51
+ async migrate() {
52
+ if (!this.options.migrations)
53
+ return false;
54
+ const migrations = new Map(Object.entries(this.options.migrations)
55
+ .sort((a, b) => Number(a[0]) - Number(b[0]))
56
+ .map((entry) => [Number(entry[0]), entry[1]]));
57
+ const fromDatabase = await this.db("migration")
58
+ .where("table", this.options.name)
59
+ .first();
60
+ const data = fromDatabase || {
61
+ table: this.options.name,
62
+ version: -1,
63
+ };
64
+ const baseVersion = data.version;
65
+ await this.db.schema.alterTable(this.options.name, (builder) => {
66
+ migrations.forEach((migration, version) => {
67
+ if (version <= data.version)
68
+ return;
69
+ migration(builder);
70
+ data.version = version;
71
+ });
72
+ });
73
+ await this.db("migration")
74
+ .insert(data)
75
+ .onConflict("table")
76
+ .merge();
77
+ return baseVersion === data.version ? false : data.version;
78
+ }
79
+ }
80
+ exports.Table = Table;
@@ -0,0 +1,2 @@
1
+ export * from "./app/orm";
2
+ export * from "./app/table";
package/dist/index.js ADDED
@@ -0,0 +1,14 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
10
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
11
+ };
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ __exportStar(require("./app/orm"), exports);
14
+ __exportStar(require("./app/table"), exports);
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@ghom/orm",
3
+ "version": "1.0.0",
4
+ "license": "MIT",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "description": "TypeScript KnexJS ORM & handler",
8
+ "prettier": {
9
+ "semi": false
10
+ },
11
+ "scripts": {
12
+ "format": "prettier --write src tsconfig.*",
13
+ "build": "tsc",
14
+ "test": "npm run build && jest tests/test.js --detectOpenHandles",
15
+ "prepublishOnly": "npm run format && npm test"
16
+ },
17
+ "devDependencies": {
18
+ "@vscode/sqlite3": "^5.0.7",
19
+ "better-sqlite3": "^7.5.0",
20
+ "jest": "^27.5.1",
21
+ "prettier": "^2.5.1",
22
+ "typescript": "^4.5.5"
23
+ },
24
+ "dependencies": {
25
+ "@ghom/handler": "^1.1.0",
26
+ "knex": "^1.0.3"
27
+ }
28
+ }
package/readme.md ADDED
@@ -0,0 +1 @@
1
+ # TypeScript KnexJS ORM & handler
package/src/app/orm.ts ADDED
@@ -0,0 +1,70 @@
1
+ import { Handler } from "@ghom/handler"
2
+ import { Knex, default as knex } from "knex"
3
+ import { MigrationData, Table } from "./table"
4
+
5
+ /**
6
+ * @property tablePath - path to directory that contains js files of tables
7
+ * @property verbose - show console logs or not
8
+ */
9
+ export interface ORMConfig {
10
+ verbose?: boolean
11
+ tablePath: string
12
+ }
13
+
14
+ export class ORM extends Handler {
15
+ db: Knex
16
+ ormConfig: ORMConfig
17
+
18
+ /**
19
+ * @param ormConfig configuration for table handler or just tablePath (path to directory that contains js files of tables)
20
+ * @param knexConfig configuration for connect to database
21
+ */
22
+ constructor(
23
+ ormConfig: ORMConfig | string,
24
+ knexConfig: Knex.Config = {
25
+ client: "sqlite3",
26
+ useNullAsDefault: true,
27
+ connection: {
28
+ filename: ":memory:",
29
+ },
30
+ }
31
+ ) {
32
+ super(typeof ormConfig === "string" ? ormConfig : ormConfig.tablePath)
33
+ this.ormConfig =
34
+ typeof ormConfig === "string" ? { tablePath: ormConfig } : ormConfig
35
+ this.db = knex(knexConfig)
36
+ }
37
+
38
+ async init() {
39
+ this.once("finish", async (pathList) => {
40
+ const tables: Table<any>[] = await Promise.all(
41
+ pathList.map(async (filepath) => {
42
+ return import(filepath).then((file) => file.default)
43
+ })
44
+ )
45
+
46
+ tables.unshift(
47
+ new Table<MigrationData>({
48
+ name: "migration",
49
+ priority: Infinity,
50
+ setup: (table) => {
51
+ table.string("table").unique().notNullable()
52
+ table.integer("version").notNullable()
53
+ },
54
+ })
55
+ )
56
+
57
+ tables.forEach((table) => (table.orm = this))
58
+
59
+ return Promise.all(
60
+ tables
61
+ .sort((a, b) => {
62
+ return (b.options.priority ?? 0) - (a.options.priority ?? 0)
63
+ })
64
+ .map((table) => table.make())
65
+ )
66
+ })
67
+
68
+ await this.load()
69
+ }
70
+ }
@@ -0,0 +1,106 @@
1
+ import { Knex } from "knex"
2
+ import { ORM } from "./orm.js"
3
+
4
+ export interface MigrationData {
5
+ table: string
6
+ version: number
7
+ }
8
+
9
+ export interface TableOptions<Type> {
10
+ name: string
11
+ priority?: number
12
+ migrations?: { [version: number]: (table: Knex.CreateTableBuilder) => void }
13
+ setup: (table: Knex.CreateTableBuilder) => void
14
+ }
15
+
16
+ export class Table<Type> {
17
+ orm?: ORM
18
+
19
+ constructor(public readonly options: TableOptions<Type>) {}
20
+
21
+ private get verbose() {
22
+ if (!this.orm) throw new Error("missing ORM")
23
+ return this.orm.ormConfig.verbose
24
+ }
25
+
26
+ get db() {
27
+ if (!this.orm) throw new Error("missing ORM")
28
+ return this.orm.db
29
+ }
30
+
31
+ get query() {
32
+ return this.db<Type>(this.options.name)
33
+ }
34
+
35
+ async make(): Promise<this> {
36
+ try {
37
+ await this.db.schema.createTable(this.options.name, this.options.setup)
38
+ if (this.verbose) console.log(`created table ${this.options.name}`)
39
+ } catch (error: any) {
40
+ if (error.toString().includes("syntax error")) {
41
+ if (this.verbose)
42
+ console.error(
43
+ `you need to implement the "setup" method in options of your ${this.options.name} table!`
44
+ )
45
+
46
+ throw error
47
+ } else {
48
+ if (this.verbose) console.log(`loaded table ${this.options.name}`)
49
+ }
50
+ }
51
+
52
+ try {
53
+ const migrated = await this.migrate()
54
+
55
+ if (migrated !== false) {
56
+ if (this.verbose)
57
+ console.log(
58
+ `migrated table ${this.options.name} to version ${migrated}`
59
+ )
60
+ }
61
+ } catch (error: any) {
62
+ if (this.verbose) console.error(error)
63
+ }
64
+
65
+ return this
66
+ }
67
+
68
+ private async migrate(): Promise<false | number> {
69
+ if (!this.options.migrations) return false
70
+
71
+ const migrations = new Map<
72
+ number,
73
+ (table: Knex.CreateTableBuilder) => void
74
+ >(
75
+ Object.entries(this.options.migrations)
76
+ .sort((a, b) => Number(a[0]) - Number(b[0]))
77
+ .map((entry) => [Number(entry[0]), entry[1]])
78
+ )
79
+
80
+ const fromDatabase = await this.db<MigrationData>("migration")
81
+ .where("table", this.options.name)
82
+ .first()
83
+
84
+ const data = fromDatabase || {
85
+ table: this.options.name,
86
+ version: -1,
87
+ }
88
+
89
+ const baseVersion = data.version
90
+
91
+ await this.db.schema.alterTable(this.options.name, (builder) => {
92
+ migrations.forEach((migration, version) => {
93
+ if (version <= data.version) return
94
+ migration(builder)
95
+ data.version = version
96
+ })
97
+ })
98
+
99
+ await this.db<MigrationData>("migration")
100
+ .insert(data)
101
+ .onConflict("table")
102
+ .merge()
103
+
104
+ return baseVersion === data.version ? false : data.version
105
+ }
106
+ }
package/src/index.ts ADDED
@@ -0,0 +1,2 @@
1
+ export * from "./app/orm"
2
+ export * from "./app/table"
@@ -0,0 +1,10 @@
1
+ const { Table } = require("../../dist/index")
2
+
3
+ module.exports = new Table({
4
+ name: "a",
5
+ priority: 0,
6
+ setup: (table) => {
7
+ table.increments("id").primary()
8
+ table.integer("b_id").references("id").inTable("b").notNullable()
9
+ },
10
+ })
@@ -0,0 +1,13 @@
1
+ const { Table } = require("../../dist/index")
2
+
3
+ module.exports = new Table({
4
+ name: "b",
5
+ migrations: {
6
+ 0: (table) =>
7
+ table.integer("c_id").references("id").inTable("c").notNullable(),
8
+ },
9
+ priority: 1,
10
+ setup: (table) => {
11
+ table.increments("id").primary()
12
+ },
13
+ })
@@ -0,0 +1,9 @@
1
+ const { Table } = require("../../dist/index")
2
+
3
+ module.exports = new Table({
4
+ name: "c",
5
+ priority: 2,
6
+ setup: (table) => {
7
+ table.increments("id").primary()
8
+ },
9
+ })
package/tests/test.js ADDED
@@ -0,0 +1,26 @@
1
+ const path = require("path")
2
+ const { ORM } = require("../dist/index")
3
+
4
+ const orm = new ORM({
5
+ tablePath: path.join(__dirname, "tables"),
6
+ verbose: false,
7
+ })
8
+
9
+ beforeAll(async () => {
10
+ await orm.init()
11
+ })
12
+
13
+ test("tables created", async () => {
14
+ expect(await orm.db.schema.hasTable("migration")).toBeTruthy()
15
+ expect(await orm.db.schema.hasTable("a")).toBeTruthy()
16
+ expect(await orm.db.schema.hasTable("b")).toBeTruthy()
17
+ expect(await orm.db.schema.hasTable("c")).toBeTruthy()
18
+ })
19
+
20
+ test("migrations ran", async () => {
21
+ expect(await orm.db.schema.hasColumn("b", "c_id")).toBeTruthy()
22
+ })
23
+
24
+ afterAll(async () => {
25
+ await orm.db.destroy()
26
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "outDir": "dist",
5
+ "rootDir": "src",
6
+ "module": "CommonJS",
7
+ "target": "es2020",
8
+ "lib": ["es2020", "dom"],
9
+ "moduleResolution": "Node",
10
+ "esModuleInterop": true,
11
+ "declaration": true
12
+ }
13
+ }