lamix 4.2.12 → 4.2.14

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 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,50 @@ 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
- }
290
+
291
+ # migrate sessions Table(whenever migration is run session is auto generated if missing)
292
+ npx lamix migrate
293
+ ➡️ sessions table + index are guaranteed to exist.
294
+
295
+ # Session setup
296
+ const express = require('express');
297
+ const session = require('express-session');
298
+ const { DB, LamixSessionStore } = require('lamix');
299
+
300
+ DB.initFromEnv();
301
+
302
+ (async () => {
303
+ await DB.connect();
304
+ })();
305
+
306
+ const app = express();
307
+
308
+ app.use(
309
+ session({
310
+ name: 'lamix.sid',
311
+ secret: process.env.SESSION_SECRET || 'dev-secret',
312
+ resave: false,
313
+ saveUninitialized: false,
314
+ store: new LamixSessionStore({
315
+ ttl: 60 * 60 * 24, // 1 day
316
+ }),
317
+ cookie: {
318
+ httpOnly: true,
319
+ secure: false, // true behind HTTPS
320
+ maxAge: 1000 * 60 * 60 * 24,
321
+ },
322
+ })
323
+ );
324
+
325
+ app.get('/', (req, res) => {
326
+ req.session.views = (req.session.views || 0) + 1;
327
+ res.json({ views: req.session.views });
328
+ });
329
+
330
+ app.listen(3000);
331
+ }
package/bin/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs');
4
4
  const path = require('path');
