lamix 4.2.12 → 4.2.13
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 +56 -3
- package/lib/index.js +195 -10
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -87,7 +87,7 @@ npm i lamix
|
|
|
87
87
|
2. Install dependencies (if any).
|
|
88
88
|
> This tool uses `mysql2`,`pg`,`sqlite3` driver, so make sure you install your prefered driver:
|
|
89
89
|
DATABASE CONNECTION IN `.env`FILE
|
|
90
|
-
Configure DB via environment variables: `'DB_CONNECTION=mysql',DB_HOST=your db hast`, `DB_USER=your db user`, `DB_PASS=your db password`, `DB_NAME=your db name`, `DB_PORT=your db port`, `DB_CONNECTION_LIMIT=
|
|
90
|
+
Configure DB via environment variables: `'DB_CONNECTION=mysql',DB_HOST=your db hast`, `DB_USER=your db user`, `DB_PASS=your db password`, `DB_NAME=your db name`, `DB_PORT=your db port`, `DB_CONNECTION_LIMIT=10`.
|
|
91
91
|
|
|
92
92
|
## Quick start
|
|
93
93
|
for database connection use any driver of your choice eg
|
|
@@ -271,7 +271,7 @@ class User extends BaseModel {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
|
|
274
|
-
|
|
274
|
+
# Many-to-many: User ↔ Role via pivot user_roles (user_id, role_id)
|
|
275
275
|
roles() {
|
|
276
276
|
const Role = require('./Role');
|
|
277
277
|
return this.belongsToMany(
|
|
@@ -282,9 +282,62 @@ class User extends BaseModel {
|
|
|
282
282
|
).onDelete('detach');
|
|
283
283
|
}
|
|
284
284
|
|
|
285
|
-
|
|
285
|
+
# One-to-many: User -> Post
|
|
286
286
|
posts() {
|
|
287
287
|
const Post = require('./Post');
|
|
288
288
|
return this.hasMany(Post', 'user_id', 'id').onDelete('cascade');
|
|
289
289
|
}
|
|
290
|
+
|
|
291
|
+
# Create sessions Table MySQL/Postgres sql
|
|
292
|
+
CREATE TABLE sessions (
|
|
293
|
+
sid VARCHAR(255) PRIMARY KEY,
|
|
294
|
+
data TEXT NOT NULL,
|
|
295
|
+
expires BIGINT NOT NULL
|
|
296
|
+
);
|
|
297
|
+
CREATE INDEX idx_sessions_expires ON sessions (expires);
|
|
298
|
+
|
|
299
|
+
# Create sessions Table sqlite sql
|
|
300
|
+
CREATE TABLE sessions (
|
|
301
|
+
sid TEXT PRIMARY KEY,
|
|
302
|
+
data TEXT NOT NULL,
|
|
303
|
+
expires INTEGER NOT NULL
|
|
304
|
+
);
|
|
305
|
+
CREATE INDEX idx_sessions_expires ON sessions (expires);
|
|
306
|
+
|
|
307
|
+
# Session setup
|
|
308
|
+
const express = require('express');
|
|
309
|
+
const session = require('express-session');
|
|
310
|
+
const { DB, LamixSessionStore } = require('lamix');
|
|
311
|
+
|
|
312
|
+
DB.initFromEnv();
|
|
313
|
+
|
|
314
|
+
(async () => {
|
|
315
|
+
await DB.connect();
|
|
316
|
+
})();
|
|
317
|
+
|
|
318
|
+
const app = express();
|
|
319
|
+
|
|
320
|
+
app.use(
|
|
321
|
+
session({
|
|
322
|
+
name: 'lamix.sid',
|
|
323
|
+
secret: process.env.SESSION_SECRET || 'dev-secret',
|
|
324
|
+
resave: false,
|
|
325
|
+
saveUninitialized: false,
|
|
326
|
+
store: new LamixSessionStore({
|
|
327
|
+
ttl: 60 * 60 * 24, // 1 day
|
|
328
|
+
}),
|
|
329
|
+
cookie: {
|
|
330
|
+
httpOnly: true,
|
|
331
|
+
secure: false, // true behind HTTPS
|
|
332
|
+
maxAge: 1000 * 60 * 60 * 24,
|
|
333
|
+
},
|
|
334
|
+
})
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
app.get('/', (req, res) => {
|
|
338
|
+
req.session.views = (req.session.views || 0) + 1;
|
|
339
|
+
res.json({ views: req.session.views });
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
app.listen(3000);
|
|
290
343
|
}
|
package/lib/index.js
CHANGED
|
@@ -136,30 +136,63 @@ class DB {
|
|
|
136
136
|
|
|
137
137
|
/* ---------- Driver ---------- */
|
|
138
138
|
|
|
139
|
+
// static _ensureModule() {
|
|
140
|
+
// if (!this.driver) this.initFromEnv();
|
|
141
|
+
|
|
142
|
+
// if (this.driver === 'mysql') {
|
|
143
|
+
// const m = tryRequire('mysql2/promise');
|
|
144
|
+
// if (!m) throw new DBError('Missing mysql2');
|
|
145
|
+
// return m;
|
|
146
|
+
// }
|
|
147
|
+
|
|
148
|
+
// if (this.driver === 'pg') {
|
|
149
|
+
// const m = tryRequire('pg');
|
|
150
|
+
// if (!m) throw new DBError('Missing pg');
|
|
151
|
+
// return m;
|
|
152
|
+
// }
|
|
153
|
+
|
|
154
|
+
// if (this.driver === 'sqlite') {
|
|
155
|
+
// const m = tryRequire('sqlite3');
|
|
156
|
+
// if (!m) throw new DBError('Missing sqlite3');
|
|
157
|
+
// return m;
|
|
158
|
+
// }
|
|
159
|
+
|
|
160
|
+
// throw new DBError(`Unsupported driver: ${this.driver}`);
|
|
161
|
+
// }
|
|
162
|
+
|
|
139
163
|
static _ensureModule() {
|
|
140
164
|
if (!this.driver) this.initFromEnv();
|
|
141
165
|
|
|
166
|
+
const safeRequire = (name) => {
|
|
167
|
+
try {
|
|
168
|
+
return require(name);
|
|
169
|
+
} catch (err) {
|
|
170
|
+
if (err.code === 'ERR_REQUIRE_ASYNC_MODULE') {
|
|
171
|
+
throw new DBError(
|
|
172
|
+
`${name} is ESM-only or uses top-level await. Use a CommonJS version.`,
|
|
173
|
+
{ module: name, err }
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
throw err;
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
142
180
|
if (this.driver === 'mysql') {
|
|
143
|
-
|
|
144
|
-
if (!m) throw new DBError('Missing mysql2');
|
|
145
|
-
return m;
|
|
181
|
+
return safeRequire('mysql2/promise');
|
|
146
182
|
}
|
|
147
183
|
|
|
148
184
|
if (this.driver === 'pg') {
|
|
149
|
-
|
|
150
|
-
if (!m) throw new DBError('Missing pg');
|
|
151
|
-
return m;
|
|
185
|
+
return safeRequire('pg');
|
|
152
186
|
}
|
|
153
187
|
|
|
154
188
|
if (this.driver === 'sqlite') {
|
|
155
|
-
|
|
156
|
-
if (!m) throw new DBError('Missing sqlite3');
|
|
157
|
-
return m;
|
|
189
|
+
return safeRequire('sqlite3');
|
|
158
190
|
}
|
|
159
191
|
|
|
160
192
|
throw new DBError(`Unsupported driver: ${this.driver}`);
|
|
161
193
|
}
|
|
162
194
|
|
|
195
|
+
|
|
163
196
|
/* ---------- Connection ---------- */
|
|
164
197
|
|
|
165
198
|
static async connect() {
|
|
@@ -3883,6 +3916,158 @@ class Model {
|
|
|
3883
3916
|
}
|
|
3884
3917
|
}
|
|
3885
3918
|
|
|
3919
|
+
class Session extends Model {
|
|
3920
|
+
static table = 'sessions';
|
|
3921
|
+
static primaryKey = 'sid';
|
|
3922
|
+
static slugKey = null;
|
|
3923
|
+
static timestamps = false;
|
|
3924
|
+
|
|
3925
|
+
static fillable = ['sid', 'data', 'expires'];
|
|
3926
|
+
}
|
|
3927
|
+
|
|
3928
|
+
const session = require('express-session');
|
|
3929
|
+
|
|
3930
|
+
class LamixSessionStore extends session.Store {
|
|
3931
|
+
constructor(options = {}) {
|
|
3932
|
+
super();
|
|
3933
|
+
|
|
3934
|
+
this.ttl = options.ttl || 86400; // seconds
|
|
3935
|
+
this.cleanupInterval = options.cleanupInterval || 60000;
|
|
3936
|
+
|
|
3937
|
+
this._startCleanup();
|
|
3938
|
+
}
|
|
3939
|
+
|
|
3940
|
+
/* ---------- Get ---------- */
|
|
3941
|
+
|
|
3942
|
+
async get(sid, cb) {
|
|
3943
|
+
try {
|
|
3944
|
+
const now = Date.now();
|
|
3945
|
+
|
|
3946
|
+
const row = await Session
|
|
3947
|
+
.query()
|
|
3948
|
+
.where('sid', sid)
|
|
3949
|
+
.where('expires', '>', now)
|
|
3950
|
+
.first();
|
|
3951
|
+
|
|
3952
|
+
if (!row) return cb(null, null);
|
|
3953
|
+
|
|
3954
|
+
cb(null, JSON.parse(row.data));
|
|
3955
|
+
} catch (err) {
|
|
3956
|
+
cb(new DBError('Failed to load session', {
|
|
3957
|
+
sid,
|
|
3958
|
+
operation: 'get',
|
|
3959
|
+
err
|
|
3960
|
+
}));
|
|
3961
|
+
}
|
|
3962
|
+
}
|
|
3963
|
+
|
|
3964
|
+
/* ---------- Set ---------- */
|
|
3965
|
+
|
|
3966
|
+
async set(sid, sessionData, cb) {
|
|
3967
|
+
try {
|
|
3968
|
+
const expires =
|
|
3969
|
+
sessionData.cookie?.expires
|
|
3970
|
+
? new Date(sessionData.cookie.expires).getTime()
|
|
3971
|
+
: Date.now() + this.ttl * 1000;
|
|
3972
|
+
|
|
3973
|
+
const payload = {
|
|
3974
|
+
sid,
|
|
3975
|
+
data: JSON.stringify(sessionData),
|
|
3976
|
+
expires
|
|
3977
|
+
};
|
|
3978
|
+
|
|
3979
|
+
// const existing = await Session.find(sid);
|
|
3980
|
+
const existing = await Session
|
|
3981
|
+
.query()
|
|
3982
|
+
.where('sid', sid)
|
|
3983
|
+
.first();
|
|
3984
|
+
|
|
3985
|
+
if (existing) {
|
|
3986
|
+
await existing.update(payload);
|
|
3987
|
+
} else {
|
|
3988
|
+
const session = new Session(payload, false);
|
|
3989
|
+
await session.saveNew(payload);
|
|
3990
|
+
}
|
|
3991
|
+
|
|
3992
|
+
cb(null);
|
|
3993
|
+
} catch (err) {
|
|
3994
|
+
cb(new DBError('Failed to persist session', {
|
|
3995
|
+
sid,
|
|
3996
|
+
operation: 'set',
|
|
3997
|
+
err
|
|
3998
|
+
}));
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
|
|
4002
|
+
/* ---------- Destroy ---------- */
|
|
4003
|
+
|
|
4004
|
+
async destroy(sid, cb) {
|
|
4005
|
+
try {
|
|
4006
|
+
await Session
|
|
4007
|
+
.query()
|
|
4008
|
+
.where('sid', sid)
|
|
4009
|
+
.delete();
|
|
4010
|
+
|
|
4011
|
+
cb(null);
|
|
4012
|
+
} catch (err) {
|
|
4013
|
+
cb(new DBError('Failed to destroy session', {
|
|
4014
|
+
sid,
|
|
4015
|
+
operation: 'destroy',
|
|
4016
|
+
err
|
|
4017
|
+
}));
|
|
4018
|
+
}
|
|
4019
|
+
}
|
|
4020
|
+
|
|
4021
|
+
/* ---------- Touch ---------- */
|
|
4022
|
+
|
|
4023
|
+
async touch(sid, sessionData, cb) {
|
|
4024
|
+
if (!sessionData) return cb(null);
|
|
4025
|
+
|
|
4026
|
+
try {
|
|
4027
|
+
const expires =
|
|
4028
|
+
sessionData.cookie?.expires
|
|
4029
|
+
? new Date(sessionData.cookie.expires).getTime()
|
|
4030
|
+
: Date.now() + this.ttl * 1000;
|
|
4031
|
+
|
|
4032
|
+
await Session
|
|
4033
|
+
.query()
|
|
4034
|
+
.where('sid', sid)
|
|
4035
|
+
.update({ expires });
|
|
4036
|
+
|
|
4037
|
+
cb();
|
|
4038
|
+
} catch (err) {
|
|
4039
|
+
cb(new DBError('Failed to touch session', {
|
|
4040
|
+
sid,
|
|
4041
|
+
operation: 'touch',
|
|
4042
|
+
err
|
|
4043
|
+
}));
|
|
4044
|
+
}
|
|
4045
|
+
}
|
|
4046
|
+
|
|
4047
|
+
/* ---------- Cleanup ---------- */
|
|
4048
|
+
|
|
4049
|
+
_startCleanup() {
|
|
4050
|
+
this._cleanupTimer = setInterval(async () => {
|
|
4051
|
+
try {
|
|
4052
|
+
await Session
|
|
4053
|
+
.query()
|
|
4054
|
+
.where('expires', '<', Date.now())
|
|
4055
|
+
.delete();
|
|
4056
|
+
} catch (err) {
|
|
4057
|
+
// cleanup must NEVER fail silently
|
|
4058
|
+
throw new DBError('Session cleanup failed', {
|
|
4059
|
+
operation: 'cleanup',
|
|
4060
|
+
err
|
|
4061
|
+
});
|
|
4062
|
+
}
|
|
4063
|
+
}, this.cleanupInterval);
|
|
4064
|
+
|
|
4065
|
+
this._cleanupTimer.unref();
|
|
4066
|
+
}
|
|
4067
|
+
|
|
4068
|
+
}
|
|
4069
|
+
|
|
4070
|
+
|
|
3886
4071
|
// --- BaseModel with bcrypt hashing ---
|
|
3887
4072
|
const bcrypt = tryRequire('bcrypt');
|
|
3888
4073
|
class BaseModel extends Model {
|
|
@@ -3971,4 +4156,4 @@ class BaseModel extends Model {
|
|
|
3971
4156
|
}
|
|
3972
4157
|
}
|
|
3973
4158
|
|
|
3974
|
-
module.exports = { DB, Model, Validator, ValidationError, Collection, QueryBuilder, HasMany, HasOne, BelongsTo, BelongsToMany, DBError, BaseModel};
|
|
4159
|
+
module.exports = { DB, Model, Validator, ValidationError, Collection, QueryBuilder, HasMany, HasOne, BelongsTo, BelongsToMany, DBError, LamixSessionStore, BaseModel};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lamix",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.13",
|
|
4
4
|
"description": "lamix - ORM for Node-express js",
|
|
5
5
|
"main": "./lib",
|
|
6
6
|
"exports": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"bcrypt": "^6.0.0",
|
|
33
|
+
"express-session": "^1.19.0",
|
|
33
34
|
"chalk": "^4.1.2",
|
|
34
35
|
"dotenv": "^17.2.2"
|
|
35
36
|
},
|
|
@@ -46,6 +47,6 @@
|
|
|
46
47
|
},
|
|
47
48
|
"license": "MIT",
|
|
48
49
|
"engines": {
|
|
49
|
-
"node": ">=
|
|
50
|
+
"node": ">=18.0.0"
|
|
50
51
|
}
|
|
51
52
|
}
|