@kiyasov/platform-hono 1.6.1 → 2.0.1

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 (237) hide show
  1. package/.claude/settings.local.json +7 -1
  2. package/dist/cjs/src/adapters/hono-adapter.d.ts +1 -0
  3. package/dist/cjs/src/adapters/hono-adapter.js +14 -3
  4. package/dist/cjs/src/adapters/hono-adapter.js.map +1 -1
  5. package/dist/cjs/src/drivers/graphQLUpload/GraphQLUpload.d.ts +1 -1
  6. package/dist/cjs/src/drivers/graphQLUpload/Upload.d.ts +11 -8
  7. package/dist/cjs/src/drivers/graphQLUpload/Upload.js.map +1 -1
  8. package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.d.ts +20 -8
  9. package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.js +111 -58
  10. package/dist/cjs/src/drivers/graphQLUpload/fs-capacitor.js.map +1 -1
  11. package/dist/cjs/src/drivers/graphQLUpload/index.d.ts +9 -3
  12. package/dist/cjs/src/drivers/graphQLUpload/index.js +21 -3
  13. package/dist/cjs/src/drivers/graphQLUpload/index.js.map +1 -1
  14. package/dist/cjs/src/drivers/graphQLUpload/processRequest.d.ts +8 -1
  15. package/dist/cjs/src/drivers/graphQLUpload/processRequest.js +43 -37
  16. package/dist/cjs/src/drivers/graphQLUpload/processRequest.js.map +1 -1
  17. package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.d.ts +15 -0
  18. package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.js +47 -0
  19. package/dist/cjs/src/drivers/graphQLUpload/storage/capacitor-storage.js.map +1 -0
  20. package/dist/cjs/src/drivers/graphQLUpload/storage/index.d.ts +3 -0
  21. package/dist/cjs/src/drivers/graphQLUpload/storage/index.js +20 -0
  22. package/dist/cjs/src/drivers/graphQLUpload/storage/index.js.map +1 -0
  23. package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.d.ts +13 -0
  24. package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.js +31 -0
  25. package/dist/cjs/src/drivers/graphQLUpload/storage/memory-storage.js.map +1 -0
  26. package/dist/cjs/src/drivers/graphQLUpload/storage/storage.d.ts +17 -0
  27. package/dist/cjs/src/drivers/graphQLUpload/storage/storage.js +3 -0
  28. package/dist/cjs/src/drivers/graphQLUpload/storage/storage.js.map +1 -0
  29. package/dist/cjs/src/drivers/graphQLUpload/utils/file.d.ts +6 -0
  30. package/dist/cjs/src/drivers/graphQLUpload/utils/file.js +62 -0
  31. package/dist/cjs/src/drivers/graphQLUpload/utils/file.js.map +1 -0
  32. package/dist/cjs/src/drivers/graphQLUpload/utils/index.d.ts +2 -0
  33. package/dist/cjs/src/drivers/graphQLUpload/utils/index.js +19 -0
  34. package/dist/cjs/src/drivers/graphQLUpload/utils/index.js.map +1 -0
  35. package/dist/cjs/src/drivers/graphQLUpload/utils/validators.d.ts +18 -0
  36. package/dist/cjs/src/drivers/graphQLUpload/utils/validators.js +171 -0
  37. package/dist/cjs/src/drivers/graphQLUpload/utils/validators.js.map +1 -0
  38. package/dist/cjs/src/multer/index.d.ts +1 -0
  39. package/dist/cjs/src/multer/index.js +1 -0
  40. package/dist/cjs/src/multer/index.js.map +1 -1
  41. package/dist/cjs/src/multer/interceptors/any-files-interceptor.d.ts +2 -2
  42. package/dist/cjs/src/multer/interceptors/any-files-interceptor.js +6 -23
  43. package/dist/cjs/src/multer/interceptors/any-files-interceptor.js.map +1 -1
  44. package/dist/cjs/src/multer/interceptors/base-interceptor.d.ts +6 -0
  45. package/dist/cjs/src/multer/interceptors/base-interceptor.js +26 -0
  46. package/dist/cjs/src/multer/interceptors/base-interceptor.js.map +1 -0
  47. package/dist/cjs/src/multer/interceptors/file-fields-interceptor.d.ts +2 -2
  48. package/dist/cjs/src/multer/interceptors/file-fields-interceptor.js +7 -24
  49. package/dist/cjs/src/multer/interceptors/file-fields-interceptor.js.map +1 -1
  50. package/dist/cjs/src/multer/interceptors/file-interceptor.d.ts +2 -2
  51. package/dist/cjs/src/multer/interceptors/file-interceptor.js +6 -23
  52. package/dist/cjs/src/multer/interceptors/file-interceptor.js.map +1 -1
  53. package/dist/cjs/src/multer/interceptors/files-interceptor.d.ts +2 -2
  54. package/dist/cjs/src/multer/interceptors/files-interceptor.js +6 -23
  55. package/dist/cjs/src/multer/interceptors/files-interceptor.js.map +1 -1
  56. package/dist/cjs/src/multer/interceptors/index.d.ts +1 -0
  57. package/dist/cjs/src/multer/interceptors/index.js +1 -0
  58. package/dist/cjs/src/multer/interceptors/index.js.map +1 -1
  59. package/dist/cjs/src/multer/multipart/handlers/any-files.d.ts +2 -8
  60. package/dist/cjs/src/multer/multipart/handlers/any-files.js +12 -25
  61. package/dist/cjs/src/multer/multipart/handlers/any-files.js.map +1 -1
  62. package/dist/cjs/src/multer/multipart/handlers/base-handler.d.ts +42 -0
  63. package/dist/cjs/src/multer/multipart/handlers/base-handler.js +106 -0
  64. package/dist/cjs/src/multer/multipart/handlers/base-handler.js.map +1 -0
  65. package/dist/cjs/src/multer/multipart/handlers/file-fields.d.ts +3 -10
  66. package/dist/cjs/src/multer/multipart/handlers/file-fields.js +19 -33
  67. package/dist/cjs/src/multer/multipart/handlers/file-fields.js.map +1 -1
  68. package/dist/cjs/src/multer/multipart/handlers/index.d.ts +6 -1
  69. package/dist/cjs/src/multer/multipart/handlers/index.js +13 -0
  70. package/dist/cjs/src/multer/multipart/handlers/index.js.map +1 -1
  71. package/dist/cjs/src/multer/multipart/handlers/multiple-files.d.ts +2 -8
  72. package/dist/cjs/src/multer/multipart/handlers/multiple-files.js +18 -36
  73. package/dist/cjs/src/multer/multipart/handlers/multiple-files.js.map +1 -1
  74. package/dist/cjs/src/multer/multipart/handlers/single-file.d.ts +2 -8
  75. package/dist/cjs/src/multer/multipart/handlers/single-file.js +11 -33
  76. package/dist/cjs/src/multer/multipart/handlers/single-file.js.map +1 -1
  77. package/dist/cjs/src/multer/multipart/index.d.ts +1 -1
  78. package/dist/cjs/src/multer/multipart/options.d.ts +10 -16
  79. package/dist/cjs/src/multer/multipart/options.js.map +1 -1
  80. package/dist/cjs/src/multer/multipart/request.js +14 -3
  81. package/dist/cjs/src/multer/multipart/request.js.map +1 -1
  82. package/dist/cjs/src/multer/storage/disk-storage.d.ts +2 -1
  83. package/dist/cjs/src/multer/storage/disk-storage.js +2 -1
  84. package/dist/cjs/src/multer/storage/disk-storage.js.map +1 -1
  85. package/dist/cjs/src/multer/storage/memory-storage.d.ts +2 -11
  86. package/dist/cjs/src/multer/storage/memory-storage.js +6 -4
  87. package/dist/cjs/src/multer/storage/memory-storage.js.map +1 -1
  88. package/dist/cjs/src/multer/storage/storage.d.ts +6 -5
  89. package/dist/cjs/src/multer/utils/file.d.ts +6 -0
  90. package/dist/cjs/src/multer/utils/file.js +62 -0
  91. package/dist/cjs/src/multer/utils/file.js.map +1 -0
  92. package/dist/cjs/src/multer/utils/index.d.ts +2 -0
  93. package/dist/cjs/src/multer/utils/index.js +19 -0
  94. package/dist/cjs/src/multer/utils/index.js.map +1 -0
  95. package/dist/cjs/src/multer/utils/validators.d.ts +18 -0
  96. package/dist/cjs/src/multer/utils/validators.js +171 -0
  97. package/dist/cjs/src/multer/utils/validators.js.map +1 -0
  98. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  99. package/dist/esm/src/adapters/hono-adapter.d.ts +1 -0
  100. package/dist/esm/src/adapters/hono-adapter.js +14 -3
  101. package/dist/esm/src/adapters/hono-adapter.js.map +1 -1
  102. package/dist/esm/src/drivers/graphQLUpload/GraphQLUpload.d.ts +1 -1
  103. package/dist/esm/src/drivers/graphQLUpload/Upload.d.ts +11 -8
  104. package/dist/esm/src/drivers/graphQLUpload/Upload.js.map +1 -1
  105. package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.d.ts +20 -8
  106. package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.js +112 -59
  107. package/dist/esm/src/drivers/graphQLUpload/fs-capacitor.js.map +1 -1
  108. package/dist/esm/src/drivers/graphQLUpload/index.d.ts +9 -3
  109. package/dist/esm/src/drivers/graphQLUpload/index.js +7 -3
  110. package/dist/esm/src/drivers/graphQLUpload/index.js.map +1 -1
  111. package/dist/esm/src/drivers/graphQLUpload/processRequest.d.ts +8 -1
  112. package/dist/esm/src/drivers/graphQLUpload/processRequest.js +42 -36
  113. package/dist/esm/src/drivers/graphQLUpload/processRequest.js.map +1 -1
  114. package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.d.ts +15 -0
  115. package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.js +43 -0
  116. package/dist/esm/src/drivers/graphQLUpload/storage/capacitor-storage.js.map +1 -0
  117. package/dist/esm/src/drivers/graphQLUpload/storage/index.d.ts +3 -0
  118. package/dist/esm/src/drivers/graphQLUpload/storage/index.js +4 -0
  119. package/dist/esm/src/drivers/graphQLUpload/storage/index.js.map +1 -0
  120. package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.d.ts +13 -0
  121. package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.js +27 -0
  122. package/dist/esm/src/drivers/graphQLUpload/storage/memory-storage.js.map +1 -0
  123. package/dist/esm/src/drivers/graphQLUpload/storage/storage.d.ts +17 -0
  124. package/dist/esm/src/drivers/graphQLUpload/storage/storage.js +2 -0
  125. package/dist/esm/src/drivers/graphQLUpload/storage/storage.js.map +1 -0
  126. package/dist/esm/src/drivers/graphQLUpload/utils/file.d.ts +6 -0
  127. package/dist/esm/src/drivers/graphQLUpload/utils/file.js +54 -0
  128. package/dist/esm/src/drivers/graphQLUpload/utils/file.js.map +1 -0
  129. package/dist/esm/src/drivers/graphQLUpload/utils/index.d.ts +2 -0
  130. package/dist/esm/src/drivers/graphQLUpload/utils/index.js +3 -0
  131. package/dist/esm/src/drivers/graphQLUpload/utils/index.js.map +1 -0
  132. package/dist/esm/src/drivers/graphQLUpload/utils/validators.d.ts +18 -0
  133. package/dist/esm/src/drivers/graphQLUpload/utils/validators.js +167 -0
  134. package/dist/esm/src/drivers/graphQLUpload/utils/validators.js.map +1 -0
  135. package/dist/esm/src/multer/index.d.ts +1 -0
  136. package/dist/esm/src/multer/index.js +1 -0
  137. package/dist/esm/src/multer/index.js.map +1 -1
  138. package/dist/esm/src/multer/interceptors/any-files-interceptor.d.ts +2 -2
  139. package/dist/esm/src/multer/interceptors/any-files-interceptor.js +6 -23
  140. package/dist/esm/src/multer/interceptors/any-files-interceptor.js.map +1 -1
  141. package/dist/esm/src/multer/interceptors/base-interceptor.d.ts +6 -0
  142. package/dist/esm/src/multer/interceptors/base-interceptor.js +23 -0
  143. package/dist/esm/src/multer/interceptors/base-interceptor.js.map +1 -0
  144. package/dist/esm/src/multer/interceptors/file-fields-interceptor.d.ts +2 -2
  145. package/dist/esm/src/multer/interceptors/file-fields-interceptor.js +7 -24
  146. package/dist/esm/src/multer/interceptors/file-fields-interceptor.js.map +1 -1
  147. package/dist/esm/src/multer/interceptors/file-interceptor.d.ts +2 -2
  148. package/dist/esm/src/multer/interceptors/file-interceptor.js +6 -23
  149. package/dist/esm/src/multer/interceptors/file-interceptor.js.map +1 -1
  150. package/dist/esm/src/multer/interceptors/files-interceptor.d.ts +2 -2
  151. package/dist/esm/src/multer/interceptors/files-interceptor.js +6 -23
  152. package/dist/esm/src/multer/interceptors/files-interceptor.js.map +1 -1
  153. package/dist/esm/src/multer/interceptors/index.d.ts +1 -0
  154. package/dist/esm/src/multer/interceptors/index.js +1 -0
  155. package/dist/esm/src/multer/interceptors/index.js.map +1 -1
  156. package/dist/esm/src/multer/multipart/handlers/any-files.d.ts +2 -8
  157. package/dist/esm/src/multer/multipart/handlers/any-files.js +12 -25
  158. package/dist/esm/src/multer/multipart/handlers/any-files.js.map +1 -1
  159. package/dist/esm/src/multer/multipart/handlers/base-handler.d.ts +42 -0
  160. package/dist/esm/src/multer/multipart/handlers/base-handler.js +102 -0
  161. package/dist/esm/src/multer/multipart/handlers/base-handler.js.map +1 -0
  162. package/dist/esm/src/multer/multipart/handlers/file-fields.d.ts +3 -10
  163. package/dist/esm/src/multer/multipart/handlers/file-fields.js +19 -33
  164. package/dist/esm/src/multer/multipart/handlers/file-fields.js.map +1 -1
  165. package/dist/esm/src/multer/multipart/handlers/index.d.ts +6 -1
  166. package/dist/esm/src/multer/multipart/handlers/index.js +6 -1
  167. package/dist/esm/src/multer/multipart/handlers/index.js.map +1 -1
  168. package/dist/esm/src/multer/multipart/handlers/multiple-files.d.ts +2 -8
  169. package/dist/esm/src/multer/multipart/handlers/multiple-files.js +18 -36
  170. package/dist/esm/src/multer/multipart/handlers/multiple-files.js.map +1 -1
  171. package/dist/esm/src/multer/multipart/handlers/single-file.d.ts +2 -8
  172. package/dist/esm/src/multer/multipart/handlers/single-file.js +11 -33
  173. package/dist/esm/src/multer/multipart/handlers/single-file.js.map +1 -1
  174. package/dist/esm/src/multer/multipart/index.d.ts +1 -1
  175. package/dist/esm/src/multer/multipart/options.d.ts +10 -16
  176. package/dist/esm/src/multer/multipart/options.js.map +1 -1
  177. package/dist/esm/src/multer/multipart/request.js +14 -3
  178. package/dist/esm/src/multer/multipart/request.js.map +1 -1
  179. package/dist/esm/src/multer/storage/disk-storage.d.ts +2 -1
  180. package/dist/esm/src/multer/storage/disk-storage.js +2 -1
  181. package/dist/esm/src/multer/storage/disk-storage.js.map +1 -1
  182. package/dist/esm/src/multer/storage/memory-storage.d.ts +2 -11
  183. package/dist/esm/src/multer/storage/memory-storage.js +6 -4
  184. package/dist/esm/src/multer/storage/memory-storage.js.map +1 -1
  185. package/dist/esm/src/multer/storage/storage.d.ts +6 -5
  186. package/dist/esm/src/multer/utils/file.d.ts +6 -0
  187. package/dist/esm/src/multer/utils/file.js +54 -0
  188. package/dist/esm/src/multer/utils/file.js.map +1 -0
  189. package/dist/esm/src/multer/utils/index.d.ts +2 -0
  190. package/dist/esm/src/multer/utils/index.js +3 -0
  191. package/dist/esm/src/multer/utils/index.js.map +1 -0
  192. package/dist/esm/src/multer/utils/validators.d.ts +18 -0
  193. package/dist/esm/src/multer/utils/validators.js +167 -0
  194. package/dist/esm/src/multer/utils/validators.js.map +1 -0
  195. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  196. package/package.json +6 -4
  197. package/src/adapters/hono-adapter.ts +18 -3
  198. package/src/drivers/graphQLUpload/Upload.ts +34 -14
  199. package/src/drivers/graphQLUpload/fs-capacitor.ts +240 -116
  200. package/src/drivers/graphQLUpload/index.ts +37 -3
  201. package/src/drivers/graphQLUpload/processRequest.ts +92 -38
  202. package/src/drivers/graphQLUpload/storage/capacitor-storage.ts +82 -0
  203. package/src/drivers/graphQLUpload/storage/index.ts +3 -0
  204. package/src/drivers/graphQLUpload/storage/memory-storage.ts +58 -0
  205. package/src/drivers/graphQLUpload/storage/storage.ts +52 -0
  206. package/src/drivers/graphQLUpload/utils/file.ts +109 -0
  207. package/src/drivers/graphQLUpload/utils/index.ts +2 -0
  208. package/src/drivers/graphQLUpload/utils/validators.ts +219 -0
  209. package/src/multer/index.ts +1 -0
  210. package/src/multer/interceptors/any-files-interceptor.ts +12 -43
  211. package/src/multer/interceptors/base-interceptor.ts +54 -0
  212. package/src/multer/interceptors/file-fields-interceptor.ts +14 -48
  213. package/src/multer/interceptors/file-interceptor.ts +12 -44
  214. package/src/multer/interceptors/files-interceptor.ts +13 -45
  215. package/src/multer/interceptors/index.ts +1 -0
  216. package/src/multer/multipart/handlers/any-files.ts +14 -32
  217. package/src/multer/multipart/handlers/base-handler.ts +204 -0
  218. package/src/multer/multipart/handlers/file-fields.ts +29 -57
  219. package/src/multer/multipart/handlers/index.ts +11 -1
  220. package/src/multer/multipart/handlers/multiple-files.ts +23 -54
  221. package/src/multer/multipart/handlers/single-file.ts +14 -47
  222. package/src/multer/multipart/index.ts +1 -1
  223. package/src/multer/multipart/options.ts +26 -8
  224. package/src/multer/multipart/request.ts +19 -3
  225. package/src/multer/storage/disk-storage.ts +2 -1
  226. package/src/multer/storage/memory-storage.ts +13 -6
  227. package/src/multer/storage/storage.ts +12 -5
  228. package/src/multer/utils/file.ts +109 -0
  229. package/src/multer/utils/index.ts +2 -0
  230. package/src/multer/utils/validators.ts +219 -0
  231. package/test/README.md +247 -0
  232. package/test/graphql-upload.test.ts +509 -0
  233. package/test/helpers.ts +70 -0
  234. package/test/integration.test.ts +197 -0
  235. package/test/interceptors-e2e.test.ts +362 -0
  236. package/test/multipart-upload.test.ts +354 -0
  237. package/test/smoke.test.ts +227 -0
