@seip/blue-bird 0.7.6 → 0.8.0

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.
Files changed (41) hide show
  1. package/.env_example +34 -34
  2. package/AGENTS.md +174 -249
  3. package/LICENSE +21 -21
  4. package/README.md +312 -367
  5. package/{index.js → backend/index.js} +22 -30
  6. package/backend/routes/api.js +57 -57
  7. package/core/app.js +338 -402
  8. package/core/auth.js +262 -256
  9. package/core/cache.js +174 -174
  10. package/core/cli/docker.js +488 -457
  11. package/core/cli/init.js +332 -336
  12. package/core/cli/route.js +42 -42
  13. package/core/config.js +52 -52
  14. package/core/database.js +263 -263
  15. package/core/debug.js +248 -248
  16. package/core/logger.js +115 -115
  17. package/core/middleware.js +27 -27
  18. package/core/router.js +144 -144
  19. package/core/swagger.js +40 -40
  20. package/core/upload.js +77 -77
  21. package/core/validate.js +380 -380
  22. package/docker/Dockerfile +16 -16
  23. package/docker/docker-compose.dev.yml +6 -0
  24. package/docker/docker-compose.mysql.yml +92 -92
  25. package/docker/docker-compose.none.yml +68 -68
  26. package/docker/docker-compose.postgres.yml +93 -93
  27. package/docker/nginx.conf +98 -106
  28. package/docker-compose.yml +92 -92
  29. package/frontend/about.html +98 -0
  30. package/frontend/css/app.css +0 -0
  31. package/frontend/favicon.ico +0 -0
  32. package/frontend/index.html +141 -0
  33. package/frontend/js/bundle.js +8 -0
  34. package/package.json +64 -71
  35. package/frontend/astro.config.mjs +0 -35
  36. package/frontend/public/css/app.css +0 -319
  37. package/frontend/public/favicon.ico +0 -0
  38. package/frontend/src/http/api.js +0 -29
  39. package/frontend/src/layouts/Layout.astro +0 -20
  40. package/frontend/src/pages/about.astro +0 -54
  41. package/frontend/src/pages/index.astro +0 -110
