@twelvehart/supermemory-runtime 1.0.0-next.0

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 (156) hide show
  1. package/.env.example +57 -0
  2. package/README.md +374 -0
  3. package/dist/index.js +189 -0
  4. package/dist/mcp/index.js +1132 -0
  5. package/docker-compose.prod.yml +91 -0
  6. package/docker-compose.yml +358 -0
  7. package/drizzle/0000_dapper_the_professor.sql +159 -0
  8. package/drizzle/0001_api_keys.sql +51 -0
  9. package/drizzle/meta/0000_snapshot.json +1532 -0
  10. package/drizzle/meta/_journal.json +13 -0
  11. package/drizzle.config.ts +20 -0
  12. package/package.json +114 -0
  13. package/scripts/add-extraction-job.ts +122 -0
  14. package/scripts/benchmark-pgvector.ts +122 -0
  15. package/scripts/bootstrap.sh +209 -0
  16. package/scripts/check-runtime-pack.ts +111 -0
  17. package/scripts/claude-mcp-config.ts +336 -0
  18. package/scripts/docker-entrypoint.sh +183 -0
  19. package/scripts/doctor.ts +377 -0
  20. package/scripts/init-db.sql +33 -0
  21. package/scripts/install.sh +1110 -0
  22. package/scripts/mcp-setup.ts +271 -0
  23. package/scripts/migrations/001_create_pgvector_extension.sql +31 -0
  24. package/scripts/migrations/002_create_memory_embeddings_table.sql +75 -0
  25. package/scripts/migrations/003_create_hnsw_index.sql +94 -0
  26. package/scripts/migrations/004_create_memory_embeddings_standalone.sql +70 -0
  27. package/scripts/migrations/005_create_chunks_table.sql +95 -0
  28. package/scripts/migrations/006_create_processing_queue.sql +45 -0
  29. package/scripts/migrations/generate_test_data.sql +42 -0
  30. package/scripts/migrations/phase1_comprehensive_test.sql +204 -0
  31. package/scripts/migrations/run_migrations.sh +286 -0
  32. package/scripts/migrations/test_hnsw_index.sql +255 -0
  33. package/scripts/pre-commit-secrets +282 -0
  34. package/scripts/run-extraction-worker.ts +46 -0
  35. package/scripts/run-phase1-tests.sh +291 -0
  36. package/scripts/setup.ts +222 -0
  37. package/scripts/smoke-install.sh +12 -0
  38. package/scripts/test-health-endpoint.sh +328 -0
  39. package/src/api/index.ts +2 -0
  40. package/src/api/middleware/auth.ts +80 -0
  41. package/src/api/middleware/csrf.ts +308 -0
  42. package/src/api/middleware/errorHandler.ts +166 -0
  43. package/src/api/middleware/rateLimit.ts +360 -0
  44. package/src/api/middleware/validation.ts +514 -0
  45. package/src/api/routes/documents.ts +286 -0
  46. package/src/api/routes/profiles.ts +237 -0
  47. package/src/api/routes/search.ts +71 -0
  48. package/src/api/stores/index.ts +58 -0
  49. package/src/config/bootstrap-env.ts +3 -0
  50. package/src/config/env.ts +71 -0
  51. package/src/config/feature-flags.ts +25 -0
  52. package/src/config/index.ts +140 -0
  53. package/src/config/secrets.config.ts +291 -0
  54. package/src/db/client.ts +92 -0
  55. package/src/db/index.ts +73 -0
  56. package/src/db/postgres.ts +72 -0
  57. package/src/db/schema/chunks.schema.ts +31 -0
  58. package/src/db/schema/containers.schema.ts +46 -0
  59. package/src/db/schema/documents.schema.ts +49 -0
  60. package/src/db/schema/embeddings.schema.ts +32 -0
  61. package/src/db/schema/index.ts +11 -0
  62. package/src/db/schema/memories.schema.ts +72 -0
  63. package/src/db/schema/profiles.schema.ts +34 -0
  64. package/src/db/schema/queue.schema.ts +59 -0
  65. package/src/db/schema/relationships.schema.ts +42 -0
  66. package/src/db/schema.ts +223 -0
  67. package/src/db/worker-connection.ts +47 -0
  68. package/src/index.ts +235 -0
  69. package/src/mcp/CLAUDE.md +1 -0
  70. package/src/mcp/index.ts +1380 -0
  71. package/src/mcp/legacyState.ts +22 -0
  72. package/src/mcp/rateLimit.ts +358 -0
  73. package/src/mcp/resources.ts +309 -0
  74. package/src/mcp/results.ts +104 -0
  75. package/src/mcp/tools.ts +401 -0
  76. package/src/queues/config.ts +119 -0
  77. package/src/queues/index.ts +289 -0
  78. package/src/sdk/client.ts +225 -0
  79. package/src/sdk/errors.ts +266 -0
  80. package/src/sdk/http.ts +560 -0
  81. package/src/sdk/index.ts +244 -0
  82. package/src/sdk/resources/base.ts +65 -0
  83. package/src/sdk/resources/connections.ts +204 -0
  84. package/src/sdk/resources/documents.ts +163 -0
  85. package/src/sdk/resources/index.ts +10 -0
  86. package/src/sdk/resources/memories.ts +150 -0
  87. package/src/sdk/resources/search.ts +60 -0
  88. package/src/sdk/resources/settings.ts +36 -0
  89. package/src/sdk/types.ts +674 -0
  90. package/src/services/chunking/index.ts +451 -0
  91. package/src/services/chunking.service.ts +650 -0
  92. package/src/services/csrf.service.ts +252 -0
  93. package/src/services/documents.repository.ts +219 -0
  94. package/src/services/documents.service.ts +191 -0
  95. package/src/services/embedding.service.ts +404 -0
  96. package/src/services/extraction.service.ts +300 -0
  97. package/src/services/extractors/code.extractor.ts +451 -0
  98. package/src/services/extractors/index.ts +9 -0
  99. package/src/services/extractors/markdown.extractor.ts +461 -0
  100. package/src/services/extractors/pdf.extractor.ts +315 -0
  101. package/src/services/extractors/text.extractor.ts +118 -0
  102. package/src/services/extractors/url.extractor.ts +243 -0
  103. package/src/services/index.ts +235 -0
  104. package/src/services/ingestion.service.ts +177 -0
  105. package/src/services/llm/anthropic.ts +400 -0
  106. package/src/services/llm/base.ts +460 -0
  107. package/src/services/llm/contradiction-detector.service.ts +526 -0
  108. package/src/services/llm/heuristics.ts +148 -0
  109. package/src/services/llm/index.ts +309 -0
  110. package/src/services/llm/memory-classifier.service.ts +383 -0
  111. package/src/services/llm/memory-extension-detector.service.ts +523 -0
  112. package/src/services/llm/mock.ts +470 -0
  113. package/src/services/llm/openai.ts +398 -0
  114. package/src/services/llm/prompts.ts +438 -0
  115. package/src/services/llm/types.ts +373 -0
  116. package/src/services/memory.repository.ts +1769 -0
  117. package/src/services/memory.service.ts +1338 -0
  118. package/src/services/memory.types.ts +234 -0
  119. package/src/services/persistence/index.ts +295 -0
  120. package/src/services/pipeline.service.ts +509 -0
  121. package/src/services/profile.repository.ts +436 -0
  122. package/src/services/profile.service.ts +560 -0
  123. package/src/services/profile.types.ts +270 -0
  124. package/src/services/relationships/detector.ts +1128 -0
  125. package/src/services/relationships/index.ts +268 -0
  126. package/src/services/relationships/memory-integration.ts +459 -0
  127. package/src/services/relationships/strategies.ts +132 -0
  128. package/src/services/relationships/types.ts +370 -0
  129. package/src/services/search.service.ts +761 -0
  130. package/src/services/search.types.ts +220 -0
  131. package/src/services/secrets.service.ts +384 -0
  132. package/src/services/vectorstore/base.ts +327 -0
  133. package/src/services/vectorstore/index.ts +444 -0
  134. package/src/services/vectorstore/memory.ts +286 -0
  135. package/src/services/vectorstore/migration.ts +295 -0
  136. package/src/services/vectorstore/mock.ts +403 -0
  137. package/src/services/vectorstore/pgvector.ts +695 -0
  138. package/src/services/vectorstore/types.ts +247 -0
  139. package/src/startup.ts +389 -0
  140. package/src/types/api.types.ts +193 -0
  141. package/src/types/document.types.ts +103 -0
  142. package/src/types/index.ts +241 -0
  143. package/src/types/profile.base.ts +133 -0
  144. package/src/utils/errors.ts +447 -0
  145. package/src/utils/id.ts +15 -0
  146. package/src/utils/index.ts +101 -0
  147. package/src/utils/logger.ts +313 -0
  148. package/src/utils/sanitization.ts +501 -0
  149. package/src/utils/secret-validation.ts +273 -0
  150. package/src/utils/synonyms.ts +188 -0
  151. package/src/utils/validation.ts +581 -0
  152. package/src/workers/chunking.worker.ts +242 -0
  153. package/src/workers/embedding.worker.ts +358 -0
  154. package/src/workers/extraction.worker.ts +346 -0
  155. package/src/workers/indexing.worker.ts +505 -0
  156. package/tsconfig.json +38 -0
