@kalutskii/foundation 2.0.5 → 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 +1008 -678
  4. package/package.json +4 -4
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,97 +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,
491
+ 410,
423
492
  413,
424
- 500
493
+ 414,
494
+ 415,
495
+ 422,
496
+ 425,
497
+ 429,
498
+ 440,
499
+ 498,
500
+ 500,
501
+ 501,
502
+ 502,
503
+ 503,
504
+ 504
425
505
  ];
426
506
  //#endregion
427
- //#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
+ };
428
513
  /**
429
- * Represents an error envelope rejected by an API request resolver.
430
- * 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.
431
516
  */
432
- var APIRequestError = class extends Error {
517
+ var ResponseError = class extends Error {
433
518
  code;
434
519
  status;
435
520
  constructor(status, code) {
436
521
  super(code);
437
- this.name = "APIRequestError";
522
+ this.name = "ResponseError";
438
523
  this.code = code;
439
524
  this.status = status;
440
525
  }
441
526
  };
442
527
  //#endregion
443
- //#region src/http/http.resolvers.ts
528
+ //#region src/response/response.validation.ts
444
529
  /**
445
- * Converts an API contract envelope into a mutually exclusive safe result.
446
- * 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.
447
532
  */
448
- async function fetchSafely(fetcher) {
449
- const response = await fetcher();
450
- if (response.kind === "error") return {
451
- error: response.error,
452
- data: null
453
- };
454
- return {
455
- error: null,
456
- data: response.data
457
- };
533
+ function isResponseErrorCode(value) {
534
+ return typeof value === "string" && /^[a-z][A-Za-z0-9]*$/.test(value);
458
535
  }
459
536
  /**
460
- * Returns successful API data and throws `APIRequestError` for a failed envelope.
461
- * 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`.
462
539
  */
463
- async function fetchAndThrow(fetcher) {
464
- const response = await fetcher();
465
- if (response.kind === "error") throw new APIRequestError(response.status, response.error);
466
- 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;
467
563
  }
468
564
  //#endregion
469
- //#region src/logging/logging.security.ts
565
+ //#region src/response/response.factories.ts
470
566
  /**
471
- * Checks whether a logging field name contains a configured sensitive fragment.
472
- * 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.
473
569
  */
474
- function isSensitiveLogKey(key) {
475
- const normalizedKey = key.toLowerCase().replaceAll(/[^a-z]/g, "");
476
- 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
+ };
477
577
  }
478
578
  /**
479
- * Recursively replaces values owned by sensitive object keys with the public marker.
480
- * 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.
481
581
  */
482
- function redactSensitiveValue(value) {
483
- if (Array.isArray(value)) {
484
- const entries = value.map(redactSensitiveValue);
485
- return {
486
- 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,
487
778
  redacted: entries.some((entry) => entry.redacted)
488
779
  };
489
780
  }
@@ -491,28 +782,48 @@ function redactSensitiveValue(value) {
491
782
  value,
492
783
  redacted: false
493
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
+ };
494
795
  let redacted = false;
495
- 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)) {
496
799
  if (isSensitiveLogKey(key)) {
497
800
  redacted = true;
498
- return [key, REDACTED_LOG_VALUE];
801
+ redactedValue[key] = REDACTED_LOG_VALUE;
802
+ continue;
499
803
  }
500
- const nestedEntry = redactSensitiveValue(entryValue);
804
+ const nestedEntry = redactSensitiveValueResult(entryValue, seenValues);
501
805
  redacted ||= nestedEntry.redacted;
502
- return [key, nestedEntry.value];
503
- });
806
+ redactedValue[key] = nestedEntry.value;
807
+ }
504
808
  return {
505
- value: Object.fromEntries(entries),
809
+ value: redactedValue,
506
810
  redacted
507
811
  };
508
812
  }
509
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
+ /**
510
821
  * Replaces sensitive query values using partial, case-insensitive key matching.
511
822
  * A new collection is returned so the caller's search parameters remain unchanged.
512
823
  */
513
824
  function redactSensitiveSearchParams(searchParams) {
514
- const redactedSearchParams = new URLSearchParams(searchParams);
515
- 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);
516
827
  return redactedSearchParams;
