@ghom/orm 1.8.0 → 1.8.2

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,6 @@
1
+ import { ORM } from "./orm.js";
2
+ import { Table } from "./table.js";
3
+ 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>;
@@ -0,0 +1,18 @@
1
+ export interface ResponseCacheData<Value> {
2
+ value: Value;
3
+ expires: number;
4
+ outdated?: boolean;
5
+ }
6
+ /**
7
+ * Advanced cache for async queries
8
+ */
9
+ export declare class ResponseCache<Params extends any[], Value> {
10
+ private _request;
11
+ private _timeout;
12
+ private _cache;
13
+ constructor(_request: (...params: Params) => Value, _timeout: number);
14
+ get(id: string, ...params: Params): Value;
15
+ fetch(id: string, ...params: Params): Value;
16
+ invalidate(): void;
17
+ invalidate(id: string): void;
18
+ }
@@ -0,0 +1,76 @@
1
+ import { Handler } from "@ghom/handler";
2
+ import { Knex } from "knex";
3
+ import { TextStyle } from "./util.js";
4
+ import { Table } from "./table.js";
5
+ import { ResponseCache } from "./caching";
6
+ export interface ILogger {
7
+ log: (message: string) => void;
8
+ error: (error: string | Error) => void;
9
+ warn: (warning: string) => void;
10
+ }
11
+ export interface ORMConfig {
12
+ /**
13
+ * path to the directory that contains js files of tables
14
+ */
15
+ tableLocation: string;
16
+ /**
17
+ * database configuration
18
+ */
19
+ database?: Knex.Config;
20
+ /**
21
+ * Logger used to log the table files loaded or created.
22
+ */
23
+ logger?: ILogger;
24
+ /**
25
+ * Pattern used on logs when the table files are loaded or created. <br>
26
+ * Based on node:util.styleText style names.
27
+ */
28
+ loggerStyles?: {
29
+ highlight: TextStyle;
30
+ rawValue: TextStyle;
31
+ description: TextStyle;
32
+ };
33
+ /**
34
+ * Configuration for the database backups.
35
+ */
36
+ backups?: {
37
+ location?: string;
38
+ chunkSize?: number;
39
+ };
40
+ /**
41
+ * The cache time in milliseconds. <br>
42
+ * Default is `Infinity`.
43
+ */
44
+ caching?: number;
45
+ }
46
+ export declare class ORM {
47
+ config: ORMConfig;
48
+ private _ready;
49
+ database: Knex;
50
+ handler: Handler<Table<any>>;
51
+ _rawCache: ResponseCache<[sql: string], Knex.Raw>;
52
+ constructor(config: ORMConfig);
53
+ get cachedTables(): Table<any>[];
54
+ get cachedTableNames(): string[];
55
+ hasCachedTable(name: string): boolean;
56
+ hasTable(name: string): Promise<boolean>;
57
+ /**
58
+ * Handle the table files and create the tables in the database.
59
+ */
60
+ init(): Promise<void>;
61
+ raw(sql: Knex.Value): Knex.Raw;
62
+ cache: {
63
+ raw: (sql: string, anyDataUpdated?: boolean) => Knex.Raw;
64
+ invalidate: () => void;
65
+ };
66
+ /**
67
+ * Create a backup of the database. <br>
68
+ * The backup will be saved in the location specified in the config.
69
+ */
70
+ createBackup(dirname?: string): Promise<void>;
71
+ /**
72
+ * Restore the database from the backup. <br>
73
+ * @warning This will delete all the data in the tables.
74
+ */
75
+ restoreBackup(dirname?: string): Promise<void>;
76
+ }
@@ -0,0 +1,55 @@
1
+ import { Knex } from "knex";
2
+ import { ORM } from "./orm.js";
3
+ import { ResponseCache } from "./caching.js";
4
+ export interface MigrationData {
5
+ table: string;
6
+ version: number;
7
+ }
8
+ export interface TableOptions<Type extends object = object> {
9
+ name: string;
10
+ description?: string;
11
+ priority?: number;
12
+ /**
13
+ * The cache time in milliseconds. <br>
14
+ * Default is `Infinity`.
15
+ */
16
+ caching?: number;
17
+ migrations?: {
18
+ [version: number]: (table: Knex.CreateTableBuilder) => void;
19
+ };
20
+ then?: (this: Table<Type>, table: Table<Type>) => unknown;
21
+ setup: (table: Knex.CreateTableBuilder) => void;
22
+ }
23
+ export declare class Table<Type extends object = object> {
24
+ readonly options: TableOptions<Type>;
25
+ orm?: ORM;
26
+ _whereCache?: ResponseCache<[
27
+ cb: (query: Table<Type>["query"]) => unknown
28
+ ], unknown>;
29
+ _countCache?: ResponseCache<[where: string | null], Promise<number>>;
30
+ constructor(options: TableOptions<Type>);
31
+ get db(): Knex<any, any[]>;
32
+ get query(): Knex.QueryBuilder<Type, {
33
+ _base: Type;
34
+ _hasSelection: false;
35
+ _keys: never;
36
+ _aliases: {};
37
+ _single: false;
38
+ _intersectProps: {};
39
+ _unionProps: never;
40
+ }[]>;
41
+ get cache(): {
42
+ get: <Return>(id: string, cb: (table: Pick<Table<Type>["query"], "select" | "count" | "avg" | "sum" | "countDistinct" | "avgDistinct" | "sumDistinct">) => Return) => Return;
43
+ set: <Return_1>(cb: (table: Pick<Table<Type>["query"], "update" | "delete" | "insert" | "upsert" | "truncate" | "jsonInsert">) => Return_1) => Return_1;
44
+ count: (where?: string) => Promise<number>;
45
+ invalidate: () => void;
46
+ };
47
+ count(where?: string): Promise<number>;
48
+ hasColumn(name: keyof Type & string): Promise<boolean>;
49
+ getColumn(name: keyof Type & string): Promise<Knex.ColumnInfo>;
50
+ getColumns(): Promise<Record<keyof Type & string, Knex.ColumnInfo>>;
51
+ getColumnNames(): Promise<Array<keyof Type & string>>;
52
+ isEmpty(): Promise<boolean>;
53
+ make(): Promise<this>;
54
+ private migrate;
55
+ }
@@ -0,0 +1,9 @@
1
+ import util from "util";
2
+ export type TextStyle = Parameters<typeof util.styleText>[0];
3
+ export declare const DEFAULT_BACKUP_LOCATION: string;
4
+ export declare const DEFAULT_BACKUP_CHUNK_SIZE: number;
5
+ export declare const DEFAULT_LOGGER_HIGHLIGHT = "blueBright";
6
+ export declare const DEFAULT_LOGGER_DESCRIPTION = "grey";
7
+ export declare const DEFAULT_LOGGER_RAW_VALUE = "magentaBright";
8
+ declare let isCJS: boolean;
9
+ export { isCJS };
@@ -0,0 +1,5 @@
1
+ export * from "./app/orm.js";
2
+ export * from "./app/table.js";
3
+ export * from "./app/caching.js";
4
+ export * from "./app/backup.js";
5
+ export * from "./app/util.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ghom/orm",
3
- "version": "1.8.0",
3
+ "version": "1.8.2",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -10,10 +10,6 @@
10
10
  "prettier": {
11
11
  "semi": false
12
12
  },
