lamix 4.2.19 → 4.2.21
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/bin/cli.js +1 -1
- package/lib/index.d.ts +39 -30
- package/lib/index.js +220 -239
- package/package.json +2 -3
package/bin/cli.js
CHANGED
package/lib/index.d.ts
CHANGED
|
@@ -67,6 +67,7 @@ export class Model {
|
|
|
67
67
|
static whereNot(...args: any[]): QueryBuilder;
|
|
68
68
|
static whereNotIn(col: any, arr: any): QueryBuilder;
|
|
69
69
|
static whereNull(col: any): QueryBuilder;
|
|
70
|
+
static filter(...args: any[]): QueryBuilder;
|
|
70
71
|
static find(value: any): Promise<any>;
|
|
71
72
|
static findOrFail(value: any): Promise<any>;
|
|
72
73
|
static findBy(col: any, value: any): Promise<any>;
|
|
@@ -212,7 +213,15 @@ export class Collection extends Array<any> {
|
|
|
212
213
|
clone(): Collection;
|
|
213
214
|
each(fn: any): Promise<this>;
|
|
214
215
|
mapAsync(fn: any): Promise<Collection>;
|
|
215
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Enhanced filter:
|
|
218
|
+
* - filter(fn)
|
|
219
|
+
* - filter({ key: value })
|
|
220
|
+
* - filter('key', value)
|
|
221
|
+
* - filter('key', '>', value)
|
|
222
|
+
*/
|
|
223
|
+
filter(condition: any, operator?: any, value?: any): Collection;
|
|
224
|
+
where(key: any, operator: any, value?: any): Collection;
|
|
216
225
|
whereNot(key: any, value: any): Collection;
|
|
217
226
|
filterNull(): Collection;
|
|
218
227
|
onlyKeys(keys: any): Collection;
|
|
@@ -492,26 +501,12 @@ export class LamixSessionStore {
|
|
|
492
501
|
constructor(options?: {});
|
|
493
502
|
ttl: any;
|
|
494
503
|
cleanupInterval: any;
|
|
495
|
-
|
|
496
|
-
compress: any;
|
|
497
|
-
cache: 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>;
|
|
504
|
+
get(sid: any, cb: any): Promise<any>;
|
|
510
505
|
set(sid: any, sessionData: any, cb: any): Promise<void>;
|
|
511
506
|
destroy(sid: any, cb: any): Promise<void>;
|
|
512
|
-
touch(sid: any, sessionData: any, cb: any): Promise<
|
|
507
|
+
touch(sid: any, sessionData: any, cb: any): Promise<any>;
|
|
513
508
|
_startCleanup(): void;
|
|
514
|
-
_cleanupTimer:
|
|
509
|
+
_cleanupTimer: NodeJS.Timeout;
|
|
515
510
|
}
|
|
516
511
|
export class BaseModel extends Model {
|
|
517
512
|
static passwordField: string;
|
|
@@ -548,23 +543,36 @@ export class BaseModel extends Model {
|
|
|
548
543
|
}
|
|
549
544
|
declare class SimpleCache {
|
|
550
545
|
constructor(options?: {});
|
|
551
|
-
cache:
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
546
|
+
cache: LRUCache<{}, {}, unknown>;
|
|
547
|
+
/**
|
|
548
|
+
* Get cached value
|
|
549
|
+
* @param {string} key
|
|
550
|
+
* @returns {*} value or null
|
|
551
|
+
*/
|
|
552
|
+
get(key: string): any;
|
|
557
553
|
/**
|
|
558
|
-
*
|
|
559
|
-
*
|
|
554
|
+
* Set cache value
|
|
555
|
+
* @param {string} key
|
|
556
|
+
* @param {*} value
|
|
557
|
+
* @param {number} ttl Optional TTL in ms
|
|
560
558
|
*/
|
|
561
|
-
|
|
559
|
+
set(key: string, value: any, ttl?: number): void;
|
|
560
|
+
/**
|
|
561
|
+
* Delete a key
|
|
562
|
+
* @param {string} key
|
|
563
|
+
*/
|
|
564
|
+
del(key: string): void;
|
|
565
|
+
/**
|
|
566
|
+
* Clear entire cache
|
|
567
|
+
*/
|
|
568
|
+
clear(): void;
|
|
562
569
|
/**
|
|
563
|
-
*
|
|
570
|
+
* Get cache stats (optional utility)
|
|
564
571
|
*/
|
|
565
572
|
stats(): {
|
|
566
|
-
size:
|
|
567
|
-
calculatedSize:
|
|
573
|
+
size: number;
|
|
574
|
+
calculatedSize: number;
|
|
575
|
+
maxSize: number;
|
|
568
576
|
};
|
|
569
577
|
}
|
|
570
578
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
@@ -625,4 +633,5 @@ declare class Relation {
|
|
|
625
633
|
deleteBehavior: any;
|
|
626
634
|
onDelete(behavior: any): this;
|
|
627
635
|
}
|
|
636
|
+
import { LRUCache } from "lru-cache";
|
|
628
637
|
export {};
|
package/lib/index.js
CHANGED
|
@@ -5,7 +5,6 @@ const { performance } = require('perf_hooks');
|
|
|
5
5
|
const { AsyncLocalStorage } = require('async_hooks');
|
|
6
6
|
require('dotenv').config();
|
|
7
7
|
const session = require('express-session');
|
|
8
|
-
const Redis = require('ioredis');
|
|
9
8
|
const { LRUCache } = require('lru-cache');
|
|
10
9
|
const zlib = require('zlib');
|
|
11
10
|
|
|
@@ -32,68 +31,75 @@ class DBError extends Error {
|
|
|
32
31
|
class SimpleCache {
|
|
33
32
|
constructor(options = {}) {
|
|
34
33
|
const {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
maxSize, // optional size-based eviction
|
|
38
|
-
sizeCalculation, // function(value, key)
|
|
34
|
+
maxSizeMB = 50, // total memory cap
|
|
35
|
+
defaultTTL = 1000 * 60 * 5, // 5 minutes
|
|
39
36
|
} = options;
|
|
40
37
|
|
|
41
38
|
this.cache = new LRUCache({
|
|
42
|
-
|
|
43
|
-
ttl,
|
|
39
|
+
maxSize: maxSizeMB * 1024 * 1024, // convert MB → bytes
|
|
40
|
+
ttl: defaultTTL,
|
|
44
41
|
ttlAutopurge: true,
|
|
45
|
-
maxSize,
|
|
46
|
-
sizeCalculation,
|
|
47
42
|
allowStale: false,
|
|
48
|
-
|
|
43
|
+
|
|
44
|
+
// Estimate memory usage per entry
|
|
45
|
+
sizeCalculation: (value, key) => {
|
|
46
|
+
try {
|
|
47
|
+
return Buffer.byteLength(JSON.stringify(value));
|
|
48
|
+
} catch (err) {
|
|
49
|
+
// fallback small size if stringify fails
|
|
50
|
+
return 1024;
|
|
51
|
+
}
|
|
52
|
+
},
|
|
49
53
|
});
|
|
50
54
|
}
|
|
51
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Get cached value
|
|
58
|
+
* @param {string} key
|
|
59
|
+
* @returns {*} value or null
|
|
60
|
+
*/
|
|
52
61
|
get(key) {
|
|
53
62
|
const value = this.cache.get(key);
|
|
54
63
|
return value === undefined ? null : value;
|
|
55
64
|
}
|
|
56
65
|
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
/**
|
|
67
|
+
* Set cache value
|
|
68
|
+
* @param {string} key
|
|
69
|
+
* @param {*} value
|
|
70
|
+
* @param {number} ttl Optional TTL in ms
|
|
71
|
+
*/
|
|
72
|
+
set(key, value, ttl = 0) {
|
|
73
|
+
if (ttl > 0) {
|
|
59
74
|
this.cache.set(key, value, { ttl });
|
|
60
75
|
} else {
|
|
61
76
|
this.cache.set(key, value);
|
|
62
77
|
}
|
|
63
78
|
}
|
|
64
79
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
clear() {
|
|
74
|
-
this.cache.clear();
|
|
80
|
+
/**
|
|
81
|
+
* Delete a key
|
|
82
|
+
* @param {string} key
|
|
83
|
+
*/
|
|
84
|
+
del(key) {
|
|
85
|
+
this.cache.delete(key);
|
|
75
86
|
}
|
|
76
87
|
|
|
77
88
|
/**
|
|
78
|
-
*
|
|
79
|
-
* Supports async functions
|
|
89
|
+
* Clear entire cache
|
|
80
90
|
*/
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
if (existing !== null) return existing;
|
|
84
|
-
|
|
85
|
-
const value = await fetchFn();
|
|
86
|
-
this.set(key, value, ttl);
|
|
87
|
-
return value;
|
|
91
|
+
clear() {
|
|
92
|
+
this.cache.clear();
|
|
88
93
|
}
|
|
89
94
|
|
|
90
95
|
/**
|
|
91
|
-
*
|
|
96
|
+
* Get cache stats (optional utility)
|
|
92
97
|
*/
|
|
93
98
|
stats() {
|
|
94
99
|
return {
|
|
95
100
|
size: this.cache.size,
|
|
96
101
|
calculatedSize: this.cache.calculatedSize,
|
|
102
|
+
maxSize: this.cache.maxSize,
|
|
97
103
|
};
|
|
98
104
|
}
|
|
99
105
|
}
|
|
@@ -124,7 +130,7 @@ class DB {
|
|
|
124
130
|
static config = null;
|
|
125
131
|
static pool = null;
|
|
126
132
|
|
|
127
|
-
static cache = new SimpleCache({
|
|
133
|
+
static cache = new SimpleCache({ maxSizeMB: 500, defaultTTL: 1000 * 60 * 2});
|
|
128
134
|
static retryAttempts = 1;
|
|
129
135
|
|
|
130
136
|
static eventHandlers = { query: [], error: [], reconnect: [] };
|
|
@@ -929,16 +935,78 @@ class Collection extends Array {
|
|
|
929
935
|
// -------------------------
|
|
930
936
|
// Filtering
|
|
931
937
|
// -------------------------
|
|
932
|
-
|
|
933
|
-
|
|
938
|
+
/**
|
|
939
|
+
* Enhanced filter:
|
|
940
|
+
* - filter(fn)
|
|
941
|
+
* - filter({ key: value })
|
|
942
|
+
* - filter('key', value)
|
|
943
|
+
* - filter('key', '>', value)
|
|
944
|
+
*/
|
|
945
|
+
filter(condition, operator = null, value = null) {
|
|
946
|
+
|
|
947
|
+
// 1️⃣ Callback style
|
|
948
|
+
if (typeof condition === 'function') {
|
|
949
|
+
return new Collection(
|
|
950
|
+
Array.prototype.filter.call(this, condition)
|
|
951
|
+
);
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
// 2️⃣ Object style
|
|
955
|
+
if (typeof condition === 'object' && !Array.isArray(condition)) {
|
|
956
|
+
return new Collection(
|
|
957
|
+
Array.prototype.filter.call(this, item =>
|
|
958
|
+
Object.entries(condition).every(
|
|
959
|
+
([k, v]) => item?.[k] === v
|
|
960
|
+
)
|
|
961
|
+
)
|
|
962
|
+
);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// 3️⃣ Key/value or key/operator/value
|
|
966
|
+
if (typeof condition === 'string') {
|
|
967
|
+
|
|
968
|
+
// If only 2 args → assume "="
|
|
969
|
+
if (value === null) {
|
|
970
|
+
value = operator;
|
|
971
|
+
operator = '=';
|
|
972
|
+
}
|
|
973
|
+
|
|
974
|
+
return new Collection(
|
|
975
|
+
Array.prototype.filter.call(this, item => {
|
|
976
|
+
const field = item?.[condition];
|
|
977
|
+
|
|
978
|
+
switch (operator) {
|
|
979
|
+
case '=':
|
|
980
|
+
case '==': return field == value;
|
|
981
|
+
case '===': return field === value;
|
|
982
|
+
case '!=': return field != value;
|
|
983
|
+
case '!==': return field !== value;
|
|
984
|
+
case '>': return field > value;
|
|
985
|
+
case '>=': return field >= value;
|
|
986
|
+
case '<': return field < value;
|
|
987
|
+
case '<=': return field <= value;
|
|
988
|
+
case 'in': return Array.isArray(value) && value.includes(field);
|
|
989
|
+
case 'not in': return Array.isArray(value) && !value.includes(field);
|
|
990
|
+
default: return false;
|
|
991
|
+
}
|
|
992
|
+
})
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
return new Collection();
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// Laravel-style aliases
|
|
1000
|
+
where(key, operator, value = null) {
|
|
1001
|
+
return this.filter(key, operator, value);
|
|
934
1002
|
}
|
|
935
1003
|
|
|
936
1004
|
whereNot(key, value) {
|
|
937
|
-
return
|
|
1005
|
+
return this.filter(item => item?.[key] !== value);
|
|
938
1006
|
}
|
|
939
1007
|
|
|
940
1008
|
filterNull() {
|
|
941
|
-
return
|
|
1009
|
+
return this.filter(v => v !== null && v !== undefined);
|
|
942
1010
|
}
|
|
943
1011
|
|
|
944
1012
|
onlyKeys(keys) {
|
|
@@ -3217,22 +3285,31 @@ class Model {
|
|
|
3217
3285
|
// ──────────────────────────────
|
|
3218
3286
|
static async all() { return await this.query().get(); }
|
|
3219
3287
|
static where(...args) { return this.query().where(...args); }
|
|
3288
|
+
// static filter(...args) { return this.query().where(...args); }
|
|
3220
3289
|
static whereIn(col, arr) { return this.query().whereIn(col, arr); }
|
|
3221
3290
|
static whereNot(...args) { return this.query().whereNot(...args); }
|
|
3222
3291
|
static whereNotIn(col, arr) { return this.query().whereNotIn(col, arr); }
|
|
3223
3292
|
static whereNull(col) { return this.query().whereNull(col); }
|
|
3224
3293
|
|
|
3294
|
+
static filter(...args) {
|
|
3295
|
+
const qb = this.query();
|
|
3296
|
+
if (args.length === 1 && typeof args[0] === 'object' && !Array.isArray(args[0])) {
|
|
3297
|
+
for (const [key, value] of Object.entries(args[0])) {
|
|
3298
|
+
qb.where(key, value);
|
|
3299
|
+
}
|
|
3300
|
+
return qb;
|
|
3301
|
+
}
|
|
3302
|
+
return qb.where(...args);
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3225
3305
|
static async find(value) {
|
|
3226
3306
|
if (value === undefined || value === null) return null;
|
|
3227
|
-
|
|
3228
3307
|
const query = this.query();
|
|
3229
|
-
|
|
3230
3308
|
// If numeric → try primary key first
|
|
3231
3309
|
if (!isNaN(value)) {
|
|
3232
3310
|
const row = await query.where(this.primaryKey, value).first();
|
|
3233
3311
|
if (row) return row;
|
|
3234
3312
|
}
|
|
3235
|
-
|
|
3236
3313
|
// Fallback or non-numeric → try slug
|
|
3237
3314
|
return await this.query()
|
|
3238
3315
|
.where(this.slugKey, value)
|
|
@@ -3965,155 +4042,79 @@ class Session extends Model {
|
|
|
3965
4042
|
let SessionModel = null;
|
|
3966
4043
|
|
|
3967
4044
|
try {
|
|
3968
|
-
// If this throws, DB-backed sessions are simply disabled
|
|
3969
4045
|
SessionModel = Session;
|
|
3970
4046
|
} catch (err) {
|
|
3971
4047
|
SessionModel = null;
|
|
3972
4048
|
}
|
|
3973
4049
|
|
|
3974
4050
|
/* -------------------- Store -------------------- */
|
|
4051
|
+
const { promisify } = require('util');
|
|
4052
|
+
|
|
4053
|
+
const gzip = promisify(zlib.gzip);
|
|
4054
|
+
const gunzip = promisify(zlib.gunzip);
|
|
4055
|
+
|
|
3975
4056
|
class LamixSessionStore extends session.Store {
|
|
3976
4057
|
constructor(options = {}) {
|
|
3977
4058
|
super();
|
|
3978
4059
|
|
|
3979
|
-
this.ttl = options.ttl || 86400;
|
|
4060
|
+
this.ttl = options.ttl || 86400; // seconds
|
|
3980
4061
|
this.cleanupInterval = options.cleanupInterval || 60000;
|
|
3981
|
-
this.logger = options.logger || console;
|
|
3982
|
-
this.compress = options.compress ?? true;
|
|
3983
|
-
|
|
3984
|
-
/* -------- In-memory cache -------- */
|
|
3985
|
-
this.cache = new LRUCache({
|
|
3986
|
-
max: options.cacheSize || 1000,
|
|
3987
|
-
ttl: options.cacheTTL || 60000,
|
|
3988
|
-
});
|
|
3989
|
-
|
|
3990
|
-
/* -------- Redis -------- */
|
|
3991
|
-
this.redisEnabled = false;
|
|
3992
|
-
this.redisRetryAt = 0;
|
|
3993
|
-
this.redisCooldown = options.redisCooldown || 30000;
|
|
3994
|
-
|
|
3995
|
-
if (options.redis) {
|
|
3996
|
-
this.redis =
|
|
3997
|
-
options.redis instanceof Redis
|
|
3998
|
-
? options.redis
|
|
3999
|
-
: new Redis(options.redis);
|
|
4000
|
-
|
|
4001
|
-
this._checkRedisHealth();
|
|
4002
|
-
}
|
|
4003
4062
|
|
|
4004
4063
|
this._startCleanup();
|
|
4005
4064
|
}
|
|
4006
4065
|
|
|
4007
|
-
/*
|
|
4008
|
-
_safeCb(cb, err, result = null) {
|
|
4009
|
-
try {
|
|
4010
|
-
cb(err, result);
|
|
4011
|
-
} catch (e) {
|
|
4012
|
-
this.logger.error('Session callback threw', e);
|
|
4013
|
-
}
|
|
4014
|
-
}
|
|
4015
|
-
|
|
4016
|
-
_hasSessionModel() {
|
|
4017
|
-
return !!SessionModel;
|
|
4018
|
-
}
|
|
4019
|
-
|
|
4020
|
-
_canUseRedis() {
|
|
4021
|
-
return this.redisEnabled && Date.now() > this.redisRetryAt;
|
|
4022
|
-
}
|
|
4023
|
-
|
|
4024
|
-
_markRedisFailure(err) {
|
|
4025
|
-
this.redisEnabled = false;
|
|
4026
|
-
this.redisRetryAt = Date.now() + this.redisCooldown;
|
|
4027
|
-
this.logger.warn('Redis disabled temporarily', err);
|
|
4028
|
-
}
|
|
4029
|
-
|
|
4030
|
-
/* -------------------- Serialization -------------------- */
|
|
4031
|
-
_serialize(data) {
|
|
4032
|
-
try {
|
|
4033
|
-
const json = JSON.stringify(data);
|
|
4034
|
-
return this.compress
|
|
4035
|
-
? zlib.deflateSync(Buffer.from(json)).toString('base64')
|
|
4036
|
-
: json;
|
|
4037
|
-
} catch (err) {
|
|
4038
|
-
this.logger.warn('Serialize failed, skipping persistence', err);
|
|
4039
|
-
return null;
|
|
4040
|
-
}
|
|
4041
|
-
}
|
|
4042
|
-
|
|
4043
|
-
_deserialize(raw) {
|
|
4044
|
-
if (!raw) return null;
|
|
4066
|
+
/* ---------- Get ---------- */
|
|
4045
4067
|
|
|
4068
|
+
async get(sid, cb) {
|
|
4046
4069
|
try {
|
|
4047
|
-
const
|
|
4048
|
-
? zlib.inflateSync(Buffer.from(raw, 'base64')).toString()
|
|
4049
|
-
: raw;
|
|
4070
|
+
const now = Date.now();
|
|
4050
4071
|
|
|
4051
|
-
|
|
4052
|
-
|
|
4053
|
-
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
}
|
|
4057
|
-
|
|
4058
|
-
/* -------------------- Redis Health -------------------- */
|
|
4059
|
-
async _checkRedisHealth() {
|
|
4060
|
-
try {
|
|
4061
|
-
await this.redis.ping();
|
|
4062
|
-
this.redisEnabled = true;
|
|
4063
|
-
this.logger.info('Redis session store active');
|
|
4064
|
-
|
|
4065
|
-
this.redis.on('error', (err) => this._markRedisFailure(err));
|
|
4066
|
-
this.redis.on('connect', () => {
|
|
4067
|
-
this.redisEnabled = true;
|
|
4068
|
-
this.logger.info('Redis reconnected');
|
|
4069
|
-
});
|
|
4070
|
-
} catch (err) {
|
|
4071
|
-
this.redisEnabled = false;
|
|
4072
|
-
this.logger.warn('Redis unavailable', err);
|
|
4073
|
-
}
|
|
4074
|
-
}
|
|
4072
|
+
const row = await Session
|
|
4073
|
+
.query()
|
|
4074
|
+
.where('sid', sid)
|
|
4075
|
+
.where('expires', '>', now)
|
|
4076
|
+
.first();
|
|
4075
4077
|
|
|
4076
|
-
|
|
4077
|
-
async get(sid, cb) {
|
|
4078
|
-
let sessionData = null;
|
|
4078
|
+
if (!row) return cb(null, null);
|
|
4079
4079
|
|
|
4080
|
-
|
|
4081
|
-
if (this.cache.has(sid)) {
|
|
4082
|
-
return this._safeCb(cb, null, this.cache.get(sid));
|
|
4083
|
-
}
|
|
4080
|
+
let sessionJSON;
|
|
4084
4081
|
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
4091
|
-
|
|
4082
|
+
// ---------- Step 1: Decompress ----------
|
|
4083
|
+
try {
|
|
4084
|
+
const buffer = Buffer.from(row.data, 'base64');
|
|
4085
|
+
const decompressed = await gunzip(buffer);
|
|
4086
|
+
sessionJSON = decompressed.toString('utf8');
|
|
4087
|
+
} catch {
|
|
4088
|
+
// fallback to legacy plain JSON
|
|
4089
|
+
sessionJSON = row.data;
|
|
4092
4090
|
}
|
|
4093
4091
|
|
|
4094
|
-
|
|
4095
|
-
|
|
4096
|
-
|
|
4097
|
-
|
|
4098
|
-
|
|
4099
|
-
|
|
4092
|
+
// ---------- Step 2: Parse JSON safely ----------
|
|
4093
|
+
try {
|
|
4094
|
+
const parsed = JSON.parse(sessionJSON);
|
|
4095
|
+
return cb(null, parsed);
|
|
4096
|
+
} catch (parseErr) {
|
|
4097
|
+
console.warn('Corrupted session detected. Destroying:', sid);
|
|
4098
|
+
|
|
4099
|
+
// destroy corrupted session
|
|
4100
|
+
await Session
|
|
4101
|
+
.query()
|
|
4102
|
+
.where('sid', sid)
|
|
4103
|
+
.delete();
|
|
4100
4104
|
|
|
4101
|
-
|
|
4102
|
-
} catch (err) {
|
|
4103
|
-
this.logger.warn('DB GET failed, disabling DB sessions', err);
|
|
4104
|
-
SessionModel = null;
|
|
4105
|
-
}
|
|
4105
|
+
return cb(null, null); // treat as no session
|
|
4106
4106
|
}
|
|
4107
4107
|
|
|
4108
|
-
if (sessionData) this.cache.set(sid, sessionData);
|
|
4109
|
-
this._safeCb(cb, null, sessionData);
|
|
4110
4108
|
} catch (err) {
|
|
4111
|
-
|
|
4112
|
-
|
|
4109
|
+
console.error('Session GET failed:', err);
|
|
4110
|
+
|
|
4111
|
+
// IMPORTANT: never pass fatal error to express-session
|
|
4112
|
+
return cb(null, null);
|
|
4113
4113
|
}
|
|
4114
4114
|
}
|
|
4115
4115
|
|
|
4116
|
-
/*
|
|
4116
|
+
/* ---------- Set ---------- */
|
|
4117
|
+
|
|
4117
4118
|
async set(sid, sessionData, cb) {
|
|
4118
4119
|
try {
|
|
4119
4120
|
const expires =
|
|
@@ -4121,75 +4122,63 @@ class LamixSessionStore extends session.Store {
|
|
|
4121
4122
|
? new Date(sessionData.cookie.expires).getTime()
|
|
4122
4123
|
: Date.now() + this.ttl * 1000;
|
|
4123
4124
|
|
|
4124
|
-
const
|
|
4125
|
-
if (!data) return this._safeCb(cb, null);
|
|
4125
|
+
const json = JSON.stringify(sessionData);
|
|
4126
4126
|
|
|
4127
|
-
|
|
4127
|
+
// Async compression (non-blocking)
|
|
4128
|
+
const compressed = await gzip(json);
|
|
4129
|
+
const base64Data = compressed.toString('base64');
|
|
4128
4130
|
|
|
4129
|
-
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
if (existing) {
|
|
4136
|
-
await existing.update({ data, expires });
|
|
4137
|
-
} else {
|
|
4138
|
-
await new SessionModel({ sid, data, expires }, false).saveNew();
|
|
4139
|
-
}
|
|
4140
|
-
} catch (err) {
|
|
4141
|
-
this.logger.warn('DB SET failed, disabling DB sessions', err);
|
|
4142
|
-
SessionModel = null;
|
|
4143
|
-
}
|
|
4144
|
-
}
|
|
4131
|
+
const payload = {
|
|
4132
|
+
sid,
|
|
4133
|
+
data: base64Data,
|
|
4134
|
+
expires
|
|
4135
|
+
};
|
|
4145
4136
|
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4137
|
+
const existing = await Session
|
|
4138
|
+
.query()
|
|
4139
|
+
.where('sid', sid)
|
|
4140
|
+
.first();
|
|
4141
|
+
|
|
4142
|
+
if (existing) {
|
|
4143
|
+
await existing.update(payload);
|
|
4144
|
+
} else {
|
|
4145
|
+
const session = new Session(payload, false);
|
|
4146
|
+
await session.saveNew(payload);
|
|
4152
4147
|
}
|
|
4153
4148
|
|
|
4154
|
-
|
|
4149
|
+
cb(null);
|
|
4155
4150
|
} catch (err) {
|
|
4156
|
-
|
|
4157
|
-
|
|
4151
|
+
cb(new DBError('Failed to persist session', {
|
|
4152
|
+
sid,
|
|
4153
|
+
operation: 'set',
|
|
4154
|
+
err
|
|
4155
|
+
}));
|
|
4158
4156
|
}
|
|
4159
4157
|
}
|
|
4160
4158
|
|
|
4161
|
-
/*
|
|
4159
|
+
/* ---------- Destroy ---------- */
|
|
4160
|
+
|
|
4162
4161
|
async destroy(sid, cb) {
|
|
4163
4162
|
try {
|
|
4164
|
-
|
|
4163
|
+
await Session
|
|
4164
|
+
.query()
|
|
4165
|
+
.where('sid', sid)
|
|
4166
|
+
.delete();
|
|
4165
4167
|
|
|
4166
|
-
|
|
4167
|
-
try {
|
|
4168
|
-
await SessionModel.query().where('sid', sid).delete();
|
|
4169
|
-
} catch (err) {
|
|
4170
|
-
this.logger.warn('DB DESTROY failed, disabling DB sessions', err);
|
|
4171
|
-
SessionModel = null;
|
|
4172
|
-
}
|
|
4173
|
-
}
|
|
4174
|
-
|
|
4175
|
-
if (this._canUseRedis()) {
|
|
4176
|
-
try {
|
|
4177
|
-
await this.redis.del(`sess:${sid}`);
|
|
4178
|
-
} catch (err) {
|
|
4179
|
-
this._markRedisFailure(err);
|
|
4180
|
-
}
|
|
4181
|
-
}
|
|
4182
|
-
|
|
4183
|
-
this._safeCb(cb, null);
|
|
4168
|
+
cb(null);
|
|
4184
4169
|
} catch (err) {
|
|
4185
|
-
|
|
4186
|
-
|
|
4170
|
+
cb(new DBError('Failed to destroy session', {
|
|
4171
|
+
sid,
|
|
4172
|
+
operation: 'destroy',
|
|
4173
|
+
err
|
|
4174
|
+
}));
|
|
4187
4175
|
}
|
|
4188
4176
|
}
|
|
4189
4177
|
|
|
4190
|
-
/*
|
|
4178
|
+
/* ---------- Touch ---------- */
|
|
4179
|
+
|
|
4191
4180
|
async touch(sid, sessionData, cb) {
|
|
4192
|
-
if (!sessionData) return
|
|
4181
|
+
if (!sessionData) return cb(null);
|
|
4193
4182
|
|
|
4194
4183
|
try {
|
|
4195
4184
|
const expires =
|
|
@@ -4197,45 +4186,37 @@ class LamixSessionStore extends session.Store {
|
|
|
4197
4186
|
? new Date(sessionData.cookie.expires).getTime()
|
|
4198
4187
|
: Date.now() + this.ttl * 1000;
|
|
4199
4188
|
|
|
4200
|
-
|
|
4189
|
+
await Session
|
|
4190
|
+
.query()
|
|
4191
|
+
.where('sid', sid)
|
|
4192
|
+
.update({ expires });
|
|
4201
4193
|
|
|
4202
|
-
|
|
4203
|
-
try {
|
|
4204
|
-
await SessionModel.query().where('sid', sid).update({ expires });
|
|
4205
|
-
} catch (err) {
|
|
4206
|
-
this.logger.warn('DB TOUCH failed, disabling DB sessions', err);
|
|
4207
|
-
SessionModel = null;
|
|
4208
|
-
}
|
|
4209
|
-
}
|
|
4210
|
-
|
|
4211
|
-
if (this._canUseRedis()) {
|
|
4212
|
-
try {
|
|
4213
|
-
await this.redis.pexpire(`sess:${sid}`, expires - Date.now());
|
|
4214
|
-
} catch (err) {
|
|
4215
|
-
this._markRedisFailure(err);
|
|
4216
|
-
}
|
|
4217
|
-
}
|
|
4218
|
-
|
|
4219
|
-
this._safeCb(cb, null);
|
|
4194
|
+
cb();
|
|
4220
4195
|
} catch (err) {
|
|
4221
|
-
|
|
4222
|
-
|
|
4196
|
+
cb(new DBError('Failed to touch session', {
|
|
4197
|
+
sid,
|
|
4198
|
+
operation: 'touch',
|
|
4199
|
+
err
|
|
4200
|
+
}));
|
|
4223
4201
|
}
|
|
4224
4202
|
}
|
|
4225
4203
|
|
|
4226
|
-
/*
|
|
4227
|
-
_startCleanup() {
|
|
4228
|
-
if (!this._hasSessionModel()) return;
|
|
4204
|
+
/* ---------- Cleanup ---------- */
|
|
4229
4205
|
|
|
4206
|
+
_startCleanup() {
|
|
4230
4207
|
this._cleanupTimer = setInterval(async () => {
|
|
4231
4208
|
try {
|
|
4232
|
-
await
|
|
4209
|
+
await Session
|
|
4210
|
+
.query()
|
|
4233
4211
|
.where('expires', '<', Date.now())
|
|
4234
4212
|
.delete();
|
|
4235
4213
|
} catch (err) {
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4214
|
+
console.error(
|
|
4215
|
+
new DBError('Session cleanup failed', {
|
|
4216
|
+
operation: 'cleanup',
|
|
4217
|
+
err
|
|
4218
|
+
})
|
|
4219
|
+
);
|
|
4239
4220
|
}
|
|
4240
4221
|
}, this.cleanupInterval);
|
|
4241
4222
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lamix",
|
|
3
|
-
"version": "4.2.
|
|
3
|
+
"version": "4.2.21",
|
|
4
4
|
"description": "lamix - ORM for Node-express js",
|
|
5
|
-
"main": "./lib",
|
|
5
|
+
"main": "./lib/index.js",
|
|
6
6
|
"type": "commonjs",
|
|
7
7
|
"exports": {
|
|
8
8
|
".": {
|
|
@@ -32,7 +32,6 @@
|
|
|
32
32
|
"bcrypt": "^6.0.0",
|
|
33
33
|
"express-session": "^1.19.0",
|
|
34
34
|
"lru-cache": "^11.2.5",
|
|
35
|
-
"ioredis": "^5.9.2",
|
|
36
35
|
"chalk": "^4.1.2",
|
|
37
36
|
"dotenv": "^17.2.4 "
|
|
38
37
|
},
|