517
828
  }
518
829
  /**
@@ -520,9 +831,8 @@ function redactSensitiveSearchParams(searchParams) {
520
831
  * Invalid JSON and payloads without matching keys are returned byte-for-byte unchanged.
521
832
  */
522
833
  function redactSensitiveJSON(json) {
523
- if (json === void 0 || null) return json;
524
834
  try {
525
- const result = redactSensitiveValue(JSON.parse(json));
835
+ const result = redactSensitiveValueResult(JSON.parse(json));
526
836
  return result.redacted ? JSON.stringify(result.value) : json;
527
837
  } catch {
528
838
  return json;
@@ -531,6 +841,15 @@ function redactSensitiveJSON(json) {
531
841
  //#endregion
532
842
  //#region src/logging/logging.utilities.ts
533
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
+ /**
534
853
  * Selects a terminal color function for one HTTP response status.
535
854
  * Successes are green, client errors yellow, and server errors red.
536
855
  */
@@ -540,358 +859,205 @@ function getColoredHTTPStatus(status) {
540
859
  });
541
860
  return statusColor ? statusColor.color : (text) => text;
542
861
  }
543
- //#endregion
544
- //#region src/logging/logging.middleware.ts
545
- /**
546
- * Logs Hono request metadata with query parameters and a normalized body preview.
547
- * Sensitive values are redacted by partial key match before output is written.
548
- */
549
- const loggingMiddleware = async (c, next) => {
550
- const contentType = c.req.header("content-type");
551
- const searchParams = redactSensitiveSearchParams(new URL(c.req.url).searchParams).toString();
552
- const body = contentType?.includes("multipart/form-data") ? "[multipart]" : redactSensitiveJSON((await c.req.raw.clone().text()).replaceAll(/\s+/g, " ").trim());
553
- const bodyPreview = body.length > 60 ? `${body.slice(0, 30)}…${body.slice(-30)}` : body;
554
- const startTime = performance.now();
555
- await next();
556
- const duration = Math.round(performance.now() - startTime);
557
- const coloredMethod = bold(blue(c.req.method.padEnd(4)));
558
- const coloredStatus = getColoredHTTPStatus(c.res.status)(String(c.res.status).padEnd(4));
559
- const coloredTime = dim(`${duration}ms`.padStart(6));
560
- const coloredPath = white(c.req.path.padEnd(32));
561
- const coloredSearchParams = searchParams ? dim(` (${searchParams})`) : "";
562
- const coloredBody = bodyPreview ? dim(` ${bodyPreview}`) : "";
563
- log.info(`${coloredMethod} ${coloredStatus} ${coloredTime} ${coloredPath}${coloredSearchParams}${coloredBody}`, "hono");
564
- };
565
- //#endregion
566
- //#region src/upload/upload.enums.ts
567
- const fileFormatsArray = [...[
568
- "png",
569
- "jpg",
570
- "webp",
571
- "avif",
572
- "heic",
573
- "svg"
574
- ], ...[
575
- "pdf",
576
- "rtf",
577
- "txt"
578
- ]];
579
- const fileFormatsRecord = createStringEnumRecord(fileFormatsArray);
580
- const fileFormat = fileFormatsRecord;
581
- const uploadValidationErrorsArray = [
582
- "empty_file",
583
- "unsupported_file_format",
584
- "file_size_exceeded",
585
- "files_count_exceeded"
586
- ];
587
- const uploadValidationErrorsRecord = createStringEnumRecord(uploadValidationErrorsArray);
588
- const uploadValidationError = uploadValidationErrorsRecord;
589
- //#endregion
590
- //#region src/upload/upload.constants.ts
591
862
  /**
592
- * Canonical metadata shared by upload controls and runtime validation.
593
- * 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.
594
865
  */
595
- const fileFormatsConfig = {
596
- [fileFormatsRecord.PNG]: {
597
- name: "PNG",
598
- mimeTypes: ["image/png"],
599
- extensions: [".png"]
600
- },
601
- [fileFormatsRecord.JPG]: {
602
- name: "JPG",
603
- mimeTypes: ["image/jpeg"],
604
- extensions: [".jpg", ".jpeg"]
605
- },
606
- [fileFormatsRecord.SVG]: {
607
- name: "SVG",
608
- mimeTypes: ["image/svg+xml"],
609
- extensions: [".svg"]
610
- },
611
- [fileFormatsRecord.WEBP]: {
612
- name: "WEBP",
613
- mimeTypes: ["image/webp"],
614
- extensions: [".webp"]
615
- },
616
- [fileFormatsRecord.AVIF]: {
617
- name: "AVIF",
618
- mimeTypes: ["image/avif"],
619
- extensions: [".avif"]
620
- },
621
- [fileFormatsRecord.HEIC]: {
622
- name: "HEIC",
623
- mimeTypes: ["image/heic", "image/heif"],
624
- extensions: [".heic", ".heif"]
625
- },
626
- [fileFormatsRecord.PDF]: {
627
- name: "PDF",
628
- mimeTypes: ["application/pdf"],
629
- extensions: [".pdf"]
630
- },
631
- [fileFormatsRecord.RTF]: {
632
- name: "RTF",
633
- mimeTypes: ["application/rtf"],
634
- extensions: [".rtf"]
635
- },
636
- [fileFormatsRecord.TXT]: {
637
- name: "TXT",
638
- mimeTypes: ["text/plain"],
639
- extensions: [".txt"]
640
- }
641
- };
642
- //#endregion
643
- //#region src/upload/upload.factory.ts
644
- /**
645
- * Defines one shared upload policy while preserving its literal format tuple.
646
- * The resulting preset can drive schemas, picker hints, and batch validation.
647
- *
648
- * @example
649
- * const imageUploadPreset = defineUploadPreset({
650
- * formats: [fileFormat.JPG, fileFormat.PNG, fileFormat.WEBP],
651
- * maxFileSize: 8 * 1024 * 1024,
652
- * maxFilesCount: 1,
653
- * });
654
- */
655
- function defineUploadPreset(preset) {
656
- 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}]`) : ""}`;
657
868
  }