13
- "exports": {
14
- "import": "./dist/esm/index.js",
15
- "require": "./dist/cjs/index.js"
16
- },
17
13
  "scripts": {
18
14
  "format": "prettier --write src tsconfig.json tests",
19
15
  "build": "rimraf dist && tsc",
package/tsconfig.json CHANGED
@@ -8,6 +8,7 @@
8
8
  "lib": ["es2020", "dom"],
9
9
  "moduleResolution": "Node",
10
10
  "esModuleInterop": true,
11
+ "declaration": true,
11
12
  "typeRoots": ["./node_modules/@types", "./dist/typings"]
12
13
  },
13
14
  "include": ["src/**/*", "dist/typings/**/*"]
package/src/app/backup.ts DELETED
@@ -1,211 +0,0 @@
1
- import fs from "fs"
2
- import path from "path"
3
- import util from "util"
4
- import csv from "json-2-csv"
5
- import csvParser from "csv-parser"
6
- import { ORM } from "./orm.js"
7
- import { Table } from "./table.js"
8
- import {
9
- DEFAULT_BACKUP_CHUNK_SIZE,
10
- DEFAULT_BACKUP_LOCATION,
11
- DEFAULT_LOGGER_HIGHLIGHT,
12
- DEFAULT_LOGGER_RAW_VALUE,
13
- } from "./util.js"
14
-
15
- export async function backupTable(table: Table, dirname?: string) {
16
- if (!table.orm) throw new Error("missing ORM")
17
-
18
- let offset = 0
19
- let chunkIndex = 0
20
-
21
- const chunkDir = path.join(
22
- table.orm.config.backups?.location ?? DEFAULT_BACKUP_LOCATION,
23
- dirname ?? "",
24
- )
25
-
26
- if (!fs.existsSync(chunkDir)) {
27
- fs.mkdirSync(chunkDir, { recursive: true })
28
-
29
- console.log(
30
- `Backup directory ${util.styleText(
31
- table.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
32
- path.relative(process.cwd(), chunkDir),
33
- )} created.`,
34
- )
35
- }
36
-
37
- try {
38
- // Compter le nombre total d'enregistrements dans la table
39
- const rowCount = await table.count()
40
- const limit = 1000 // Limite par requête
41
- const chunkCount = Math.ceil(rowCount / limit)
42
-
43
- let writeStream: fs.WriteStream | null = null
44
- const closePromises = [] // Tableau pour stocker les promesses de fermeture
45
-
46
- while (offset < rowCount) {
47
- // Récupérer un "chunk" de données
48
- const rows = await table.query.select("*").limit(limit).offset(offset)
49
-
50
- // Convertir les données en CSV
51
- const csvData = csv.json2csv(rows)
52
-
53
- // Si aucun fichier n'est créé ou qu'on a dépassé la taille max du chunk, on crée un nouveau fichier CSV
54
- if (
55
- !writeStream ||
56
- writeStream.bytesWritten + Buffer.byteLength(csvData, "utf8") >
57
- (table.orm.config.backups?.chunkSize ?? DEFAULT_BACKUP_CHUNK_SIZE)
58
- ) {
59
- if (writeStream) {
60
- closePromises.push(
61
- new Promise((resolve) => writeStream!.end(resolve)),
62
- ) // Ajouter la promesse de fermeture
63
- }
64
-
65
- const chunkFile = path.join(
66
- chunkDir,
67
- `${table.options.name}_chunk_${chunkIndex}.csv`,
68
- )
69
- writeStream = fs.createWriteStream(chunkFile, { flags: "a" })
70
- chunkIndex++
71
- }
72
-
73
- // Écrire les données dans le stream
74
- writeStream.write(csvData)
75
- offset += limit
76
-
77
- process.stdout.write(
78
- `\rBacking up table ${util.styleText(
79
- table.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
80
- table.options.name,
81
- )}: ${util.styleText(
82
- table.orm.config.loggerStyles?.rawValue ?? DEFAULT_LOGGER_RAW_VALUE,
83
- String(Math.round((chunkIndex / chunkCount) * 100)),
84
- )}%`,
85
- )
86
- }
87
-
88
- if (writeStream) {
89
- closePromises.push(new Promise((resolve) => writeStream!.end(resolve))) // Ajouter la promesse de fermeture pour le dernier stream
90
- }
91
-
92
- // Attendre que tous les flux d'écriture soient fermés
93
- await Promise.all(closePromises)
94
-
95
- console.log(
96
- `\nBackup of table ${util.styleText(
97
- table.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
98
- table.options.name,
99
- )} completed.`,
100
- )
101
- } catch (error) {
102
- console.error(
103
- `\nError while backing up table ${util.styleText(
104
- table.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
105
- table.options.name,
106
- )}:`,
107
- error,
108
- )
109
- }
110
- }
111
-
112
- export async function restoreBackup(table: Table, dirname?: string) {
113
- if (!table.orm) throw new Error("missing ORM")
114
-
115
- const chunkDir = path.join(
116
- table.orm.config.backups?.location ?? DEFAULT_BACKUP_LOCATION,
117
- dirname ?? "",
118
- )
119
-
120
- const chunkFiles = fs
121
- .readdirSync(chunkDir)
122
- .filter((file) => file.split("_chunk_")[0] === table.options.name)
123
-
124
- await table.query.truncate()
125
-
126
- try {
127
- const limit = 1000 // Limite par requête
128
-
129
- for (let chunkFile of chunkFiles) {
130
- const filePath = path.join(chunkDir, chunkFile)
131
-
132
- let rows: any[] = []
133
-
134
- await new Promise<void>((resolve, reject) => {
135
- fs.createReadStream(filePath)
136
- .pipe(csvParser())
137
- .on("data", async (row) => {
138
- rows.push(row)
139
-
140
- if (rows.length > limit) {
141
- const rowsCopy = rows.slice()
142
- rows = []
143
- await table.query.insert(rowsCopy)
144
- }
145
- })
146
- .on("end", async () => {
147
- // Insérer les données dans la table une fois le fichier entièrement lu
148
- if (rows.length > 0) await table.query.insert(rows)
149
-
150
- console.log(
151
- `Restored chunk ${util.styleText(
152
- table.orm!.config.loggerStyles?.highlight ??
153
- DEFAULT_LOGGER_HIGHLIGHT,
154
- chunkFile,
155
- )} into table ${util.styleText(
156
- table.orm!.config.loggerStyles?.highlight ??
157
- DEFAULT_LOGGER_HIGHLIGHT,
158
- table.options.name,
159
- )}.`,
160
- )
161
-
162
- resolve()
163
- })
164
- .on("error", reject)
165
- })
166
- }
167
- } catch (error) {
168
- console.error(
169
- `Error while restoring backup of table ${util.styleText(
170
- table.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
171
- table.options.name,
172
- )}:`,
173
- error,
174
- )
175
- }
176
-
177
- console.log(
178
- `Backup of table ${util.styleText(
179
- table.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
180
- table.options.name,
181
- )} restored.`,
182
- )
183
- }
184
-
185
- export async function disableForeignKeys(orm: ORM) {
186
- const result = await Promise.allSettled([
187
- orm.raw("SET session_replication_role = replica;"), // for pg
188
- orm.raw("PRAGMA foreign_keys = OFF;"), // for sqlite3
189
- orm.raw("SET FOREIGN_KEY_CHECKS = 0;"), // for mysql2
190
- ])
191
-
192
- const errors = result.filter((r) => r.status === "rejected")
193
-
194
- if (errors.length === 3) {
195
- throw new Error("Failed to disable foreign key constraints.")
196
- }
197
- }
198
-
199
- export async function enableForeignKeys(orm: ORM) {
200
- const result = await Promise.allSettled([
201
- orm.raw("SET session_replication_role = DEFAULT;"), // for pg
202
- orm.raw("PRAGMA foreign_keys = ON;"), // for sqlite3
203
- orm.raw("SET FOREIGN_KEY_CHECKS = 1;"), // for mysql2
204
- ])
205
-
206
- const errors = result.filter((r) => r.status === "rejected")
207
-
208
- if (errors.length === 3) {
209
- throw new Error("Failed to enable foreign key constraints.")
210
- }
211
- }
@@ -1,51 +0,0 @@
1
- export interface ResponseCacheData<Value> {
2
- value: Value
3
- expires: number
4
- outdated?: boolean
5
- }
6
-
7
- /**
8
- * Advanced cache for async queries
9
- */
10
- export class ResponseCache<Params extends any[], Value> {
11
- private _cache = new Map<string, ResponseCacheData<Value>>()
12
-
13
- constructor(
14
- private _request: (...params: Params) => Value,
15
- private _timeout: number,
16
- ) {}
17
-
18
- get(id: string, ...params: Params): Value {
19
- const cached = this._cache.get(id)
20
-
21
- if (!cached || cached.expires < Date.now()) {
22
- this._cache.set(id, {
23
- value: this._request(...params),
24
- expires: Date.now() + this._timeout,
25
- })
26
- }
27
-
28
- return this._cache.get(id)!.value
29
- }
30
-
31
- fetch(id: string, ...params: Params): Value {
32
- this._cache.set(id, {
33
- value: this._request(...params),
34
- expires: Date.now() + this._timeout,
35
- })
36
-
37
- return this._cache.get(id)!.value
38
- }
39
-
40
- invalidate(): void
41
- invalidate(id: string): void
42
- invalidate(id?: string): void {
43
- if (!id) {
44
- this._cache.clear()
45
-
46
- return
47
- }
48
-
49
- this._cache.delete(id)
50
- }
51
- }
package/src/app/orm.ts DELETED
@@ -1,191 +0,0 @@
1
- import url from "url"
2
- import { Handler } from "@ghom/handler"
3
- import { Knex, default as knex } from "knex"
4
- import { isCJS, TextStyle } from "./util.js"
5
- import { MigrationData, Table } from "./table.js"
6
- import {
7
- backupTable,
8
- restoreBackup,
9
- disableForeignKeys,
10
- enableForeignKeys,
11
- } from "./backup.js"
12
- import { ResponseCache } from "./caching"
13
-
14
- export interface ILogger {
15
- log: (message: string) => void
16
- error: (error: string | Error) => void
17
- warn: (warning: string) => void
18
- }
19
-
20
- export interface ORMConfig {
21
- /**
22
- * path to the directory that contains js files of tables
23
- */
24
- tableLocation: string
25
-
26
- /**
27
- * database configuration
28
- */
29
- database?: Knex.Config
30
-
31
- /**
32
- * Logger used to log the table files loaded or created.
33
- */
34
- logger?: ILogger
35
-
36
- /**
37
- * Pattern used on logs when the table files are loaded or created. <br>
38
- * Based on node:util.styleText style names.
39
- */
40
- loggerStyles?: {
41
- highlight: TextStyle
42
- rawValue: TextStyle
43
- description: TextStyle
44
- }
45
-
46
- /**
47
- * Configuration for the database backups.
48
- */
49
- backups?: {
50
- location?: string
51
- chunkSize?: number
52
- }
53
-
54
- /**
55
- * The cache time in milliseconds. <br>
56
- * Default is `Infinity`.
57
- */
58
- caching?: number
59
- }
60
-
61
- export class ORM {
62
- private _ready = false
63
-
64
- public database: Knex
65
- public handler: Handler<Table<any>>
66
-
67
- public _rawCache: ResponseCache<[sql: string], Knex.Raw>
68
-
69
- constructor(public config: ORMConfig) {
70
- this.database = knex(
71
- config.database ?? {
72
- client: "sqlite3",
73
- useNullAsDefault: true,
74
- connection: {
75
- filename: ":memory:",
76
- },
77
- },
78
- )
79
-
80
- this.handler = new Handler(config.tableLocation, {
81
- loader: (filepath) =>
82
- import(isCJS ? filepath : url.pathToFileURL(filepath).href).then(
83
- (file) => file.default,
84
- ),
85
- pattern: /\.js$/,
86
- })
87
-
88
- this._rawCache = new ResponseCache(
89
- (raw: string) => this.raw(raw),
90
- config.caching ?? Infinity,
91
- )
92
- }
93
-
94
- get cachedTables() {
95
- return [...this.handler.elements.values()]
96
- }
97
-
98
- get cachedTableNames() {
99
- return this.cachedTables.map((table) => table.options.name)
100
- }
101
-
102
- hasCachedTable(name: string) {
103
- return this.cachedTables.some((table) => table.options.name === name)
104
- }
105
-
106
- async hasTable(name: string): Promise<boolean> {
107
- return this.database.schema.hasTable(name)
108
- }
109
-
110
- /**
111
- * Handle the table files and create the tables in the database.
112
- */
113
- async init() {
114
- await this.handler.init()
115
-
116
- await enableForeignKeys(this)
117
-
118
- this.handler.elements.set(
119
- "migration",
120
- new Table<MigrationData>({
121
- name: "migration",
122
- priority: Infinity,
123
- setup: (table) => {
124
- table.string("table").unique().notNullable()
125
- table.integer("version").notNullable()
126
- },
127
- }),
128
- )
129
-
130
- for (const table of this.cachedTables.sort(
131
- (a, b) => (b.options.priority ?? 0) - (a.options.priority ?? 0),
132
- )) {
133
- table.orm = this
134
- await table.make()
135
- }
136
-
137
- this._ready = true
138
- }
139
-
140
- raw(sql: Knex.Value): Knex.Raw {
141
- if (this._ready) this.cache.invalidate()
142
- return this.database.raw(sql)
143
- }
144
-
145
- cache = {
146
- raw: (sql: string, anyDataUpdated?: boolean): Knex.Raw => {
147
- if (anyDataUpdated) this.cache.invalidate()
148
- return this._rawCache!.get(sql, sql)
149
- },
150
- invalidate: () => {
151
- this._rawCache.invalidate()
152
- this.cachedTables.forEach((table) => table.cache.invalidate())
153
- },
154
- }
155
-
156
- /**
157
- * Create a backup of the database. <br>
158
- * The backup will be saved in the location specified in the config.
159
- */
160
- async createBackup(dirname?: string) {
161
- try {
162
- for (let table of this.cachedTables) {
163
- await backupTable(table, dirname)
164
- }
165
-
166
- console.log("Database backup created.")
167
- } catch (error) {
168
- console.error("Error while creating backup of the database.", error)
169
- }
170
- }
171
-
172
- /**
173
- * Restore the database from the backup. <br>
174
- * @warning This will delete all the data in the tables.
175
- */
176
- async restoreBackup(dirname?: string) {
177
- try {
178
- await disableForeignKeys(this)
179
-
180
- for (let table of this.cachedTables) {
181
- await restoreBackup(table, dirname)
182
- }
183
-
184
- await enableForeignKeys(this)
185
-
186
- console.log("Database restored from backup.")
187
- } catch (error) {
188
- console.error("Error while restoring backup of the database.", error)
189
- }
190
- }
191
- }
package/src/app/table.ts DELETED
@@ -1,252 +0,0 @@
1
- import util from "util"
2
- import { Knex } from "knex"
3
- import { ORM } from "./orm.js"
4
- import { ResponseCache } from "./caching.js"
5
- import {
6
- DEFAULT_LOGGER_DESCRIPTION,
7
- DEFAULT_LOGGER_HIGHLIGHT,
8
- DEFAULT_LOGGER_RAW_VALUE,
9
- } from "./util.js"
10
-
11
- export interface MigrationData {
12
- table: string
13
- version: number
14
- }
15
-
16
- export interface TableOptions<Type extends object = object> {
17
- name: string
18
- description?: string
19
- priority?: number
20
- /**
21
- * The cache time in milliseconds. <br>
22
- * Default is `Infinity`.
23
- */
24
- caching?: number
25
- migrations?: { [version: number]: (table: Knex.CreateTableBuilder) => void }
26
- then?: (this: Table<Type>, table: Table<Type>) => unknown
27
- setup: (table: Knex.CreateTableBuilder) => void
28
- }
29
-
30
- export class Table<Type extends object = object> {
31
- public orm?: ORM
32
-
33
- public _whereCache?: ResponseCache<
34
- [cb: (query: Table<Type>["query"]) => unknown],
35
- unknown
36
- >
37
-
38
- public _countCache?: ResponseCache<[where: string | null], Promise<number>>
39
-
40
- constructor(public readonly options: TableOptions<Type>) {}
41
-
42
- get db() {
43
- if (!this.orm) throw new Error("missing ORM")
44
- return this.orm.database
45
- }
46
-
47
- get query() {
48
- return this.db<Type>(this.options.name)
49
- }
50
-
51
- get cache() {
52
- if (!this._whereCache || !this._countCache) throw new Error("missing cache")
53
-
54
- if (!this.orm) throw new Error("missing ORM")
55
-
56
- return {
57
- get: <Return>(
58
- id: string,
59
- cb: (
60
- table: Pick<
61
- Table<Type>["query"],
62
- | "select"
63
- | "count"
64
- | "avg"
65
- | "sum"
66
- | "countDistinct"
67
- | "avgDistinct"
68
- | "sumDistinct"
69
- >,
70
- ) => Return,
71
- ): Return => {
72
- return this._whereCache!.get(id, cb) as Return
73
- },
74
- set: <Return>(
75
- cb: (
76
- table: Pick<
77
- Table<Type>["query"],
78
- | "update"
79
- | "delete"
80
- | "insert"
81
- | "upsert"
82
- | "truncate"
83
- | "jsonInsert"
84
- >,
85
- ) => Return,
86
- ) => {
87
- // todo: invalidate only the related tables
88
- this.orm!.cache.invalidate()
89
- return cb(this.query)
90
- },
91
- count: (where?: string) => {
92
- return this._countCache!.get(where ?? "*", where ?? null)
93
- },
94
- invalidate: () => {
95
- this._whereCache!.invalidate()
96
- this._countCache!.invalidate()
97
- this.orm!._rawCache.invalidate()
98
- },
99
- }
100
- }
101
-
102
- async count(where?: string): Promise<number> {
103
- return this.query
104
- .select(this.db.raw("count(*) as total"))
105
- .whereRaw(where ?? "1=1")
106
- .then((rows) => (rows[0] as unknown as { total: number }).total)
107
- }
108
-
109
- async hasColumn(name: keyof Type & string): Promise<boolean> {
110
- return this.db.schema.hasColumn(this.options.name, name as string)
111
- }
112
-
113
- async getColumn(name: keyof Type & string): Promise<Knex.ColumnInfo> {
114
- return this.db(this.options.name).columnInfo(name)
115
- }
116
-
117
- async getColumns(): Promise<Record<keyof Type & string, Knex.ColumnInfo>> {
118
- return this.db(this.options.name).columnInfo()
119
- }
120
-
121
- async getColumnNames(): Promise<Array<keyof Type & string>> {
122
- return this.getColumns().then(Object.keys) as Promise<
123
- Array<keyof Type & string>
124
- >
125
- }
126
-
127
- async isEmpty(): Promise<boolean> {
128
- return this.count().then((count) => +count === 0)
129
- }
130
-
131
- async make(): Promise<this> {
132
- if (!this.orm) throw new Error("missing ORM")
133
-
134
- this._whereCache = new ResponseCache(
135
- (cb: (query: Knex.QueryBuilder<Type>) => unknown) => cb(this.query),
136
- this.options.caching ?? this.orm?.config.caching ?? Infinity,
137
- )
138
-
139
- this._countCache = new ResponseCache(
140
- (where: string | null) => this.count(where ?? undefined),
141
- this.options.caching ?? this.orm?.config.caching ?? Infinity,
142
- )
143
-
144
- try {
145
- await this.db.schema.createTable(this.options.name, this.options.setup)
146
-
147
- this.orm.config.logger?.log(
148
- `created table ${util.styleText(
149
- this.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
150
- this.options.name,
151
- )}${
152
- this.options.description
153
- ? ` ${util.styleText(
154
- this.orm.config.loggerStyles?.description ??
155
- DEFAULT_LOGGER_DESCRIPTION,
156
- this.options.description,
157
- )}`
158
- : ""
159
- }`,
160
- )
161
- } catch (error: any) {
162
- if (error.toString().includes("syntax error")) {
163
- this.orm.config.logger?.error(
164
- `you need to implement the "setup" method in options of your ${util.styleText(
165
- this.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
166
- this.options.name,
167
- )} table!`,
168
- )
169
-
170
- throw error
171
- } else {
172
- this.orm.config.logger?.log(
173
- `loaded table ${util.styleText(
174
- this.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
175
- this.options.name,
176
- )}${
177
- this.options.description
178
- ? ` ${util.styleText(
179
- this.orm.config.loggerStyles?.description ??
180
- DEFAULT_LOGGER_DESCRIPTION,
181
- this.options.description,
182
- )}`
183
- : ""
184
- }`,
185
- )
186
- }
187
- }
188
-
189
- try {
190
- const migrated = await this.migrate()
191
-
192
- if (migrated !== false) {
193
- this.orm.config.logger?.log(
194
- `migrated table ${util.styleText(
195
- this.orm.config.loggerStyles?.highlight ?? DEFAULT_LOGGER_HIGHLIGHT,
196
- this.options.name,
197
- )} to version ${util.styleText(
198
- this.orm.config.loggerStyles?.rawValue ?? DEFAULT_LOGGER_RAW_VALUE,
199
- String(migrated),
200
- )}`,
201
- )
202
- }
203
- } catch (error: any) {
204
- this.orm.config.logger?.error(error)
205
-
206
- throw error
207
- }
208
-
209
- if ((await this.count()) === 0) await this.options.then?.bind(this)(this)
210
-
211
- return this
212
- }
213
-
214
- private async migrate(): Promise<false | number> {
215
- if (!this.options.migrations) return false
216
-
217
- const migrations = new Map<
218
- number,
219
- (table: Knex.CreateTableBuilder) => void
220
- >(
221
- Object.entries(this.options.migrations)
222
- .sort((a, b) => Number(a[0]) - Number(b[0]))
223
- .map((entry) => [Number(entry[0]), entry[1]]),
224
- )
225
-
226
- const fromDatabase = await this.db<MigrationData>("migration")
227
- .where("table", this.options.name)
228
- .first()
229
-
230
- const data = fromDatabase || {
231
- table: this.options.name,
232
- version: -Infinity,
233
- }
234
-
235
- const baseVersion = data.version
236
-
237
- await this.db.schema.alterTable(this.options.name, (builder) => {
238
- migrations.forEach((migration, version) => {
239
- if (version <= data.version) return
240
- migration(builder)
241
- data.version = version
242
- })
243
- })
244
-
245
- await this.db<MigrationData>("migration")
246
- .insert(data)
247
- .onConflict("table")
248
- .merge()
249
-
250
- return baseVersion === data.version ? false : data.version
251
- }
252
- }
package/src/app/util.ts DELETED
@@ -1,30 +0,0 @@
1
- import util from "util"
2
- import path from "path"
3
- import fs from "fs"
4
-
5
- export type TextStyle = Parameters<typeof util.styleText>[0]
6
-
7
- export const DEFAULT_BACKUP_LOCATION = path.join(process.cwd(), "backup")
8
- export const DEFAULT_BACKUP_CHUNK_SIZE = 5 * 1024 * 1024 // 5MB
9
-
10
- export const DEFAULT_LOGGER_HIGHLIGHT = "blueBright"
11
- export const DEFAULT_LOGGER_DESCRIPTION = "grey"
12
- export const DEFAULT_LOGGER_RAW_VALUE = "magentaBright"
13
-
14
- let isCJS: boolean = false
15
-
16
- try {
17
- const pack = JSON.parse(
18
- fs.readFileSync(path.join(process.cwd(), "package.json"), "utf8"),
19
- )
20
-
21
- isCJS = pack.type === "commonjs" || pack.type == void 0
22
- } catch {
23
- throw new Error(
24
- "Missing package.json: Can't detect the type of modules.\n" +
25
- "The ORM needs a package.json file present in the process's current working directory.\n" +
26
- "Please create a package.json file or run the project from another entry point.",
27
- )
28
- }
29
-
30
- export { isCJS }
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- export * from "./app/orm.js"
2
- export * from "./app/table.js"
3
- export * from "./app/caching.js"
4
- export * from "./app/backup.js"
5
- export * from "./app/util.js"