@kalutskii/foundation 0.7.22 → 0.7.25

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 (3) hide show
  1. package/dist/index.d.ts +463 -429
  2. package/dist/index.js +954 -644
  3. package/package.json +8 -9
package/dist/index.js CHANGED
@@ -1,730 +1,1040 @@
1
- // src/drizzle/drizzle.refiners.ts
2
1
  import { and, eq } from "drizzle-orm";
3
- var AT_LEAST_ONE_DEFINED_CONDITION_ERROR = "sqlWhere requires at least one defined condition.";
4
- function sqlWhere(table, where) {
5
- const columns = table;
6
- const conditions = Object.entries(where).filter(([, value]) => value !== void 0).map(([key, value]) => eq(columns[key], value));
7
- if (conditions.length === 0) {
8
- throw new Error(AT_LEAST_ONE_DEFINED_CONDITION_ERROR);
9
- }
10
- return and(...conditions);
11
- }
12
-
13
- // src/hono/hono.execution.ts
14
2
  import { HTTPException } from "hono/http-exception";
15
- import { red as red3 } from "kleur/colors";
16
-
17
- // src/http/http.factory.ts
3
+ import { blue, bold, dim, green, red, white, yellow } from "kleur/colors";
4
+ import { format } from "date-fns";
5
+ import { getTimezoneOffset, toZonedTime } from "date-fns-tz";
6
+ import { ru } from "date-fns/locale";
7
+ import { z as z$1 } from "zod";
8
+ 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.";
11
+ /**
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();
17
+ */
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
+ }
24
+ //#endregion
25
+ //#region src/http/http.factory.ts
26
+ /**
27
+ * Creates a successful API envelope without cloning its data.
28
+ * Generic inference preserves the exact supplied payload type.
29
+ */
18
30
  function success({ status, data }) {
19
- return { kind: "data", status, data };
31
+ return {
32
+ kind: "data",
33
+ status,
34
+ data
35
+ };
20
36
  }
37
+ /**
38
+ * Creates a failed API envelope with one supported exception status.
39
+ * The supplied message remains unchanged for downstream presentation.
40
+ */
21
41
  function failure({ status, error }) {
22
- return { kind: "error", status, error };
42
+ return {
43
+ kind: "error",
44
+ status,
45
+ error
46
+ };
23
47
  }
24
-
25
- // src/logging/logging.services.ts
26
- import { dim, red as red2, white } from "kleur/colors";
27
-
28
- // src/utilities/datetime.utilities.ts
29
- import { format } from "date-fns";
30
- import { getTimezoneOffset, toZonedTime } from "date-fns-tz";
31
- import { ru } from "date-fns/locale";
48
+ //#endregion
49
+ //#region src/utilities/datetime.utilities.ts
50
+ /**
51
+ * Projects the current instant onto the wall-clock fields of another timezone.
52
+ * The timezone defaults to `Europe/London` when no option is provided.
53
+ *
54
+ * @example
55
+ * const londonTime = getZonedTime({ tz: 'Europe/London' });
56
+ */
32
57
  function getZonedTime({ tz = "Europe/London" } = {}) {
33
- return toZonedTime(/* @__PURE__ */ new Date(), tz);
58
+ return toZonedTime(/* @__PURE__ */ new Date(), tz);
34
59
  }
60
+ /**
61
+ * Formats the UTC offset of a timezone at the supplied date.
62
+ * An explicit date preserves daylight-saving and historical offset rules.
63
+ *
64
+ * @example
65
+ * getUTCOffset(date, 'Europe/Moscow'); // `(+3 UTC)`
66
+ */
35
67
  function getUTCOffset(date, tz) {
36
- const offset = getTimezoneOffset(tz, date) / (60 * 60 * 1e3);
37
- return `(${offset >= 0 ? "+" : ""}${offset} UTC)`;
68
+ const offset = getTimezoneOffset(tz, date) / 36e5;
69
+ return `(${offset >= 0 ? "+" : ""}${offset} UTC)`;
38
70
  }
71
+ /**
72
+ * Formats the current time and UTC offset in the selected timezone.
73
+ * The timezone defaults to `Europe/London` when no option is provided.
74
+ *
75
+ * @example
76
+ * getFormattedTime({ tz: 'Europe/Moscow' }); // `03:04:05 (+3 UTC)`
77
+ */
39
78
  function getFormattedTime({ tz = "Europe/London" } = {}) {
40
- const zonedTime = getZonedTime({ tz });
41
- return `${format(zonedTime, "HH:mm:ss")} ${getUTCOffset(zonedTime, tz)}`;
79
+ const zonedTime = getZonedTime({ tz });
80
+ return `${format(zonedTime, "HH:mm:ss")} ${getUTCOffset(zonedTime, tz)}`;
42
81
  }
82
+ /**
83
+ * Formats the current date with optional time and UTC offset components.
84
+ * Time is included by default and uses the selected timezone for display.
85
+ *
86
+ * @example
87
+ * getFormattedDate({ tz: 'UTC', withTime: false }); // `02.01.2024`
88
+ */
43
89
  function getFormattedDate({ tz = "Europe/London", withTime = true } = {}) {
44
- const zonedTime = getZonedTime({ tz });
45
- const pattern = withTime ? "dd.MM.yyyy HH:mm:ss" : "dd.MM.yyyy";
46
- const formatted = format(zonedTime, pattern);
47
- return `${formatted}${withTime ? ` ${getUTCOffset(zonedTime, tz)}` : ""}`;
90
+ const zonedTime = getZonedTime({ tz });
91
+ return `${format(zonedTime, withTime ? "dd.MM.yyyy HH:mm:ss" : "dd.MM.yyyy")}${withTime ? ` ${getUTCOffset(zonedTime, tz)}` : ""}`;
48
92
  }
93
+ /**
94
+ * Formats an explicit instant in another timezone using the selected locale.
95
+ * The result includes localized date text, wall-clock time, and UTC offset.
96
+ *
97
+ * @example
98
+ * formatTime(date, { locale: ru, tz: 'Europe/Moscow' });
99
+ */
49
100
  function formatTime(time, { locale = ru, tz = "Europe/London" } = {}) {
50
- const zonedTime = toZonedTime(time, tz);
51
- return `${format(zonedTime, "HH:mm:ss, d MMMM yyyy", { locale })} ${getUTCOffset(zonedTime, tz)}`;
52
- }
53
-
54
- // src/logging/logging.constants.ts
55
- import { blue, green, red, yellow } from "kleur/colors";
56
-
57
- // src/utilities/enums.utilities.ts
101
+ const zonedTime = toZonedTime(time, tz);
102
+ return `${format(zonedTime, "HH:mm:ss, d MMMM yyyy", { locale })} ${getUTCOffset(zonedTime, tz)}`;
103
+ }
104
+ //#endregion
105
+ //#region src/utilities/enums.utilities.ts
106
+ /**
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.
109
+ *
110
+ * @example
111
+ * createStringEnumRecord(['foo-bar.s', 'baz'] as const); // `{ FOO_BAR_S: 'foo-bar.s', BAZ: 'baz' }`
112
+ */
58
113
  function createStringEnumRecord(values) {
59
- return Object.freeze(Object.fromEntries(values.map((value) => [value.replaceAll(/[.-]/g, "_").toUpperCase(), value])));
60
- }
61
-
62
- // src/logging/logging.enums.ts
63
- var logLevelsArray = ["info", "warn", "error"];
64
- var logLevelsRecord = createStringEnumRecord(logLevelsArray);
65
- var logLevel = logLevelsRecord;
66
-
67
- // src/logging/logging.constants.ts
68
- var logLevelColors = {
69
- [logLevel.INFO]: blue,
70
- [logLevel.WARN]: yellow,
71
- [logLevel.ERROR]: red
114
+ return Object.freeze(Object.fromEntries(values.map((value) => [value.replaceAll(/[.-]/g, "_").toUpperCase(), value])));
115
+ }
116
+ //#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
127
+ /**
128
+ * Terminal color configuration for every supported logging level.
129
+ * The `LogLevel` contract prevents missing or unsupported level entries.
130
+ */
131
+ const logLevelColors = {
132
+ [logLevel.INFO]: blue,
133
+ [logLevel.WARN]: yellow,
134
+ [logLevel.ERROR]: red
72
135
  };