@@ -0,0 +1,509 @@
1
+ import { describe, expect, test } from 'bun:test';
2
+
3
+ import {
4
+ processRequest,
5
+ ProcessRequestOptions,
6
+ } from '../src/drivers/graphQLUpload/processRequest';
7
+ import {
8
+ CapacitorStorage,
9
+ type CapacitorStorageFile,
10
+ } from '../src/drivers/graphQLUpload/storage/capacitor-storage';
11
+ import {
12
+ MemoryStorage,
13
+ type MemoryStorageFile,
14
+ } from '../src/drivers/graphQLUpload/storage/memory-storage';
15
+ import { Storage } from '../src/drivers/graphQLUpload/storage/storage';
16
+ import { MemoryUploadFile, Upload } from '../src/drivers/graphQLUpload/Upload';
17
+ import {
18
+ asContext,
19
+ asHonoRequest,
20
+ type MockContext,
21
+ type MockRequest,
22
+ } from './helpers';
23
+
24
+ describe('GraphQL Upload', () => {
25
+ describe('Upload class', () => {
26
+ test('should create upload promise', () => {
27
+ const upload = new Upload();
28
+
29
+ expect(upload.promise).toBeInstanceOf(Promise);
30
+ expect(upload.resolve).toBeInstanceOf(Function);
31
+ expect(upload.reject).toBeInstanceOf(Function);
32
+ expect(upload.file).toBeUndefined();
33
+ });
34
+
35
+ test('should resolve upload with file', async () => {
36
+ const upload = new Upload();
37
+ const mockFile: MemoryUploadFile = {
38
+ fieldName: 'file',
39
+ originalFilename: 'test.txt',
40
+ mimetype: 'text/plain',
41
+ encoding: '7bit',
42
+ size: 4,
43
+ uploadedAt: new Date().toISOString(),
44
+ };
45
+
46
+ upload.resolve(mockFile);
47
+
48
+ const result = await upload.promise;
49
+ expect(result).toEqual(mockFile);
50
+ expect(upload.file).toEqual(mockFile);
51
+ });
52
+
53
+ test('should reject upload with error', async () => {
54
+ const upload = new Upload();
55
+ const error = new Error('Upload failed');
56
+
57
+ upload.reject(error);
58
+
59
+ let caughtError: Error | undefined;
60
+ try {
61
+ await upload.promise;
62
+ } catch (e) {
63
+ caughtError = e as Error;
64
+ }
65
+
66
+ expect(caughtError).toEqual(error);
67
+ });
68
+
69
+ test('should handle unhandled rejections gracefully', async () => {
70
+ const upload = new Upload();
71
+ upload.reject(new Error('Test error'));
72
+
73
+ // Should not throw unhandled rejection
74
+ await new Promise((resolve) => setTimeout(resolve, 10));
75
+ expect(true).toBe(true);
76
+ });
77
+ });
78
+
79
+ describe('MemoryStorage', () => {
80
+ test('should store file in memory', async () => {
81
+ const storage = new MemoryStorage();
82
+ const file = new File(['test content'], 'test.txt', {
83
+ type: 'text/plain',
84
+ });
85
+
86
+ const mockReq: MockRequest = {
87
+ header: () => 'multipart/form-data',
88
+ };
89
+
90
+ const result = await storage.handleFile(
91
+ file,
92
+ asHonoRequest(mockReq),
93
+ 'file',
94
+ );
95
+
96
+ expect(result.fieldName).toBe('file');
97
+ expect(result.originalFilename).toBe('test.txt');
98
+ expect(result.mimetype).toContain('text/plain');
99
+ expect(result.encoding).toBe('7bit');
100
+ expect(result.size).toBe(12);
101
+ expect(result.uploadedAt).toBeDefined();
102
+ expect((result as MemoryStorageFile).buffer).toBeInstanceOf(Buffer);
103
+ expect((result as MemoryStorageFile).buffer?.toString()).toBe(
104
+ 'test content',
105
+ );
106
+ });
107
+
108
+ test('should enforce max file size', async () => {
109
+ const storage = new MemoryStorage({ maxSize: 10 });
110
+ const file = new File(['this is too large'], 'test.txt', {
111
+ type: 'text/plain',
112
+ });
113
+
114
+ const mockReq: MockRequest = {
115
+ header: () => 'multipart/form-data',
116
+ };
117
+
118
+ let errorThrown = false;
119
+ try {
120
+ await storage.handleFile(file, asHonoRequest(mockReq), 'file');
121
+ } catch (err) {
122
+ errorThrown = true;
123
+ expect(err instanceof Error && err.message).toContain(
124
+ 'exceeds maximum size',
125
+ );
126
+ }
127
+
128
+ expect(errorThrown).toBe(true);
129
+ });
130
+
131
+ test('should remove file buffer', async () => {
132
+ const storage = new MemoryStorage();
133
+ const file = new File(['test'], 'test.txt', { type: 'text/plain' });
134
+
135
+ const mockReq: MockRequest = {
136
+ header: () => 'multipart/form-data',
137
+ };
138
+
139
+ const result = await storage.handleFile(
140
+ file,
141
+ asHonoRequest(mockReq),
142
+ 'file',
143
+ );
144
+
145
+ expect((result as MemoryStorageFile).buffer).toBeDefined();
146
+
147
+ await storage.removeFile(result);
148
+
149
+ // Buffer should be deleted
150
+ expect((result as MemoryStorageFile).buffer).toBeUndefined();
151
+ });
152
+ });
153
+
154
+ describe('CapacitorStorage', () => {
155
+ test('should store file with capacitor', async () => {
156
+ const storage = new CapacitorStorage();
157
+ const file = new File(['test content'], 'test.txt', {
158
+ type: 'text/plain',
159
+ });
160
+
161
+ const mockReq: MockRequest = {
162
+ header: () => 'multipart/form-data',
163
+ };
164
+
165
+ const result = await storage.handleFile(
166
+ file,
167
+ asHonoRequest(mockReq),
168
+ 'file',
169
+ );
170
+
171
+ expect(result.fieldName).toBe('file');
172
+ expect(result.originalFilename).toBe('test.txt');
173
+ expect(result.mimetype).toContain('text/plain');
174
+ expect(result.encoding).toBe('7bit');
175
+ expect(result.size).toBe(12);
176
+ expect(result.uploadedAt).toBeDefined();
177
+ expect(result.capacitor).toBeDefined();
178
+ expect(result.createReadStream).toBeInstanceOf(Function);
179
+ });
180
+
181
+ test('should enforce max file size', async () => {
182
+ const storage = new CapacitorStorage({ maxSize: 10 });
183
+ const file = new File(['this is too large'], 'test.txt', {
184
+ type: 'text/plain',
185
+ });
186
+
187
+ const mockReq: MockRequest = {
188
+ header: () => 'multipart/form-data',
189
+ };
190
+
191
+ let errorThrown = false;
192
+ try {
193
+ await storage.handleFile(file, asHonoRequest(mockReq), 'file');
194
+ } catch (err) {
195
+ errorThrown = true;
196
+ expect(err instanceof Error && err.message).toContain(
197
+ 'exceeds maximum size',
198
+ );
199
+ }
200
+
201
+ expect(errorThrown).toBe(true);
202
+ });
203
+
204
+ test('should release capacitor on remove', async () => {
205
+ const storage = new CapacitorStorage();
206
+ const file = new File(['test'], 'test.txt', { type: 'text/plain' });
207
+
208
+ const mockReq: MockRequest = {
209
+ header: () => 'multipart/form-data',
210
+ };
211
+
212
+ const result = await storage.handleFile(
213
+ file,
214
+ asHonoRequest(mockReq),
215
+ 'file',
216
+ );
217
+
218
+ // Release should not throw
219
+ await storage.removeFile(result);
220
+
221
+ // Verify capacitor is released (no throw = success)
222
+ expect(true).toBe(true);
223
+ });
224
+ });
225
+
226
+ describe('processRequest', () => {
227
+ test('should process single file upload', async () => {
228
+ const file = new File(['test content'], 'test.txt', {
229
+ type: 'text/plain',
230
+ });
231
+
232
+ const mockCtx: MockContext = {
233
+ req: {
234
+ parseBody: async () => ({
235
+ operations: JSON.stringify({
236
+ query: 'mutation ($file: Upload!) { uploadFile(file: $file) }',
237
+ variables: { file: null },
238
+ }),
239
+ map: JSON.stringify({ '0': ['variables.file'] }),
240
+ '0': file,
241
+ }),
242
+ header: () => 'multipart/form-data',
243
+ },
244
+ };
245
+
246
+ const result = await processRequest(asContext(mockCtx));
247
+
248
+ expect((result as { variables: unknown }).variables).toBeDefined();
249
+ expect(
250
+ (result as { variables: { file: unknown } }).variables.file,
251
+ ).toBeInstanceOf(Upload);
252
+ });
253
+
254
+ test('should process multiple files upload', async () => {
255
+ const file1 = new File(['content1'], 'file1.txt', {
256
+ type: 'text/plain',
257
+ });
258
+ const file2 = new File(['content2'], 'file2.txt', {
259
+ type: 'text/plain',
260
+ });
261
+
262
+ const mockCtx: MockContext = {
263
+ req: {
264
+ parseBody: async () => ({
265
+ operations: JSON.stringify({
266
+ query:
267
+ 'mutation ($files: [Upload!]!) { uploadFiles(files: $files) }',
268
+ variables: { files: [null, null] },
269
+ }),
270
+ map: JSON.stringify({
271
+ '0': ['variables.files.0'],
272
+ '1': ['variables.files.1'],
273
+ }),
274
+ '0': file1,
275
+ '1': file2,
276
+ }),
277
+ header: () => 'multipart/form-data',
278
+ },
279
+ };
280
+
281
+ const result = await processRequest(asContext(mockCtx));
282
+
283
+ expect((result as { variables: unknown }).variables).toBeDefined();
284
+ expect(
285
+ (result as { variables: { files: unknown[] } }).variables.files,
286
+ ).toHaveLength(2);
287
+ expect(
288
+ (result as { variables: { files: unknown[] } }).variables.files[0],
289
+ ).toBeInstanceOf(Upload);
290
+ expect(
291
+ (result as { variables: { files: unknown[] } }).variables.files[1],
292
+ ).toBeInstanceOf(Upload);
293
+
294
+ // Verify fieldname is extracted correctly from array paths
295
+ const upload1 = (result as { variables: { files: Upload[] } }).variables
296
+ .files[0] as Upload;
297
+ const upload2 = (result as { variables: { files: Upload[] } }).variables
298
+ .files[1] as Upload;
299
+ const uploadedFile1 = await upload1.promise;
300
+ const uploadedFile2 = await upload2.promise;
301
+
302
+ expect(uploadedFile1.fieldName).toBe('files');
303
+ expect(uploadedFile1.fieldName).toBe('files');
304
+ expect(uploadedFile2.fieldName).toBe('files');
305
+ expect(uploadedFile2.fieldName).toBe('files');
306
+ });
307
+
308
+ test('should use CapacitorStorage by default', async () => {
309
+ const file = new File(['test'], 'test.txt', { type: 'text/plain' });
310
+
311
+ const mockCtx: MockContext = {
312
+ req: {
313
+ parseBody: async () => ({
314
+ operations: JSON.stringify({
315
+ variables: { file: null },
316
+ }),
317
+ map: JSON.stringify({ '0': ['variables.file'] }),
318
+ '0': file,
319
+ }),
320
+ header: () => 'multipart/form-data',
321
+ },
322
+ };
323
+
324
+ const result = await processRequest(asContext(mockCtx));
325
+ const upload = (result as { variables: { file: Upload } }).variables
326
+ .file as Upload;
327
+ const uploadedFile = await upload.promise;
328
+
329
+ // Default storage should be CapacitorStorage
330
+ expect((uploadedFile as CapacitorStorageFile).capacitor).toBeDefined();
331
+ expect(uploadedFile.createReadStream).toBeInstanceOf(Function);
332
+ // fieldName should be extracted from GraphQL path, not from multipart index
333
+ expect(uploadedFile.fieldName).toBe('file');
334
+ expect(uploadedFile.fieldName).toBe('file');
335
+ });
336
+
337
+ test('should use CapacitorStorage when tmpDir is specified', async () => {
338
+ const file = new File(['test'], 'test.txt', { type: 'text/plain' });
339
+
340
+ const mockCtx: MockContext = {
341
+ req: {
342
+ parseBody: async () => ({
343
+ operations: JSON.stringify({
344
+ variables: { file: null },
345
+ }),
346
+ map: JSON.stringify({ '0': ['variables.file'] }),
347
+ '0': file,
348
+ }),
349
+ header: () => 'multipart/form-data',
350
+ },
351
+ };
352
+
353
+ const options: ProcessRequestOptions = {
354
+ tmpDir: '/tmp',
355
+ };
356
+
357
+ const result = await processRequest(asContext(mockCtx), options);
358
+ const upload = (result as { variables: { file: Upload } }).variables
359
+ .file as Upload;
360
+ const uploadedFile = await upload.promise;
361
+
362
+ expect((uploadedFile as CapacitorStorageFile).capacitor).toBeDefined();
363
+ });
364
+
365
+ test('should use MemoryStorage when explicitly provided', async () => {
366
+ const file = new File(['test'], 'test.txt', { type: 'text/plain' });
367
+
368
+ const mockCtx: MockContext = {
369
+ req: {
370
+ parseBody: async () => ({
371
+ operations: JSON.stringify({
372
+ variables: { file: null },
373
+ }),
374
+ map: JSON.stringify({ '0': ['variables.file'] }),
375
+ '0': file,
376
+ }),
377
+ header: () => 'multipart/form-data',
378
+ },
379
+ };
380
+
381
+ const options: ProcessRequestOptions = {
382
+ storage: new MemoryStorage(),
383
+ };
384
+
385
+ const result = await processRequest(asContext(mockCtx), options);
386
+ const upload = (result as { variables: { file: Upload } }).variables
387
+ .file as Upload;
388
+ const uploadedFile = await upload.promise;
389
+
390
+ // MemoryStorage should have buffer
391
+ expect((uploadedFile as MemoryStorageFile).buffer).toBeInstanceOf(Buffer);
392
+ });
393
+
394
+ test('should use custom storage when provided', async () => {
395
+ const customStorage: Storage<MemoryUploadFile> = {
396
+ async handleFile(file, _req, fieldName): Promise<MemoryUploadFile> {
397
+ return {
398
+ fieldName,
399
+ originalFilename: file.name,
400
+ mimetype: file.type,
401
+ encoding: '7bit',
402
+ size: file.size,
403
+ uploadedAt: new Date().toISOString(),
404
+ };
405
+ },
406
+ async removeFile() {
407
+ // Custom cleanup
408
+ },
409
+ };
410
+
411
+ const file = new File(['test'], 'test.txt', { type: 'text/plain' });
412
+
413
+ const mockCtx: MockContext = {
414
+ req: {
415
+ parseBody: async () => ({
416
+ operations: JSON.stringify({
417
+ variables: { file: null },
418
+ }),
419
+ map: JSON.stringify({ '0': ['variables.file'] }),
420
+ '0': file,
421
+ }),
422
+ header: () => 'multipart/form-data',
423
+ },
424
+ };
425
+
426
+ const options: ProcessRequestOptions = {
427
+ storage: customStorage,
428
+ };
429
+
430
+ const result = await processRequest(asContext(mockCtx), options);
431
+ const upload = (result as { variables: { file: Upload } }).variables
432
+ .file as Upload;
433
+ const uploadedFile = await upload.promise;
434
+
435
+ expect(uploadedFile.originalFilename).toBe('test.txt');
436
+ });
437
+
438
+ test('should enforce maxFileSize when specified', async () => {
439
+ const file = new File(['this content is too large'], 'test.txt', {
440
+ type: 'text/plain',
441
+ });
442
+
443
+ const mockCtx: MockContext = {
444
+ req: {
445
+ parseBody: async () => ({
446
+ operations: JSON.stringify({
447
+ variables: { file: null },
448
+ }),
449
+ map: JSON.stringify({ '0': ['variables.file'] }),
450
+ '0': file,
451
+ }),
452
+ header: () => 'multipart/form-data',
453
+ },
454
+ };
455
+
456
+ const options: ProcessRequestOptions = {
457
+ maxFileSize: 10,
458
+ };
459
+
460
+ const result = await processRequest(asContext(mockCtx), options);
461
+ const upload = (result as { variables: { file: Upload } }).variables
462
+ .file as Upload;
463
+
464
+ let errorThrown = false;
465
+ try {
466
+ await upload.promise;
467
+ } catch (err) {
468
+ errorThrown = true;
469
+ expect(err instanceof Error && err.message).toContain(
470
+ 'exceeds maximum size',
471
+ );
472
+ }
473
+
474
+ expect(errorThrown).toBe(true);
475
+ });
476
+
477
+ test('should handle complex nested paths', async () => {
478
+ const file = new File(['avatar'], 'avatar.jpg', {
479
+ type: 'image/jpeg',
480
+ });
481
+
482
+ const mockCtx: MockContext = {
483
+ req: {
484
+ parseBody: async () => ({
485
+ operations: JSON.stringify({
486
+ variables: {
487
+ user: {
488
+ profile: {
489
+ avatar: null,
490
+ },
491
+ },
492
+ },
493
+ }),
494
+ map: JSON.stringify({ '0': ['variables.user.profile.avatar'] }),
495
+ '0': file,
496
+ }),
497
+ header: () => 'multipart/form-data',
498
+ },
499
+ };
500
+
501
+ const result = await processRequest(asContext(mockCtx));
502
+
503
+ expect(
504
+ (result as { variables: { user: { profile: { avatar: Upload } } } })
505
+ .variables.user.profile.avatar,
506
+ ).toBeInstanceOf(Upload);
507
+ });
508
+ });
509
+ });
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Test helper types and utilities
3
+ */
4
+
5
+ import { HonoRequest } from 'hono';
6
+ import { Context } from 'hono';
7
+
8
+ /**
9
+ * Mock request for testing
10
+ */
11
+ export type MockRequest = {
12
+ header: (name: string) => string | undefined;
13
+ };
14
+
15
+ /**
16
+ * Mock context for testing
17
+ */
18
+ export type MockContext = {
19
+ req: {
20
+ parseBody: () => Promise<Record<string, unknown>>;
21
+ header: (name: string) => string | undefined;
22
+ };
23
+ };
24
+
25
+ /**
26
+ * Create a mock Hono request
27
+ */
28
+ export function createMockRequest(
29
+ overrides?: Partial<MockRequest>,
30
+ ): MockRequest {
31
+ return {
32
+ header: (name: string) => {
33
+ if (name === 'content-type') return 'multipart/form-data';
34
+ return undefined;
35
+ },
36
+ ...overrides,
37
+ };
38
+ }
39
+
40
+ /**
41
+ * Create a mock Hono context
42
+ */
43
+ export function createMockContext(
44
+ overrides?: Partial<MockContext>,
45
+ ): MockContext {
46
+ return {
47
+ req: {
48
+ parseBody: async () => ({}),
49
+ header: (name: string) => {
50
+ if (name === 'content-type') return 'multipart/form-data';
51
+ return undefined;
52
+ },
53
+ },
54
+ ...overrides,
55
+ };
56
+ }
57
+
58
+ /**
59
+ * Cast mock request to HonoRequest
60
+ */
61
+ export function asHonoRequest(mock: MockRequest): HonoRequest {
62
+ return mock as unknown as HonoRequest;
63
+ }
64
+
65
+ /**
66
+ * Cast mock context to Context
67
+ */
68
+ export function asContext(mock: MockContext): Context {
69
+ return mock as unknown as Context;
70
+ }