@aceitadev/adatabase 0.1.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.
- package/README.md +66 -0
- package/dist/ActiveRecord.d.ts +12 -0
- package/dist/ActiveRecord.js +115 -0
- package/dist/ColumnAdapter.d.ts +8 -0
- package/dist/ColumnAdapter.js +12 -0
- package/dist/Database.d.ts +12 -0
- package/dist/Database.js +58 -0
- package/dist/MySQL.d.ts +18 -0
- package/dist/MySQL.js +72 -0
- package/dist/PersistenceException.d.ts +4 -0
- package/dist/PersistenceException.js +16 -0
- package/dist/QueryBuilder.d.ts +17 -0
- package/dist/QueryBuilder.js +180 -0
- package/dist/SchemaManager.d.ts +11 -0
- package/dist/SchemaManager.js +200 -0
- package/dist/adapters/IDatabaseAdapter.d.ts +12 -0
- package/dist/adapters/IDatabaseAdapter.js +2 -0
- package/dist/adapters/MySQLAdapter.d.ts +14 -0
- package/dist/adapters/MySQLAdapter.js +74 -0
- package/dist/adapters/PostgresAdapter.d.ts +16 -0
- package/dist/adapters/PostgresAdapter.js +85 -0
- package/dist/decorators/BelongsTo.d.ts +7 -0
- package/dist/decorators/BelongsTo.js +17 -0
- package/dist/decorators/Column.d.ts +12 -0
- package/dist/decorators/Column.js +17 -0
- package/dist/decorators/HasMany.d.ts +7 -0
- package/dist/decorators/HasMany.js +17 -0
- package/dist/decorators/HasOne.d.ts +7 -0
- package/dist/decorators/HasOne.js +17 -0
- package/dist/decorators/Id.d.ts +1 -0
- package/dist/decorators/Id.js +12 -0
- package/dist/decorators/Nullable.d.ts +2 -0
- package/dist/decorators/Nullable.js +16 -0
- package/dist/decorators/Table.d.ts +2 -0
- package/dist/decorators/Table.js +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.js +29 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
```markdown
|
|
2
|
+
# aMySQL - Node.js (TypeScript) port
|
|
3
|
+
|
|
4
|
+
Esta é uma implementação inicial do seu sistema aMySQL para Node.js usando TypeScript e decorators.
|
|
5
|
+
|
|
6
|
+
Principais características
|
|
7
|
+
- Pool de conexões (mysql2/promise)
|
|
8
|
+
- Decorators: @Table, @Column, @Id, @Nullable
|
|
9
|
+
- ColumnAdapter para tipos serializados customizados
|
|
10
|
+
- ActiveRecord com save/update/delete
|
|
11
|
+
- QueryBuilder (where/order/limit)
|
|
12
|
+
- SchemaManager para criar/atualizar colunas automaticamente
|
|
13
|
+
|
|
14
|
+
Como usar (exemplo rápido)
|
|
15
|
+
|
|
16
|
+
1) Instale dependências
|
|
17
|
+
npm install
|
|
18
|
+
|
|
19
|
+
2) Configure tsconfig (já incluído) e habilite experimentalDecorators e emitDecoratorMetadata.
|
|
20
|
+
|
|
21
|
+
3) Exemplo de modelo (src/example.ts)
|
|
22
|
+
```ts
|
|
23
|
+
import "reflect-metadata";
|
|
24
|
+
import { init } from "./MySQL";
|
|
25
|
+
import { Table } from "./decorators/Table";
|
|
26
|
+
import { Column } from "./decorators/Column";
|
|
27
|
+
import { Id } from "./decorators/Id";
|
|
28
|
+
import { ActiveRecord } from "./ActiveRecord";
|
|
29
|
+
import { SchemaManager } from "./SchemaManager";
|
|
30
|
+
|
|
31
|
+
@Table("users")
|
|
32
|
+
class User extends ActiveRecord {
|
|
33
|
+
@Id()
|
|
34
|
+
id!: number;
|
|
35
|
+
|
|
36
|
+
@Column({ limit: 150 })
|
|
37
|
+
name!: string;
|
|
38
|
+
|
|
39
|
+
@Column()
|
|
40
|
+
email!: string;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
async function main() {
|
|
44
|
+
await init({ host: "127.0.0.1", database: "test", user: "root", password: "" });
|
|
45
|
+
const sm = new SchemaManager([User]);
|
|
46
|
+
await sm.migrate();
|
|
47
|
+
|
|
48
|
+
const u = new User();
|
|
49
|
+
u.name = "Fulano";
|
|
50
|
+
u.email = "fulano@example.com";
|
|
51
|
+
await u.save();
|
|
52
|
+
|
|
53
|
+
const fetched = await User.find().where("email", "=", "fulano@example.com").first();
|
|
54
|
+
console.log(fetched);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main().catch(console.error);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Limitações & próximos passos
|
|
61
|
+
- QueryBuilder suporta nomes de campos como strings; não foi implementado introspecção de lambda getter como no Java.
|
|
62
|
+
- Adapters: você fornece uma classe com métodos serialize/deserialize para o Column decorator.
|
|
63
|
+
- Migração: apenas cria tabelas e adiciona colunas que faltam; não altera ou remove colunas existentes.
|
|
64
|
+
- Recomendado: adicionar testes, melhorar mapeamento de tipos (enums, UUID), e adicionar caching e transações.
|
|
65
|
+
|
|
66
|
+
```
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { QueryBuilder } from "./QueryBuilder";
|
|
2
|
+
import { GenericConnection } from "./adapters/IDatabaseAdapter";
|
|
3
|
+
type Constructor<T> = {
|
|
4
|
+
new (...args: any[]): T;
|
|
5
|
+
};
|
|
6
|
+
export declare abstract class ActiveRecord {
|
|
7
|
+
private static getIdField;
|
|
8
|
+
save<T extends ActiveRecord>(this: T, tx?: GenericConnection): Promise<T>;
|
|
9
|
+
static find<T extends ActiveRecord>(this: Constructor<T>): QueryBuilder<T>;
|
|
10
|
+
delete(this: ActiveRecord, tx?: GenericConnection): Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ActiveRecord = void 0;
|
|
13
|
+
const Database_1 = require("./Database");
|
|
14
|
+
const Column_1 = require("./decorators/Column");
|
|
15
|
+
const PersistenceException_1 = require("./PersistenceException");
|
|
16
|
+
const QueryBuilder_1 = require("./QueryBuilder");
|
|
17
|
+
const Table_1 = require("./decorators/Table");
|
|
18
|
+
function camelToSnake(s) {
|
|
19
|
+
return s.replace(/([a-z0-9])([A-Z])/g, "$1_$2").toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
class ActiveRecord {
|
|
22
|
+
static getIdField(ctor) {
|
|
23
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
24
|
+
const columns = (0, Column_1.getColumnMeta)(ctor);
|
|
25
|
+
if (!columns)
|
|
26
|
+
throw new PersistenceException_1.PersistenceException("Model has no @Column decorators", null);
|
|
27
|
+
for (const [prop, opts] of columns.entries()) {
|
|
28
|
+
if (opts === null || opts === void 0 ? void 0 : opts.id) {
|
|
29
|
+
return prop;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
throw new PersistenceException_1.PersistenceException("No @Id field on class", null);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
save(tx) {
|
|
36
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
const ctor = this.constructor;
|
|
38
|
+
const table = (0, Table_1.getTableName)(ctor);
|
|
39
|
+
if (!table)
|
|
40
|
+
throw new PersistenceException_1.PersistenceException("No @Table defined on class", null);
|
|
41
|
+
const columns = (0, Column_1.getColumnMeta)(ctor);
|
|
42
|
+
if (!columns)
|
|
43
|
+
throw new PersistenceException_1.PersistenceException("No @Column decorators found on class", null);
|
|
44
|
+
const conn = tx !== null && tx !== void 0 ? tx : yield (0, Database_1.getConnection)();
|
|
45
|
+
try {
|
|
46
|
+
const idField = yield ActiveRecord.getIdField(ctor);
|
|
47
|
+
const colEntries = [];
|
|
48
|
+
for (const [prop, opts] of columns.entries()) {
|
|
49
|
+
if (opts === null || opts === void 0 ? void 0 : opts.id)
|
|
50
|
+
continue;
|
|
51
|
+
if (!this.hasOwnProperty(prop))
|
|
52
|
+
continue;
|
|
53
|
+
const value = this[prop];
|
|
54
|
+
const colName = (opts === null || opts === void 0 ? void 0 : opts.name) ? opts.name : camelToSnake(prop);
|
|
55
|
+
colEntries.push({ colName, value });
|
|
56
|
+
}
|
|
57
|
+
const idValue = this[idField];
|
|
58
|
+
if (idValue == null || idValue === 0) {
|
|
59
|
+
const cols = colEntries.map(c => `\`${c.colName}\``).join(", ");
|
|
60
|
+
const placeholders = colEntries.map(() => "?").join(", ");
|
|
61
|
+
const params = colEntries.map(c => c.value);
|
|
62
|
+
const sql = `INSERT INTO \`${table}\` (${cols}) VALUES (${placeholders});`;
|
|
63
|
+
const res = yield (0, Database_1.execute)(sql, params, conn);
|
|
64
|
+
if (res.insertId) {
|
|
65
|
+
this[idField] = res.insertId;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const setClause = colEntries.map(c => `\`${c.colName}\` = ?`).join(", ");
|
|
70
|
+
const params = [...colEntries.map(c => c.value), idValue];
|
|
71
|
+
const idColName = camelToSnake(idField);
|
|
72
|
+
const sql = `UPDATE \`${table}\` SET ${setClause} WHERE \`${idColName}\` = ?;`;
|
|
73
|
+
yield (0, Database_1.execute)(sql, params, conn);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
if (e.code === 'ER_DUP_ENTRY') {
|
|
78
|
+
throw new PersistenceException_1.PersistenceException(`Duplicate entry violation: ${e.message}`, e);
|
|
79
|
+
}
|
|
80
|
+
throw new PersistenceException_1.PersistenceException(`Failed to save entity: ${e.message}`, e);
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
if (!tx && conn.release) {
|
|
84
|
+
conn.release();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return this;
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
static find() {
|
|
91
|
+
return new QueryBuilder_1.QueryBuilder(this);
|
|
92
|
+
}
|
|
93
|
+
delete(tx) {
|
|
94
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
95
|
+
const ctor = this.constructor;
|
|
96
|
+
const table = (0, Table_1.getTableName)(ctor);
|
|
97
|
+
const idField = yield ActiveRecord.getIdField(ctor);
|
|
98
|
+
const idValue = this[idField];
|
|
99
|
+
if (idValue == null)
|
|
100
|
+
return;
|
|
101
|
+
const conn = tx !== null && tx !== void 0 ? tx : yield (0, Database_1.getConnection)();
|
|
102
|
+
try {
|
|
103
|
+
const idColName = camelToSnake(idField);
|
|
104
|
+
const sql = `DELETE FROM \`${table}\` WHERE \`${idColName}\` = ?`;
|
|
105
|
+
yield (0, Database_1.execute)(sql, [idValue], conn);
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
if (!tx && conn.release) {
|
|
109
|
+
conn.release();
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
exports.ActiveRecord = ActiveRecord;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.NoneAdapter = void 0;
|
|
4
|
+
class NoneAdapter {
|
|
5
|
+
serialize() {
|
|
6
|
+
throw new Error("NoneAdapter should not be used.");
|
|
7
|
+
}
|
|
8
|
+
deserialize() {
|
|
9
|
+
throw new Error("NoneAdapter should not be used.");
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
exports.NoneAdapter = NoneAdapter;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { IDatabaseAdapter, GenericConnection } from './adapters/IDatabaseAdapter';
|
|
2
|
+
type DbType = 'mysql' | 'postgres';
|
|
3
|
+
export declare function init(dbType: DbType, config: any): void;
|
|
4
|
+
export declare function getAdapter(): IDatabaseAdapter;
|
|
5
|
+
export declare function query(sql: string, params?: any[], connection?: GenericConnection): Promise<any[]>;
|
|
6
|
+
export declare function execute(sql: string, params?: any[], connection?: GenericConnection): Promise<{
|
|
7
|
+
insertId?: number;
|
|
8
|
+
affectedRows?: number;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function getConnection(): Promise<GenericConnection>;
|
|
11
|
+
export declare function closePool(): Promise<void>;
|
|
12
|
+
export {};
|
package/dist/Database.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.init = init;
|
|
13
|
+
exports.getAdapter = getAdapter;
|
|
14
|
+
exports.query = query;
|
|
15
|
+
exports.execute = execute;
|
|
16
|
+
exports.getConnection = getConnection;
|
|
17
|
+
exports.closePool = closePool;
|
|
18
|
+
const MySQLAdapter_1 = require("./adapters/MySQLAdapter");
|
|
19
|
+
const PostgresAdapter_1 = require("./adapters/PostgresAdapter");
|
|
20
|
+
let adapter;
|
|
21
|
+
function init(dbType, config) {
|
|
22
|
+
if (dbType === 'mysql') {
|
|
23
|
+
adapter = new MySQLAdapter_1.MySQLAdapter();
|
|
24
|
+
}
|
|
25
|
+
else if (dbType === 'postgres') {
|
|
26
|
+
adapter = new PostgresAdapter_1.PostgresAdapter();
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
throw new Error(`Tipo de banco de dados não suportado: ${dbType}`);
|
|
30
|
+
}
|
|
31
|
+
adapter.init(config);
|
|
32
|
+
}
|
|
33
|
+
function getAdapter() {
|
|
34
|
+
if (!adapter) {
|
|
35
|
+
throw new Error("O banco de dados não foi inicializado. Chame a função init() primeiro.");
|
|
36
|
+
}
|
|
37
|
+
return adapter;
|
|
38
|
+
}
|
|
39
|
+
function query(sql_1) {
|
|
40
|
+
return __awaiter(this, arguments, void 0, function* (sql, params = [], connection) {
|
|
41
|
+
return getAdapter().query(sql, params, connection);
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
function execute(sql_1) {
|
|
45
|
+
return __awaiter(this, arguments, void 0, function* (sql, params = [], connection) {
|
|
46
|
+
return getAdapter().execute(sql, params, connection);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
function getConnection() {
|
|
50
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
51
|
+
return getAdapter().getConnection();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
function closePool() {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
return getAdapter().close();
|
|
57
|
+
});
|
|
58
|
+
}
|
package/dist/MySQL.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import mysql, { PoolConnection } from "mysql2/promise";
|
|
2
|
+
export type Auth = {
|
|
3
|
+
host: string;
|
|
4
|
+
port?: number;
|
|
5
|
+
database: string;
|
|
6
|
+
user: string;
|
|
7
|
+
password?: string;
|
|
8
|
+
};
|
|
9
|
+
export type PoolOptions = {
|
|
10
|
+
connectionLimit?: number;
|
|
11
|
+
queueLimit?: number;
|
|
12
|
+
idleTimeout?: number;
|
|
13
|
+
waitForConnections?: boolean;
|
|
14
|
+
};
|
|
15
|
+
export declare function init(auth: Auth, options?: PoolOptions): void;
|
|
16
|
+
export declare function getConnection(): Promise<PoolConnection>;
|
|
17
|
+
export declare function query(sql: string, params?: any[]): Promise<mysql.QueryResult>;
|
|
18
|
+
export declare function closePool(): Promise<void>;
|
package/dist/MySQL.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
12
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
13
|
+
};
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.init = init;
|
|
16
|
+
exports.getConnection = getConnection;
|
|
17
|
+
exports.query = query;
|
|
18
|
+
exports.closePool = closePool;
|
|
19
|
+
const promise_1 = __importDefault(require("mysql2/promise"));
|
|
20
|
+
let pool = null;
|
|
21
|
+
function init(auth, options = {}) {
|
|
22
|
+
var _a, _b, _c, _d, _e;
|
|
23
|
+
if (pool) {
|
|
24
|
+
console.warn("[aMySQL] Pool de conexões já foi inicializado. Ignorando nova chamada.");
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
pool = promise_1.default.createPool({
|
|
29
|
+
host: auth.host,
|
|
30
|
+
port: (_a = auth.port) !== null && _a !== void 0 ? _a : 3306,
|
|
31
|
+
database: auth.database,
|
|
32
|
+
user: auth.user,
|
|
33
|
+
password: auth.password,
|
|
34
|
+
waitForConnections: (_b = options.waitForConnections) !== null && _b !== void 0 ? _b : true,
|
|
35
|
+
connectionLimit: (_c = options.connectionLimit) !== null && _c !== void 0 ? _c : 10,
|
|
36
|
+
queueLimit: (_d = options.queueLimit) !== null && _d !== void 0 ? _d : 0,
|
|
37
|
+
idleTimeout: (_e = options.idleTimeout) !== null && _e !== void 0 ? _e : 60000,
|
|
38
|
+
namedPlaceholders: false
|
|
39
|
+
});
|
|
40
|
+
console.log("[aMySQL] ✅ Pool de conexões inicializado com sucesso.");
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error("[aMySQL] ❌ Falha catastrófica ao inicializar o pool de conexões:", error);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function getPool() {
|
|
48
|
+
if (!pool) {
|
|
49
|
+
throw new Error("[aMySQL] O pool de conexões não foi inicializado. Chame a função init() na inicialização da sua aplicação.");
|
|
50
|
+
}
|
|
51
|
+
return pool;
|
|
52
|
+
}
|
|
53
|
+
function getConnection() {
|
|
54
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
55
|
+
return getPool().getConnection();
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function query(sql_1) {
|
|
59
|
+
return __awaiter(this, arguments, void 0, function* (sql, params = []) {
|
|
60
|
+
const [rows] = yield getPool().query(sql, params);
|
|
61
|
+
return rows;
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
function closePool() {
|
|
65
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
66
|
+
if (pool) {
|
|
67
|
+
yield pool.end();
|
|
68
|
+
pool = null;
|
|
69
|
+
console.log("[aMySQL] Pool de conexões foi fechado com sucesso.");
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PersistenceException = void 0;
|
|
4
|
+
class PersistenceException extends Error {
|
|
5
|
+
constructor(message, cause) {
|
|
6
|
+
var _a, _b;
|
|
7
|
+
super(message);
|
|
8
|
+
this.cause = cause;
|
|
9
|
+
if (cause) {
|
|
10
|
+
// preserve stack if present
|
|
11
|
+
this.stack = ((_a = this.stack) !== null && _a !== void 0 ? _a : "") + "\nCaused by: " + ((_b = cause.stack) !== null && _b !== void 0 ? _b : cause.message);
|
|
12
|
+
}
|
|
13
|
+
Object.setPrototypeOf(this, PersistenceException.prototype);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
exports.PersistenceException = PersistenceException;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ActiveRecord } from "./ActiveRecord";
|
|
2
|
+
export declare class QueryBuilder<T extends ActiveRecord> {
|
|
3
|
+
private model;
|
|
4
|
+
private whereClauses;
|
|
5
|
+
private _limit?;
|
|
6
|
+
private _includes;
|
|
7
|
+
private primaryKey;
|
|
8
|
+
constructor(model: {
|
|
9
|
+
new (): T;
|
|
10
|
+
});
|
|
11
|
+
where(field: keyof T | string, operator: string, value: any): this;
|
|
12
|
+
include(...models: (new (...args: any[]) => any)[]): this;
|
|
13
|
+
first(): Promise<T | null>;
|
|
14
|
+
get(): Promise<T[]>;
|
|
15
|
+
private getColumnName;
|
|
16
|
+
private mapRowsToEntities;
|
|
17
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.QueryBuilder = void 0;
|
|
13
|
+
const Table_1 = require("./decorators/Table");
|
|
14
|
+
const Column_1 = require("./decorators/Column");
|
|
15
|
+
const Database_1 = require("./Database");
|
|
16
|
+
const PersistenceException_1 = require("./PersistenceException");
|
|
17
|
+
const BelongsTo_1 = require("./decorators/BelongsTo");
|
|
18
|
+
const HasMany_1 = require("./decorators/HasMany");
|
|
19
|
+
const HasOne_1 = require("./decorators/HasOne");
|
|
20
|
+
function camelToSnake(s) {
|
|
21
|
+
return s.replace(/([a-z0-g])([A-Z])/g, "$1_$2").toLowerCase();
|
|
22
|
+
}
|
|
23
|
+
class QueryBuilder {
|
|
24
|
+
constructor(model) {
|
|
25
|
+
this.model = model;
|
|
26
|
+
this.whereClauses = [];
|
|
27
|
+
this._includes = [];
|
|
28
|
+
this.primaryKey = 'id';
|
|
29
|
+
const colMeta = (0, Column_1.getColumnMeta)(this.model);
|
|
30
|
+
if (colMeta) {
|
|
31
|
+
for (const [prop, opts] of colMeta.entries()) {
|
|
32
|
+
if (opts.id) {
|
|
33
|
+
this.primaryKey = prop;
|
|
34
|
+
break;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
where(field, operator, value) {
|
|
40
|
+
this.whereClauses.push({ field: field, operator, value, booleanOp: "AND" });
|
|
41
|
+
return this;
|
|
42
|
+
}
|
|
43
|
+
include(...models) {
|
|
44
|
+
this._includes.push(...models);
|
|
45
|
+
return this;
|
|
46
|
+
}
|
|
47
|
+
first() {
|
|
48
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
49
|
+
this._limit = 1;
|
|
50
|
+
const rows = yield this.get();
|
|
51
|
+
return rows.length > 0 ? rows[0] : null;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
get() {
|
|
55
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
56
|
+
const table = (0, Table_1.getTableName)(this.model);
|
|
57
|
+
if (!table)
|
|
58
|
+
throw new PersistenceException_1.PersistenceException("Model has no @Table", null);
|
|
59
|
+
const columnsMeta = (0, Column_1.getColumnMeta)(this.model);
|
|
60
|
+
if (!columnsMeta)
|
|
61
|
+
throw new PersistenceException_1.PersistenceException("Model has no @Column decorators", null);
|
|
62
|
+
const params = [];
|
|
63
|
+
const baseAlias = 't1';
|
|
64
|
+
let selectFields = `${baseAlias}.*`;
|
|
65
|
+
let joinClause = '';
|
|
66
|
+
const allRelationMetas = [
|
|
67
|
+
{ meta: (0, BelongsTo_1.getBelongsToMeta)(this.model), type: 'BelongsTo' },
|
|
68
|
+
{ meta: (0, HasMany_1.getHasManyMeta)(this.model), type: 'HasMany' },
|
|
69
|
+
{ meta: (0, HasOne_1.getHasOneMeta)(this.model), type: 'HasOne' }
|
|
70
|
+
];
|
|
71
|
+
if (this._includes.length > 0) {
|
|
72
|
+
this._includes.forEach((includeModel, index) => {
|
|
73
|
+
const relationAlias = `t${index + 2}`;
|
|
74
|
+
let relationInfo = null;
|
|
75
|
+
for (const { meta, type } of allRelationMetas) {
|
|
76
|
+
if (meta) {
|
|
77
|
+
for (const [prop, opts] of meta.entries()) {
|
|
78
|
+
if (opts.model() === includeModel) {
|
|
79
|
+
relationInfo = { prop, opts, type };
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (relationInfo)
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
if (relationInfo) {
|
|
88
|
+
const relatedTable = (0, Table_1.getTableName)(includeModel);
|
|
89
|
+
const relatedColumnsMeta = (0, Column_1.getColumnMeta)(includeModel);
|
|
90
|
+
if (!relatedTable || !relatedColumnsMeta)
|
|
91
|
+
return;
|
|
92
|
+
for (const prop of relatedColumnsMeta.keys()) {
|
|
93
|
+
const colName = this.getColumnName(prop, relatedColumnsMeta);
|
|
94
|
+
selectFields += `, ${relationAlias}.\`${colName}\` AS \`${relationInfo.prop}__${prop}\``;
|
|
95
|
+
}
|
|
96
|
+
if (relationInfo.type === 'BelongsTo') {
|
|
97
|
+
const foreignKey = relationInfo.opts.foreignKey;
|
|
98
|
+
const foreignKeyCol = this.getColumnName(foreignKey, columnsMeta);
|
|
99
|
+
joinClause += ` LEFT JOIN \`${relatedTable}\` AS ${relationAlias} ON ${baseAlias}.\`${foreignKeyCol}\` = ${relationAlias}.id`;
|
|
100
|
+
}
|
|
101
|
+
else { // HasOne or HasMany
|
|
102
|
+
const foreignKey = relationInfo.opts.foreignKey;
|
|
103
|
+
const foreignKeyCol = this.getColumnName(foreignKey, relatedColumnsMeta);
|
|
104
|
+
joinClause += ` LEFT JOIN \`${relatedTable}\` AS ${relationAlias} ON ${baseAlias}.id = ${relationAlias}.\`${foreignKeyCol}\``;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
let sql = `SELECT ${selectFields} FROM \`${table}\` AS ${baseAlias}${joinClause}`;
|
|
110
|
+
if (this.whereClauses.length) {
|
|
111
|
+
sql += " WHERE ";
|
|
112
|
+
this.whereClauses.forEach((c, i) => {
|
|
113
|
+
if (i > 0)
|
|
114
|
+
sql += ` ${c.booleanOp} `;
|
|
115
|
+
const colName = this.getColumnName(c.field, columnsMeta);
|
|
116
|
+
sql += `${baseAlias}.\`${colName}\` ${c.operator} ?`;
|
|
117
|
+
params.push(c.value);
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
if (this._limit)
|
|
121
|
+
sql += ` LIMIT ${this._limit}`;
|
|
122
|
+
sql += ";";
|
|
123
|
+
const rows = yield (0, Database_1.query)(sql, params);
|
|
124
|
+
return this.mapRowsToEntities(rows);
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
getColumnName(prop, meta) {
|
|
128
|
+
const opts = meta.get(prop);
|
|
129
|
+
return (opts === null || opts === void 0 ? void 0 : opts.name) ? opts.name : camelToSnake(prop);
|
|
130
|
+
}
|
|
131
|
+
mapRowsToEntities(rows) {
|
|
132
|
+
const mainEntityMap = new Map();
|
|
133
|
+
const columnsMeta = (0, Column_1.getColumnMeta)(this.model);
|
|
134
|
+
const allRelationMetas = [
|
|
135
|
+
{ meta: (0, BelongsTo_1.getBelongsToMeta)(this.model), type: 'BelongsTo' },
|
|
136
|
+
{ meta: (0, HasMany_1.getHasManyMeta)(this.model), type: 'HasMany' },
|
|
137
|
+
{ meta: (0, HasOne_1.getHasOneMeta)(this.model), type: 'HasOne' }
|
|
138
|
+
];
|
|
139
|
+
for (const row of rows) {
|
|
140
|
+
const pkValue = row[this.primaryKey];
|
|
141
|
+
if (!mainEntityMap.has(pkValue)) {
|
|
142
|
+
const obj = new this.model();
|
|
143
|
+
for (const [prop, opts] of columnsMeta.entries()) {
|
|
144
|
+
const colName = this.getColumnName(prop, columnsMeta);
|
|
145
|
+
if (row.hasOwnProperty(colName)) {
|
|
146
|
+
obj[prop] = row[colName];
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
mainEntityMap.set(pkValue, obj);
|
|
150
|
+
}
|
|
151
|
+
const mainEntity = mainEntityMap.get(pkValue);
|
|
152
|
+
for (const { meta, type } of allRelationMetas) {
|
|
153
|
+
if (meta) {
|
|
154
|
+
for (const [relationProp, opts] of meta.entries()) {
|
|
155
|
+
const prefix = `${relationProp}__`;
|
|
156
|
+
if (row[`${prefix}id`] === null)
|
|
157
|
+
continue;
|
|
158
|
+
const relatedModelCtor = opts.model();
|
|
159
|
+
const relatedObj = new relatedModelCtor();
|
|
160
|
+
const relatedColsMeta = (0, Column_1.getColumnMeta)(relatedModelCtor);
|
|
161
|
+
for (const [prop] of relatedColsMeta.entries()) {
|
|
162
|
+
relatedObj[prop] = row[`${prefix}${prop}`];
|
|
163
|
+
}
|
|
164
|
+
if (type === 'HasMany') {
|
|
165
|
+
if (!mainEntity[relationProp]) {
|
|
166
|
+
mainEntity[relationProp] = [];
|
|
167
|
+
}
|
|
168
|
+
mainEntity[relationProp].push(relatedObj);
|
|
169
|
+
}
|
|
170
|
+
else { // BelongsTo or HasOne
|
|
171
|
+
mainEntity[relationProp] = relatedObj;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return Array.from(mainEntityMap.values());
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
exports.QueryBuilder = QueryBuilder;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class SchemaManager {
|
|
2
|
+
private models;
|
|
3
|
+
private logger;
|
|
4
|
+
constructor(models: Function[], logger?: Console);
|
|
5
|
+
migrate(): Promise<void>;
|
|
6
|
+
private createTable;
|
|
7
|
+
private updateTable;
|
|
8
|
+
private getSchemaFromModel;
|
|
9
|
+
private getSqlTypeForClass;
|
|
10
|
+
private getExistingColumns;
|
|
11
|
+
}
|