658
- //#endregion
659
- //#region src/upload/upload.presets.ts
660
- /**
661
- * Default maximum size of one file accepted by the built-in upload presets.
662
- * The 20 MiB boundary matches the existing upload component policy.
663
- */
664
- const DEFAULT_UPLOAD_MAX_FILE_SIZE = 20971520;
665
- /**
666
- * Ready-to-use policy for every image format supported by the upload catalog.
667
- * Each image may occupy up to 20 MiB while collection capacity remains unrestricted.
668
- */
669
- const imageUploadPreset = defineUploadPreset({
670
- formats: [
671
- fileFormat.PNG,
672
- fileFormat.JPG,
673
- fileFormat.WEBP,
674
- fileFormat.AVIF,
675
- fileFormat.HEIC
676
- ],
677
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
678
- });
679
- /**
680
- * Ready-to-use policy for every image format supporting transparency (alpha channel).
681
- * Each image may occupy up to 20 MiB while collection capacity remains unrestricted.
682
- */
683
- const imageTransparentUploadPreset = defineUploadPreset({
684
- formats: [
685
- fileFormat.PNG,
686
- fileFormat.WEBP,
687
- fileFormat.SVG
688
- ],
689
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
690
- });
691
- /**
692
- * Ready-to-use policy for every document format supported by the upload catalog.
693
- * Each document may occupy up to 20 MiB while collection capacity remains unrestricted.
694
- */
695
- const documentUploadPreset = defineUploadPreset({
696
- formats: [
697
- fileFormat.PDF,
698
- fileFormat.RTF,
699
- fileFormat.TXT
700
- ],
701
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
702
- });
703
- //#endregion
704
- //#region src/upload/upload.utilities.ts
705
869
  /**
706
- * Extracts a normalized trailing extension from a complete file name.
707
- * 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.
708
872
  */
