@spfn/core 0.1.0-alpha.88 → 0.2.0-beta.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/README.md +1046 -384
- package/dist/boss-D-fGtVgM.d.ts +187 -0
- package/dist/cache/index.d.ts +13 -33
- package/dist/cache/index.js +14 -703
- package/dist/cache/index.js.map +1 -1
- package/dist/codegen/index.d.ts +167 -17
- package/dist/codegen/index.js +76 -1419
- package/dist/codegen/index.js.map +1 -1
- package/dist/config/index.d.ts +1191 -0
- package/dist/config/index.js +264 -0
- package/dist/config/index.js.map +1 -0
- package/dist/db/index.d.ts +728 -59
- package/dist/db/index.js +1028 -1225
- package/dist/db/index.js.map +1 -1
- package/dist/env/index.d.ts +579 -308
- package/dist/env/index.js +438 -930
- package/dist/env/index.js.map +1 -1
- package/dist/errors/index.d.ts +417 -29
- package/dist/errors/index.js +359 -98
- package/dist/errors/index.js.map +1 -1
- package/dist/event/index.d.ts +108 -0
- package/dist/event/index.js +122 -0
- package/dist/event/index.js.map +1 -0
- package/dist/job/index.d.ts +172 -0
- package/dist/job/index.js +361 -0
- package/dist/job/index.js.map +1 -0
- package/dist/logger/index.d.ts +20 -79
- package/dist/logger/index.js +82 -387
- package/dist/logger/index.js.map +1 -1
- package/dist/middleware/index.d.ts +2 -11
- package/dist/middleware/index.js +49 -703
- package/dist/middleware/index.js.map +1 -1
- package/dist/nextjs/index.d.ts +120 -0
- package/dist/nextjs/index.js +416 -0
- package/dist/nextjs/index.js.map +1 -0
- package/dist/{client/nextjs/index.d.ts → nextjs/server.d.ts} +288 -262
- package/dist/nextjs/server.js +568 -0
- package/dist/nextjs/server.js.map +1 -0
- package/dist/route/index.d.ts +686 -25
- package/dist/route/index.js +440 -1287
- package/dist/route/index.js.map +1 -1
- package/dist/route/types.d.ts +38 -0
- package/dist/route/types.js +3 -0
- package/dist/route/types.js.map +1 -0
- package/dist/server/index.d.ts +201 -67
- package/dist/server/index.js +921 -3182
- package/dist/server/index.js.map +1 -1
- package/dist/types-BGl4QL1w.d.ts +77 -0
- package/dist/types-DRG2XMTR.d.ts +157 -0
- package/package.json +52 -47
- package/dist/auto-loader-JFaZ9gON.d.ts +0 -80
- package/dist/client/index.d.ts +0 -358
- package/dist/client/index.js +0 -357
- package/dist/client/index.js.map +0 -1
- package/dist/client/nextjs/index.js +0 -371
- package/dist/client/nextjs/index.js.map +0 -1
- package/dist/codegen/generators/index.d.ts +0 -19
- package/dist/codegen/generators/index.js +0 -1404
- package/dist/codegen/generators/index.js.map +0 -1
- package/dist/database-errors-BNNmLTJE.d.ts +0 -86
- package/dist/events/index.d.ts +0 -183
- package/dist/events/index.js +0 -77
- package/dist/events/index.js.map +0 -1
- package/dist/index-DHiAqhKv.d.ts +0 -101
- package/dist/index.d.ts +0 -8
- package/dist/index.js +0 -3674
- package/dist/index.js.map +0 -1
- package/dist/types/index.d.ts +0 -121
- package/dist/types/index.js +0 -38
- package/dist/types/index.js.map +0 -1
- package/dist/types-BXibIEyj.d.ts +0 -60
package/dist/route/index.js
CHANGED
|
@@ -1,1354 +1,507 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { join, dirname, relative } from 'path';
|
|
3
|
-
import { readdir, stat } from 'fs/promises';
|
|
1
|
+
import { logger } from '@spfn/core/logger';
|
|
4
2
|
import { Value } from '@sinclair/typebox/value';
|
|
5
|
-
import {
|
|
3
|
+
import { ValidationError } from '@spfn/core/errors';
|
|
6
4
|
import { Type } from '@sinclair/typebox';
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
var
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// src/logger/formatters.ts
|
|
33
|
-
function isSensitiveKey(key) {
|
|
34
|
-
const lowerKey = key.toLowerCase();
|
|
35
|
-
return SENSITIVE_KEYS.some((sensitive) => lowerKey.includes(sensitive));
|
|
36
|
-
}
|
|
37
|
-
function maskSensitiveData(data) {
|
|
38
|
-
if (data === null || data === void 0) {
|
|
39
|
-
return data;
|
|
40
|
-
}
|
|
41
|
-
if (Array.isArray(data)) {
|
|
42
|
-
return data.map((item) => maskSensitiveData(item));
|
|
43
|
-
}
|
|
44
|
-
if (typeof data === "object") {
|
|
45
|
-
const masked = {};
|
|
46
|
-
for (const [key, value] of Object.entries(data)) {
|
|
47
|
-
if (isSensitiveKey(key)) {
|
|
48
|
-
masked[key] = MASKED_VALUE;
|
|
49
|
-
} else if (typeof value === "object" && value !== null) {
|
|
50
|
-
masked[key] = maskSensitiveData(value);
|
|
51
|
-
} else {
|
|
52
|
-
masked[key] = value;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
return masked;
|
|
56
|
-
}
|
|
57
|
-
return data;
|
|
58
|
-
}
|
|
59
|
-
function formatTimestamp(date) {
|
|
60
|
-
return date.toISOString();
|
|
61
|
-
}
|
|
62
|
-
function formatTimestampHuman(date) {
|
|
63
|
-
const year = date.getFullYear();
|
|
64
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
65
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
66
|
-
const hours = String(date.getHours()).padStart(2, "0");
|
|
67
|
-
const minutes = String(date.getMinutes()).padStart(2, "0");
|
|
68
|
-
const seconds = String(date.getSeconds()).padStart(2, "0");
|
|
69
|
-
const ms = String(date.getMilliseconds()).padStart(3, "0");
|
|
70
|
-
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}.${ms}`;
|
|
71
|
-
}
|
|
72
|
-
function formatError(error) {
|
|
73
|
-
const lines = [];
|
|
74
|
-
lines.push(`${error.name}: ${error.message}`);
|
|
75
|
-
if (error.stack) {
|
|
76
|
-
const stackLines = error.stack.split("\n").slice(1);
|
|
77
|
-
lines.push(...stackLines);
|
|
78
|
-
}
|
|
79
|
-
return lines.join("\n");
|
|
80
|
-
}
|
|
81
|
-
function formatConsole(metadata, colorize = true) {
|
|
82
|
-
const parts = [];
|
|
83
|
-
const timestamp = formatTimestampHuman(metadata.timestamp);
|
|
84
|
-
if (colorize) {
|
|
85
|
-
parts.push(`${COLORS.gray}[${timestamp}]${COLORS.reset}`);
|
|
86
|
-
} else {
|
|
87
|
-
parts.push(`[${timestamp}]`);
|
|
88
|
-
}
|
|
89
|
-
if (metadata.module) {
|
|
90
|
-
if (colorize) {
|
|
91
|
-
parts.push(`${COLORS.dim}[module=${metadata.module}]${COLORS.reset}`);
|
|
92
|
-
} else {
|
|
93
|
-
parts.push(`[module=${metadata.module}]`);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
if (metadata.context && Object.keys(metadata.context).length > 0) {
|
|
97
|
-
Object.entries(metadata.context).forEach(([key, value]) => {
|
|
98
|
-
let valueStr;
|
|
99
|
-
if (typeof value === "string") {
|
|
100
|
-
valueStr = value;
|
|
101
|
-
} else if (typeof value === "object" && value !== null) {
|
|
102
|
-
try {
|
|
103
|
-
valueStr = JSON.stringify(value);
|
|
104
|
-
} catch (error) {
|
|
105
|
-
valueStr = "[circular]";
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
valueStr = String(value);
|
|
109
|
-
}
|
|
110
|
-
if (colorize) {
|
|
111
|
-
parts.push(`${COLORS.dim}[${key}=${valueStr}]${COLORS.reset}`);
|
|
112
|
-
} else {
|
|
113
|
-
parts.push(`[${key}=${valueStr}]`);
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
}
|
|
117
|
-
const levelStr = metadata.level.toUpperCase();
|
|
118
|
-
if (colorize) {
|
|
119
|
-
const color = COLORS[metadata.level];
|
|
120
|
-
parts.push(`${color}(${levelStr})${COLORS.reset}:`);
|
|
121
|
-
} else {
|
|
122
|
-
parts.push(`(${levelStr}):`);
|
|
123
|
-
}
|
|
124
|
-
if (colorize) {
|
|
125
|
-
parts.push(`${COLORS.bright}${metadata.message}${COLORS.reset}`);
|
|
126
|
-
} else {
|
|
127
|
-
parts.push(metadata.message);
|
|
128
|
-
}
|
|
129
|
-
let output = parts.join(" ");
|
|
130
|
-
if (metadata.error) {
|
|
131
|
-
output += "\n" + formatError(metadata.error);
|
|
132
|
-
}
|
|
133
|
-
return output;
|
|
134
|
-
}
|
|
135
|
-
function formatJSON(metadata) {
|
|
136
|
-
const obj = {
|
|
137
|
-
timestamp: formatTimestamp(metadata.timestamp),
|
|
138
|
-
level: metadata.level,
|
|
139
|
-
message: metadata.message
|
|
140
|
-
};
|
|
141
|
-
if (metadata.module) {
|
|
142
|
-
obj.module = metadata.module;
|
|
6
|
+
// src/route/route-builder.ts
|
|
7
|
+
var RouteBuilder = class _RouteBuilder {
|
|
8
|
+
_method;
|
|
9
|
+
_path;
|
|
10
|
+
_input;
|
|
11
|
+
_interceptor;
|
|
12
|
+
_middlewares;
|
|
13
|
+
_skipMiddlewares;
|
|
14
|
+
/**
|
|
15
|
+
* Create a new RouteBuilder with copied properties and optional overrides
|
|
16
|
+
*/
|
|
17
|
+
clone(overrides) {
|
|
18
|
+
const builder = new _RouteBuilder();
|
|
19
|
+
builder._method = this._method;
|
|
20
|
+
builder._path = this._path;
|
|
21
|
+
builder._input = overrides?.input ?? this._input;
|
|
22
|
+
builder._interceptor = overrides?.interceptor ?? this._interceptor;
|
|
23
|
+
builder._middlewares = overrides?.middlewares ?? this._middlewares;
|
|
24
|
+
builder._skipMiddlewares = overrides?.skipMiddlewares ?? this._skipMiddlewares;
|
|
25
|
+
return builder;
|
|
143
26
|
}
|
|
144
|
-
|
|
145
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Define input schemas
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```ts
|
|
32
|
+
* route.get('/users/:id')
|
|
33
|
+
* .input({
|
|
34
|
+
* params: Type.Object({ id: Type.String() }),
|
|
35
|
+
* query: Type.Object({ page: Type.Number() }),
|
|
36
|
+
* headers: Type.Object({ authorization: Type.String() })
|
|
37
|
+
* })
|
|
38
|
+
* .handler(async (c) => {
|
|
39
|
+
* const { params, query, headers } = await c.data();
|
|
40
|
+
* // params = { id: string }
|
|
41
|
+
* // query = { page: number }
|
|
42
|
+
* // headers = { authorization: string }
|
|
43
|
+
* })
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
46
|
+
input(input) {
|
|
47
|
+
return this.clone({ input });
|
|
146
48
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
49
|
+
/**
|
|
50
|
+
* Define fields injected by interceptors
|
|
51
|
+
*
|
|
52
|
+
* These fields are:
|
|
53
|
+
* - Available in the handler (merged with input)
|
|
54
|
+
* - Excluded from client types (codegen uses only input)
|
|
55
|
+
* - Not validated by route input schema (injected by middleware)
|
|
56
|
+
*
|
|
57
|
+
* Use this when middleware/interceptors add fields to the request
|
|
58
|
+
* before it reaches the handler.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```ts
|
|
62
|
+
* // Auth interceptor injects crypto key fields
|
|
63
|
+
* route.post('/_auth/login')
|
|
64
|
+
* .input({
|
|
65
|
+
* body: Type.Object({
|
|
66
|
+
* email: Type.String(),
|
|
67
|
+
* password: Type.String()
|
|
68
|
+
* })
|
|
69
|
+
* })
|
|
70
|
+
* .interceptor({
|
|
71
|
+
* body: Type.Object({
|
|
72
|
+
* publicKey: Type.String(),
|
|
73
|
+
* keyId: Type.String(),
|
|
74
|
+
* fingerprint: Type.String()
|
|
75
|
+
* })
|
|
76
|
+
* })
|
|
77
|
+
* .handler(async (c) => {
|
|
78
|
+
* const { body } = await c.data();
|
|
79
|
+
* // body type: { email, password, publicKey, keyId, fingerprint }
|
|
80
|
+
* // Client only sees: { email, password }
|
|
81
|
+
* return loginService(body);
|
|
82
|
+
* });
|
|
83
|
+
* ```
|
|
84
|
+
*/
|
|
85
|
+
interceptor(interceptor) {
|
|
86
|
+
return this.clone({ interceptor });
|
|
153
87
|
}
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
"session_id",
|
|
177
|
-
"privatekey",
|
|
178
|
-
"private_key",
|
|
179
|
-
"creditcard",
|
|
180
|
-
"credit_card",
|
|
181
|
-
"cardnumber",
|
|
182
|
-
"card_number",
|
|
183
|
-
"cvv",
|
|
184
|
-
"ssn",
|
|
185
|
-
"pin"
|
|
186
|
-
];
|
|
187
|
-
MASKED_VALUE = "***MASKED***";
|
|
188
|
-
COLORS = {
|
|
189
|
-
reset: "\x1B[0m",
|
|
190
|
-
bright: "\x1B[1m",
|
|
191
|
-
dim: "\x1B[2m",
|
|
192
|
-
// 로그 레벨 컬러
|
|
193
|
-
debug: "\x1B[36m",
|
|
194
|
-
// cyan
|
|
195
|
-
info: "\x1B[32m",
|
|
196
|
-
// green
|
|
197
|
-
warn: "\x1B[33m",
|
|
198
|
-
// yellow
|
|
199
|
-
error: "\x1B[31m",
|
|
200
|
-
// red
|
|
201
|
-
fatal: "\x1B[35m",
|
|
202
|
-
// magenta
|
|
203
|
-
// 추가 컬러
|
|
204
|
-
gray: "\x1B[90m"
|
|
205
|
-
};
|
|
88
|
+
/**
|
|
89
|
+
* Add middlewares to the route
|
|
90
|
+
*
|
|
91
|
+
* Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
|
|
92
|
+
* Named middlewares that are already registered globally will be automatically
|
|
93
|
+
* deduplicated to prevent double execution.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```ts
|
|
97
|
+
* import { authenticate } from '@spfn/auth/server/middleware';
|
|
98
|
+
*
|
|
99
|
+
* // With NamedMiddleware (auto-deduped if registered globally)
|
|
100
|
+
* route.get('/users')
|
|
101
|
+
* .use([authenticate, RateLimitMiddleware()])
|
|
102
|
+
*
|
|
103
|
+
* // With regular middleware handlers
|
|
104
|
+
* route.get('/users')
|
|
105
|
+
* .use([AuthMiddleware(), RateLimitMiddleware()])
|
|
106
|
+
* ```
|
|
107
|
+
*/
|
|
108
|
+
middleware(middlewares) {
|
|
109
|
+
return this.clone({ middlewares });
|
|
206
110
|
}
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
* Create child logger (per module)
|
|
230
|
-
*/
|
|
231
|
-
child(module) {
|
|
232
|
-
return new _Logger({
|
|
233
|
-
...this.config,
|
|
234
|
-
module
|
|
235
|
-
});
|
|
236
|
-
}
|
|
237
|
-
/**
|
|
238
|
-
* Debug log
|
|
239
|
-
*/
|
|
240
|
-
debug(message, context) {
|
|
241
|
-
this.log("debug", message, void 0, context);
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Info log
|
|
245
|
-
*/
|
|
246
|
-
info(message, context) {
|
|
247
|
-
this.log("info", message, void 0, context);
|
|
248
|
-
}
|
|
249
|
-
warn(message, errorOrContext, context) {
|
|
250
|
-
if (errorOrContext instanceof Error) {
|
|
251
|
-
this.log("warn", message, errorOrContext, context);
|
|
252
|
-
} else {
|
|
253
|
-
this.log("warn", message, void 0, errorOrContext);
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
error(message, errorOrContext, context) {
|
|
257
|
-
if (errorOrContext instanceof Error) {
|
|
258
|
-
this.log("error", message, errorOrContext, context);
|
|
259
|
-
} else {
|
|
260
|
-
this.log("error", message, void 0, errorOrContext);
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
fatal(message, errorOrContext, context) {
|
|
264
|
-
if (errorOrContext instanceof Error) {
|
|
265
|
-
this.log("fatal", message, errorOrContext, context);
|
|
266
|
-
} else {
|
|
267
|
-
this.log("fatal", message, void 0, errorOrContext);
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Log processing (internal)
|
|
272
|
-
*/
|
|
273
|
-
log(level, message, error, context) {
|
|
274
|
-
if (LOG_LEVEL_PRIORITY[level] < LOG_LEVEL_PRIORITY[this.config.level]) {
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
const metadata = {
|
|
278
|
-
timestamp: /* @__PURE__ */ new Date(),
|
|
279
|
-
level,
|
|
280
|
-
message,
|
|
281
|
-
module: this.module,
|
|
282
|
-
error,
|
|
283
|
-
// Mask sensitive information in context to prevent credential leaks
|
|
284
|
-
context: context ? maskSensitiveData(context) : void 0
|
|
285
|
-
};
|
|
286
|
-
this.processTransports(metadata);
|
|
287
|
-
}
|
|
288
|
-
/**
|
|
289
|
-
* Process Transports
|
|
290
|
-
*/
|
|
291
|
-
processTransports(metadata) {
|
|
292
|
-
const promises = this.config.transports.filter((transport) => transport.enabled).map((transport) => this.safeTransportLog(transport, metadata));
|
|
293
|
-
Promise.all(promises).catch((error) => {
|
|
294
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
295
|
-
process.stderr.write(`[Logger] Transport error: ${errorMessage}
|
|
296
|
-
`);
|
|
297
|
-
});
|
|
298
|
-
}
|
|
299
|
-
/**
|
|
300
|
-
* Transport log (error-safe)
|
|
301
|
-
*/
|
|
302
|
-
async safeTransportLog(transport, metadata) {
|
|
303
|
-
try {
|
|
304
|
-
await transport.log(metadata);
|
|
305
|
-
} catch (error) {
|
|
306
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
307
|
-
process.stderr.write(`[Logger] Transport "${transport.name}" failed: ${errorMessage}
|
|
308
|
-
`);
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
/**
|
|
312
|
-
* Close all Transports
|
|
313
|
-
*/
|
|
314
|
-
async close() {
|
|
315
|
-
const closePromises = this.config.transports.filter((transport) => transport.close).map((transport) => transport.close());
|
|
316
|
-
await Promise.all(closePromises);
|
|
317
|
-
}
|
|
318
|
-
};
|
|
111
|
+
/**
|
|
112
|
+
* Add middlewares to the route (alias for `.middleware()`)
|
|
113
|
+
*
|
|
114
|
+
* Accepts both regular middleware handlers and named middlewares (NamedMiddleware).
|
|
115
|
+
* Named middlewares that are already registered globally will be automatically
|
|
116
|
+
* deduplicated to prevent double execution.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { authenticate } from '@spfn/auth/server/middleware';
|
|
121
|
+
*
|
|
122
|
+
* // With NamedMiddleware (auto-deduped if registered globally)
|
|
123
|
+
* route.get('/users')
|
|
124
|
+
* .use([authenticate, RateLimitMiddleware()])
|
|
125
|
+
*
|
|
126
|
+
* // With regular middleware handlers
|
|
127
|
+
* route.get('/users')
|
|
128
|
+
* .use([AuthMiddleware(), RateLimitMiddleware()])
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
use(middlewares) {
|
|
132
|
+
return this.middleware(middlewares);
|
|
319
133
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
console.error(message);
|
|
348
|
-
} else {
|
|
349
|
-
console.log(message);
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
};
|
|
134
|
+
/**
|
|
135
|
+
* Skip server-level named middlewares
|
|
136
|
+
*
|
|
137
|
+
* Useful for public endpoints that should bypass auth or rate limiting
|
|
138
|
+
*
|
|
139
|
+
* @param middlewareNames - Array of middleware names to skip, or '*' to skip all
|
|
140
|
+
*
|
|
141
|
+
* @example
|
|
142
|
+
* ```ts
|
|
143
|
+
* // Skip specific middlewares
|
|
144
|
+
* route.get('/health')
|
|
145
|
+
* .skip(['auth', 'rateLimit'])
|
|
146
|
+
* .handler(async (c) => c.json({ status: 'ok' }));
|
|
147
|
+
*
|
|
148
|
+
* // Skip only auth (still apply rate limiting)
|
|
149
|
+
* route.get('/public-data')
|
|
150
|
+
* .skip(['auth'])
|
|
151
|
+
* .handler(async (c) => { ... });
|
|
152
|
+
*
|
|
153
|
+
* // Skip all middlewares
|
|
154
|
+
* route.get('/public-health')
|
|
155
|
+
* .skip('*')
|
|
156
|
+
* .handler(async (c) => c.json({ status: 'ok' }));
|
|
157
|
+
* ```
|
|
158
|
+
*/
|
|
159
|
+
skip(middlewareNames) {
|
|
160
|
+
return this.clone({ skipMiddlewares: middlewareNames });
|
|
353
161
|
}
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
if (!this.enabled) {
|
|
381
|
-
return;
|
|
382
|
-
}
|
|
383
|
-
if (LOG_LEVEL_PRIORITY[metadata.level] < LOG_LEVEL_PRIORITY[this.level]) {
|
|
384
|
-
return;
|
|
385
|
-
}
|
|
386
|
-
const message = formatJSON(metadata);
|
|
387
|
-
const filename = this.getLogFilename(metadata.timestamp);
|
|
388
|
-
if (this.currentFilename !== filename) {
|
|
389
|
-
await this.rotateStream(filename);
|
|
390
|
-
await this.cleanOldFiles();
|
|
391
|
-
} else if (this.currentFilename) {
|
|
392
|
-
await this.checkAndRotateBySize();
|
|
393
|
-
}
|
|
394
|
-
if (this.currentStream) {
|
|
395
|
-
return new Promise((resolve, reject) => {
|
|
396
|
-
this.currentStream.write(message + "\n", "utf-8", (error) => {
|
|
397
|
-
if (error) {
|
|
398
|
-
process.stderr.write(`[FileTransport] Failed to write log: ${error.message}
|
|
399
|
-
`);
|
|
400
|
-
reject(error);
|
|
401
|
-
} else {
|
|
402
|
-
resolve();
|
|
403
|
-
}
|
|
404
|
-
});
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
/**
|
|
409
|
-
* 스트림 교체 (날짜 변경 시)
|
|
410
|
-
*/
|
|
411
|
-
async rotateStream(filename) {
|
|
412
|
-
if (this.currentStream) {
|
|
413
|
-
await this.closeStream();
|
|
414
|
-
}
|
|
415
|
-
const filepath = join(this.logDir, filename);
|
|
416
|
-
this.currentStream = createWriteStream(filepath, {
|
|
417
|
-
flags: "a",
|
|
418
|
-
// append mode
|
|
419
|
-
encoding: "utf-8"
|
|
420
|
-
});
|
|
421
|
-
this.currentFilename = filename;
|
|
422
|
-
this.currentStream.on("error", (error) => {
|
|
423
|
-
process.stderr.write(`[FileTransport] Stream error: ${error.message}
|
|
424
|
-
`);
|
|
425
|
-
this.currentStream = null;
|
|
426
|
-
this.currentFilename = null;
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
/**
|
|
430
|
-
* 현재 스트림 닫기
|
|
431
|
-
*/
|
|
432
|
-
async closeStream() {
|
|
433
|
-
if (!this.currentStream) {
|
|
434
|
-
return;
|
|
435
|
-
}
|
|
436
|
-
return new Promise((resolve, reject) => {
|
|
437
|
-
this.currentStream.end((error) => {
|
|
438
|
-
if (error) {
|
|
439
|
-
reject(error);
|
|
440
|
-
} else {
|
|
441
|
-
this.currentStream = null;
|
|
442
|
-
this.currentFilename = null;
|
|
443
|
-
resolve();
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
});
|
|
447
|
-
}
|
|
448
|
-
/**
|
|
449
|
-
* 파일 크기 체크 및 크기 기반 로테이션
|
|
450
|
-
*/
|
|
451
|
-
async checkAndRotateBySize() {
|
|
452
|
-
if (!this.currentFilename) {
|
|
453
|
-
return;
|
|
454
|
-
}
|
|
455
|
-
const filepath = join(this.logDir, this.currentFilename);
|
|
456
|
-
if (!existsSync(filepath)) {
|
|
457
|
-
return;
|
|
458
|
-
}
|
|
459
|
-
try {
|
|
460
|
-
const stats = statSync(filepath);
|
|
461
|
-
if (stats.size >= this.maxFileSize) {
|
|
462
|
-
await this.rotateBySize();
|
|
463
|
-
}
|
|
464
|
-
} catch (error) {
|
|
465
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
466
|
-
process.stderr.write(`[FileTransport] Failed to check file size: ${errorMessage}
|
|
467
|
-
`);
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
/**
|
|
471
|
-
* 크기 기반 로테이션 수행
|
|
472
|
-
* 예: 2025-01-01.log -> 2025-01-01.1.log, 2025-01-01.1.log -> 2025-01-01.2.log
|
|
473
|
-
*/
|
|
474
|
-
async rotateBySize() {
|
|
475
|
-
if (!this.currentFilename) {
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
await this.closeStream();
|
|
479
|
-
const baseName = this.currentFilename.replace(/\.log$/, "");
|
|
480
|
-
const files = readdirSync(this.logDir);
|
|
481
|
-
const relatedFiles = files.filter((file) => file.startsWith(baseName) && file.endsWith(".log")).sort().reverse();
|
|
482
|
-
for (const file of relatedFiles) {
|
|
483
|
-
const match = file.match(/\.(\d+)\.log$/);
|
|
484
|
-
if (match) {
|
|
485
|
-
const oldNum = parseInt(match[1], 10);
|
|
486
|
-
const newNum = oldNum + 1;
|
|
487
|
-
const oldPath = join(this.logDir, file);
|
|
488
|
-
const newPath2 = join(this.logDir, `${baseName}.${newNum}.log`);
|
|
489
|
-
try {
|
|
490
|
-
renameSync(oldPath, newPath2);
|
|
491
|
-
} catch (error) {
|
|
492
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
493
|
-
process.stderr.write(`[FileTransport] Failed to rotate file: ${errorMessage}
|
|
494
|
-
`);
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
const currentPath = join(this.logDir, this.currentFilename);
|
|
499
|
-
const newPath = join(this.logDir, `${baseName}.1.log`);
|
|
500
|
-
try {
|
|
501
|
-
if (existsSync(currentPath)) {
|
|
502
|
-
renameSync(currentPath, newPath);
|
|
503
|
-
}
|
|
504
|
-
} catch (error) {
|
|
505
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
506
|
-
process.stderr.write(`[FileTransport] Failed to rotate current file: ${errorMessage}
|
|
507
|
-
`);
|
|
508
|
-
}
|
|
509
|
-
await this.rotateStream(this.currentFilename);
|
|
510
|
-
}
|
|
511
|
-
/**
|
|
512
|
-
* 오래된 로그 파일 정리
|
|
513
|
-
* maxFiles 개수를 초과하는 로그 파일 삭제
|
|
514
|
-
*/
|
|
515
|
-
async cleanOldFiles() {
|
|
516
|
-
try {
|
|
517
|
-
if (!existsSync(this.logDir)) {
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
const files = readdirSync(this.logDir);
|
|
521
|
-
const logFiles = files.filter((file) => file.endsWith(".log")).map((file) => {
|
|
522
|
-
const filepath = join(this.logDir, file);
|
|
523
|
-
const stats = statSync(filepath);
|
|
524
|
-
return { file, mtime: stats.mtime };
|
|
525
|
-
}).sort((a, b) => b.mtime.getTime() - a.mtime.getTime());
|
|
526
|
-
if (logFiles.length > this.maxFiles) {
|
|
527
|
-
const filesToDelete = logFiles.slice(this.maxFiles);
|
|
528
|
-
for (const { file } of filesToDelete) {
|
|
529
|
-
const filepath = join(this.logDir, file);
|
|
530
|
-
try {
|
|
531
|
-
unlinkSync(filepath);
|
|
532
|
-
} catch (error) {
|
|
533
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
534
|
-
process.stderr.write(`[FileTransport] Failed to delete old file "${file}": ${errorMessage}
|
|
535
|
-
`);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
} catch (error) {
|
|
540
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
541
|
-
process.stderr.write(`[FileTransport] Failed to clean old files: ${errorMessage}
|
|
542
|
-
`);
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
/**
|
|
546
|
-
* 날짜별 로그 파일명 생성
|
|
547
|
-
*/
|
|
548
|
-
getLogFilename(date) {
|
|
549
|
-
const year = date.getFullYear();
|
|
550
|
-
const month = String(date.getMonth() + 1).padStart(2, "0");
|
|
551
|
-
const day = String(date.getDate()).padStart(2, "0");
|
|
552
|
-
return `${year}-${month}-${day}.log`;
|
|
553
|
-
}
|
|
554
|
-
async close() {
|
|
555
|
-
await this.closeStream();
|
|
556
|
-
}
|
|
162
|
+
/**
|
|
163
|
+
* Define handler function
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
* ```ts
|
|
167
|
+
* route.get('/users/:id')
|
|
168
|
+
* .input({ params: Type.Object({ id: Type.String() }) })
|
|
169
|
+
* .handler(async (c) => {
|
|
170
|
+
* const { params } = await c.data();
|
|
171
|
+
* const user = await getUser(params.id);
|
|
172
|
+
* return user; // Type inferred!
|
|
173
|
+
* })
|
|
174
|
+
* ```
|
|
175
|
+
*/
|
|
176
|
+
handler(fn) {
|
|
177
|
+
return {
|
|
178
|
+
method: this._method,
|
|
179
|
+
path: this._path,
|
|
180
|
+
input: this._input,
|
|
181
|
+
interceptor: this._interceptor,
|
|
182
|
+
middlewares: this._middlewares,
|
|
183
|
+
skipMiddlewares: this._skipMiddlewares,
|
|
184
|
+
handler: fn,
|
|
185
|
+
_input: {},
|
|
186
|
+
_interceptor: {},
|
|
187
|
+
_response: {}
|
|
557
188
|
};
|
|
558
189
|
}
|
|
559
|
-
}
|
|
560
|
-
function
|
|
561
|
-
return
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
if (isDevelopment) {
|
|
567
|
-
return "debug";
|
|
568
|
-
}
|
|
569
|
-
if (isProduction) {
|
|
570
|
-
return "info";
|
|
571
|
-
}
|
|
572
|
-
return "warn";
|
|
573
|
-
}
|
|
574
|
-
function getConsoleConfig() {
|
|
575
|
-
const isProduction = process.env.NODE_ENV === "production";
|
|
576
|
-
return {
|
|
577
|
-
level: "debug",
|
|
578
|
-
enabled: true,
|
|
579
|
-
colorize: !isProduction
|
|
580
|
-
// Dev: colored output, Production: plain text
|
|
190
|
+
};
|
|
191
|
+
function createMethodRoute(method) {
|
|
192
|
+
return (path) => {
|
|
193
|
+
const builder = new RouteBuilder();
|
|
194
|
+
builder._method = method;
|
|
195
|
+
builder._path = path;
|
|
196
|
+
return builder;
|
|
581
197
|
};
|
|
582
198
|
}
|
|
583
|
-
|
|
584
|
-
|
|
199
|
+
var route = {
|
|
200
|
+
get: createMethodRoute("GET"),
|
|
201
|
+
post: createMethodRoute("POST"),
|
|
202
|
+
put: createMethodRoute("PUT"),
|
|
203
|
+
patch: createMethodRoute("PATCH"),
|
|
204
|
+
delete: createMethodRoute("DELETE")
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/route/router.ts
|
|
208
|
+
function createRouterInstance(routes, packageRouters = [], globalMiddlewares = []) {
|
|
585
209
|
return {
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
throw new Error(`Failed to create log directory "${dirPath}": ${errorMessage}`);
|
|
210
|
+
routes,
|
|
211
|
+
_routes: routes,
|
|
212
|
+
_packageRouters: packageRouters,
|
|
213
|
+
_globalMiddlewares: globalMiddlewares,
|
|
214
|
+
packages(routers) {
|
|
215
|
+
const newPackageRouters = [...this._packageRouters, ...routers];
|
|
216
|
+
for (const pkgRouter of routers) {
|
|
217
|
+
if (pkgRouter._packageRouters?.length > 0) {
|
|
218
|
+
newPackageRouters.push(...pkgRouter._packageRouters);
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
return createRouterInstance(this.routes, newPackageRouters, this._globalMiddlewares);
|
|
222
|
+
},
|
|
223
|
+
use(middlewares) {
|
|
224
|
+
return createRouterInstance(this.routes, this._packageRouters, [...this._globalMiddlewares, ...middlewares]);
|
|
602
225
|
}
|
|
603
|
-
}
|
|
604
|
-
try {
|
|
605
|
-
accessSync(dirPath, constants.W_OK);
|
|
606
|
-
} catch {
|
|
607
|
-
throw new Error(`Log directory "${dirPath}" is not writable. Please check permissions.`);
|
|
608
|
-
}
|
|
609
|
-
const testFile = join(dirPath, ".logger-write-test");
|
|
610
|
-
try {
|
|
611
|
-
writeFileSync(testFile, "test", "utf-8");
|
|
612
|
-
unlinkSync(testFile);
|
|
613
|
-
} catch (error) {
|
|
614
|
-
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
615
|
-
throw new Error(`Cannot write to log directory "${dirPath}": ${errorMessage}`);
|
|
616
|
-
}
|
|
226
|
+
};
|
|
617
227
|
}
|
|
618
|
-
function
|
|
619
|
-
|
|
620
|
-
return;
|
|
621
|
-
}
|
|
622
|
-
const logDir = process.env.LOG_DIR;
|
|
623
|
-
if (!logDir) {
|
|
624
|
-
throw new Error(
|
|
625
|
-
"LOG_DIR environment variable is required when LOGGER_FILE_ENABLED=true. Example: LOG_DIR=/var/log/myapp"
|
|
626
|
-
);
|
|
627
|
-
}
|
|
628
|
-
validateDirectoryWritable(logDir);
|
|
228
|
+
function defineRouter(routes) {
|
|
229
|
+
return createRouterInstance(routes);
|
|
629
230
|
}
|
|
630
|
-
function
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
231
|
+
function validateField(schema, rawValue, fieldName) {
|
|
232
|
+
if (!schema) {
|
|
233
|
+
return {};
|
|
234
|
+
}
|
|
235
|
+
const converted = Value.Convert(schema, rawValue);
|
|
236
|
+
const errors = [...Value.Errors(schema, converted)];
|
|
237
|
+
if (errors.length > 0) {
|
|
238
|
+
throw new ValidationError({
|
|
239
|
+
message: `Invalid ${fieldName}`,
|
|
240
|
+
fields: errors.map((e) => ({
|
|
241
|
+
path: e.path,
|
|
242
|
+
message: e.message,
|
|
243
|
+
value: e.value
|
|
244
|
+
}))
|
|
245
|
+
});
|
|
639
246
|
}
|
|
247
|
+
return converted;
|
|
640
248
|
}
|
|
641
|
-
function
|
|
642
|
-
const
|
|
643
|
-
const
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
const missingFields = [];
|
|
651
|
-
if (!smtpHost) missingFields.push("SMTP_HOST");
|
|
652
|
-
if (!smtpPort) missingFields.push("SMTP_PORT");
|
|
653
|
-
if (!emailFrom) missingFields.push("EMAIL_FROM");
|
|
654
|
-
if (!emailTo) missingFields.push("EMAIL_TO");
|
|
655
|
-
if (missingFields.length > 0) {
|
|
656
|
-
throw new Error(
|
|
657
|
-
`Email transport configuration incomplete. Missing: ${missingFields.join(", ")}. Either set all required fields or remove all email configuration.`
|
|
658
|
-
);
|
|
659
|
-
}
|
|
660
|
-
const port = parseInt(smtpPort, 10);
|
|
661
|
-
if (isNaN(port) || port < 1 || port > 65535) {
|
|
662
|
-
throw new Error(
|
|
663
|
-
`Invalid SMTP_PORT: "${smtpPort}". Must be a number between 1 and 65535.`
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
667
|
-
if (!emailRegex.test(emailFrom)) {
|
|
668
|
-
throw new Error(`Invalid EMAIL_FROM format: "${emailFrom}"`);
|
|
669
|
-
}
|
|
670
|
-
const recipients = emailTo.split(",").map((e) => e.trim());
|
|
671
|
-
for (const email of recipients) {
|
|
672
|
-
if (!emailRegex.test(email)) {
|
|
673
|
-
throw new Error(`Invalid email address in EMAIL_TO: "${email}"`);
|
|
249
|
+
function extractQueryParams(c) {
|
|
250
|
+
const url = new URL(c.req.url);
|
|
251
|
+
const queryObj = {};
|
|
252
|
+
url.searchParams.forEach((v, k) => {
|
|
253
|
+
const existing = queryObj[k];
|
|
254
|
+
if (existing) {
|
|
255
|
+
queryObj[k] = Array.isArray(existing) ? [...existing, v] : [existing, v];
|
|
256
|
+
} else {
|
|
257
|
+
queryObj[k] = v;
|
|
674
258
|
}
|
|
675
|
-
}
|
|
259
|
+
});
|
|
260
|
+
return queryObj;
|
|
261
|
+
}
|
|
262
|
+
function extractHeaders(c) {
|
|
263
|
+
const rawHeaders = {};
|
|
264
|
+
c.req.raw.headers.forEach((value, key) => {
|
|
265
|
+
rawHeaders[key.toLowerCase()] = value;
|
|
266
|
+
});
|
|
267
|
+
return rawHeaders;
|
|
676
268
|
}
|
|
677
|
-
function
|
|
678
|
-
const
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
269
|
+
function extractCookies(c) {
|
|
270
|
+
const cookieHeader = c.req.header("cookie");
|
|
271
|
+
const rawCookies = {};
|
|
272
|
+
if (cookieHeader) {
|
|
273
|
+
cookieHeader.split(";").forEach((cookie) => {
|
|
274
|
+
const [key, value] = cookie.trim().split("=");
|
|
275
|
+
if (key && value) {
|
|
276
|
+
rawCookies[key] = decodeURIComponent(value);
|
|
277
|
+
}
|
|
278
|
+
});
|
|
683
279
|
}
|
|
280
|
+
return rawCookies;
|
|
684
281
|
}
|
|
685
|
-
function
|
|
282
|
+
async function parseJsonBody(c) {
|
|
686
283
|
try {
|
|
687
|
-
|
|
688
|
-
validateFileConfig();
|
|
689
|
-
validateSlackConfig();
|
|
690
|
-
validateEmailConfig();
|
|
284
|
+
return await c.req.json();
|
|
691
285
|
} catch (error) {
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
286
|
+
throw new ValidationError({
|
|
287
|
+
message: "Invalid JSON body",
|
|
288
|
+
fields: [{
|
|
289
|
+
path: "/",
|
|
290
|
+
message: "Failed to parse JSON",
|
|
291
|
+
value: error instanceof Error ? error.message : "Unknown error"
|
|
292
|
+
}]
|
|
293
|
+
});
|
|
696
294
|
}
|
|
697
295
|
}
|
|
698
|
-
var init_config = __esm({
|
|
699
|
-
"src/logger/config.ts"() {
|
|
700
|
-
}
|
|
701
|
-
});
|
|
702
296
|
|
|
703
|
-
// src/
|
|
704
|
-
function
|
|
705
|
-
|
|
706
|
-
const consoleConfig = getConsoleConfig();
|
|
707
|
-
transports.push(new ConsoleTransport(consoleConfig));
|
|
708
|
-
const fileConfig = getFileConfig();
|
|
709
|
-
if (fileConfig.enabled) {
|
|
710
|
-
transports.push(new FileTransport(fileConfig));
|
|
711
|
-
}
|
|
712
|
-
return transports;
|
|
297
|
+
// src/route/register-routes.ts
|
|
298
|
+
function isRouter(value) {
|
|
299
|
+
return value !== null && typeof value === "object" && "routes" in value && "_routes" in value;
|
|
713
300
|
}
|
|
714
|
-
function
|
|
715
|
-
|
|
716
|
-
return new Logger({
|
|
717
|
-
level: getDefaultLogLevel(),
|
|
718
|
-
transports: initializeTransports()
|
|
719
|
-
});
|
|
301
|
+
function isRouteDef(value) {
|
|
302
|
+
return value !== null && typeof value === "object" && "handler" in value;
|
|
720
303
|
}
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
"src/logger/factory.ts"() {
|
|
724
|
-
init_logger();
|
|
725
|
-
init_console();
|
|
726
|
-
init_file();
|
|
727
|
-
init_config();
|
|
728
|
-
logger = initializeLogger();
|
|
729
|
-
}
|
|
730
|
-
});
|
|
731
|
-
|
|
732
|
-
// src/logger/index.ts
|
|
733
|
-
var init_logger2 = __esm({
|
|
734
|
-
"src/logger/index.ts"() {
|
|
735
|
-
init_factory();
|
|
736
|
-
init_logger();
|
|
737
|
-
}
|
|
738
|
-
});
|
|
739
|
-
|
|
740
|
-
// src/route/function-routes.ts
|
|
741
|
-
var function_routes_exports = {};
|
|
742
|
-
__export(function_routes_exports, {
|
|
743
|
-
discoverFunctionRoutes: () => discoverFunctionRoutes
|
|
744
|
-
});
|
|
745
|
-
function discoverFunctionRoutes(cwd = process.cwd()) {
|
|
746
|
-
const functions = [];
|
|
747
|
-
const nodeModulesPath = join(cwd, "node_modules");
|
|
748
|
-
try {
|
|
749
|
-
const projectPkgPath = join(cwd, "package.json");
|
|
750
|
-
const projectPkg = JSON.parse(readFileSync(projectPkgPath, "utf-8"));
|
|
751
|
-
const dependencies = {
|
|
752
|
-
...projectPkg.dependencies,
|
|
753
|
-
...projectPkg.devDependencies
|
|
754
|
-
};
|
|
755
|
-
for (const [packageName] of Object.entries(dependencies)) {
|
|
756
|
-
if (!packageName.startsWith("@spfn/") && !packageName.startsWith("spfn-")) {
|
|
757
|
-
continue;
|
|
758
|
-
}
|
|
759
|
-
try {
|
|
760
|
-
const pkgPath = join(nodeModulesPath, ...packageName.split("/"), "package.json");
|
|
761
|
-
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
|
762
|
-
if (pkg.spfn?.routes?.dir) {
|
|
763
|
-
const { dir } = pkg.spfn.routes;
|
|
764
|
-
const prefix = pkg.spfn.prefix;
|
|
765
|
-
const packagePath = dirname(pkgPath);
|
|
766
|
-
const routesDir = join(packagePath, dir);
|
|
767
|
-
functions.push({
|
|
768
|
-
packageName,
|
|
769
|
-
routesDir,
|
|
770
|
-
packagePath,
|
|
771
|
-
prefix
|
|
772
|
-
// Include prefix in function info
|
|
773
|
-
});
|
|
774
|
-
routeLogger.debug("Discovered function routes", {
|
|
775
|
-
package: packageName,
|
|
776
|
-
dir,
|
|
777
|
-
prefix: prefix || "(none)"
|
|
778
|
-
});
|
|
779
|
-
}
|
|
780
|
-
} catch (error) {
|
|
781
|
-
}
|
|
782
|
-
}
|
|
783
|
-
} catch (error) {
|
|
784
|
-
routeLogger.warn("Failed to discover function routes", {
|
|
785
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
786
|
-
});
|
|
787
|
-
}
|
|
788
|
-
return functions;
|
|
304
|
+
function isNamedMiddleware(value) {
|
|
305
|
+
return value !== null && typeof value === "object" && "name" in value && "handler" in value && "_name" in value;
|
|
789
306
|
}
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
this.routesDir = routesDir;
|
|
804
|
-
this.debug = debug;
|
|
805
|
-
this.middlewares = middlewares;
|
|
806
|
-
}
|
|
807
|
-
routes = [];
|
|
808
|
-
debug;
|
|
809
|
-
middlewares;
|
|
810
|
-
async load(app) {
|
|
811
|
-
const startTime = Date.now();
|
|
812
|
-
const files = await this.scanFiles(this.routesDir);
|
|
813
|
-
if (files.length === 0) {
|
|
814
|
-
routeLogger2.warn("No route files found");
|
|
815
|
-
return this.getStats();
|
|
816
|
-
}
|
|
817
|
-
let failureCount = 0;
|
|
818
|
-
for (const file of files) {
|
|
819
|
-
const success = await this.loadRoute(app, file);
|
|
820
|
-
if (success) ; else {
|
|
821
|
-
failureCount++;
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
const elapsed = Date.now() - startTime;
|
|
825
|
-
const stats = this.getStats();
|
|
826
|
-
if (this.debug) {
|
|
827
|
-
this.logStats(stats, elapsed);
|
|
828
|
-
}
|
|
829
|
-
if (failureCount > 0) {
|
|
830
|
-
routeLogger2.warn("Some routes failed to load", { failureCount });
|
|
831
|
-
}
|
|
832
|
-
return stats;
|
|
833
|
-
}
|
|
834
|
-
/**
|
|
835
|
-
* Load routes from an external directory (e.g., from SPFN function packages)
|
|
836
|
-
* Reads package.json spfn.prefix and mounts routes under that prefix
|
|
837
|
-
*
|
|
838
|
-
* @param app - Hono app instance
|
|
839
|
-
* @param routesDir - Directory containing route handlers
|
|
840
|
-
* @param packageName - Name of the package (for logging)
|
|
841
|
-
* @param prefix - Optional prefix to mount routes under (from package.json spfn.prefix)
|
|
842
|
-
* @returns Route statistics
|
|
843
|
-
*/
|
|
844
|
-
async loadExternalRoutes(app, routesDir, packageName, prefix) {
|
|
845
|
-
const startTime = Date.now();
|
|
846
|
-
const tempRoutesDir = this.routesDir;
|
|
847
|
-
this.routesDir = routesDir;
|
|
848
|
-
const files = await this.scanFiles(routesDir);
|
|
849
|
-
if (files.length === 0) {
|
|
850
|
-
routeLogger2.warn("No route files found", { dir: routesDir, package: packageName });
|
|
851
|
-
this.routesDir = tempRoutesDir;
|
|
852
|
-
return this.getStats();
|
|
853
|
-
}
|
|
854
|
-
let successCount = 0;
|
|
855
|
-
let failureCount = 0;
|
|
856
|
-
for (const file of files) {
|
|
857
|
-
const success = await this.loadRoute(app, file, prefix);
|
|
858
|
-
if (success) {
|
|
859
|
-
successCount++;
|
|
860
|
-
} else {
|
|
861
|
-
failureCount++;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
const elapsed = Date.now() - startTime;
|
|
865
|
-
if (this.debug) {
|
|
866
|
-
routeLogger2.info("External routes loaded", {
|
|
867
|
-
package: packageName,
|
|
868
|
-
prefix: prefix || "/",
|
|
869
|
-
total: successCount,
|
|
870
|
-
failed: failureCount,
|
|
871
|
-
elapsed: `${elapsed}ms`
|
|
307
|
+
function registerRoutes(app, router, namedMiddlewares) {
|
|
308
|
+
const allNamedMiddlewares = [
|
|
309
|
+
...namedMiddlewares ?? [],
|
|
310
|
+
...router._globalMiddlewares.map((mw) => ({ name: mw.name, handler: mw.handler }))
|
|
311
|
+
];
|
|
312
|
+
for (const [name, routeOrRouter] of Object.entries(router.routes)) {
|
|
313
|
+
if (isRouter(routeOrRouter)) {
|
|
314
|
+
registerRoutes(app, routeOrRouter, allNamedMiddlewares);
|
|
315
|
+
} else if (isRouteDef(routeOrRouter)) {
|
|
316
|
+
registerRoute(app, name, routeOrRouter, allNamedMiddlewares);
|
|
317
|
+
} else {
|
|
318
|
+
logger.warn(`Unknown route type for "${name}" - skipping`, {
|
|
319
|
+
type: typeof routeOrRouter
|
|
872
320
|
});
|
|
873
321
|
}
|
|
874
|
-
this.routesDir = tempRoutesDir;
|
|
875
|
-
return this.getStats();
|
|
876
322
|
}
|
|
877
|
-
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
byPriority: { static: 0, dynamic: 0, catchAll: 0 },
|
|
881
|
-
byTag: {},
|
|
882
|
-
routes: this.routes
|
|
883
|
-
};
|
|
884
|
-
for (const route of this.routes) {
|
|
885
|
-
if (route.priority === 1) stats.byPriority.static++;
|
|
886
|
-
else if (route.priority === 2) stats.byPriority.dynamic++;
|
|
887
|
-
else if (route.priority === 3) stats.byPriority.catchAll++;
|
|
888
|
-
if (route.meta?.tags) {
|
|
889
|
-
for (const tag of route.meta.tags) {
|
|
890
|
-
stats.byTag[tag] = (stats.byTag[tag] || 0) + 1;
|
|
891
|
-
}
|
|
892
|
-
}
|
|
893
|
-
}
|
|
894
|
-
return stats;
|
|
895
|
-
}
|
|
896
|
-
async scanFiles(dir, files = []) {
|
|
897
|
-
const entries = await readdir(dir);
|
|
898
|
-
for (const entry of entries) {
|
|
899
|
-
const fullPath = join(dir, entry);
|
|
900
|
-
const fileStat = await stat(fullPath);
|
|
901
|
-
if (fileStat.isDirectory()) {
|
|
902
|
-
await this.scanFiles(fullPath, files);
|
|
903
|
-
} else if (this.isValidRouteFile(entry)) {
|
|
904
|
-
files.push(fullPath);
|
|
905
|
-
}
|
|
323
|
+
if (router._packageRouters && router._packageRouters.length > 0) {
|
|
324
|
+
for (const pkgRouter of router._packageRouters) {
|
|
325
|
+
registerRoutes(app, pkgRouter, allNamedMiddlewares);
|
|
906
326
|
}
|
|
907
|
-
return files;
|
|
908
327
|
}
|
|
909
|
-
|
|
910
|
-
|
|
328
|
+
}
|
|
329
|
+
function registerRoute(app, name, routeDef, namedMiddlewares) {
|
|
330
|
+
const { method, path, input, middlewares = [], skipMiddlewares, handler } = routeDef;
|
|
331
|
+
if (!method || !path) {
|
|
332
|
+
logger.warn(`Route "${name}" is missing method or path - skipping`, {
|
|
333
|
+
method,
|
|
334
|
+
path
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
911
337
|
}
|
|
912
|
-
async
|
|
913
|
-
const
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
return false;
|
|
918
|
-
}
|
|
919
|
-
const hasContractMetas = module.default._contractMetas && module.default._contractMetas.size > 0;
|
|
920
|
-
if (!hasContractMetas) {
|
|
921
|
-
routeLogger2.error("Route must use contract-based routing", {
|
|
922
|
-
file: relativePath,
|
|
923
|
-
hint: "Export contracts using satisfies RouteContract and use app.bind()"
|
|
924
|
-
});
|
|
925
|
-
return false;
|
|
926
|
-
}
|
|
927
|
-
const contractPaths = this.extractContractPaths(module);
|
|
928
|
-
if (prefix) {
|
|
929
|
-
const invalidPaths = contractPaths.filter((path) => !path.startsWith(prefix));
|
|
930
|
-
if (invalidPaths.length > 0) {
|
|
931
|
-
routeLogger2.error("Contract paths must include the package prefix", {
|
|
932
|
-
file: relativePath,
|
|
933
|
-
prefix,
|
|
934
|
-
invalidPaths,
|
|
935
|
-
hint: `Contract paths should start with "${prefix}". Example: path: "${prefix}/labels"`
|
|
936
|
-
});
|
|
937
|
-
return false;
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
this.registerContractBasedMiddlewares(app, contractPaths, module);
|
|
941
|
-
app.route("/", module.default);
|
|
942
|
-
contractPaths.forEach((path) => {
|
|
943
|
-
this.routes.push({
|
|
944
|
-
path,
|
|
945
|
-
// Use contract path as-is (already includes prefix)
|
|
946
|
-
file: relativePath,
|
|
947
|
-
meta: module.meta,
|
|
948
|
-
priority: this.calculateContractPriority(path)
|
|
949
|
-
});
|
|
950
|
-
if (this.debug) {
|
|
951
|
-
const icon = path.includes("*") ? "\u2B50" : path.includes(":") ? "\u{1F538}" : "\u{1F539}";
|
|
952
|
-
routeLogger2.debug(`Registered route: ${path}`, { icon, file: relativePath });
|
|
953
|
-
}
|
|
954
|
-
});
|
|
955
|
-
return true;
|
|
956
|
-
} catch (error) {
|
|
957
|
-
this.categorizeAndLogError(error, relativePath);
|
|
958
|
-
return false;
|
|
338
|
+
const wrappedHandler = async (c) => {
|
|
339
|
+
const context = await createRouteBuilderContext(c, input || {});
|
|
340
|
+
const result = await handler(context);
|
|
341
|
+
if (result instanceof Response) {
|
|
342
|
+
return result;
|
|
959
343
|
}
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
344
|
+
return c.json(result);
|
|
345
|
+
};
|
|
346
|
+
const allMiddlewares = [];
|
|
347
|
+
const registeredNames = /* @__PURE__ */ new Set();
|
|
348
|
+
const registeredHandlers = /* @__PURE__ */ new Set();
|
|
349
|
+
const skipAll = skipMiddlewares === "*";
|
|
350
|
+
if (namedMiddlewares && namedMiddlewares.length > 0) {
|
|
351
|
+
if (skipAll) {
|
|
352
|
+
logger.debug(`\u23ED\uFE0F Skipping all middlewares (*) for route: ${method} ${path}`, { name });
|
|
353
|
+
} else {
|
|
354
|
+
const skipSet = new Set(Array.isArray(skipMiddlewares) ? skipMiddlewares : []);
|
|
355
|
+
for (const middleware of namedMiddlewares) {
|
|
356
|
+
if (!skipSet.has(middleware.name)) {
|
|
357
|
+
allMiddlewares.push(middleware.handler);
|
|
358
|
+
registeredNames.add(middleware.name);
|
|
359
|
+
registeredHandlers.add(middleware.handler);
|
|
360
|
+
} else {
|
|
361
|
+
logger.debug(`\u23ED\uFE0F Skipping middleware '${middleware.name}' for route: ${method} ${path}`, { name });
|
|
968
362
|
}
|
|
969
363
|
}
|
|
970
364
|
}
|
|
971
|
-
return Array.from(paths);
|
|
972
|
-
}
|
|
973
|
-
calculateContractPriority(path) {
|
|
974
|
-
if (path.includes("*")) return 3;
|
|
975
|
-
if (path.includes(":")) return 2;
|
|
976
|
-
return 1;
|
|
977
|
-
}
|
|
978
|
-
validateModule(module, relativePath) {
|
|
979
|
-
if (!module.default) {
|
|
980
|
-
routeLogger2.error("Route must export Hono instance as default", { file: relativePath });
|
|
981
|
-
return false;
|
|
982
|
-
}
|
|
983
|
-
if (typeof module.default.route !== "function") {
|
|
984
|
-
routeLogger2.error("Default export is not a Hono instance", { file: relativePath });
|
|
985
|
-
return false;
|
|
986
|
-
}
|
|
987
|
-
return true;
|
|
988
365
|
}
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
const meta = module.default._contractMetas?.get(key);
|
|
995
|
-
if (meta?.skipMiddlewares) {
|
|
996
|
-
c.set("_skipMiddlewares", meta.skipMiddlewares);
|
|
997
|
-
}
|
|
998
|
-
return next();
|
|
999
|
-
});
|
|
1000
|
-
for (const contractPath of contractPaths) {
|
|
1001
|
-
const middlewarePath = contractPath === "/" ? "/*" : `${contractPath}/*`;
|
|
1002
|
-
for (const middleware of this.middlewares) {
|
|
1003
|
-
app.use(middlewarePath, async (c, next) => {
|
|
1004
|
-
const skipList = c.get("_skipMiddlewares") || [];
|
|
1005
|
-
if (skipList.includes(middleware.name)) {
|
|
1006
|
-
return next();
|
|
1007
|
-
}
|
|
1008
|
-
return middleware.handler(c, next);
|
|
1009
|
-
});
|
|
366
|
+
for (const mw of middlewares) {
|
|
367
|
+
if (isNamedMiddleware(mw)) {
|
|
368
|
+
if (registeredNames.has(mw.name)) {
|
|
369
|
+
logger.debug(`\u{1F504} Skipping duplicate middleware '${mw.name}' for route: ${method} ${path}`, { name });
|
|
370
|
+
continue;
|
|
1010
371
|
}
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
categorizeAndLogError(error, relativePath) {
|
|
1014
|
-
const message = error.message;
|
|
1015
|
-
const stack = error.stack;
|
|
1016
|
-
if (message.includes("Cannot find module") || message.includes("MODULE_NOT_FOUND")) {
|
|
1017
|
-
routeLogger2.error("Missing dependency", {
|
|
1018
|
-
file: relativePath,
|
|
1019
|
-
error: message,
|
|
1020
|
-
hint: "Run: npm install"
|
|
1021
|
-
});
|
|
1022
|
-
} else if (message.includes("SyntaxError") || stack?.includes("SyntaxError")) {
|
|
1023
|
-
routeLogger2.error("Syntax error", {
|
|
1024
|
-
file: relativePath,
|
|
1025
|
-
error: message,
|
|
1026
|
-
...this.debug && stack && {
|
|
1027
|
-
stack: stack.split("\n").slice(0, 5).join("\n")
|
|
1028
|
-
}
|
|
1029
|
-
});
|
|
1030
|
-
} else if (message.includes("Unexpected token")) {
|
|
1031
|
-
routeLogger2.error("Parse error", {
|
|
1032
|
-
file: relativePath,
|
|
1033
|
-
error: message,
|
|
1034
|
-
hint: "Check for syntax errors or invalid TypeScript"
|
|
1035
|
-
});
|
|
372
|
+
registeredNames.add(mw.name);
|
|
373
|
+
allMiddlewares.push(mw.handler);
|
|
1036
374
|
} else {
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
...this.debug && stack && { stack }
|
|
1041
|
-
});
|
|
1042
|
-
}
|
|
1043
|
-
}
|
|
1044
|
-
logStats(stats, elapsed) {
|
|
1045
|
-
const tagCounts = Object.entries(stats.byTag).map(([tag, count]) => `${tag}(${count})`).join(", ");
|
|
1046
|
-
routeLogger2.info("Routes loaded successfully", {
|
|
1047
|
-
total: stats.total,
|
|
1048
|
-
priority: {
|
|
1049
|
-
static: stats.byPriority.static,
|
|
1050
|
-
dynamic: stats.byPriority.dynamic,
|
|
1051
|
-
catchAll: stats.byPriority.catchAll
|
|
1052
|
-
},
|
|
1053
|
-
...tagCounts && { tags: tagCounts },
|
|
1054
|
-
elapsed: `${elapsed}ms`
|
|
1055
|
-
});
|
|
1056
|
-
}
|
|
1057
|
-
};
|
|
1058
|
-
async function loadRoutes(app, options) {
|
|
1059
|
-
const routesDir = options?.routesDir ?? join(process.cwd(), "src", "server", "routes");
|
|
1060
|
-
const debug = options?.debug ?? false;
|
|
1061
|
-
const middlewares = options?.middlewares ?? [];
|
|
1062
|
-
const includeFunctionRoutes = options?.includeFunctionRoutes ?? true;
|
|
1063
|
-
const loader = new AutoRouteLoader(routesDir, debug, middlewares);
|
|
1064
|
-
const stats = await loader.load(app);
|
|
1065
|
-
if (includeFunctionRoutes) {
|
|
1066
|
-
const { discoverFunctionRoutes: discoverFunctionRoutes2 } = await Promise.resolve().then(() => (init_function_routes(), function_routes_exports));
|
|
1067
|
-
const functionRoutes = discoverFunctionRoutes2();
|
|
1068
|
-
if (functionRoutes.length > 0) {
|
|
1069
|
-
routeLogger2.info("Loading function routes", { count: functionRoutes.length });
|
|
1070
|
-
for (const func of functionRoutes) {
|
|
1071
|
-
try {
|
|
1072
|
-
await loader.loadExternalRoutes(app, func.routesDir, func.packageName, func.prefix);
|
|
1073
|
-
routeLogger2.info("Function routes loaded", {
|
|
1074
|
-
package: func.packageName,
|
|
1075
|
-
routesDir: func.routesDir,
|
|
1076
|
-
prefix: func.prefix || "/"
|
|
1077
|
-
});
|
|
1078
|
-
} catch (error) {
|
|
1079
|
-
routeLogger2.error("Failed to load function routes", {
|
|
1080
|
-
package: func.packageName,
|
|
1081
|
-
error: error instanceof Error ? error.message : "Unknown error"
|
|
1082
|
-
});
|
|
1083
|
-
}
|
|
375
|
+
if (registeredHandlers.has(mw)) {
|
|
376
|
+
logger.debug(`\u{1F504} Skipping duplicate middleware handler for route: ${method} ${path}`, { name });
|
|
377
|
+
continue;
|
|
1084
378
|
}
|
|
379
|
+
registeredHandlers.add(mw);
|
|
380
|
+
allMiddlewares.push(mw);
|
|
1085
381
|
}
|
|
1086
382
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
statusCode;
|
|
1093
|
-
details;
|
|
1094
|
-
timestamp;
|
|
1095
|
-
constructor(message, statusCode, details) {
|
|
1096
|
-
super(message);
|
|
1097
|
-
this.name = "HttpError";
|
|
1098
|
-
this.statusCode = statusCode;
|
|
1099
|
-
this.details = details;
|
|
1100
|
-
this.timestamp = /* @__PURE__ */ new Date();
|
|
1101
|
-
Error.captureStackTrace(this, this.constructor);
|
|
1102
|
-
}
|
|
1103
|
-
/**
|
|
1104
|
-
* Serialize error for API response
|
|
1105
|
-
*/
|
|
1106
|
-
toJSON() {
|
|
1107
|
-
return {
|
|
1108
|
-
name: this.name,
|
|
1109
|
-
message: this.message,
|
|
1110
|
-
statusCode: this.statusCode,
|
|
1111
|
-
details: this.details,
|
|
1112
|
-
timestamp: this.timestamp.toISOString()
|
|
1113
|
-
};
|
|
383
|
+
const methodLower = method.toLowerCase();
|
|
384
|
+
if (allMiddlewares.length > 0) {
|
|
385
|
+
app[methodLower](path, ...allMiddlewares, wrappedHandler);
|
|
386
|
+
} else {
|
|
387
|
+
app[methodLower](path, wrappedHandler);
|
|
1114
388
|
}
|
|
1115
|
-
};
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
389
|
+
logger.debug(`Registered route: ${method} ${path}`, { name });
|
|
390
|
+
}
|
|
391
|
+
async function createRouteBuilderContext(c, input) {
|
|
392
|
+
const params = validateField(input.params, c.req.param(), "path parameters");
|
|
393
|
+
const query = validateField(input.query, extractQueryParams(c), "query parameters");
|
|
394
|
+
const headers = validateField(input.headers, extractHeaders(c), "headers");
|
|
395
|
+
const cookies = validateField(input.cookies, extractCookies(c), "cookies");
|
|
396
|
+
let body = {};
|
|
397
|
+
if (input.body) {
|
|
398
|
+
const rawBody = await parseJsonBody(c);
|
|
399
|
+
body = validateField(input.body, rawBody, "request body");
|
|
1120
400
|
}
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
// src/route/bind.ts
|
|
1124
|
-
init_logger2();
|
|
1125
|
-
function bind(contract, handler) {
|
|
1126
|
-
return async (rawContext) => {
|
|
1127
|
-
let params = rawContext.req.param();
|
|
1128
|
-
if (contract.params) {
|
|
1129
|
-
params = Value.Convert(contract.params, params);
|
|
1130
|
-
const errors = [...Value.Errors(contract.params, params)];
|
|
1131
|
-
if (errors.length > 0) {
|
|
1132
|
-
throw new ValidationError(
|
|
1133
|
-
"Invalid path parameters",
|
|
1134
|
-
{
|
|
1135
|
-
fields: errors.map((e) => ({
|
|
1136
|
-
path: e.path,
|
|
1137
|
-
message: e.message,
|
|
1138
|
-
value: e.value
|
|
1139
|
-
}))
|
|
1140
|
-
}
|
|
1141
|
-
);
|
|
1142
|
-
}
|
|
1143
|
-
}
|
|
1144
|
-
const url = new URL(rawContext.req.url);
|
|
1145
|
-
let query = {};
|
|
1146
|
-
url.searchParams.forEach((v, k) => {
|
|
1147
|
-
const existing = query[k];
|
|
1148
|
-
if (existing) {
|
|
1149
|
-
query[k] = Array.isArray(existing) ? [...existing, v] : [existing, v];
|
|
1150
|
-
} else {
|
|
1151
|
-
query[k] = v;
|
|
1152
|
-
}
|
|
1153
|
-
});
|
|
1154
|
-
if (contract.query) {
|
|
1155
|
-
query = Value.Convert(contract.query, query);
|
|
1156
|
-
const errors = [...Value.Errors(contract.query, query)];
|
|
1157
|
-
if (errors.length > 0) {
|
|
1158
|
-
throw new ValidationError(
|
|
1159
|
-
"Invalid query parameters",
|
|
1160
|
-
{
|
|
1161
|
-
fields: errors.map((e) => ({
|
|
1162
|
-
path: e.path,
|
|
1163
|
-
message: e.message,
|
|
1164
|
-
value: e.value
|
|
1165
|
-
}))
|
|
1166
|
-
}
|
|
1167
|
-
);
|
|
1168
|
-
}
|
|
1169
|
-
}
|
|
1170
|
-
const routeContext = {
|
|
401
|
+
return {
|
|
402
|
+
data: async () => ({
|
|
1171
403
|
params,
|
|
1172
404
|
query,
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
},
|
|
1215
|
-
paginated: (data, page, limit, total) => {
|
|
1216
|
-
const response = {
|
|
1217
|
-
success: true,
|
|
1218
|
-
data,
|
|
1219
|
-
meta: {
|
|
1220
|
-
pagination: {
|
|
1221
|
-
page,
|
|
1222
|
-
limit,
|
|
1223
|
-
total,
|
|
1224
|
-
totalPages: Math.ceil(total / limit)
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
};
|
|
1228
|
-
return rawContext.json(response, 200);
|
|
1229
|
-
},
|
|
1230
|
-
raw: rawContext
|
|
1231
|
-
};
|
|
1232
|
-
return handler(routeContext);
|
|
405
|
+
body,
|
|
406
|
+
headers,
|
|
407
|
+
cookies
|
|
408
|
+
}),
|
|
409
|
+
json: (data, status, resHeaders) => {
|
|
410
|
+
return c.json(data, status, resHeaders);
|
|
411
|
+
},
|
|
412
|
+
created: (data, location) => {
|
|
413
|
+
const resHeaders = {};
|
|
414
|
+
if (location) {
|
|
415
|
+
resHeaders["Location"] = location;
|
|
416
|
+
}
|
|
417
|
+
return c.json(data, 201, resHeaders);
|
|
418
|
+
},
|
|
419
|
+
accepted: (data) => {
|
|
420
|
+
if (data === void 0) {
|
|
421
|
+
return c.body(null, 202);
|
|
422
|
+
}
|
|
423
|
+
return c.json(data, 202);
|
|
424
|
+
},
|
|
425
|
+
noContent: () => {
|
|
426
|
+
return c.body(null, 204);
|
|
427
|
+
},
|
|
428
|
+
notModified: () => {
|
|
429
|
+
return c.body(null, 304);
|
|
430
|
+
},
|
|
431
|
+
paginated: (data, page, limit, total) => {
|
|
432
|
+
return c.json({
|
|
433
|
+
items: data,
|
|
434
|
+
pagination: {
|
|
435
|
+
page,
|
|
436
|
+
limit,
|
|
437
|
+
total,
|
|
438
|
+
totalPages: Math.ceil(total / limit)
|
|
439
|
+
}
|
|
440
|
+
}, 200);
|
|
441
|
+
},
|
|
442
|
+
redirect: (url, status) => {
|
|
443
|
+
return c.redirect(url, status);
|
|
444
|
+
},
|
|
445
|
+
raw: c
|
|
1233
446
|
};
|
|
1234
447
|
}
|
|
1235
448
|
|
|
1236
|
-
// src/
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
const errorWithCode = err;
|
|
1246
|
-
const statusCode = errorWithCode.statusCode || 500;
|
|
1247
|
-
const errorType = err.name || "Error";
|
|
1248
|
-
if (enableLogging) {
|
|
1249
|
-
const logLevel = statusCode >= 500 ? "error" : "warn";
|
|
1250
|
-
const logData = {
|
|
1251
|
-
type: errorType,
|
|
1252
|
-
message: err.message,
|
|
1253
|
-
statusCode,
|
|
1254
|
-
path: c.req.path,
|
|
1255
|
-
method: c.req.method
|
|
449
|
+
// src/route/define-middleware.ts
|
|
450
|
+
function defineMiddleware(name, handlerOrFactory) {
|
|
451
|
+
if (typeof handlerOrFactory === "function") {
|
|
452
|
+
const paramCount = handlerOrFactory.length;
|
|
453
|
+
if (paramCount === 2) {
|
|
454
|
+
return {
|
|
455
|
+
name,
|
|
456
|
+
handler: handlerOrFactory,
|
|
457
|
+
_name: name
|
|
1256
458
|
};
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
}
|
|
1272
|
-
|
|
1273
|
-
if (errorWithCode.details) {
|
|
1274
|
-
response.error.details = errorWithCode.details;
|
|
1275
|
-
}
|
|
1276
|
-
if (includeStack) {
|
|
1277
|
-
response.error.stack = err.stack;
|
|
1278
|
-
}
|
|
1279
|
-
return c.json(response, statusCode);
|
|
1280
|
-
};
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
// src/middleware/request-logger.ts
|
|
1284
|
-
init_logger2();
|
|
1285
|
-
|
|
1286
|
-
// src/route/create-app.ts
|
|
1287
|
-
function createApp() {
|
|
1288
|
-
const hono = new Hono();
|
|
1289
|
-
const app = hono;
|
|
1290
|
-
app._contractMetas = /* @__PURE__ */ new Map();
|
|
1291
|
-
app.onError(ErrorHandler());
|
|
1292
|
-
const methodMap = /* @__PURE__ */ new Map([
|
|
1293
|
-
["get", (path, handlers) => hono.get(path, ...handlers)],
|
|
1294
|
-
["post", (path, handlers) => hono.post(path, ...handlers)],
|
|
1295
|
-
["put", (path, handlers) => hono.put(path, ...handlers)],
|
|
1296
|
-
["patch", (path, handlers) => hono.patch(path, ...handlers)],
|
|
1297
|
-
["delete", (path, handlers) => hono.delete(path, ...handlers)]
|
|
1298
|
-
]);
|
|
1299
|
-
app.bind = function(contract, ...args) {
|
|
1300
|
-
const method = contract.method.toLowerCase();
|
|
1301
|
-
const path = contract.path;
|
|
1302
|
-
const [middlewares, handler] = args.length === 1 ? [[], args[0]] : [args[0], args[1]];
|
|
1303
|
-
const key = `${contract.method} ${path}`;
|
|
1304
|
-
app._contractMetas.set(key, contract.meta || {});
|
|
1305
|
-
const boundHandler = bind(contract, handler);
|
|
1306
|
-
const handlers = middlewares.length > 0 ? [...middlewares, boundHandler] : [boundHandler];
|
|
1307
|
-
const registerMethod = methodMap.get(method);
|
|
1308
|
-
if (!registerMethod) {
|
|
1309
|
-
throw new Error(`Unsupported HTTP method: ${contract.method}`);
|
|
459
|
+
} else {
|
|
460
|
+
const factory = handlerOrFactory;
|
|
461
|
+
const wrapper = (...args) => factory(...args);
|
|
462
|
+
Object.defineProperty(wrapper, "name", {
|
|
463
|
+
value: name,
|
|
464
|
+
writable: false,
|
|
465
|
+
enumerable: false,
|
|
466
|
+
configurable: true
|
|
467
|
+
});
|
|
468
|
+
Object.defineProperty(wrapper, "_name", {
|
|
469
|
+
value: name,
|
|
470
|
+
writable: false,
|
|
471
|
+
enumerable: false,
|
|
472
|
+
configurable: true
|
|
473
|
+
});
|
|
474
|
+
return wrapper;
|
|
1310
475
|
}
|
|
1311
|
-
|
|
476
|
+
}
|
|
477
|
+
return {
|
|
478
|
+
name,
|
|
479
|
+
handler: handlerOrFactory,
|
|
480
|
+
_name: name
|
|
1312
481
|
};
|
|
1313
|
-
return app;
|
|
1314
482
|
}
|
|
1315
|
-
function
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
pagination: Type.Optional(Type.Object({
|
|
1323
|
-
page: Type.Number(),
|
|
1324
|
-
limit: Type.Number(),
|
|
1325
|
-
total: Type.Number(),
|
|
1326
|
-
totalPages: Type.Number()
|
|
1327
|
-
}))
|
|
1328
|
-
}))
|
|
483
|
+
function defineMiddlewareFactory(name, factory) {
|
|
484
|
+
const wrapper = (...args) => factory(...args);
|
|
485
|
+
Object.defineProperty(wrapper, "name", {
|
|
486
|
+
value: name,
|
|
487
|
+
writable: false,
|
|
488
|
+
enumerable: false,
|
|
489
|
+
configurable: true
|
|
1329
490
|
});
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
message: Type.String(),
|
|
1336
|
-
type: Type.String(),
|
|
1337
|
-
statusCode: Type.Number(),
|
|
1338
|
-
stack: Type.Optional(Type.String()),
|
|
1339
|
-
details: Type.Optional(Type.Any())
|
|
1340
|
-
})
|
|
491
|
+
Object.defineProperty(wrapper, "_name", {
|
|
492
|
+
value: name,
|
|
493
|
+
writable: false,
|
|
494
|
+
enumerable: false,
|
|
495
|
+
configurable: true
|
|
1341
496
|
});
|
|
497
|
+
return wrapper;
|
|
1342
498
|
}
|
|
1343
|
-
function ApiResponseSchema(dataSchema) {
|
|
1344
|
-
return ApiSuccessSchema(dataSchema);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
// src/route/types.ts
|
|
1348
499
|
function isHttpMethod(value) {
|
|
1349
500
|
return typeof value === "string" && ["GET", "POST", "PUT", "PATCH", "DELETE"].includes(value);
|
|
1350
501
|
}
|
|
502
|
+
var Nullable = (schema) => Type.Union([schema, Type.Null()]);
|
|
503
|
+
var OptionalNullable = (schema) => Type.Optional(Type.Union([schema, Type.Null()]));
|
|
1351
504
|
|
|
1352
|
-
export {
|
|
505
|
+
export { Nullable, OptionalNullable, RouteBuilder, defineMiddleware, defineMiddlewareFactory, defineRouter, isHttpMethod, registerRoutes, route };
|
|
1353
506
|
//# sourceMappingURL=index.js.map
|
|
1354
507
|
//# sourceMappingURL=index.js.map
|