lamix 4.2.14 → 4.2.16

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/lib/index.d.ts CHANGED
@@ -492,12 +492,26 @@ export class LamixSessionStore {
492
492
  constructor(options?: {});
493
493
  ttl: any;
494
494
  cleanupInterval: any;
495
- get(sid: any, cb: any): Promise<any>;
495
+ logger: any;
496
+ compress: any;
497
+ cache: LRU<any, any>;
498
+ redisEnabled: boolean;
499
+ redisRetryAt: number;
500
+ redisCooldown: any;
501
+ redis: any;
502
+ _safeCb(cb: any, err: any, result?: any): void;
503
+ _hasSessionModel(): boolean;
504
+ _canUseRedis(): boolean;
505
+ _markRedisFailure(err: any): void;
506
+ _serialize(data: any): string;
507
+ _deserialize(raw: any): any;
508
+ _checkRedisHealth(): Promise<void>;
509
+ get(sid: any, cb: any): Promise<void>;
496
510
  set(sid: any, sessionData: any, cb: any): Promise<void>;
497
511
  destroy(sid: any, cb: any): Promise<void>;
498
- touch(sid: any, sessionData: any, cb: any): Promise<any>;
512
+ touch(sid: any, sessionData: any, cb: any): Promise<void>;
499
513
  _startCleanup(): void;
500
- _cleanupTimer: NodeJS.Timeout;
514
+ _cleanupTimer: any;
501
515
  }
502
516
  export class BaseModel extends Model {
503
517
  static passwordField: string;
@@ -597,4 +611,5 @@ declare class Relation {
597
611
  deleteBehavior: any;
598
612
  onDelete(behavior: any): this;
599
613
  }
614
+ import LRU = require("lru-cache");
600
615
  export {};
package/lib/index.js CHANGED
@@ -3891,6 +3891,12 @@ class Model {
3891
3891
  }
3892
3892
  }
3893
3893
 