73
- var REDACTED_LOG_VALUE = "[redacted]";
74
- var sensitiveLogKeyParts = [
75
- "password",
76
- "passwd",
77
- "token",
78
- "secret",
79
- "authorization",
80
- "apikey",
81
- "credential"
136
+ /**
137
+ * Public placeholder written instead of sensitive query and JSON values.
138
+ * One stable marker keeps redacted output recognizable across log consumers.
139
+ */
140
+ const REDACTED_LOG_VALUE = "[redacted]";
141
+ /**
142
+ * Normalized key fragments treated as sensitive by logging redaction.
143
+ * Matching ignores case and separators so compound field names remain covered.
144
+ */
145
+ const sensitiveLogKeyParts = [
146
+ "password",
147
+ "passwd",
148
+ "token",
149
+ "secret",
150
+ "authorization",
151
+ "apikey",
152
+ "credential"
82
153
  ];
83
- var httpStatusColors = [
84
- { range: [200, 299], color: green },
85
- { range: [400, 499], color: yellow },
86
- { range: [500, 599], color: red }
154
+ /**
155
+ * Terminal colors assigned to the supported HTTP response status ranges.
156
+ * Unlisted ranges intentionally retain the terminal's default text appearance.
157
+ */
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
+ }
87
171
  ];