package/core/cache.js CHANGED
@@ -1,174 +1,174 @@
1
- import fs from "node:fs";
2
-
3
- const CACHE = {};
4
-
5
- let redisClient = null;
6
- let isRedisConnected = false;
7
- const redisHost = process.env.REDIS_HOST ?? false;
8
- const redisPort = process.env.REDIS_PORT ?? 6379;
9
- const redisPassword = process.env.REDIS_PASSWORD || "";
10
- const redisUrl = redisPassword
11
- ? `redis://:${redisPassword}@${redisHost}:${redisPort}`
12
- : `redis://${redisHost}:${redisPort}`;
13
-
14
- /**
15
- * Initializes the Redis client connection if REDIS_HOST env is set.
16
- * @returns {Promise<void>}
17
- */
18
- async function initRedis() {
19
- if (redisClient) return;
20
- if (!redisHost) return;
21
-
22
- try {
23
- const { createClient } = await import("redis");
24
- let host = redisHost;
25
- if (host === "localhost" && fs.existsSync("/.dockerenv")) {
26
- host = "redis";
27
- }
28
- const url = redisUrl;
29
-
30
- redisClient = createClient({ url });
31
- redisClient.on("error", () => {
32
- isRedisConnected = false;
33
- });
34
- redisClient.on("ready", () => {
35
- isRedisConnected = true;
36
- });
37
- redisClient.on("connect", () => {
38
- isRedisConnected = true;
39
- });
40
- await redisClient.connect();
41
- isRedisConnected = true;
42
- } catch (err) {
43
- redisClient = null;
44
- isRedisConnected = false;
45
- }
46
- }
47
-
48
- initRedis().catch(() => {});
49
-
50
- setInterval(() => {
51
- const now = Date.now();
52
- for (const key in CACHE) {
53
- if (CACHE[key].expiry <= now) {
54
- delete CACHE[key];
55
- }
56
- }
57
- }, 300000).unref();
58
-
59
- /**
60
- * High-performance Caching class supporting both local memory and Redis backends.
61
- */
62
- class Cache {
63
- /**
64
- * Express middleware to cache route JSON and HTML responses.
65
- * @param {number} [seconds=60] - Expiry time in seconds.
66
- * @returns {Function} Express middleware.
67
- */
68
- static middleware(seconds = 60) {
69
- return async (req, res, next) => {
70
- const key = req.originalUrl;
71
-
72
- if (redisHost && !redisClient) {
73
- await initRedis().catch(() => {});
74
- }
75
-
76
- if (isRedisConnected && redisClient) {
77
- try {
78
- const cachedData = await redisClient.get(key);
79
- if (cachedData) {
80
- const cached = JSON.parse(cachedData);
81
- if (cached.type === "json") {
82
- return res.json(cached.data);
83
- } else {
84
- res.type("text/html");
85
- res.set("X-Blue-Bird-Cache", "HIT");
86
- return res.send(cached.data);
87
- }
88
- }
89
- } catch (err) {
90
- isRedisConnected = false;
91
- }
92
- }
93
-
94
- if (!isRedisConnected || !redisClient) {
95
- if (CACHE[key] && CACHE[key].expiry > Date.now()) {
96
- const cached = CACHE[key];
97
- if (cached.type === "json") {
98
- res.set("X-Blue-Bird-Cache", "HIT");
99
- return res.json(cached.data);
100
- } else {
101
- res.type("text/html");
102
- res.set("X-Blue-Bird-Cache", "HIT");
103
- return res.send(cached.data);
104
- }
105
- }
106
- }
107
-
108
- const originalJson = res.json.bind(res);
109
- const originalSend = res.send.bind(res);
110
- let cachedInRequest = false;
111
-
112
- res.json = async (body) => {
113
- if (!cachedInRequest) {
114
- cachedInRequest = true;
115
- const cacheObject = {
116
- type: "json",
117
- data: body,
118
- expiry: Date.now() + seconds * 1000,
119
- };
120
- if (isRedisConnected && redisClient) {
121
- try {
122
- await redisClient.set(key, JSON.stringify(cacheObject), {
123
- EX: seconds,
124
- });
125
- } catch (err) {
126
- CACHE[key] = cacheObject;
127
- }
128
- } else {
129
- CACHE[key] = cacheObject;
130
- }
131
- }
132
- res.set("X-Blue-Bird-Cache", "MISS");
133
- return originalJson(body);
134
- };
135
-
136
- res.send = async (body) => {
137
- if (!cachedInRequest && typeof body === "string") {
138
- cachedInRequest = true;
139
- const cacheObject = {
140
- type: "html",
141
- data: body,
142
- expiry: Date.now() + seconds * 1000,
143
- };
144
- if (isRedisConnected && redisClient) {
145
- try {
146
- await redisClient.set(key, JSON.stringify(cacheObject), {
147
- EX: seconds,
148
- });
149
- } catch (err) {
150
- CACHE[key] = cacheObject;
151
- }
152
- } else {
153
- CACHE[key] = cacheObject;
154
- }
155
- }
156
- res.set("X-Blue-Bird-Cache", "MISS");
157
- return originalSend(body);
158
- };
159
-
160
- res.set("X-Blue-Bird-Cache", "MISS");
161
- next();
162
- };
163
- }
164
- }
165
-
166
- /**
167
- * Returns the active Redis client if connected.
168
- * @returns {Object|null} The Redis client instance or null.
169
- */
170
- export function getRedisClient() {
171
- return isRedisConnected ? redisClient : null;
172
- }
173
-
174
- export default Cache;
1
+ import fs from "node:fs";
2
+
3
+ const CACHE = {};
4
+
5
+ let redisClient = null;
6
+ let isRedisConnected = false;
7
+ const redisHost = process.env.REDIS_HOST ?? false;
8
+ const redisPort = process.env.REDIS_PORT ?? 6379;
9
+ const redisPassword = process.env.REDIS_PASSWORD || "";
10
+ const redisUrl = redisPassword
11
+ ? `redis://:${redisPassword}@${redisHost}:${redisPort}`
12
+ : `redis://${redisHost}:${redisPort}`;
13
+
14
+ /**
15
+ * Initializes the Redis client connection if REDIS_HOST env is set.
16
+ * @returns {Promise<void>}
17
+ */
18
+ async function initRedis() {
19
+ if (redisClient) return;
20
+ if (!redisHost) return;
21
+
22
+ try {
23
+ const { createClient } = await import("redis");
24
+ let host = redisHost;
25
+ if (host === "localhost" && fs.existsSync("/.dockerenv")) {
26
+ host = "redis";
27
+ }
28
+ const url = redisUrl;
29
+
30
+ redisClient = createClient({ url });
31
+ redisClient.on("error", () => {
32
+ isRedisConnected = false;
33
+ });
34
+ redisClient.on("ready", () => {
35
+ isRedisConnected = true;
36
+ });
37
+ redisClient.on("connect", () => {
38
+ isRedisConnected = true;
39
+ });
40
+ await redisClient.connect();
41
+ isRedisConnected = true;
42
+ } catch (err) {
43
+ redisClient = null;
44
+ isRedisConnected = false;
45
+ }
46
+ }
47
+
48
+ initRedis().catch(() => {});
49
+
50
+ setInterval(() => {
51
+ const now = Date.now();
52
+ for (const key in CACHE) {
53
+ if (CACHE[key].expiry <= now) {
54
+ delete CACHE[key];
55
+ }
56
+ }
57
+ }, 300000).unref();
58
+
59
+ /**
60
+ * High-performance Caching class supporting both local memory and Redis backends.
61
+ */
62
+ class Cache {
63
+ /**
64
+ * Express middleware to cache route JSON and HTML responses.
65
+ * @param {number} [seconds=60] - Expiry time in seconds.
66
+ * @returns {Function} Express middleware.
67
+ */
68
+ static middleware(seconds = 60) {
69
+ return async (req, res, next) => {
70
+ const key = req.originalUrl;
71
+
72
+ if (redisHost && !redisClient) {
73
+ await initRedis().catch(() => {});
74
+ }
75
+
76
+ if (isRedisConnected && redisClient) {
77
+ try {
78
+ const cachedData = await redisClient.get(key);
79
+ if (cachedData) {
80
+ const cached = JSON.parse(cachedData);
81
+ if (cached.type === "json") {
82
+ return res.json(cached.data);
83
+ } else {
84
+ res.type("text/html");
85
+ res.set("X-Blue-Bird-Cache", "HIT");
86
+ return res.send(cached.data);
87
+ }
88
+ }
89
+ } catch (err) {
90
+ isRedisConnected = false;
91
+ }
92
+ }
93
+
94
+ if (!isRedisConnected || !redisClient) {
95
+ if (CACHE[key] && CACHE[key].expiry > Date.now()) {
96
+ const cached = CACHE[key];
97
+ if (cached.type === "json") {
98
+ res.set("X-Blue-Bird-Cache", "HIT");
99
+ return res.json(cached.data);
100
+ } else {
101
+ res.type("text/html");
102
+ res.set("X-Blue-Bird-Cache", "HIT");
103
+ return res.send(cached.data);
104
+ }
105
+ }
106
+ }
107
+
108
+ const originalJson = res.json.bind(res);
109
+ const originalSend = res.send.bind(res);
110
+ let cachedInRequest = false;
111
+
112
+ res.json = async (body) => {
113
+ if (!cachedInRequest) {
114
+ cachedInRequest = true;
115
+ const cacheObject = {
116
+ type: "json",
117
+ data: body,
118
+ expiry: Date.now() + seconds * 1000,
119
+ };
120
+ if (isRedisConnected && redisClient) {
121
+ try {
122
+ await redisClient.set(key, JSON.stringify(cacheObject), {
123
+ EX: seconds,
124
+ });
125
+ } catch (err) {
126
+ CACHE[key] = cacheObject;
127
+ }
128
+ } else {
129
+ CACHE[key] = cacheObject;
130
+ }
131
+ }
132
+ res.set("X-Blue-Bird-Cache", "MISS");
133
+ return originalJson(body);
134
+ };
135
+
136
+ res.send = async (body) => {
137
+ if (!cachedInRequest && typeof body === "string") {
138
+ cachedInRequest = true;
139
+ const cacheObject = {
140
+ type: "html",
141
+ data: body,
142
+ expiry: Date.now() + seconds * 1000,
143
+ };
144
+ if (isRedisConnected && redisClient) {
145
+ try {
146
+ await redisClient.set(key, JSON.stringify(cacheObject), {
147
+ EX: seconds,
148
+ });
149
+ } catch (err) {
150
+ CACHE[key] = cacheObject;
151
+ }
152
+ } else {
153
+ CACHE[key] = cacheObject;
154
+ }
155
+ }
156
+ res.set("X-Blue-Bird-Cache", "MISS");
157
+ return originalSend(body);
158
+ };
159
+
160
+ res.set("X-Blue-Bird-Cache", "MISS");
161
+ next();
162
+ };
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Returns the active Redis client if connected.
168
+ * @returns {Object|null} The Redis client instance or null.
169
+ */
170
+ export function getRedisClient() {
171
+ return isRedisConnected ? redisClient : null;
172
+ }
173
+
174
+ export default Cache;