709
- function getFileExtension(fileName) {
710
- const extensionIndex = fileName.lastIndexOf(".");
711
- return extensionIndex > 0 && extensionIndex < fileName.length - 1 ? fileName.slice(extensionIndex).toLowerCase() : "";
712
- }
713
- /**
714
- * Normalizes a configured extension to a lowercase dot-prefixed value.
715
- * Existing prefixes remain intact so repeated normalization is stable.
716
- */
717
- function normalizeFileExtension(extension) {
718
- const normalizedExtension = extension.toLowerCase();
719
- return normalizedExtension.startsWith(".") ? normalizedExtension : `.${normalizedExtension}`;
720
- }
721
- /**
722
- * Compares a concrete MIME type with an exact or wildcard configuration.
723
- * Wildcards match every subtype belonging to the configured media group.
724
- */
725
- function matchesMimeType(fileMimeType, configuredMimeType) {
726
- if (configuredMimeType.endsWith("/*")) return fileMimeType.startsWith(configuredMimeType.slice(0, -1));
727
- 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)}`;
728
879
  }
880
+ //#endregion
881
+ //#region src/logging/logging.services.ts
729
882
  /**
730
- * Checks whether a file MIME type belongs to one selected format.
731
- * 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.
732
885
  */
733
- function isFileMimeTypeSupported(file, formats) {
734
- return formats.some((format) => fileFormatsConfig[format].mimeTypes.some((mimeType) => matchesMimeType(file.type, mimeType)));
886
+ function writeConsoleLog(line) {
887
+ console.log(line);
735
888
  }
736
889
  /**
737
- * Checks whether a file extension belongs to one selected format.
738
- * 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.
739
892
  */
740
- function isFileExtensionSupported(file, formats) {
741
- const fileExtension = getFileExtension(file.name);
742
- 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)}`);
743
900
  }
744
901
  /**
745
- * Checks whether a file matches one configured MIME type or extension.
746
- * 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.
747
904
  */
748
- function isFileFormatSupported(file, formats) {
749
- 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
+ };
750
917
  }
751
918
  /**
752
- * Builds a native file-picker hint from configured MIME types and extensions.
753
- * 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.
754
921
  */
755
- function createUploadAccept(formats) {
756
- const acceptedValues = formats.flatMap((format) => [...fileFormatsConfig[format].mimeTypes, ...fileFormatsConfig[format].extensions]);
757
- return [...new Set(acceptedValues)].join(",");
758
- }
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
+ };
759
945
  //#endregion
760
- //#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
+ }
761
977
  /**
762
- * Builds a reusable Zod schema for one uploaded file with format and size.
763
- * 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.
764
980
  */
765
- function zodUploadFileSchema(options) {
766
- const { formats, maxFileSize, extensionFallback = false } = options;
767
- 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);
768
- }
981
+ const loggingMiddleware = createLoggingMiddleware();
769
982
  //#endregion
770
- //#region src/upload/upload.validation.ts
983
+ //#region src/hono/hono.respond.ts
771
984
  /**
772
- * Validates one file against configured format and size constraints.
773
- * 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.
774
987
  */
775
- function validateUploadFile(file, formats, maxFileSize) {
776
- if (file.size === 0) return uploadValidationError.EMPTY_FILE;
777
- if (isFileFormatSupported(file, formats) === false) return uploadValidationError.UNSUPPORTED_FILE_FORMAT;
778
- 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);
779
991
  }
780
992
  /**
781
- * Validates an incoming collection while preserving every accepted file.
782
- * 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`.
783
995
  */
784
- function validateUploadFiles(incomingFiles, options) {
785
- const { currentFilesCount, formats, maxFileSize, maxFilesCount } = options;
786
- const availableFilesCount = maxFilesCount === void 0 ? Infinity : maxFilesCount - currentFilesCount;
787
- const acceptedFiles = [];
788
- let validationError;
789
- for (const file of incomingFiles) {
790
- const fileValidationError = validateUploadFile(file, formats, maxFileSize) ?? (acceptedFiles.length >= availableFilesCount ? uploadValidationError.FILES_COUNT_EXCEEDED : void 0);
791
- if (fileValidationError) {
792
- validationError = fileValidationError;
793
- continue;
794
- }
795
- acceptedFiles.push(file);
796
- }
797
- return {
798
- acceptedFiles,
799
- validationError
800
- };
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);
801
1000
  }
802
1001
  //#endregion