3894
+ const session = require('express-session');
3895
+ const Redis = require('ioredis');
3896
+ const LRU = require('lru-cache');
3897
+ const zlib = require('zlib');
3898
+
3899
+ /* -------------------- DB Model -------------------- */
3894
3900
  class Session extends Model {
3895
3901
  static table = 'sessions';
3896
3902
  static primaryKey = 'sid';
@@ -3900,44 +3906,159 @@ class Session extends Model {
3900
3906
  static fillable = ['sid', 'data', 'expires'];
3901
3907
  }
3902
3908
 
3903
- const session = require('express-session');
3909
+ /* -------------------- Optional Session Model -------------------- */
3910
+ let SessionModel = null;
3904
3911
 
3912
+ try {
3913
+ // If this throws, DB-backed sessions are simply disabled
3914
+ SessionModel = Session;
3915
+ } catch (err) {
3916
+ SessionModel = null;
3917
+ }
3918
+
3919
+ /* -------------------- Store -------------------- */
3905
3920
  class LamixSessionStore extends session.Store {
3906
3921
  constructor(options = {}) {
3907
3922
  super();
3908
3923
 
3909
- this.ttl = options.ttl || 86400; // seconds
3924
+ this.ttl = options.ttl || 86400;
3910
3925
  this.cleanupInterval = options.cleanupInterval || 60000;
3926
+ this.logger = options.logger || console;
3927
+ this.compress = options.compress ?? true;
3928
+
3929
+ /* -------- In-memory cache -------- */
3930
+ this.cache = new LRU({
3931
+ max: options.cacheSize || 1000,
3932
+ ttl: options.cacheTTL || 60000,
3933
+ });
3934
+
3935
+ /* -------- Redis -------- */
3936
+ this.redisEnabled = false;
3937
+ this.redisRetryAt = 0;
3938
+ this.redisCooldown = options.redisCooldown || 30000;
3939
+
3940
+ if (options.redis) {
3941
+ this.redis =
3942
+ options.redis instanceof Redis
3943
+ ? options.redis
3944
+ : new Redis(options.redis);
3945
+
3946
+ this._checkRedisHealth();
3947
+ }
3911
3948
 
3912
3949
  this._startCleanup();
3913
3950
  }
3914
3951
 
3915
- /* ---------- Get ---------- */
3952
+ /* -------------------- Safety Helpers -------------------- */
3953
+ _safeCb(cb, err, result = null) {
3954
+ try {
3955
+ cb(err, result);
3956
+ } catch (e) {
3957
+ this.logger.error('Session callback threw', e);
3958
+ }
3959
+ }
3960
+
3961
+ _hasSessionModel() {
3962
+ return !!SessionModel;
3963
+ }
3916
3964
 
3917
- async get(sid, cb) {
3965
+ _canUseRedis() {
3966
+ return this.redisEnabled && Date.now() > this.redisRetryAt;
3967
+ }
3968
+
3969
+ _markRedisFailure(err) {
3970
+ this.redisEnabled = false;
3971
+ this.redisRetryAt = Date.now() + this.redisCooldown;
3972
+ this.logger.warn('Redis disabled temporarily', err);
3973
+ }
3974
+
3975
+ /* -------------------- Serialization -------------------- */
3976
+ _serialize(data) {
3918
3977
  try {
3919
- const now = Date.now();
3978
+ const json = JSON.stringify(data);
3979
+ return this.compress
3980
+ ? zlib.deflateSync(Buffer.from(json)).toString('base64')
3981
+ : json;
3982
+ } catch (err) {
3983
+ this.logger.warn('Serialize failed, skipping persistence', err);
3984
+ return null;
3985
+ }
3986
+ }
3920
3987
 
3921
- const row = await Session
3922
- .query()
3923
- .where('sid', sid)
3924
- .where('expires', '>', now)
3925
- .first();
3988
+ _deserialize(raw) {
3989
+ if (!raw) return null;
3926
3990
 
3927
- if (!row) return cb(null, null);
3991
+ try {
3992
+ const str = this.compress
3993
+ ? zlib.inflateSync(Buffer.from(raw, 'base64')).toString()
3994
+ : raw;
3928
3995
 
3929
- cb(null, JSON.parse(row.data));
3996
+ return JSON.parse(str);
3930
3997
  } catch (err) {
3931
- cb(new DBError('Failed to load session', {
3932
- sid,
3933
- operation: 'get',
3934
- err
3935
- }));
3998
+ this.logger.warn('Deserialize failed, dropping session', err);
3999
+ return null;
3936
4000
  }
3937
4001
  }
3938
4002
 
3939
- /* ---------- Set ---------- */
4003
+ /* -------------------- Redis Health -------------------- */
4004
+ async _checkRedisHealth() {
4005
+ try {
4006
+ await this.redis.ping();
4007
+ this.redisEnabled = true;
4008
+ this.logger.info('Redis session store active');
4009
+
4010
+ this.redis.on('error', (err) => this._markRedisFailure(err));
4011
+ this.redis.on('connect', () => {
4012
+ this.redisEnabled = true;
4013
+ this.logger.info('Redis reconnected');
4014
+ });
4015
+ } catch (err) {
4016
+ this.redisEnabled = false;
4017
+ this.logger.warn('Redis unavailable', err);
4018
+ }
4019
+ }
4020
+
4021
+ /* -------------------- GET -------------------- */
4022
+ async get(sid, cb) {
4023
+ let sessionData = null;
4024
+
4025
+ try {
4026
+ if (this.cache.has(sid)) {
4027
+ return this._safeCb(cb, null, this.cache.get(sid));
4028
+ }
4029
+
4030
+ if (this._canUseRedis()) {
4031
+ try {
4032
+ const raw = await this.redis.get(`sess:${sid}`);
4033
+ sessionData = this._deserialize(raw);
4034
+ } catch (err) {
4035
+ this._markRedisFailure(err);
4036
+ }
4037
+ }
4038
+
4039
+ if (!sessionData && this._hasSessionModel()) {
4040
+ try {
4041
+ const row = await SessionModel.query()
4042
+ .where('sid', sid)
4043
+ .where('expires', '>', Date.now())
4044
+ .first();
4045
+
4046
+ if (row) sessionData = this._deserialize(row.data);
4047
+ } catch (err) {
4048
+ this.logger.warn('DB GET failed, disabling DB sessions', err);
4049
+ SessionModel = null;
4050
+ }
4051
+ }
4052
+
4053
+ if (sessionData) this.cache.set(sid, sessionData);
4054
+ this._safeCb(cb, null, sessionData);
4055
+ } catch (err) {
4056
+ this.logger.error('GET failed safely', err);
4057
+ this._safeCb(cb, null, null);
4058
+ }
4059
+ }
3940
4060
 
4061
+ /* -------------------- SET -------------------- */
3941
4062
  async set(sid, sessionData, cb) {
3942
4063
  try {
3943
4064
  const expires =
@@ -3945,58 +4066,75 @@ class LamixSessionStore extends session.Store {
3945
4066
  ? new Date(sessionData.cookie.expires).getTime()
3946
4067
  : Date.now() + this.ttl * 1000;
3947
4068
 
3948
- const payload = {
3949
- sid,
3950
- data: JSON.stringify(sessionData),
3951
- expires
3952
- };
4069
+ const data = this._serialize(sessionData);
4070
+ if (!data) return this._safeCb(cb, null);
3953
4071
 
3954
- // const existing = await Session.find(sid);
3955
- const existing = await Session
3956
- .query()
3957
- .where('sid', sid)
3958
- .first();
4072
+ this.cache.set(sid, sessionData);
3959
4073
 
3960
- if (existing) {
3961
- await existing.update(payload);
3962
- } else {
3963
- const session = new Session(payload, false);
3964
- await session.saveNew(payload);
4074
+ if (this._hasSessionModel()) {
4075
+ try {
4076
+ const existing = await SessionModel.query()
4077
+ .where('sid', sid)
4078
+ .first();
4079
+
4080
+ if (existing) {
4081
+ await existing.update({ data, expires });
4082
+ } else {
4083
+ await new SessionModel({ sid, data, expires }, false).saveNew();
4084
+ }
4085
+ } catch (err) {
4086
+ this.logger.warn('DB SET failed, disabling DB sessions', err);
4087
+ SessionModel = null;
4088
+ }
4089
+ }
4090
+
4091
+ if (this._canUseRedis()) {
4092
+ try {
4093
+ await this.redis.set(`sess:${sid}`, data, 'PX', expires - Date.now());
4094
+ } catch (err) {
4095
+ this._markRedisFailure(err);
4096
+ }
3965
4097
  }
3966
4098
 
3967
- cb(null);
4099
+ this._safeCb(cb, null);
3968
4100
  } catch (err) {
3969
- cb(new DBError('Failed to persist session', {
3970
- sid,
3971
- operation: 'set',
3972
- err
3973
- }));
4101
+ this.logger.error('SET failed safely', err);
4102
+ this._safeCb(cb, null);
3974
4103
  }
3975
4104
  }
3976
4105
 
3977
- /* ---------- Destroy ---------- */
3978
-
4106
+ /* -------------------- DESTROY -------------------- */
3979
4107
  async destroy(sid, cb) {
3980
4108
  try {
3981
- await Session
3982
- .query()
3983
- .where('sid', sid)
3984
- .delete();
4109
+ this.cache.delete(sid);
3985
4110
 
3986
- cb(null);
4111
+ if (this._hasSessionModel()) {
4112
+ try {
4113
+ await SessionModel.query().where('sid', sid).delete();
4114
+ } catch (err) {
4115
+ this.logger.warn('DB DESTROY failed, disabling DB sessions', err);
4116
+ SessionModel = null;
4117
+ }
4118
+ }
4119
+
4120
+ if (this._canUseRedis()) {
4121
+ try {
4122
+ await this.redis.del(`sess:${sid}`);
4123
+ } catch (err) {
4124
+ this._markRedisFailure(err);
4125
+ }
4126
+ }
4127
+
4128
+ this._safeCb(cb, null);
3987
4129
  } catch (err) {
3988
- cb(new DBError('Failed to destroy session', {
3989
- sid,
3990
- operation: 'destroy',
3991
- err
3992
- }));
4130
+ this.logger.error('DESTROY failed safely', err);
4131
+ this._safeCb(cb, null);
3993
4132
  }
3994
4133
  }
3995
4134
 
3996
- /* ---------- Touch ---------- */
3997
-
4135
+ /* -------------------- TOUCH -------------------- */
3998
4136
  async touch(sid, sessionData, cb) {
3999
- if (!sessionData) return cb(null);
4137
+ if (!sessionData) return this._safeCb(cb, null);
4000
4138
 
4001
4139
  try {
4002
4140
  const expires =
@@ -4004,45 +4142,54 @@ class LamixSessionStore extends session.Store {
4004
4142
  ? new Date(sessionData.cookie.expires).getTime()
4005
4143
  : Date.now() + this.ttl * 1000;
4006
4144
 
4007
- await Session
4008
- .query()
4009
- .where('sid', sid)
4010
- .update({ expires });
4145
+ this.cache.set(sid, sessionData);
4011
4146
 
4012
- cb();
4147
+ if (this._hasSessionModel()) {
4148
+ try {
4149
+ await SessionModel.query().where('sid', sid).update({ expires });
4150
+ } catch (err) {
4151
+ this.logger.warn('DB TOUCH failed, disabling DB sessions', err);
4152
+ SessionModel = null;
4153
+ }
4154
+ }
4155
+
4156
+ if (this._canUseRedis()) {
4157
+ try {
4158
+ await this.redis.pexpire(`sess:${sid}`, expires - Date.now());
4159
+ } catch (err) {
4160
+ this._markRedisFailure(err);
4161
+ }
4162
+ }
4163
+
4164
+ this._safeCb(cb, null);
4013
4165
  } catch (err) {
4014
- cb(new DBError('Failed to touch session', {
4015
- sid,
4016
- operation: 'touch',
4017
- err
4018
- }));
4166
+ this.logger.error('TOUCH failed safely', err);
4167
+ this._safeCb(cb, null);
4019
4168
  }
4020
4169
  }
4021
4170
 
4022
- /* ---------- Cleanup ---------- */
4023
-
4171
+ /* -------------------- Cleanup -------------------- */
4024
4172
  _startCleanup() {
4173
+ if (!this._hasSessionModel()) return;
4174
+
4025
4175
  this._cleanupTimer = setInterval(async () => {
4026
4176
  try {
4027
- await Session
4028
- .query()
4177
+ await SessionModel.query()
4029
4178
  .where('expires', '<', Date.now())
4030
4179
  .delete();
4031
4180
  } catch (err) {
4032
- // cleanup must NEVER fail silently
4033
- throw new DBError('Session cleanup failed', {
4034
- operation: 'cleanup',
4035
- err
4036
- });
4181
+ this.logger.warn('Cleanup failed, disabling DB sessions', err);
4182
+ SessionModel = null;
4183
+ clearInterval(this._cleanupTimer);
4037
4184
  }
4038
4185
  }, this.cleanupInterval);
4039
4186
 
4040
4187
  this._cleanupTimer.unref();
4041
4188
  }
4042
-
4043
4189
  }
4044
4190
 
4045
4191
 
4192
+
4046
4193
  // --- BaseModel with bcrypt hashing ---
4047
4194
  const bcrypt = tryRequire('bcrypt');
4048
4195
  class BaseModel extends Model {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lamix",
3
- "version": "4.2.14",
3
+ "version": "4.2.16",
4
4
  "description": "lamix - ORM for Node-express js",
5
5
  "main": "./lib",
6
6
  "type": "commonjs",
@@ -32,6 +32,8 @@
32
32
  "dependencies": {
33
33
  "bcrypt": "^6.0.0",
34
34
  "express-session": "^1.19.0",
35
+ "lru-cache": "^7.18.3",
36
+ "ioredis": "^5.9.2",
35
37
  "chalk": "^4.1.2",
36
38
  "dotenv": "^17.2.2"
37
39
  },