@kalutskii/foundation 2.0.0 → 2.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (4) hide show
  1. package/README.md +13 -347
  2. package/dist/index.d.ts +939 -737
  3. package/dist/index.js +1009 -678
  4. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -1,52 +1,48 @@
1
- import { and, eq } from "drizzle-orm";
2
- import { HTTPException } from "hono/http-exception";
3
- import { blue, bold, dim, green, red, white, yellow } from "kleur/colors";
4
1
  import { format } from "date-fns";
5
2
  import { getTimezoneOffset, toZonedTime } from "date-fns-tz";
6
3
  import { ru } from "date-fns/locale";
7
- import { z as z$1 } from "zod";
4
+ import { and, eq, getTableColumns, isNull } from "drizzle-orm";
5
+ import { HTTPException } from "hono/http-exception";
6
+ import { blue, bold, dim, green, red, white, yellow } from "kleur/colors";
8
7
  import { decode, sign, verify } from "hono/jwt";
9
- //#region src/drizzle/drizzle.refiners.ts
10
- const AT_LEAST_ONE_DEFINED_CONDITION_ERROR = "sqlWhere requires at least one defined condition.";
8
+ import { z } from "zod";
9
+ //#region src/base64/base64.constants.ts
11
10
  /**
12
- * Builds a Drizzle `WHERE` clause by combining defined object entries with `and`.
13
- * Expects the supplied keys to be validated against the table beforehand.
14
- *
15
- * @example
16
- * await db.update(usersTable).set(values).where(sqlWhere(usersTable, { id: 1 })).returning();
11
+ * Maximum byte count converted by one `String.fromCharCode` invocation.
12
+ * Chunking keeps large payloads below engine-specific argument-count limits.
17
13
  */
18
- function sqlWhere(table, where) {
19
- const columns = table;
20
- const conditions = Object.entries(where).filter(([, value]) => value !== void 0).map(([key, value]) => eq(columns[key], value));
21
- if (conditions.length === 0) throw new Error(AT_LEAST_ONE_DEFINED_CONDITION_ERROR);
22
- return and(...conditions);
23
- }
14
+ const BASE64_BYTE_CHUNK_SIZE = 32768;
24
15
  //#endregion
25
- //#region src/http/http.factory.ts
16
+ //#region src/base64/base64.utilities.ts
26
17
  /**
27
- * Creates a successful API envelope without cloning its data.
28
- * Generic inference preserves the exact supplied payload type.
18
+ * Encodes arbitrary binary bytes into their canonical Base64 representation.
19
+ * Chunked conversion avoids the argument limit imposed by `String.fromCharCode`.
29
20
  */
30
- function success({ status, data }) {
31
- return {
32
- kind: "data",
33
- status,
34
- data
35
- };
21
+ function encodeBase64(bytes) {
22
+ let binary = "";
23
+ for (let offset = 0; offset < bytes.length; offset += BASE64_BYTE_CHUNK_SIZE) binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_BYTE_CHUNK_SIZE));
24
+ return btoa(binary);
36
25
  }
37
26
  /**
38
- * Creates a failed API envelope with one supported exception status.
39
- * The supplied error code remains unchanged for downstream resolution.
27
+ * Decodes Base64 text accepted by native `atob` into its original binary bytes.
28
+ * Unsupported input preserves the platform failure instead of returning partial data.
40
29
  */