803
- //#region src/utilities/execution.utilities.ts
1002
+ //#region src/jwt/jwt.constants.ts
804
1003
  /**
805
- * Resolves synchronous and asynchronous executions through one promise-based contract.
806
- * Failures use the supplied fallback or propagate unchanged when none is available.
807
- *
808
- * @example
809
- * 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.
810
1006
  */
811
- async function safeExecute(fn, onError) {
812
- try {
813
- return await fn();
814
- } catch (error) {
815
- if (onError) return await onError(error);
816
- throw error;
817
- }
818
- }
1007
+ const DEFAULT_JWT_ALGORITHM = "HS256";
819
1008
  /**
820
- * Measures an asynchronous execution while preserving its resolved result.
821
- * Rejected executions propagate unchanged and do not produce a measurement.
822
- *
823
- * @example
824
- * const { result, executionTime } = await measureExecutionTime(async () => {
825
- * return await fetchData();
826
- * });
827
- * 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.
828
1011
  */
829
- async function measureExecutionTime(execution) {
830
- const startedAt = performance.now();
831
- const result = await execution();
832
- const executionTime = performance.now() - startedAt;
833
- return {
834
- result,
835
- executionTime: Math.round(executionTime)
836
- };
837
- }
1012
+ const DEFAULT_JWT_EXPIRATION_SECONDS = 900;
838
1013
  //#endregion
839
- //#region src/zod-bulk/zod-bulk.schemas.ts
840
- 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
841
1018
  /**
842
- * Builds a strict selection contract for bulk operations across paginated data.
843
- * The identifier schema is shared by explicit and all-matching selection modes.
844
- *
845
- * `include` targets only identifiers listed by the client. `exclude` targets every
846
- * item matching the accompanying search snapshot except excluded identifiers.
847
- *
848
- * @example
849
- * const assetBulkSelectionSchema = zodBulkSelectionSchema({
850
- * identifierSchema: z.string().min(1),
851
- * });
1019
+ * Validates a JWT lifetime before it participates in expiration calculation.
1020
+ * Whole positive, zero, and negative seconds are accepted within the safe range.
852
1021
  */
