@web-ts-toolkit/express-runtime 0.7.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/LICENSE +201 -0
- package/README.md +337 -0
- package/cli.js +636 -0
- package/index.d.mts +189 -0
- package/index.d.ts +189 -0
- package/index.js +259 -0
- package/index.mjs +220 -0
- package/package.json +52 -0
package/index.mjs
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import http from "http";
|
|
3
|
+
import express from "express";
|
|
4
|
+
import serverless from "serverless-http";
|
|
5
|
+
var defaultLogger = {
|
|
6
|
+
log: (...args) => console.log(...args),
|
|
7
|
+
error: (...args) => console.error(...args),
|
|
8
|
+
debug: (...args) => console.debug(...args)
|
|
9
|
+
};
|
|
10
|
+
function applySettings(app, options) {
|
|
11
|
+
if (options.disablePoweredBy !== false) {
|
|
12
|
+
app.disable("x-powered-by");
|
|
13
|
+
}
|
|
14
|
+
app.set("etag", options.etag ?? false);
|
|
15
|
+
app.set("trust proxy", options.trustProxy ?? false);
|
|
16
|
+
}
|
|
17
|
+
function applyMiddlewareList(app, list) {
|
|
18
|
+
if (list) {
|
|
19
|
+
for (const mw of list) {
|
|
20
|
+
app.use(mw);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function applyRouters(app, options) {
|
|
25
|
+
const mounts = [];
|
|
26
|
+
if (options.router) mounts.push(options.router);
|
|
27
|
+
if (options.routers) mounts.push(...options.routers);
|
|
28
|
+
for (const mount of mounts) {
|
|
29
|
+
const path = typeof mount.path === "function" ? mount.path() : mount.path;
|
|
30
|
+
app.use(path, mount.handler);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
function createExpressApp(options = {}) {
|
|
34
|
+
const app = express();
|
|
35
|
+
applySettings(app, options);
|
|
36
|
+
applyMiddlewareList(app, options.preMiddleware);
|
|
37
|
+
if (options.json !== false) {
|
|
38
|
+
app.use(express.json(options.json ?? { limit: "1mb" }));
|
|
39
|
+
}
|
|
40
|
+
if (options.urlencoded !== false) {
|
|
41
|
+
app.use(express.urlencoded(options.urlencoded ?? { extended: false, limit: "1mb" }));
|
|
42
|
+
}
|
|
43
|
+
applyMiddlewareList(app, options.middleware);
|
|
44
|
+
applyRouters(app, options);
|
|
45
|
+
applyMiddlewareList(app, options.postMiddleware);
|
|
46
|
+
if (options.finalize) {
|
|
47
|
+
options.finalize(app);
|
|
48
|
+
}
|
|
49
|
+
if (options.errorHandler) {
|
|
50
|
+
app.use(options.errorHandler);
|
|
51
|
+
}
|
|
52
|
+
return app;
|
|
53
|
+
}
|
|
54
|
+
function defaultRequestHook(req, maxBodyBytes = 1024 * 1024, logger = defaultLogger) {
|
|
55
|
+
if (!req.body || !Buffer.isBuffer(req.body)) {
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (req.body.length > maxBodyBytes) {
|
|
59
|
+
logger.debug?.(" Skipping oversized serverless body for content-type parsing");
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const bodyStr = req.body.toString("utf8");
|
|
64
|
+
const contentType = (req.headers?.["content-type"] ?? "").toLowerCase();
|
|
65
|
+
if (contentType.startsWith("application/json")) {
|
|
66
|
+
req.body = JSON.parse(bodyStr);
|
|
67
|
+
} else {
|
|
68
|
+
req.body = bodyStr;
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
logger.error("Failed to parse serverless request body:", error);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function createServerlessHandler(app, options = {}) {
|
|
75
|
+
const logger = options.logger ?? defaultLogger;
|
|
76
|
+
const maxBodyBytes = options.maxBodyBytes ?? 1024 * 1024;
|
|
77
|
+
const requestHook = options.request ?? ((req) => defaultRequestHook(req, maxBodyBytes, logger));
|
|
78
|
+
const baseOptions = {
|
|
79
|
+
...options.serverlessOptions ?? {},
|
|
80
|
+
request: requestHook
|
|
81
|
+
};
|
|
82
|
+
if (options.response) {
|
|
83
|
+
baseOptions.response = options.response;
|
|
84
|
+
}
|
|
85
|
+
const apiHandler = serverless(app, baseOptions);
|
|
86
|
+
let initialized = null;
|
|
87
|
+
const ensureInit = () => {
|
|
88
|
+
if (!initialized) {
|
|
89
|
+
logger.debug?.("Serverless cold start: running init");
|
|
90
|
+
initialized = options.init ? options.init() : Promise.resolve();
|
|
91
|
+
}
|
|
92
|
+
return initialized;
|
|
93
|
+
};
|
|
94
|
+
const handler = async (event, context) => {
|
|
95
|
+
await ensureInit();
|
|
96
|
+
return apiHandler(event, context);
|
|
97
|
+
};
|
|
98
|
+
handler.reset = () => {
|
|
99
|
+
initialized = null;
|
|
100
|
+
};
|
|
101
|
+
return handler;
|
|
102
|
+
}
|
|
103
|
+
var DEFAULT_SIGNALS = ["SIGINT", "SIGTERM"];
|
|
104
|
+
var DEFAULT_SHUTDOWN_TIMEOUT = 5e3;
|
|
105
|
+
function normalizePort(val) {
|
|
106
|
+
if (val === void 0 || val === "") {
|
|
107
|
+
const envPort = process.env.PORT;
|
|
108
|
+
if (envPort === void 0 || envPort === "") {
|
|
109
|
+
return 8080;
|
|
110
|
+
}
|
|
111
|
+
val = envPort;
|
|
112
|
+
}
|
|
113
|
+
if (typeof val === "string") {
|
|
114
|
+
const parsed = Number(val);
|
|
115
|
+
if (Number.isNaN(parsed)) {
|
|
116
|
+
return val;
|
|
117
|
+
}
|
|
118
|
+
val = parsed;
|
|
119
|
+
}
|
|
120
|
+
if (!Number.isFinite(val) || val < 0 || val > 65535) {
|
|
121
|
+
throw new Error(`Invalid port: ${String(val)}`);
|
|
122
|
+
}
|
|
123
|
+
return val;
|
|
124
|
+
}
|
|
125
|
+
function defaultOnError(error, port, logger) {
|
|
126
|
+
if (error.syscall !== "listen") {
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
const bind = typeof port === "string" ? `Pipe ${port}` : `Port ${port}`;
|
|
130
|
+
if (error.code === "EACCES") {
|
|
131
|
+
logger.error(`${bind} requires elevated privileges`);
|
|
132
|
+
process.exit(1);
|
|
133
|
+
} else if (error.code === "EADDRINUSE") {
|
|
134
|
+
logger.error(`${bind} is already in use`);
|
|
135
|
+
process.exit(1);
|
|
136
|
+
} else {
|
|
137
|
+
throw error;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function startLocalServer(app, options = {}) {
|
|
141
|
+
const logger = options.logger ?? defaultLogger;
|
|
142
|
+
const port = normalizePort(options.port);
|
|
143
|
+
const host = options.host ?? process.env.HOST ?? "0.0.0.0";
|
|
144
|
+
const shutdownTimeout = options.shutdownTimeout ?? DEFAULT_SHUTDOWN_TIMEOUT;
|
|
145
|
+
const server = http.createServer(app);
|
|
146
|
+
app.set("port", port);
|
|
147
|
+
const onError = (error) => {
|
|
148
|
+
if (options.onError) {
|
|
149
|
+
options.onError(error);
|
|
150
|
+
} else {
|
|
151
|
+
defaultOnError(error, port, logger);
|
|
152
|
+
}
|
|
153
|
+
};
|
|
154
|
+
const onListening = () => {
|
|
155
|
+
const addr = server.address();
|
|
156
|
+
const bind = typeof addr === "string" ? `pipe ${addr}` : `port ${addr?.port}`;
|
|
157
|
+
logger.log(`Server running at http://${host}:${port}/ (${bind})`);
|
|
158
|
+
options.onListening?.();
|
|
159
|
+
};
|
|
160
|
+
server.on("error", onError);
|
|
161
|
+
server.on("listening", onListening);
|
|
162
|
+
const shutdown = async () => {
|
|
163
|
+
logger.log("Shutting down...");
|
|
164
|
+
try {
|
|
165
|
+
if (options.onShutdown) {
|
|
166
|
+
await options.onShutdown();
|
|
167
|
+
}
|
|
168
|
+
} catch (err) {
|
|
169
|
+
logger.error("onShutdown hook failed:", err);
|
|
170
|
+
}
|
|
171
|
+
await new Promise((resolve) => {
|
|
172
|
+
const timer = setTimeout(() => {
|
|
173
|
+
server.closeAllConnections?.();
|
|
174
|
+
resolve();
|
|
175
|
+
}, shutdownTimeout);
|
|
176
|
+
server.close((err) => {
|
|
177
|
+
clearTimeout(timer);
|
|
178
|
+
if (err) {
|
|
179
|
+
logger.error("Server close error:", err);
|
|
180
|
+
}
|
|
181
|
+
resolve();
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
if (options.exitAfterShutdown) {
|
|
185
|
+
process.exit(0);
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
if (options.signals !== false) {
|
|
189
|
+
const list = options.signals === void 0 || options.signals === true ? DEFAULT_SIGNALS : options.signals;
|
|
190
|
+
for (const sig of list) {
|
|
191
|
+
process.once(sig, () => void shutdown());
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const start = async () => {
|
|
195
|
+
try {
|
|
196
|
+
if (options.init) {
|
|
197
|
+
await options.init();
|
|
198
|
+
}
|
|
199
|
+
if (typeof port === "number") {
|
|
200
|
+
server.listen(port, host);
|
|
201
|
+
} else {
|
|
202
|
+
server.listen(port);
|
|
203
|
+
}
|
|
204
|
+
} catch (err) {
|
|
205
|
+
server.emit("error", err);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
void start();
|
|
209
|
+
return {
|
|
210
|
+
server,
|
|
211
|
+
shutdown
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
export {
|
|
215
|
+
createExpressApp,
|
|
216
|
+
createServerlessHandler,
|
|
217
|
+
defaultRequestHook,
|
|
218
|
+
normalizePort,
|
|
219
|
+
startLocalServer
|
|
220
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@web-ts-toolkit/express-runtime",
|
|
3
|
+
"description": "Express app factory plus serverless handler and local dev server helpers",
|
|
4
|
+
"homepage": "https://web-ts-toolkit.pages.dev/docs/packages/express-runtime",
|
|
5
|
+
"version": "0.7.0",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"keywords": [
|
|
8
|
+
"express",
|
|
9
|
+
"serverless",
|
|
10
|
+
"local-server",
|
|
11
|
+
"dev-server",
|
|
12
|
+
"runtime",
|
|
13
|
+
"http"
|
|
14
|
+
],
|
|
15
|
+
"main": "./index.js",
|
|
16
|
+
"module": "./index.mjs",
|
|
17
|
+
"types": "./index.d.ts",
|
|
18
|
+
"bin": {
|
|
19
|
+
"web-ts-toolkit-express-runtime": "./cli.js"
|
|
20
|
+
},
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./index.d.ts",
|
|
24
|
+
"import": "./index.mjs",
|
|
25
|
+
"require": "./index.js",
|
|
26
|
+
"default": "./index.js"
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=20"
|
|
31
|
+
},
|
|
32
|
+
"dependencies": {
|
|
33
|
+
"serverless-http": "^4.0.0"
|
|
34
|
+
},
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"express": ">=5.0.0"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"**/*",
|
|
40
|
+
"!**/*.map"
|
|
41
|
+
],
|
|
42
|
+
"author": "Junmin Ahn",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/egose/web-ts-toolkit/issues"
|
|
45
|
+
},
|
|
46
|
+
"license": "Apache-2.0",
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/egose/web-ts-toolkit.git",
|
|
50
|
+
"directory": "packages/express-runtime"
|
|
51
|
+
}
|
|
52
|
+
}
|