@seip/blue-bird 1.0.2 → 1.1.1
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/.env_example +26 -12
- package/AGENTS.md +98 -26
- package/README.md +80 -23
- package/core/app.js +42 -0
- package/core/cache.js +62 -30
- package/core/cli/docker.js +236 -59
- package/core/cli/init.js +135 -9
- package/core/database.js +205 -20
- package/core/hash.js +201 -0
- package/core/index.d.ts +194 -137
- package/core/upload.js +83 -57
- package/core/ws.js +227 -210
- package/docker/docker-compose.sqlite.yml +69 -0
- package/frontend/js/utils.js +557 -557
- package/package.json +68 -66
package/core/cache.js
CHANGED
|
@@ -4,6 +4,17 @@ const CACHE = {};
|
|
|
4
4
|
|
|
5
5
|
let redisClient = null;
|
|
6
6
|
let isRedisConnected = false;
|
|
7
|
+
|
|
8
|
+
const rawCacheMode = (process.env.CACHE_MODE || "").toLowerCase().trim();
|
|
9
|
+
const CACHE_MODE =
|
|
10
|
+
rawCacheMode || (process.env.REDIS_HOST ? "redis" : "memory");
|
|
11
|
+
const isRedisMode = CACHE_MODE === "redis";
|
|
12
|
+
const isMemoryMode = CACHE_MODE === "memory" || CACHE_MODE === "inmemory";
|
|
13
|
+
const isNoneMode =
|
|
14
|
+
CACHE_MODE === "none" ||
|
|
15
|
+
CACHE_MODE === "disabled" ||
|
|
16
|
+
CACHE_MODE === "false";
|
|
17
|
+
|
|
7
18
|
const redisHost = process.env.REDIS_HOST ?? false;
|
|
8
19
|
const redisPort = process.env.REDIS_PORT ?? 6379;
|
|
9
20
|
const redisPassword = process.env.REDIS_PASSWORD || "";
|
|
@@ -12,12 +23,11 @@ const redisUrl = redisPassword
|
|
|
12
23
|
: `redis://${redisHost}:${redisPort}`;
|
|
13
24
|
|
|
14
25
|
/**
|
|
15
|
-
* Initializes the Redis client connection if REDIS_HOST
|
|
26
|
+
* Initializes the Redis client connection if CACHE_MODE is 'redis' and REDIS_HOST is configured.
|
|
16
27
|
* @returns {Promise<void>}
|
|
17
28
|
*/
|
|
18
29
|
async function initRedis() {
|
|
19
|
-
if (redisClient) return;
|
|
20
|
-
if (!redisHost) return;
|
|
30
|
+
if (!isRedisMode || !redisHost || redisClient) return;
|
|
21
31
|
|
|
22
32
|
try {
|
|
23
33
|
const { createClient } = await import("redis");
|
|
@@ -45,8 +55,11 @@ async function initRedis() {
|
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
|
|
58
|
+
if (isRedisMode && redisHost) {
|
|
59
|
+
initRedis().catch(() => {});
|
|
60
|
+
}
|
|
49
61
|
|
|
62
|
+
// Background cleanup timer for in-memory cache
|
|
50
63
|
setInterval(() => {
|
|
51
64
|
const now = Date.now();
|
|
52
65
|
for (const key in CACHE) {
|
|
@@ -67,13 +80,18 @@ class Cache {
|
|
|
67
80
|
*/
|
|
68
81
|
static middleware(seconds = 60) {
|
|
69
82
|
return async (req, res, next) => {
|
|
83
|
+
if (isNoneMode) {
|
|
84
|
+
return next();
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
const key = req.originalUrl;
|
|
71
88
|
|
|
72
|
-
|
|
73
|
-
|
|
89
|
+
// 1. Redis Cache Lookup (if enabled)
|
|
90
|
+
if (isRedisMode && redisHost && !redisClient) {
|
|
91
|
+
await initRedis().catch(() => {});
|
|
74
92
|
}
|
|
75
93
|
|
|
76
|
-
if (isRedisConnected && redisClient) {
|
|
94
|
+
if (isRedisMode && isRedisConnected && redisClient) {
|
|
77
95
|
try {
|
|
78
96
|
const cachedData = await redisClient.get(key);
|
|
79
97
|
if (cachedData) {
|
|
@@ -87,12 +105,13 @@ class Cache {
|
|
|
87
105
|
return res.send(cached.data);
|
|
88
106
|
}
|
|
89
107
|
}
|
|
90
|
-
} catch
|
|
108
|
+
} catch {
|
|
91
109
|
isRedisConnected = false;
|
|
92
110
|
}
|
|
93
111
|
}
|
|
94
112
|
|
|
95
|
-
|
|
113
|
+
// 2. In-Memory Cache Lookup (memory mode or fallback)
|
|
114
|
+
if (!isRedisMode || !isRedisConnected || !redisClient) {
|
|
96
115
|
if (CACHE[key] && CACHE[key].expiry > Date.now()) {
|
|
97
116
|
const cached = CACHE[key];
|
|
98
117
|
if (cached.type === "json") {
|
|
@@ -118,12 +137,12 @@ class Cache {
|
|
|
118
137
|
data: body,
|
|
119
138
|
expiry: Date.now() + seconds * 1000,
|
|
120
139
|
};
|
|
121
|
-
if (isRedisConnected && redisClient) {
|
|
140
|
+
if (isRedisMode && isRedisConnected && redisClient) {
|
|
122
141
|
try {
|
|
123
142
|
await redisClient.set(key, JSON.stringify(cacheObject), {
|
|
124
143
|
EX: seconds,
|
|
125
144
|
});
|
|
126
|
-
} catch
|
|
145
|
+
} catch {
|
|
127
146
|
CACHE[key] = cacheObject;
|
|
128
147
|
}
|
|
129
148
|
} else {
|
|
@@ -142,12 +161,12 @@ class Cache {
|
|
|
142
161
|
data: body,
|
|
143
162
|
expiry: Date.now() + seconds * 1000,
|
|
144
163
|
};
|
|
145
|
-
if (isRedisConnected && redisClient) {
|
|
164
|
+
if (isRedisMode && isRedisConnected && redisClient) {
|
|
146
165
|
try {
|
|
147
166
|
await redisClient.set(key, JSON.stringify(cacheObject), {
|
|
148
167
|
EX: seconds,
|
|
149
168
|
});
|
|
150
|
-
} catch
|
|
169
|
+
} catch {
|
|
151
170
|
CACHE[key] = cacheObject;
|
|
152
171
|
}
|
|
153
172
|
} else {
|
|
@@ -169,26 +188,29 @@ class Cache {
|
|
|
169
188
|
* @returns {Promise<any|null>} Cached payload or null.
|
|
170
189
|
*/
|
|
171
190
|
static async get(key) {
|
|
191
|
+
if (isNoneMode) return null;
|
|
172
192
|
key = key.trim();
|
|
173
193
|
if (!key) return null;
|
|
174
194
|
|
|
175
|
-
if (redisHost && !redisClient) {
|
|
176
|
-
await initRedis().catch(() => {
|
|
195
|
+
if (isRedisMode && redisHost && !redisClient) {
|
|
196
|
+
await initRedis().catch(() => {});
|
|
177
197
|
}
|
|
178
198
|
|
|
179
|
-
if (isRedisConnected && redisClient) {
|
|
199
|
+
if (isRedisMode && isRedisConnected && redisClient) {
|
|
180
200
|
try {
|
|
181
201
|
const cachedData = await redisClient.get(key);
|
|
182
202
|
if (cachedData) {
|
|
183
203
|
try {
|
|
184
204
|
const cached = JSON.parse(cachedData);
|
|
185
|
-
return cached && typeof cached === "object" && "data" in cached
|
|
205
|
+
return cached && typeof cached === "object" && "data" in cached
|
|
206
|
+
? cached.data
|
|
207
|
+
: cached;
|
|
186
208
|
} catch {
|
|
187
209
|
return cachedData;
|
|
188
210
|
}
|
|
189
211
|
}
|
|
190
212
|
return null;
|
|
191
|
-
} catch
|
|
213
|
+
} catch {
|
|
192
214
|
isRedisConnected = false;
|
|
193
215
|
}
|
|
194
216
|
}
|
|
@@ -212,11 +234,12 @@ class Cache {
|
|
|
212
234
|
* @returns {Promise<boolean>} True if set successfully.
|
|
213
235
|
*/
|
|
214
236
|
static async set(key, value, seconds = 60) {
|
|
237
|
+
if (isNoneMode) return true;
|
|
215
238
|
key = key.trim();
|
|
216
239
|
if (!key) return false;
|
|
217
240
|
|
|
218
|
-
if (redisHost && !redisClient) {
|
|
219
|
-
await initRedis().catch(() => {
|
|
241
|
+
if (isRedisMode && redisHost && !redisClient) {
|
|
242
|
+
await initRedis().catch(() => {});
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
const cacheObject = {
|
|
@@ -225,12 +248,12 @@ class Cache {
|
|
|
225
248
|
expiry: Date.now() + seconds * 1000,
|
|
226
249
|
};
|
|
227
250
|
|
|
228
|
-
if (isRedisConnected && redisClient) {
|
|
251
|
+
if (isRedisMode && isRedisConnected && redisClient) {
|
|
229
252
|
try {
|
|
230
253
|
await redisClient.set(key, JSON.stringify(cacheObject), {
|
|
231
254
|
EX: seconds,
|
|
232
255
|
});
|
|
233
|
-
} catch
|
|
256
|
+
} catch {
|
|
234
257
|
CACHE[key] = cacheObject;
|
|
235
258
|
}
|
|
236
259
|
} else {
|
|
@@ -246,19 +269,19 @@ class Cache {
|
|
|
246
269
|
* @returns {Promise<boolean>} True if deleted.
|
|
247
270
|
*/
|
|
248
271
|
static async delete(keys) {
|
|
249
|
-
if (!keys) return false;
|
|
272
|
+
if (isNoneMode || !keys) return false;
|
|
250
273
|
const keyList = Array.isArray(keys) ? keys : [keys];
|
|
251
274
|
|
|
252
|
-
if (redisHost && !redisClient) {
|
|
253
|
-
await initRedis().catch(() => {
|
|
275
|
+
if (isRedisMode && redisHost && !redisClient) {
|
|
276
|
+
await initRedis().catch(() => {});
|
|
254
277
|
}
|
|
255
278
|
|
|
256
279
|
for (const key of keyList) {
|
|
257
280
|
delete CACHE[key];
|
|
258
|
-
if (isRedisConnected && redisClient) {
|
|
281
|
+
if (isRedisMode && isRedisConnected && redisClient) {
|
|
259
282
|
try {
|
|
260
283
|
await redisClient.del(key);
|
|
261
|
-
} catch
|
|
284
|
+
} catch {
|
|
262
285
|
isRedisConnected = false;
|
|
263
286
|
}
|
|
264
287
|
}
|
|
@@ -284,15 +307,23 @@ class Cache {
|
|
|
284
307
|
for (const key in CACHE) {
|
|
285
308
|
delete CACHE[key];
|
|
286
309
|
}
|
|
287
|
-
if (isRedisConnected && redisClient) {
|
|
310
|
+
if (isRedisMode && isRedisConnected && redisClient) {
|
|
288
311
|
try {
|
|
289
312
|
await redisClient.flushDb();
|
|
290
|
-
} catch
|
|
313
|
+
} catch {
|
|
291
314
|
isRedisConnected = false;
|
|
292
315
|
}
|
|
293
316
|
}
|
|
294
317
|
return true;
|
|
295
318
|
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Returns current active cache mode ('redis', 'memory', or 'none').
|
|
322
|
+
* @returns {string}
|
|
323
|
+
*/
|
|
324
|
+
static getMode() {
|
|
325
|
+
return CACHE_MODE;
|
|
326
|
+
}
|
|
296
327
|
}
|
|
297
328
|
|
|
298
329
|
/**
|
|
@@ -300,7 +331,8 @@ class Cache {
|
|
|
300
331
|
* @returns {Object|null} The Redis client instance or null.
|
|
301
332
|
*/
|
|
302
333
|
export function getRedisClient() {
|
|
303
|
-
return isRedisConnected ? redisClient : null;
|
|
334
|
+
return isRedisMode && isRedisConnected ? redisClient : null;
|
|
304
335
|
}
|
|
305
336
|
|
|
306
337
|
export default Cache;
|
|
338
|
+
|