853
- function zodBulkSelectionSchema({ identifierSchema }) {
854
- const identifiersSchema = z$1.array(identifierSchema).refine((identifiers) => new Set(identifiers).size === identifiers.length, { message: DEFAULT_ERROR_MESSAGE$1 });
855
- return z$1.discriminatedUnion("mode", [z$1.object({
856
- mode: z$1.literal("include"),
857
- identifiers: identifiersSchema
858
- }).strict(), z$1.object({
859
- mode: z$1.literal("exclude"),
860
- excludedIdentifiers: identifiersSchema
861
- }).strict()]);
1022
+ function validateJWTExpirationSeconds(expirationSeconds) {
1023
+ if (Number.isSafeInteger(expirationSeconds) === false) throw jwtErrors.invalidExpirationSeconds();
862
1024
  }
863
1025
  //#endregion
864
- //#region src/zod-jwt/zod-jwt.services.ts
1026
+ //#region src/jwt/jwt.services.ts
865
1027
  /**
866
- * Signs, decodes, and verifies JWTs with optional Zod payload validation.
1028
+ * Signs, decodes, and verifies JWTs with optional payload schema validation.
867
1029
  * A supplied schema parses payloads returned by decoding and verification.
868
1030
  */
869
- var ZodJWTService = class {
1031
+ var JWTService = class {
870
1032
  payloadSchema;
871
1033
  algorithm;
872
1034
  defaultExpirationSeconds;
873
- constructor(payloadSchema, options) {
1035
+ constructor(payloadSchema, options = {}) {
1036
+ const { algorithm = DEFAULT_JWT_ALGORITHM, defaultExpirationSeconds = 900 } = options;
1037
+ validateJWTExpirationSeconds(defaultExpirationSeconds);
874
1038
  this.payloadSchema = payloadSchema;
875
- this.algorithm = options?.algorithm ?? "HS256";
876
- this.defaultExpirationSeconds = options?.defaultExpirationSeconds ?? 900;
1039
+ this.algorithm = algorithm;
1040
+ this.defaultExpirationSeconds = defaultExpirationSeconds;
877
1041
  }
878
1042
  /**
879
1043
  * Signs a payload using the configured algorithm and expiration settings.
880
- * Per-call expiration overrides the default configured by the service.
1044
+ * Per-operation expiration overrides the default configured by the service.
881
1045
  *
882
1046
  * @example
883
1047
  * const token = await jwtService.sign({ userId: '123' }, secret, { expiresInSeconds: 300 });
884
1048
  */
885
- async sign(payload, secret, options) {
886
- 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;
887
1053
  return sign({
888
1054
  ...payload,
889
- exp
1055
+ exp: expirationTimestamp
890
1056
  }, secret, this.algorithm);
891
1057
  }
892
1058
  /**
893
- * Decodes without authenticating it and parses its payload when a schema exists.
894
- * 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.
895
1061
  *
896
1062
  * @example
897
1063
  * const payload = await jwtService.decode(token);
@@ -899,12 +1065,12 @@ var ZodJWTService = class {
899
1065
  async decode(token) {
900
1066
  const { payload } = decode(token);
901
1067
  if (this.payloadSchema === void 0) return payload;
902
- const { success, data } = await this.payloadSchema.safeParseAsync(payload);
903
- return success ? data : null;
1068
+ const result = await this.payloadSchema.safeParseAsync(payload);
1069
+ return result.success ? result.data : null;
904
1070
  }
905
1071
  /**
906
1072
  * Verifies a token using the configured algorithm and parses its payload.
907
- * Signature, expiration, and schema validation failures reject the operation.
1073
+ * Authentication, expiration, and schema failures reject the operation.
908
1074
  *
909
1075
  * @example
910
1076
  * const payload = await jwtService.verifyOrThrow(token, secret);
@@ -916,142 +1082,306 @@ var ZodJWTService = class {
916
1082
  }
917
1083
  };
918
1084
  //#endregion
919
- //#region src/zod-search/zod-search.pagination.schemas.ts
1085
+ //#region src/object/object.utilities.ts
920
1086
  /**
921
- * Validates offset-based pagination shared by search request contracts.
922
- * 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.
923
1089
  */
924
- const zodPaginationSchema = z$1.object({
925
- /**
926
- * Zero-based number of records skipped before collecting a result page.
927
- * String values are coerced to support validation of URL query input.
928
- */
929
- offset: z$1.coerce.number().int().nonnegative().max(1048576).default(0),
930
- /**
931
- * Positive maximum number of records returned in a single result page.
932
- * String values are coerced to support validation of URL query input.
933
- */
934
- limit: z$1.coerce.number().int().positive().max(256).default(10)
935
- });
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
936
1117
  /**
937
- * Exposes pagination fields for composition into other object schemas.
938
- * 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.
939
1120
  */
940
- const zodPaginationShape = zodPaginationSchema.shape;
1121
+ function assertRandomStringLength(length) {
1122
+ if ((Number.isSafeInteger(length) && length >= 0) === false) throw randomErrors.invalidRandomStringLength();
1123
+ }
941
1124
  //#endregion
942
- //#region src/zod-validation/zod-validation.refiners.ts
943
- const DEFAULT_ERROR_MESSAGE = "Invalid input. At least one field must be provided.";
1125
+ //#region src/random/random.utilities.ts
944
1126
  /**
945
- * Wraps a partial Zod object schema with:
946
- * 1. A runtime check ensuring at least one field is non-undefined.
947
- * 2. A `.transform()` that narrows the output type to `AtLeastOne<T>`,
948
- * 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.
949
1129
  *
950
1130
  * @example
951
- * zQuery(zodAtLeastOne(userSelectSchema)) // result: UserSelect ✓
1131
+ * generateRandomString(5); // `aZ3fG`
1132
+ * generateRandomString(); // `G5kLm2P9sQ`
952
1133
  */
953
- const zodAtLeastOne = (schema) => schema.superRefine((val, ctx) => {
954
- if (!Object.values(val).some((v) => v !== void 0)) ctx.addIssue({
955
- code: "custom",
956
- message: DEFAULT_ERROR_MESSAGE
957
- });
958
- }).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
+ }
959
1149
  //#endregion
960
- //#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
961
1171
  /**
962
- * Optional text query shared by search request contracts.
963
- * 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.
964
1174
  */
965
- const zodSearchQuerySchema = z$1.string().trim().min(1).optional();
1175
+ const UPLOAD_MIME_TYPE_PATTERN = /^(?:\*\/\*|[A-Za-z0-9!#$&^_.+-]+\/(?:\*|[A-Za-z0-9!#$&^_.+-]+))$/;
966
1176
  /**
967
- * Makes every supplied filter optional while requiring one defined value.
968
- * 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.
969
1179
  */
970
- 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
971
1183
  /**
972
- * Builds a strict schema for reusable search request contracts.
973
- * The optional `where` object must contain one defined filter.
974
- *
975
- * Query is optional and non-empty; pagination is required by default.
976
- * Literal feature flags update both runtime and inferred static shapes.
977
- *
978
- * Pass `filters` for automatic partial and non-empty validation, or use
979
- * `whereSchema` to preserve a prepared schema with custom Zod effects.
980
- *
981
- * @example
982
- * const assetSearchSchema = zodSearchSchema({
983
- * filters: assetSchema.pick({ status: true }),
984
- * });
985
- *
986
- * const refinedAssetSearchSchema = zodSearchSchema({
987
- * whereSchema: zodAtLeastOne(assetSchema.pick({ status: true }).partial()),
988
- * });
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.
989
1277
  */
990
- const zodSearchSchema = (options) => {
991
- const shape = {
992
- where: (options.filters !== void 0 ? createZodSearchWhereSchema(options.filters) : options.whereSchema).optional(),
993
- ...options.queryEnabled !== false ? { query: zodSearchQuerySchema } : {},
994
- ...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
995
1311
  };
996
- return z$1.object(shape).strict();
997
- };
1312
+ }
998
1313
  //#endregion
999
- //#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
+ }
1000
1330
  /**
1001
- * Checks whether a value is a plain record backed by `Object.prototype`.
1002
- * 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.
1003
1333
  */
1004
- const isPlainObject = (value) => {
1005
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
1006
- };
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
+ }
1007
1346
  //#endregion
1008
- //#region src/zod-validation/zod-validation.parsing.ts
1009
- const parseQueryValue = (value) => {
1010
- if (Array.isArray(value)) return value.map(parseQueryValue);
1011
- if (isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, parseQueryValue(entryValue)]));
1012
- if (typeof value !== "string") return value;
1013
- const normalized = value.trim().toLowerCase();
1014
- if (normalized === "") return value;
1015
- if (normalized === "true") return true;
1016
- if (normalized === "false") return false;
1017
- if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized)) return Number(normalized);
1018
- return value;
1019
- };
1347
+ //#region src/upload/upload.schemas.ts
1020
1348
  /**
1021
- * Preprocesses query-like values before Zod validation.
1022
- *
1023
- * Query parameters are string-based by nature, even when they semantically represent
1024
- * numbers or booleans. This helper converts only clear primitive values, allowing
1025
- * regular schemas like `z.number()` and `z.boolean()` to validate query input directly.
1026
- *
1027
- * @example
1028
- * const schema = asQuery(z.object({
1029
- * page: z.number().int().positive(),
1030
- * isActive: z.boolean(),
1031
- * }));
1032
- *
1033
- * schema.parse({ page: '2', isActive: 'true' });
1034
- * // { 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.
1035
1351
  */
1036
- 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
1037
1371
  /**
1038
- * Recursively converts payload values to strings without changing container structure.
1039
- * 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.
1040
1374
  *
1041
1375
  * @example
1042
- * stringifyPayloadValues({ page: 2, enabled: true });
1043
- * // { page: '2', enabled: 'true' }
1044
- */
1045
- function stringifyPayloadValues(value) {
1046
- if (Array.isArray(value)) return value.map(stringifyPayloadValues);
1047
- if (isPlainObject(value)) {
1048
- const entries = Object.entries(value).map(([key, entryValue]) => {
1049
- return [key, stringifyPayloadValues(entryValue)];
1050
- });
1051
- return Object.fromEntries(entries);
1052
- }
1053
- if (value === null || value === void 0) return value;
1054
- 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
+ });
1055
1385
  }
1056
1386
  //#endregion
1057
- 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 };