yinzerflow 0.2.4 → 0.2.6
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/docs/advanced-configuration-options.md +116 -0
- package/docs/request.md +67 -18
- package/docs/routes.md +656 -0
- package/docs/start-here.md +178 -0
- package/example/README.md +11 -0
- package/example/app/handlers/example.ts +30 -0
- package/example/app/index.ts +110 -0
- package/example/app/routes/example.ts +18 -0
- package/example/app/util/customLogger.ts +166 -0
- package/example/docker-compose.yml +22 -0
- package/example/package.json +16 -0
- package/example/tsconfig.json +54 -0
- package/index.d.ts +14 -7
- package/index.js +10 -10
- package/index.js.map +5 -5
- package/package.json +2 -2
- package/docs/start-here.MD +0 -116
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
# Getting Started with YinzerFlow
|
|
2
|
+
|
|
3
|
+
YinzerFlow is a lightweight, modular HTTP server framework for Node.js built with TypeScript. It provides a powerful routing system, comprehensive security features, and built-in Pittsburgh personality with flexible logging and configuration options.
|
|
4
|
+
|
|
5
|
+
## What is YinzerFlow?
|
|
6
|
+
|
|
7
|
+
YinzerFlow is designed for developers who want:
|
|
8
|
+
- **Security-first approach** with built-in protections against common web vulnerabilities
|
|
9
|
+
- **TypeScript-first development** with full type safety and IntelliSense support
|
|
10
|
+
- **Flexible configuration** for different deployment environments and use cases
|
|
11
|
+
- **Pittsburgh personality** with witty logging and error messages
|
|
12
|
+
- **Modular architecture** that scales from simple APIs to complex microservices
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### Installation
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# Using npm
|
|
20
|
+
npm install yinzerflow
|
|
21
|
+
|
|
22
|
+
# Using Yarn
|
|
23
|
+
yarn add yinzerflow
|
|
24
|
+
|
|
25
|
+
# Using Bun
|
|
26
|
+
bun add yinzerflow
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### Minimal Example
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
33
|
+
|
|
34
|
+
// Create a new YinzerFlow instance
|
|
35
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
36
|
+
|
|
37
|
+
// Add a simple route
|
|
38
|
+
app.get('/hello', () => {
|
|
39
|
+
return { message: 'Hello, World!' };
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// Start the server
|
|
43
|
+
await app.listen();
|
|
44
|
+
// Although you can log the server is started, the built in logging will log this for you already when the server is listening
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Basic API Example
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
51
|
+
|
|
52
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
53
|
+
|
|
54
|
+
// GET route with parameters
|
|
55
|
+
app.get('/api/users/:id', ({ request }) => {
|
|
56
|
+
const userId = request.params.id;
|
|
57
|
+
const includeProfile = request.query.include_profile;
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
id: userId,
|
|
61
|
+
name: 'John Doe',
|
|
62
|
+
includeProfile: !!includeProfile
|
|
63
|
+
};
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// POST route with body parsing
|
|
67
|
+
app.post('/api/users', ({ request }) => {
|
|
68
|
+
const userData = request.body;
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
message: 'User created successfully',
|
|
72
|
+
data: userData
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
await app.listen();
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Graceful Shutdown
|
|
80
|
+
|
|
81
|
+
YinzerFlow automatically handles graceful shutdown for SIGTERM and SIGINT signals. No manual setup required:
|
|
82
|
+
|
|
83
|
+
```typescript
|
|
84
|
+
import { YinzerFlow } from 'yinzerflow';
|
|
85
|
+
|
|
86
|
+
const app = new YinzerFlow({ port: 3000 });
|
|
87
|
+
|
|
88
|
+
// Add your routes
|
|
89
|
+
app.get('/hello', () => {
|
|
90
|
+
return { message: 'Hello, World!' };
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
// Start the server - graceful shutdown is automatically configured
|
|
94
|
+
await app.listen();
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
**That's it!** YinzerFlow will automatically:
|
|
98
|
+
- Listen for SIGTERM and SIGINT signals
|
|
99
|
+
- Log the shutdown process with Pittsburgh personality
|
|
100
|
+
- Close the server gracefully
|
|
101
|
+
- Exit the process cleanly
|
|
102
|
+
|
|
103
|
+
For custom shutdown handling, see [Advanced Configuration](./advanced-configuration-options.md).
|
|
104
|
+
|
|
105
|
+
## Documentation Overview
|
|
106
|
+
|
|
107
|
+
### Core Features
|
|
108
|
+
|
|
109
|
+
- **[Routes](./routes.md)** - Comprehensive routing system with HTTP methods, parameters, hooks, and groups
|
|
110
|
+
- **[Request Object](./request.md)** - Access headers, body, query parameters, route parameters, and raw body
|
|
111
|
+
- **[Response Object](./response.md)** - Set status codes, headers, and return various response types
|
|
112
|
+
- **[Body Parsing](./body-parsing.md)** - Secure parsing of JSON, file uploads, and form data with DoS protection
|
|
113
|
+
|
|
114
|
+
### Security & Configuration
|
|
115
|
+
|
|
116
|
+
- **[Advanced Configuration](./advanced-configuration-options.md)** - Fine-tune security, performance, and functionality
|
|
117
|
+
- **[IP Security](./ip-security.md)** - Client IP validation and spoofing protection for load balancers and CDNs
|
|
118
|
+
- **[CORS](./cors.md)** - Cross-Origin Resource Sharing with comprehensive security measures
|
|
119
|
+
- **[Logging](./logging.md)** - Flexible logging with custom logger support and Pittsburgh personality
|
|
120
|
+
|
|
121
|
+
### Common Use Cases
|
|
122
|
+
|
|
123
|
+
- **API Development**: Create RESTful APIs with automatic body parsing and security
|
|
124
|
+
- **File Uploads**: Handle multipart form data with size limits and type validation
|
|
125
|
+
- **Authentication**: Implement middleware hooks for token validation and user sessions
|
|
126
|
+
- **Rate Limiting**: Add before hooks to limit request frequency and prevent abuse
|
|
127
|
+
- **Load Balancer Integration**: Configure trusted proxies for accurate client IP detection
|
|
128
|
+
- **Production Monitoring**: Use custom loggers with structured logging and monitoring
|
|
129
|
+
|
|
130
|
+
## Key Features
|
|
131
|
+
|
|
132
|
+
### 🛡️ Security First
|
|
133
|
+
- Built-in protection against DoS attacks, prototype pollution, and memory exhaustion
|
|
134
|
+
- Automatic security headers and CORS validation
|
|
135
|
+
- IP spoofing protection with trusted proxy validation
|
|
136
|
+
- Comprehensive input validation and sanitization
|
|
137
|
+
|
|
138
|
+
### 🔧 Flexible Configuration
|
|
139
|
+
- Configurable body parsing limits and security settings
|
|
140
|
+
- Custom logger integration (Winston, Pino, etc.)
|
|
141
|
+
- Environment-specific configurations (development, production, high-security)
|
|
142
|
+
- Graceful shutdown with connection timeout handling
|
|
143
|
+
|
|
144
|
+
### 🚀 TypeScript Native
|
|
145
|
+
- Full TypeScript support with comprehensive type definitions
|
|
146
|
+
- IntelliSense support for all framework features
|
|
147
|
+
- Type-safe request/response handling
|
|
148
|
+
- Generic support for custom body and response types
|
|
149
|
+
|
|
150
|
+
### 🎭 Pittsburgh Personality
|
|
151
|
+
- Witty logging messages and error handling
|
|
152
|
+
- Built-in Pittsburgh-themed personality
|
|
153
|
+
- Customizable logging with familiar `log.info()` interface
|
|
154
|
+
- Network request logging with nginx-style formatting
|
|
155
|
+
|
|
156
|
+
## Next Steps
|
|
157
|
+
|
|
158
|
+
1. **Start with Routes**: Learn the routing system in [routes.md](./routes.md)
|
|
159
|
+
2. **Understand Requests**: Explore request handling in [request.md](./request.md)
|
|
160
|
+
3. **Configure Security**: Set up IP security and CORS in [ip-security.md](./ip-security.md) and [cors.md](./cors.md)
|
|
161
|
+
4. **Customize Logging**: Implement custom loggers in [logging.md](./logging.md)
|
|
162
|
+
5. **Advanced Configuration**: Fine-tune settings in [advanced-configuration-options.md](./advanced-configuration-options.md)
|
|
163
|
+
|
|
164
|
+
## Examples
|
|
165
|
+
|
|
166
|
+
Check out the `/example` directory for complete working examples:
|
|
167
|
+
|
|
168
|
+
- **TypeScript Example** - Full-featured API with authentication, file uploads, and custom logging
|
|
169
|
+
- **Basic Example** - Minimal server setup for quick prototyping
|
|
170
|
+
- **Production Example** - High-security configuration with monitoring and graceful shutdown
|
|
171
|
+
|
|
172
|
+
## Contributing
|
|
173
|
+
|
|
174
|
+
We welcome contributions! Please see our contribution guidelines and feel free to submit pull requests for documentation improvements, bug fixes, or new features.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
**Ready to build something amazing?** Start with the [Routes documentation](./routes.md) to learn how to create your first API endpoints!
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
For the types to work, you need to connect to the docker container using the IDE
|
|
2
|
+
|
|
3
|
+
1. Ensure the [Dev Containers](https://marketplace.cursorapi.com/items?itemName=ms-vscode-remote.remote-containers) extension is installed in IDE
|
|
4
|
+
2. Start this image using `docker compose up -d`
|
|
5
|
+
3. To start the test server run `bun start`
|
|
6
|
+
4. Use `Ctrl + Shift + P`
|
|
7
|
+
5. Select "Dev Containers: Attach to Running Container ..."
|
|
8
|
+
6. Select this container which should be something like `yinzerflow-testing-app...`
|
|
9
|
+
7. In the IDE that just opened, select folder `/app/yinzerflow-local`
|
|
10
|
+
|
|
11
|
+
To aid in development, you can use the `bun run build:watch` command in the yinzerflow (the actual module) terminal
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
|
|
2
|
+
import type { HandlerCallback } from 'yinzerflow';
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
export const exampleHandler: HandlerCallback<{
|
|
8
|
+
body: {
|
|
9
|
+
something: string;
|
|
10
|
+
};
|
|
11
|
+
response: {
|
|
12
|
+
message: string;
|
|
13
|
+
success: boolean;
|
|
14
|
+
data: {
|
|
15
|
+
something: string;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
}> = async ({ request }) => {
|
|
20
|
+
const { something } = request.body;
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
return {
|
|
24
|
+
success: true,
|
|
25
|
+
message: "Hello World",
|
|
26
|
+
data: {
|
|
27
|
+
something
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { registerExampleRoutes } from "@app/routes/example.ts";
|
|
2
|
+
import log from "app/util/customLogger.ts";
|
|
3
|
+
import { YinzerFlow, type HandlerCallback } from "yinzerflow";
|
|
4
|
+
|
|
5
|
+
const app = new YinzerFlow({
|
|
6
|
+
cors: {
|
|
7
|
+
enabled: true,
|
|
8
|
+
origin: "*",
|
|
9
|
+
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
10
|
+
allowedHeaders: ["Content-Type", "Authorization", "x-session-id"],
|
|
11
|
+
preflightContinue: false,
|
|
12
|
+
},
|
|
13
|
+
bodyParser: {
|
|
14
|
+
json: {
|
|
15
|
+
maxDepth: 500
|
|
16
|
+
},
|
|
17
|
+
|
|
18
|
+
},
|
|
19
|
+
logLevel: "info",
|
|
20
|
+
networkLogs: true,
|
|
21
|
+
// logger: log,
|
|
22
|
+
// networkLogger: log,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
app.onError((ctx) => {
|
|
28
|
+
ctx.response.setStatusCode(404);
|
|
29
|
+
return {
|
|
30
|
+
success: false,
|
|
31
|
+
message: "Something went wrong",
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
app.onNotFound((ctx) => {
|
|
36
|
+
ctx.response.setStatusCode(404);
|
|
37
|
+
return {
|
|
38
|
+
success: false,
|
|
39
|
+
message: "Not Found",
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
app.beforeAll([
|
|
44
|
+
(_) => {
|
|
45
|
+
console.log("======== beforeAll ========");
|
|
46
|
+
},
|
|
47
|
+
]);
|
|
48
|
+
|
|
49
|
+
app.afterAll([
|
|
50
|
+
(ctx) => {
|
|
51
|
+
ctx.response.setStatusCode(202);
|
|
52
|
+
console.log("afterAll");
|
|
53
|
+
},
|
|
54
|
+
]);
|
|
55
|
+
|
|
56
|
+
const login: HandlerCallback<{
|
|
57
|
+
response: {
|
|
58
|
+
success: boolean;
|
|
59
|
+
message: string;
|
|
60
|
+
};
|
|
61
|
+
body: {
|
|
62
|
+
email: string;
|
|
63
|
+
password: string;
|
|
64
|
+
};
|
|
65
|
+
}> = (({ request }) => {
|
|
66
|
+
const { email, password } = request.body;
|
|
67
|
+
console.log("========================");
|
|
68
|
+
console.log(email, password);
|
|
69
|
+
console.log("========================");
|
|
70
|
+
|
|
71
|
+
return {
|
|
72
|
+
success: true,
|
|
73
|
+
message: "Login successful",
|
|
74
|
+
};
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
app.post("/login", login);
|
|
78
|
+
|
|
79
|
+
app.group("/api", (app) => {
|
|
80
|
+
app.post(
|
|
81
|
+
"/get/:id",
|
|
82
|
+
((ctx) => {
|
|
83
|
+
console.log("route handler");
|
|
84
|
+
console.log(ctx.request);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
message: false
|
|
88
|
+
};
|
|
89
|
+
}),
|
|
90
|
+
{
|
|
91
|
+
beforeHooks: [
|
|
92
|
+
(ctx) => {
|
|
93
|
+
ctx.response.setStatusCode(200);
|
|
94
|
+
console.log("beforeRoute");
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
afterHooks: [
|
|
98
|
+
() => {
|
|
99
|
+
console.log("afterRoute");
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// Register example routes
|
|
107
|
+
registerExampleRoutes(app);
|
|
108
|
+
|
|
109
|
+
// Start the server AFTER defining all routes
|
|
110
|
+
await app.listen();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { exampleHandler } from '@app/handlers/example.ts';
|
|
2
|
+
import type { YinzerFlow } from 'yinzerflow';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Register example routes on the main app instance
|
|
6
|
+
*/
|
|
7
|
+
export const registerExampleRoutes = (app: YinzerFlow) => {
|
|
8
|
+
/**
|
|
9
|
+
* Get all users
|
|
10
|
+
*/
|
|
11
|
+
app.post('/get-users', exampleHandler);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Add an override reason for a user to be able to add a payout method
|
|
15
|
+
* This is used to remove all requirements bypassing any checks such as the $20 minimum balance
|
|
16
|
+
*/
|
|
17
|
+
// app.post('/user/update', updateUserController);
|
|
18
|
+
};
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
|
|
2
|
+
import type { TransformableInfo } from 'logform';
|
|
3
|
+
import { addColors, createLogger, format, transports } from 'winston';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Logging levels in winston conform to the severity ordering specified by RFC5424: severity of all levels is assumed to
|
|
7
|
+
* be numerically ascending from most important to least important.
|
|
8
|
+
*/
|
|
9
|
+
enum LogLevel {
|
|
10
|
+
CRITICAL = 0,
|
|
11
|
+
ERROR = 1,
|
|
12
|
+
WARN = 2,
|
|
13
|
+
INFO = 3,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
enum LogColor {
|
|
17
|
+
CRITICAL = 'bold white redBG',
|
|
18
|
+
ERROR = 'bold red',
|
|
19
|
+
WARN = 'bold yellow',
|
|
20
|
+
INFO = 'bold cyan',
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Transports {
|
|
24
|
+
console: transports.ConsoleTransportInstance;
|
|
25
|
+
file: transports.FileTransportInstance;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Define the levels that winston will use. This is used later on
|
|
30
|
+
* when we create the logger
|
|
31
|
+
*/
|
|
32
|
+
const logLevels = {
|
|
33
|
+
critical: LogLevel.CRITICAL,
|
|
34
|
+
error: LogLevel.ERROR,
|
|
35
|
+
warn: LogLevel.WARN,
|
|
36
|
+
info: LogLevel.INFO,
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* This lets us add custom logging levels and colors so that
|
|
43
|
+
* winston recognizes them
|
|
44
|
+
*/
|
|
45
|
+
addColors({
|
|
46
|
+
critical: LogColor.CRITICAL,
|
|
47
|
+
error: LogColor.ERROR,
|
|
48
|
+
warn: LogColor.WARN,
|
|
49
|
+
info: LogColor.INFO,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Safely formats a message for logging, handling different types appropriately
|
|
54
|
+
*/
|
|
55
|
+
const formatLogMessage = (message: unknown, meta: Record<string, unknown> = {}): string => {
|
|
56
|
+
let formattedMessage = '';
|
|
57
|
+
|
|
58
|
+
if (typeof message === 'string') {
|
|
59
|
+
formattedMessage = message;
|
|
60
|
+
} else if (typeof message === 'object' && message !== null) {
|
|
61
|
+
formattedMessage = JSON.stringify(message);
|
|
62
|
+
} else {
|
|
63
|
+
formattedMessage = String(message);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const metaKeys = Object.keys(meta);
|
|
67
|
+
if (metaKeys.length === 0) {
|
|
68
|
+
return formattedMessage;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const formattedMeta = JSON.stringify(meta, null, 2);
|
|
72
|
+
|
|
73
|
+
return `${formattedMessage}\n${formattedMeta}`;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* This is the format that will be used for all logs except File logs
|
|
78
|
+
*/
|
|
79
|
+
const formatter = format.combine(
|
|
80
|
+
/** Adds color to the format */
|
|
81
|
+
format.colorize(),
|
|
82
|
+
|
|
83
|
+
/** Adds timestamp to the format */
|
|
84
|
+
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
85
|
+
|
|
86
|
+
/** Format the way the log is output to the console */
|
|
87
|
+
format.printf((info: TransformableInfo) => {
|
|
88
|
+
const { timestamp, level, message, ...meta } = info;
|
|
89
|
+
const formattedMessage = formatLogMessage(message, Object.keys(meta).length > 0 ? meta : undefined);
|
|
90
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
91
|
+
return `${timestamp} [${level}]: ${formattedMessage}`;
|
|
92
|
+
}),
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* This is the format that will be used for all File logs
|
|
97
|
+
* The only difference is that we don't want to add color because it will mess up the log file
|
|
98
|
+
*/
|
|
99
|
+
const fileFormatter = format.combine(
|
|
100
|
+
/** Adds timestamp to the format */
|
|
101
|
+
format.timestamp({ format: 'YYYY-MM-DD HH:mm:ss' }),
|
|
102
|
+
|
|
103
|
+
/** Format the way the log is output to the console */
|
|
104
|
+
format.printf((info: TransformableInfo) => {
|
|
105
|
+
const { timestamp, level, message, ...meta } = info;
|
|
106
|
+
const formattedMessage = formatLogMessage(message, Object.keys(meta).length > 0 ? meta : undefined);
|
|
107
|
+
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
108
|
+
return `${timestamp} [${level}]: ${formattedMessage}`;
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
/** ======================================== Defineing Transports ============================================ */
|
|
113
|
+
const transporters: Transports = {
|
|
114
|
+
console: new transports.Console({
|
|
115
|
+
format: formatter,
|
|
116
|
+
}),
|
|
117
|
+
file: new transports.File({
|
|
118
|
+
filename: 'storage/logs/info.log',
|
|
119
|
+
format: fileFormatter,
|
|
120
|
+
}),
|
|
121
|
+
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Creates and configures the logger based on the current configuration
|
|
126
|
+
*/
|
|
127
|
+
const createConfiguredLogger = (): ReturnType<typeof createLogger> => {
|
|
128
|
+
const transportsToUse = [];
|
|
129
|
+
for (const transport of ['console', 'file']) {
|
|
130
|
+
switch (transport) {
|
|
131
|
+
case 'console':
|
|
132
|
+
transportsToUse.push(transporters.console);
|
|
133
|
+
break;
|
|
134
|
+
case 'file':
|
|
135
|
+
transportsToUse.push(transporters.file);
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
transportsToUse.push(transporters.console);
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return createLogger({
|
|
144
|
+
level: 'info',
|
|
145
|
+
transports: transportsToUse,
|
|
146
|
+
levels: logLevels,
|
|
147
|
+
});
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// Create the logger with the configured settings
|
|
151
|
+
const log = createConfiguredLogger();
|
|
152
|
+
|
|
153
|
+
export default log;
|
|
154
|
+
export const networkLog = createLogger({
|
|
155
|
+
level: 'info',
|
|
156
|
+
transports: [
|
|
157
|
+
new transports.Console({
|
|
158
|
+
format: formatter,
|
|
159
|
+
}),
|
|
160
|
+
new transports.File({
|
|
161
|
+
filename: 'storage/logs/info.log',
|
|
162
|
+
format: fileFormatter,
|
|
163
|
+
}),
|
|
164
|
+
],
|
|
165
|
+
levels: logLevels,
|
|
166
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
user: bun
|
|
4
|
+
image: oven/bun:1.2.17
|
|
5
|
+
working_dir: /app/yinzerflow-local
|
|
6
|
+
volumes:
|
|
7
|
+
- .:/app/yinzerflow-local
|
|
8
|
+
- ../yinzerflow/lib:/app/yinzerflow/lib
|
|
9
|
+
ports:
|
|
10
|
+
- "5005:5000"
|
|
11
|
+
command: sh -c "bun install && tail -f /dev/null"
|
|
12
|
+
tty: true
|
|
13
|
+
stdin_open: true
|
|
14
|
+
|
|
15
|
+
benchmark:
|
|
16
|
+
image: williamyeh/wrk
|
|
17
|
+
restart: no
|
|
18
|
+
command: ["-t20", "-c400", "-d10s", "http://api:5000/status"]
|
|
19
|
+
depends_on:
|
|
20
|
+
- app
|
|
21
|
+
networks:
|
|
22
|
+
- yinzerflow-network
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "yinzerflow-local",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"start": "bun --watch app/index.ts"
|
|
6
|
+
},
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"logform": "^2.7.0",
|
|
9
|
+
"winston": "^3.17.0",
|
|
10
|
+
"yinzerflow": "file:../yinzerflow/lib"
|
|
11
|
+
},
|
|
12
|
+
"devDependencies": {
|
|
13
|
+
"bun-types": "1.2.17",
|
|
14
|
+
"typescript": "^5.8.2"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"composite": true,
|
|
4
|
+
"target": "ESNext",
|
|
5
|
+
"useDefineForClassFields": true,
|
|
6
|
+
"module": "NodeNext",
|
|
7
|
+
"lib": ["ESNext"],
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"types": ["bun-types"],
|
|
10
|
+
|
|
11
|
+
/* Bundler mode */
|
|
12
|
+
"moduleResolution": "NodeNext",
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"allowImportingTsExtensions": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"isolatedModules": true,
|
|
17
|
+
"noEmit": true,
|
|
18
|
+
"jsx": "preserve",
|
|
19
|
+
"alwaysStrict": true,
|
|
20
|
+
"declaration": true,
|
|
21
|
+
"outDir": "lib",
|
|
22
|
+
"declarationDir": "lib",
|
|
23
|
+
"verbatimModuleSyntax": true,
|
|
24
|
+
"baseUrl": ".",
|
|
25
|
+
"paths": {
|
|
26
|
+
"@app/*": ["./app/*"]
|
|
27
|
+
},
|
|
28
|
+
"sourceMap": false,
|
|
29
|
+
"removeComments": true,
|
|
30
|
+
"declarationMap": true,
|
|
31
|
+
|
|
32
|
+
/* Linting */
|
|
33
|
+
"strict": true,
|
|
34
|
+
"noUnusedLocals": true,
|
|
35
|
+
"noUnusedParameters": true,
|
|
36
|
+
"noFallthroughCasesInSwitch": true,
|
|
37
|
+
"allowSyntheticDefaultImports": true,
|
|
38
|
+
"allowJs": false,
|
|
39
|
+
"forceConsistentCasingInFileNames": true,
|
|
40
|
+
"noImplicitAny": true,
|
|
41
|
+
"noImplicitReturns": true,
|
|
42
|
+
"noImplicitThis": true,
|
|
43
|
+
"noImplicitOverride": true,
|
|
44
|
+
"strictFunctionTypes": true,
|
|
45
|
+
"strictNullChecks": true,
|
|
46
|
+
"strictPropertyInitialization": true,
|
|
47
|
+
"allowUnreachableCode": false,
|
|
48
|
+
"allowUnusedLabels": false,
|
|
49
|
+
"exactOptionalPropertyTypes": true,
|
|
50
|
+
"noUncheckedIndexedAccess": true,
|
|
51
|
+
"strictBindCallApply": true
|
|
52
|
+
},
|
|
53
|
+
"include": ["app/**/*"]
|
|
54
|
+
}
|
package/index.d.ts
CHANGED
|
@@ -578,6 +578,12 @@ export interface InternalServerConfiguration {
|
|
|
578
578
|
* Server connection options
|
|
579
579
|
*/
|
|
580
580
|
connectionOptions: InternalConnectionOptions;
|
|
581
|
+
/**
|
|
582
|
+
* Automatic graceful shutdown configuration
|
|
583
|
+
* When enabled, YinzerFlow automatically sets up signal handlers for SIGTERM and SIGINT
|
|
584
|
+
* @default true
|
|
585
|
+
*/
|
|
586
|
+
autoGracefulShutdown: boolean;
|
|
581
587
|
}
|
|
582
588
|
export interface Setup {
|
|
583
589
|
get: InternalSetupMethod;
|
|
@@ -637,13 +643,13 @@ declare class SetupImpl implements InternalSetupImpl {
|
|
|
637
643
|
readonly _routeRegistry: RouteRegistryImpl;
|
|
638
644
|
readonly _hooks: HookRegistryImpl;
|
|
639
645
|
constructor(customConfiguration?: ServerConfiguration);
|
|
640
|
-
get(path: string, handler: HandlerCallback
|
|
641
|
-
head(path: string, handler: HandlerCallback
|
|
642
|
-
post(path: string, handler: HandlerCallback
|
|
643
|
-
put(path: string, handler: HandlerCallback
|
|
644
|
-
patch(path: string, handler: HandlerCallback
|
|
645
|
-
delete(path: string, handler: HandlerCallback
|
|
646
|
-
options(path: string, handler: HandlerCallback
|
|
646
|
+
get(path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions): void;
|
|
647
|
+
head(path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions): void;
|
|
648
|
+
post(path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions): void;
|
|
649
|
+
put(path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions): void;
|
|
650
|
+
patch(path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions): void;
|
|
651
|
+
delete(path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions): void;
|
|
652
|
+
options(path: string, handler: HandlerCallback<any>, options?: InternalRouteRegistryOptions): void;
|
|
647
653
|
group(prefix: string, callback: (group: Record<Lowercase<InternalHttpMethod>, InternalSetupMethod>) => void, options?: InternalRouteRegistryOptions): void;
|
|
648
654
|
beforeAll(handlers: Array<HandlerCallback>, options?: InternalGlobalHookOptions): void;
|
|
649
655
|
afterAll(handlers: Array<HandlerCallback>, options?: InternalGlobalHookOptions): void;
|
|
@@ -664,6 +670,7 @@ export declare class YinzerFlow extends SetupImpl {
|
|
|
664
670
|
port: number | undefined;
|
|
665
671
|
host: string | undefined;
|
|
666
672
|
};
|
|
673
|
+
private _setupGracefulShutdown;
|
|
667
674
|
}
|
|
668
675
|
export declare const log: {
|
|
669
676
|
setLogLevel: (level: "error" | "info" | "off" | "warn") => void;
|