@@ -274,6 +274,8 @@ async function runMigrations() {
274
274
  DB.initFromEnv();
275
275
  await DB.connect();
276
276
 
277
+ await ensureSessionsTable();
278
+
277
279
  const applied = await getAppliedMigrations();
278
280
  const files = fs.readdirSync(MIGRATIONS_DIR)
279
281
  .filter(f => f.endsWith('.js'))
@@ -348,6 +350,63 @@ async function tableExists(tableName) {
348
350
  return false;
349
351
  }
350
352
 
353
+ // ------------------ SESSION hELPER ------------------
354
+
355
+ async function ensureSessionsTable() {
356
+ const exists = await tableExists('sessions');
357
+ if (exists) {
358
+ log.info('ℹ️ sessions table already exists. Skipping.');
359
+ return;
360
+ }
361
+
362
+ log.info('⚙️ Creating sessions table...');
363
+
364
+ if (DB.driver === 'mysql') {
365
+ await DB.raw(`
366
+ CREATE TABLE sessions (
367
+ sid VARCHAR(255) PRIMARY KEY,
368
+ data TEXT NOT NULL,
369
+ expires BIGINT NOT NULL
370
+ )
371
+ `);
372
+
373
+ await DB.raw(`
374
+ CREATE INDEX idx_sessions_expires ON sessions (expires)
375
+ `);
376
+ }
377
+
378
+ if (DB.driver === 'sqlite') {
379
+ await DB.raw(`
380
+ CREATE TABLE sessions (
381
+ sid TEXT PRIMARY KEY,
382
+ data TEXT NOT NULL,
383
+ expires INTEGER NOT NULL
384
+ )
385
+ `);
386
+
387
+ await DB.raw(`
388
+ CREATE INDEX idx_sessions_expires ON sessions (expires)
389
+ `);
390
+ }
391
+
392
+ if (DB.driver === 'pg') {
393
+ await DB.raw(`
394
+ CREATE TABLE sessions (
395
+ sid VARCHAR(255) PRIMARY KEY,
396
+ data TEXT NOT NULL,
397
+ expires BIGINT NOT NULL
398
+ )
399
+ `);
400
+
401
+ await DB.raw(`
402
+ CREATE INDEX idx_sessions_expires ON sessions (expires)
403
+ `);
404
+ }
405
+
406
+ log.success('✅ sessions table created successfully.');
407
+ }
408
+
409
+
351
410
 
352
411
  async function rollbackLastMigration() {
353
412
  DB.initFromEnv();
package/lib/index.d.ts CHANGED
@@ -488,6 +488,17 @@ export class DBError extends Error {
488
488
  constructor(message: any, meta?: {});
489
489
  meta: {};
490
490
  }
491
+ export class LamixSessionStore {
492
+ constructor(options?: {});
493
+ ttl: any;
494
+ cleanupInterval: any;
495
+ get(sid: any, cb: any): Promise<any>;
496
+ set(sid: any, sessionData: any, cb: any): Promise<void>;
497
+ destroy(sid: any, cb: any): Promise<void>;
498
+ touch(sid: any, sessionData: any, cb: any): Promise<any>;
499
+ _startCleanup(): void;
500
+ _cleanupTimer: NodeJS.Timeout;
501
+ }
491
502
  export class BaseModel extends Model {
492
503
  static passwordField: string;
493
504
  static hashRounds: number;
package/lib/index.js CHANGED
@@ -135,31 +135,39 @@ class DB {
135
135
  }
136
136
 
137
137
  /* ---------- Driver ---------- */
138
-
139
138
  static _ensureModule() {
140
139
  if (!this.driver) this.initFromEnv();
141
140
 
141
+ const safeRequire = (name) => {
142
+ try {
143
+ return require(name);
144
+ } catch (err) {
145
+ if (err.code === 'ERR_REQUIRE_ASYNC_MODULE') {
146
+ throw new DBError(
147
+ `${name} is ESM-only or uses top-level await. Use a CommonJS version.`,
148
+ { module: name, err }
149
+ );
150
+ }
151
+ throw err;
152
+ }
153
+ };
154
+
142
155
  if (this.driver === 'mysql') {
143
- const m = tryRequire('mysql2/promise');
144
- if (!m) throw new DBError('Missing mysql2');
145
- return m;
156
+ return safeRequire('mysql2/promise');
146
157
  }
147
158
 
148
159
  if (this.driver === 'pg') {
149
- const m = tryRequire('pg');
150
- if (!m) throw new DBError('Missing pg');
151
- return m;
160
+ return safeRequire('pg');
152
161
  }
153
162
 
154
163
  if (this.driver === 'sqlite') {
155
- const m = tryRequire('sqlite3');
156
- if (!m) throw new DBError('Missing sqlite3');
157
- return m;
164
+ return safeRequire('sqlite3');
158
165
  }
159
166
 
160
167
  throw new DBError(`Unsupported driver: ${this.driver}`);
161
168
  }
162
169
 
170
+
163
171
  /* ---------- Connection ---------- */
164
172
 
165
173
  static async connect() {
@@ -3883,6 +3891,158 @@ class Model {
3883
3891
  }
3884
3892
  }
3885
3893
 
3894
+ class Session extends Model {
3895
+ static table = 'sessions';
3896
+ static primaryKey = 'sid';
3897
+ static slugKey = null;
3898
+ static timestamps = false;
3899
+
3900
+ static fillable = ['sid', 'data', 'expires'];
3901
+ }
3902
+
3903
+ const session = require('express-session');
3904
+
3905
+ class LamixSessionStore extends session.Store {
3906
+ constructor(options = {}) {
3907
+ super();
3908
+
3909
+ this.ttl = options.ttl || 86400; // seconds
3910
+ this.cleanupInterval = options.cleanupInterval || 60000;
3911
+
3912
+ this._startCleanup();
3913
+ }
3914
+
3915
+ /* ---------- Get ---------- */
3916
+
3917
+ async get(sid, cb) {
3918
+ try {
3919
+ const now = Date.now();
3920
+
3921
+ const row = await Session
3922
+ .query()
3923
+ .where('sid', sid)
3924
+ .where('expires', '>', now)
3925
+ .first();
3926
+
3927
+ if (!row) return cb(null, null);
3928
+
3929
+ cb(null, JSON.parse(row.data));
3930
+ } catch (err) {
3931
+ cb(new DBError('Failed to load session', {
3932
+ sid,
3933
+ operation: 'get',
3934
+ err
3935
+ }));
3936
+ }
3937
+ }
3938
+
3939
+ /* ---------- Set ---------- */
3940
+
3941
+ async set(sid, sessionData, cb) {
3942
+ try {
3943
+ const expires =
3944
+ sessionData.cookie?.expires
3945
+ ? new Date(sessionData.cookie.expires).getTime()
3946
+ : Date.now() + this.ttl * 1000;
3947
+
3948
+ const payload = {
3949
+ sid,
3950
+ data: JSON.stringify(sessionData),
3951
+ expires
3952
+ };
3953
+
3954
+ // const existing = await Session.find(sid);
3955
+ const existing = await Session
3956
+ .query()
3957
+ .where('sid', sid)
3958
+ .first();
3959
+
3960
+ if (existing) {
3961
+ await existing.update(payload);
3962
+ } else {
3963
+ const session = new Session(payload, false);
3964
+ await session.saveNew(payload);
3965
+ }
3966
+
3967
+ cb(null);
3968
+ } catch (err) {
3969
+ cb(new DBError('Failed to persist session', {
3970
+ sid,
3971
+ operation: 'set',
3972
+ err
3973
+ }));
3974
+ }
3975
+ }
3976
+
3977
+ /* ---------- Destroy ---------- */
3978
+
3979
+ async destroy(sid, cb) {
3980
+ try {
3981
+ await Session
3982
+ .query()
3983
+ .where('sid', sid)
3984
+ .delete();
3985
+
3986
+ cb(null);
3987
+ } catch (err) {
3988
+ cb(new DBError('Failed to destroy session', {
3989
+ sid,
3990
+ operation: 'destroy',
3991
+ err
3992
+ }));
3993
+ }
3994
+ }
3995
+
3996
+ /* ---------- Touch ---------- */
3997
+
3998
+ async touch(sid, sessionData, cb) {
3999
+ if (!sessionData) return cb(null);
4000
+
4001
+ try {
4002
+ const expires =
4003
+ sessionData.cookie?.expires
4004
+ ? new Date(sessionData.cookie.expires).getTime()
4005
+ : Date.now() + this.ttl * 1000;
4006
+
4007
+ await Session
4008
+ .query()
4009
+ .where('sid', sid)
4010
+ .update({ expires });
4011
+
4012
+ cb();
4013
+ } catch (err) {
4014
+ cb(new DBError('Failed to touch session', {
4015
+ sid,
4016
+ operation: 'touch',
4017
+ err
4018
+ }));
4019
+ }
4020
+ }
4021
+
4022
+ /* ---------- Cleanup ---------- */
4023
+
4024
+ _startCleanup() {
4025
+ this._cleanupTimer = setInterval(async () => {
4026
+ try {
4027
+ await Session
4028
+ .query()
4029
+ .where('expires', '<', Date.now())
4030
+ .delete();
4031
+ } catch (err) {
4032
+ // cleanup must NEVER fail silently
4033
+ throw new DBError('Session cleanup failed', {
4034
+ operation: 'cleanup',
4035
+ err
4036
+ });
4037
+ }
4038
+ }, this.cleanupInterval);
4039
+
4040
+ this._cleanupTimer.unref();
4041
+ }
4042
+
4043
+ }
4044
+
4045
+
3886
4046
  // --- BaseModel with bcrypt hashing ---
3887
4047
  const bcrypt = tryRequire('bcrypt');
3888
4048
  class BaseModel extends Model {
@@ -3971,4 +4131,4 @@ class BaseModel extends Model {
3971
4131
  }
3972
4132
  }
3973
4133
 
3974
- module.exports = { DB, Model, Validator, ValidationError, Collection, QueryBuilder, HasMany, HasOne, BelongsTo, BelongsToMany, DBError, BaseModel};
4134
+ module.exports = { DB, Model, Validator, ValidationError, Collection, QueryBuilder, HasMany, HasOne, BelongsTo, BelongsToMany, DBError, LamixSessionStore, BaseModel};
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "lamix",
3
- "version": "4.2.12",
3
+ "version": "4.2.14",
4
4
  "description": "lamix - ORM for Node-express js",
5
5
  "main": "./lib",
6
+ "type": "commonjs",
6
7
  "exports": {
7
8
  ".": {
8
9
  "require": "./lib/index.js",
@@ -30,6 +31,7 @@
30
31
  },
31
32
  "dependencies": {
32
33
  "bcrypt": "^6.0.0",
34
+ "express-session": "^1.19.0",
33
35
  "chalk": "^4.1.2",
34
36
  "dotenv": "^17.2.2"
35
37
  },
@@ -38,6 +40,7 @@
38
40
  "orm",
39
41
  "nodejs",
40
42
  "database",
43
+ "express",
41
44
  "sql"
42
45
  ],
43
46
  "author": {
@@ -46,6 +49,6 @@
46
49
  },
47
50
  "license": "MIT",
48
51
  "engines": {
49
- "node": ">= 0.8.0"
52
+ "node": ">=18.0.0"
50
53
  }
51
54
  }