@seip/blue-bird 1.1.2 → 1.1.4
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/.vscode/extensions.json +6 -0
- package/.vscode/settings.json +10 -0
- package/AGENTS.md +110 -3
- package/README.md +184 -1
- package/core/cli/docker.js +10 -1
- package/core/cli/doctor.js +294 -0
- package/core/cli/init.js +34 -5
- package/core/cli/migrate.js +342 -0
- package/core/cli/nginx.js +138 -0
- package/core/cli/route.js +144 -43
- package/core/cli/swagger.js +2 -0
- package/core/index.d.ts +7 -0
- package/core/queue.js +121 -0
- package/core/validate.js +3 -2
- package/docker/nginx.conf +23 -0
- package/frontend/about.html +156 -107
- package/frontend/css/bluebird.css +9852 -0
- package/frontend/index.html +114 -114
- package/frontend/js/bluebird.d.ts +423 -0
- package/frontend/js/bluebird.js +1302 -0
- package/jsconfig.json +15 -0
- package/package.json +7 -2
- package/frontend/js/tailwind.js +0 -8
- package/frontend/js/utils.js +0 -558
package/core/cli/route.js
CHANGED
|
@@ -1,43 +1,144 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class RouteCLI {
|
|
8
|
-
/**
|
|
9
|
-
*
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fs from "node:fs";
|
|
5
|
+
import chalk from "chalk";
|
|
6
|
+
|
|
7
|
+
class RouteCLI {
|
|
8
|
+
/**
|
|
9
|
+
* Creates a new RESTful route file with Validation, Cache, and optional Auth.
|
|
10
|
+
*/
|
|
11
|
+
create() {
|
|
12
|
+
const rawArgs = process.argv.slice(2);
|
|
13
|
+
// Filter out command name if invoked as 'route' or 'make:route'
|
|
14
|
+
const args = rawArgs.filter(
|
|
15
|
+
(a) => a !== "route" && a !== "make:route" && !a.endsWith("route.js")
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
let routeName = args.find((a) => !a.startsWith("-"));
|
|
19
|
+
const withAuth = args.some((a) => a === "--auth" || a === "-a");
|
|
20
|
+
|
|
21
|
+
if (!routeName) {
|
|
22
|
+
console.log(chalk.red("[ERROR] Missing route name."));
|
|
23
|
+
console.log("");
|
|
24
|
+
console.log("Usage:");
|
|
25
|
+
console.log(" npx blue-bird make:route <name> [--auth]");
|
|
26
|
+
console.log("");
|
|
27
|
+
console.log("Examples:");
|
|
28
|
+
console.log(" npx blue-bird make:route products");
|
|
29
|
+
console.log(" npx blue-bird make:route articles --auth");
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Normalize casing
|
|
34
|
+
routeName = routeName.toLowerCase().replace(/[^a-z0-9_-]/g, "");
|
|
35
|
+
const singularName = routeName.endsWith("s") ? routeName.slice(0, -1) : routeName;
|
|
36
|
+
const pascalName = routeName.charAt(0).toUpperCase() + routeName.slice(1);
|
|
37
|
+
const routerVarName = `router${pascalName}`;
|
|
38
|
+
const basePath = `/${routeName}`;
|
|
39
|
+
|
|
40
|
+
const routesFolder = path.resolve(process.cwd(), "backend/routes");
|
|
41
|
+
if (!fs.existsSync(routesFolder)) {
|
|
42
|
+
fs.mkdirSync(routesFolder, { recursive: true });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const filePath = path.join(routesFolder, `${routeName}.js`);
|
|
46
|
+
if (fs.existsSync(filePath)) {
|
|
47
|
+
console.log(chalk.yellow(`[WARN] Route file '${routeName}.js' already exists at backend/routes/${routeName}.js.`));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const authImport = withAuth
|
|
52
|
+
? `import Auth from "@seip/blue-bird/core/auth.js";\n`
|
|
53
|
+
: "";
|
|
54
|
+
|
|
55
|
+
const authProtect = withAuth ? `Auth.protect(), ` : "";
|
|
56
|
+
|
|
57
|
+
const content = `import Router from "@seip/blue-bird/core/router.js";
|
|
58
|
+
import Validator from "@seip/blue-bird/core/validate.js";
|
|
59
|
+
import Cache from "@seip/blue-bird/core/cache.js";
|
|
60
|
+
${authImport}
|
|
61
|
+
const ${routerVarName} = new Router("${basePath}");
|
|
62
|
+
|
|
63
|
+
// Validation schema for incoming requests
|
|
64
|
+
const ${singularName}Schema = {
|
|
65
|
+
name: { required: true, min: 2, max: 255 },
|
|
66
|
+
description: { required: false },
|
|
67
|
+
price: { required: false }
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const validate${pascalName} = new Validator(${singularName}Schema, "en");
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* GET ${basePath}
|
|
74
|
+
* List all items with in-memory / Redis route caching (60 seconds)
|
|
75
|
+
*/
|
|
76
|
+
${routerVarName}.get("/", Cache.middleware(60), (req, res) => {
|
|
77
|
+
res.ok({ ${routeName}: [] }, "${pascalName} list retrieved successfully");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* GET ${basePath}/:id
|
|
82
|
+
* Retrieve a single item by ID
|
|
83
|
+
*/
|
|
84
|
+
${routerVarName}.get("/:id", Cache.middleware(60), (req, res) => {
|
|
85
|
+
const { id } = req.params;
|
|
86
|
+
res.ok({ ${singularName}: { id } }, "${pascalName} retrieved successfully");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* POST ${basePath}
|
|
91
|
+
* Create a new item (with validation and automatic cache invalidation)
|
|
92
|
+
*/
|
|
93
|
+
${routerVarName}.post("/", ${authProtect}validate${pascalName}.middleware(), async (req, res) => {
|
|
94
|
+
const data = req.body;
|
|
95
|
+
|
|
96
|
+
// Invalidate cached route list
|
|
97
|
+
await Cache.delete("${basePath}");
|
|
98
|
+
|
|
99
|
+
res.created({ ${singularName}: data }, "${pascalName} created successfully");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* PUT ${basePath}/:id
|
|
104
|
+
* Update an existing item
|
|
105
|
+
*/
|
|
106
|
+
${routerVarName}.put("/:id", ${authProtect}validate${pascalName}.middleware(), async (req, res) => {
|
|
107
|
+
const { id } = req.params;
|
|
108
|
+
const data = req.body;
|
|
109
|
+
|
|
110
|
+
// Invalidate cached item and list
|
|
111
|
+
await Cache.delete("${basePath}");
|
|
112
|
+
await Cache.delete(\`${basePath}/\${id}\`);
|
|
113
|
+
|
|
114
|
+
res.ok({ ${singularName}: { id, ...data } }, "${pascalName} updated successfully");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* DELETE ${basePath}/:id
|
|
119
|
+
* Delete an existing item
|
|
120
|
+
*/
|
|
121
|
+
${routerVarName}.delete("/:id", ${authProtect}async (req, res) => {
|
|
122
|
+
const { id } = req.params;
|
|
123
|
+
|
|
124
|
+
// Invalidate cache
|
|
125
|
+
await Cache.delete("${basePath}");
|
|
126
|
+
await Cache.delete(\`${basePath}/\${id}\`);
|
|
127
|
+
|
|
128
|
+
res.ok({ id }, "${pascalName} deleted successfully");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
export default ${routerVarName};
|
|
132
|
+
`;
|
|
133
|
+
|
|
134
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
135
|
+
console.log(chalk.green(`[OK] Route '${routeName}' created successfully at backend/routes/${routeName}.js`));
|
|
136
|
+
console.log("");
|
|
137
|
+
console.log(chalk.cyan("To register this route, import it in backend/index.js:"));
|
|
138
|
+
console.log(chalk.gray(` import ${routerVarName} from "./routes/${routeName}.js";`));
|
|
139
|
+
console.log(chalk.gray(` // Pass ${routerVarName} into App({ routes: [...] })`));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const routeCLI = new RouteCLI();
|
|
144
|
+
routeCLI.create();
|
package/core/cli/swagger.js
CHANGED
package/core/index.d.ts
CHANGED
|
@@ -190,5 +190,12 @@ export class Database {
|
|
|
190
190
|
close(): Promise<void>;
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
+
export class Queue {
|
|
194
|
+
static process(jobName: string, handler: (payload: any) => Promise<any> | any): void;
|
|
195
|
+
static dispatch(jobName: string, payload?: any, options?: { delayMs?: number }): Promise<boolean>;
|
|
196
|
+
static loadJobs(jobsDir?: string): Promise<void>;
|
|
197
|
+
}
|
|
198
|
+
|
|
193
199
|
export default App;
|
|
194
200
|
|
|
201
|
+
|
package/core/queue.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { getRedisClient } from "./cache.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lightweight background queue worker module with Redis and in-memory fallback.
|
|
7
|
+
*/
|
|
8
|
+
class QueueManager {
|
|
9
|
+
constructor() {
|
|
10
|
+
this.handlers = new Map();
|
|
11
|
+
this.memoryQueue = [];
|
|
12
|
+
this.isProcessing = false;
|
|
13
|
+
this.redisPrefix = "bluebird:queue:";
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Registers a job handler function.
|
|
18
|
+
* @param {string} jobName - Name of the job.
|
|
19
|
+
* @param {Function} handler - Async function(payload).
|
|
20
|
+
*/
|
|
21
|
+
process(jobName, handler) {
|
|
22
|
+
if (typeof handler !== "function") {
|
|
23
|
+
throw new Error(`Handler for job '${jobName}' must be a function.`);
|
|
24
|
+
}
|
|
25
|
+
this.handlers.set(jobName, handler);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Dispatches a new job to the queue.
|
|
30
|
+
* @param {string} jobName - Name of the job.
|
|
31
|
+
* @param {any} payload - Data payload to pass to the handler.
|
|
32
|
+
* @param {object} [options] - Options (e.g. delayMs).
|
|
33
|
+
* @returns {Promise<boolean>}
|
|
34
|
+
*/
|
|
35
|
+
async dispatch(jobName, payload = {}, options = {}) {
|
|
36
|
+
const jobItem = {
|
|
37
|
+
id: `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`,
|
|
38
|
+
name: jobName,
|
|
39
|
+
payload,
|
|
40
|
+
createdAt: new Date().toISOString(),
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const redis = getRedisClient();
|
|
44
|
+
if (redis && redis.isReady) {
|
|
45
|
+
try {
|
|
46
|
+
await redis.lPush(`${this.redisPrefix}jobs`, JSON.stringify(jobItem));
|
|
47
|
+
return true;
|
|
48
|
+
} catch (err) {
|
|
49
|
+
console.error("[QUEUE ERROR] Failed to dispatch job to Redis:", err.message);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// In-memory fallback
|
|
54
|
+
if (options.delayMs && options.delayMs > 0) {
|
|
55
|
+
setTimeout(() => {
|
|
56
|
+
this.memoryQueue.push(jobItem);
|
|
57
|
+
this.runMemoryWorker();
|
|
58
|
+
}, options.delayMs);
|
|
59
|
+
} else {
|
|
60
|
+
this.memoryQueue.push(jobItem);
|
|
61
|
+
setImmediate(() => this.runMemoryWorker());
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Executes in-memory queue jobs sequentially.
|
|
69
|
+
* @private
|
|
70
|
+
*/
|
|
71
|
+
async runMemoryWorker() {
|
|
72
|
+
if (this.isProcessing || this.memoryQueue.length === 0) return;
|
|
73
|
+
this.isProcessing = true;
|
|
74
|
+
|
|
75
|
+
while (this.memoryQueue.length > 0) {
|
|
76
|
+
const job = this.memoryQueue.shift();
|
|
77
|
+
if (!job) continue;
|
|
78
|
+
|
|
79
|
+
const handler = this.handlers.get(job.name);
|
|
80
|
+
if (!handler) {
|
|
81
|
+
console.warn(`[QUEUE WARN] No handler registered for job '${job.name}'.`);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
await handler(job.payload);
|
|
87
|
+
} catch (err) {
|
|
88
|
+
console.error(`[QUEUE ERROR] Error processing job '${job.name}' (${job.id}):`, err);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
this.isProcessing = false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Auto-loads all job definition files from backend/jobs/.
|
|
97
|
+
*/
|
|
98
|
+
async loadJobs(jobsDir = path.resolve(process.cwd(), "backend/jobs")) {
|
|
99
|
+
if (!fs.existsSync(jobsDir)) return;
|
|
100
|
+
|
|
101
|
+
const files = fs
|
|
102
|
+
.readdirSync(jobsDir)
|
|
103
|
+
.filter((f) => f.endsWith(".js") || f.endsWith(".mjs"));
|
|
104
|
+
|
|
105
|
+
for (const file of files) {
|
|
106
|
+
const fullPath = path.join(jobsDir, file);
|
|
107
|
+
try {
|
|
108
|
+
const module = await import(`file://${fullPath}`);
|
|
109
|
+
if (typeof module.default === "function") {
|
|
110
|
+
const jobName = path.basename(file, path.extname(file));
|
|
111
|
+
this.process(jobName, module.default);
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
console.error(`[QUEUE ERROR] Failed to load job file '${file}':`, err.message);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export const Queue = new QueueManager();
|
|
121
|
+
export default Queue;
|
package/core/validate.js
CHANGED
|
@@ -148,19 +148,20 @@ class Validator {
|
|
|
148
148
|
* const result = await loginValidator.validate(req);
|
|
149
149
|
*/
|
|
150
150
|
async validate(req) {
|
|
151
|
+
const isExpressReq = req && (req.body !== undefined || req.headers !== undefined);
|
|
151
152
|
let lang =
|
|
152
153
|
req?.body?.lang ||
|
|
153
154
|
req?.query?.lang ||
|
|
154
155
|
req?.params?.lang ||
|
|
155
156
|
req?.cookies?.lang ||
|
|
156
|
-
req?.headers["accept-language"]?.split(",")[0]?.split("-")[0] ||
|
|
157
|
+
req?.headers?.["accept-language"]?.split(",")[0]?.split("-")[0] ||
|
|
157
158
|
req?.session?.lang ||
|
|
158
159
|
this.lang_default ||
|
|
159
160
|
"es";
|
|
160
161
|
const msg = this.messages[lang] || this.messages.es;
|
|
161
162
|
const errors = [];
|
|
162
163
|
const messages = [];
|
|
163
|
-
const body = req.body || {};
|
|
164
|
+
const body = isExpressReq ? (req.body || {}) : (req || {});
|
|
164
165
|
|
|
165
166
|
for (const [field, config] of Object.entries(this.schema)) {
|
|
166
167
|
let value = body[field];
|
package/docker/nginx.conf
CHANGED
|
@@ -65,6 +65,29 @@ http {
|
|
|
65
65
|
try_files $uri =404;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
# -------------------------------------------------------------
|
|
69
|
+
# Optional: Protected Static Frontend Pages (auth_request)
|
|
70
|
+
# Uncomment this block to protect private static pages (e.g. /dashboard)
|
|
71
|
+
# Nginx will subrequest Express /api/auth/check before serving HTML.
|
|
72
|
+
# -------------------------------------------------------------
|
|
73
|
+
# location ~ ^/(dashboard|admin|app|account) {
|
|
74
|
+
# auth_request /api/auth/check;
|
|
75
|
+
# error_page 401 = @login_redirect;
|
|
76
|
+
# try_files $uri $uri.html /index.html =404;
|
|
77
|
+
# }
|
|
78
|
+
#
|
|
79
|
+
# location = /api/auth/check {
|
|
80
|
+
# internal;
|
|
81
|
+
# proxy_pass http://app:3000/api/auth/check;
|
|
82
|
+
# proxy_pass_request_body off;
|
|
83
|
+
# proxy_set_header Content-Length "";
|
|
84
|
+
# proxy_set_header X-Original-URI $request_uri;
|
|
85
|
+
# }
|
|
86
|
+
#
|
|
87
|
+
# location @login_redirect {
|
|
88
|
+
# return 302 /login?redirect=$request_uri;
|
|
89
|
+
# }
|
|
90
|
+
|
|
68
91
|
location / {
|
|
69
92
|
add_header Cache-Control "no-cache";
|
|
70
93
|
try_files $uri $uri.html $uri/ @node_app;
|
package/frontend/about.html
CHANGED
|
@@ -1,108 +1,157 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en" data-theme="dark">
|
|
3
|
-
|
|
4
|
-
<head>
|
|
5
|
-
<meta charset="UTF-8">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Blue Bird - About</title>
|
|
8
|
-
<link rel="canonical" href="https://seip25.github.io/Blue-bird/" />
|
|
9
|
-
<meta name="description"
|
|
10
|
-
|
|
11
|
-
<meta name="keywords" content="">
|
|
12
|
-
<meta name="author" content="Seip25">
|
|
13
|
-
<link rel="icon" href="/images/favicon.ico" />
|
|
14
|
-
<
|
|
15
|
-
<script src="/js/
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
<
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
<
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
</
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
<
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
</
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
</
|
|
107
|
-
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
|
+
|
|
4
|
+
<head>
|
|
5
|
+
<meta charset="UTF-8">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
+
<title>Blue Bird - About</title>
|
|
8
|
+
<link rel="canonical" href="https://seip25.github.io/Blue-bird/" />
|
|
9
|
+
<meta name="description"
|
|
10
|
+
content="Blue Bird is a fast, structured, and opinionated Express.js framework for developers who want visual elegance and performance with raw HTML and CSS.">
|
|
11
|
+
<meta name="keywords" content="Blue Bird, Express.js, About, Semantic CSS, Architecture">
|
|
12
|
+
<meta name="author" content="Seip25">
|
|
13
|
+
<link rel="icon" href="/images/favicon.ico" />
|
|
14
|
+
<link rel="stylesheet" href="/css/bluebird.css" />
|
|
15
|
+
<script src="/js/bluebird.js"></script>
|
|
16
|
+
</head>
|
|
17
|
+
|
|
18
|
+
<body>
|
|
19
|
+
<header>
|
|
20
|
+
<nav>
|
|
21
|
+
<a href="/" class="flex items-center gap-2">
|
|
22
|
+
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
|
|
23
|
+
stroke-linecap="round" stroke-linejoin="round" class="text-blue">
|
|
24
|
+
<path d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
|
25
|
+
</svg>
|
|
26
|
+
<strong class="text-xl text-gradient">Blue Bird</strong>
|
|
27
|
+
</a>
|
|
28
|
+
</nav>
|
|
29
|
+
</header>
|
|
30
|
+
|
|
31
|
+
<main>
|
|
32
|
+
<aside>
|
|
33
|
+
<h4>Menu</h4>
|
|
34
|
+
<a href="/">Home</a>
|
|
35
|
+
<a href="/about" class="active badge badge-glow px-3 py-3">About</a>
|
|
36
|
+
<a href="https://seip25.github.io/Blue-bird-css/" target="_blank">Blue Bird CSS</a>
|
|
37
|
+
<a href="https://github.com/seip25/Blue-bird" target="_blank" role="button"
|
|
38
|
+
class="btn-sm outline">GitHub</a>
|
|
39
|
+
</aside>
|
|
40
|
+
|
|
41
|
+
<div>
|
|
42
|
+
<section class="hero ">
|
|
43
|
+
<h1 class="mt-3 text-gradient font-bold">About Blue Bird</h1>
|
|
44
|
+
<p class="lead">Built for performance, simplicity, and raw speed.</p>
|
|
45
|
+
</section>
|
|
46
|
+
|
|
47
|
+
<article class="mt-6 ">
|
|
48
|
+
<header>
|
|
49
|
+
<h2 class="text-gradient-blue font-semibold">Our Philosophy</h2>
|
|
50
|
+
</header>
|
|
51
|
+
<p>
|
|
52
|
+
Blue Bird was born out of a desire for a clean, performance-first approach to web development. We
|
|
53
|
+
believe in harnessing the raw power of Express.js and Nginx, stripping away unnecessary bloat, and
|
|
54
|
+
providing developers with a robust foundation that just works.
|
|
55
|
+
</p>
|
|
56
|
+
<p>
|
|
57
|
+
By separating the static frontend delivery (handled blazingly fast by Nginx) from the dynamic API
|
|
58
|
+
layer (powered by Express and Redis), Blue Bird achieves unparalleled performance out of the box.
|
|
59
|
+
</p>
|
|
60
|
+
</article>
|
|
61
|
+
|
|
62
|
+
<div class="px-4 py-4 mb-4">
|
|
63
|
+
<h2 class="text-gradient-blue font-semibold">Why Blue Bird?</h2>
|
|
64
|
+
|
|
65
|
+
<div class="grid cols-2 gap-4">
|
|
66
|
+
<div>
|
|
67
|
+
<details open>
|
|
68
|
+
<summary>Zero Config Docker</summary>
|
|
69
|
+
<p>Go from development to production seamlessly with ready-to-use
|
|
70
|
+
Docker orchestration for
|
|
71
|
+
SQLite, MySQL, PostgreSQL, and Redis.</p>
|
|
72
|
+
</details>
|
|
73
|
+
<details open>
|
|
74
|
+
<summary>Military Grade Security</summary>
|
|
75
|
+
<p>Built-in AES-256-GCM JWT encryption, secure HttpOnly cookie
|
|
76
|
+
sessions, helmet headers, and
|
|
77
|
+
route rate limiting.</p>
|
|
78
|
+
</details>
|
|
79
|
+
</div>
|
|
80
|
+
<div>
|
|
81
|
+
<details open>
|
|
82
|
+
<summary>High Performance Layer</summary>
|
|
83
|
+
<p>Redis caching for the data layer and Nginx static delivery with
|
|
84
|
+
1-month browser asset
|
|
85
|
+
caching.</p>
|
|
86
|
+
</details>
|
|
87
|
+
<details open>
|
|
88
|
+
<summary>Pure Semantic HTML & CSS</summary>
|
|
89
|
+
<p>No bloated frontend frameworks or heavy node compile steps. Write
|
|
90
|
+
clean, accessible code
|
|
91
|
+
close to the metal.</p>
|
|
92
|
+
</details>
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
</article>
|
|
96
|
+
|
|
97
|
+
<article class="">
|
|
98
|
+
<header>
|
|
99
|
+
<h2 class="text-gradient-blue font-semibold">Core Architecture Summary</h2>
|
|
100
|
+
</header>
|
|
101
|
+
<table class="table table-hover">
|
|
102
|
+
<thead>
|
|
103
|
+
<tr>
|
|
104
|
+
<th>Layer</th>
|
|
105
|
+
<th>Technology</th>
|
|
106
|
+
<th>Role</th>
|
|
107
|
+
</tr>
|
|
108
|
+
</thead>
|
|
109
|
+
<tbody>
|
|
110
|
+
<tr>
|
|
111
|
+
<td><strong>Frontend</strong></td>
|
|
112
|
+
<td>Pure Semantic HTML5 + <a href="https://seip25.github.io/Blue-bird-css/"
|
|
113
|
+
target="_blank">Blue Bird CSS</a></td>
|
|
114
|
+
<td>Static rendering served directly via Nginx</td>
|
|
115
|
+
</tr>
|
|
116
|
+
<tr>
|
|
117
|
+
<td><strong>Backend</strong></td>
|
|
118
|
+
<td>Node.js + Express + PM2</td>
|
|
119
|
+
<td>High-performance REST API services</td>
|
|
120
|
+
</tr>
|
|
121
|
+
<tr>
|
|
122
|
+
<td><strong>Cache</strong></td>
|
|
123
|
+
<td>Redis / In-Memory RAM</td>
|
|
124
|
+
<td>Sub-millisecond route & query caching</td>
|
|
125
|
+
</tr>
|
|
126
|
+
<tr>
|
|
127
|
+
<td><strong>Database</strong></td>
|
|
128
|
+
<td>SQLite (WAL) / PostgreSQL / MySQL</td>
|
|
129
|
+
<td>Unified driver with connection pooling</td>
|
|
130
|
+
</tr>
|
|
131
|
+
</tbody>
|
|
132
|
+
</table>
|
|
133
|
+
</article>
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
</div>
|
|
137
|
+
</main>
|
|
138
|
+
|
|
139
|
+
<footer>
|
|
140
|
+
<div class="footer-content">
|
|
141
|
+
<p>Powered by <strong class="text-gradient">Blue Bird Framework</strong> &
|
|
142
|
+
<strong class="text-gradient-blue">
|
|
143
|
+
<a href="https://seip25.github.io/Blue-bird-css/" target="_blank">Blue Bird
|
|
144
|
+
CSS</a>
|
|
145
|
+
</strong>.
|
|
146
|
+
</p>
|
|
147
|
+
<div class="lang-switcher">
|
|
148
|
+
<a href="/" class="lang-link">Home</a>
|
|
149
|
+
<a href="/about" class="lang-link">About</a>
|
|
150
|
+
<a href="https://github.com/seip25/Blue-bird" target="_blank" class="github-star mt-2">★
|
|
151
|
+
GitHub</a>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
</footer>
|
|
155
|
+
</body>
|
|
156
|
+
|
|
108
157
|
</html>
|