@@ -0,0 +1,514 @@
1
+ /**
2
+ * Validation Middleware for Supermemory Clone API
3
+ *
4
+ * Provides request validation middleware including:
5
+ * - Zod schema validation for request bodies
6
+ * - Content size limits (50KB default)
7
+ * - Path traversal protection
8
+ * - XSS content sanitization
9
+ */
10
+
11
+ import { Context, MiddlewareHandler, Next } from 'hono'
12
+ import { ZodSchema, ZodError } from 'zod'
13
+ import { ErrorCodes, ErrorResponse } from '../../types/api.types.js'
14
+ import { sanitizeHtml, sanitizeForStorage, isPathSafe } from '../../utils/sanitization.js'
15
+
16
+ // ============================================================================
17
+ // Configuration
18
+ // ============================================================================
19
+
20
+ /**
21
+ * Default maximum content size in bytes (50KB).
22
+ * Can be overridden via SUPERMEMORY_MAX_CONTENT_SIZE environment variable.
23
+ */
24
+ export const MAX_CONTENT_SIZE = parseInt(process.env.SUPERMEMORY_MAX_CONTENT_SIZE || '', 10) || 50 * 1024
25
+
26
+ /**
27
+ * Maximum JSON body size for metadata (10KB).
28
+ */
29
+ export const MAX_METADATA_SIZE = 10 * 1024
30
+
31
+ /**
32
+ * Maximum query string length (10KB).
33
+ */
34
+ export const MAX_QUERY_LENGTH = 10 * 1024
35
+
36
+ /**
37
+ * Maximum container tag length.
38
+ */
39
+ export const MAX_CONTAINER_TAG_LENGTH = 100
40
+
41
+ // ============================================================================
42
+ // Error Helpers
43
+ // ============================================================================
44
+
45
+ /**
46
+ * Creates a standardized validation error response.
47
+ */
48
+ function createValidationErrorResponse(
49
+ message: string,
50
+ details?: Record<string, unknown>
51
+ ): {
52
+ response: ErrorResponse
53
+ status: 400
54
+ } {
55
+ return {
56
+ response: {
57
+ error: {
58
+ code: ErrorCodes.VALIDATION_ERROR,
59
+ message,
60
+ ...(details && { details }),
61
+ },
62
+ status: 400,
63
+ },
64
+ status: 400,
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Creates a security error response for path traversal or similar attacks.
70
+ */
71
+ function createSecurityErrorResponse(message: string): { response: ErrorResponse; status: 400 } {
72
+ return {
73
+ response: {
74
+ error: {
75
+ code: ErrorCodes.BAD_REQUEST,
76
+ message: `Security violation: ${message}`,
77
+ },
78
+ status: 400,
79
+ },
80
+ status: 400,
81
+ }
82
+ }
83
+
84
+ // ============================================================================
85
+ // Content Size Middleware
86
+ // ============================================================================
87
+
88
+ /**
89
+ * Options for content size limit middleware.
90
+ */
91
+ interface ContentSizeLimitOptions {
92
+ /** Maximum content size in bytes */
93
+ maxSize?: number
94
+ /** Whether to include the limit in error messages */
95
+ includeLimit?: boolean
96
+ }
97
+
98
+ /**
99
+ * Middleware that enforces content size limits on request bodies.
100
+ *
101
+ * Prevents denial-of-service attacks via extremely large payloads.
102
+ * Returns 400 Bad Request if the content exceeds the limit.
103
+ *
104
+ * @param options - Configuration options
105
+ * @returns Hono middleware handler
106
+ *
107
+ * @example
108
+ * ```typescript
109
+ * app.post('/documents', contentSizeLimit({ maxSize: 100 * 1024 }), async (c) => {
110
+ * // Handler only runs if content <= 100KB
111
+ * });
112
+ * ```
113
+ */
114
+ export function contentSizeLimit(options: ContentSizeLimitOptions = {}): MiddlewareHandler {
115
+ const maxSize = options.maxSize ?? MAX_CONTENT_SIZE
116
+ const includeLimit = options.includeLimit ?? true
117
+
118
+ return async (c: Context, next: Next) => {
119
+ const contentLength = c.req.header('content-length')
120
+
121
+ if (contentLength) {
122
+ const size = parseInt(contentLength, 10)
123
+
124
+ if (!Number.isNaN(size) && size > maxSize) {
125
+ const { response, status } = createValidationErrorResponse(
126
+ includeLimit
127
+ ? `Content size ${formatBytes(size)} exceeds maximum allowed size of ${formatBytes(maxSize)}`
128
+ : 'Content size exceeds maximum allowed size'
129
+ )
130
+ return c.json(response, status)
131
+ }
132
+ }
133
+
134
+ return next()
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Default content size limit middleware (50KB).
140
+ */
141
+ export const defaultContentSizeLimit = contentSizeLimit()
142
+
143
+ /**
144
+ * Large content size limit middleware (1MB) for file uploads.
145
+ */
146
+ export const largeContentSizeLimit = contentSizeLimit({ maxSize: 1024 * 1024 })
147
+
148
+ // ============================================================================
149
+ // Schema Validation Middleware
150
+ // ============================================================================
151
+
152
+ /**
153
+ * Options for schema validation middleware.
154
+ */
155
+ interface ValidateSchemaOptions {
156
+ /** Whether to sanitize string values in the body */
157
+ sanitize?: boolean
158
+ /** Whether to strip HTML from string values */
159
+ stripHtml?: boolean
160
+ /** Fields that should preserve HTML (not be sanitized) */
161
+ preserveHtmlFields?: string[]
162
+ }
163
+
164
+ /**
165
+ * Middleware that validates request body against a Zod schema.
166
+ *
167
+ * Parses and validates the JSON body, attaching the validated data
168
+ * to the context for use in handlers.
169
+ *
170
+ * @param schema - Zod schema to validate against
171
+ * @param options - Validation options
172
+ * @returns Hono middleware handler
173
+ *
174
+ * @example
175
+ * ```typescript
176
+ * const CreateUserSchema = z.object({
177
+ * name: z.string().min(1),
178
+ * email: z.string().email()
179
+ * });
180
+ *
181
+ * app.post('/users', validateSchema(CreateUserSchema), async (c) => {
182
+ * const body = c.get('validatedBody');
183
+ * // body is typed and validated
184
+ * });
185
+ * ```
186
+ */
187
+ export function validateSchema<T>(schema: ZodSchema<T>, options: ValidateSchemaOptions = {}): MiddlewareHandler {
188
+ const { sanitize = true, stripHtml: shouldStripHtml = false, preserveHtmlFields = [] } = options
189
+
190
+ return async (c: Context, next: Next) => {
191
+ try {
192
+ let body = await c.req.json()
193
+
194
+ // Apply sanitization if enabled
195
+ if (sanitize && typeof body === 'object' && body !== null) {
196
+ body = sanitizeRequestBody(body, {
197
+ stripHtml: shouldStripHtml,
198
+ preserveFields: preserveHtmlFields,
199
+ })
200
+ }
201
+
202
+ // Validate against schema
203
+ const validated = schema.parse(body)
204
+
205
+ // Store validated body for handler access
206
+ c.set('validatedBody', validated)
207
+
208
+ return next()
209
+ } catch (error) {
210
+ if (error instanceof ZodError) {
211
+ const formattedErrors = formatZodErrors(error)
212
+ const { response, status } = createValidationErrorResponse(`Validation failed: ${formattedErrors}`, {
213
+ fieldErrors: extractFieldErrors(error),
214
+ })
215
+ return c.json(response, status)
216
+ }
217
+
218
+ if (error instanceof SyntaxError) {
219
+ const { response, status } = createValidationErrorResponse('Invalid JSON in request body')
220
+ return c.json(response, status)
221
+ }
222
+
223
+ throw error
224
+ }
225
+ }
226
+ }
227
+
228
+ // ============================================================================
229
+ // Path Validation Middleware
230
+ // ============================================================================
231
+
232
+ /**
233
+ * Middleware that validates path parameters for path traversal attacks.
234
+ *
235
+ * Checks all path parameters and rejects requests containing:
236
+ * - Parent directory references (..)
237
+ * - Absolute paths
238
+ * - URL-encoded traversal sequences
239
+ *
240
+ * @param paramNames - Optional list of specific parameter names to validate
241
+ * @returns Hono middleware handler
242
+ *
243
+ * @example
244
+ * ```typescript
245
+ * app.get('/files/:path', validatePathParams(), async (c) => {
246
+ * const path = c.req.param('path');
247
+ * // path is guaranteed to be safe
248
+ * });
249
+ * ```
250
+ */
251
+ export function validatePathParams(paramNames?: string[]): MiddlewareHandler {
252
+ return async (c: Context, next: Next) => {
253
+ const params = c.req.param()
254
+
255
+ const paramsToCheck = paramNames || Object.keys(params)
256
+
257
+ for (const name of paramsToCheck) {
258
+ const value = params[name]
259
+
260
+ if (value && !isPathSafe(value)) {
261
+ const { response, status } = createSecurityErrorResponse(
262
+ 'Path contains invalid characters or traversal sequences'
263
+ )
264
+ return c.json(response, status)
265
+ }
266
+ }
267
+
268
+ return next()
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Middleware that validates query parameters for dangerous content.
274
+ *
275
+ * Checks query string length and validates specific parameters.
276
+ *
277
+ * @returns Hono middleware handler
278
+ */
279
+ export function validateQueryParams(): MiddlewareHandler {
280
+ return async (c: Context, next: Next) => {
281
+ const url = new URL(c.req.url)
282
+ const queryString = url.search
283
+
284
+ // Check total query string length
285
+ if (queryString.length > MAX_QUERY_LENGTH) {
286
+ const { response, status } = createValidationErrorResponse(
287
+ `Query string exceeds maximum length of ${MAX_QUERY_LENGTH} characters`
288
+ )
289
+ return c.json(response, status)
290
+ }
291
+
292
+ return next()
293
+ }
294
+ }
295
+
296
+ // ============================================================================
297
+ // Content Sanitization Middleware
298
+ // ============================================================================
299
+
300
+ /**
301
+ * Options for content sanitization.
302
+ */
303
+ interface SanitizeOptions {
304
+ /** Whether to strip all HTML tags */
305
+ stripHtml: boolean
306
+ /** Fields to preserve (not sanitize) */
307
+ preserveFields: string[]
308
+ }
309
+
310
+ /**
311
+ * Sanitizes a request body object, removing XSS vectors from string values.
312
+ *
313
+ * @param body - Request body to sanitize
314
+ * @param options - Sanitization options
315
+ * @returns Sanitized body
316
+ */
317
+ function sanitizeRequestBody(body: Record<string, unknown>, options: SanitizeOptions): Record<string, unknown> {
318
+ const result: Record<string, unknown> = {}
319
+
320
+ for (const [key, value] of Object.entries(body)) {
321
+ if (options.preserveFields.includes(key)) {
322
+ result[key] = value
323
+ } else if (typeof value === 'string') {
324
+ result[key] = options.stripHtml ? sanitizeForStorage(value) : sanitizeHtml(value)
325
+ } else if (Array.isArray(value)) {
326
+ result[key] = value.map((item) =>
327
+ typeof item === 'object' && item !== null
328
+ ? sanitizeRequestBody(item as Record<string, unknown>, options)
329
+ : typeof item === 'string'
330
+ ? options.stripHtml
331
+ ? sanitizeForStorage(item)
332
+ : sanitizeHtml(item)
333
+ : item
334
+ )
335
+ } else if (typeof value === 'object' && value !== null) {
336
+ result[key] = sanitizeRequestBody(value as Record<string, unknown>, options)
337
+ } else {
338
+ result[key] = value
339
+ }
340
+ }
341
+
342
+ return result
343
+ }
344
+
345
+ /**
346
+ * Middleware that sanitizes request body content for XSS.
347
+ *
348
+ * Applies HTML sanitization to all string values in the request body.
349
+ * Does not reject content, just cleans it.
350
+ *
351
+ * @param options - Sanitization options
352
+ * @returns Hono middleware handler
353
+ */
354
+ export function sanitizeContent(options: Partial<SanitizeOptions> = {}): MiddlewareHandler {
355
+ const sanitizeOptions: SanitizeOptions = {
356
+ stripHtml: options.stripHtml ?? false,
357
+ preserveFields: options.preserveFields ?? [],
358
+ }
359
+
360
+ return async (c: Context, next: Next) => {
361
+ // Only process JSON bodies
362
+ const contentType = c.req.header('content-type')
363
+ if (!contentType?.includes('application/json')) {
364
+ return next()
365
+ }
366
+
367
+ try {
368
+ const body = await c.req.json()
369
+
370
+ if (typeof body === 'object' && body !== null) {
371
+ const sanitized = sanitizeRequestBody(body, sanitizeOptions)
372
+ c.set('sanitizedBody', sanitized)
373
+ }
374
+
375
+ return next()
376
+ } catch {
377
+ // If JSON parsing fails, let the next middleware handle it
378
+ return next()
379
+ }
380
+ }
381
+ }
382
+
383
+ // ============================================================================
384
+ // Combined Validation Middleware
385
+ // ============================================================================
386
+
387
+ /**
388
+ * Options for combined request validation.
389
+ */
390
+ interface RequestValidationOptions<T> {
391
+ /** Zod schema for body validation */
392
+ schema: ZodSchema<T>
393
+ /** Maximum content size in bytes */
394
+ maxSize?: number
395
+ /** Whether to sanitize string values */
396
+ sanitize?: boolean
397
+ /** Whether to strip HTML from strings */
398
+ stripHtml?: boolean
399
+ /** Fields to preserve HTML in */
400
+ preserveHtmlFields?: string[]
401
+ }
402
+
403
+ /**
404
+ * Combined middleware that applies size limit, sanitization, and schema validation.
405
+ *
406
+ * This is the recommended middleware for most endpoints.
407
+ *
408
+ * @param options - Validation configuration
409
+ * @returns Hono middleware handler
410
+ *
411
+ * @example
412
+ * ```typescript
413
+ * app.post('/documents',
414
+ * validateRequest({
415
+ * schema: CreateDocumentSchema,
416
+ * maxSize: 50 * 1024,
417
+ * sanitize: true
418
+ * }),
419
+ * async (c) => {
420
+ * const body = c.get('validatedBody');
421
+ * }
422
+ * );
423
+ * ```
424
+ */
425
+ export function validateRequest<T>(options: RequestValidationOptions<T>): MiddlewareHandler {
426
+ const sizeLimitMiddleware = contentSizeLimit({ maxSize: options.maxSize })
427
+ const schemaMiddleware = validateSchema(options.schema, {
428
+ sanitize: options.sanitize,
429
+ stripHtml: options.stripHtml,
430
+ preserveHtmlFields: options.preserveHtmlFields,
431
+ })
432
+
433
+ return async (c: Context, next: Next) => {
434
+ // Apply size limit
435
+ const sizeResult = await sizeLimitMiddleware(c, async () => {})
436
+ if (sizeResult) return sizeResult
437
+
438
+ // Apply schema validation
439
+ return schemaMiddleware(c, next)
440
+ }
441
+ }
442
+
443
+ // ============================================================================
444
+ // Utility Functions
445
+ // ============================================================================
446
+
447
+ /**
448
+ * Formats Zod validation errors into a human-readable string.
449
+ */
450
+ function formatZodErrors(error: ZodError): string {
451
+ return error.issues
452
+ .map((issue) => {
453
+ const path = issue.path.join('.')
454
+ return path ? `${path}: ${issue.message}` : issue.message
455
+ })
456
+ .join('; ')
457
+ }
458
+
459
+ /**
460
+ * Extracts field-level errors from a ZodError.
461
+ */
462
+ function extractFieldErrors(error: ZodError): Record<string, string[]> {
463
+ const fieldErrors: Record<string, string[]> = {}
464
+
465
+ for (const issue of error.issues) {
466
+ const path = issue.path.join('.') || '_root'
467
+ if (!fieldErrors[path]) {
468
+ fieldErrors[path] = []
469
+ }
470
+ fieldErrors[path].push(issue.message)
471
+ }
472
+
473
+ return fieldErrors
474
+ }
475
+
476
+ /**
477
+ * Formats bytes into a human-readable string.
478
+ */
479
+ function formatBytes(bytes: number): string {
480
+ if (bytes < 1024) return `${bytes} bytes`
481
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`
482
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`
483
+ }
484
+
485
+ // ============================================================================
486
+ // Context Type Extensions
487
+ // ============================================================================
488
+
489
+ // Extend Hono's context type to include validated body
490
+ declare module 'hono' {
491
+ interface ContextVariableMap {
492
+ validatedBody: unknown
493
+ sanitizedBody: Record<string, unknown>
494
+ }
495
+ }
496
+
497
+ // ============================================================================
498
+ // Exports
499
+ // ============================================================================
500
+
501
+ export default {
502
+ contentSizeLimit,
503
+ defaultContentSizeLimit,
504
+ largeContentSizeLimit,
505
+ validateSchema,
506
+ validatePathParams,
507
+ validateQueryParams,
508
+ sanitizeContent,
509
+ validateRequest,
510
+ MAX_CONTENT_SIZE,
511
+ MAX_METADATA_SIZE,
512
+ MAX_QUERY_LENGTH,
513
+ MAX_CONTAINER_TAG_LENGTH,
514
+ }