@kalutskii/foundation 0.7.11 → 0.7.13
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/dist/index.d.ts +103 -50
- package/dist/index.js +145 -63
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SQL } from 'drizzle-orm';
|
|
2
|
-
import { ErrorHandler,
|
|
2
|
+
import { ErrorHandler, Context, TypedResponse, MiddlewareHandler } from 'hono';
|
|
3
|
+
import * as kleur_colors from 'kleur/colors';
|
|
3
4
|
import z$1, { z, ZodObject, ZodRawShape } from 'zod';
|
|
4
5
|
import { Locale } from 'date-fns';
|
|
5
6
|
import { SymmetricAlgorithm } from 'hono/utils/jwt/jwa';
|
|
@@ -19,23 +20,6 @@ declare function sqlWhere(table: unknown, where: Record<string, unknown>): SQL;
|
|
|
19
20
|
*/
|
|
20
21
|
declare const onHandlerError: ErrorHandler;
|
|
21
22
|
|
|
22
|
-
/**
|
|
23
|
-
* Options controlling which request details the Hono logger may expose.
|
|
24
|
-
* Secure mode excludes query parameters and body content from log output.
|
|
25
|
-
*/
|
|
26
|
-
type HonoLoggingOptions = {
|
|
27
|
-
/**
|
|
28
|
-
* Prevents query parameters and request bodies from being read or logged.
|
|
29
|
-
* Defaults to `false`, preserving detailed request logging.
|
|
30
|
-
*/
|
|
31
|
-
secure?: boolean;
|
|
32
|
-
};
|
|
33
|
-
/**
|
|
34
|
-
* Creates middleware that logs request metadata and an optional body preview.
|
|
35
|
-
* Multipart bodies remain unread, while secure mode omits all sensitive input details.
|
|
36
|
-
*/
|
|
37
|
-
declare function honoLoggingHandler(options?: HonoLoggingOptions): MiddlewareHandler;
|
|
38
|
-
|
|
39
23
|
declare const SUCCESS_STATUS_CODES: readonly [200, 201, 202, 307];
|
|
40
24
|
declare const EXCEPTION_STATUS_CODES: readonly [400, 401, 403, 404, 405, 409, 500];
|
|
41
25
|
|
|
@@ -120,6 +104,98 @@ declare function fetchSafely<TResult extends APIContractResult<unknown>>(fetcher
|
|
|
120
104
|
*/
|
|
121
105
|
declare function fetchAndThrow<TResult extends APIContractResult<unknown>>(fetcher: () => Promise<TResult>): Promise<APIContractData<TResult>>;
|
|
122
106
|
|
|
107
|
+
/**
|
|
108
|
+
* Terminal color configuration for every supported logging level.
|
|
109
|
+
* The `LogLevel` contract prevents missing or unsupported level entries.
|
|
110
|
+
*/
|
|
111
|
+
declare const logLevelColors: {
|
|
112
|
+
readonly info: typeof kleur_colors.print;
|
|
113
|
+
readonly warn: typeof kleur_colors.print;
|
|
114
|
+
readonly error: typeof kleur_colors.print;
|
|
115
|
+
};
|
|
116
|
+
/**
|
|
117
|
+
* Public placeholder written instead of sensitive query and JSON values.
|
|
118
|
+
* One stable marker keeps redacted output recognizable across log consumers.
|
|
119
|
+
*/
|
|
120
|
+
declare const REDACTED_LOG_VALUE = "[redacted]";
|
|
121
|
+
/**
|
|
122
|
+
* Normalized key fragments treated as sensitive by logging redaction.
|
|
123
|
+
* Matching ignores case and separators so compound field names remain covered.
|
|
124
|
+
*/
|
|
125
|
+
declare const sensitiveLogKeyParts: readonly ["password", "passwd", "token", "secret", "authorization", "apikey", "credential"];
|
|
126
|
+
/**
|
|
127
|
+
* Terminal colors assigned to the supported HTTP response status ranges.
|
|
128
|
+
* Unlisted ranges intentionally retain the terminal's default text appearance.
|
|
129
|
+
*/
|
|
130
|
+
declare const httpStatusColors: readonly [{
|
|
131
|
+
readonly range: readonly [200, 299];
|
|
132
|
+
readonly color: typeof kleur_colors.print;
|
|
133
|
+
}, {
|
|
134
|
+
readonly range: readonly [400, 499];
|
|
135
|
+
readonly color: typeof kleur_colors.print;
|
|
136
|
+
}, {
|
|
137
|
+
readonly range: readonly [500, 599];
|
|
138
|
+
readonly color: typeof kleur_colors.print;
|
|
139
|
+
}];
|
|
140
|
+
|
|
141
|
+
declare const logLevelsArray: readonly ["info", "warn", "error"];
|
|
142
|
+
type LogLevel = (typeof logLevelsArray)[number];
|
|
143
|
+
declare const logLevelsRecord: Readonly<{
|
|
144
|
+
ERROR: "error";
|
|
145
|
+
INFO: "info";
|
|
146
|
+
WARN: "warn";
|
|
147
|
+
}>;
|
|
148
|
+
declare const logLevel: Readonly<{
|
|
149
|
+
ERROR: "error";
|
|
150
|
+
INFO: "info";
|
|
151
|
+
WARN: "warn";
|
|
152
|
+
}>;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Logs Hono request metadata with query parameters and a normalized body preview.
|
|
156
|
+
* Sensitive values are redacted by partial key match before output is written.
|
|
157
|
+
*/
|
|
158
|
+
declare const loggingMiddleware: MiddlewareHandler;
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Replaces sensitive query values using partial, case-insensitive key matching.
|
|
162
|
+
* A new collection is returned so the caller's search parameters remain unchanged.
|
|
163
|
+
*/
|
|
164
|
+
declare function redactSensitiveSearchParams(searchParams: URLSearchParams): URLSearchParams;
|
|
165
|
+
/**
|
|
166
|
+
* Replaces values under sensitive keys throughout a serialized JSON structure.
|
|
167
|
+
* Invalid JSON and payloads without matching keys are returned byte-for-byte unchanged.
|
|
168
|
+
*/
|
|
169
|
+
declare function redactSensitiveJSON(json: string): string;
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Writes timestamped and colorized messages using a stable service column.
|
|
173
|
+
* Error messages may include a stack trace on a subordinate second line.
|
|
174
|
+
*/
|
|
175
|
+
declare const log: {
|
|
176
|
+
/**
|
|
177
|
+
* Writes an informational message with an optional service label.
|
|
178
|
+
* Missing service names use the shared `log` fallback label.
|
|
179
|
+
*/
|
|
180
|
+
info(message: string, service?: string): void;
|
|
181
|
+
/**
|
|
182
|
+
* Writes a warning message with an optional service label.
|
|
183
|
+
* Missing service names use the shared `log` fallback label.
|
|
184
|
+
*/
|
|
185
|
+
warn(message: string, service?: string): void;
|
|
186
|
+
/**
|
|
187
|
+
* Writes an error message with optional service and stack trace context.
|
|
188
|
+
* Provided stack traces are rendered beneath the primary message.
|
|
189
|
+
*/
|
|
190
|
+
error(message: string, service?: string, stack?: string): void;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Selects a terminal color function for one HTTP response status.
|
|
195
|
+
* Successes are green, client errors yellow, and server errors red.
|
|
196
|
+
*/
|
|
197
|
+
declare function getColoredHTTPStatus(status: number): (text: string) => string;
|
|
198
|
+
|
|
123
199
|
/**
|
|
124
200
|
* Canonical metadata shared by upload controls and runtime validation.
|
|
125
201
|
* Every supported format defines display, MIME, and extension values.
|
|
@@ -135,6 +211,11 @@ declare const fileFormatsConfig: {
|
|
|
135
211
|
readonly mimeTypes: readonly ["image/jpeg"];
|
|
136
212
|
readonly extensions: readonly [".jpg", ".jpeg"];
|
|
137
213
|
};
|
|
214
|
+
readonly svg: {
|
|
215
|
+
readonly name: "SVG";
|
|
216
|
+
readonly mimeTypes: readonly ["image/svg+xml"];
|
|
217
|
+
readonly extensions: readonly [".svg"];
|
|
218
|
+
};
|
|
138
219
|
readonly webp: {
|
|
139
220
|
readonly name: "WEBP";
|
|
140
221
|
readonly mimeTypes: readonly ["image/webp"];
|
|
@@ -167,7 +248,7 @@ declare const fileFormatsConfig: {
|
|
|
167
248
|
};
|
|
168
249
|
};
|
|
169
250
|
|
|
170
|
-
declare const fileFormatsArray: readonly ["png", "jpg", "webp", "avif", "heic", "pdf", "rtf", "txt"];
|
|
251
|
+
declare const fileFormatsArray: readonly ["png", "jpg", "webp", "avif", "heic", "svg", "pdf", "rtf", "txt"];
|
|
171
252
|
type FileFormat = (typeof fileFormatsArray)[number];
|
|
172
253
|
declare const fileFormatsRecord: Readonly<{
|
|
173
254
|
AVIF: "avif";
|
|
@@ -175,6 +256,7 @@ declare const fileFormatsRecord: Readonly<{
|
|
|
175
256
|
PDF: "pdf";
|
|
176
257
|
PNG: "png";
|
|
177
258
|
RTF: "rtf";
|
|
259
|
+
SVG: "svg";
|
|
178
260
|
TXT: "txt";
|
|
179
261
|
WEBP: "webp";
|
|
180
262
|
HEIC: "heic";
|
|
@@ -185,6 +267,7 @@ declare const fileFormat: Readonly<{
|
|
|
185
267
|
PDF: "pdf";
|
|
186
268
|
PNG: "png";
|
|
187
269
|
RTF: "rtf";
|
|
270
|
+
SVG: "svg";
|
|
188
271
|
TXT: "txt";
|
|
189
272
|
WEBP: "webp";
|
|
190
273
|
HEIC: "heic";
|
|
@@ -511,36 +594,6 @@ declare function measureExecutionTime<T>(execution: () => Promise<T>): Promise<M
|
|
|
511
594
|
*/
|
|
512
595
|
declare function generateRandomString(length?: number): string;
|
|
513
596
|
|
|
514
|
-
/**
|
|
515
|
-
* Selects a terminal color function for one HTTP response status.
|
|
516
|
-
* Successes are green, client errors yellow, and server errors red.
|
|
517
|
-
*
|
|
518
|
-
* @example
|
|
519
|
-
* getColoredHTTPStatus(404)('404');
|
|
520
|
-
*/
|
|
521
|
-
declare function getColoredHTTPStatus(status: number): (text: string) => string;
|
|
522
|
-
/**
|
|
523
|
-
* Shared logging interface for informational, warning, and error messages.
|
|
524
|
-
* Every level supports a service label while errors may include a stack trace.
|
|
525
|
-
*/
|
|
526
|
-
declare const log: {
|
|
527
|
-
/**
|
|
528
|
-
* Writes an informational message with an optional service label.
|
|
529
|
-
* Missing service names use the shared `log` fallback label.
|
|
530
|
-
*/
|
|
531
|
-
info: (message: string, service?: string) => void;
|
|
532
|
-
/**
|
|
533
|
-
* Writes a warning message with an optional service label.
|
|
534
|
-
* Missing service names use the shared `log` fallback label.
|
|
535
|
-
*/
|
|
536
|
-
warn: (message: string, service?: string) => void;
|
|
537
|
-
/**
|
|
538
|
-
* Writes an error message with optional service and stack trace context.
|
|
539
|
-
* Stack traces are rendered beneath the primary error message when supplied.
|
|
540
|
-
*/
|
|
541
|
-
error: (message: string, service?: string, stack?: string) => void;
|
|
542
|
-
};
|
|
543
|
-
|
|
544
597
|
/**
|
|
545
598
|
* Utility type that simplifies a given type T by flattening its structure.
|
|
546
599
|
* This is particularly useful for improving the readability of complex types.
|
|
@@ -843,4 +896,4 @@ declare const asQuery: <T extends z.ZodTypeAny>(schema: T) => z.ZodPreprocess<T>
|
|
|
843
896
|
*/
|
|
844
897
|
declare const isPlainObject: (value: unknown) => value is Record<string, unknown>;
|
|
845
898
|
|
|
846
|
-
export { type APIContractData, type APIContractError, type APIContractResult, type APIError, type APISuccess, type AsQuery, type AtLeastOne, DEFAULT_UPLOAD_MAX_FILE_SIZE, EXCEPTION_STATUS_CODES, type ExceptionStatusCode, type FetchResult, type FileFormat, type FileFormatConfig, type HonoFileRespondOptions, type
|
|
899
|
+
export { type APIContractData, type APIContractError, type APIContractResult, type APIError, type APISuccess, type AsQuery, type AtLeastOne, DEFAULT_UPLOAD_MAX_FILE_SIZE, EXCEPTION_STATUS_CODES, type ExceptionStatusCode, type FetchResult, type FileFormat, type FileFormatConfig, type HonoFileRespondOptions, type HonoRespondOptions, type JWTServiceOptions, type JWTSignOptions, type LogLevel, type MeasuredExecution, type Payload, type PayloadSchema, REDACTED_LOG_VALUE, type ReplaceDotsWithUnderscores, SUCCESS_STATUS_CODES, type Simplify, type StringEnumRecord, type SuccessStatusCode, type UploadFilesValidationOptions, type UploadFilesValidationResult, type UploadPreset, type UploadValidationError, type ZodBulkExcludeSelection, type ZodBulkIncludeSelection, type ZodBulkSelection, ZodJWTService, type ZodPaginationOptions, type ZodSearchSchemaOptions, type ZodUploadFileSchemaOptions, asQuery, createStringEnumRecord, createUploadAccept, createZodSearchWhereSchema, defineUploadPreset, documentUploadPreset, failure, fetchAndThrow, fetchSafely, fileFormat, fileFormatsArray, fileFormatsConfig, fileFormatsRecord, fileRespond, formatTime, generateRandomString, getColoredHTTPStatus, getFileExtension, getFormattedDate, getFormattedTime, getUTCOffset, getZonedTime, httpStatusColors, imageUploadPreset, isFileExtensionSupported, isFileFormatSupported, isFileMimeTypeSupported, isPlainObject, log, logLevel, logLevelColors, logLevelsArray, logLevelsRecord, loggingMiddleware, matchesMimeType, measureExecutionTime, normalizeFileExtension, onHandlerError, parseQueryValue, redactSensitiveJSON, redactSensitiveSearchParams, respond, safeExecute, sensitiveLogKeyParts, sqlWhere, success, uploadValidationError, uploadValidationErrorsArray, uploadValidationErrorsRecord, validateUploadFile, validateUploadFiles, zodAtLeastOne, zodBulkSelectionSchema, zodPaginationSchema, zodPaginationShape, zodSearchQuerySchema, zodSearchSchema, zodUploadFileSchema };
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ function sqlWhere(table, where) {
|
|
|
12
12
|
|
|
13
13
|
// src/hono/hono.execution.ts
|
|
14
14
|
import { HTTPException } from "hono/http-exception";
|
|
15
|
-
import { red as
|
|
15
|
+
import { red as red3 } from "kleur/colors";
|
|
16
16
|
|
|
17
17
|
// src/http/http.factory.ts
|
|
18
18
|
function success({ status, data }) {
|
|
@@ -22,19 +22,8 @@ function failure({ status, error }) {
|
|
|
22
22
|
return { kind: "error", status, error };
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
// src/
|
|
26
|
-
|
|
27
|
-
function generateRandomString(length = 10) {
|
|
28
|
-
const randomValues = crypto.getRandomValues(new Uint32Array(length));
|
|
29
|
-
let result = "";
|
|
30
|
-
for (let i = 0; i < length; i++) {
|
|
31
|
-
result += DEFAULT_CHARACTERS[randomValues[i] % DEFAULT_CHARACTERS.length];
|
|
32
|
-
}
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
// src/utilities/logging.utilities.ts
|
|
37
|
-
import { blue, dim, green, red, white, yellow } from "kleur/colors";
|
|
25
|
+
// src/logging/logging.services.ts
|
|
26
|
+
import { dim, red as red2, white } from "kleur/colors";
|
|
38
27
|
|
|
39
28
|
// src/utilities/datetime.utilities.ts
|
|
40
29
|
import { format } from "date-fns";
|
|
@@ -62,50 +51,90 @@ function formatTime(time, { locale = ru, tz = "Europe/London" } = {}) {
|
|
|
62
51
|
return `${format(zonedTime, "HH:mm:ss, d MMMM yyyy", { locale })} ${getUTCOffset(zonedTime, tz)}`;
|
|
63
52
|
}
|
|
64
53
|
|
|
65
|
-
// src/
|
|
66
|
-
|
|
54
|
+
// src/logging/logging.constants.ts
|
|
55
|
+
import { blue, green, red, yellow } from "kleur/colors";
|
|
56
|
+
|
|
57
|
+
// src/utilities/enums.utilities.ts
|
|
58
|
+
function createStringEnumRecord(values) {
|
|
59
|
+
return Object.freeze(Object.fromEntries(values.map((value) => [value.replace(/\./g, "_").toUpperCase(), value])));
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// src/logging/logging.enums.ts
|
|
63
|
+
var logLevelsArray = ["info", "warn", "error"];
|
|
64
|
+
var logLevelsRecord = createStringEnumRecord(logLevelsArray);
|
|
65
|
+
var logLevel = logLevelsRecord;
|
|
66
|
+
|
|
67
|
+
// src/logging/logging.constants.ts
|
|
68
|
+
var logLevelColors = {
|
|
69
|
+
[logLevel.INFO]: blue,
|
|
70
|
+
[logLevel.WARN]: yellow,
|
|
71
|
+
[logLevel.ERROR]: red
|
|
72
|
+
};
|
|
73
|
+
var REDACTED_LOG_VALUE = "[redacted]";
|
|
74
|
+
var sensitiveLogKeyParts = [
|
|
75
|
+
"password",
|
|
76
|
+
"passwd",
|
|
77
|
+
"token",
|
|
78
|
+
"secret",
|
|
79
|
+
"authorization",
|
|
80
|
+
"apikey",
|
|
81
|
+
"credential"
|
|
82
|
+
];
|
|
83
|
+
var httpStatusColors = [
|
|
67
84
|
{ range: [200, 299], color: green },
|
|
68
85
|
{ range: [400, 499], color: yellow },
|
|
69
86
|
{ range: [500, 599], color: red }
|
|
70
87
|
];
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
const statusColor = HTTP_STATUS_COLORS.find(({ range: [minimum, maximum] }) => {
|
|
74
|
-
return status >= minimum && status <= maximum;
|
|
75
|
-
});
|
|
76
|
-
return statusColor ? statusColor.color : (text) => text;
|
|
77
|
-
}
|
|
88
|
+
|
|
89
|
+
// src/logging/logging.services.ts
|
|
78
90
|
function writeLog(message, level, service = "log", stack) {
|
|
79
91
|
const timestamp = dim(getFormattedTime());
|
|
80
|
-
const serviceName =
|
|
92
|
+
const serviceName = logLevelColors[level](service.padEnd(12));
|
|
81
93
|
const formattedMessage = white(message);
|
|
82
94
|
console.log(`[${timestamp}] ${serviceName} | ${formattedMessage}`);
|
|
83
95
|
if (stack)
|
|
84
|
-
console.log(`[${timestamp}] ${
|
|
96
|
+
console.log(`[${timestamp}] ${red2("\u21B3 trace").padEnd(18)} | ${dim(stack)}`);
|
|
85
97
|
}
|
|
86
98
|
var log = {
|
|
87
99
|
/**
|
|
88
100
|
* Writes an informational message with an optional service label.
|
|
89
101
|
* Missing service names use the shared `log` fallback label.
|
|
90
102
|
*/
|
|
91
|
-
info
|
|
103
|
+
info(message, service) {
|
|
104
|
+
writeLog(message, "info", service);
|
|
105
|
+
},
|
|
92
106
|
/**
|
|
93
107
|
* Writes a warning message with an optional service label.
|
|
94
108
|
* Missing service names use the shared `log` fallback label.
|
|
95
109
|
*/
|
|
96
|
-
warn
|
|
110
|
+
warn(message, service) {
|
|
111
|
+
writeLog(message, "warn", service);
|
|
112
|
+
},
|
|
97
113
|
/**
|
|
98
114
|
* Writes an error message with optional service and stack trace context.
|
|
99
|
-
*
|
|
115
|
+
* Provided stack traces are rendered beneath the primary message.
|
|
100
116
|
*/
|
|
101
|
-
error
|
|
117
|
+
error(message, service, stack) {
|
|
118
|
+
writeLog(message, "error", service, stack);
|
|
119
|
+
}
|
|
102
120
|
};
|
|
103
121
|
|
|
122
|
+
// src/utilities/generation.utilities.ts
|
|
123
|
+
var DEFAULT_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
124
|
+
function generateRandomString(length = 10) {
|
|
125
|
+
const randomValues = crypto.getRandomValues(new Uint32Array(length));
|
|
126
|
+
let result = "";
|
|
127
|
+
for (let i = 0; i < length; i++) {
|
|
128
|
+
result += DEFAULT_CHARACTERS[randomValues[i] % DEFAULT_CHARACTERS.length];
|
|
129
|
+
}
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
104
133
|
// src/hono/hono.execution.ts
|
|
105
134
|
function proceedUnhandledError(error) {
|
|
106
135
|
const errorId = generateRandomString(6);
|
|
107
136
|
const errorMessage = error instanceof Error ? error.stack ?? error.message : JSON.stringify(error);
|
|
108
|
-
log.error(`Unhandled error: ${
|
|
137
|
+
log.error(`Unhandled error: ${red3(errorMessage)}`, errorId);
|
|
109
138
|
return failure({ status: 500, error: `Internal server error | ${errorId}` });
|
|
110
139
|
}
|
|
111
140
|
var onHandlerError = (error, c) => {
|
|
@@ -117,34 +146,6 @@ var onHandlerError = (error, c) => {
|
|
|
117
146
|
return c.json(response, response.status);
|
|
118
147
|
};
|
|
119
148
|
|
|
120
|
-
// src/hono/hono.logging.ts
|
|
121
|
-
import { blue as blue2, bold, dim as dim2, white as white2 } from "kleur/colors";
|
|
122
|
-
function honoLoggingHandler(options = {}) {
|
|
123
|
-
return async (c, next) => {
|
|
124
|
-
const searchParams = new URL(c.req.url).searchParams.toString();
|
|
125
|
-
const body = c.req.header("content-type")?.includes("multipart/form-data") ? (
|
|
126
|
-
// Multipart content remains unread to avoid buffering uploaded files;
|
|
127
|
-
// The placeholder records its presence without exposing any fields;
|
|
128
|
-
"[multipart]"
|
|
129
|
-
) : (
|
|
130
|
-
// Other bodies are read from a clone and normalized for one-line output;
|
|
131
|
-
// The original request stream remains available to downstream handlers;
|
|
132
|
-
(await c.req.raw.clone().text()).replaceAll(/\s+/g, " ").trim()
|
|
133
|
-
);
|
|
134
|
-
const bodyPreview = body.length > 60 ? `${body.slice(0, 30)}\u2026${body.slice(-30)}` : body;
|
|
135
|
-
const startTime = performance.now();
|
|
136
|
-
await next();
|
|
137
|
-
const duration = Math.round(performance.now() - startTime);
|
|
138
|
-
const coloredMethod = bold(blue2(c.req.method.padEnd(4)));
|
|
139
|
-
const coloredStatus = getColoredHTTPStatus(c.res.status)(String(c.res.status).padEnd(4));
|
|
140
|
-
const coloredTime = dim2(`${duration}ms`.padStart(6));
|
|
141
|
-
const coloredPath = white2(c.req.path.padEnd(32));
|
|
142
|
-
const coloredSearchParams = options.secure ? "" : dim2(` (${searchParams})`);
|
|
143
|
-
const coloredBody = options.secure ? "" : dim2(` ${bodyPreview}`);
|
|
144
|
-
log.info(`${coloredMethod} ${coloredStatus} ${coloredTime} ${coloredPath}${coloredSearchParams}${coloredBody}`, "hono");
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
|
|
148
149
|
// src/hono/hono.respond.ts
|
|
149
150
|
function respond(c, options) {
|
|
150
151
|
return c.json(success({ status: options.status, data: options.data ?? {} }), options.status);
|
|
@@ -173,14 +174,85 @@ async function fetchAndThrow(fetcher) {
|
|
|
173
174
|
return response.data;
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
// src/
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
// src/logging/logging.middleware.ts
|
|
178
|
+
import { blue as blue2, bold, dim as dim2, white as white2 } from "kleur/colors";
|
|
179
|
+
|
|
180
|
+
// src/logging/logging.security.ts
|
|
181
|
+
function isSensitiveLogKey(key) {
|
|
182
|
+
const normalizedKey = key.toLowerCase().replaceAll(/[^a-z]/g, "");
|
|
183
|
+
return sensitiveLogKeyParts.some((sensitivePart) => normalizedKey.includes(sensitivePart));
|
|
184
|
+
}
|
|
185
|
+
function redactSensitiveValue(value) {
|
|
186
|
+
if (Array.isArray(value)) {
|
|
187
|
+
const entries2 = value.map(redactSensitiveValue);
|
|
188
|
+
return {
|
|
189
|
+
value: entries2.map((entry) => entry.value),
|
|
190
|
+
redacted: entries2.some((entry) => entry.redacted)
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (typeof value !== "object" || value === null)
|
|
194
|
+
return { value, redacted: false };
|
|
195
|
+
let redacted = false;
|
|
196
|
+
const entries = Object.entries(value).map(([key, entryValue]) => {
|
|
197
|
+
if (isSensitiveLogKey(key)) {
|
|
198
|
+
redacted = true;
|
|
199
|
+
return [key, REDACTED_LOG_VALUE];
|
|
200
|
+
}
|
|
201
|
+
const nestedEntry = redactSensitiveValue(entryValue);
|
|
202
|
+
redacted ||= nestedEntry.redacted;
|
|
203
|
+
return [key, nestedEntry.value];
|
|
204
|
+
});
|
|
205
|
+
return { value: Object.fromEntries(entries), redacted };
|
|
206
|
+
}
|
|
207
|
+
function redactSensitiveSearchParams(searchParams) {
|
|
208
|
+
const redactedSearchParams = new URLSearchParams(searchParams);
|
|
209
|
+
for (const key of new Set(redactedSearchParams.keys())) {
|
|
210
|
+
if (isSensitiveLogKey(key))
|
|
211
|
+
redactedSearchParams.set(key, REDACTED_LOG_VALUE);
|
|
212
|
+
}
|
|
213
|
+
return redactedSearchParams;
|
|
179
214
|
}
|
|
215
|
+
function redactSensitiveJSON(json) {
|
|
216
|
+
if (json === void 0 || null)
|
|
217
|
+
return json;
|
|
218
|
+
try {
|
|
219
|
+
const parsedValue = JSON.parse(json);
|
|
220
|
+
const result = redactSensitiveValue(parsedValue);
|
|
221
|
+
return result.redacted ? JSON.stringify(result.value) : json;
|
|
222
|
+
} catch {
|
|
223
|
+
return json;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// src/logging/logging.utilities.ts
|
|
228
|
+
function getColoredHTTPStatus(status) {
|
|
229
|
+
const statusColor = httpStatusColors.find(({ range: [minimum, maximum] }) => {
|
|
230
|
+
return status >= minimum && status <= maximum;
|
|
231
|
+
});
|
|
232
|
+
return statusColor ? statusColor.color : (text) => text;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// src/logging/logging.middleware.ts
|
|
236
|
+
var loggingMiddleware = async (c, next) => {
|
|
237
|
+
const contentType = c.req.header("content-type");
|
|
238
|
+
const searchParams = redactSensitiveSearchParams(new URL(c.req.url).searchParams).toString();
|
|
239
|
+
const body = contentType?.includes("multipart/form-data") ? "[multipart]" : redactSensitiveJSON((await c.req.raw.clone().text()).replaceAll(/\s+/g, " ").trim());
|
|
240
|
+
const bodyPreview = body.length > 60 ? `${body.slice(0, 30)}\u2026${body.slice(-30)}` : body;
|
|
241
|
+
const startTime = performance.now();
|
|
242
|
+
await next();
|
|
243
|
+
const duration = Math.round(performance.now() - startTime);
|
|
244
|
+
const coloredMethod = bold(blue2(c.req.method.padEnd(4)));
|
|
245
|
+
const coloredStatus = getColoredHTTPStatus(c.res.status)(String(c.res.status).padEnd(4));
|
|
246
|
+
const coloredTime = dim2(`${duration}ms`.padStart(6));
|
|
247
|
+
const coloredPath = white2(c.req.path.padEnd(32));
|
|
248
|
+
const coloredSearchParams = searchParams ? dim2(` (${searchParams})`) : "";
|
|
249
|
+
const coloredBody = bodyPreview ? dim2(` ${bodyPreview}`) : "";
|
|
250
|
+
log.info(`${coloredMethod} ${coloredStatus} ${coloredTime} ${coloredPath}${coloredSearchParams}${coloredBody}`, "hono");
|
|
251
|
+
};
|
|
180
252
|
|
|
181
253
|
// src/upload/upload.enums.ts
|
|
182
254
|
var fileFormatsArray = [
|
|
183
|
-
...["png", "jpg", "webp", "avif", "heic"],
|
|
255
|
+
...["png", "jpg", "webp", "avif", "heic", "svg"],
|
|
184
256
|
// Image formats.
|
|
185
257
|
...["pdf", "rtf", "txt"]
|
|
186
258
|
// Text and document formats.
|
|
@@ -201,6 +273,7 @@ var fileFormatsConfig = {
|
|
|
201
273
|
// Image formats.
|
|
202
274
|
[fileFormatsRecord.PNG]: { name: "PNG", mimeTypes: ["image/png"], extensions: [".png"] },
|
|
203
275
|
[fileFormatsRecord.JPG]: { name: "JPG", mimeTypes: ["image/jpeg"], extensions: [".jpg", ".jpeg"] },
|
|
276
|
+
[fileFormatsRecord.SVG]: { name: "SVG", mimeTypes: ["image/svg+xml"], extensions: [".svg"] },
|
|
204
277
|
[fileFormatsRecord.WEBP]: { name: "WEBP", mimeTypes: ["image/webp"], extensions: [".webp"] },
|
|
205
278
|
[fileFormatsRecord.AVIF]: { name: "AVIF", mimeTypes: ["image/avif"], extensions: [".avif"] },
|
|
206
279
|
[fileFormatsRecord.HEIC]: { name: "HEIC", mimeTypes: ["image/heic", "image/heif"], extensions: [".heic", ".heif"] },
|
|
@@ -450,6 +523,7 @@ var asQuery = (schema) => z5.preprocess(parseQueryValue, schema);
|
|
|
450
523
|
export {
|
|
451
524
|
DEFAULT_UPLOAD_MAX_FILE_SIZE,
|
|
452
525
|
EXCEPTION_STATUS_CODES,
|
|
526
|
+
REDACTED_LOG_VALUE,
|
|
453
527
|
SUCCESS_STATUS_CODES,
|
|
454
528
|
ZodJWTService,
|
|
455
529
|
asQuery,
|
|
@@ -474,20 +548,28 @@ export {
|
|
|
474
548
|
getFormattedTime,
|
|
475
549
|
getUTCOffset,
|
|
476
550
|
getZonedTime,
|
|
477
|
-
|
|
551
|
+
httpStatusColors,
|
|
478
552
|
imageUploadPreset,
|
|
479
553
|
isFileExtensionSupported,
|
|
480
554
|
isFileFormatSupported,
|
|
481
555
|
isFileMimeTypeSupported,
|
|
482
556
|
isPlainObject,
|
|
483
557
|
log,
|
|
558
|
+
logLevel,
|
|
559
|
+
logLevelColors,
|
|
560
|
+
logLevelsArray,
|
|
561
|
+
logLevelsRecord,
|
|
562
|
+
loggingMiddleware,
|
|
484
563
|
matchesMimeType,
|
|
485
564
|
measureExecutionTime,
|
|
486
565
|
normalizeFileExtension,
|
|
487
566
|
onHandlerError,
|
|
488
567
|
parseQueryValue,
|
|
568
|
+
redactSensitiveJSON,
|
|
569
|
+
redactSensitiveSearchParams,
|
|
489
570
|
respond,
|
|
490
571
|
safeExecute,
|
|
572
|
+
sensitiveLogKeyParts,
|
|
491
573
|
sqlWhere,
|
|
492
574
|
success,
|
|
493
575
|
uploadValidationError,
|