41
- function failure({ status, error }) {
42
- return {
43
- kind: "error",
44
- status,
45
- error
46
- };
30
+ function decodeBase64(value) {
31
+ const binary = atob(value);
32
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
47
33
  }
48
34
  //#endregion
49
- //#region src/utilities/datetime.utilities.ts
35
+ //#region src/datetime/datetime.constants.ts
36
+ /**
37
+ * Default timezone applied by datetime utilities when no explicit zone is provided.
38
+ * The value preserves the package's historical London-based formatting behavior.
39
+ */
40
+ const DEFAULT_DATETIME_TIMEZONE = "Europe/London";
41
+ const DATETIME_DATE_PATTERN = "dd.MM.yyyy";
42
+ const DATETIME_TIME_PATTERN = "HH:mm:ss";
43
+ const DATETIME_LOCALIZED_PATTERN = "HH:mm:ss, d MMMM yyyy";
44
+ //#endregion
45
+ //#region src/datetime/datetime.utilities.ts
50
46
  /**
51
47
  * Projects the current instant onto the wall-clock fields of another timezone.
52
48
  * The timezone defaults to `Europe/London` when no option is provided.
@@ -54,7 +50,7 @@ function failure({ status, error }) {
54
50
  * @example
55
51
  * const londonTime = getZonedTime({ tz: 'Europe/London' });
56
52
  */
57
- function getZonedTime({ tz = "Europe/London" } = {}) {
53
+ function getZonedTime({ tz = DEFAULT_DATETIME_TIMEZONE } = {}) {
58
54
  return toZonedTime(/* @__PURE__ */ new Date(), tz);
59
55
  }
60
56
  /**
@@ -75,9 +71,10 @@ function getUTCOffset(date, tz) {
75
71
  * @example
76
72
  * getFormattedTime({ tz: 'Europe/Moscow' }); // `03:04:05 (+3 UTC)`
77
73
  */
78
- function getFormattedTime({ tz = "Europe/London" } = {}) {
79
- const zonedTime = getZonedTime({ tz });
80
- return `${format(zonedTime, "HH:mm:ss")} ${getUTCOffset(zonedTime, tz)}`;
74
+ function getFormattedTime({ tz = DEFAULT_DATETIME_TIMEZONE } = {}) {
75
+ const currentTime = /* @__PURE__ */ new Date();
76
+ const zonedTime = toZonedTime(currentTime, tz);
77
+ return `${format(zonedTime, DATETIME_TIME_PATTERN)} ${getUTCOffset(currentTime, tz)}`;
81
78
  }
82
79
  /**
83
80
  * Formats the current date with optional time and UTC offset components.
@@ -86,9 +83,10 @@ function getFormattedTime({ tz = "Europe/London" } = {}) {
86
83
  * @example
87
84
  * getFormattedDate({ tz: 'UTC', withTime: false }); // `02.01.2024`
88
85
  */
89
- function getFormattedDate({ tz = "Europe/London", withTime = true } = {}) {
90
- const zonedTime = getZonedTime({ tz });
91
- return `${format(zonedTime, withTime ? "dd.MM.yyyy HH:mm:ss" : "dd.MM.yyyy")}${withTime ? ` ${getUTCOffset(zonedTime, tz)}` : ""}`;
86
+ function getFormattedDate({ tz = DEFAULT_DATETIME_TIMEZONE, withTime = true } = {}) {
87
+ const currentTime = /* @__PURE__ */ new Date();
88
+ const zonedTime = toZonedTime(currentTime, tz);
89
+ return `${format(zonedTime, withTime ? `${DATETIME_DATE_PATTERN} ${DATETIME_TIME_PATTERN}` : DATETIME_DATE_PATTERN)}${withTime ? ` ${getUTCOffset(currentTime, tz)}` : ""}`;
92
90
  }
93
91
  /**
94
92
  * Formats an explicit instant in another timezone using the selected locale.
@@ -97,175 +95,221 @@ function getFormattedDate({ tz = "Europe/London", withTime = true } = {}) {
97
95
  * @example
98
96
  * formatTime(date, { locale: ru, tz: 'Europe/Moscow' });
99
97
  */
100
- function formatTime(time, { locale = ru, tz = "Europe/London" } = {}) {
98
+ function formatTime(time, options = {}) {
99
+ const { locale = ru, tz = DEFAULT_DATETIME_TIMEZONE } = options;
101
100
  const zonedTime = toZonedTime(time, tz);
102
- return `${format(zonedTime, "HH:mm:ss, d MMMM yyyy", { locale })} ${getUTCOffset(zonedTime, tz)}`;
101
+ return `${format(zonedTime, DATETIME_LOCALIZED_PATTERN, { locale })} ${getUTCOffset(time, tz)}`;
103
102
  }
104
103
  //#endregion
105
- //#region src/utilities/enums.utilities.ts
104
+ //#region src/drizzle/drizzle.errors.ts
105
+ const drizzleErrors = {
106
+ whereConditionsRequired: () => /* @__PURE__ */ new Error("whereConditionsRequired"),
107
+ whereColumnNotFound: () => /* @__PURE__ */ new Error("whereColumnNotFound")
108
+ };
109
+ //#endregion
110
+ //#region src/drizzle/drizzle.refiners.ts
106
111
  /**
107
- * Creates an immutable enum-like record from a readonly string array.
108
- * Keys are uppercased and every dot or hyphen is replaced with an underscore.
112
+ * Builds a Drizzle `WHERE` clause by combining defined object entries with `and`.
113
+ * Keys and values follow the table model while empty conditions remain forbidden.
109
114
  *
110
115
  * @example
111
- * createStringEnumRecord(['foo-bar.s', 'baz'] as const); // `{ FOO_BAR_S: 'foo-bar.s', BAZ: 'baz' }`
116
+ * await db.update(usersTable).set(values).where(sqlWhere(usersTable, { id: 1 })).returning();
112
117
  */
113
- function createStringEnumRecord(values) {
114
- return Object.freeze(Object.fromEntries(values.map((value) => [value.replaceAll(/[.-]/g, "_").toUpperCase(), value])));
118
+ function sqlWhere(table, where) {
119
+ const columns = getTableColumns(table);
120
+ const conditions = Object.entries(where).filter(([, value]) => value !== void 0).map(([key, value]) => {
121
+ const column = columns[key];
122
+ if (column === void 0) throw drizzleErrors.whereColumnNotFound();
123
+ return value === null ? isNull(column) : eq(column, value);
124
+ });
125
+ const condition = and(...conditions);
126
+ if (condition === void 0) throw drizzleErrors.whereConditionsRequired();
127
+ return condition;
115
128
  }
116
129
  //#endregion
117
- //#region src/logging/logging.enums.ts
118
- const logLevelsArray = [
119
- "info",
120
- "warn",
121
- "error"
122
- ];
123
- const logLevelsRecord = createStringEnumRecord(logLevelsArray);
124
- const logLevel = logLevelsRecord;
125
- //#endregion
126
- //#region src/logging/logging.constants.ts
130
+ //#region src/execution/execution.constants.ts
127
131
  /**
128
- * Terminal color configuration for every supported logging level.
129
- * The `LogLevel` contract prevents missing or unsupported level entries.
132
+ * Default total attempt count applied when `retryExecution` receives no override.
133
+ * The value includes the initial operation and every subsequent retry attempt.
130
134
  */
131
- const logLevelColors = {
132
- [logLevel.INFO]: blue,
133
- [logLevel.WARN]: yellow,
134
- [logLevel.ERROR]: red
135
+ const DEFAULT_RETRY_MAX_ATTEMPTS = 3;
136
+ //#endregion
137
+ //#region src/execution/execution.errors.ts
138
+ const executionErrors = {
139
+ invalidMaxAttempts: () => /* @__PURE__ */ new RangeError("invalidMaxAttempts"),
140
+ invalidRetryDelay: () => /* @__PURE__ */ new RangeError("invalidRetryDelay"),
141
+ invalidTimeout: () => /* @__PURE__ */ new RangeError("invalidTimeout"),
142
+ executionTimedOut: () => new DOMException("executionTimedOut", "TimeoutError")
135
143
  };
144
+ //#endregion
145
+ //#region src/execution/execution.measurements.ts
136
146
  /**
137
- * Public placeholder written instead of sensitive query and JSON values.
138
- * One stable marker keeps redacted output recognizable across log consumers.
147
+ * Measures a synchronous or asynchronous execution while preserving its resolved result.
148
+ * Rejected operations propagate unchanged and never produce a measurement result.
149
+ *
150
+ * @example
151
+ * const { result, executionTime } = await measureExecutionTime(async () => {
152
+ * return await fetchData();
153
+ * });
154
+ * console.log(`Execution time: ${executionTime}ms`);
139
155
  */
140
- const REDACTED_LOG_VALUE = "[redacted]";
156
+ async function measureExecutionTime(execution) {
157
+ const startedAt = performance.now();
158
+ const result = await execution();
159
+ const executionTime = performance.now() - startedAt;
160
+ return {
161
+ result,
162
+ executionTime: Math.round(executionTime)
163
+ };
164
+ }
165
+ //#endregion
166
+ //#region src/execution/execution.validation.ts
141
167
  /**
142
- * Normalized key fragments treated as sensitive by logging redaction.
143
- * Matching ignores case and separators so compound field names remain covered.
168
+ * Validates one resolved delay before a retry timer is allocated.
169
+ * Only finite non-negative millisecond durations are accepted.
144
170
  */
145
- const sensitiveLogKeyParts = [
146
- "password",
147
- "passwd",
148
- "token",
149
- "secret",
150
- "authorization",
151
- "apikey",
152
- "credential"
153
- ];
171
+ function validateRetryDelay(delayMilliseconds) {
172
+ if (Number.isFinite(delayMilliseconds) === false || delayMilliseconds < 0) throw executionErrors.invalidRetryDelay();
173
+ }
154
174
  /**
155
- * Terminal colors assigned to the supported HTTP response status ranges.
156
- * Unlisted ranges intentionally retain the terminal's default text appearance.
175
+ * Validates static retry policy values before the first operation attempt.
176
+ * Dynamic delay values remain validated after their resolver completes.
157
177
  */
158
- const httpStatusColors = [
159
- {
160
- range: [200, 299],
161
- color: green
162
- },
163
- {
164
- range: [400, 499],
165
- color: yellow
166
- },
167
- {
168
- range: [500, 599],
169
- color: red
170
- }
171
- ];
172
- //#endregion
173
- //#region src/logging/logging.services.ts
174
- function writeLog(message, level, service = "log", stack) {
175
- const timestamp = dim(getFormattedTime());
176
- const serviceName = logLevelColors[level](service.padEnd(12));
177
- const formattedMessage = white(message);
178
- console.log(`[${timestamp}] ${serviceName} | ${formattedMessage}`);
179
- if (stack) console.log(`[${timestamp}] ${red("↳ trace").padEnd(18)} | ${dim(stack)}`);
178
+ function validateRetryExecutionOptions(options, maxAttempts) {
179
+ if (Number.isInteger(maxAttempts) === false || maxAttempts < 1) throw executionErrors.invalidMaxAttempts();
180
+ if (typeof options.delayMilliseconds === "number") validateRetryDelay(options.delayMilliseconds);
180
181
  }
181
182
  /**
182
- * Writes timestamped and colorized messages using a stable service column.
183
- * Error messages may include a stack trace on a subordinate second line.
183
+ * Validates the duration used to bound an execution with a timeout.
184
+ * Only finite non-negative millisecond durations are accepted.
184
185
  */
185
- const log = {
186
- /**
187
- * Writes an informational message with an optional service label.
188
- * Missing service names use the shared `log` fallback label.
189
- */
190
- info(message, service) {
191
- writeLog(message, "info", service);
192
- },
193
- /**
194
- * Writes a warning message with an optional service label.
195
- * Missing service names use the shared `log` fallback label.
196
- */
197
- warn(message, service) {
198
- writeLog(message, "warn", service);
199
- },
200
- /**
201
- * Writes an error message with optional service and stack trace context.
202
- * Provided stack traces are rendered beneath the primary message.
203
- */
204
- error(message, service, stack) {
205
- writeLog(message, "error", service, stack);
206
- }
207
- };
186
+ function validateExecutionTimeout(timeoutMilliseconds) {
187
+ if (Number.isFinite(timeoutMilliseconds) === false || timeoutMilliseconds < 0) throw executionErrors.invalidTimeout();
188
+ }
208
189
  //#endregion
209
- //#region src/utilities/generation.utilities.ts
210
- const DEFAULT_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
190
+ //#region src/execution/execution.retry.ts
191
+ /**
192
+ * Waits for a retry delay while preserving cooperative caller cancellation.
193
+ * Zero-duration delays resolve immediately without allocating a timer.
194
+ */
195
+ async function waitForRetry(delayMilliseconds, signal) {
196
+ validateRetryDelay(delayMilliseconds);
197
+ signal?.throwIfAborted();
198
+ if (delayMilliseconds === 0) return;
199
+ await new Promise((resolve, reject) => {
200
+ const onAbort = () => {
201
+ clearTimeout(timeout);
202
+ reject(signal?.reason);
203
+ };
204
+ const timeout = setTimeout(() => {
205
+ signal?.removeEventListener("abort", onAbort);
206
+ resolve();
207
+ }, delayMilliseconds);
208
+ signal?.addEventListener("abort", onAbort, { once: true });
209
+ });
210
+ }
211
211
  /**
212
- * Creates a cryptographically sourced string from the alphanumeric set.
213
- * Requested length defaults to `10` characters when omitted.
214
- *
215
- * @example
216
- * generateRandomString(5); // `aZ3fG`
217
- * generateRandomString(); // `G5kLm2P9sQ`
212
+ * Repeats a failed operation under explicit attempt, delay, and filtering policies.
213
+ * Exhaustion, rejected filtering, and cancellation preserve the original failure.
218
214
  */
219
- function generateRandomString(length = 10) {
220
- const randomValues = crypto.getRandomValues(new Uint32Array(length));
221
- let result = "";
222
- for (let i = 0; i < length; i++) result += DEFAULT_CHARACTERS[randomValues[i] % 62];
223
- return result;
215
+ async function retryExecution(execution, options = {}) {
216
+ const { shouldRetry, signal } = options;
217
+ const { maxAttempts = 3, delayMilliseconds = 0 } = options;
218
+ validateRetryExecutionOptions(options, maxAttempts);
219
+ for (let attempt = 1;; attempt += 1) {
220
+ signal?.throwIfAborted();
221
+ try {
222
+ return await execution({
223
+ attempt,
224
+ signal
225
+ });
226
+ } catch (error) {
227
+ const isMaxAttempts = attempt === maxAttempts;
228
+ const isRetryRejected = isMaxAttempts === false && shouldRetry ? await shouldRetry(error, attempt) === false : false;
229
+ if (isMaxAttempts || isRetryRejected) throw error;
230
+ signal?.throwIfAborted();
231
+ await waitForRetry(typeof delayMilliseconds === "function" ? await delayMilliseconds(error, attempt) : delayMilliseconds, signal);
232
+ }
233
+ }
224
234
  }
225
235
  //#endregion
226
- //#region src/hono/hono.execution.ts
227
- function proceedUnhandledError(error) {
228
- const errorId = generateRandomString(6);
229
- const errorMessage = error instanceof Error ? error.stack ?? error.message : JSON.stringify(error);
230
- log.error(`Unhandled error: ${red(errorMessage)}`, errorId);
231
- return failure({
232
- status: 500,
233
- error: `Internal server error | ${errorId}`
236
+ //#region src/execution/execution.timeout.ts
237
+ /**
238
+ * Runs an operation with a signal controlled by timeout and caller cancellation.
239
+ * The promise rejects promptly even when the operation ignores its abort signal.
240
+ */
241
+ async function executeWithTimeout(execution, options) {
242
+ const { timeoutMilliseconds, signal: callerSignal } = options;
243
+ validateExecutionTimeout(timeoutMilliseconds);
244
+ const controller = new AbortController();
245
+ const forwardCallerAbort = () => controller.abort(callerSignal?.reason);
246
+ const rejectOnAbort = () => rejectAbortedExecution?.(controller.signal.reason);
247
+ let rejectAbortedExecution;
248
+ if (callerSignal?.aborted) controller.abort(callerSignal.reason);
249
+ else callerSignal?.addEventListener("abort", forwardCallerAbort, { once: true });
250
+ controller.signal.throwIfAborted();
251
+ const timeout = setTimeout(() => {
252
+ controller.abort(executionErrors.executionTimedOut());
253
+ }, timeoutMilliseconds);
254
+ const abortedExecution = new Promise((_, reject) => {
255
+ rejectAbortedExecution = reject;
256
+ if (controller.signal.aborted) reject(controller.signal.reason);
257
+ else controller.signal.addEventListener("abort", rejectOnAbort, { once: true });
234
258
  });
259
+ try {
260
+ const executionPromise = Promise.resolve(execution(controller.signal));
261
+ return await Promise.race([executionPromise, abortedExecution]);
262
+ } finally {
263
+ clearTimeout(timeout);
264
+ callerSignal?.removeEventListener("abort", forwardCallerAbort);
265
+ controller.signal.removeEventListener("abort", rejectOnAbort);
266
+ }
235
267
  }
268
+ //#endregion
269
+ //#region src/execution/execution.utilities.ts
236
270
  /**
237
- * Converts expected `HTTPException` values into shared API error envelopes.
238
- * Unexpected failures are logged and represented by a traceable generic response.
271
+ * Resolves synchronous and asynchronous operations through one promise-based contract.
272
+ * Failures use the supplied fallback or propagate unchanged when none is available.
273
+ *
274
+ * @example
275
+ * await safeExecute(() => fetchData(), (error) => console.error(error));
239
276
  */
240
- const onHandlerError = (error, c) => {
241
- let response;
242
- if (error instanceof HTTPException && error.status < 500) response = failure({
243
- status: error.status,
244
- error: error.message
245
- });
246
- else response = proceedUnhandledError(error);
247
- return c.json(response, response.status);
248
- };
249
- //#endregion
250
- //#region src/hono/hono.respond.ts
277
+ async function safeExecute(fn, onError) {
278
+ try {
279
+ return await fn();
280
+ } catch (error) {
281
+ if (onError) return await onError(error);
282
+ throw error;
283
+ }
284
+ }
251
285
  /**
252
- * Wraps `c.json` in the shared success envelope while preserving its literal status.
253
- * Missing response data is represented by an empty object for contract consistency.
286
+ * Captures a synchronous or asynchronous operation in a discriminated result.
287
+ * Successful values and original unknown failures remain available unchanged.
254
288
  */
255
- function respond(c, options) {
256
- return c.json(success({
257
- status: options.status,
258
- data: options.data ?? {}
259
- }), options.status);
289
+ async function captureExecution(execution) {
290
+ try {
291
+ return {
292
+ success: true,
293
+ data: await execution()
294
+ };
295
+ } catch (error) {
296
+ return {
297
+ success: false,
298
+ error
299
+ };
300
+ }
260
301
  }
302
+ //#endregion
303
+ //#region src/string-enum/string-enum.utilities.ts
261
304
  /**
262
- * Responds with downloadable binary content and its attachment headers.
263
- * Unknown/undefined content types default to `application/octet-stream`.
305
+ * Creates an immutable enum-like record from a readonly string array.
306
+ * Keys become upper snake case across camelCase, dotted, and hyphenated values.
307
+ *
308
+ * @example
309
+ * createStringEnumRecord(['foo-bar.s', 'baz'] as const); // `{ FOO_BAR_S: 'foo-bar.s', BAZ: 'baz' }`
264
310
  */
265
- function fileRespond(c, options) {
266
- c.header("Content-Disposition", `attachment; filename="${options.filename}"`);
267
- c.header("Content-Type", options.contentType ?? "application/octet-stream");
268
- return c.body(options.content, options.status);
311
+ function createStringEnumRecord(values) {
312
+ return Object.freeze(Object.fromEntries(values.map((value) => [value.replaceAll(/([a-z0-9])([A-Z])/g, "$1_$2").replaceAll(/[.-]/g, "_").toUpperCase(), value])));
269
313
  }
270
314
  //#endregion
271
315
  //#region src/hmac/hmac.enums.ts
@@ -296,25 +340,13 @@ const DEFAULT_HMAC_ALGORITHM = hmacAlgorithm.SHA_256;
296
340
  */
297
341
  const DEFAULT_HMAC_ENCODING = hmacEncoding.HEX;
298
342
  //#endregion
299
- //#region src/utilities/encoding.utilities.ts
300
- const BASE64_BYTE_CHUNK_SIZE = 32768;
301
- /**
302
- * Encodes arbitrary binary bytes into their canonical Base64 representation.
303
- * Chunked conversion avoids the argument limit imposed by `String.fromCharCode`.
304
- */
305
- function encodeBase64(bytes) {
306
- let binary = "";
307
- for (let offset = 0; offset < bytes.length; offset += BASE64_BYTE_CHUNK_SIZE) binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_BYTE_CHUNK_SIZE));
308
- return btoa(binary);
309
- }
310
- /**
311
- * Decodes a canonical or padded Base64 string into its original binary bytes.
312
- * Invalid input preserves the native `atob` failure instead of returning partial data.
313
- */
314
- function decodeBase64(value) {
315
- const binary = atob(value);
316
- return Uint8Array.from(binary, (character) => character.charCodeAt(0));
317
- }
343
+ //#region src/hmac/hmac.errors.ts
344
+ const hmacErrors = {
345
+ invalidHexSignature: () => /* @__PURE__ */ new TypeError("invalidHexSignature"),
346
+ invalidBase64Signature: () => /* @__PURE__ */ new TypeError("invalidBase64Signature"),
347
+ invalidBase64UrlSignature: () => /* @__PURE__ */ new TypeError("invalidBase64UrlSignature"),
348
+ incompatibleCryptoKey: () => /* @__PURE__ */ new TypeError("incompatibleCryptoKey")
349
+ };
318
350
  //#endregion
319
351
  //#region src/hmac/hmac.utilities.ts
320
352
  const textEncoder = new TextEncoder();
@@ -333,10 +365,12 @@ function toHMACBytes(input) {
333
365
  * Hex output remains lowercase while Base64 URL output omits conventional padding.
334
366
  */
335
367
  function encodeHMACSignature(signature, encoding) {
336
- if (encoding === "hex") return Array.from(signature, (byte) => byte.toString(16).padStart(2, "0")).join("");
337
- const base64 = encodeBase64(signature);
338
- if (encoding === "base64") return base64;
339
- return base64.replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
368
+ if (encoding === "hex") return signature.toHex();
369
+ if (encoding === "base64") return signature.toBase64();
370
+ return signature.toBase64({
371
+ alphabet: "base64url",
372
+ omitPadding: true
373
+ });
340
374
  }
341
375
  /**
342
376
  * Decodes a textual HMAC signature into bytes for cryptographic verification.
@@ -344,15 +378,15 @@ function encodeHMACSignature(signature, encoding) {
344
378
  */
345
379
  function decodeHMACSignature(signature, encoding) {
346
380
  if (encoding === "hex") {
347
- if (signature.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(signature)) throw new TypeError("Invalid hexadecimal HMAC signature");
348
- return Uint8Array.from(signature.match(/.{2}/g) ?? [], (byte) => Number.parseInt(byte, 16));
381
+ if (signature.length % 2 !== 0 || /^[0-9a-f]*$/i.test(signature) === false) throw hmacErrors.invalidHexSignature();
382
+ return Uint8Array.fromHex(signature);
349
383
  }
350
384
  if (encoding === "base64") {
351
- if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(signature)) throw new TypeError("Invalid Base64 HMAC signature");
352
- return decodeBase64(signature);
385
+ if (/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(signature) === false) throw hmacErrors.invalidBase64Signature();
386
+ return Uint8Array.fromBase64(signature, { lastChunkHandling: "strict" });
353
387
  }
354
- if (!/^[A-Za-z0-9_-]*$/.test(signature) || signature.length % 4 === 1) throw new TypeError("Invalid Base64 URL HMAC signature");
355
- return decodeBase64(signature.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(signature.length / 4) * 4, "="));
388
+ if (/^[A-Za-z0-9_-]*$/.test(signature) === false || signature.length % 4 === 1) throw hmacErrors.invalidBase64UrlSignature();
389
+ return Uint8Array.fromBase64(signature, { alphabet: "base64url" });
356
390
  }
357
391
  //#endregion
358
392
  //#region src/hmac/hmac.services.ts
@@ -368,20 +402,32 @@ var HMACService = class {
368
402
  this.encoding = options.encoding ?? DEFAULT_HMAC_ENCODING;
369
403
  }
370
404
  /**
405
+ * Imports raw secret material as a non-extractable Web Crypto HMAC key.
406
+ * The resulting key supports repeated signing and verification without reimporting.
407
+ */
408
+ async importKey(secret) {
409
+ const secretBytes = toHMACBytes(secret);
410
+ const algorithm = {
411
+ name: "HMAC",
412
+ hash: this.algorithm
413
+ };
414
+ return crypto.subtle.importKey("raw", secretBytes, algorithm, false, ["sign", "verify"]);
415
+ }
416
+ /**
371
417
  * Authenticates a payload with a secret and returns the encoded signature.
372
- * String inputs use UTF-8 while byte arrays preserve their exact byte sequence.
418
+ * Imported keys bypass repeated key creation while preserving service policy.
373
419
  *
374
420
  * @example
375
421
  * const signature = await hmacService.sign('payload', 'shared-secret');
376
422
  */
377
423
  async sign(payload, secret) {
378
- const key = await this.importSecret(secret);
424
+ const key = await this.resolveKey(secret, "sign");
379
425
  const signature = await crypto.subtle.sign("HMAC", key, toHMACBytes(payload));
380
426
  return encodeHMACSignature(new Uint8Array(signature), this.encoding);
381
427
  }
382
428
  /**
383
429
  * Verifies an encoded signature without requiring a manual equality comparison.
384
- * Invalid encodings, incorrect secrets, and altered payloads all resolve to `false`.
430
+ * Invalid signatures resolve to `false`; incompatible imported keys reject explicitly.
385
431
  *
386
432
  * @example
387
433
  * const verified = await hmacService.verify('payload', signature, 'shared-secret');
@@ -393,96 +439,342 @@ var HMACService = class {
393
439
  } catch {
394
440
  return false;
395
441
  }
396
- const key = await this.importSecret(secret);
442
+ const key = await this.resolveKey(secret, "verify");
397
443
  return crypto.subtle.verify("HMAC", key, signatureBytes, toHMACBytes(payload));
398
444
  }
399
- async importSecret(secret) {
400
- const secretBytes = toHMACBytes(secret);
401
- const algorithm = {
402
- name: "HMAC",
403
- hash: this.algorithm
404
- };
405
- return crypto.subtle.importKey("raw", secretBytes, algorithm, false, ["sign", "verify"]);
445
+ async resolveKey(secret, usage) {
446
+ if (typeof secret === "string" || secret instanceof Uint8Array) return this.importKey(secret);
447
+ const hash = Reflect.get(secret.algorithm, "hash");
448
+ const hashName = typeof hash === "object" && hash !== null && "name" in hash ? hash.name : void 0;
449
+ if ((secret.type === "secret" && secret.algorithm.name === "HMAC" && hashName === this.algorithm && secret.usages.includes(usage)) === false) throw hmacErrors.incompatibleCryptoKey();
450
+ return secret;
406
451
  }
407
452
  };
408
453
  //#endregion
409
- //#region src/http/http.constants.ts
410
- const SUCCESS_STATUS_CODES = [
454
+ //#region src/hono/hono.constants.ts
455
+ /**
456
+ * Default HTTP header carrying one request identifier across service boundaries.
457
+ * Incoming non-empty values are preserved while missing identifiers are generated.
458
+ */
459
+ const HONO_REQUEST_ID_HEADER = "X-Request-ID";
460
+ /**
461
+ * Default service label applied to request lines emitted by Hono logging middleware.
462
+ * Configured middleware may replace the label without changing message formatting.
463
+ */
464
+ const HONO_LOGGING_SERVICE = "hono";
465
+ //#endregion
466
+ //#region src/hono/hono.errors.ts
467
+ const honoErrors = {
468
+ internalServerError: () => new HTTPException(500, { message: "internalServerError" }),
469
+ invalidRequestId: () => /* @__PURE__ */ new TypeError("invalidRequestId")
470
+ };
471
+ //#endregion
472
+ //#region src/response/response.enums.ts
473
+ const responseKindsArray = ["success", "error"];
474
+ const responseKindsRecord = createStringEnumRecord(responseKindsArray);
475
+ const responseKind = responseKindsRecord;
476
+ const SUCCESS_RESPONSE_STATUSES = [
411
477
  200,
412
478
  201,
413
479
  202,
414
- 307
480
+ 206
415
481
  ];
416
- const EXCEPTION_STATUS_CODES = [
482
+ const ERROR_RESPONSE_STATUSES = [
417
483
  400,
418
484
  401,
419
485
  403,
420
486
  404,
421
487
  405,
488
+ 406,
489
+ 408,
422
490
  409,
423
- 500
491
+ 410,
492
+ 413,
493
+ 414,
494
+ 415,
495
+ 422,
496
+ 425,
497
+ 429,
498
+ 440,
499
+ 498,
500
+ 500,
501
+ 501,
502
+ 502,
503
+ 503,
504
+ 504
424
505
  ];
425
506
  //#endregion
426
- //#region src/http/http.errors.ts
507
+ //#region src/response/response.errors.ts
508
+ const responseErrors = {
509
+ invalidResponseErrorCode: () => /* @__PURE__ */ new TypeError("invalidResponseErrorCode"),
510
+ invalidSuccessResponseStatus: () => /* @__PURE__ */ new RangeError("invalidSuccessResponseStatus"),
511
+ invalidErrorResponseStatus: () => /* @__PURE__ */ new RangeError("invalidErrorResponseStatus")
512
+ };
427
513
  /**
428
- * Represents an error envelope rejected by an API request resolver.
429
- * The original status and typed code remain available for client handling.
514
+ * Represents a failed response rejected while unwrapping a remote operation.
515
+ * The original machine-readable code and response status remain available.
430
516
  */
431
- var APIRequestError = class extends Error {
517
+ var ResponseError = class extends Error {
432
518
  code;
433
519
  status;
434
520
  constructor(status, code) {
435
521
  super(code);
436
- this.name = "APIRequestError";
522
+ this.name = "ResponseError";
437
523
  this.code = code;
438
524
  this.status = status;
439
525
  }
440
526
  };
441
527
  //#endregion
442
- //#region src/http/http.resolvers.ts
528
+ //#region src/response/response.validation.ts
443
529
  /**
444
- * Converts an API contract envelope into a mutually exclusive safe result.
445
- * Only `APIError` values are normalized; rejected fetchers still reject.
530
+ * Determines whether a value is a stable camelCase response error code.
531
+ * Human-readable text and punctuation remain excluded from public envelopes.
446
532
  */
447
- async function fetchSafely(fetcher) {
448
- const response = await fetcher();
449
- if (response.kind === "error") return {
450
- error: response.error,
451
- data: null
452
- };
453
- return {
454
- error: null,
455
- data: response.data
456
- };
533
+ function isResponseErrorCode(value) {
534
+ return typeof value === "string" && /^[a-z][A-Za-z0-9]*$/.test(value);
457
535
  }
458
536
  /**
459
- * Returns successful API data and throws `APIRequestError` for a failed envelope.
460
- * Transport and programming rejections propagate without replacement.
537
+ * Determines whether a value belongs to the supported success-status catalog.
538
+ * Runtime membership mirrors the exact union exposed by `SuccessResponseStatus`.
461
539
  */
462
- async function fetchAndThrow(fetcher) {
463
- const response = await fetcher();
464
- if (response.kind === "error") throw new APIRequestError(response.status, response.error);
465
- return response.data;
540
+ function isSuccessResponseStatus(value) {
541
+ return SUCCESS_RESPONSE_STATUSES.some((status) => status === value);
542
+ }
543
+ /**
544
+ * Determines whether a value belongs to the supported error-status catalog.
545
+ * Runtime membership mirrors the exact union exposed by `ErrorResponseStatus`.
546
+ */
547
+ function isErrorResponseStatus(value) {
548
+ return ERROR_RESPONSE_STATUSES.some((status) => status === value);
549
+ }
550
+ /**
551
+ * Narrows a response result to its successful branch through the shared discriminator.
552
+ * Payload and error-code generics remain unchanged for downstream control-flow analysis.
553
+ */
554
+ function isSuccessResponse(response) {
555
+ return response.kind === responseKind.SUCCESS;
556
+ }
557
+ /**
558
+ * Narrows a response result to its failed branch through the shared discriminator.
559
+ * Payload and error-code generics remain unchanged for downstream control-flow analysis.
560
+ */
561
+ function isErrorResponse(response) {
562
+ return response.kind === responseKind.ERROR;
466
563
  }
467
564
  //#endregion
468
- //#region src/logging/logging.security.ts
565
+ //#region src/response/response.factories.ts
469
566
  /**
470
- * Checks whether a logging field name contains a configured sensitive fragment.
471
- * Matching ignores casing and separators to cover compound naming conventions.
567
+ * Creates a successful response envelope without cloning its supplied data.
568
+ * Generic inference preserves the exact payload type for downstream contracts.
472
569
  */
473
- function isSensitiveLogKey(key) {
474
- const normalizedKey = key.toLowerCase().replaceAll(/[^a-z]/g, "");
475
- return sensitiveLogKeyParts.some((sensitivePart) => normalizedKey.includes(sensitivePart));
570
+ function createSuccessResponse(status, data) {
571
+ if (isSuccessResponseStatus(status) === false) throw responseErrors.invalidSuccessResponseStatus();
572
+ return {
573
+ kind: responseKind.SUCCESS,
574
+ status,
575
+ data
576
+ };
476
577
  }
477
578
  /**
478
- * Recursively replaces values owned by sensitive object keys with the public marker.
479
- * The result reports whether any replacement occurred to avoid needless serialization.
579
+ * Creates a failed response envelope from one stable machine-readable error code.
580
+ * Human-readable or malformed values reject before crossing the response boundary.
480
581
  */
481
- function redactSensitiveValue(value) {
482
- if (Array.isArray(value)) {
483
- const entries = value.map(redactSensitiveValue);
484
- return {
485
- value: entries.map((entry) => entry.value),
582
+ function createErrorResponse(status, error) {
583
+ if (isErrorResponseStatus(status) === false) throw responseErrors.invalidErrorResponseStatus();
584
+ if (isResponseErrorCode(error) === false) throw responseErrors.invalidResponseErrorCode();
585
+ return {
586
+ kind: responseKind.ERROR,
587
+ status,
588
+ error
589
+ };
590
+ }
591
+ //#endregion
592
+ //#region src/response/response.resolvers.ts
593
+ /**
594
+ * Resolves a response envelope into a status-preserving discriminated result.
595
+ * Transport and programming failures remain observable promise rejections.
596
+ */
597
+ async function resolveResponse(operation) {
598
+ const response = await operation();
599
+ if (isErrorResponse(response)) return {
600
+ success: false,
601
+ status: response.status,
602
+ data: null,
603
+ error: response.error
604
+ };
605
+ return {
606
+ success: true,
607
+ status: response.status,
608
+ data: response.data,
609
+ error: null
610
+ };
611
+ }
612
+ /**
613
+ * Unwraps successful response data and throws `ResponseError` for a failed envelope.
614
+ * Transport and programming failures propagate without replacement or normalization.
615
+ */
616
+ async function unwrapResponse(operation) {
617
+ const response = await operation();
618
+ if (isErrorResponse(response)) throw new ResponseError(response.status, response.error);
619
+ return response.data;
620
+ }
621
+ //#endregion
622
+ //#region src/hono/hono.request-id.ts
623
+ /**
624
+ * Resolves a request identifier from response metadata before its incoming header.
625
+ * This ordering exposes identifiers generated by the request middleware downstream.
626
+ */
627
+ function getHonoRequestId(context, header = HONO_REQUEST_ID_HEADER) {
628
+ return (context.res.headers.get(header) ?? context.req.header(header))?.trim() || void 0;
629
+ }
630
+ /**
631
+ * Creates middleware that preserves or generates one request correlation identifier.
632
+ * The resolved value is returned on the response and remains available to Hono adapters.
633
+ */
634
+ function createRequestIdMiddleware(options = {}) {
635
+ const header = options.header ?? "X-Request-ID";
636
+ const createRequestId = options.createRequestId ?? (() => crypto.randomUUID());
637
+ return async (context, next) => {
638
+ const incomingRequestId = context.req.header(header)?.trim();
639
+ const generatedRequestId = incomingRequestId ? void 0 : createRequestId();
640
+ const requestId = typeof generatedRequestId === "string" ? generatedRequestId.trim() : incomingRequestId;
641
+ if (requestId === void 0 || requestId.length === 0) throw honoErrors.invalidRequestId();
642
+ context.header(header, requestId);
643
+ await next();
644
+ context.header(header, requestId);
645
+ };
646
+ }
647
+ /**
648
+ * Ready-to-use request identifier middleware backed by the shared default policy.
649
+ * Applications may use the factory when a custom header or generator is required.
650
+ */
651
+ const requestIdMiddleware = createRequestIdMiddleware();
652
+ //#endregion
653
+ //#region src/hono/hono.execution.ts
654
+ /**
655
+ * Identifies client HTTP exceptions safe to expose through the shared response envelope.
656
+ * Both status membership and camelCase error-code syntax must satisfy the contract.
657
+ */
658
+ function isExpectedHTTPException(error) {
659
+ if (error instanceof HTTPException === false) return false;
660
+ if (error.status >= 500) return false;
661
+ const isSupportedStatus = isErrorResponseStatus(error.status);
662
+ const isMachineReadableCode = isResponseErrorCode(error.message);
663
+ return isSupportedStatus && isMachineReadableCode;
664
+ }
665
+ /**
666
+ * Creates a Hono error handler that preserves expected machine-readable exceptions.
667
+ * Unexpected failures invoke application reporting before returning a stable fallback.
668
+ */
669
+ function createHonoErrorHandler(options) {
670
+ return (error, context) => {
671
+ if (isExpectedHTTPException(error)) {
672
+ const response = createErrorResponse(error.status, error.message);
673
+ return context.json(response, response.status);
674
+ }
675
+ options.onUnexpectedError(error, context, getHonoRequestId(context));
676
+ const fallback = honoErrors.internalServerError();
677
+ const status = fallback.status;
678
+ const response = createErrorResponse(status, fallback.message);
679
+ return context.json(response, status);
680
+ };
681
+ }
682
+ //#endregion
683
+ //#region src/logging/logging.enums.ts
684
+ const logLevelsArray = [
685
+ "info",
686
+ "warn",
687
+ "error"
688
+ ];
689
+ const logLevelsRecord = createStringEnumRecord(logLevelsArray);
690
+ const logLevel = logLevelsRecord;
691
+ //#endregion
692
+ //#region src/logging/logging.constants.ts
693
+ /**
694
+ * Terminal color configuration for every supported logging level.
695
+ * The `LogLevel` contract prevents missing or unsupported level entries.
696
+ */
697
+ const logLevelColors = {
698
+ [logLevel.INFO]: blue,
699
+ [logLevel.WARN]: yellow,
700
+ [logLevel.ERROR]: red
701
+ };
702
+ /**
703
+ * Public placeholder written instead of sensitive query and JSON values.
704
+ * One stable marker keeps redacted output recognizable across log consumers.
705
+ */
706
+ const REDACTED_LOG_VALUE = "[redacted]";
707
+ /**
708
+ * Number of characters retained from each edge of an oversized request body.
709
+ * The complete preview includes both edges separated by one explicit ellipsis.
710
+ */
711
+ const LOG_BODY_PREVIEW_EDGE_LENGTH = 30;
712
+ /**
713
+ * Safe request-body placeholder emitted instead of reading multipart content.
714
+ * Avoiding multipart reads prevents file payloads from entering logs or memory copies.
715
+ */
716
+ const MULTIPART_LOG_BODY = "[multipart]";
717
+ /**
718
+ * Normalized key fragments treated as sensitive by logging redaction.
719
+ * Matching ignores case and separators so compound field names remain covered.
720
+ */
721
+ const sensitiveLogKeyParts = [
722
+ "password",
723
+ "passwd",
724
+ "token",
725
+ "secret",
726
+ "authorization",
727
+ "apikey",
728
+ "credential"
729
+ ];
730
+ /**
731
+ * Terminal colors assigned to the supported HTTP response status ranges.
732
+ * Unlisted ranges intentionally retain the terminal's default text appearance.
733
+ */
734
+ const httpStatusColors = [
735
+ {
736
+ range: [200, 299],
737
+ color: green
738
+ },
739
+ {
740
+ range: [400, 499],
741
+ color: yellow
742
+ },
743
+ {
744
+ range: [500, 599],
745
+ color: red
746
+ }
747
+ ];
748
+ //#endregion
749
+ //#region src/logging/logging.errors.ts
750
+ const loggingErrors = { invalidBodyPreviewEdgeLength: () => /* @__PURE__ */ new RangeError("invalidBodyPreviewEdgeLength") };
751
+ //#endregion
752
+ //#region src/logging/logging.security.ts
753
+ /**
754
+ * Checks whether a logging field name contains a configured sensitive fragment.
755
+ * Matching ignores casing and separators to cover compound naming conventions.
756
+ */
757
+ function isSensitiveLogKey(key) {
758
+ const normalizedKey = key.toLowerCase().replaceAll(/[^a-z]/g, "");
759
+ return sensitiveLogKeyParts.some((sensitivePart) => normalizedKey.includes(sensitivePart));
760
+ }
761
+ /**
762
+ * Recursively replaces values owned by sensitive object keys with the public marker.
763
+ * The result reports whether any replacement occurred to avoid needless serialization.
764
+ */
765
+ function redactSensitiveValueResult(value, seenValues = /* @__PURE__ */ new WeakMap()) {
766
+ if (Array.isArray(value)) {
767
+ const existingValue = seenValues.get(value);
768
+ if (existingValue) return {
769
+ value: existingValue,
770
+ redacted: false
771
+ };
772
+ const redactedValue = [];
773
+ seenValues.set(value, redactedValue);
774
+ const entries = value.map((entry) => redactSensitiveValueResult(entry, seenValues));
775
+ redactedValue.push(...entries.map((entry) => entry.value));
776
+ return {
777
+ value: redactedValue,
486
778
  redacted: entries.some((entry) => entry.redacted)
487
779
  };
488
780
  }
@@ -490,28 +782,48 @@ function redactSensitiveValue(value) {
490
782
  value,
491
783
  redacted: false
492
784
  };
785
+ const prototype = Object.getPrototypeOf(value);
786
+ if (prototype !== Object.prototype && prototype !== null) return {
787
+ value,
788
+ redacted: false
789
+ };
790
+ const existingValue = seenValues.get(value);
791
+ if (existingValue) return {
792
+ value: existingValue,
793
+ redacted: false
794
+ };
493
795
  let redacted = false;
494
- const entries = Object.entries(value).map(([key, entryValue]) => {
796
+ const redactedValue = Object.create(prototype);
797
+ seenValues.set(value, redactedValue);
798
+ for (const [key, entryValue] of Object.entries(value)) {
495
799
  if (isSensitiveLogKey(key)) {
496
800
  redacted = true;
497
- return [key, REDACTED_LOG_VALUE];
801
+ redactedValue[key] = REDACTED_LOG_VALUE;
802
+ continue;
498
803
  }
499
- const nestedEntry = redactSensitiveValue(entryValue);
804
+ const nestedEntry = redactSensitiveValueResult(entryValue, seenValues);
500
805
  redacted ||= nestedEntry.redacted;
501
- return [key, nestedEntry.value];
502
- });
806
+ redactedValue[key] = nestedEntry.value;
807
+ }
503
808
  return {
504
- value: Object.fromEntries(entries),
809
+ value: redactedValue,
505
810
  redacted
506
811
  };
507
812
  }
508
813
  /**
814
+ * Recursively replaces values owned by sensitive keys in an arbitrary structure.
815
+ * Arrays and safe values retain their original ordering and primitive identity.
816
+ */
817
+ function redactSensitiveValue(value) {
818
+ return redactSensitiveValueResult(value).value;
819
+ }
820
+ /**
509
821
  * Replaces sensitive query values using partial, case-insensitive key matching.
510
822
  * A new collection is returned so the caller's search parameters remain unchanged.
511
823
  */
512
824
  function redactSensitiveSearchParams(searchParams) {
513
- const redactedSearchParams = new URLSearchParams(searchParams);
514
- for (const key of new Set(redactedSearchParams.keys())) if (isSensitiveLogKey(key)) redactedSearchParams.set(key, REDACTED_LOG_VALUE);
825
+ const redactedSearchParams = new URLSearchParams();
826
+ for (const [key, value] of searchParams) redactedSearchParams.append(key, isSensitiveLogKey(key) ? REDACTED_LOG_VALUE : value);
515
827
  return redactedSearchParams;
516
828
  }
517
829
  /**
@@ -519,9 +831,8 @@ function redactSensitiveSearchParams(searchParams) {
519
831
  * Invalid JSON and payloads without matching keys are returned byte-for-byte unchanged.
520
832
  */
521
833
  function redactSensitiveJSON(json) {
522
- if (json === void 0 || null) return json;
523
834
  try {
524
- const result = redactSensitiveValue(JSON.parse(json));
835
+ const result = redactSensitiveValueResult(JSON.parse(json));
525
836
  return result.redacted ? JSON.stringify(result.value) : json;
526
837
  } catch {
527
838
  return json;
@@ -530,6 +841,15 @@ function redactSensitiveJSON(json) {
530
841
  //#endregion
531
842
  //#region src/logging/logging.utilities.ts
532
843
  /**
844
+ * Extracts useful trace context from an unknown failure without inventing a message.
845
+ * Strings pass through directly, while native errors prefer their complete stack.
846
+ */
847
+ function normalizeLogError(error) {
848
+ if (typeof error === "string") return error;
849
+ if (error instanceof Error === false) return void 0;
850
+ return error.stack ?? error.message;
851
+ }
852
+ /**
533
853
  * Selects a terminal color function for one HTTP response status.
534
854
  * Successes are green, client errors yellow, and server errors red.
535
855
  */
@@ -539,358 +859,205 @@ function getColoredHTTPStatus(status) {
539
859
  });
540
860
  return statusColor ? statusColor.color : (text) => text;
541
861
  }
542
- //#endregion
543
- //#region src/logging/logging.middleware.ts
544
- /**
545
- * Logs Hono request metadata with query parameters and a normalized body preview.
546
- * Sensitive values are redacted by partial key match before output is written.
547
- */
548
- const loggingMiddleware = async (c, next) => {
549
- const contentType = c.req.header("content-type");
550
- const searchParams = redactSensitiveSearchParams(new URL(c.req.url).searchParams).toString();
551
- const body = contentType?.includes("multipart/form-data") ? "[multipart]" : redactSensitiveJSON((await c.req.raw.clone().text()).replaceAll(/\s+/g, " ").trim());
552
- const bodyPreview = body.length > 60 ? `${body.slice(0, 30)}…${body.slice(-30)}` : body;
553
- const startTime = performance.now();
554
- await next();
555
- const duration = Math.round(performance.now() - startTime);
556
- const coloredMethod = bold(blue(c.req.method.padEnd(4)));
557
- const coloredStatus = getColoredHTTPStatus(c.res.status)(String(c.res.status).padEnd(4));
558
- const coloredTime = dim(`${duration}ms`.padStart(6));
559
- const coloredPath = white(c.req.path.padEnd(32));
560
- const coloredSearchParams = searchParams ? dim(` (${searchParams})`) : "";
561
- const coloredBody = bodyPreview ? dim(` ${bodyPreview}`) : "";
562
- log.info(`${coloredMethod} ${coloredStatus} ${coloredTime} ${coloredPath}${coloredSearchParams}${coloredBody}`, "hono");
563
- };
564
- //#endregion
565
- //#region src/upload/upload.enums.ts
566
- const fileFormatsArray = [...[
567
- "png",
568
- "jpg",
569
- "webp",
570
- "avif",
571
- "heic",
572
- "svg"
573
- ], ...[
574
- "pdf",
575
- "rtf",
576
- "txt"
577
- ]];
578
- const fileFormatsRecord = createStringEnumRecord(fileFormatsArray);
579
- const fileFormat = fileFormatsRecord;
580
- const uploadValidationErrorsArray = [
581
- "empty_file",
582
- "unsupported_file_format",
583
- "file_size_exceeded",
584
- "files_count_exceeded"
585
- ];
586
- const uploadValidationErrorsRecord = createStringEnumRecord(uploadValidationErrorsArray);
587
- const uploadValidationError = uploadValidationErrorsRecord;
588
- //#endregion
589
- //#region src/upload/upload.constants.ts
590
862
  /**
591
- * Canonical metadata shared by upload controls and runtime validation.
592
- * Every supported format defines display, MIME, and extension values.
863
+ * Formats one completed HTTP request as a compact colorized terminal line.
864
+ * Empty query and body values are omitted without disturbing column alignment.
593
865
  */
594
- const fileFormatsConfig = {
595
- [fileFormatsRecord.PNG]: {
596
- name: "PNG",
597
- mimeTypes: ["image/png"],
598
- extensions: [".png"]
599
- },
600
- [fileFormatsRecord.JPG]: {
601
- name: "JPG",
602
- mimeTypes: ["image/jpeg"],
603
- extensions: [".jpg", ".jpeg"]
604
- },
605
- [fileFormatsRecord.SVG]: {
606
- name: "SVG",
607
- mimeTypes: ["image/svg+xml"],
608
- extensions: [".svg"]
609
- },
610
- [fileFormatsRecord.WEBP]: {
611
- name: "WEBP",
612
- mimeTypes: ["image/webp"],
613
- extensions: [".webp"]
614
- },
615
- [fileFormatsRecord.AVIF]: {
616
- name: "AVIF",
617
- mimeTypes: ["image/avif"],
618
- extensions: [".avif"]
619
- },
620
- [fileFormatsRecord.HEIC]: {
621
- name: "HEIC",
622
- mimeTypes: ["image/heic", "image/heif"],
623
- extensions: [".heic", ".heif"]
624
- },
625
- [fileFormatsRecord.PDF]: {
626
- name: "PDF",
627
- mimeTypes: ["application/pdf"],
628
- extensions: [".pdf"]
629
- },
630
- [fileFormatsRecord.RTF]: {
631
- name: "RTF",
632
- mimeTypes: ["application/rtf"],
633
- extensions: [".rtf"]
634
- },
635
- [fileFormatsRecord.TXT]: {
636
- name: "TXT",
637
- mimeTypes: ["text/plain"],
638
- extensions: [".txt"]
639
- }
640
- };
641
- //#endregion
642
- //#region src/upload/upload.factory.ts
643
- /**
644
- * Defines one shared upload policy while preserving its literal format tuple.
645
- * The resulting preset can drive schemas, picker hints, and batch validation.
646
- *
647
- * @example
648
- * const imageUploadPreset = defineUploadPreset({
649
- * formats: [fileFormat.JPG, fileFormat.PNG, fileFormat.WEBP],
650
- * maxFileSize: 8 * 1024 * 1024,
651
- * maxFilesCount: 1,
652
- * });
653
- */
654
- function defineUploadPreset(preset) {
655
- return preset;
866
+ function formatHTTPRequestLog(options) {
867
+ return `${bold(blue(options.method.padEnd(4)))} ${getColoredHTTPStatus(options.status)(String(options.status).padEnd(4))} ${dim(`${options.duration}ms`.padStart(6))} ${white(options.path.padEnd(32))}${options.searchParams ? dim(` (${options.searchParams})`) : ""}${options.bodyPreview ? dim(` ${options.bodyPreview}`) : ""}${options.requestId ? dim(` [${options.requestId}]`) : ""}`;
656
868
  }
657
- //#endregion
658
- //#region src/upload/upload.presets.ts
659
- /**
660
- * Default maximum size of one file accepted by the built-in upload presets.
661
- * The 20 MiB boundary matches the existing upload component policy.
662
- */
663
- const DEFAULT_UPLOAD_MAX_FILE_SIZE = 20971520;
664
- /**
665
- * Ready-to-use policy for every image format supported by the upload catalog.
666
- * Each image may occupy up to 20 MiB while collection capacity remains unrestricted.
667
- */
668
- const imageUploadPreset = defineUploadPreset({
669
- formats: [
670
- fileFormat.PNG,
671
- fileFormat.JPG,
672
- fileFormat.WEBP,
673
- fileFormat.AVIF,
674
- fileFormat.HEIC
675
- ],
676
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
677
- });
678
- /**
679
- * Ready-to-use policy for every image format supporting transparency (alpha channel).
680
- * Each image may occupy up to 20 MiB while collection capacity remains unrestricted.
681
- */
682
- const imageTransparentUploadPreset = defineUploadPreset({
683
- formats: [
684
- fileFormat.PNG,
685
- fileFormat.WEBP,
686
- fileFormat.SVG
687
- ],
688
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
689
- });
690
- /**
691
- * Ready-to-use policy for every document format supported by the upload catalog.
692
- * Each document may occupy up to 20 MiB while collection capacity remains unrestricted.
693
- */
694
- const documentUploadPreset = defineUploadPreset({
695
- formats: [
696
- fileFormat.PDF,
697
- fileFormat.RTF,
698
- fileFormat.TXT
699
- ],
700
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
701
- });
702
- //#endregion
703
- //#region src/upload/upload.utilities.ts
704
869
  /**
705
- * Extracts a normalized trailing extension from a complete file name.
706
- * Returns an empty string when no valid dot-delimited suffix exists.
870
+ * Reads one request clone and returns a normalized, redacted, and bounded body preview.
871
+ * Multipart requests return a safe marker without reading or retaining uploaded content.
707
872
  */
708
- function getFileExtension(fileName) {
709
- const extensionIndex = fileName.lastIndexOf(".");
710
- return extensionIndex > 0 && extensionIndex < fileName.length - 1 ? fileName.slice(extensionIndex).toLowerCase() : "";
711
- }
712
- /**
713
- * Normalizes a configured extension to a lowercase dot-prefixed value.
714
- * Existing prefixes remain intact so repeated normalization is stable.
715
- */
716
- function normalizeFileExtension(extension) {
717
- const normalizedExtension = extension.toLowerCase();
718
- return normalizedExtension.startsWith(".") ? normalizedExtension : `.${normalizedExtension}`;
719
- }
720
- /**
721
- * Compares a concrete MIME type with an exact or wildcard configuration.
722
- * Wildcards match every subtype belonging to the configured media group.
723
- */
724
- function matchesMimeType(fileMimeType, configuredMimeType) {
725
- if (configuredMimeType.endsWith("/*")) return fileMimeType.startsWith(configuredMimeType.slice(0, -1));
726
- return fileMimeType === configuredMimeType;
873
+ async function createHTTPRequestBodyPreview(request, contentType, edgeLength = 30) {
874
+ if (Number.isInteger(edgeLength) === false || edgeLength < 1) throw loggingErrors.invalidBodyPreviewEdgeLength();
875
+ if (contentType?.includes("multipart/form-data")) return MULTIPART_LOG_BODY;
876
+ const redactedBody = redactSensitiveJSON((await request.clone().text()).replaceAll(/\s+/g, " ").trim());
877
+ if (redactedBody.length <= edgeLength * 2) return redactedBody;
878
+ return `${redactedBody.slice(0, edgeLength)}…${redactedBody.slice(-edgeLength)}`;
727
879
  }
880
+ //#endregion
881
+ //#region src/logging/logging.services.ts
728
882
  /**
729
- * Checks whether a file MIME type belongs to one selected format.
730
- * File names and extensions cannot make an unsupported MIME type valid.
883
+ * Writes one fully formatted logging line through the standard console destination.
884
+ * Keeping the sink explicit allows configured loggers to replace output independently.
731
885
  */
732
- function isFileMimeTypeSupported(file, formats) {
733
- return formats.some((format) => fileFormatsConfig[format].mimeTypes.some((mimeType) => matchesMimeType(file.type, mimeType)));
886
+ function writeConsoleLog(line) {
887
+ console.log(line);
734
888
  }
735
889
  /**
736
- * Checks whether a file extension belongs to one selected format.
737
- * The comparison is case-insensitive and requires a complete suffix.
890
+ * Writes one normalized terminal message for the requested logging level and service.
891
+ * Recognized error context retains the primary timestamp on a subordinate aligned line.
738
892
  */
739
- function isFileExtensionSupported(file, formats) {
740
- const fileExtension = getFileExtension(file.name);
741
- return formats.some((format) => fileFormatsConfig[format].extensions.some((extension) => normalizeFileExtension(extension) === fileExtension));
893
+ function writeLog(message, level, service = "log", error, sink = writeConsoleLog) {
894
+ const timestamp = dim(getFormattedTime());
895
+ const serviceName = logLevelColors[level](service.padEnd(12));
896
+ const formattedMessage = white(message);
897
+ const trace = normalizeLogError(error);
898
+ sink(`[${timestamp}] ${serviceName} | ${formattedMessage}`);
899
+ if (trace) sink(`[${timestamp}] ${red("↳ trace").padEnd(18)} | ${dim(trace)}`);
742
900
  }
743
901
  /**
744
- * Checks whether a file matches one configured MIME type or extension.
745
- * This permissive predicate supports clients where MIME metadata is absent.
902
+ * Creates a logger permanently bound to one service label and output destination.
903
+ * Calls retain the shared formatting while avoiding repeated service arguments.
746
904
  */
747
- function isFileFormatSupported(file, formats) {
748
- return isFileMimeTypeSupported(file, formats) || isFileExtensionSupported(file, formats);
905
+ function createLogger(options) {
906
+ return {
907
+ info(message) {
908
+ writeLog(message, logLevel.INFO, options.service, void 0, options.sink);
909
+ },
910
+ warn(message) {
911
+ writeLog(message, logLevel.WARN, options.service, void 0, options.sink);
912
+ },
913
+ error(message, error) {
914
+ writeLog(message, logLevel.ERROR, options.service, error, options.sink);
915
+ }
916
+ };
749
917
  }
750
918
  /**
751
- * Builds a native file-picker hint from configured MIME types and extensions.
752
- * Duplicate values are removed while their configuration order is retained.
919
+ * Writes timestamped and colorized messages using a stable service column.
920
+ * Error messages may include a stack trace on a subordinate second line.
753
921
  */
754
- function createUploadAccept(formats) {
755
- const acceptedValues = formats.flatMap((format) => [...fileFormatsConfig[format].mimeTypes, ...fileFormatsConfig[format].extensions]);
756
- return [...new Set(acceptedValues)].join(",");
757
- }
922
+ const log = {
923
+ /**
924
+ * Writes an informational message with an optional service label.
925
+ * Missing service names use the shared `log` fallback label.
926
+ */
927
+ info(message, service) {
928
+ writeLog(message, logLevel.INFO, service);
929
+ },
930
+ /**
931
+ * Writes a warning message with an optional service label.
932
+ * Missing service names use the shared `log` fallback label.
933
+ */
934
+ warn(message, service) {
935
+ writeLog(message, logLevel.WARN, service);
936
+ },
937
+ /**
938
+ * Writes an error message with optional service and unknown failure context.
939
+ * Native errors and strings render a normalized subordinate trace line.
940
+ */
941
+ error(message, service, error) {
942
+ writeLog(message, logLevel.ERROR, service, error);
943
+ }
944
+ };
758
945
  //#endregion
759
- //#region src/upload/upload.schemas.ts
946
+ //#region src/hono/hono.logging.ts
947
+ /**
948
+ * Creates Hono middleware with configurable body capture, correlation, and output.
949
+ * Sensitive values are redacted before the completed request line reaches its writer.
950
+ */
951
+ function createLoggingMiddleware(options = {}) {
952
+ const { bodyPreviewEdgeLength } = options;
953
+ if (bodyPreviewEdgeLength !== void 0 && (Number.isInteger(bodyPreviewEdgeLength) === false || bodyPreviewEdgeLength < 1)) throw loggingErrors.invalidBodyPreviewEdgeLength();
954
+ const service = options.service ?? "hono";
955
+ const includeBody = options.includeBody ?? true;
956
+ const requestIdHeader = options.requestIdHeader ?? "X-Request-ID";
957
+ const write = options.write ?? ((message, serviceName) => log.info(message, serviceName));
958
+ return async (context, next) => {
959
+ const contentType = context.req.header("content-type");
960
+ const searchParams = redactSensitiveSearchParams(new URL(context.req.url).searchParams).toString();
961
+ const bodyPreview = includeBody ? await createHTTPRequestBodyPreview(context.req.raw, contentType, bodyPreviewEdgeLength) : void 0;
962
+ const startTime = performance.now();
963
+ await next();
964
+ const duration = Math.round(performance.now() - startTime);
965
+ const message = formatHTTPRequestLog({
966
+ method: context.req.method,
967
+ status: context.res.status,
968
+ duration,
969
+ path: context.req.path,
970
+ searchParams,
971
+ bodyPreview,
972
+ requestId: getHonoRequestId(context, requestIdHeader)
973
+ });
974
+ write(message, service);
975
+ };
976
+ }
760
977
  /**
761
- * Builds a reusable Zod schema for one uploaded file with format and size.
762
- * MIME matching is strict unless extension fallback is explicitly enabled.
978
+ * Ready-to-use Hono request logger backed by the shared default logging policy.
979
+ * Applications may use the factory when request capture or output needs configuration.
763
980
  */
764
- function zodUploadFileSchema(options) {
765
- const { formats, maxFileSize, extensionFallback = false } = options;
766
- return z$1.file().min(1, uploadValidationError.EMPTY_FILE).max(maxFileSize, uploadValidationError.FILE_SIZE_EXCEEDED).refine((file) => extensionFallback ? isFileFormatSupported(file, formats) : isFileMimeTypeSupported(file, formats), uploadValidationError.UNSUPPORTED_FILE_FORMAT);
767
- }
981
+ const loggingMiddleware = createLoggingMiddleware();
768
982
  //#endregion
769
- //#region src/upload/upload.validation.ts
983
+ //#region src/hono/hono.respond.ts
770
984
  /**
771
- * Validates one file against configured format and size constraints.
772
- * Returns the first stable error key or nothing for a valid file.
985
+ * Wraps `c.json` in the shared success envelope while preserving its literal status.
986
+ * Missing response data is represented by an empty object for contract consistency.
773
987
  */
774
- function validateUploadFile(file, formats, maxFileSize) {
775
- if (file.size === 0) return uploadValidationError.EMPTY_FILE;
776
- if (isFileFormatSupported(file, formats) === false) return uploadValidationError.UNSUPPORTED_FILE_FORMAT;
777
- if (file.size > maxFileSize) return uploadValidationError.FILE_SIZE_EXCEEDED;
988
+ function respond(c, options) {
989
+ const response = createSuccessResponse(options.status, options.data ?? {});
990
+ return c.json(response, options.status);
778
991
  }
779
992
  /**
780
- * Validates an incoming collection while preserving every accepted file.
781
- * Returns accepted entries and the final rejection key from the batch.
993
+ * Responds with downloadable binary content and its attachment headers.
994
+ * Unknown/undefined content types default to `application/octet-stream`.
782
995
  */
783
- function validateUploadFiles(incomingFiles, options) {
784
- const { currentFilesCount, formats, maxFileSize, maxFilesCount } = options;
785
- const availableFilesCount = maxFilesCount === void 0 ? Infinity : maxFilesCount - currentFilesCount;
786
- const acceptedFiles = [];
787
- let validationError;
788
- for (const file of incomingFiles) {
789
- const fileValidationError = validateUploadFile(file, formats, maxFileSize) ?? (acceptedFiles.length >= availableFilesCount ? uploadValidationError.FILES_COUNT_EXCEEDED : void 0);
790
- if (fileValidationError) {
791
- validationError = fileValidationError;
792
- continue;
793
- }
794
- acceptedFiles.push(file);
795
- }
796
- return {
797
- acceptedFiles,
798
- validationError
799
- };
996
+ function fileRespond(c, options) {
997
+ c.header("Content-Disposition", `attachment; filename="${options.filename}"`);
998
+ c.header("Content-Type", options.contentType ?? "application/octet-stream");
999
+ return c.body(options.content, options.status);
800
1000
  }
801
1001
  //#endregion
802
- //#region src/utilities/execution.utilities.ts
1002
+ //#region src/jwt/jwt.constants.ts
803
1003
  /**
804
- * Resolves synchronous and asynchronous executions through one promise-based contract.
805
- * Failures use the supplied fallback or propagate unchanged when none is available.
806
- *
807
- * @example
808
- * await safeExecute(() => fetchData(), (error) => console.error(error));
1004
+ * Default symmetric algorithm applied to JWT signing and verification operations.
1005
+ * The policy uses `HS256` unless a service instance explicitly selects another value.
809
1006
  */
810
- async function safeExecute(fn, onError) {
811
- try {
812
- return await fn();
813
- } catch (error) {
814
- if (onError) return await onError(error);
815
- throw error;
816
- }
817
- }
1007
+ const DEFAULT_JWT_ALGORITHM = "HS256";
818
1008
  /**
819
- * Measures an asynchronous execution while preserving its resolved result.
820
- * Rejected executions propagate unchanged and do not produce a measurement.
821
- *
822
- * @example
823
- * const { result, executionTime } = await measureExecutionTime(async () => {
824
- * return await fetchData();
825
- * });
826
- * console.log(`Execution time: ${executionTime}ms`);
1009
+ * Default token lifetime applied when a signing operation supplies no override.
1010
+ * The duration is expressed in seconds and represents exactly fifteen minutes.
827
1011
  */
828
- async function measureExecutionTime(execution) {
829
- const startedAt = performance.now();
830
- const result = await execution();
831
- const executionTime = performance.now() - startedAt;
832
- return {
833
- result,
834
- executionTime: Math.round(executionTime)
835
- };
836
- }
1012
+ const DEFAULT_JWT_EXPIRATION_SECONDS = 900;
837
1013
  //#endregion
838
- //#region src/zod-bulk/zod-bulk.schemas.ts
839
- const DEFAULT_ERROR_MESSAGE$1 = "Selection identifiers must be provided.";
1014
+ //#region src/jwt/jwt.errors.ts
1015
+ const jwtErrors = { invalidExpirationSeconds: () => /* @__PURE__ */ new RangeError("invalidExpirationSeconds") };
1016
+ //#endregion
1017
+ //#region src/jwt/jwt.validation.ts
840
1018
  /**
841
- * Builds a strict selection contract for bulk operations across paginated data.
842
- * The identifier schema is shared by explicit and all-matching selection modes.
843
- *
844
- * `include` targets only identifiers listed by the client. `exclude` targets every
845
- * item matching the accompanying search snapshot except excluded identifiers.
846
- *
847
- * @example
848
- * const assetBulkSelectionSchema = zodBulkSelectionSchema({
849
- * identifierSchema: z.string().min(1),
850
- * });
1019
+ * Validates a JWT lifetime before it participates in expiration calculation.
1020
+ * Whole positive, zero, and negative seconds are accepted within the safe range.
851
1021
  */
852
- function zodBulkSelectionSchema({ identifierSchema }) {
853
- const identifiersSchema = z$1.array(identifierSchema).refine((identifiers) => new Set(identifiers).size === identifiers.length, { message: DEFAULT_ERROR_MESSAGE$1 });
854
- return z$1.discriminatedUnion("mode", [z$1.object({
855
- mode: z$1.literal("include"),
856
- identifiers: identifiersSchema
857
- }).strict(), z$1.object({
858
- mode: z$1.literal("exclude"),
859
- excludedIdentifiers: identifiersSchema
860
- }).strict()]);
1022
+ function validateJWTExpirationSeconds(expirationSeconds) {
1023
+ if (Number.isSafeInteger(expirationSeconds) === false) throw jwtErrors.invalidExpirationSeconds();
861
1024
  }
862
1025
  //#endregion
863
- //#region src/zod-jwt/zod-jwt.services.ts
1026
+ //#region src/jwt/jwt.services.ts
864
1027
  /**
865
- * Signs, decodes, and verifies JWTs with optional Zod payload validation.
1028
+ * Signs, decodes, and verifies JWTs with optional payload schema validation.
866
1029
  * A supplied schema parses payloads returned by decoding and verification.
867
1030
  */
868
- var ZodJWTService = class {
1031
+ var JWTService = class {
869
1032
  payloadSchema;
870
1033
  algorithm;
871
1034
  defaultExpirationSeconds;
872
- constructor(payloadSchema, options) {
1035
+ constructor(payloadSchema, options = {}) {
1036
+ const { algorithm = DEFAULT_JWT_ALGORITHM, defaultExpirationSeconds = 900 } = options;
1037
+ validateJWTExpirationSeconds(defaultExpirationSeconds);
873
1038
  this.payloadSchema = payloadSchema;
874
- this.algorithm = options?.algorithm ?? "HS256";
875
- this.defaultExpirationSeconds = options?.defaultExpirationSeconds ?? 900;
1039
+ this.algorithm = algorithm;
1040
+ this.defaultExpirationSeconds = defaultExpirationSeconds;
876
1041
  }
877
1042
  /**
878
1043
  * Signs a payload using the configured algorithm and expiration settings.
879
- * Per-call expiration overrides the default configured by the service.
1044
+ * Per-operation expiration overrides the default configured by the service.
880
1045
  *
881
1046
  * @example
882
1047
  * const token = await jwtService.sign({ userId: '123' }, secret, { expiresInSeconds: 300 });
883
1048
  */
884
- async sign(payload, secret, options) {
885
- const exp = Math.floor(Date.now() / 1e3) + (options?.expiresInSeconds ?? this.defaultExpirationSeconds);
1049
+ async sign(payload, secret, options = {}) {
1050
+ const { expiresInSeconds = this.defaultExpirationSeconds } = options;
1051
+ validateJWTExpirationSeconds(expiresInSeconds);
1052
+ const expirationTimestamp = Math.floor(Date.now() / 1e3) + expiresInSeconds;
886
1053
  return sign({
887
1054
  ...payload,
888
- exp
1055
+ exp: expirationTimestamp
889
1056
  }, secret, this.algorithm);
890
1057
  }
891
1058
  /**
892
- * Decodes without authenticating it and parses its payload when a schema exists.
893
- * Schema validation failures return `null`, while malformed tokens still reject.
1059
+ * Decodes a token without authentication and parses its payload when configured.
1060
+ * Schema failures return `null`, while malformed token failures remain unchanged.
894
1061
  *
895
1062
  * @example
896
1063
  * const payload = await jwtService.decode(token);
@@ -898,12 +1065,12 @@ var ZodJWTService = class {
898
1065
  async decode(token) {
899
1066
  const { payload } = decode(token);
900
1067
  if (this.payloadSchema === void 0) return payload;
901
- const { success, data } = await this.payloadSchema.safeParseAsync(payload);
902
- return success ? data : null;
1068
+ const result = await this.payloadSchema.safeParseAsync(payload);
1069
+ return result.success ? result.data : null;
903
1070
  }
904
1071
  /**
905
1072
  * Verifies a token using the configured algorithm and parses its payload.
906
- * Signature, expiration, and schema validation failures reject the operation.
1073
+ * Authentication, expiration, and schema failures reject the operation.
907
1074
  *
908
1075
  * @example
909
1076
  * const payload = await jwtService.verifyOrThrow(token, secret);
@@ -915,142 +1082,306 @@ var ZodJWTService = class {
915
1082
  }
916
1083
  };
917
1084
  //#endregion
918
- //#region src/zod-search/zod-search.pagination.schemas.ts
1085
+ //#region src/object/object.utilities.ts
919
1086
  /**
920
- * Validates offset-based pagination shared by search request contracts.
921
- * Missing fields inside the required object receive predictable defaults.
1087
+ * Recognizes ordinary records whose direct prototype is `Object.prototype`.
1088
+ * Arrays, null, native objects, and custom class instances remain excluded.
922
1089
  */
923
- const zodPaginationSchema = z$1.object({
924
- /**
925
- * Zero-based number of records skipped before collecting a result page.
926
- * String values are coerced to support validation of URL query input.
927
- */
928
- offset: z$1.coerce.number().int().nonnegative().max(1048576).default(0),
929
- /**
930
- * Positive maximum number of records returned in a single result page.
931
- * String values are coerced to support validation of URL query input.
932
- */
933
- limit: z$1.coerce.number().int().positive().max(256).default(10)
934
- });
1090
+ function isPlainObject(value) {
1091
+ if ((typeof value === "object" && value !== null) === false || Array.isArray(value)) return false;
1092
+ return Object.getPrototypeOf(value) === Object.prototype;
1093
+ }
1094
+ //#endregion
1095
+ //#region src/random/random.errors.ts
1096
+ const randomErrors = { invalidRandomStringLength: () => /* @__PURE__ */ new RangeError("invalidRandomStringLength") };
1097
+ //#endregion
1098
+ //#region src/random/random.constants.ts
1099
+ /**
1100
+ * Alphanumeric character collection used by the default random-string generator.
1101
+ * Its fixed ordering keeps byte-to-character translation stable across runtimes.
1102
+ */
1103
+ const RANDOM_ALPHANUMERIC_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
1104
+ /**
1105
+ * Default output length used when `generateRandomString` receives no override.
1106
+ * Ten characters preserve the historical package contract for existing consumers.
1107
+ */
1108
+ const DEFAULT_RANDOM_STRING_LENGTH = 10;
1109
+ /**
1110
+ * Maximum random-byte count requested from Web Crypto in one operation.
1111
+ * The boundary follows the platform limit enforced by `crypto.getRandomValues`.
1112
+ */
1113
+ const RANDOM_BYTE_BATCH_SIZE = 65536;
1114
+ Math.floor(256 / 62) * 62;
1115
+ //#endregion
1116
+ //#region src/random/random.validation.ts
935
1117
  /**
936
- * Exposes pagination fields for composition into other object schemas.
937
- * The shape stays derived from the schema to prevent contract divergence.
1118
+ * Validates the requested random-string length before allocating random bytes.
1119
+ * Only non-negative safe integers can represent a complete output character count.
938
1120
  */
939
- const zodPaginationShape = zodPaginationSchema.shape;
1121
+ function assertRandomStringLength(length) {
1122
+ if ((Number.isSafeInteger(length) && length >= 0) === false) throw randomErrors.invalidRandomStringLength();
1123
+ }
940
1124
  //#endregion
941
- //#region src/zod-validation/zod-validation.refiners.ts
942
- const DEFAULT_ERROR_MESSAGE = "Invalid input. At least one field must be provided.";
1125
+ //#region src/random/random.utilities.ts
943
1126
  /**
944
- * Wraps a partial Zod object schema with:
945
- * 1. A runtime check ensuring at least one field is non-undefined.
946
- * 2. A `.transform()` that narrows the output type to `AtLeastOne<T>`,
947
- * making it directly assignable to domain `*Select` and `*Update` types without casting.
1127
+ * Creates a cryptographically sourced string from the alphanumeric collection.
1128
+ * Rejection sampling prevents modulo bias while bounded batches support long output.
948
1129
  *
949
1130
  * @example
950
- * zQuery(zodAtLeastOne(userSelectSchema)) // result: UserSelect ✓
1131
+ * generateRandomString(5); // `aZ3fG`
1132
+ * generateRandomString(); // `G5kLm2P9sQ`
951
1133
  */
952
- const zodAtLeastOne = (schema) => schema.superRefine((val, ctx) => {
953
- if (!Object.values(val).some((v) => v !== void 0)) ctx.addIssue({
954
- code: "custom",
955
- message: DEFAULT_ERROR_MESSAGE
956
- });
957
- }).transform((val) => val);
1134
+ function generateRandomString(length = 10) {
1135
+ assertRandomStringLength(length);
1136
+ let result = "";
1137
+ while (result.length < length) {
1138
+ const remainingLength = length - result.length;
1139
+ const batchSize = Math.min(Math.max(remainingLength * 2, 1), RANDOM_BYTE_BATCH_SIZE);
1140
+ const randomBytes = crypto.getRandomValues(new Uint8Array(batchSize));
1141
+ for (const randomByte of randomBytes) {
1142
+ if (randomByte >= 248) continue;
1143
+ result += RANDOM_ALPHANUMERIC_CHARACTERS.charAt(randomByte % 62);
1144
+ if (result.length === length) break;
1145
+ }
1146
+ }
1147
+ return result;
1148
+ }
958
1149
  //#endregion
959
- //#region src/zod-search/zod-search.schemas.ts
1150
+ //#region src/upload/upload.enums.ts
1151
+ const uploadValidationErrorsArray = [
1152
+ "emptyFile",
1153
+ "unsupportedFileFormat",
1154
+ "fileSizeExceeded",
1155
+ "filesCountExceeded"
1156
+ ];
1157
+ const uploadValidationErrorsRecord = createStringEnumRecord(uploadValidationErrorsArray);
1158
+ const uploadValidationError = uploadValidationErrorsRecord;
1159
+ //#endregion
1160
+ //#region src/upload/upload.errors.ts
1161
+ const uploadErrors = {
1162
+ uploadFormatRequired: () => /* @__PURE__ */ new TypeError("uploadFormatRequired"),
1163
+ invalidMimeType: () => /* @__PURE__ */ new TypeError("invalidMimeType"),
1164
+ invalidFileExtension: () => /* @__PURE__ */ new TypeError("invalidFileExtension"),
1165
+ invalidMaxFileSize: () => /* @__PURE__ */ new RangeError("invalidMaxFileSize"),
1166
+ invalidMaxFilesCount: () => /* @__PURE__ */ new RangeError("invalidMaxFilesCount"),
1167
+ invalidCurrentFilesCount: () => /* @__PURE__ */ new RangeError("invalidCurrentFilesCount")
1168
+ };
1169
+ //#endregion
1170
+ //#region src/upload/upload.constants.ts
960
1171
  /**
961
- * Optional text query shared by search request contracts.
962
- * Provided values are trimmed and must contain visible text.
1172
+ * Matches concrete MIME types, one media wildcard, or the complete wildcard.
1173
+ * Tokens follow the transport-safe characters accepted by standard metadata.
963
1174
  */
964
- const zodSearchQuerySchema = z$1.string().trim().min(1).optional();
1175
+ const UPLOAD_MIME_TYPE_PATTERN = /^(?:\*\/\*|[A-Za-z0-9!#$&^_.+-]+\/(?:\*|[A-Za-z0-9!#$&^_.+-]+))$/;
965
1176
  /**
966
- * Makes every supplied filter optional while requiring one defined value.
967
- * Top-level optionality is added later for both composition modes equally.
1177
+ * Matches one alphanumeric file extension with an optional leading dot.
1178
+ * Compound and path-like values remain invalid format declarations.
968
1179
  */
969
- const createZodSearchWhereSchema = (filters) => zodAtLeastOne(filters.partial());
1180
+ const UPLOAD_FILE_EXTENSION_PATTERN = /^\.?[A-Za-z0-9]+$/;
1181
+ //#endregion
1182
+ //#region src/upload/upload.utilities.ts
970
1183
  /**
971
- * Builds a strict schema for reusable search request contracts.
972
- * The optional `where` object must contain one defined filter.
973
- *
974
- * Query is optional and non-empty; pagination is required by default.
975
- * Literal feature flags update both runtime and inferred static shapes.
976
- *
977
- * Pass `filters` for automatic partial and non-empty validation, or use
978
- * `whereSchema` to preserve a prepared schema with custom Zod effects.
979
- *
980
- * @example
981
- * const assetSearchSchema = zodSearchSchema({
982
- * filters: assetSchema.pick({ status: true }),
983
- * });
984
- *
985
- * const refinedAssetSearchSchema = zodSearchSchema({
986
- * whereSchema: zodAtLeastOne(assetSchema.pick({ status: true }).partial()),
987
- * });
1184
+ * Extracts a normalized trailing extension from a complete file name.
1185
+ * Returns an empty string when no valid dot-delimited suffix exists.
1186
+ */
1187
+ function getFileExtension(fileName) {
1188
+ const normalizedFileName = fileName.trim();
1189
+ const extensionIndex = normalizedFileName.lastIndexOf(".");
1190
+ return extensionIndex > 0 && extensionIndex < normalizedFileName.length - 1 ? normalizedFileName.slice(extensionIndex).toLowerCase() : "";
1191
+ }
1192
+ /**
1193
+ * Normalizes a configured extension to a lowercase dot-prefixed value.
1194
+ * Existing prefixes remain intact so repeated normalization is stable.
1195
+ */
1196
+ function normalizeFileExtension(extension) {
1197
+ const normalizedExtension = extension.trim().toLowerCase();
1198
+ if (normalizedExtension.length === 0 || normalizedExtension === ".") return "";
1199
+ return normalizedExtension.startsWith(".") ? normalizedExtension : `.${normalizedExtension}`;
1200
+ }
1201
+ /**
1202
+ * Compares a concrete MIME type with an exact or wildcard configuration.
1203
+ * Wildcards match every subtype belonging to the configured media group.
1204
+ */
1205
+ function matchesMimeType(fileMimeType, configuredMimeType) {
1206
+ const normalizedFileMimeType = fileMimeType.trim().toLowerCase();
1207
+ const normalizedConfiguredMimeType = configuredMimeType.trim().toLowerCase();
1208
+ const hasMimeType = /^[^/\s]+\/[^/\s]+$/.test(normalizedFileMimeType);
1209
+ const hasConfiguredMimeType = normalizedConfiguredMimeType.length > 0;
1210
+ if (hasMimeType === false || hasConfiguredMimeType === false) return false;
1211
+ if (normalizedConfiguredMimeType === "*/*") return true;
1212
+ if (normalizedConfiguredMimeType.endsWith("/*")) {
1213
+ const mediaTypePrefix = normalizedConfiguredMimeType.slice(0, -1);
1214
+ const mimeTypeLengthValid = normalizedFileMimeType.length > mediaTypePrefix.length;
1215
+ return normalizedFileMimeType.startsWith(mediaTypePrefix) && mimeTypeLengthValid;
1216
+ }
1217
+ return normalizedFileMimeType === normalizedConfiguredMimeType;
1218
+ }
1219
+ /**
1220
+ * Checks whether a file MIME type belongs to one selected format.
1221
+ * File names and extensions cannot make an unsupported MIME type valid.
1222
+ */
1223
+ function isFileMimeTypeSupported(file, formats) {
1224
+ return formats.some((format) => format.mimeTypes.some((mimeType) => matchesMimeType(file.type, mimeType)));
1225
+ }
1226
+ /**
1227
+ * Checks whether a file extension belongs to one selected format.
1228
+ * The comparison is case-insensitive and requires a complete suffix.
1229
+ */
1230
+ function isFileExtensionSupported(file, formats) {
1231
+ const fileExtension = getFileExtension(file.name);
1232
+ if (fileExtension.length === 0) return false;
1233
+ return formats.some((format) => format.extensions.some((extension) => normalizeFileExtension(extension) === fileExtension));
1234
+ }
1235
+ /**
1236
+ * Checks whether a file satisfies the format policy selected by one validation boundary.
1237
+ * MIME matching remains strict unless the caller explicitly enables extension fallback.
1238
+ */
1239
+ function isFileFormatSupported(file, options) {
1240
+ const { formats, extensionFallback = false } = options;
1241
+ if (isFileMimeTypeSupported(file, formats)) return true;
1242
+ return extensionFallback && isFileExtensionSupported(file, formats);
1243
+ }
1244
+ /**
1245
+ * Builds a native file-picker hint from configured MIME types and extensions.
1246
+ * Duplicate values are removed while their configuration order is retained.
1247
+ */
1248
+ function createUploadAccept(formats) {
1249
+ const acceptedValues = formats.flatMap((format) => [...format.mimeTypes.map((mimeType) => mimeType.trim().toLowerCase()), ...format.extensions.map(normalizeFileExtension)]);
1250
+ return [...new Set(acceptedValues.filter((value) => value.length > 0))].join(",");
1251
+ }
1252
+ //#endregion
1253
+ //#region src/upload/upload.validation.ts
1254
+ function resolveUploadFileValidationError(file, options) {
1255
+ if (file.size === 0) return uploadValidationError.EMPTY_FILE;
1256
+ if (isFileFormatSupported(file, options) === false) return uploadValidationError.UNSUPPORTED_FILE_FORMAT;
1257
+ if (file.size > options.maxFileSize) return uploadValidationError.FILE_SIZE_EXCEEDED;
1258
+ }
1259
+ /**
1260
+ * Asserts that one file validation policy contains a usable size boundary.
1261
+ * Invalid configuration rejects before any externally owned file is inspected.
1262
+ */
1263
+ function assertUploadFileValidationOptions(options) {
1264
+ if (Number.isInteger(options.maxFileSize) === false || options.maxFileSize < 0) throw uploadErrors.invalidMaxFileSize();
1265
+ }
1266
+ /**
1267
+ * Asserts that one reusable upload preset contains a usable collection capacity.
1268
+ * Optional capacity remains unrestricted while supplied values require whole counts.
1269
+ */
1270
+ function assertUploadPreset(preset) {
1271
+ assertUploadFileValidationOptions(preset);
1272
+ if (preset.maxFilesCount !== void 0 && (Number.isInteger(preset.maxFilesCount) === false || preset.maxFilesCount < 0)) throw uploadErrors.invalidMaxFilesCount();
1273
+ }
1274
+ /**
1275
+ * Asserts that one collection policy contains usable integer capacity values.
1276
+ * Optional collection capacity remains unrestricted when it is omitted.
988
1277
  */
989
- const zodSearchSchema = (options) => {
990
- const shape = {
991
- where: (options.filters !== void 0 ? createZodSearchWhereSchema(options.filters) : options.whereSchema).optional(),
992
- ...options.queryEnabled !== false ? { query: zodSearchQuerySchema } : {},
993
- ...options.paginationEnabled !== false ? { pagination: zodPaginationSchema } : {}
1278
+ function assertUploadFilesValidationOptions(options) {
1279
+ assertUploadPreset(options);
1280
+ if (Number.isInteger(options.currentFilesCount) === false || options.currentFilesCount < 0) throw uploadErrors.invalidCurrentFilesCount();
1281
+ }
1282
+ /**
1283
+ * Validates one file against configured format and size constraints.
1284
+ * Returns the first stable error key or nothing for a valid file.
1285
+ */
1286
+ function validateUploadFile(file, options) {
1287
+ assertUploadFileValidationOptions(options);
1288
+ return resolveUploadFileValidationError(file, options);
1289
+ }
1290
+ /**
1291
+ * Validates an incoming collection while preserving every accepted file.
1292
+ * Returns accepted entries and the first rejection key from the batch.
1293
+ */
1294
+ function validateUploadFiles(incomingFiles, options) {
1295
+ assertUploadFilesValidationOptions(options);
1296
+ const { currentFilesCount, maxFilesCount } = options;
1297
+ const availableFilesCount = maxFilesCount === void 0 ? Infinity : maxFilesCount - currentFilesCount;
1298
+ const acceptedFiles = [];
1299
+ let validationError;
1300
+ for (const file of incomingFiles) {
1301
+ const fileValidationError = resolveUploadFileValidationError(file, options) ?? (acceptedFiles.length >= availableFilesCount ? uploadValidationError.FILES_COUNT_EXCEEDED : void 0);
1302
+ if (fileValidationError) {
1303
+ validationError ??= fileValidationError;
1304
+ continue;
1305
+ }
1306
+ acceptedFiles.push(file);
1307
+ }
1308
+ return {
1309
+ acceptedFiles,
1310
+ validationError
994
1311
  };
995
- return z$1.object(shape).strict();
996
- };
1312
+ }
997
1313
  //#endregion
998
- //#region src/zod-validation/zod-validation.utilities.ts
1314
+ //#region src/upload/upload.factory.ts
1315
+ /**
1316
+ * Validates one upload format before it becomes part of a reusable policy.
1317
+ * Accepts MIME-only and extension-only definitions while rejecting malformed values.
1318
+ */
1319
+ function assertUploadFormat(format) {
1320
+ const hasMimeTypes = format.mimeTypes.length > 0;
1321
+ const hasExtensions = format.extensions.length > 0;
1322
+ if (hasMimeTypes === false && hasExtensions === false) throw uploadErrors.uploadFormatRequired();
1323
+ if (format.mimeTypes.some((mimeType) => {
1324
+ return UPLOAD_MIME_TYPE_PATTERN.test(mimeType.trim()) === false;
1325
+ })) throw uploadErrors.invalidMimeType();
1326
+ if (format.extensions.some((extension) => {
1327
+ return UPLOAD_FILE_EXTENSION_PATTERN.test(extension.trim()) === false;
1328
+ })) throw uploadErrors.invalidFileExtension();
1329
+ }
999
1330
  /**
1000
- * Checks whether a value is a plain record backed by `Object.prototype`.
1001
- * Arrays, null, dates, collections, and custom class instances are rejected.
1331
+ * Defines one upload format while preserving its literal MIME and extension tuples.
1332
+ * Applications may enrich the returned object with their own display metadata.
1002
1333
  */
1003
- const isPlainObject = (value) => {
1004
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
1005
- };
1334
+ function defineUploadFormat(format) {
1335
+ assertUploadFormat(format);
1336
+ return format;
1337
+ }
1338
+ /**
1339
+ * Defines one shared upload policy while preserving its literal format tuple.
1340
+ * The resulting preset can drive schemas, picker hints, and batch validation.
1341
+ */
1342
+ function defineUploadPreset(preset) {
1343
+ assertUploadPreset(preset);
1344
+ return preset;
1345
+ }
1006
1346
  //#endregion
1007
- //#region src/zod-validation/zod-validation.parsing.ts
1008
- const parseQueryValue = (value) => {
1009
- if (Array.isArray(value)) return value.map(parseQueryValue);
1010
- if (isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, parseQueryValue(entryValue)]));
1011
- if (typeof value !== "string") return value;
1012
- const normalized = value.trim().toLowerCase();
1013
- if (normalized === "") return value;
1014
- if (normalized === "true") return true;
1015
- if (normalized === "false") return false;
1016
- if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized)) return Number(normalized);
1017
- return value;
1018
- };
1347
+ //#region src/upload/upload.schemas.ts
1019
1348
  /**
1020
- * Preprocesses query-like values before Zod validation.
1021
- *
1022
- * Query parameters are string-based by nature, even when they semantically represent
1023
- * numbers or booleans. This helper converts only clear primitive values, allowing
1024
- * regular schemas like `z.number()` and `z.boolean()` to validate query input directly.
1025
- *
1026
- * @example
1027
- * const schema = asQuery(z.object({
1028
- * page: z.number().int().positive(),
1029
- * isActive: z.boolean(),
1030
- * }));
1031
- *
1032
- * schema.parse({ page: '2', isActive: 'true' });
1033
- * // { page: 2, isActive: true }
1349
+ * Builds a reusable Zod schema for one uploaded file with format and size.
1350
+ * MIME matching is strict unless extension fallback is explicitly enabled.
1034
1351
  */
1035
- const asQuery = (schema) => z$1.preprocess(parseQueryValue, schema);
1352
+ function zodUploadFileSchema(options) {
1353
+ assertUploadFileValidationOptions(options);
1354
+ return z.file().superRefine((file, context) => {
1355
+ const validationError = validateUploadFile(file, options);
1356
+ if (validationError === void 0) return;
1357
+ context.addIssue({
1358
+ code: "custom",
1359
+ message: validationError
1360
+ });
1361
+ });
1362
+ }
1363
+ //#endregion
1364
+ //#region src/zod/zod.errors.ts
1365
+ const zodErrors = { atLeastOneRequired: () => ({
1366
+ code: "custom",
1367
+ message: "atLeastOneRequired"
1368
+ }) };
1369
+ //#endregion
1370
+ //#region src/zod/zod.refiners.ts
1036
1371
  /**
1037
- * Recursively converts payload values to strings without changing container structure.
1038
- * Objects and arrays are copied while `null` and `undefined` retain omission semantics.
1372
+ * Requires one defined property while preserving the supplied Zod object validation.
1373
+ * The output type reflects semantic presence and retains valid falsy or nullable values.
1039
1374
  *
1040
1375
  * @example
1041
- * stringifyPayloadValues({ page: 2, enabled: true });
1042
- * // { page: '2', enabled: 'true' }
1043
- */
1044
- function stringifyPayloadValues(value) {
1045
- if (Array.isArray(value)) return value.map(stringifyPayloadValues);
1046
- if (isPlainObject(value)) {
1047
- const entries = Object.entries(value).map(([key, entryValue]) => {
1048
- return [key, stringifyPayloadValues(entryValue)];
1049
- });
1050
- return Object.fromEntries(entries);
1051
- }
1052
- if (value === null || value === void 0) return value;
1053
- return String(value);
1376
+ * const patchSchema = zodAtLeastOne(z.object({ name: z.string().optional() }));
1377
+ */
1378
+ function zodAtLeastOne(schema) {
1379
+ return schema.superRefine((value, context) => {
1380
+ if (Object.values(value).some((property) => property !== void 0)) return;
1381
+ context.addIssue(zodErrors.atLeastOneRequired());
1382
+ }).transform((value) => {
1383
+ return value;
1384
+ });
1054
1385
  }
1055
1386
  //#endregion
1056
- export { APIRequestError, DEFAULT_HMAC_ALGORITHM, DEFAULT_HMAC_ENCODING, DEFAULT_UPLOAD_MAX_FILE_SIZE, EXCEPTION_STATUS_CODES, HMACService, REDACTED_LOG_VALUE, SUCCESS_STATUS_CODES, ZodJWTService, asQuery, createStringEnumRecord, createUploadAccept, createZodSearchWhereSchema, decodeBase64, decodeHMACSignature, defineUploadPreset, documentUploadPreset, encodeBase64, encodeHMACSignature, failure, fetchAndThrow, fetchSafely, fileFormat, fileFormatsArray, fileFormatsConfig, fileFormatsRecord, fileRespond, formatTime, generateRandomString, getColoredHTTPStatus, getFileExtension, getFormattedDate, getFormattedTime, getUTCOffset, getZonedTime, hmacAlgorithm, hmacAlgorithmsArray, hmacAlgorithmsRecord, hmacEncoding, hmacEncodingsArray, hmacEncodingsRecord, httpStatusColors, imageTransparentUploadPreset, imageUploadPreset, isFileExtensionSupported, isFileFormatSupported, isFileMimeTypeSupported, isPlainObject, log, logLevel, logLevelColors, logLevelsArray, logLevelsRecord, loggingMiddleware, matchesMimeType, measureExecutionTime, normalizeFileExtension, onHandlerError, parseQueryValue, redactSensitiveJSON, redactSensitiveSearchParams, respond, safeExecute, sensitiveLogKeyParts, sqlWhere, stringifyPayloadValues, success, toHMACBytes, uploadValidationError, uploadValidationErrorsArray, uploadValidationErrorsRecord, validateUploadFile, validateUploadFiles, zodAtLeastOne, zodBulkSelectionSchema, zodPaginationSchema, zodPaginationShape, zodSearchQuerySchema, zodSearchSchema, zodUploadFileSchema };
1387
+ export { DEFAULT_DATETIME_TIMEZONE, DEFAULT_HMAC_ALGORITHM, DEFAULT_HMAC_ENCODING, DEFAULT_JWT_ALGORITHM, DEFAULT_JWT_EXPIRATION_SECONDS, DEFAULT_RANDOM_STRING_LENGTH, DEFAULT_RETRY_MAX_ATTEMPTS, ERROR_RESPONSE_STATUSES, HMACService, HONO_LOGGING_SERVICE, HONO_REQUEST_ID_HEADER, JWTService, LOG_BODY_PREVIEW_EDGE_LENGTH, MULTIPART_LOG_BODY, RANDOM_ALPHANUMERIC_CHARACTERS, RANDOM_BYTE_BATCH_SIZE, REDACTED_LOG_VALUE, ResponseError, SUCCESS_RESPONSE_STATUSES, assertRandomStringLength, assertUploadFormat, captureExecution, createErrorResponse, createHTTPRequestBodyPreview, createHonoErrorHandler, createLogger, createLoggingMiddleware, createRequestIdMiddleware, createStringEnumRecord, createSuccessResponse, createUploadAccept, decodeBase64, decodeHMACSignature, defineUploadFormat, defineUploadPreset, drizzleErrors, encodeBase64, encodeHMACSignature, executeWithTimeout, executionErrors, fileRespond, formatHTTPRequestLog, formatTime, generateRandomString, getColoredHTTPStatus, getFileExtension, getFormattedDate, getFormattedTime, getHonoRequestId, getUTCOffset, getZonedTime, hmacAlgorithm, hmacAlgorithmsArray, hmacAlgorithmsRecord, hmacEncoding, hmacEncodingsArray, hmacEncodingsRecord, hmacErrors, honoErrors, httpStatusColors, isErrorResponse, isErrorResponseStatus, isFileExtensionSupported, isFileFormatSupported, isFileMimeTypeSupported, isPlainObject, isResponseErrorCode, isSuccessResponse, isSuccessResponseStatus, jwtErrors, log, logLevel, logLevelColors, logLevelsArray, logLevelsRecord, loggingErrors, loggingMiddleware, matchesMimeType, measureExecutionTime, normalizeFileExtension, normalizeLogError, randomErrors, redactSensitiveJSON, redactSensitiveSearchParams, redactSensitiveValue, requestIdMiddleware, resolveResponse, respond, responseErrors, responseKind, responseKindsArray, responseKindsRecord, retryExecution, safeExecute, sensitiveLogKeyParts, sqlWhere, toHMACBytes, unwrapResponse, uploadErrors, uploadValidationError, uploadValidationErrorsArray, uploadValidationErrorsRecord, validateJWTExpirationSeconds, validateUploadFile, validateUploadFiles, waitForRetry, zodAtLeastOne, zodErrors, zodUploadFileSchema };