88
-
89
- // src/logging/logging.services.ts
172
+ //#endregion
173
+ //#region src/logging/logging.services.ts
90
174
  function writeLog(message, level, service = "log", stack) {
91
- const timestamp = dim(getFormattedTime());
92
- const serviceName = logLevelColors[level](service.padEnd(12));
93
- const formattedMessage = white(message);
94
- console.log(`[${timestamp}] ${serviceName} | ${formattedMessage}`);
95
- if (stack)
96
- console.log(`[${timestamp}] ${red2("\u21B3 trace").padEnd(18)} | ${dim(stack)}`);
97
- }
98
- var log = {
99
- /**
100
- * Writes an informational message with an optional service label.
101
- * Missing service names use the shared `log` fallback label.
102
- */
103
- info(message, service) {
104
- writeLog(message, "info", service);
105
- },
106
- /**
107
- * Writes a warning message with an optional service label.
108
- * Missing service names use the shared `log` fallback label.
109
- */
110
- warn(message, service) {
111
- writeLog(message, "warn", service);
112
- },
113
- /**
114
- * Writes an error message with optional service and stack trace context.
115
- * Provided stack traces are rendered beneath the primary message.
116
- */
117
- error(message, service, stack) {
118
- writeLog(message, "error", service, stack);
119
- }
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)}`);
180
+ }
181
+ /**
182
+ * Writes timestamped and colorized messages using a stable service column.
183
+ * Error messages may include a stack trace on a subordinate second line.
184
+ */
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
+ }
120
207
  };
121
-
122
- // src/utilities/generation.utilities.ts
123
- var DEFAULT_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
208
+ //#endregion
209
+ //#region src/utilities/generation.utilities.ts
210
+ const DEFAULT_CHARACTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
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`
218
+ */
124
219
  function generateRandomString(length = 10) {
125
- const randomValues = crypto.getRandomValues(new Uint32Array(length));
126
- let result = "";
127
- for (let i = 0; i < length; i++) {
128
- result += DEFAULT_CHARACTERS[randomValues[i] % DEFAULT_CHARACTERS.length];
129
- }
130
- return result;
131
- }
132
-
133
- // src/hono/hono.execution.ts
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;
224
+ }
225
+ //#endregion
226
+ //#region src/hono/hono.execution.ts
134
227
  function proceedUnhandledError(error) {
135
- const errorId = generateRandomString(6);
136
- const errorMessage = error instanceof Error ? error.stack ?? error.message : JSON.stringify(error);
137
- log.error(`Unhandled error: ${red3(errorMessage)}`, errorId);
138
- return failure({ status: 500, error: `Internal server error | ${errorId}` });
139
- }
140
- var onHandlerError = (error, c) => {
141
- let response;
142
- if (error instanceof HTTPException && error.status < 500) {
143
- response = failure({ status: error.status, error: error.message });
144
- } else
145
- response = proceedUnhandledError(error);
146
- return c.json(response, response.status);
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}`
234
+ });
235
+ }
236
+ /**
237
+ * Converts expected `HTTPException` values into shared API error envelopes.
238
+ * Unexpected failures are logged and represented by a traceable generic response.
239
+ */
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);
147
248
  };
148
-
149
- // src/hono/hono.respond.ts
249
+ //#endregion
250
+ //#region src/hono/hono.respond.ts
251
+ /**
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.
254
+ */
150
255
  function respond(c, options) {
151
- return c.json(success({ status: options.status, data: options.data ?? {} }), options.status);
256
+ return c.json(success({
257
+ status: options.status,
258
+ data: options.data ?? {}
259
+ }), options.status);
152
260
  }
261
+ /**
262
+ * Responds with downloadable binary content and its attachment headers.
263
+ * Unknown/undefined content types default to `application/octet-stream`.
264
+ */
153
265
  function fileRespond(c, options) {
154
- c.header("Content-Disposition", `attachment; filename="${options.filename}"`);
155
- c.header("Content-Type", options.contentType ?? "application/octet-stream");
156
- return c.body(options.content, options.status);
157
- }
158
-
159
- // src/hmac/hmac.enums.ts
160
- var hmacAlgorithmsArray = ["SHA-256", "SHA-384", "SHA-512"];
161
- var hmacAlgorithmsRecord = createStringEnumRecord(hmacAlgorithmsArray);
162
- var hmacAlgorithm = hmacAlgorithmsRecord;
163
- var hmacEncodingsArray = ["hex", "base64", "base64url"];
164
- var hmacEncodingsRecord = createStringEnumRecord(hmacEncodingsArray);
165
- var hmacEncoding = hmacEncodingsRecord;
166
-
167
- // src/hmac/hmac.constants.ts
168
- var DEFAULT_HMAC_ALGORITHM = hmacAlgorithm.SHA_256;
169
- var DEFAULT_HMAC_ENCODING = hmacEncoding.HEX;
170
-
171
- // src/utilities/encoding.utilities.ts
172
- var BASE64_BYTE_CHUNK_SIZE = 32768;
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);
269
+ }
270
+ //#endregion
271
+ //#region src/hmac/hmac.enums.ts
272
+ const hmacAlgorithmsArray = [
273
+ "SHA-256",
274
+ "SHA-384",
275
+ "SHA-512"
276
+ ];
277
+ const hmacAlgorithmsRecord = createStringEnumRecord(hmacAlgorithmsArray);
278
+ const hmacAlgorithm = hmacAlgorithmsRecord;
279
+ const hmacEncodingsArray = [
280
+ "hex",
281
+ "base64",
282
+ "base64url"
283
+ ];
284
+ const hmacEncodingsRecord = createStringEnumRecord(hmacEncodingsArray);
285
+ const hmacEncoding = hmacEncodingsRecord;
286
+ //#endregion
287
+ //#region src/hmac/hmac.constants.ts
288
+ /**
289
+ * Default digest algorithm used by `HMACService` when none is configured.
290
+ * SHA-256 provides broad Web Crypto support and a 256-bit authentication tag.
291
+ */
292
+ const DEFAULT_HMAC_ALGORITHM = hmacAlgorithm.SHA_256;
293
+ /**
294
+ * Default textual encoding used for signatures returned by `HMACService`.
295
+ * Hex output remains deterministic, portable, and independent of padding rules.
296
+ */
297
+ const DEFAULT_HMAC_ENCODING = hmacEncoding.HEX;
298
+ //#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
+ */
173
305
  function encodeBase64(bytes) {
174
- let binary = "";
175
- for (let offset = 0; offset < bytes.length; offset += BASE64_BYTE_CHUNK_SIZE) {
176
- binary += String.fromCharCode(...bytes.subarray(offset, offset + BASE64_BYTE_CHUNK_SIZE));
177
- }
178
- return btoa(binary);
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);
179
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
+ */
180
314
  function decodeBase64(value) {
181
- const binary = atob(value);
182
- return Uint8Array.from(binary, (character) => character.charCodeAt(0));
315
+ const binary = atob(value);
316
+ return Uint8Array.from(binary, (character) => character.charCodeAt(0));
183
317
  }
184
-
185
- // src/hmac/hmac.utilities.ts
186
- var textEncoder = new TextEncoder();
318
+ //#endregion
319
+ //#region src/hmac/hmac.utilities.ts
320
+ const textEncoder = new TextEncoder();
321
+ /**
322
+ * Converts an HMAC payload or secret into an isolated byte array representation.
323
+ * Strings use UTF-8 while supplied bytes are copied to prevent later mutation.
324
+ */
187
325
  function toHMACBytes(input) {
188
- if (typeof input === "string")
189
- return textEncoder.encode(input);
190
- const bytes = new Uint8Array(input.length);
191
- bytes.set(input);
192
- return bytes;
326
+ if (typeof input === "string") return textEncoder.encode(input);
327
+ const bytes = new Uint8Array(input.length);
328
+ bytes.set(input);
329
+ return bytes;
193
330
  }
331
+ /**
332
+ * Encodes binary HMAC output with one supported transport-safe representation.
333
+ * Hex output remains lowercase while Base64 URL output omits conventional padding.
334
+ */
194
335
  function encodeHMACSignature(signature, encoding) {
195
- if (encoding === "hex") {
196
- return Array.from(signature, (byte) => byte.toString(16).padStart(2, "0")).join("");
197
- }
198
- const base64 = encodeBase64(signature);
199
- if (encoding === "base64")
200
- return base64;
201
- return base64.replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "");
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(/=+$/, "");
202
340
  }
341
+ /**
342
+ * Decodes a textual HMAC signature into bytes for cryptographic verification.
343
+ * Malformed alphabets, lengths, or padding combinations reject with `TypeError`.
344
+ */
203
345
  function decodeHMACSignature(signature, encoding) {
204
- if (encoding === "hex") {
205
- if (signature.length % 2 !== 0 || !/^[0-9a-f]*$/i.test(signature)) {
206
- throw new TypeError("Invalid hexadecimal HMAC signature");
207
- }
208
- return Uint8Array.from(signature.match(/.{2}/g) ?? [], (byte) => Number.parseInt(byte, 16));
209
- }
210
- if (encoding === "base64") {
211
- if (!/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(signature)) {
212
- throw new TypeError("Invalid Base64 HMAC signature");
213
- }
214
- return decodeBase64(signature);
215
- }
216
- if (!/^[A-Za-z0-9_-]*$/.test(signature) || signature.length % 4 === 1) {
217
- throw new TypeError("Invalid Base64 URL HMAC signature");
218
- }
219
- const base64 = signature.replaceAll("-", "+").replaceAll("_", "/").padEnd(Math.ceil(signature.length / 4) * 4, "=");
220
- return decodeBase64(base64);
221
- }
222
-
223
- // src/hmac/hmac.services.ts
346
+ 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));
349
+ }
350
+ 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);
353
+ }
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, "="));
356
+ }
357
+ //#endregion
358
+ //#region src/hmac/hmac.services.ts
359
+ /**
360
+ * Creates and verifies keyed message authentication codes through Web Crypto.
361
+ * Each instance preserves its digest algorithm and textual encoding configuration.
362
+ */
224
363
  var HMACService = class {
225
- algorithm;
226
- encoding;
227
- constructor(options = {}) {
228
- this.algorithm = options.algorithm ?? DEFAULT_HMAC_ALGORITHM;
229
- this.encoding = options.encoding ?? DEFAULT_HMAC_ENCODING;
230
- }
231
- /**
232
- * Authenticates a payload with a secret and returns the encoded signature.
233
- * String inputs use UTF-8 while byte arrays preserve their exact byte sequence.
234
- *
235
- * @example
236
- * const signature = await hmacService.sign('payload', 'shared-secret');
237
- */
238
- async sign(payload, secret) {
239
- const key = await this.importSecret(secret);
240
- const signature = await crypto.subtle.sign("HMAC", key, toHMACBytes(payload));
241
- return encodeHMACSignature(new Uint8Array(signature), this.encoding);
242
- }
243
- /**
244
- * Verifies an encoded signature without requiring a manual equality comparison.
245
- * Invalid encodings, incorrect secrets, and altered payloads all resolve to `false`.
246
- *
247
- * @example
248
- * const verified = await hmacService.verify('payload', signature, 'shared-secret');
249
- */
250
- async verify(payload, signature, secret) {
251
- let signatureBytes;
252
- try {
253
- signatureBytes = decodeHMACSignature(signature, this.encoding);
254
- } catch {
255
- return false;
256
- }
257
- const key = await this.importSecret(secret);
258
- return crypto.subtle.verify("HMAC", key, signatureBytes, toHMACBytes(payload));
259
- }
260
- async importSecret(secret) {
261
- const secretBytes = toHMACBytes(secret);
262
- const algorithm = { name: "HMAC", hash: this.algorithm };
263
- const keyUsages = ["sign", "verify"];
264
- return crypto.subtle.importKey("raw", secretBytes, algorithm, false, keyUsages);
265
- }
364
+ algorithm;
365
+ encoding;
366
+ constructor(options = {}) {
367
+ this.algorithm = options.algorithm ?? DEFAULT_HMAC_ALGORITHM;
368
+ this.encoding = options.encoding ?? DEFAULT_HMAC_ENCODING;
369
+ }
370
+ /**
371
+ * 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.
373
+ *
374
+ * @example
375
+ * const signature = await hmacService.sign('payload', 'shared-secret');
376
+ */
377
+ async sign(payload, secret) {
378
+ const key = await this.importSecret(secret);
379
+ const signature = await crypto.subtle.sign("HMAC", key, toHMACBytes(payload));
380
+ return encodeHMACSignature(new Uint8Array(signature), this.encoding);
381
+ }
382
+ /**
383
+ * Verifies an encoded signature without requiring a manual equality comparison.
384
+ * Invalid encodings, incorrect secrets, and altered payloads all resolve to `false`.
385
+ *
386
+ * @example
387
+ * const verified = await hmacService.verify('payload', signature, 'shared-secret');
388
+ */
389
+ async verify(payload, signature, secret) {
390
+ let signatureBytes;
391
+ try {
392
+ signatureBytes = decodeHMACSignature(signature, this.encoding);
393
+ } catch {
394
+ return false;
395
+ }
396
+ const key = await this.importSecret(secret);
397
+ return crypto.subtle.verify("HMAC", key, signatureBytes, toHMACBytes(payload));
398
+ }
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"]);
406
+ }
266
407
  };
267
-
268
- // src/http/http.constants.ts
269
- var SUCCESS_STATUS_CODES = [200, 201, 202, 307];
270
- var EXCEPTION_STATUS_CODES = [400, 401, 403, 404, 405, 409, 500];
271
-
272
- // src/http/http.resolvers.ts
408
+ //#endregion
409
+ //#region src/http/http.constants.ts
410
+ const SUCCESS_STATUS_CODES = [
411
+ 200,
412
+ 201,
413
+ 202,
414
+ 307
415
+ ];
416
+ const EXCEPTION_STATUS_CODES = [
417
+ 400,
418
+ 401,
419
+ 403,
420
+ 404,
421
+ 405,
422
+ 409,
423
+ 500
424
+ ];
425
+ //#endregion
426
+ //#region src/http/http.resolvers.ts
427
+ /**
428
+ * Converts an API contract envelope into a mutually exclusive safe result.
429
+ * Only `APIError` values are normalized; rejected fetchers still reject.
430
+ */
273
431
  async function fetchSafely(fetcher) {
274
- const response = await fetcher();
275
- if (response.kind === "error")
276
- return { error: response.error, data: null };
277
- return { error: null, data: response.data };
432
+ const response = await fetcher();
433
+ if (response.kind === "error") return {
434
+ error: response.error,
435
+ data: null
436
+ };
437
+ return {
438
+ error: null,
439
+ data: response.data
440
+ };
278
441
  }
442
+ /**
443
+ * Returns successful API data and throws for an `APIError` envelope.
444
+ * Rejected fetchers propagate their original error without replacement.
445
+ */
279
446
  async function fetchAndThrow(fetcher) {
280
- const response = await fetcher();
281
- if (response.kind === "error")
282
- throw new Error(response.error);
283
- return response.data;
284
- }
285
-
286
- // src/logging/logging.middleware.ts
287
- import { blue as blue2, bold, dim as dim2, white as white2 } from "kleur/colors";
288
-
289
- // src/logging/logging.security.ts
447
+ const response = await fetcher();
448
+ if (response.kind === "error") throw new Error(response.error);
449
+ return response.data;
450
+ }
451
+ //#endregion
452
+ //#region src/logging/logging.security.ts
453
+ /**
454
+ * Checks whether a logging field name contains a configured sensitive fragment.
455
+ * Matching ignores casing and separators to cover compound naming conventions.
456
+ */
290
457
  function isSensitiveLogKey(key) {
291
- const normalizedKey = key.toLowerCase().replaceAll(/[^a-z]/g, "");
292
- return sensitiveLogKeyParts.some((sensitivePart) => normalizedKey.includes(sensitivePart));
458
+ const normalizedKey = key.toLowerCase().replaceAll(/[^a-z]/g, "");
459
+ return sensitiveLogKeyParts.some((sensitivePart) => normalizedKey.includes(sensitivePart));
293
460
  }
461
+ /**
462
+ * Recursively replaces values owned by sensitive object keys with the public marker.
463
+ * The result reports whether any replacement occurred to avoid needless serialization.
464
+ */
294
465
  function redactSensitiveValue(value) {
295
- if (Array.isArray(value)) {
296
- const entries2 = value.map(redactSensitiveValue);
297
- return {
298
- value: entries2.map((entry) => entry.value),
299
- redacted: entries2.some((entry) => entry.redacted)
300
- };
301
- }
302
- if (typeof value !== "object" || value === null)
303
- return { value, redacted: false };
304
- let redacted = false;
305
- const entries = Object.entries(value).map(([key, entryValue]) => {
306
- if (isSensitiveLogKey(key)) {
307
- redacted = true;
308
- return [key, REDACTED_LOG_VALUE];
309
- }
310
- const nestedEntry = redactSensitiveValue(entryValue);
311
- redacted ||= nestedEntry.redacted;
312
- return [key, nestedEntry.value];
313
- });
314
- return { value: Object.fromEntries(entries), redacted };
466
+ if (Array.isArray(value)) {
467
+ const entries = value.map(redactSensitiveValue);
468
+ return {
469
+ value: entries.map((entry) => entry.value),
470
+ redacted: entries.some((entry) => entry.redacted)
471
+ };
472
+ }
473
+ if (typeof value !== "object" || value === null) return {
474
+ value,
475
+ redacted: false
476
+ };
477
+ let redacted = false;
478
+ const entries = Object.entries(value).map(([key, entryValue]) => {
479
+ if (isSensitiveLogKey(key)) {
480
+ redacted = true;
481
+ return [key, REDACTED_LOG_VALUE];
482
+ }
483
+ const nestedEntry = redactSensitiveValue(entryValue);
484
+ redacted ||= nestedEntry.redacted;
485
+ return [key, nestedEntry.value];
486
+ });
487
+ return {
488
+ value: Object.fromEntries(entries),
489
+ redacted
490
+ };
315
491
  }
492
+ /**
493
+ * Replaces sensitive query values using partial, case-insensitive key matching.
494
+ * A new collection is returned so the caller's search parameters remain unchanged.
495
+ */
316
496
  function redactSensitiveSearchParams(searchParams) {
317
- const redactedSearchParams = new URLSearchParams(searchParams);
318
- for (const key of new Set(redactedSearchParams.keys())) {
319
- if (isSensitiveLogKey(key))
320
- redactedSearchParams.set(key, REDACTED_LOG_VALUE);
321
- }
322
- return redactedSearchParams;
497
+ const redactedSearchParams = new URLSearchParams(searchParams);
498
+ for (const key of new Set(redactedSearchParams.keys())) if (isSensitiveLogKey(key)) redactedSearchParams.set(key, REDACTED_LOG_VALUE);
499
+ return redactedSearchParams;
323
500
  }
501
+ /**
502
+ * Replaces values under sensitive keys throughout a serialized JSON structure.
503
+ * Invalid JSON and payloads without matching keys are returned byte-for-byte unchanged.
504
+ */
324
505
  function redactSensitiveJSON(json) {
325
- if (json === void 0 || null)
326
- return json;
327
- try {
328
- const parsedValue = JSON.parse(json);
329
- const result = redactSensitiveValue(parsedValue);
330
- return result.redacted ? JSON.stringify(result.value) : json;
331
- } catch {
332
- return json;
333
- }
334
- }
335
-
336
- // src/logging/logging.utilities.ts
506
+ if (json === void 0 || null) return json;
507
+ try {
508
+ const result = redactSensitiveValue(JSON.parse(json));
509
+ return result.redacted ? JSON.stringify(result.value) : json;
510
+ } catch {
511
+ return json;
512
+ }
513
+ }
514
+ //#endregion
515
+ //#region src/logging/logging.utilities.ts
516
+ /**
517
+ * Selects a terminal color function for one HTTP response status.
518
+ * Successes are green, client errors yellow, and server errors red.
519
+ */
337
520
  function getColoredHTTPStatus(status) {
338
- const statusColor = httpStatusColors.find(({ range: [minimum, maximum] }) => {
339
- return status >= minimum && status <= maximum;
340
- });
341
- return statusColor ? statusColor.color : (text) => text;
342
- }
343
-
344
- // src/logging/logging.middleware.ts
345
- var loggingMiddleware = async (c, next) => {
346
- const contentType = c.req.header("content-type");
347
- const searchParams = redactSensitiveSearchParams(new URL(c.req.url).searchParams).toString();
348
- const body = contentType?.includes("multipart/form-data") ? "[multipart]" : redactSensitiveJSON((await c.req.raw.clone().text()).replaceAll(/\s+/g, " ").trim());
349
- const bodyPreview = body.length > 60 ? `${body.slice(0, 30)}\u2026${body.slice(-30)}` : body;
350
- const startTime = performance.now();
351
- await next();
352
- const duration = Math.round(performance.now() - startTime);
353
- const coloredMethod = bold(blue2(c.req.method.padEnd(4)));
354
- const coloredStatus = getColoredHTTPStatus(c.res.status)(String(c.res.status).padEnd(4));
355
- const coloredTime = dim2(`${duration}ms`.padStart(6));
356
- const coloredPath = white2(c.req.path.padEnd(32));
357
- const coloredSearchParams = searchParams ? dim2(` (${searchParams})`) : "";
358
- const coloredBody = bodyPreview ? dim2(` ${bodyPreview}`) : "";
359
- log.info(`${coloredMethod} ${coloredStatus} ${coloredTime} ${coloredPath}${coloredSearchParams}${coloredBody}`, "hono");
521
+ const statusColor = httpStatusColors.find(({ range: [minimum, maximum] }) => {
522
+ return status >= minimum && status <= maximum;
523
+ });
524
+ return statusColor ? statusColor.color : (text) => text;
525
+ }
526
+ //#endregion
527
+ //#region src/logging/logging.middleware.ts
528
+ /**
529
+ * Logs Hono request metadata with query parameters and a normalized body preview.
530
+ * Sensitive values are redacted by partial key match before output is written.
531
+ */
532
+ const loggingMiddleware = async (c, next) => {
533
+ const contentType = c.req.header("content-type");
534
+ const searchParams = redactSensitiveSearchParams(new URL(c.req.url).searchParams).toString();
535
+ const body = contentType?.includes("multipart/form-data") ? "[multipart]" : redactSensitiveJSON((await c.req.raw.clone().text()).replaceAll(/\s+/g, " ").trim());
536
+ const bodyPreview = body.length > 60 ? `${body.slice(0, 30)}…${body.slice(-30)}` : body;
537
+ const startTime = performance.now();
538
+ await next();
539
+ const duration = Math.round(performance.now() - startTime);
540
+ const coloredMethod = bold(blue(c.req.method.padEnd(4)));
541
+ const coloredStatus = getColoredHTTPStatus(c.res.status)(String(c.res.status).padEnd(4));
542
+ const coloredTime = dim(`${duration}ms`.padStart(6));
543
+ const coloredPath = white(c.req.path.padEnd(32));
544
+ const coloredSearchParams = searchParams ? dim(` (${searchParams})`) : "";
545
+ const coloredBody = bodyPreview ? dim(` ${bodyPreview}`) : "";
546
+ log.info(`${coloredMethod} ${coloredStatus} ${coloredTime} ${coloredPath}${coloredSearchParams}${coloredBody}`, "hono");
360
547
  };
361
-
362
- // src/upload/upload.enums.ts
363
- var fileFormatsArray = [
364
- ...["png", "jpg", "webp", "avif", "heic", "svg"],
365
- // Image formats.
366
- ...["pdf", "rtf", "txt"]
367
- // Text and document formats.
548
+ //#endregion
549
+ //#region src/upload/upload.enums.ts
550
+ const fileFormatsArray = [...[
551
+ "png",
552
+ "jpg",
553
+ "webp",
554
+ "avif",
555
+ "heic",
556
+ "svg"
557
+ ], ...[
558
+ "pdf",
559
+ "rtf",
560
+ "txt"
561
+ ]];
562
+ const fileFormatsRecord = createStringEnumRecord(fileFormatsArray);
563
+ const fileFormat = fileFormatsRecord;
564
+ const uploadValidationErrorsArray = [
565
+ "empty_file",
566
+ "unsupported_file_format",
567
+ "file_size_exceeded",
568
+ "files_count_exceeded"
368
569
  ];
369
- var fileFormatsRecord = createStringEnumRecord(fileFormatsArray);
370
- var fileFormat = fileFormatsRecord;
371
- var uploadValidationErrorsArray = [
372
- "empty_file",
373
- "unsupported_file_format",
374
- "file_size_exceeded",
375
- "files_count_exceeded"
376
- ];
377
- var uploadValidationErrorsRecord = createStringEnumRecord(uploadValidationErrorsArray);
378
- var uploadValidationError = uploadValidationErrorsRecord;
379
-
380
- // src/upload/upload.constants.ts
381
- var fileFormatsConfig = {
382
- // Image formats.
383
- [fileFormatsRecord.PNG]: { name: "PNG", mimeTypes: ["image/png"], extensions: [".png"] },
384
- [fileFormatsRecord.JPG]: { name: "JPG", mimeTypes: ["image/jpeg"], extensions: [".jpg", ".jpeg"] },
385
- [fileFormatsRecord.SVG]: { name: "SVG", mimeTypes: ["image/svg+xml"], extensions: [".svg"] },
386
- [fileFormatsRecord.WEBP]: { name: "WEBP", mimeTypes: ["image/webp"], extensions: [".webp"] },
387
- [fileFormatsRecord.AVIF]: { name: "AVIF", mimeTypes: ["image/avif"], extensions: [".avif"] },
388
- [fileFormatsRecord.HEIC]: { name: "HEIC", mimeTypes: ["image/heic", "image/heif"], extensions: [".heic", ".heif"] },
389
- // Text and document formats.
390
- [fileFormatsRecord.PDF]: { name: "PDF", mimeTypes: ["application/pdf"], extensions: [".pdf"] },
391
- [fileFormatsRecord.RTF]: { name: "RTF", mimeTypes: ["application/rtf"], extensions: [".rtf"] },
392
- [fileFormatsRecord.TXT]: { name: "TXT", mimeTypes: ["text/plain"], extensions: [".txt"] }
570
+ const uploadValidationErrorsRecord = createStringEnumRecord(uploadValidationErrorsArray);
571
+ const uploadValidationError = uploadValidationErrorsRecord;
572
+ //#endregion
573
+ //#region src/upload/upload.constants.ts
574
+ /**
575
+ * Canonical metadata shared by upload controls and runtime validation.
576
+ * Every supported format defines display, MIME, and extension values.
577
+ */
578
+ const fileFormatsConfig = {
579
+ [fileFormatsRecord.PNG]: {
580
+ name: "PNG",
581
+ mimeTypes: ["image/png"],
582
+ extensions: [".png"]
583
+ },
584
+ [fileFormatsRecord.JPG]: {
585
+ name: "JPG",
586
+ mimeTypes: ["image/jpeg"],
587
+ extensions: [".jpg", ".jpeg"]
588
+ },
589
+ [fileFormatsRecord.SVG]: {
590
+ name: "SVG",
591
+ mimeTypes: ["image/svg+xml"],
592
+ extensions: [".svg"]
593
+ },
594
+ [fileFormatsRecord.WEBP]: {
595
+ name: "WEBP",
596
+ mimeTypes: ["image/webp"],
597
+ extensions: [".webp"]
598
+ },
599
+ [fileFormatsRecord.AVIF]: {
600
+ name: "AVIF",
601
+ mimeTypes: ["image/avif"],
602
+ extensions: [".avif"]
603
+ },
604
+ [fileFormatsRecord.HEIC]: {
605
+ name: "HEIC",
606
+ mimeTypes: ["image/heic", "image/heif"],
607
+ extensions: [".heic", ".heif"]
608
+ },
609
+ [fileFormatsRecord.PDF]: {
610
+ name: "PDF",
611
+ mimeTypes: ["application/pdf"],
612
+ extensions: [".pdf"]
613
+ },
614
+ [fileFormatsRecord.RTF]: {
615
+ name: "RTF",
616
+ mimeTypes: ["application/rtf"],
617
+ extensions: [".rtf"]
618
+ },
619
+ [fileFormatsRecord.TXT]: {
620
+ name: "TXT",
621
+ mimeTypes: ["text/plain"],
622
+ extensions: [".txt"]
623
+ }
393
624
  };
394
-
395
- // src/upload/upload.factory.ts
625
+ //#endregion
626
+ //#region src/upload/upload.factory.ts
627
+ /**
628
+ * Defines one shared upload policy while preserving its literal format tuple.
629
+ * The resulting preset can drive schemas, picker hints, and batch validation.
630
+ *
631
+ * @example
632
+ * const imageUploadPreset = defineUploadPreset({
633
+ * formats: [fileFormat.JPG, fileFormat.PNG, fileFormat.WEBP],
634
+ * maxFileSize: 8 * 1024 * 1024,
635
+ * maxFilesCount: 1,
636
+ * });
637
+ */
396
638
  function defineUploadPreset(preset) {
397
- return preset;
398
- }
399
-
400
- // src/upload/upload.presets.ts
401
- var DEFAULT_UPLOAD_MAX_FILE_SIZE = 20 * 1024 * 1024;
402
- var imageUploadPreset = defineUploadPreset({
403
- formats: [fileFormat.PNG, fileFormat.JPG, fileFormat.WEBP, fileFormat.AVIF, fileFormat.HEIC],
404
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
639
+ return preset;
640
+ }
641
+ //#endregion
642
+ //#region src/upload/upload.presets.ts
643
+ /**
644
+ * Default maximum size of one file accepted by the built-in upload presets.
645
+ * The 20 MiB boundary matches the existing upload component policy.
646
+ */
647
+ const DEFAULT_UPLOAD_MAX_FILE_SIZE = 20971520;
648
+ /**
649
+ * Ready-to-use policy for every image format supported by the upload catalog.
650
+ * Each image may occupy up to 20 MiB while collection capacity remains unrestricted.
651
+ */
652
+ const imageUploadPreset = defineUploadPreset({
653
+ formats: [
654
+ fileFormat.PNG,
655
+ fileFormat.JPG,
656
+ fileFormat.WEBP,
657
+ fileFormat.AVIF,
658
+ fileFormat.HEIC
659
+ ],
660
+ maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
405
661
  });
406
- var imageTransparentUploadPreset = defineUploadPreset({
407
- formats: [fileFormat.PNG, fileFormat.WEBP, fileFormat.SVG],
408
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
662
+ /**
663
+ * Ready-to-use policy for every image format supporting transparency (alpha channel).
664
+ * Each image may occupy up to 20 MiB while collection capacity remains unrestricted.
665
+ */
666
+ const imageTransparentUploadPreset = defineUploadPreset({
667
+ formats: [
668
+ fileFormat.PNG,
669
+ fileFormat.WEBP,
670
+ fileFormat.SVG
671
+ ],
672
+ maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
409
673
  });
410
- var documentUploadPreset = defineUploadPreset({
411
- formats: [fileFormat.PDF, fileFormat.RTF, fileFormat.TXT],
412
- maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
674
+ /**
675
+ * Ready-to-use policy for every document format supported by the upload catalog.
676
+ * Each document may occupy up to 20 MiB while collection capacity remains unrestricted.
677
+ */
678
+ const documentUploadPreset = defineUploadPreset({
679
+ formats: [
680
+ fileFormat.PDF,
681
+ fileFormat.RTF,
682
+ fileFormat.TXT
683
+ ],
684
+ maxFileSize: DEFAULT_UPLOAD_MAX_FILE_SIZE
413
685
  });
414
-
415
- // src/upload/upload.schemas.ts
416
- import { z } from "zod";
417
-
418
- // src/upload/upload.utilities.ts
686
+ //#endregion
687
+ //#region src/upload/upload.utilities.ts
688
+ /**
689
+ * Extracts a normalized trailing extension from a complete file name.
690
+ * Returns an empty string when no valid dot-delimited suffix exists.
691
+ */
419
692
  function getFileExtension(fileName) {
420
- const extensionIndex = fileName.lastIndexOf(".");
421
- return extensionIndex > 0 && extensionIndex < fileName.length - 1 ? fileName.slice(extensionIndex).toLowerCase() : "";
693
+ const extensionIndex = fileName.lastIndexOf(".");
694
+ return extensionIndex > 0 && extensionIndex < fileName.length - 1 ? fileName.slice(extensionIndex).toLowerCase() : "";
422
695
  }
696
+ /**
697
+ * Normalizes a configured extension to a lowercase dot-prefixed value.
698
+ * Existing prefixes remain intact so repeated normalization is stable.
699
+ */
423
700
  function normalizeFileExtension(extension) {
424
- const normalizedExtension = extension.toLowerCase();
425
- return normalizedExtension.startsWith(".") ? normalizedExtension : `.${normalizedExtension}`;
701
+ const normalizedExtension = extension.toLowerCase();
702
+ return normalizedExtension.startsWith(".") ? normalizedExtension : `.${normalizedExtension}`;
426
703
  }
704
+ /**
705
+ * Compares a concrete MIME type with an exact or wildcard configuration.
706
+ * Wildcards match every subtype belonging to the configured media group.
707
+ */
427
708
  function matchesMimeType(fileMimeType, configuredMimeType) {
428
- if (configuredMimeType.endsWith("/*"))
429
- return fileMimeType.startsWith(configuredMimeType.slice(0, -1));
430
- return fileMimeType === configuredMimeType;
709
+ if (configuredMimeType.endsWith("/*")) return fileMimeType.startsWith(configuredMimeType.slice(0, -1));
710
+ return fileMimeType === configuredMimeType;
431
711
  }
712
+ /**
713
+ * Checks whether a file MIME type belongs to one selected format.
714
+ * File names and extensions cannot make an unsupported MIME type valid.
715
+ */
432
716
  function isFileMimeTypeSupported(file, formats) {
433
- return formats.some((format2) => fileFormatsConfig[format2].mimeTypes.some((mimeType) => matchesMimeType(file.type, mimeType)));
717
+ return formats.some((format) => fileFormatsConfig[format].mimeTypes.some((mimeType) => matchesMimeType(file.type, mimeType)));
434
718
  }
719
+ /**
720
+ * Checks whether a file extension belongs to one selected format.
721
+ * The comparison is case-insensitive and requires a complete suffix.
722
+ */
435
723
  function isFileExtensionSupported(file, formats) {
436
- const fileExtension = getFileExtension(file.name);
437
- return formats.some((format2) => fileFormatsConfig[format2].extensions.some((extension) => normalizeFileExtension(extension) === fileExtension));
724
+ const fileExtension = getFileExtension(file.name);
725
+ return formats.some((format) => fileFormatsConfig[format].extensions.some((extension) => normalizeFileExtension(extension) === fileExtension));
438
726
  }
727
+ /**
728
+ * Checks whether a file matches one configured MIME type or extension.
729
+ * This permissive predicate supports clients where MIME metadata is absent.
730
+ */
439
731
  function isFileFormatSupported(file, formats) {
440
- return isFileMimeTypeSupported(file, formats) || isFileExtensionSupported(file, formats);
732
+ return isFileMimeTypeSupported(file, formats) || isFileExtensionSupported(file, formats);
441
733
  }
734
+ /**
735
+ * Builds a native file-picker hint from configured MIME types and extensions.
736
+ * Duplicate values are removed while their configuration order is retained.
737
+ */
442
738
  function createUploadAccept(formats) {
443
- const acceptedValues = formats.flatMap((format2) => [
444
- ...fileFormatsConfig[format2].mimeTypes,
445
- ...fileFormatsConfig[format2].extensions
446
- ]);
447
- return [...new Set(acceptedValues)].join(",");
448
- }
449
-
450
- // src/upload/upload.schemas.ts
739
+ const acceptedValues = formats.flatMap((format) => [...fileFormatsConfig[format].mimeTypes, ...fileFormatsConfig[format].extensions]);
740
+ return [...new Set(acceptedValues)].join(",");
741
+ }
742
+ //#endregion
743
+ //#region src/upload/upload.schemas.ts
744
+ /**
745
+ * Builds a reusable Zod schema for one uploaded file with format and size.
746
+ * MIME matching is strict unless extension fallback is explicitly enabled.
747
+ */
451
748
  function zodUploadFileSchema(options) {
452
- const { formats, maxFileSize, extensionFallback = false } = options;
453
- return z.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);
749
+ const { formats, maxFileSize, extensionFallback = false } = options;
750
+ 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);
454
751
  }
455
-
456
- // src/upload/upload.validation.ts
752
+ //#endregion
753
+ //#region src/upload/upload.validation.ts
754
+ /**
755
+ * Validates one file against configured format and size constraints.
756
+ * Returns the first stable error key or nothing for a valid file.
757
+ */
457
758
  function validateUploadFile(file, formats, maxFileSize) {
458
- if (file.size === 0)
459
- return uploadValidationError.EMPTY_FILE;
460
- if (isFileFormatSupported(file, formats) === false)
461
- return uploadValidationError.UNSUPPORTED_FILE_FORMAT;
462
- if (file.size > maxFileSize)
463
- return uploadValidationError.FILE_SIZE_EXCEEDED;
464
- return void 0;
759
+ if (file.size === 0) return uploadValidationError.EMPTY_FILE;
760
+ if (isFileFormatSupported(file, formats) === false) return uploadValidationError.UNSUPPORTED_FILE_FORMAT;
761
+ if (file.size > maxFileSize) return uploadValidationError.FILE_SIZE_EXCEEDED;
465
762
  }
763
+ /**
764
+ * Validates an incoming collection while preserving every accepted file.
765
+ * Returns accepted entries and the final rejection key from the batch.
766
+ */
466
767
  function validateUploadFiles(incomingFiles, options) {
467
- const { currentFilesCount, formats, maxFileSize, maxFilesCount } = options;
468
- const availableFilesCount = maxFilesCount === void 0 ? Infinity : maxFilesCount - currentFilesCount;
469
- const acceptedFiles = [];
470
- let validationError;
471
- for (const file of incomingFiles) {
472
- const fileValidationError = validateUploadFile(file, formats, maxFileSize) ?? // Collection limits apply only after intrinsic file validation succeeds.
473
- // This preserves remaining slots for supported files from the same batch.
474
- (acceptedFiles.length >= availableFilesCount ? uploadValidationError.FILES_COUNT_EXCEEDED : void 0);
475
- if (fileValidationError) {
476
- validationError = fileValidationError;
477
- continue;
478
- }
479
- acceptedFiles.push(file);
480
- }
481
- return { acceptedFiles, validationError };
482
- }
483
-
484
- // src/utilities/execution.utilities.ts
768
+ const { currentFilesCount, formats, maxFileSize, maxFilesCount } = options;
769
+ const availableFilesCount = maxFilesCount === void 0 ? Infinity : maxFilesCount - currentFilesCount;
770
+ const acceptedFiles = [];
771
+ let validationError;
772
+ for (const file of incomingFiles) {
773
+ const fileValidationError = validateUploadFile(file, formats, maxFileSize) ?? (acceptedFiles.length >= availableFilesCount ? uploadValidationError.FILES_COUNT_EXCEEDED : void 0);
774
+ if (fileValidationError) {
775
+ validationError = fileValidationError;
776
+ continue;
777
+ }
778
+ acceptedFiles.push(file);
779
+ }
780
+ return {
781
+ acceptedFiles,
782
+ validationError
783
+ };
784
+ }
785
+ //#endregion
786
+ //#region src/utilities/execution.utilities.ts
787
+ /**
788
+ * Resolves synchronous and asynchronous executions through one promise-based contract.
789
+ * Failures use the supplied fallback or propagate unchanged when none is available.
790
+ *
791
+ * @example
792
+ * await safeExecute(() => fetchData(), (error) => console.error(error));
793
+ */
485
794
  async function safeExecute(fn, onError) {
486
- try {
487
- return await fn();
488
- } catch (error) {
489
- if (onError)
490
- return await onError(error);
491
- throw error;
492
- }
795
+ try {
796
+ return await fn();
797
+ } catch (error) {
798
+ if (onError) return await onError(error);
799
+ throw error;
800
+ }
493
801
  }
802
+ /**
803
+ * Measures an asynchronous execution while preserving its resolved result.
804
+ * Rejected executions propagate unchanged and do not produce a measurement.
805
+ *
806
+ * @example
807
+ * const { result, executionTime } = await measureExecutionTime(async () => {
808
+ * return await fetchData();
809
+ * });
810
+ * console.log(`Execution time: ${executionTime}ms`);
811
+ */
494
812
  async function measureExecutionTime(execution) {
495
- const startedAt = performance.now();
496
- const result = await execution();
497
- const executionTime = performance.now() - startedAt;
498
- return { result, executionTime: Math.round(executionTime) };
499
- }
500
-
501
- // src/zod-bulk/zod-bulk.schemas.ts
502
- import { z as z2 } from "zod";
503
- var DEFAULT_ERROR_MESSAGE = "Selection identifiers must be provided.";
813
+ const startedAt = performance.now();
814
+ const result = await execution();
815
+ const executionTime = performance.now() - startedAt;
816
+ return {
817
+ result,
818
+ executionTime: Math.round(executionTime)
819
+ };
820
+ }
821
+ //#endregion
822
+ //#region src/zod-bulk/zod-bulk.schemas.ts
823
+ const DEFAULT_ERROR_MESSAGE$1 = "Selection identifiers must be provided.";
824
+ /**
825
+ * Builds a strict selection contract for bulk operations across paginated data.
826
+ * The identifier schema is shared by explicit and all-matching selection modes.
827
+ *
828
+ * `include` targets only identifiers listed by the client. `exclude` targets every
829
+ * item matching the accompanying search snapshot except excluded identifiers.
830
+ *
831
+ * @example
832
+ * const assetBulkSelectionSchema = zodBulkSelectionSchema({
833
+ * identifierSchema: z.string().min(1),
834
+ * });
835
+ */
504
836
  function zodBulkSelectionSchema({ identifierSchema }) {
505
- const identifiersSchema = z2.array(identifierSchema).refine((identifiers) => new Set(identifiers).size === identifiers.length, {
506
- message: DEFAULT_ERROR_MESSAGE
507
- });
508
- return z2.discriminatedUnion("mode", [
509
- z2.object({ mode: z2.literal("include"), identifiers: identifiersSchema }).strict(),
510
- z2.object({ mode: z2.literal("exclude"), excludedIdentifiers: identifiersSchema }).strict()
511
- ]);
512
- }
513
-
514
- // src/zod-jwt/zod-jwt.services.ts
515
- import { decode as decodeJWT, sign as signJWT, verify as verifyJWT } from "hono/jwt";
837
+ const identifiersSchema = z$1.array(identifierSchema).refine((identifiers) => new Set(identifiers).size === identifiers.length, { message: DEFAULT_ERROR_MESSAGE$1 });
838
+ return z$1.discriminatedUnion("mode", [z$1.object({
839
+ mode: z$1.literal("include"),
840
+ identifiers: identifiersSchema
841
+ }).strict(), z$1.object({
842
+ mode: z$1.literal("exclude"),
843
+ excludedIdentifiers: identifiersSchema
844
+ }).strict()]);
845
+ }
846
+ //#endregion
847
+ //#region src/zod-jwt/zod-jwt.services.ts
848
+ /**
849
+ * Signs, decodes, and verifies JWTs with optional Zod payload validation.
850
+ * A supplied schema parses payloads returned by decoding and verification.
851
+ */
516
852
  var ZodJWTService = class {
517
- payloadSchema;
518
- algorithm;
519
- defaultExpirationSeconds;
520
- constructor(payloadSchema, options) {
521
- this.payloadSchema = payloadSchema;
522
- this.algorithm = options?.algorithm ?? "HS256";
523
- this.defaultExpirationSeconds = options?.defaultExpirationSeconds ?? 60 * 15;
524
- }
525
- /**
526
- * Signs a payload using the configured algorithm and expiration settings.
527
- * Per-call expiration overrides the default configured by the service.
528
- *
529
- * @example
530
- * const token = await jwtService.sign({ userId: '123' }, secret, { expiresInSeconds: 300 });
531
- */
532
- async sign(payload, secret, options) {
533
- const exp = Math.floor(Date.now() / 1e3) + (options?.expiresInSeconds ?? this.defaultExpirationSeconds);
534
- return signJWT({ ...payload, exp }, secret, this.algorithm);
535
- }
536
- /**
537
- * Decodes without authenticating it and parses its payload when a schema exists.
538
- * Schema validation failures return `null`, while malformed tokens still reject.
539
- *
540
- * @example
541
- * const payload = await jwtService.decode(token);
542
- */
543
- async decode(token) {
544
- const { payload } = decodeJWT(token);
545
- if (this.payloadSchema === void 0) {
546
- return payload;
547
- }
548
- const { success: success2, data } = await this.payloadSchema.safeParseAsync(payload);
549
- return success2 ? data : null;
550
- }
551
- /**
552
- * Verifies a token using the configured algorithm and parses its payload.
553
- * Signature, expiration, and schema validation failures reject the operation.
554
- *
555
- * @example
556
- * const payload = await jwtService.verifyOrThrow(token, secret);
557
- */
558
- async verifyOrThrow(token, secret) {
559
- const payload = await verifyJWT(token, secret, this.algorithm);
560
- if (this.payloadSchema === void 0) {
561
- return payload;
562
- }
563
- return this.payloadSchema.parseAsync(payload);
564
- }
853
+ payloadSchema;
854
+ algorithm;
855
+ defaultExpirationSeconds;
856
+ constructor(payloadSchema, options) {
857
+ this.payloadSchema = payloadSchema;
858
+ this.algorithm = options?.algorithm ?? "HS256";
859
+ this.defaultExpirationSeconds = options?.defaultExpirationSeconds ?? 900;
860
+ }
861
+ /**
862
+ * Signs a payload using the configured algorithm and expiration settings.
863
+ * Per-call expiration overrides the default configured by the service.
864
+ *
865
+ * @example
866
+ * const token = await jwtService.sign({ userId: '123' }, secret, { expiresInSeconds: 300 });
867
+ */
868
+ async sign(payload, secret, options) {
869
+ const exp = Math.floor(Date.now() / 1e3) + (options?.expiresInSeconds ?? this.defaultExpirationSeconds);
870
+ return sign({
871
+ ...payload,
872
+ exp
873
+ }, secret, this.algorithm);
874
+ }
875
+ /**
876
+ * Decodes without authenticating it and parses its payload when a schema exists.
877
+ * Schema validation failures return `null`, while malformed tokens still reject.
878
+ *
879
+ * @example
880
+ * const payload = await jwtService.decode(token);
881
+ */
882
+ async decode(token) {
883
+ const { payload } = decode(token);
884
+ if (this.payloadSchema === void 0) return payload;
885
+ const { success, data } = await this.payloadSchema.safeParseAsync(payload);
886
+ return success ? data : null;
887
+ }
888
+ /**
889
+ * Verifies a token using the configured algorithm and parses its payload.
890
+ * Signature, expiration, and schema validation failures reject the operation.
891
+ *
892
+ * @example
893
+ * const payload = await jwtService.verifyOrThrow(token, secret);
894
+ */
895
+ async verifyOrThrow(token, secret) {
896
+ const payload = await verify(token, secret, this.algorithm);
897
+ if (this.payloadSchema === void 0) return payload;
898
+ return this.payloadSchema.parseAsync(payload);
899
+ }
565
900
  };
566
-
567
- // src/zod-search/zod-search.pagination.schemas.ts
568
- import { z as z3 } from "zod";
569
- var zodPaginationSchema = z3.object({
570
- /**
571
- * Zero-based number of records skipped before collecting a result page.
572
- * String values are coerced to support validation of URL query input.
573
- */
574
- offset: z3.coerce.number().int().nonnegative().default(0),
575
- /**
576
- * Positive maximum number of records returned in a single result page.
577
- * String values are coerced to support validation of URL query input.
578
- */
579
- limit: z3.coerce.number().int().positive().max(256).default(10)
901
+ //#endregion
902
+ //#region src/zod-search/zod-search.pagination.schemas.ts
903
+ /**
904
+ * Validates offset-based pagination shared by search request contracts.
905
+ * Missing fields inside the required object receive predictable defaults.
906
+ */
907
+ const zodPaginationSchema = z$1.object({
908
+ /**
909
+ * Zero-based number of records skipped before collecting a result page.
910
+ * String values are coerced to support validation of URL query input.
911
+ */
912
+ offset: z$1.coerce.number().int().nonnegative().max(1048576).default(0),
913
+ /**
914
+ * Positive maximum number of records returned in a single result page.
915
+ * String values are coerced to support validation of URL query input.
916
+ */
917
+ limit: z$1.coerce.number().int().positive().max(256).default(10)
580
918
  });
581
- var zodPaginationShape = zodPaginationSchema.shape;
582
-
583
- // src/zod-search/zod-search.schemas.ts
584
- import { z as z4 } from "zod";
585
-
586
- // src/zod-validation/zod-validation.refiners.ts
587
- var DEFAULT_ERROR_MESSAGE2 = "Invalid input. At least one field must be provided.";
588
- var zodAtLeastOne = (schema) => schema.superRefine((val, ctx) => {
589
- const hasValue = Object.values(val).some((v) => v !== void 0);
590
- if (!hasValue)
591
- ctx.addIssue({ code: "custom", message: DEFAULT_ERROR_MESSAGE2 });
919
+ /**
920
+ * Exposes pagination fields for composition into other object schemas.
921
+ * The shape stays derived from the schema to prevent contract divergence.
922
+ */
923
+ const zodPaginationShape = zodPaginationSchema.shape;
924
+ //#endregion
925
+ //#region src/zod-validation/zod-validation.refiners.ts
926
+ const DEFAULT_ERROR_MESSAGE = "Invalid input. At least one field must be provided.";
927
+ /**
928
+ * Wraps a partial Zod object schema with:
929
+ * 1. A runtime check ensuring at least one field is non-undefined.
930
+ * 2. A `.transform()` that narrows the output type to `AtLeastOne<T>`,
931
+ * making it directly assignable to domain `*Select` and `*Update` types without casting.
932
+ *
933
+ * @example
934
+ * zQuery(zodAtLeastOne(userSelectSchema)) // result: UserSelect ✓
935
+ */
936
+ const zodAtLeastOne = (schema) => schema.superRefine((val, ctx) => {
937
+ if (!Object.values(val).some((v) => v !== void 0)) ctx.addIssue({
938
+ code: "custom",
939
+ message: DEFAULT_ERROR_MESSAGE
940
+ });
592
941
  }).transform((val) => val);
593
-
594
- // src/zod-search/zod-search.schemas.ts
595
- var zodSearchQuerySchema = z4.string().trim().min(1).optional();
596
- var createZodSearchWhereSchema = (filters) => zodAtLeastOne(filters.partial());
597
- var zodSearchSchema = (options) => {
598
- const whereSchema = options.filters !== void 0 ? createZodSearchWhereSchema(options.filters) : options.whereSchema;
599
- const shape = {
600
- where: whereSchema.optional(),
601
- ...options.queryEnabled !== false ? { query: zodSearchQuerySchema } : {},
602
- ...options.paginationEnabled !== false ? { pagination: zodPaginationSchema } : {}
603
- };
604
- return z4.object(shape).strict();
942
+ //#endregion
943
+ //#region src/zod-search/zod-search.schemas.ts
944
+ /**
945
+ * Optional text query shared by search request contracts.
946
+ * Provided values are trimmed and must contain visible text.
947
+ */
948
+ const zodSearchQuerySchema = z$1.string().trim().min(1).optional();
949
+ /**
950
+ * Makes every supplied filter optional while requiring one defined value.
951
+ * Top-level optionality is added later for both composition modes equally.
952
+ */
953
+ const createZodSearchWhereSchema = (filters) => zodAtLeastOne(filters.partial());
954
+ /**
955
+ * Builds a strict schema for reusable search request contracts.
956
+ * The optional `where` object must contain one defined filter.
957
+ *
958
+ * Query is optional and non-empty; pagination is required by default.
959
+ * Literal feature flags update both runtime and inferred static shapes.
960
+ *
961
+ * Pass `filters` for automatic partial and non-empty validation, or use
962
+ * `whereSchema` to preserve a prepared schema with custom Zod effects.
963
+ *
964
+ * @example
965
+ * const assetSearchSchema = zodSearchSchema({
966
+ * filters: assetSchema.pick({ status: true }),
967
+ * });
968
+ *
969
+ * const refinedAssetSearchSchema = zodSearchSchema({
970
+ * whereSchema: zodAtLeastOne(assetSchema.pick({ status: true }).partial()),
971
+ * });
972
+ */
973
+ const zodSearchSchema = (options) => {
974
+ const shape = {
975
+ where: (options.filters !== void 0 ? createZodSearchWhereSchema(options.filters) : options.whereSchema).optional(),
976
+ ...options.queryEnabled !== false ? { query: zodSearchQuerySchema } : {},
977
+ ...options.paginationEnabled !== false ? { pagination: zodPaginationSchema } : {}
978
+ };
979
+ return z$1.object(shape).strict();
605
980
  };
606
-
607
- // src/zod-validation/zod-validation.parsing.ts
608
- import { z as z5 } from "zod";
609
-
610
- // src/zod-validation/zod-validation.utilities.ts
611
- var isPlainObject = (value) => {
612
- return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
981
+ //#endregion
982
+ //#region src/zod-validation/zod-validation.utilities.ts
983
+ /**
984
+ * Checks whether a value is a plain record backed by `Object.prototype`.
985
+ * Arrays, null, dates, collections, and custom class instances are rejected.
986
+ */
987
+ const isPlainObject = (value) => {
988
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.getPrototypeOf(value) === Object.prototype;
613
989
  };
614
-
615
- // src/zod-validation/zod-validation.parsing.ts
616
- var parseQueryValue = (value) => {
617
- if (Array.isArray(value))
618
- return value.map(parseQueryValue);
619
- if (isPlainObject(value)) {
620
- return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, parseQueryValue(entryValue)]));
621
- }
622
- if (typeof value !== "string")
623
- return value;
624
- const normalized = value.trim().toLowerCase();
625
- if (normalized === "")
626
- return value;
627
- if (normalized === "true")
628
- return true;
629
- if (normalized === "false")
630
- return false;
631
- if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized))
632
- return Number(normalized);
633
- return value;
990
+ //#endregion
991
+ //#region src/zod-validation/zod-validation.parsing.ts
992
+ const parseQueryValue = (value) => {
993
+ if (Array.isArray(value)) return value.map(parseQueryValue);
994
+ if (isPlainObject(value)) return Object.fromEntries(Object.entries(value).map(([key, entryValue]) => [key, parseQueryValue(entryValue)]));
995
+ if (typeof value !== "string") return value;
996
+ const normalized = value.trim().toLowerCase();
997
+ if (normalized === "") return value;
998
+ if (normalized === "true") return true;
999
+ if (normalized === "false") return false;
1000
+ if (/^[+-]?(?:\d+\.?\d*|\.\d+)(?:e[+-]?\d+)?$/i.test(normalized)) return Number(normalized);
1001
+ return value;
634
1002
  };
635
- var asQuery = (schema) => z5.preprocess(parseQueryValue, schema);
1003
+ /**
1004
+ * Preprocesses query-like values before Zod validation.
1005
+ *
1006
+ * Query parameters are string-based by nature, even when they semantically represent
1007
+ * numbers or booleans. This helper converts only clear primitive values, allowing
1008
+ * regular schemas like `z.number()` and `z.boolean()` to validate query input directly.
1009
+ *
1010
+ * @example
1011
+ * const schema = asQuery(z.object({
1012
+ * page: z.number().int().positive(),
1013
+ * isActive: z.boolean(),
1014
+ * }));
1015
+ *
1016
+ * schema.parse({ page: '2', isActive: 'true' });
1017
+ * // { page: 2, isActive: true }
1018
+ */
1019
+ const asQuery = (schema) => z$1.preprocess(parseQueryValue, schema);
1020
+ /**
1021
+ * Recursively converts payload values to strings without changing container structure.
1022
+ * Objects and arrays are copied while `null` and `undefined` retain omission semantics.
1023
+ *
1024
+ * @example
1025
+ * stringifyPayloadValues({ page: 2, enabled: true });
1026
+ * // { page: '2', enabled: 'true' }
1027
+ */
636
1028
  function stringifyPayloadValues(value) {
637
- if (Array.isArray(value)) {
638
- return value.map(stringifyPayloadValues);
639
- }
640
- if (isPlainObject(value)) {
641
- const entries = Object.entries(value).map(([key, entryValue]) => {
642
- return [key, stringifyPayloadValues(entryValue)];
643
- });
644
- return Object.fromEntries(entries);
645
- }
646
- if (value === null || value === void 0)
647
- return value;
648
- return String(value);
649
- }
650
- export {
651
- DEFAULT_HMAC_ALGORITHM,
652
- DEFAULT_HMAC_ENCODING,
653
- DEFAULT_UPLOAD_MAX_FILE_SIZE,
654
- EXCEPTION_STATUS_CODES,
655
- HMACService,
656
- REDACTED_LOG_VALUE,
657
- SUCCESS_STATUS_CODES,
658
- ZodJWTService,
659
- asQuery,
660
- createStringEnumRecord,
661
- createUploadAccept,
662
- createZodSearchWhereSchema,
663
- decodeBase64,
664
- decodeHMACSignature,
665
- defineUploadPreset,
666
- documentUploadPreset,
667
- encodeBase64,
668
- encodeHMACSignature,
669
- failure,
670
- fetchAndThrow,
671
- fetchSafely,
672
- fileFormat,
673
- fileFormatsArray,
674
- fileFormatsConfig,
675
- fileFormatsRecord,
676
- fileRespond,
677
- formatTime,
678
- generateRandomString,
679
- getColoredHTTPStatus,
680
- getFileExtension,
681
- getFormattedDate,
682
- getFormattedTime,
683
- getUTCOffset,
684
- getZonedTime,
685
- hmacAlgorithm,
686
- hmacAlgorithmsArray,
687
- hmacAlgorithmsRecord,
688
- hmacEncoding,
689
- hmacEncodingsArray,
690
- hmacEncodingsRecord,
691
- httpStatusColors,
692
- imageTransparentUploadPreset,
693
- imageUploadPreset,
694
- isFileExtensionSupported,
695
- isFileFormatSupported,
696
- isFileMimeTypeSupported,
697
- isPlainObject,
698
- log,
699
- logLevel,
700
- logLevelColors,
701
- logLevelsArray,
702
- logLevelsRecord,
703
- loggingMiddleware,
704
- matchesMimeType,
705
- measureExecutionTime,
706
- normalizeFileExtension,
707
- onHandlerError,
708
- parseQueryValue,
709
- redactSensitiveJSON,
710
- redactSensitiveSearchParams,
711
- respond,
712
- safeExecute,
713
- sensitiveLogKeyParts,
714
- sqlWhere,
715
- stringifyPayloadValues,
716
- success,
717
- toHMACBytes,
718
- uploadValidationError,
719
- uploadValidationErrorsArray,
720
- uploadValidationErrorsRecord,
721
- validateUploadFile,
722
- validateUploadFiles,
723
- zodAtLeastOne,
724
- zodBulkSelectionSchema,
725
- zodPaginationSchema,
726
- zodPaginationShape,
727
- zodSearchQuerySchema,
728
- zodSearchSchema,
729
- zodUploadFileSchema
730
- };
1029
+ if (Array.isArray(value)) return value.map(stringifyPayloadValues);
1030
+ if (isPlainObject(value)) {
1031
+ const entries = Object.entries(value).map(([key, entryValue]) => {
1032
+ return [key, stringifyPayloadValues(entryValue)];
1033
+ });
1034
+ return Object.fromEntries(entries);
1035
+ }
1036
+ if (value === null || value === void 0) return value;
1037
+ return String(value);
1038
+ }
1039
+ //#endregion
1040
+ export { 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 };