@seip/blue-bird 1.1.0 → 1.1.2

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/core/upload.js CHANGED
@@ -1,4 +1,3 @@
1
- import multer from "multer";
2
1
  import path from "node:path";
3
2
  import fs from "node:fs";
4
3
  import Config from "./config.js";
@@ -6,72 +5,99 @@ import Config from "./config.js";
6
5
  const __dirname = Config.dirname();
7
6
  const props = Config.props();
8
7
 
8
+ let multer = null;
9
+ try {
10
+ multer = (await import("multer")).default || (await import("multer"));
11
+ } catch {
12
+ // multer not installed
13
+ }
14
+
9
15
  /**
10
16
  * Upload helper to manage file uploads using multer.
11
17
  */
12
18
  class Upload {
13
- /**
14
- * Configures storage for uploaded files.
15
- * @param {string} folder - The destination folder within the static path.
16
- * @returns {import('multer').StorageEngine}
17
- * @example
18
- * const storage = Upload.storage("uploads");
19
- */
20
- static storage(folder = "uploads") {
21
- const dest = path.join(__dirname, props.static.path, folder);
19
+ /**
20
+ * Asserts that the multer package is available.
21
+ * @private
22
+ */
23
+ static _ensureMulter() {
24
+ if (!multer) {
25
+ throw new Error(
26
+ "[UPLOAD ERROR] 'multer' package is not installed. Install it with: npm install multer or npx blue-bird add upload",
27
+ );
28
+ }
29
+ }
22
30
 
23
- if (!fs.existsSync(dest)) {
24
- fs.mkdirSync(dest, { recursive: true });
25
- }
31
+ /**
32
+ * Configures storage for uploaded files.
33
+ * @param {string} folder - The destination folder within the static path.
34
+ * @returns {import('multer').StorageEngine}
35
+ * @example
36
+ * const storage = Upload.storage("uploads");
37
+ */
38
+ static storage(folder = "uploads") {
39
+ this._ensureMulter();
40
+ const dest = path.join(__dirname, props.static.path, folder);
26
41
 
27
- return multer.diskStorage({
28
- destination: (req, file, cb) => {
29
- cb(null, dest);
30
- },
31
- filename: (req, file, cb) => {
32
- const uniqueSuffix = Date.now() + "-" + Math.round(Math.random() * 1e9);
33
- cb(null, uniqueSuffix + path.extname(file.originalname));
34
- }
35
- });
42
+ if (!fs.existsSync(dest)) {
43
+ fs.mkdirSync(dest, { recursive: true });
36
44
  }
37
45
 
38
- /**
39
- * Returns a multer instance for single or multiple file uploads.
40
- * @param {Object} options - Multer options.
41
- * @param {string} [options.folder='uploads'] - Destination folder.
42
- * @param {number} [options.fileSize=5000000] - Max file size in bytes (default 5MB).
43
- * @param {Array<string>} [options.allowedTypes=[]] - Allowed mime types (e.g. ['image/png', 'image/jpeg']).
44
- * @returns {import('multer').Multer}
45
- * @example
46
- * const upload = Upload.disk({ folder: "uploads", fileSize: 5000000, allowedTypes: ["image/png", "image/jpeg"] });
47
- */
48
- static disk(options = {}) {
49
- const { folder = "uploads", fileSize = 5000000, allowedTypes = [] } = options;
46
+ return multer.diskStorage({
47
+ destination: (req, file, cb) => {
48
+ cb(null, dest);
49
+ },
50
+ filename: (req, file, cb) => {
51
+ const uniqueSuffix =
52
+ Date.now() + "-" + Math.round(Math.random() * 1e9);
53
+ cb(null, uniqueSuffix + path.extname(file.originalname));
54
+ },
55
+ });
56
+ }
50
57
 
51
- return multer({
52
- storage: this.storage(folder),
53
- limits: { fileSize },
54
- fileFilter: (req, file, cb) => {
55
- if (allowedTypes.length > 0 && !allowedTypes.includes(file.mimetype)) {
56
- return cb(new Error("File type not allowed"), false);
57
- }
58
- cb(null, true);
59
- }
60
- });
61
- }
58
+ /**
59
+ * Returns a multer instance for single or multiple file uploads.
60
+ * @param {Object} options - Multer options.
61
+ * @param {string} [options.folder='uploads'] - Destination folder.
62
+ * @param {number} [options.fileSize=5000000] - Max file size in bytes (default 5MB).
63
+ * @param {Array<string>} [options.allowedTypes=[]] - Allowed mime types (e.g. ['image/png', 'image/jpeg']).
64
+ * @returns {import('multer').Multer}
65
+ * @example
66
+ * const upload = Upload.disk({ folder: "uploads", fileSize: 5000000, allowedTypes: ["image/png", "image/jpeg"] });
67
+ */
68
+ static disk(options = {}) {
69
+ this._ensureMulter();
70
+ const {
71
+ folder = "uploads",
72
+ fileSize = 5000000,
73
+ allowedTypes = [],
74
+ } = options;
62
75
 
63
- /**
64
- * Helper to get the public URL of an uploaded file.
65
- * @param {string} filename - The name of the file.
66
- * @param {string} [folder='uploads'] - The folder where the file is stored.
67
- * @returns {string} The full public URL.
68
- * @example
69
- * const url = Upload.url("file.jpg", "uploads");
70
- */
71
- static url(filename, folder = "uploads") {
72
- const appUrl = props.appUrl ?? `${props.host}:${props.port}`;
73
- return `${appUrl}/${folder}/${filename}`;
74
- }
76
+ return multer({
77
+ storage: this.storage(folder),
78
+ limits: { fileSize },
79
+ fileFilter: (req, file, cb) => {
80
+ if (allowedTypes.length > 0 && !allowedTypes.includes(file.mimetype)) {
81
+ return cb(new Error("File type not allowed"), false);
82
+ }
83
+ cb(null, true);
84
+ },
85
+ });
86
+ }
87
+
88
+ /**
89
+ * Helper to get the public URL of an uploaded file.
90
+ * @param {string} filename - The name of the file.
91
+ * @param {string} [folder='uploads'] - The folder where the file is stored.
92
+ * @returns {string} The full public URL.
93
+ * @example
94
+ * const url = Upload.url("file.jpg", "uploads");
95
+ */
96
+ static url(filename, folder = "uploads") {
97
+ const appUrl = props.appUrl ?? `${props.host}:${props.port}`;
98
+ return `${appUrl}/${folder}/${filename}`;
99
+ }
75
100
  }
76
101
 
77
102
  export default Upload;
103
+
package/core/ws.js CHANGED
@@ -1,4 +1,14 @@
1
- import { WebSocketServer, WebSocket } from "ws";
1
+ let WebSocketServer = null;
2
+ let WebSocket = null;
3
+
4
+ try {
5
+ const wsModule = await import("ws");
6
+ WebSocketServer = wsModule.WebSocketServer || wsModule.default?.WebSocketServer;
7
+ WebSocket = wsModule.WebSocket || wsModule.default?.WebSocket;
8
+ } catch {
9
+ // ws not installed
10
+ }
11
+
2
12
  import { getRedisClient } from "./cache.js";
3
13
  import Auth from "./auth.js";
4
14
 
@@ -15,6 +25,12 @@ class WebSocketManager {
15
25
  * @param {boolean} [options.auth=false] - Require valid Auth JWT token on connection.
16
26
  */
17
27
  constructor(server, options = {}) {
28
+ if (!WebSocketServer || !WebSocket) {
29
+ throw new Error(
30
+ "[WS ERROR] 'ws' package is not installed. Install it with: npm install ws or npx blue-bird add ws",
31
+ );
32
+ }
33
+
18
34
  this.server = server;
19
35
  this.path = options.path || "/ws";
20
36
  this.requireAuth = options.auth ?? false;
@@ -31,6 +47,7 @@ class WebSocketManager {
31
47
  this._setupRedisPubSub();
32
48
  }
33
49
 
50
+
34
51
  /**
35
52
  * Attaches the HTTP Upgrade listener to the Express HTTP server instance.
36
53
  * @private
package/docker/Dockerfile CHANGED
@@ -4,10 +4,14 @@ ENV NODE_ENV=production
4
4
 
5
5
  WORKDIR /app
6
6
 
7
+ # Install native build tools for C++ addons (better-sqlite3, bcrypt)
8
+ RUN apk add --no-cache python3 make g++
9
+
7
10
  COPY package*.json ./
8
11
 
9
12
  RUN npm ci --omit=dev && npm install -g pm2
10
13
 
14
+
11
15
  COPY . .
12
16
 
13
17
  EXPOSE 3000
@@ -0,0 +1,69 @@
1
+ services:
2
+ app:
3
+ build:
4
+ context: .
5
+ dockerfile: docker/Dockerfile
6
+ container_name: ${TITLE:-bluebird}-app
7
+ restart: unless-stopped
8
+ expose:
9
+ - "3000"
10
+ volumes:
11
+ - .:/app
12
+ - /app/node_modules
13
+ - ./database:/app/database
14
+ env_file:
15
+ - .env
16
+ environment:
17
+ - NODE_ENV=production
18
+ - DEBUG=false
19
+ - DB_TYPE=sqlite
20
+ - DB_FILE=${DB_FILE:-database/blue_bird.db}
21
+ - DATABASE_URL=${DATABASE_URL:-sqlite:database/blue_bird.db}
22
+ - REDIS_HOST=redis
23
+ - REDIS_PORT=6379
24
+ - PORT=3000
25
+ depends_on:
26
+ redis:
27
+ condition: service_healthy
28
+ networks:
29
+ - bluebird_net
30
+ profiles:
31
+ - prod
32
+
33
+ nginx:
34
+ image: nginx:1.27-alpine
35
+ container_name: ${TITLE:-bluebird}-nginx
36
+ restart: unless-stopped
37
+ ports:
38
+ - "${PORT:-3000}:80"
39
+ volumes:
40
+ - .:/app:ro
41
+ - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
42
+ networks:
43
+ - bluebird_net
44
+ profiles:
45
+ - prod
46
+
47
+ redis:
48
+ image: redis:7-alpine
49
+ container_name: ${TITLE:-bluebird}-redis
50
+ restart: unless-stopped
51
+ ports:
52
+ - "${REDIS_PORT:-6379}:6379"
53
+ volumes:
54
+ - redis_data:/data
55
+ networks:
56
+ - bluebird_net
57
+ healthcheck:
58
+ test: ["CMD", "redis-cli", "ping"]
59
+ interval: 5s
60
+ timeout: 3s
61
+ retries: 5
62
+
63
+ volumes:
64
+ redis_data:
65
+
66
+ networks:
67
+ bluebird_net:
68
+ name: ${TITLE:-bluebird}_network
69
+ driver: bridge
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seip/blue-bird",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Express opinionated API framework with built-in JWT auth, validation, and caching",
5
5
  "type": "module",
6
6
  "types": "core/index.d.ts",
@@ -34,7 +34,8 @@
34
34
  "init": "node core/cli/init.js",
35
35
  "route": "node core/cli/route.js",
36
36
  "swagger-install": "node core/cli/swagger.js",
37
- "docker": "node core/cli/init.js docker"
37
+ "docker": "node core/cli/init.js docker",
38
+ "test": "node test/test_suite.js"
38
39
  },
39
40
  "publishConfig": {
40
41
  "access": "public"
@@ -49,6 +50,7 @@
49
50
  ".env_example"
50
51
  ],
51
52
  "dependencies": {
53
+ "better-sqlite3": "^13.0.3",
52
54
  "chalk": "^5.6.2",
53
55
  "compression": "^1.8.1",
54
56
  "cookie-parser": "^1.4.7",