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.
Files changed (3) hide show
  1. package/README.md +56 -3
  2. package/lib/index.js +195 -10
  3. 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=100`.
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
- // Many-to-many: User ↔ Role via pivot user_roles (user_id, role_id)
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
- // One-to-many: User -> Post
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
- const m = tryRequire('mysql2/promise');
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
- const m = tryRequire('pg');
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
- const m = tryRequire('sqlite3');
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.12",
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": ">= 0.8.0"
50
+ "node": ">=18.0.0"
50
51
  }
51
52
  }