@seip/blue-bird 1.0.2 → 1.1.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.
- package/.env_example +1 -0
- package/AGENTS.md +1 -1
- package/core/cli/docker.js +19 -19
- package/core/cli/init.js +44 -2
- package/core/index.d.ts +137 -137
- package/core/ws.js +210 -210
- package/frontend/js/utils.js +557 -557
- package/package.json +64 -65
package/.env_example
CHANGED
package/AGENTS.md
CHANGED
|
@@ -6,7 +6,7 @@ This document serves as the primary manual for any AI Agent interacting with the
|
|
|
6
6
|
|
|
7
7
|
Blue Bird is a performance-first API framework built on **Express**. It saves developers from repetitive configuration, validation, security, JWT authentication, and database environment configuration out of the box, delegating all static frontend rendering to Nginx.
|
|
8
8
|
|
|
9
|
-
- **Entrypoint (`index.js`)**: Initializes the server using `App` from `core/app.js` and registers the routes.
|
|
9
|
+
- **Entrypoint (`backend/index.js`)**: Initializes the server using `App` from `core/app.js` and registers the routes.
|
|
10
10
|
- **Backend (`backend/`)**: Application routes and logic (e.g. `backend/routes/`).
|
|
11
11
|
- **Frontend (`frontend/`)**: Static assets (HTML, CSS, JS). Handled directly by Nginx in production, bypassing Express.
|
|
12
12
|
- **Core (`core/`)**: The framework core. Contains wrapper classes such as `Router`, `Validator`, `Auth`, `Cache`, etc. **DO NOT MODIFY** the core unless explicitly requested, as it could break other apps.
|
package/core/cli/docker.js
CHANGED
|
@@ -145,25 +145,25 @@ async function startCommand(service) {
|
|
|
145
145
|
/**
|
|
146
146
|
* Handles the 'dev' CLI command.
|
|
147
147
|
*/
|
|
148
|
-
async function devCommand() {
|
|
149
|
-
checkComposeFile();
|
|
150
|
-
const dbType = getDbType();
|
|
151
|
-
console.log(chalk.cyan("Starting development environment (Database + Redis)..."));
|
|
152
|
-
|
|
153
|
-
const containers = ["redis"];
|
|
154
|
-
if (dbType !== "none") {
|
|
155
|
-
containers.unshift(dbType);
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
const code = await runCmd("docker", ["compose", "up", "-d", ...containers]);
|
|
159
|
-
if (code === 0) {
|
|
160
|
-
console.log(chalk.green("Development containers started."));
|
|
161
|
-
console.log(chalk.cyan("View live logs with: npx blue-bird docker logs db"));
|
|
162
|
-
console.log(chalk.yellow("Now execute: npm run dev"));
|
|
163
|
-
} else {
|
|
164
|
-
console.error(chalk.red("Error starting development environment."));
|
|
165
|
-
process.exit(1);
|
|
166
|
-
}
|
|
148
|
+
async function devCommand() {
|
|
149
|
+
checkComposeFile();
|
|
150
|
+
const dbType = getDbType();
|
|
151
|
+
console.log(chalk.cyan("Starting development environment (Database + Redis)..."));
|
|
152
|
+
|
|
153
|
+
const containers = ["redis"];
|
|
154
|
+
if (dbType !== "none") {
|
|
155
|
+
containers.unshift(dbType);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const code = await runCmd("docker", ["compose", "up", "-d", ...containers]);
|
|
159
|
+
if (code === 0) {
|
|
160
|
+
console.log(chalk.green("Development containers started."));
|
|
161
|
+
console.log(chalk.cyan("View live logs with: npx blue-bird docker logs db"));
|
|
162
|
+
console.log(chalk.yellow("Now execute: npm run dev"));
|
|
163
|
+
} else {
|
|
164
|
+
console.error(chalk.red("Error starting development environment."));
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
167
|
}
|
|
168
168
|
|
|
169
169
|
/**
|
package/core/cli/init.js
CHANGED
|
@@ -103,8 +103,6 @@ class ProjectInit {
|
|
|
103
103
|
"docker",
|
|
104
104
|
".env_example",
|
|
105
105
|
"AGENTS.md",
|
|
106
|
-
"index.js",
|
|
107
|
-
".gitignore"
|
|
108
106
|
];
|
|
109
107
|
|
|
110
108
|
try {
|
|
@@ -128,6 +126,24 @@ class ProjectInit {
|
|
|
128
126
|
}
|
|
129
127
|
});
|
|
130
128
|
|
|
129
|
+
const gitignoreContent = `node_modules\nlogs\n.env\npackage-lock.json\n*.db\ntest/\n\nbackups/*.sql\n`;
|
|
130
|
+
const gitignoreDest = path.join(this.appDir, ".gitignore");
|
|
131
|
+
const gitignoreSrc = path.join(this.sourceDir, ".gitignore");
|
|
132
|
+
|
|
133
|
+
if (!fs.existsSync(gitignoreDest)) {
|
|
134
|
+
if (fs.existsSync(gitignoreSrc)) {
|
|
135
|
+
fs.copyFileSync(gitignoreSrc, gitignoreDest);
|
|
136
|
+
console.log(chalk.green("[OK] Copied .gitignore to root."));
|
|
137
|
+
} else {
|
|
138
|
+
fs.writeFileSync(gitignoreDest, gitignoreContent, "utf-8");
|
|
139
|
+
console.log(chalk.green("[OK] Created .gitignore file."));
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
console.log(
|
|
143
|
+
chalk.yellow("[SKIP] .gitignore already exists, skipping."),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
131
147
|
const composeTemplateName =
|
|
132
148
|
dbType === "postgres"
|
|
133
149
|
? "docker-compose.postgres.yml"
|
|
@@ -166,8 +182,15 @@ class ProjectInit {
|
|
|
166
182
|
let envContent = fs.readFileSync(envExamplePath, "utf-8");
|
|
167
183
|
|
|
168
184
|
const jwtSecret = crypto.randomBytes(32).toString("hex");
|
|
185
|
+
const composeProjectName =
|
|
186
|
+
title
|
|
187
|
+
.toLowerCase()
|
|
188
|
+
.trim()
|
|
189
|
+
.replace(/\s+/g, "-")
|
|
190
|
+
.replace(/[^a-z0-9_-]/g, "") || "blue-bird";
|
|
169
191
|
|
|
170
192
|
const updates = {
|
|
193
|
+
COMPOSE_PROJECT_NAME: composeProjectName,
|
|
171
194
|
TITLE: title,
|
|
172
195
|
PORT: port,
|
|
173
196
|
APP_URL: appUrl,
|
|
@@ -190,10 +213,14 @@ class ProjectInit {
|
|
|
190
213
|
}
|
|
191
214
|
|
|
192
215
|
const lines = envContent.split(/\r?\n/);
|
|
216
|
+
let foundComposeProjectName = false;
|
|
193
217
|
const updatedLines = lines.map((line) => {
|
|
194
218
|
const match = line.match(/^([A-Z_]+)=(.+)/);
|
|
195
219
|
if (match) {
|
|
196
220
|
const key = match[1];
|
|
221
|
+
if (key === "COMPOSE_PROJECT_NAME") {
|
|
222
|
+
foundComposeProjectName = true;
|
|
223
|
+
}
|
|
197
224
|
if (updates[key] !== undefined) {
|
|
198
225
|
const value = updates[key];
|
|
199
226
|
if (typeof value === "string" && !value.startsWith('"')) {
|
|
@@ -204,6 +231,21 @@ class ProjectInit {
|
|
|
204
231
|
}
|
|
205
232
|
return line;
|
|
206
233
|
});
|
|
234
|
+
|
|
235
|
+
if (!foundComposeProjectName && updates.COMPOSE_PROJECT_NAME) {
|
|
236
|
+
const titleIdx = updatedLines.findIndex((l) => l.startsWith("TITLE="));
|
|
237
|
+
if (titleIdx !== -1) {
|
|
238
|
+
updatedLines.splice(
|
|
239
|
+
titleIdx,
|
|
240
|
+
0,
|
|
241
|
+
`COMPOSE_PROJECT_NAME="${updates.COMPOSE_PROJECT_NAME}"`,
|
|
242
|
+
);
|
|
243
|
+
} else {
|
|
244
|
+
updatedLines.push(
|
|
245
|
+
`COMPOSE_PROJECT_NAME="${updates.COMPOSE_PROJECT_NAME}"`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
207
249
|
envContent = updatedLines.join("\n");
|
|
208
250
|
|
|
209
251
|
fs.writeFileSync(envPath, envContent, "utf-8");
|
package/core/index.d.ts
CHANGED
|
@@ -1,137 +1,137 @@
|
|
|
1
|
-
import { Router as ExpressRouter, Request, Response, NextFunction } from "express";
|
|
2
|
-
|
|
3
|
-
declare global {
|
|
4
|
-
namespace Express {
|
|
5
|
-
interface Response {
|
|
6
|
-
/**
|
|
7
|
-
* Sends a standardized JSON success response.
|
|
8
|
-
* @param data Data payload to return.
|
|
9
|
-
* @param message Success message.
|
|
10
|
-
* @param statusCode HTTP status code (default: 200).
|
|
11
|
-
*/
|
|
12
|
-
success(data?: any, message?: string, statusCode?: number): Response;
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Sends a standardized JSON error response.
|
|
16
|
-
* @param message Error message.
|
|
17
|
-
* @param statusCode HTTP status code (default: 400).
|
|
18
|
-
* @param errors Array or object of detailed errors.
|
|
19
|
-
*/
|
|
20
|
-
error(message?: string, statusCode?: number, errors?: any): Response;
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* Sends a standardized paginated JSON response.
|
|
24
|
-
* @param data Array of records for current page.
|
|
25
|
-
* @param pagination Object containing page, limit, and total count.
|
|
26
|
-
* @param message Success message.
|
|
27
|
-
*/
|
|
28
|
-
paginate(
|
|
29
|
-
data?: any[],
|
|
30
|
-
pagination?: { page?: number; limit?: number; total?: number },
|
|
31
|
-
message?: string
|
|
32
|
-
): Response;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export class AppError extends Error {
|
|
38
|
-
statusCode: number;
|
|
39
|
-
errors: any;
|
|
40
|
-
isOperational: boolean;
|
|
41
|
-
|
|
42
|
-
constructor(message: string, statusCode?: number, errors?: any);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export class App {
|
|
46
|
-
constructor(options?: {
|
|
47
|
-
routes?: any[];
|
|
48
|
-
cors?: any;
|
|
49
|
-
middlewares?: any[];
|
|
50
|
-
port?: number | string;
|
|
51
|
-
host?: string;
|
|
52
|
-
logger?: boolean;
|
|
53
|
-
notFound?: boolean;
|
|
54
|
-
json?: boolean;
|
|
55
|
-
urlencoded?: boolean;
|
|
56
|
-
static?: { path: string; options?: any };
|
|
57
|
-
cookieParser?: boolean;
|
|
58
|
-
rateLimit?: boolean | any;
|
|
59
|
-
swagger?: boolean | any;
|
|
60
|
-
compression?: boolean;
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
use(record: any): void;
|
|
64
|
-
set(key: string, value: any): void;
|
|
65
|
-
websocket(
|
|
66
|
-
options?:
|
|
67
|
-
| ((ws: any, req: any) => void)
|
|
68
|
-
| { path?: string; auth?: boolean }
|
|
69
|
-
): WebSocketManager;
|
|
70
|
-
run(): void;
|
|
71
|
-
|
|
72
|
-
static helmet(options?: any): any;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
export class WebSocketManager {
|
|
76
|
-
constructor(server: any, options?: { path?: string; auth?: boolean });
|
|
77
|
-
onConnection(handler: (ws: any, req: any) => void): void;
|
|
78
|
-
join(room: string, ws: any): void;
|
|
79
|
-
leave(room: string, ws: any): void;
|
|
80
|
-
broadcast(data: any, room?: string | null): void;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
export class Router {
|
|
84
|
-
constructor(path?: string, options?: { seo?: boolean; languages?: string[] });
|
|
85
|
-
|
|
86
|
-
use(...middleware: any[]): void;
|
|
87
|
-
get(path: string | RegExp, ...callback: any[]): void;
|
|
88
|
-
post(path: string | RegExp, ...callback: any[]): void;
|
|
89
|
-
put(path: string | RegExp, ...callback: any[]): void;
|
|
90
|
-
delete(path: string | RegExp, ...callback: any[]): void;
|
|
91
|
-
patch(path: string | RegExp, ...callback: any[]): void;
|
|
92
|
-
options(path: string | RegExp, ...callback: any[]): void;
|
|
93
|
-
getRouter(): ExpressRouter;
|
|
94
|
-
getPath(): string;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
export class Validator {
|
|
98
|
-
constructor(schema: Record<string, any>, lang?: string);
|
|
99
|
-
middleware(): (req: Request, res: Response, next: NextFunction) => void;
|
|
100
|
-
validate(data: Record<string, any>): { valid: boolean; errors: any[] };
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
export class Auth {
|
|
104
|
-
static encrypt(payload: any, secret: string): string;
|
|
105
|
-
static decrypt(data: string, secret: string): any;
|
|
106
|
-
static generateToken(payload: any, secret?: string, expiresIn?: string | number): string;
|
|
107
|
-
static verifyToken(token: string, secret?: string): any;
|
|
108
|
-
static protect(options?: { redirect?: string | null; key?: string; cookieKey?: string }): (req: Request, res: Response, next: NextFunction) => Promise<any>;
|
|
109
|
-
static login(res: Response, data: any, key?: string, options?: { expiresIn?: string | number; cookie?: any }): Promise<string>;
|
|
110
|
-
static logout(res: Response, key?: string, options?: any, req?: Request): Promise<boolean>;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export class Cache {
|
|
114
|
-
static middleware(seconds?: number): (req: Request, res: Response, next: NextFunction) => Promise<any>;
|
|
115
|
-
static get(key: string): Promise<any | null>;
|
|
116
|
-
static set(key: string, value: any, seconds?: number): Promise<boolean>;
|
|
117
|
-
static delete(keys: string | string[]): Promise<boolean>;
|
|
118
|
-
static del(keys: string | string[]): Promise<boolean>;
|
|
119
|
-
static clear(): Promise<boolean>;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function getRedisClient(): any;
|
|
123
|
-
|
|
124
|
-
export class Database {
|
|
125
|
-
constructor(connectionLimit?: number, queueLimit?: number, config?: any);
|
|
126
|
-
init(retries?: number): Promise<boolean>;
|
|
127
|
-
query(sql: string, params?: any[], options?: any): Promise<any>;
|
|
128
|
-
paginate(
|
|
129
|
-
sql: string,
|
|
130
|
-
params?: any[],
|
|
131
|
-
options?: { page?: number; limit?: number; cache?: number }
|
|
132
|
-
): Promise<{ data: any[]; total: number; page: number; limit: number; totalPages: number }>;
|
|
133
|
-
transaction<T = any>(callback: (tx: { query: (sql: string, params?: any[], options?: any) => Promise<any> }) => Promise<T>): Promise<T>;
|
|
134
|
-
executeTransaction<T = any>(callback: (tx: { query: (sql: string, params?: any[], options?: any) => Promise<any> }) => Promise<T>): Promise<T>;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
export default App;
|
|
1
|
+
import { Router as ExpressRouter, Request, Response, NextFunction } from "express";
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
namespace Express {
|
|
5
|
+
interface Response {
|
|
6
|
+
/**
|
|
7
|
+
* Sends a standardized JSON success response.
|
|
8
|
+
* @param data Data payload to return.
|
|
9
|
+
* @param message Success message.
|
|
10
|
+
* @param statusCode HTTP status code (default: 200).
|
|
11
|
+
*/
|
|
12
|
+
success(data?: any, message?: string, statusCode?: number): Response;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Sends a standardized JSON error response.
|
|
16
|
+
* @param message Error message.
|
|
17
|
+
* @param statusCode HTTP status code (default: 400).
|
|
18
|
+
* @param errors Array or object of detailed errors.
|
|
19
|
+
*/
|
|
20
|
+
error(message?: string, statusCode?: number, errors?: any): Response;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Sends a standardized paginated JSON response.
|
|
24
|
+
* @param data Array of records for current page.
|
|
25
|
+
* @param pagination Object containing page, limit, and total count.
|
|
26
|
+
* @param message Success message.
|
|
27
|
+
*/
|
|
28
|
+
paginate(
|
|
29
|
+
data?: any[],
|
|
30
|
+
pagination?: { page?: number; limit?: number; total?: number },
|
|
31
|
+
message?: string
|
|
32
|
+
): Response;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export class AppError extends Error {
|
|
38
|
+
statusCode: number;
|
|
39
|
+
errors: any;
|
|
40
|
+
isOperational: boolean;
|
|
41
|
+
|
|
42
|
+
constructor(message: string, statusCode?: number, errors?: any);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export class App {
|
|
46
|
+
constructor(options?: {
|
|
47
|
+
routes?: any[];
|
|
48
|
+
cors?: any;
|
|
49
|
+
middlewares?: any[];
|
|
50
|
+
port?: number | string;
|
|
51
|
+
host?: string;
|
|
52
|
+
logger?: boolean;
|
|
53
|
+
notFound?: boolean;
|
|
54
|
+
json?: boolean;
|
|
55
|
+
urlencoded?: boolean;
|
|
56
|
+
static?: { path: string; options?: any };
|
|
57
|
+
cookieParser?: boolean;
|
|
58
|
+
rateLimit?: boolean | any;
|
|
59
|
+
swagger?: boolean | any;
|
|
60
|
+
compression?: boolean;
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
use(record: any): void;
|
|
64
|
+
set(key: string, value: any): void;
|
|
65
|
+
websocket(
|
|
66
|
+
options?:
|
|
67
|
+
| ((ws: any, req: any) => void)
|
|
68
|
+
| { path?: string; auth?: boolean }
|
|
69
|
+
): WebSocketManager;
|
|
70
|
+
run(): void;
|
|
71
|
+
|
|
72
|
+
static helmet(options?: any): any;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export class WebSocketManager {
|
|
76
|
+
constructor(server: any, options?: { path?: string; auth?: boolean });
|
|
77
|
+
onConnection(handler: (ws: any, req: any) => void): void;
|
|
78
|
+
join(room: string, ws: any): void;
|
|
79
|
+
leave(room: string, ws: any): void;
|
|
80
|
+
broadcast(data: any, room?: string | null): void;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export class Router {
|
|
84
|
+
constructor(path?: string, options?: { seo?: boolean; languages?: string[] });
|
|
85
|
+
|
|
86
|
+
use(...middleware: any[]): void;
|
|
87
|
+
get(path: string | RegExp, ...callback: any[]): void;
|
|
88
|
+
post(path: string | RegExp, ...callback: any[]): void;
|
|
89
|
+
put(path: string | RegExp, ...callback: any[]): void;
|
|
90
|
+
delete(path: string | RegExp, ...callback: any[]): void;
|
|
91
|
+
patch(path: string | RegExp, ...callback: any[]): void;
|
|
92
|
+
options(path: string | RegExp, ...callback: any[]): void;
|
|
93
|
+
getRouter(): ExpressRouter;
|
|
94
|
+
getPath(): string;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export class Validator {
|
|
98
|
+
constructor(schema: Record<string, any>, lang?: string);
|
|
99
|
+
middleware(): (req: Request, res: Response, next: NextFunction) => void;
|
|
100
|
+
validate(data: Record<string, any>): { valid: boolean; errors: any[] };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export class Auth {
|
|
104
|
+
static encrypt(payload: any, secret: string): string;
|
|
105
|
+
static decrypt(data: string, secret: string): any;
|
|
106
|
+
static generateToken(payload: any, secret?: string, expiresIn?: string | number): string;
|
|
107
|
+
static verifyToken(token: string, secret?: string): any;
|
|
108
|
+
static protect(options?: { redirect?: string | null; key?: string; cookieKey?: string }): (req: Request, res: Response, next: NextFunction) => Promise<any>;
|
|
109
|
+
static login(res: Response, data: any, key?: string, options?: { expiresIn?: string | number; cookie?: any }): Promise<string>;
|
|
110
|
+
static logout(res: Response, key?: string, options?: any, req?: Request): Promise<boolean>;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export class Cache {
|
|
114
|
+
static middleware(seconds?: number): (req: Request, res: Response, next: NextFunction) => Promise<any>;
|
|
115
|
+
static get(key: string): Promise<any | null>;
|
|
116
|
+
static set(key: string, value: any, seconds?: number): Promise<boolean>;
|
|
117
|
+
static delete(keys: string | string[]): Promise<boolean>;
|
|
118
|
+
static del(keys: string | string[]): Promise<boolean>;
|
|
119
|
+
static clear(): Promise<boolean>;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function getRedisClient(): any;
|
|
123
|
+
|
|
124
|
+
export class Database {
|
|
125
|
+
constructor(connectionLimit?: number, queueLimit?: number, config?: any);
|
|
126
|
+
init(retries?: number): Promise<boolean>;
|
|
127
|
+
query(sql: string, params?: any[], options?: any): Promise<any>;
|
|
128
|
+
paginate(
|
|
129
|
+
sql: string,
|
|
130
|
+
params?: any[],
|
|
131
|
+
options?: { page?: number; limit?: number; cache?: number }
|
|
132
|
+
): Promise<{ data: any[]; total: number; page: number; limit: number; totalPages: number }>;
|
|
133
|
+
transaction<T = any>(callback: (tx: { query: (sql: string, params?: any[], options?: any) => Promise<any> }) => Promise<T>): Promise<T>;
|
|
134
|
+
executeTransaction<T = any>(callback: (tx: { query: (sql: string, params?: any[], options?: any) => Promise<any> }) => Promise<T>): Promise<T>;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default App;
|