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 +18 -3
- package/lib/index.js +221 -74
- package/package.json +3 -1
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
|
-
|
|
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<
|
|
512
|
+
touch(sid: any, sessionData: any, cb: any): Promise<void>;
|
|
499
513
|
_startCleanup(): void;
|
|
500
|
-
_cleanupTimer:
|
|
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
|
-
|
|
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;
|
|
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
|
-
/*
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
3922
|
-
|
|
3923
|
-
.where('sid', sid)
|
|
3924
|
-
.where('expires', '>', now)
|
|
3925
|
-
.first();
|
|
3988
|
+
_deserialize(raw) {
|
|
3989
|
+
if (!raw) return null;
|
|
3926
3990
|
|
|
3927
|
-
|
|
3991
|
+
try {
|
|
3992
|
+
const str = this.compress
|
|
3993
|
+
? zlib.inflateSync(Buffer.from(raw, 'base64')).toString()
|
|
3994
|
+
: raw;
|
|
3928
3995
|
|
|
3929
|
-
|
|
3996
|
+
return JSON.parse(str);
|
|
3930
3997
|
} catch (err) {
|
|
3931
|
-
|
|
3932
|
-
|
|
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
|
-
/*
|
|
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
|
|
3949
|
-
|
|
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
|
-
|
|
3955
|
-
const existing = await Session
|
|
3956
|
-
.query()
|
|
3957
|
-
.where('sid', sid)
|
|
3958
|
-
.first();
|
|
4072
|
+
this.cache.set(sid, sessionData);
|
|
3959
4073
|
|
|
3960
|
-
if (
|
|
3961
|
-
|
|
3962
|
-
|
|
3963
|
-
|
|
3964
|
-
|
|
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
|
|
4099
|
+
this._safeCb(cb, null);
|
|
3968
4100
|
} catch (err) {
|
|
3969
|
-
|
|
3970
|
-
|
|
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
|
-
/*
|
|
3978
|
-
|
|
4106
|
+
/* -------------------- DESTROY -------------------- */
|
|
3979
4107
|
async destroy(sid, cb) {
|
|
3980
4108
|
try {
|
|
3981
|
-
|
|
3982
|
-
.query()
|
|
3983
|
-
.where('sid', sid)
|
|
3984
|
-
.delete();
|
|
4109
|
+
this.cache.delete(sid);
|
|
3985
4110
|
|
|
3986
|
-
|
|
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
|
-
|
|
3989
|
-
|
|
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
|
-
/*
|
|
3997
|
-
|
|
4135
|
+
/* -------------------- TOUCH -------------------- */
|
|
3998
4136
|
async touch(sid, sessionData, cb) {
|
|
3999
|
-
if (!sessionData) return cb
|
|
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
|
-
|
|
4008
|
-
.query()
|
|
4009
|
-
.where('sid', sid)
|
|
4010
|
-
.update({ expires });
|
|
4145
|
+
this.cache.set(sid, sessionData);
|
|
4011
4146
|
|
|
4012
|
-
|
|
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
|
-
|
|
4015
|
-
|
|
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
|
-
/*
|
|
4023
|
-
|
|
4171
|
+
/* -------------------- Cleanup -------------------- */
|
|
4024
4172
|
_startCleanup() {
|
|
4173
|
+
if (!this._hasSessionModel()) return;
|
|
4174
|
+
|
|
4025
4175
|
this._cleanupTimer = setInterval(async () => {
|
|
4026
4176
|
try {
|
|
4027
|
-
await
|
|
4028
|
-
.query()
|
|
4177
|
+
await SessionModel.query()
|
|
4029
4178
|
.where('expires', '<', Date.now())
|
|
4030
4179
|
.delete();
|
|
4031
4180
|
} catch (err) {
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
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.
|
|
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
|
},
|