lamix 4.2.9 → 4.2.11
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 +180 -1
- package/{artisan.js → bin/cli.js} +1 -1
- package/{index.d.ts → lib/index.d.ts} +17 -8
- package/{index.js → lib/index.js} +155 -140
- package/package.json +12 -23
- /package/{artisan.d.ts → bin/cli.d.ts} +0 -0
package/README.md
CHANGED
|
@@ -106,6 +106,185 @@ npm i lamix
|
|
|
106
106
|
|
|
107
107
|
npm install mysql2 # or pg or sqlite3
|
|
108
108
|
|
|
109
|
-
```bash
|
|
110
109
|
# TO See All the Available CLI Commands Run in Terminal
|
|
111
110
|
npx lamix
|
|
111
|
+
|
|
112
|
+
#Basic CRUD & Query
|
|
113
|
+
|
|
114
|
+
const User = require('./models/User');
|
|
115
|
+
|
|
116
|
+
# Create new user
|
|
117
|
+
const user = await User.create({ name: 'Alice', email: 'alice@example.com', password: 'secret' });
|
|
118
|
+
|
|
119
|
+
# Fill & save (update)
|
|
120
|
+
user.name = 'Alice Smith'; // you can also use user.fill({ name: 'Alice Smith' })
|
|
121
|
+
await user.save();
|
|
122
|
+
|
|
123
|
+
# Find by primary key
|
|
124
|
+
const user1 = await User.find(1);
|
|
125
|
+
const user2 = await User.findOrFail(param.id);
|
|
126
|
+
|
|
127
|
+
# Query with where
|
|
128
|
+
const someUsers = await User.where('email', 'alice@example.com').get();
|
|
129
|
+
|
|
130
|
+
# First match
|
|
131
|
+
const firstUser = await User.where('name', 'Alice').first();
|
|
132
|
+
const firstOrFail = await User.where('name', 'Alice').firstOrFail();
|
|
133
|
+
|
|
134
|
+
const user = await User.findBy('email', 'jane@example.com');
|
|
135
|
+
# by default Field password is Auto encrypted when creating
|
|
136
|
+
const isValid = await user.checkPassword('user_input_password');
|
|
137
|
+
# Delete (soft delete if enabled)
|
|
138
|
+
await user.delete();
|
|
139
|
+
|
|
140
|
+
# destroy (delete Multiple ids)
|
|
141
|
+
await user.destroy();
|
|
142
|
+
|
|
143
|
+
# update (by default)
|
|
144
|
+
await user.update({ body });
|
|
145
|
+
|
|
146
|
+
# or update
|
|
147
|
+
await user.fill({ body });
|
|
148
|
+
await user.save();
|
|
149
|
+
|
|
150
|
+
# Restore (if softDeletes enabled)
|
|
151
|
+
await user.restore();
|
|
152
|
+
|
|
153
|
+
# List all (excluding trashed, by default)
|
|
154
|
+
const allUsers = await User.all();
|
|
155
|
+
|
|
156
|
+
# Query including soft-deleted (trashed) records
|
|
157
|
+
const withTrashed = await User.withTrashed().where('id', 1).first();
|
|
158
|
+
|
|
159
|
+
#QueryBuilder Advanced Features
|
|
160
|
+
|
|
161
|
+
const qb = User.query();
|
|
162
|
+
|
|
163
|
+
# Select specific columns
|
|
164
|
+
const names = await User.query().select('id', 'name').get();
|
|
165
|
+
|
|
166
|
+
# Aggregates
|
|
167
|
+
const cnt = await User.query().count(); // count(*)
|
|
168
|
+
const sumId = await User.query().sum('id');
|
|
169
|
+
const avgId = await User.query().avg('id');
|
|
170
|
+
const minId = await User.query().min('id');
|
|
171
|
+
const maxId = await User.query().max('id');
|
|
172
|
+
|
|
173
|
+
# status a column (returns status of boolean values)
|
|
174
|
+
const usersAll = await User.all();
|
|
175
|
+
const users = await User.query().where('status', true).get();
|
|
176
|
+
const usersorderbyid = await User.query().where('status', true).orderBy('id', 'desc').get();
|
|
177
|
+
const usersorderbycreateAt = await User.query().where('status', true).orderBy('created_at', 'desc').get();
|
|
178
|
+
|
|
179
|
+
# Pluck a column (returns array of values)
|
|
180
|
+
const Pluckemails = await User.query().pluck('email');
|
|
181
|
+
|
|
182
|
+
# Pagination
|
|
183
|
+
const page1 = await User.query().paginate(1, 10);
|
|
184
|
+
// page1 = { total, perPage, page, lastPage, data: [users...] }
|
|
185
|
+
|
|
186
|
+
# Where In
|
|
187
|
+
const usersIn = await User.query().whereIn('id', [1, 2, 3]).get();
|
|
188
|
+
|
|
189
|
+
// Where Null / Not Null
|
|
190
|
+
const withNull = await User.query().whereNull('deleted_at').get();
|
|
191
|
+
const notNull = await User.query().whereNotNull('deleted_at').get();
|
|
192
|
+
|
|
193
|
+
# Where Between
|
|
194
|
+
const inRange = await User.query().whereBetween('id', [10, 20]).get();
|
|
195
|
+
|
|
196
|
+
# Ordering, grouping, having
|
|
197
|
+
const grouped = await User.query()
|
|
198
|
+
.select('user_id', 'COUNT(*) as cnt')
|
|
199
|
+
.groupBy('user_id')
|
|
200
|
+
.having('cnt', '>', 1)
|
|
201
|
+
.get();
|
|
202
|
+
|
|
203
|
+
# Joins
|
|
204
|
+
const withPosts = await User.query()
|
|
205
|
+
.select('users.*', 'posts.title as post_title')
|
|
206
|
+
.join('INNER', 'posts', 'users.id', '=', 'posts.user_id')
|
|
207
|
+
.get();
|
|
208
|
+
|
|
209
|
+
# Using “forUpdate” (locking)
|
|
210
|
+
await User.query().where('id', 5).forUpdate().get();
|
|
211
|
+
|
|
212
|
+
# Raw queries
|
|
213
|
+
const rows = await User.raw('SELECT * FROM users WHERE id = ?', [5]);
|
|
214
|
+
|
|
215
|
+
# Caching
|
|
216
|
+
const cachedRows = await User.raw('SELECT * FROM users', []).then(rows => rows);
|
|
217
|
+
const cached = await DB.cached('SELECT * FROM users WHERE id = ?', [5], 60000);
|
|
218
|
+
|
|
219
|
+
#You can use relations:
|
|
220
|
+
|
|
221
|
+
const user = await User.find(1);
|
|
222
|
+
const posts = await user.posts().get(); // all posts for the user
|
|
223
|
+
|
|
224
|
+
# Eager load in a query with Relations:
|
|
225
|
+
const usersWithPosts = await User.query().with('posts').get();
|
|
226
|
+
# Each user will have a `posts` field with array of post models.
|
|
227
|
+
|
|
228
|
+
# Many-to-many example
|
|
229
|
+
# Suppose you have an intermediate pivot table user_roles (user_id, role_id)
|
|
230
|
+
# and models User and Role, with pivot user_roles.
|
|
231
|
+
#
|
|
232
|
+
|
|
233
|
+
# Then:
|
|
234
|
+
const user = await User.find(2);
|
|
235
|
+
const roles = await user.roles().get();
|
|
236
|
+
|
|
237
|
+
# Eager load with Relations:
|
|
238
|
+
const usersWithRoles = await User.query().with('roles').get();
|
|
239
|
+
|
|
240
|
+
# Eager load with Many to Many Relations:
|
|
241
|
+
const usersWith_Roles_posts_comments = await User.query().with(['roles', 'posts', 'comments' ]).get();
|
|
242
|
+
#OR with Many to Many Relations:
|
|
243
|
+
const users_With_Roles_posts_comments = await User.with(['roles', 'posts', 'comments' ]).get();
|
|
244
|
+
|
|
245
|
+
# Find by field
|
|
246
|
+
const user = await User.findBy('email', 'jane@example.com');
|
|
247
|
+
|
|
248
|
+
# Check hashed password
|
|
249
|
+
const isValid = await user.checkPassword('password');
|
|
250
|
+
|
|
251
|
+
# models/User.js(Example)
|
|
252
|
+
const { BaseModel} = require('lamix');
|
|
253
|
+
|
|
254
|
+
class User extends BaseModel {
|
|
255
|
+
static table = 'users';
|
|
256
|
+
static primaryKey = 'id';
|
|
257
|
+
static timestamps = true;
|
|
258
|
+
static softDeletes = false; # Optional if you don't need it
|
|
259
|
+
static fillable = ['name', 'email', 'password']; # password is Auto encrypted when creating.
|
|
260
|
+
static rules = { # Optional if you don't need it
|
|
261
|
+
name: 'required|string',
|
|
262
|
+
email: 'required|email|unique:users,email',
|
|
263
|
+
password: 'required|string|min:6',
|
|
264
|
+
phone: 'nullable|phone',
|
|
265
|
+
status: 'boolean'
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
profile() {
|
|
269
|
+
const Profile = require('./Profile');
|
|
270
|
+
return this.hasOne(Profile).onDelete('restrict');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
// Many-to-many: User ↔ Role via pivot user_roles (user_id, role_id)
|
|
275
|
+
roles() {
|
|
276
|
+
const Role = require('./Role');
|
|
277
|
+
return this.belongsToMany(
|
|
278
|
+
Role,
|
|
279
|
+
'user_roles',
|
|
280
|
+
'user_id',
|
|
281
|
+
'role_id'
|
|
282
|
+
).onDelete('detach');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// One-to-many: User -> Post
|
|
286
|
+
posts() {
|
|
287
|
+
const Post = require('./Post');
|
|
288
|
+
return this.hasMany(Post', 'user_id', 'id').onDelete('cascade');
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -7,7 +7,10 @@ export class DB {
|
|
|
7
7
|
static eventHandlers: {
|
|
8
8
|
query: any[];
|
|
9
9
|
error: any[];
|
|
10
|
+
reconnect: any[];
|
|
10
11
|
};
|
|
12
|
+
static _grammar: any;
|
|
13
|
+
static _als: AsyncLocalStorage<any>;
|
|
11
14
|
static initFromEnv({ driver, config, retryAttempts }?: {
|
|
12
15
|
driver?: string;
|
|
13
16
|
config?: any;
|
|
@@ -17,17 +20,22 @@ export class DB {
|
|
|
17
20
|
static _emit(event: any, payload: any): void;
|
|
18
21
|
static _ensureModule(): any;
|
|
19
22
|
static connect(): Promise<any>;
|
|
23
|
+
static getConnection(): Promise<any>;
|
|
24
|
+
static reconnect(): Promise<any>;
|
|
20
25
|
static end(): Promise<void>;
|
|
21
|
-
static
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
static
|
|
26
|
-
static
|
|
27
|
-
static
|
|
26
|
+
static run(sql: any, bindings: any, executor: any, { isWrite }?: {
|
|
27
|
+
isWrite?: boolean;
|
|
28
|
+
}): Promise<any>;
|
|
29
|
+
static raw(sql: any, bindings?: any[]): Promise<any>;
|
|
30
|
+
static affectingStatement(sql: any, bindings?: any[]): Promise<any>;
|
|
31
|
+
static select(sql: any, bindings?: any[]): Promise<any>;
|
|
32
|
+
static statement(sql: any, bindings?: any[]): Promise<any>;
|
|
33
|
+
static insert(sql: any, bindings?: any[]): Promise<any>;
|
|
34
|
+
static update(sql: any, bindings?: any[]): Promise<any>;
|
|
35
|
+
static delete(sql: any, bindings?: any[]): Promise<any>;
|
|
28
36
|
static transaction(fn: any): Promise<any>;
|
|
29
37
|
static escapeId(id: any): any;
|
|
30
|
-
static cached(sql: any,
|
|
38
|
+
static cached(sql: any, bindings?: any[], ttl?: number): Promise<any>;
|
|
31
39
|
}
|
|
32
40
|
export class Model {
|
|
33
41
|
static table: any;
|
|
@@ -520,6 +528,7 @@ declare class SimpleCache {
|
|
|
520
528
|
del(k: any): void;
|
|
521
529
|
clear(): void;
|
|
522
530
|
}
|
|
531
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
523
532
|
declare class HasManyThrough extends Relation {
|
|
524
533
|
constructor(parent: any, relatedClass: any, throughClass: any, firstKey: any, secondKey: any, localKey: any, secondLocalKey: any);
|
|
525
534
|
throughClass: any;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const util = require('util');
|
|
4
4
|
const { performance } = require('perf_hooks');
|
|
5
|
+
const { AsyncLocalStorage } = require('async_hooks');
|
|
5
6
|
require('dotenv').config();
|
|
6
7
|
|
|
7
8
|
/* ---------------- Utilities ---------------- */
|
|
@@ -10,6 +11,8 @@ function tryRequire(name) {
|
|
|
10
11
|
try { return require(name); } catch { return null; }
|
|
11
12
|
}
|
|
12
13
|
|
|
14
|
+
const sleep = ms => new Promise(r => setTimeout(r, ms));
|
|
15
|
+
|
|
13
16
|
/* ---------------- Errors ---------------- */
|
|
14
17
|
|
|
15
18
|
class DBError extends Error {
|
|
@@ -17,6 +20,7 @@ class DBError extends Error {
|
|
|
17
20
|
super(message);
|
|
18
21
|
this.name = 'DBError';
|
|
19
22
|
this.meta = meta;
|
|
23
|
+
if (meta.err?.stack) this.stack += `\nCaused by: ${meta.err.stack}`;
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
|
|
@@ -33,20 +37,45 @@ class SimpleCache {
|
|
|
33
37
|
}
|
|
34
38
|
return e.v;
|
|
35
39
|
}
|
|
36
|
-
set(k, v, ttl = 0) {
|
|
40
|
+
set(k, v, ttl = 0) {
|
|
41
|
+
this.map.set(k, { v, ts: Date.now(), ttl });
|
|
42
|
+
}
|
|
37
43
|
del(k) { this.map.delete(k); }
|
|
38
44
|
clear() { this.map.clear(); }
|
|
39
45
|
}
|
|
40
46
|
|
|
47
|
+
/* ---------------- Grammar ---------------- */
|
|
48
|
+
|
|
49
|
+
class Grammar {
|
|
50
|
+
prepare(sql, bindings) {
|
|
51
|
+
return { sql, bindings };
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
class PostgresGrammar extends Grammar {
|
|
56
|
+
prepare(sql, bindings) {
|
|
57
|
+
let i = 0;
|
|
58
|
+
return {
|
|
59
|
+
sql: sql.replace(/\?/g, () => `$${++i}`),
|
|
60
|
+
bindings
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
41
65
|
/* ---------------- DB Core ---------------- */
|
|
42
66
|
|
|
43
67
|
class DB {
|
|
44
68
|
static driver = null;
|
|
45
69
|
static config = null;
|
|
46
70
|
static pool = null;
|
|
71
|
+
|
|
47
72
|
static cache = new SimpleCache();
|
|
48
73
|
static retryAttempts = 1;
|
|
49
|
-
|
|
74
|
+
|
|
75
|
+
static eventHandlers = { query: [], error: [], reconnect: [] };
|
|
76
|
+
|
|
77
|
+
static _grammar = null;
|
|
78
|
+
static _als = new AsyncLocalStorage();
|
|
50
79
|
|
|
51
80
|
/* ---------- Init ---------- */
|
|
52
81
|
|
|
@@ -58,6 +87,9 @@ class DB {
|
|
|
58
87
|
this.driver = driver.toLowerCase();
|
|
59
88
|
this.retryAttempts = Math.max(1, Number(retryAttempts));
|
|
60
89
|
|
|
90
|
+
this._grammar =
|
|
91
|
+
this.driver === 'pg' ? new PostgresGrammar() : new Grammar();
|
|
92
|
+
|
|
61
93
|
if (config) {
|
|
62
94
|
this.config = config;
|
|
63
95
|
return;
|
|
@@ -128,7 +160,7 @@ class DB {
|
|
|
128
160
|
throw new DBError(`Unsupported driver: ${this.driver}`);
|
|
129
161
|
}
|
|
130
162
|
|
|
131
|
-
/* ----------
|
|
163
|
+
/* ---------- Connection ---------- */
|
|
132
164
|
|
|
133
165
|
static async connect() {
|
|
134
166
|
if (this.pool) return this.pool;
|
|
@@ -137,26 +169,25 @@ class DB {
|
|
|
137
169
|
|
|
138
170
|
if (this.driver === 'mysql') {
|
|
139
171
|
this.pool = driver.createPool(this.config);
|
|
140
|
-
return this.pool;
|
|
141
172
|
}
|
|
142
173
|
|
|
143
174
|
if (this.driver === 'pg') {
|
|
144
175
|
const { Pool } = driver;
|
|
145
176
|
this.pool = new Pool(this.config);
|
|
146
|
-
return this.pool;
|
|
147
177
|
}
|
|
148
178
|
|
|
149
179
|
if (this.driver === 'sqlite') {
|
|
150
180
|
const db = new driver.Database(this.config.filename);
|
|
151
|
-
|
|
152
181
|
db.runAsync = util.promisify(db.run.bind(db));
|
|
153
|
-
db.getAsync = util.promisify(db.get.bind(db));
|
|
154
182
|
db.allAsync = util.promisify(db.all.bind(db));
|
|
155
183
|
db.execAsync = util.promisify(db.exec.bind(db));
|
|
156
184
|
|
|
185
|
+
await db.execAsync('PRAGMA journal_mode=WAL');
|
|
186
|
+
await db.execAsync('PRAGMA busy_timeout=5000');
|
|
187
|
+
|
|
157
188
|
this.pool = {
|
|
158
189
|
__sqlite_db: db,
|
|
159
|
-
|
|
190
|
+
async query(sql, params = []) {
|
|
160
191
|
const isSelect = /^\s*(select|pragma)/i.test(sql);
|
|
161
192
|
if (isSelect) return [await db.allAsync(sql, params)];
|
|
162
193
|
const r = await db.runAsync(sql, params);
|
|
@@ -164,9 +195,21 @@ class DB {
|
|
|
164
195
|
},
|
|
165
196
|
close: () => new Promise((r, j) => db.close(e => e ? j(e) : r()))
|
|
166
197
|
};
|
|
167
|
-
|
|
168
|
-
return this.pool;
|
|
169
198
|
}
|
|
199
|
+
|
|
200
|
+
return this.pool;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
static async getConnection() {
|
|
204
|
+
const store = this._als.getStore();
|
|
205
|
+
if (store?.conn) return store.conn;
|
|
206
|
+
return this.connect();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
static async reconnect() {
|
|
210
|
+
await this.end();
|
|
211
|
+
this._emit('reconnect', {});
|
|
212
|
+
return this.connect();
|
|
170
213
|
}
|
|
171
214
|
|
|
172
215
|
static async end() {
|
|
@@ -179,159 +222,130 @@ class DB {
|
|
|
179
222
|
}
|
|
180
223
|
}
|
|
181
224
|
|
|
182
|
-
/* ----------
|
|
183
|
-
|
|
184
|
-
static _pgConvert(sql, params) {
|
|
185
|
-
let i = 0;
|
|
186
|
-
return {
|
|
187
|
-
text: sql.replace(/\?/g, () => `$${++i}`),
|
|
188
|
-
values: params
|
|
189
|
-
};
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
static _extractTable(sql) {
|
|
193
|
-
if (typeof sql !== 'string') return null;
|
|
194
|
-
|
|
195
|
-
const cleaned = sql
|
|
196
|
-
.replace(/`|"|\[|\]/g, '')
|
|
197
|
-
.replace(/\s+/g, ' ')
|
|
198
|
-
.trim()
|
|
199
|
-
.toLowerCase();
|
|
200
|
-
|
|
201
|
-
const match =
|
|
202
|
-
cleaned.match(/\bfrom\s+([a-z0-9_]+)/) ||
|
|
203
|
-
cleaned.match(/\binto\s+([a-z0-9_]+)/) ||
|
|
204
|
-
cleaned.match(/\bupdate\s+([a-z0-9_]+)/);
|
|
205
|
-
|
|
206
|
-
return match ? match[1] : null;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
static async _tableExists(table) {
|
|
210
|
-
if (!table) return true;
|
|
211
|
-
|
|
212
|
-
const pool = await this.connect();
|
|
213
|
-
|
|
214
|
-
if (this.driver === 'mysql') {
|
|
215
|
-
const [rows] = await pool.query(
|
|
216
|
-
`SELECT 1 FROM information_schema.tables
|
|
217
|
-
WHERE table_schema = DATABASE() AND table_name = ? LIMIT 1`,
|
|
218
|
-
[table]
|
|
219
|
-
);
|
|
220
|
-
return rows.length > 0;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
if (this.driver === 'pg') {
|
|
224
|
-
const res = await pool.query(
|
|
225
|
-
`SELECT 1 FROM information_schema.tables
|
|
226
|
-
WHERE table_schema = 'public' AND table_name = $1 LIMIT 1`,
|
|
227
|
-
[table]
|
|
228
|
-
);
|
|
229
|
-
return res.rows.length > 0;
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
if (this.driver === 'sqlite') {
|
|
233
|
-
const [rows] = await pool.query(
|
|
234
|
-
`SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1`,
|
|
235
|
-
[table]
|
|
236
|
-
);
|
|
237
|
-
return rows.length > 0;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
return true;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
static async raw(sql, params = [], options = {}) {
|
|
244
|
-
const { checkTable = false } = options;
|
|
245
|
-
if (checkTable) {
|
|
246
|
-
const table = this._extractTable(sql);
|
|
247
|
-
if (table) {
|
|
248
|
-
const exists = await this._tableExists(table);
|
|
249
|
-
if (!exists) {
|
|
250
|
-
throw new DBError(`Table does not exist: ${table}`, { sql });
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
}
|
|
225
|
+
/* ---------- Core Runner ---------- */
|
|
254
226
|
|
|
227
|
+
static async run(sql, bindings, executor, { isWrite = false } = {}) {
|
|
255
228
|
let attempt = 0;
|
|
256
229
|
|
|
257
230
|
while (++attempt <= this.retryAttempts) {
|
|
258
|
-
const pool = await this.connect();
|
|
259
231
|
const start = performance.now();
|
|
260
232
|
|
|
261
233
|
try {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const [rows] = await pool.query(sql, params);
|
|
271
|
-
return rows;
|
|
272
|
-
|
|
234
|
+
const result = await executor();
|
|
235
|
+
this._emit('query', {
|
|
236
|
+
sql,
|
|
237
|
+
bindings,
|
|
238
|
+
time: performance.now() - start,
|
|
239
|
+
driver: this.driver
|
|
240
|
+
});
|
|
241
|
+
return result;
|
|
273
242
|
} catch (err) {
|
|
274
|
-
this._emit('error', { err, sql,
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
243
|
+
this._emit('error', { err, sql, bindings, attempt });
|
|
244
|
+
|
|
245
|
+
const canRetry =
|
|
246
|
+
!isWrite ||
|
|
247
|
+
this._als.getStore()?.inTransaction;
|
|
248
|
+
|
|
249
|
+
if (
|
|
250
|
+
canRetry &&
|
|
251
|
+
attempt < this.retryAttempts &&
|
|
252
|
+
/dead|lost|timeout|reset|closed/i.test(err.message)
|
|
253
|
+
) {
|
|
254
|
+
await this.reconnect();
|
|
255
|
+
await sleep(100 * attempt);
|
|
280
256
|
continue;
|
|
281
257
|
}
|
|
282
258
|
|
|
283
|
-
throw new DBError('DB
|
|
259
|
+
throw new DBError('DB query failed', { sql, bindings, err });
|
|
284
260
|
}
|
|
285
261
|
}
|
|
286
262
|
}
|
|
287
263
|
|
|
264
|
+
/* ---------- Queries ---------- */
|
|
265
|
+
|
|
266
|
+
static async raw(sql, bindings = []) {
|
|
267
|
+
const pool = await this.getConnection();
|
|
268
|
+
const prepared = this._grammar.prepare(sql, bindings);
|
|
269
|
+
|
|
270
|
+
return this.run(sql, bindings, async () => {
|
|
271
|
+
if (this.driver === 'pg') {
|
|
272
|
+
const r = await pool.query(prepared.sql, prepared.bindings);
|
|
273
|
+
return r.rows;
|
|
274
|
+
}
|
|
275
|
+
const [rows] = await pool.query(prepared.sql, prepared.bindings);
|
|
276
|
+
return rows;
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
static async affectingStatement(sql, bindings = []) {
|
|
281
|
+
const pool = await this.getConnection();
|
|
282
|
+
const prepared = this._grammar.prepare(sql, bindings);
|
|
283
|
+
|
|
284
|
+
return this.run(sql, bindings, async () => {
|
|
285
|
+
if (this.driver === 'pg') {
|
|
286
|
+
const r = await pool.query(prepared.sql, prepared.bindings);
|
|
287
|
+
return { affectedRows: r.rowCount };
|
|
288
|
+
}
|
|
289
|
+
const [r] = await pool.query(prepared.sql, prepared.bindings);
|
|
290
|
+
return r;
|
|
291
|
+
}, { isWrite: true });
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
static select(sql, bindings = []) { return this.raw(sql, bindings); }
|
|
295
|
+
static statement(sql, bindings = []) { return this.affectingStatement(sql, bindings); }
|
|
296
|
+
static insert(sql, bindings = []) {
|
|
297
|
+
return this.affectingStatement(sql, bindings)
|
|
298
|
+
.then(r => r.insertId ?? true);
|
|
299
|
+
}
|
|
300
|
+
static update(sql, bindings = []) {
|
|
301
|
+
return this.affectingStatement(sql, bindings)
|
|
302
|
+
.then(r => r.affectedRows ?? 0);
|
|
303
|
+
}
|
|
304
|
+
static delete(sql, bindings = []) { return this.update(sql, bindings); }
|
|
305
|
+
|
|
288
306
|
/* ---------- Transactions ---------- */
|
|
289
307
|
|
|
290
308
|
static async transaction(fn) {
|
|
291
309
|
const pool = await this.connect();
|
|
292
310
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
try {
|
|
296
|
-
await c.beginTransaction();
|
|
297
|
-
const r = await fn(c);
|
|
298
|
-
await c.commit();
|
|
299
|
-
return r;
|
|
300
|
-
} catch (e) {
|
|
301
|
-
await c.rollback();
|
|
302
|
-
throw e;
|
|
303
|
-
} finally {
|
|
304
|
-
c.release();
|
|
305
|
-
}
|
|
306
|
-
}
|
|
311
|
+
return this._als.run({ inTransaction: true }, async () => {
|
|
312
|
+
let conn;
|
|
307
313
|
|
|
308
|
-
if (this.driver === 'pg') {
|
|
309
|
-
const c = await pool.connect();
|
|
310
314
|
try {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
c.release();
|
|
320
|
-
}
|
|
321
|
-
}
|
|
315
|
+
if (this.driver === 'mysql') {
|
|
316
|
+
conn = await pool.getConnection();
|
|
317
|
+
await conn.beginTransaction();
|
|
318
|
+
this._als.getStore().conn = conn;
|
|
319
|
+
const r = await fn(conn);
|
|
320
|
+
await conn.commit();
|
|
321
|
+
return r;
|
|
322
|
+
}
|
|
322
323
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
324
|
+
if (this.driver === 'pg') {
|
|
325
|
+
conn = await pool.connect();
|
|
326
|
+
await conn.query('BEGIN');
|
|
327
|
+
this._als.getStore().conn = conn;
|
|
328
|
+
const r = await fn(conn);
|
|
329
|
+
await conn.query('COMMIT');
|
|
330
|
+
return r;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (this.driver === 'sqlite') {
|
|
334
|
+
await pool.__sqlite_db.execAsync('BEGIN');
|
|
335
|
+
this._als.getStore().conn = pool;
|
|
336
|
+
const r = await fn(pool);
|
|
337
|
+
await pool.__sqlite_db.execAsync('COMMIT');
|
|
338
|
+
return r;
|
|
339
|
+
}
|
|
330
340
|
} catch (e) {
|
|
331
|
-
|
|
341
|
+
try {
|
|
342
|
+
await conn?.query?.('ROLLBACK');
|
|
343
|
+
} catch {}
|
|
332
344
|
throw e;
|
|
345
|
+
} finally {
|
|
346
|
+
conn?.release?.();
|
|
333
347
|
}
|
|
334
|
-
}
|
|
348
|
+
});
|
|
335
349
|
}
|
|
336
350
|
|
|
337
351
|
/* ---------- Helpers ---------- */
|
|
@@ -344,11 +358,11 @@ class DB {
|
|
|
344
358
|
: `\`${id.replace(/`/g, '``')}\``;
|
|
345
359
|
}
|
|
346
360
|
|
|
347
|
-
static async cached(sql,
|
|
348
|
-
const key = JSON.stringify([sql,
|
|
361
|
+
static async cached(sql, bindings = [], ttl = 0) {
|
|
362
|
+
const key = JSON.stringify([sql, bindings]);
|
|
349
363
|
const hit = this.cache.get(key);
|
|
350
364
|
if (hit) return hit;
|
|
351
|
-
const rows = await this.raw(sql,
|
|
365
|
+
const rows = await this.raw(sql, bindings);
|
|
352
366
|
this.cache.set(key, rows, ttl);
|
|
353
367
|
return rows;
|
|
354
368
|
}
|
|
@@ -356,6 +370,7 @@ class DB {
|
|
|
356
370
|
|
|
357
371
|
const escapeId = s => DB.escapeId(s);
|
|
358
372
|
|
|
373
|
+
|
|
359
374
|
// -----------------------------
|
|
360
375
|
// MAIN Validator
|
|
361
376
|
// -----------------------------
|
package/package.json
CHANGED
|
@@ -1,21 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lamix",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.11",
|
|
4
4
|
"description": "lamix - ORM for Node-express js",
|
|
5
|
-
"main": "
|
|
6
|
-
"types": "index.d.ts",
|
|
5
|
+
"main": "./lib",
|
|
7
6
|
"exports": {
|
|
8
7
|
".": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"types": "./index.d.ts"
|
|
8
|
+
"require": "./lib/index.js",
|
|
9
|
+
"types": "./lib/index.d.ts"
|
|
12
10
|
}
|
|
13
11
|
},
|
|
14
12
|
"files": [
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"artisan.js",
|
|
18
|
-
"artisan.d.ts",
|
|
13
|
+
"lib",
|
|
14
|
+
"bin",
|
|
19
15
|
"README.md",
|
|
20
16
|
"examples"
|
|
21
17
|
],
|
|
@@ -24,7 +20,7 @@
|
|
|
24
20
|
"url": "https://github.com/andrewkhabweri-spec/lamix.git"
|
|
25
21
|
},
|
|
26
22
|
"bin": {
|
|
27
|
-
"lamix": "./
|
|
23
|
+
"lamix": "./bin/cli.js"
|
|
28
24
|
},
|
|
29
25
|
"scripts": {
|
|
30
26
|
"build": "tsc && echo 'Build complete'"
|
|
@@ -43,21 +39,14 @@
|
|
|
43
39
|
"orm",
|
|
44
40
|
"nodejs",
|
|
45
41
|
"database",
|
|
46
|
-
"
|
|
42
|
+
"sql"
|
|
47
43
|
],
|
|
48
44
|
"author": {
|
|
49
45
|
"name": "Andrew Khabweri",
|
|
50
46
|
"email": "andrewkhabweri@gmail.com"
|
|
51
47
|
},
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
"outDir": ".",
|
|
57
|
-
"clean": true,
|
|
58
|
-
"dts": true,
|
|
59
|
-
"sourcemaps": true,
|
|
60
|
-
"theme": "default"
|
|
61
|
-
},
|
|
62
|
-
"license": "MIT"
|
|
48
|
+
"license": "MIT",
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">= 0.8.0"
|
|
51
|
+
}
|
|
63
52
|
}
|
|
File without changes
|