@juzi/file-box 1.7.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 (210) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +613 -0
  3. package/dist/cjs/package.json +3 -0
  4. package/dist/cjs/src/config.d.ts +4 -0
  5. package/dist/cjs/src/config.d.ts.map +1 -0
  6. package/dist/cjs/src/config.js +9 -0
  7. package/dist/cjs/src/config.js.map +1 -0
  8. package/dist/cjs/src/file-box.d.ts +209 -0
  9. package/dist/cjs/src/file-box.d.ts.map +1 -0
  10. package/dist/cjs/src/file-box.js +804 -0
  11. package/dist/cjs/src/file-box.js.map +1 -0
  12. package/dist/cjs/src/file-box.spec.d.ts +14 -0
  13. package/dist/cjs/src/file-box.spec.d.ts.map +1 -0
  14. package/dist/cjs/src/file-box.spec.js +393 -0
  15. package/dist/cjs/src/file-box.spec.js.map +1 -0
  16. package/dist/cjs/src/file-box.type.d.ts +103 -0
  17. package/dist/cjs/src/file-box.type.d.ts.map +1 -0
  18. package/dist/cjs/src/file-box.type.js +34 -0
  19. package/dist/cjs/src/file-box.type.js.map +1 -0
  20. package/dist/cjs/src/interface.d.ts +25 -0
  21. package/dist/cjs/src/interface.d.ts.map +1 -0
  22. package/dist/cjs/src/interface.js +3 -0
  23. package/dist/cjs/src/interface.js.map +1 -0
  24. package/dist/cjs/src/interface.spec.d.ts +3 -0
  25. package/dist/cjs/src/interface.spec.d.ts.map +1 -0
  26. package/dist/cjs/src/interface.spec.js +11 -0
  27. package/dist/cjs/src/interface.spec.js.map +1 -0
  28. package/dist/cjs/src/misc.d.ts +17 -0
  29. package/dist/cjs/src/misc.d.ts.map +1 -0
  30. package/dist/cjs/src/misc.js +137 -0
  31. package/dist/cjs/src/misc.js.map +1 -0
  32. package/dist/cjs/src/misc.spec.d.ts +3 -0
  33. package/dist/cjs/src/misc.spec.d.ts.map +1 -0
  34. package/dist/cjs/src/misc.spec.js +51 -0
  35. package/dist/cjs/src/misc.spec.js.map +1 -0
  36. package/dist/cjs/src/mod.d.ts +9 -0
  37. package/dist/cjs/src/mod.d.ts.map +1 -0
  38. package/dist/cjs/src/mod.js +12 -0
  39. package/dist/cjs/src/mod.js.map +1 -0
  40. package/dist/cjs/src/pure-functions/sized-chunk-transformer.d.ts +13 -0
  41. package/dist/cjs/src/pure-functions/sized-chunk-transformer.d.ts.map +1 -0
  42. package/dist/cjs/src/pure-functions/sized-chunk-transformer.js +45 -0
  43. package/dist/cjs/src/pure-functions/sized-chunk-transformer.js.map +1 -0
  44. package/dist/cjs/src/pure-functions/sized-chunk-transformer.spec.d.ts +3 -0
  45. package/dist/cjs/src/pure-functions/sized-chunk-transformer.spec.d.ts.map +1 -0
  46. package/dist/cjs/src/pure-functions/sized-chunk-transformer.spec.js +39 -0
  47. package/dist/cjs/src/pure-functions/sized-chunk-transformer.spec.js.map +1 -0
  48. package/dist/cjs/src/qrcode.d.ts +6 -0
  49. package/dist/cjs/src/qrcode.d.ts.map +1 -0
  50. package/dist/cjs/src/qrcode.js +35 -0
  51. package/dist/cjs/src/qrcode.js.map +1 -0
  52. package/dist/cjs/src/qrcode.spec.d.ts +3 -0
  53. package/dist/cjs/src/qrcode.spec.d.ts.map +1 -0
  54. package/dist/cjs/src/qrcode.spec.js +47 -0
  55. package/dist/cjs/src/qrcode.spec.js.map +1 -0
  56. package/dist/cjs/src/urn-registry/mod.d.ts +3 -0
  57. package/dist/cjs/src/urn-registry/mod.d.ts.map +1 -0
  58. package/dist/cjs/src/urn-registry/mod.js +6 -0
  59. package/dist/cjs/src/urn-registry/mod.js.map +1 -0
  60. package/dist/cjs/src/urn-registry/random-uuid.d.ts +4 -0
  61. package/dist/cjs/src/urn-registry/random-uuid.d.ts.map +1 -0
  62. package/dist/cjs/src/urn-registry/random-uuid.js +30 -0
  63. package/dist/cjs/src/urn-registry/random-uuid.js.map +1 -0
  64. package/dist/cjs/src/urn-registry/uniform-resource-name-registry.d.ts +76 -0
  65. package/dist/cjs/src/urn-registry/uniform-resource-name-registry.d.ts.map +1 -0
  66. package/dist/cjs/src/urn-registry/uniform-resource-name-registry.js +259 -0
  67. package/dist/cjs/src/urn-registry/uniform-resource-name-registry.js.map +1 -0
  68. package/dist/cjs/src/urn-registry/uniform-resource-name-registry.spec.d.ts +3 -0
  69. package/dist/cjs/src/urn-registry/uniform-resource-name-registry.spec.d.ts.map +1 -0
  70. package/dist/cjs/src/urn-registry/uniform-resource-name-registry.spec.js +81 -0
  71. package/dist/cjs/src/urn-registry/uniform-resource-name-registry.spec.js.map +1 -0
  72. package/dist/cjs/src/urn-registry/uuid-to-big-int.d.ts +8 -0
  73. package/dist/cjs/src/urn-registry/uuid-to-big-int.d.ts.map +1 -0
  74. package/dist/cjs/src/urn-registry/uuid-to-big-int.js +16 -0
  75. package/dist/cjs/src/urn-registry/uuid-to-big-int.js.map +1 -0
  76. package/dist/cjs/src/urn-registry/uuid-to-big-int.spec.d.ts +3 -0
  77. package/dist/cjs/src/urn-registry/uuid-to-big-int.spec.d.ts.map +1 -0
  78. package/dist/cjs/src/urn-registry/uuid-to-big-int.spec.js +17 -0
  79. package/dist/cjs/src/urn-registry/uuid-to-big-int.spec.js.map +1 -0
  80. package/dist/cjs/src/version.d.ts +5 -0
  81. package/dist/cjs/src/version.d.ts.map +1 -0
  82. package/dist/cjs/src/version.js +8 -0
  83. package/dist/cjs/src/version.js.map +1 -0
  84. package/dist/cjs/src/version.spec.d.ts +3 -0
  85. package/dist/cjs/src/version.spec.d.ts.map +1 -0
  86. package/dist/cjs/src/version.spec.js +9 -0
  87. package/dist/cjs/src/version.spec.js.map +1 -0
  88. package/dist/cjs/tests/integration.spec.d.ts +3 -0
  89. package/dist/cjs/tests/integration.spec.d.ts.map +1 -0
  90. package/dist/cjs/tests/integration.spec.js +8 -0
  91. package/dist/cjs/tests/integration.spec.js.map +1 -0
  92. package/dist/cjs/tests/network-timeout.spec.d.ts +3 -0
  93. package/dist/cjs/tests/network-timeout.spec.d.ts.map +1 -0
  94. package/dist/cjs/tests/network-timeout.spec.js +121 -0
  95. package/dist/cjs/tests/network-timeout.spec.js.map +1 -0
  96. package/dist/esm/src/config.d.ts +4 -0
  97. package/dist/esm/src/config.d.ts.map +1 -0
  98. package/dist/esm/src/config.js +5 -0
  99. package/dist/esm/src/config.js.map +1 -0
  100. package/dist/esm/src/file-box.d.ts +209 -0
  101. package/dist/esm/src/file-box.d.ts.map +1 -0
  102. package/dist/esm/src/file-box.js +775 -0
  103. package/dist/esm/src/file-box.js.map +1 -0
  104. package/dist/esm/src/file-box.spec.d.ts +14 -0
  105. package/dist/esm/src/file-box.spec.d.ts.map +1 -0
  106. package/dist/esm/src/file-box.spec.js +386 -0
  107. package/dist/esm/src/file-box.spec.js.map +1 -0
  108. package/dist/esm/src/file-box.type.d.ts +103 -0
  109. package/dist/esm/src/file-box.type.d.ts.map +1 -0
  110. package/dist/esm/src/file-box.type.js +31 -0
  111. package/dist/esm/src/file-box.type.js.map +1 -0
  112. package/dist/esm/src/interface.d.ts +25 -0
  113. package/dist/esm/src/interface.d.ts.map +1 -0
  114. package/dist/esm/src/interface.js +2 -0
  115. package/dist/esm/src/interface.js.map +1 -0
  116. package/dist/esm/src/interface.spec.d.ts +3 -0
  117. package/dist/esm/src/interface.spec.d.ts.map +1 -0
  118. package/dist/esm/src/interface.spec.js +9 -0
  119. package/dist/esm/src/interface.spec.js.map +1 -0
  120. package/dist/esm/src/misc.d.ts +17 -0
  121. package/dist/esm/src/misc.d.ts.map +1 -0
  122. package/dist/esm/src/misc.js +126 -0
  123. package/dist/esm/src/misc.js.map +1 -0
  124. package/dist/esm/src/misc.spec.d.ts +3 -0
  125. package/dist/esm/src/misc.spec.d.ts.map +1 -0
  126. package/dist/esm/src/misc.spec.js +49 -0
  127. package/dist/esm/src/misc.spec.js.map +1 -0
  128. package/dist/esm/src/mod.d.ts +9 -0
  129. package/dist/esm/src/mod.d.ts.map +1 -0
  130. package/dist/esm/src/mod.js +6 -0
  131. package/dist/esm/src/mod.js.map +1 -0
  132. package/dist/esm/src/pure-functions/sized-chunk-transformer.d.ts +13 -0
  133. package/dist/esm/src/pure-functions/sized-chunk-transformer.d.ts.map +1 -0
  134. package/dist/esm/src/pure-functions/sized-chunk-transformer.js +39 -0
  135. package/dist/esm/src/pure-functions/sized-chunk-transformer.js.map +1 -0
  136. package/dist/esm/src/pure-functions/sized-chunk-transformer.spec.d.ts +3 -0
  137. package/dist/esm/src/pure-functions/sized-chunk-transformer.spec.d.ts.map +1 -0
  138. package/dist/esm/src/pure-functions/sized-chunk-transformer.spec.js +37 -0
  139. package/dist/esm/src/pure-functions/sized-chunk-transformer.spec.js.map +1 -0
  140. package/dist/esm/src/qrcode.d.ts +6 -0
  141. package/dist/esm/src/qrcode.d.ts.map +1 -0
  142. package/dist/esm/src/qrcode.js +27 -0
  143. package/dist/esm/src/qrcode.js.map +1 -0
  144. package/dist/esm/src/qrcode.spec.d.ts +3 -0
  145. package/dist/esm/src/qrcode.spec.d.ts.map +1 -0
  146. package/dist/esm/src/qrcode.spec.js +45 -0
  147. package/dist/esm/src/qrcode.spec.js.map +1 -0
  148. package/dist/esm/src/urn-registry/mod.d.ts +3 -0
  149. package/dist/esm/src/urn-registry/mod.d.ts.map +1 -0
  150. package/dist/esm/src/urn-registry/mod.js +3 -0
  151. package/dist/esm/src/urn-registry/mod.js.map +1 -0
  152. package/dist/esm/src/urn-registry/random-uuid.d.ts +4 -0
  153. package/dist/esm/src/urn-registry/random-uuid.d.ts.map +1 -0
  154. package/dist/esm/src/urn-registry/random-uuid.js +4 -0
  155. package/dist/esm/src/urn-registry/random-uuid.js.map +1 -0
  156. package/dist/esm/src/urn-registry/uniform-resource-name-registry.d.ts +76 -0
  157. package/dist/esm/src/urn-registry/uniform-resource-name-registry.d.ts.map +1 -0
  158. package/dist/esm/src/urn-registry/uniform-resource-name-registry.js +253 -0
  159. package/dist/esm/src/urn-registry/uniform-resource-name-registry.js.map +1 -0
  160. package/dist/esm/src/urn-registry/uniform-resource-name-registry.spec.d.ts +3 -0
  161. package/dist/esm/src/urn-registry/uniform-resource-name-registry.spec.d.ts.map +1 -0
  162. package/dist/esm/src/urn-registry/uniform-resource-name-registry.spec.js +79 -0
  163. package/dist/esm/src/urn-registry/uniform-resource-name-registry.spec.js.map +1 -0
  164. package/dist/esm/src/urn-registry/uuid-to-big-int.d.ts +8 -0
  165. package/dist/esm/src/urn-registry/uuid-to-big-int.d.ts.map +1 -0
  166. package/dist/esm/src/urn-registry/uuid-to-big-int.js +13 -0
  167. package/dist/esm/src/urn-registry/uuid-to-big-int.js.map +1 -0
  168. package/dist/esm/src/urn-registry/uuid-to-big-int.spec.d.ts +3 -0
  169. package/dist/esm/src/urn-registry/uuid-to-big-int.spec.d.ts.map +1 -0
  170. package/dist/esm/src/urn-registry/uuid-to-big-int.spec.js +15 -0
  171. package/dist/esm/src/urn-registry/uuid-to-big-int.spec.js.map +1 -0
  172. package/dist/esm/src/version.d.ts +5 -0
  173. package/dist/esm/src/version.d.ts.map +1 -0
  174. package/dist/esm/src/version.js +5 -0
  175. package/dist/esm/src/version.js.map +1 -0
  176. package/dist/esm/src/version.spec.d.ts +3 -0
  177. package/dist/esm/src/version.spec.d.ts.map +1 -0
  178. package/dist/esm/src/version.spec.js +7 -0
  179. package/dist/esm/src/version.spec.js.map +1 -0
  180. package/dist/esm/tests/integration.spec.d.ts +3 -0
  181. package/dist/esm/tests/integration.spec.d.ts.map +1 -0
  182. package/dist/esm/tests/integration.spec.js +6 -0
  183. package/dist/esm/tests/integration.spec.js.map +1 -0
  184. package/dist/esm/tests/network-timeout.spec.d.ts +3 -0
  185. package/dist/esm/tests/network-timeout.spec.d.ts.map +1 -0
  186. package/dist/esm/tests/network-timeout.spec.js +119 -0
  187. package/dist/esm/tests/network-timeout.spec.js.map +1 -0
  188. package/package.json +85 -0
  189. package/src/config.ts +5 -0
  190. package/src/file-box.spec.ts +480 -0
  191. package/src/file-box.ts +1042 -0
  192. package/src/file-box.type.ts +145 -0
  193. package/src/interface.spec.ts +17 -0
  194. package/src/interface.ts +47 -0
  195. package/src/misc.spec.ts +70 -0
  196. package/src/misc.ts +157 -0
  197. package/src/mod.ts +33 -0
  198. package/src/pure-functions/sized-chunk-transformer.spec.ts +52 -0
  199. package/src/pure-functions/sized-chunk-transformer.ts +49 -0
  200. package/src/qrcode.spec.ts +55 -0
  201. package/src/qrcode.ts +38 -0
  202. package/src/typings.d.ts +1 -0
  203. package/src/urn-registry/mod.ts +7 -0
  204. package/src/urn-registry/random-uuid.ts +7 -0
  205. package/src/urn-registry/uniform-resource-name-registry.spec.ts +109 -0
  206. package/src/urn-registry/uniform-resource-name-registry.ts +342 -0
  207. package/src/urn-registry/uuid-to-big-int.spec.ts +19 -0
  208. package/src/urn-registry/uuid-to-big-int.ts +16 -0
  209. package/src/version.spec.ts +9 -0
  210. package/src/version.ts +4 -0
@@ -0,0 +1,1042 @@
1
+ /**
2
+ * File Box
3
+ * https://github.com/huan/file-box
4
+ *
5
+ * 2018 Huan LI <zixia@zixia.net>
6
+ */
7
+ /* eslint no-use-before-define: off */
8
+
9
+ import * as FS from 'fs'
10
+ import type * as HTTP from 'http'
11
+ import * as PATH from 'path'
12
+ import * as URL from 'url'
13
+
14
+ import mime from 'mime'
15
+
16
+ import {
17
+ PassThrough,
18
+ Readable,
19
+ Writable,
20
+ } from 'stream'
21
+ import {
22
+ instanceToClass,
23
+ looseInstanceOfClass,
24
+ interfaceOfClass,
25
+ } from 'clone-class'
26
+
27
+ import {
28
+ VERSION,
29
+ } from './config.js'
30
+ import {
31
+ FileBoxJsonObject,
32
+ FileBoxOptions,
33
+ FileBoxOptionsBase64,
34
+ FileBoxOptionsCommon,
35
+ FileBoxOptionsQRCode,
36
+ FileBoxOptionsUrl,
37
+ FileBoxOptionsUuid,
38
+ FileBoxType,
39
+ Metadata,
40
+ Pipeable,
41
+ UuidLoader,
42
+ UuidSaver,
43
+ } from './file-box.type.js'
44
+ import {
45
+ dataUrlToBase64,
46
+ httpHeaderToFileName,
47
+ httpHeadHeader,
48
+ httpStream,
49
+ streamToBuffer,
50
+ } from './misc.js'
51
+ import {
52
+ bufferToQrValue,
53
+ qrValueToStream,
54
+ } from './qrcode.js'
55
+ import {
56
+ sizedChunkTransformer,
57
+ } from './pure-functions/sized-chunk-transformer.js'
58
+ import type {
59
+ FileBoxInterface,
60
+ } from './interface.js'
61
+
62
+ const EMPTY_META_DATA = Object.freeze({})
63
+ const UNKNOWN_SIZE = -1
64
+
65
+ let interfaceOfFileBox = (_: any): _ is FileBoxInterface => false
66
+ let looseInstanceOfFileBox = (_: any): _ is FileBox => false
67
+
68
+ class FileBox implements Pipeable, FileBoxInterface {
69
+
70
+ /**
71
+ *
72
+ * Static Properties
73
+ *
74
+ */
75
+ static readonly version = VERSION
76
+
77
+ /**
78
+ * Symbol.hasInstance: instanceof
79
+ *
80
+ * @link https://www.keithcirkel.co.uk/metaprogramming-in-es6-symbols/
81
+ */
82
+ static [Symbol.hasInstance] (lho: any): lho is FileBoxInterface {
83
+ return this.validInterface(lho)
84
+ }
85
+
86
+ /**
87
+ * Check if obj satisfy FileBox interface
88
+ */
89
+ static valid (target: any): target is FileBoxInterface {
90
+ return this.validInstance(target) || this.validInterface(target)
91
+ }
92
+
93
+ /**
94
+ * Check if obj satisfy FileBox interface
95
+ */
96
+ static validInterface (target: any): target is FileBoxInterface {
97
+ return interfaceOfFileBox(target)
98
+ }
99
+
100
+ /**
101
+ * loose check instance of FileBox
102
+ */
103
+ static validInstance (target: any): target is FileBox {
104
+ return looseInstanceOfFileBox(target)
105
+ }
106
+
107
+ static fromUrl (
108
+ url : string,
109
+ options? : {
110
+ headers? : HTTP.OutgoingHttpHeaders,
111
+ name? : string,
112
+ size? : number,
113
+ md5? : string,
114
+ },
115
+ ): FileBox
116
+
117
+ /**
118
+ * @deprecated use `fromUrl(url, options)` instead
119
+ */
120
+ static fromUrl (
121
+ url : string,
122
+ name? : string,
123
+ headers? : HTTP.OutgoingHttpHeaders,
124
+ ): FileBox
125
+
126
+ /**
127
+ * fromUrl()
128
+ */
129
+ static fromUrl (
130
+ url : string,
131
+ nameOrOptions? : string | {
132
+ headers? : HTTP.OutgoingHttpHeaders,
133
+ name? : string,
134
+ size? : number,
135
+ md5? : string,
136
+ },
137
+ headers? : HTTP.OutgoingHttpHeaders,
138
+ ): FileBox {
139
+ let name: undefined | string
140
+ let size: undefined | number
141
+ let md5: undefined | string
142
+
143
+ if (typeof nameOrOptions === 'object') {
144
+ headers = nameOrOptions.headers
145
+ name = nameOrOptions.name
146
+ size = nameOrOptions.size
147
+ md5 = nameOrOptions.md5
148
+ } else {
149
+ name = nameOrOptions
150
+ }
151
+
152
+ if (!name) {
153
+ const parsedUrl = new URL.URL(url)
154
+ name = parsedUrl.pathname
155
+ }
156
+ const options: FileBoxOptions = {
157
+ headers,
158
+ md5,
159
+ name,
160
+ size,
161
+ type : FileBoxType.Url,
162
+ url,
163
+ }
164
+ return new this(options)
165
+ }
166
+
167
+ /**
168
+ * Alias for `FileBox.fromFile()`
169
+ *
170
+ * @alias fromFile
171
+ */
172
+
173
+ static fromFile (
174
+ path: string,
175
+ name?: string,
176
+ md5?: string,
177
+ ): FileBox {
178
+ if (!name) {
179
+ name = PATH.parse(path).base
180
+ }
181
+ const options: FileBoxOptions = {
182
+ md5,
183
+ name,
184
+ path,
185
+ type : FileBoxType.File,
186
+ }
187
+
188
+ return new this(options)
189
+ }
190
+
191
+ /**
192
+ * TODO: add `FileBoxStreamOptions` with `size` support (@huan, 202111)
193
+ */
194
+ static fromStream (
195
+ stream: Readable,
196
+ name?: string,
197
+ md5?: string,
198
+ ): FileBox {
199
+ const options: FileBoxOptions = {
200
+ md5,
201
+ name: name || 'stream.dat',
202
+ stream,
203
+ type: FileBoxType.Stream,
204
+ }
205
+ return new this(options)
206
+ }
207
+
208
+ static fromBuffer (
209
+ buffer: Buffer,
210
+ name?: string,
211
+ md5?: string,
212
+ ): FileBox {
213
+ const options: FileBoxOptions = {
214
+ buffer,
215
+ md5,
216
+ name: name || 'buffer.dat',
217
+ type : FileBoxType.Buffer,
218
+ }
219
+ return new this(options)
220
+ }
221
+
222
+ /**
223
+ * @param base64
224
+ * @param name the file name of the base64 data
225
+ */
226
+ static fromBase64 (
227
+ base64: string,
228
+ name?: string,
229
+ md5?: string,
230
+ ): FileBox {
231
+ const options: FileBoxOptions = {
232
+ base64,
233
+ md5,
234
+ name: name || 'base64.dat',
235
+ type : FileBoxType.Base64,
236
+ }
237
+ return new this(options)
238
+ }
239
+
240
+ /**
241
+ * dataURL: `data:image/png;base64,${base64Text}`,
242
+ */
243
+ static fromDataURL (
244
+ dataUrl : string,
245
+ name? : string,
246
+ md5? : string,
247
+ ): FileBox {
248
+ return this.fromBase64(
249
+ dataUrlToBase64(dataUrl),
250
+ name || 'data-url.dat',
251
+ md5,
252
+ )
253
+ }
254
+
255
+ /**
256
+ *
257
+ * @param qrCode the value of the QR Code. For example: `https://github.com`
258
+ */
259
+ static fromQRCode (
260
+ qrCode: string,
261
+ md5?: string,
262
+ ): FileBox {
263
+ const options: FileBoxOptions = {
264
+ md5,
265
+ name: 'qrcode.png',
266
+ qrCode,
267
+ type: FileBoxType.QRCode,
268
+ }
269
+ return new this(options)
270
+ }
271
+
272
+ protected static uuidToStream?: UuidLoader
273
+ protected static uuidFromStream?: UuidSaver
274
+
275
+ static fromUuid (
276
+ uuid: string,
277
+ options?: {
278
+ name?: string,
279
+ size?: number,
280
+ md5? : string,
281
+ },
282
+ ): FileBox
283
+
284
+ /**
285
+ * @deprecated use `fromUuid(name, options)` instead
286
+ */
287
+ static fromUuid (
288
+ uuid: string,
289
+ name?: string,
290
+ md5?: string,
291
+ ): FileBox
292
+
293
+ /**
294
+ * @param uuid the UUID of the file. For example: `6f88b03c-1237-4f46-8db2-98ef23200551`
295
+ * @param name the name of the file. For example: `video.mp4`
296
+ */
297
+ static fromUuid (
298
+ uuid: string,
299
+ nameOrOptions?: string | {
300
+ name?: string,
301
+ size?: number,
302
+ md5? : string
303
+ },
304
+ ): FileBox {
305
+ let name: undefined | string
306
+ let size: undefined | number
307
+ let md5 : undefined | string
308
+
309
+ if (typeof nameOrOptions === 'object') {
310
+ name = nameOrOptions.name
311
+ size = nameOrOptions.size
312
+ md5 = nameOrOptions.md5
313
+ } else {
314
+ name = nameOrOptions
315
+ }
316
+
317
+ const options: FileBoxOptions = {
318
+ md5,
319
+ name: name || `${uuid}.dat`,
320
+ size,
321
+ type: FileBoxType.Uuid,
322
+ uuid,
323
+ }
324
+ return new this(options)
325
+ }
326
+
327
+ /**
328
+ * UUID Type FielBox Loader
329
+ */
330
+ static setUuidLoader (
331
+ loader: UuidLoader,
332
+ ): void {
333
+ if (Object.prototype.hasOwnProperty.call(this, 'uuidToStream')) {
334
+ throw new Error('this FileBox has been set resolver before, can not set twice')
335
+ }
336
+ this.uuidToStream = loader
337
+ }
338
+
339
+ /**
340
+ * UUID Type FielBox Saver
341
+ */
342
+ static setUuidSaver (
343
+ saver: UuidSaver,
344
+ ): void {
345
+ if (Object.prototype.hasOwnProperty.call(this, 'uuidFromStream')) {
346
+ throw new Error('this FileBox has been set register before, can not set twice')
347
+ }
348
+ this.uuidFromStream = saver
349
+ }
350
+
351
+ /**
352
+ *
353
+ * @static
354
+ * @param {(FileBoxJsonObject | string)} obj
355
+ * @returns {FileBox}
356
+ */
357
+ static fromJSON (obj: FileBoxJsonObject | string): FileBox {
358
+ if (typeof obj === 'string') {
359
+ obj = JSON.parse(obj) as FileBoxJsonObject
360
+ }
361
+
362
+ /**
363
+ * Huan(202111): compatible with old FileBox.toJSON() key: `boxType`
364
+ * this is a breaking change made by v1.0
365
+ *
366
+ * convert `obj.boxType` to `obj.type`
367
+ * (will be removed after Dec 31, 2022)
368
+ */
369
+ if (!(obj as any).type && 'boxType' in (obj as any)) {
370
+ obj.type = (obj as any)['boxType']
371
+ }
372
+
373
+ let fileBox: FileBox
374
+
375
+ switch (obj.type) {
376
+ case FileBoxType.Base64:
377
+ fileBox = this.fromBase64(
378
+ obj.base64,
379
+ obj.name,
380
+ obj.md5,
381
+ )
382
+ break
383
+
384
+ case FileBoxType.Url:
385
+ fileBox = this.fromUrl(obj.url, {
386
+ md5: obj.md5,
387
+ name: obj.name,
388
+ size: obj.size,
389
+ })
390
+ break
391
+
392
+ case FileBoxType.QRCode:
393
+ fileBox = this.fromQRCode(
394
+ obj.qrCode,
395
+ obj.md5,
396
+ )
397
+ break
398
+
399
+ case FileBoxType.Uuid:
400
+ fileBox = this.fromUuid(obj.uuid, {
401
+ md5: obj.md5,
402
+ name: obj.name,
403
+ size: obj.size,
404
+ })
405
+ break
406
+
407
+ default:
408
+ throw new Error(`unknown filebox json object{type}: ${JSON.stringify(obj)}`)
409
+ }
410
+
411
+ if (obj.metadata) {
412
+ (fileBox as FileBox).metadata = obj.metadata
413
+ }
414
+
415
+ return fileBox
416
+ }
417
+
418
+ /**
419
+ *
420
+ * Instance Properties
421
+ *
422
+ */
423
+ readonly version = VERSION
424
+
425
+ /**
426
+ * We are using a getter for `type` is because
427
+ * getter name can be enumurated by the `Object.hasOwnProperties()`*
428
+ * but property name can not.
429
+ *
430
+ * * required by `validInterface()`
431
+ */
432
+ readonly _type: FileBoxType
433
+ get type () { return this._type }
434
+
435
+ /**
436
+ * the Content-Length of the file
437
+ * `SIZE_UNKNOWN(-1)` means unknown
438
+ *
439
+ * @example
440
+ * ```ts
441
+ * const fileBox = FileBox.fromUrl('http://example.com/image.png')
442
+ * await fileBox.ready()
443
+ * console.log(fileBox.size)
444
+ * // > 102400 <- this is the size of the remote image.png
445
+ * ```
446
+ */
447
+ _size?: number
448
+ get size (): number {
449
+ if (this._size) {
450
+ return this._size
451
+ }
452
+ return UNKNOWN_SIZE
453
+ }
454
+
455
+ /**
456
+ * File MD5 Sum
457
+ */
458
+ readonly md5: undefined | string
459
+
460
+ /**
461
+ * @deprecated: use `mediaType` instead. will be removed after Dec 31, 2022
462
+ */
463
+ mimeType = 'application/unknown'
464
+
465
+ /**
466
+ * (Internet) Media Type is the proper technical term of `MIME Type`
467
+ * @see https://stackoverflow.com/a/9277778/1123955
468
+ *
469
+ * @example 'text/plain'
470
+ */
471
+ protected _mediaType?: string
472
+ get mediaType (): string {
473
+ if (this._mediaType) {
474
+ return this._mediaType
475
+ }
476
+ return 'application/unknown'
477
+ }
478
+
479
+ protected _name: string
480
+ get name (): string {
481
+ return this._name
482
+ }
483
+
484
+ protected _metadata?: Metadata
485
+ get metadata (): Metadata {
486
+ if (this._metadata) {
487
+ return this._metadata
488
+ }
489
+ return EMPTY_META_DATA
490
+ }
491
+
492
+ set metadata (data: Metadata) {
493
+ if (this._metadata) {
494
+ throw new Error('metadata can not be modified after set')
495
+ }
496
+ this._metadata = { ...data }
497
+ Object.freeze(this._metadata)
498
+ }
499
+
500
+ /**
501
+ * Lazy load data: (can be serialized to JSON)
502
+ * Do not read file to Buffer until there's a consumer.
503
+ */
504
+ private readonly base64? : string
505
+ private readonly remoteUrl? : string
506
+ private readonly qrCode? : string
507
+ private readonly uuid? : string
508
+
509
+ /**
510
+ * Can not be serialized to JSON
511
+ */
512
+ private readonly buffer? : Buffer
513
+ private readonly localPath? : string
514
+ private readonly stream? : Readable
515
+
516
+ private readonly headers?: HTTP.OutgoingHttpHeaders
517
+
518
+ constructor (
519
+ options: FileBoxOptions,
520
+ ) {
521
+ // Only keep `basename` in this.name
522
+ this._name = PATH.basename(options.name)
523
+ this._type = options.type
524
+
525
+ /**
526
+ * Unknown file type MIME: `'application/unknown'`
527
+ * @see https://stackoverflow.com/a/6080707/1123955
528
+ */
529
+ this._mediaType = mime.getType(this.name) ?? undefined
530
+ this.md5 = options.md5
531
+
532
+ switch (options.type) {
533
+ case FileBoxType.Buffer:
534
+ this.buffer = options.buffer
535
+ this._size = options.buffer.length
536
+ break
537
+
538
+ case FileBoxType.File:
539
+ if (!options.path) {
540
+ throw new Error('no path')
541
+ }
542
+ this.localPath = options.path
543
+ this._size = FS.statSync(this.localPath).size
544
+ break
545
+
546
+ case FileBoxType.Url:
547
+ if (!options.url) {
548
+ throw new Error('no url')
549
+ }
550
+ this.remoteUrl = options.url
551
+
552
+ if (options.headers) {
553
+ this.headers = options.headers
554
+ }
555
+ if (options.size) {
556
+ this._size = options.size
557
+ } else {
558
+ /**
559
+ * Add a background task to fetch remote file name & size
560
+ *
561
+ * TODO: how to improve it?
562
+ */
563
+ // this.syncUrlMetadata().catch(console.error)
564
+ }
565
+
566
+ break
567
+
568
+ case FileBoxType.Stream:
569
+ this.stream = options.stream
570
+ if (options.size) {
571
+ this._size = options.size
572
+ }
573
+ break
574
+
575
+ case FileBoxType.QRCode:
576
+ if (!options.qrCode) {
577
+ throw new Error('no QR Code')
578
+ }
579
+ this.qrCode = options.qrCode
580
+ break
581
+
582
+ case FileBoxType.Base64:
583
+ if (!options.base64) {
584
+ throw new Error('no Base64 data')
585
+ }
586
+ this.base64 = options.base64
587
+ this._size = Buffer.byteLength(options.base64, 'base64')
588
+ break
589
+
590
+ case FileBoxType.Uuid:
591
+ if (!options.uuid) {
592
+ throw new Error('no UUID data')
593
+ }
594
+ this.uuid = options.uuid
595
+ if (options.size) {
596
+ this._size = options.size
597
+ }
598
+ break
599
+
600
+ default:
601
+ throw new Error(`unknown options(type): ${JSON.stringify(options)}`)
602
+ }
603
+
604
+ }
605
+
606
+ async ready (): Promise<void> {
607
+ switch (this.type) {
608
+ case FileBoxType.Url:
609
+ await this._syncUrlMetadata()
610
+ break
611
+
612
+ case FileBoxType.QRCode:
613
+ if (this.size === UNKNOWN_SIZE) {
614
+ this._size = (await this.toBuffer()).length
615
+ }
616
+ break
617
+
618
+ default:
619
+ break
620
+ }
621
+ }
622
+
623
+ /**
624
+ * @todo use http.get/gets instead of Request
625
+ */
626
+ protected async _syncUrlMetadata (): Promise<void> {
627
+ /**
628
+ * https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Disposition
629
+ * > Content-Disposition: attachment; filename="cool.html"
630
+ */
631
+
632
+ if (this.type !== FileBoxType.Url) {
633
+ throw new Error('type is not Url')
634
+ }
635
+ if (!this.remoteUrl) {
636
+ throw new Error('no url')
637
+ }
638
+
639
+ const headers = await httpHeadHeader(this.remoteUrl)
640
+
641
+ const httpFilename = httpHeaderToFileName(headers)
642
+ if (httpFilename) {
643
+ this._name = httpFilename
644
+ }
645
+
646
+ if (!this.name) {
647
+ throw new Error('NONAME')
648
+ }
649
+
650
+ const httpMediaType = headers['content-type'] || (httpFilename && mime.getType(httpFilename))
651
+ if (httpMediaType) {
652
+ this._mediaType = httpMediaType
653
+ }
654
+
655
+ if (headers['content-length']) {
656
+ this._size = Number(headers['content-length'])
657
+ }
658
+ }
659
+
660
+ /**
661
+ *
662
+ * toXXX methods
663
+ *
664
+ */
665
+ toString () {
666
+ return [
667
+ 'FileBox#',
668
+ FileBoxType[this.type],
669
+ '<',
670
+ this.name,
671
+ '>',
672
+ ].join('')
673
+ }
674
+
675
+ toJSON (): FileBoxJsonObject {
676
+ const objCommon: FileBoxOptionsCommon = {
677
+ md5 : this.md5,
678
+ metadata : this.metadata,
679
+ name : this.name,
680
+ }
681
+
682
+ if (typeof this.size !== 'undefined') {
683
+ objCommon.size = this.size
684
+ }
685
+
686
+ let obj: FileBoxJsonObject
687
+
688
+ switch (this.type) {
689
+ case FileBoxType.Url: {
690
+ if (!this.remoteUrl) {
691
+ throw new Error('no url')
692
+ }
693
+ const objUrl: FileBoxOptionsUrl = {
694
+ headers : this.headers,
695
+ type : FileBoxType.Url,
696
+ url : this.remoteUrl,
697
+ }
698
+ obj = {
699
+ ...objCommon,
700
+ ...objUrl,
701
+ }
702
+ break
703
+ }
704
+
705
+ case FileBoxType.QRCode: {
706
+ if (!this.qrCode) {
707
+ throw new Error('no qr code')
708
+ }
709
+ const objQRCode: FileBoxOptionsQRCode = {
710
+ qrCode : this.qrCode,
711
+ type : FileBoxType.QRCode,
712
+ }
713
+ obj = {
714
+ ...objCommon,
715
+ ...objQRCode,
716
+ }
717
+ break
718
+ }
719
+
720
+ case FileBoxType.Base64: {
721
+ if (!this.base64) {
722
+ throw new Error('no base64 data')
723
+ }
724
+ const objBase64: FileBoxOptionsBase64 = {
725
+ base64 : this.base64,
726
+ type : FileBoxType.Base64,
727
+ }
728
+ obj = {
729
+ ...objCommon,
730
+ ...objBase64,
731
+ }
732
+ break
733
+ }
734
+
735
+ case FileBoxType.Uuid: {
736
+ if (!this.uuid) {
737
+ throw new Error('no uuid data')
738
+ }
739
+ const objUuid: FileBoxOptionsUuid = {
740
+ type : FileBoxType.Uuid,
741
+ uuid : this.uuid,
742
+ }
743
+ obj = {
744
+ ...objCommon,
745
+ ...objUuid,
746
+ }
747
+ break
748
+ }
749
+
750
+ default:
751
+ void this.type
752
+ throw new Error('FileBox.toJSON() can only work on limited FileBoxType(s). See: <https://github.com/huan/file-box/issues/25>')
753
+ }
754
+
755
+ /**
756
+ * Huan(202111): compatible with old FileBox.toJSON() key: `boxType`
757
+ * this is a breaking change made by v1.0
758
+ *
759
+ * save `obj.type` a copy to `obj.boxType`
760
+ * (will be removed after Dec 31, 2022)
761
+ */
762
+ (obj as any)['boxType'] = obj.type
763
+
764
+ return obj
765
+ }
766
+
767
+ async toStream (): Promise<Readable> {
768
+ let stream: Readable
769
+
770
+ switch (this.type) {
771
+ case FileBoxType.Buffer:
772
+ stream = this._transformBufferToStream()
773
+ break
774
+
775
+ case FileBoxType.File:
776
+ stream = this._transformFileToStream()
777
+ break
778
+
779
+ case FileBoxType.Url:
780
+ stream = await this._transformUrlToStream()
781
+ break
782
+
783
+ case FileBoxType.Stream:
784
+ if (!this.stream) {
785
+ throw new Error('no stream')
786
+ }
787
+
788
+ /**
789
+ * Huan(202109): the stream.destroyed will not be `true`
790
+ * when we have read all the data
791
+ * after we change some code.
792
+ * The reason is unbase64 : this.base64,
793
+ type : FileBoxType.Base64,known... so we change to check `readable`
794
+ */
795
+ if (!this.stream.readable) {
796
+ throw new Error('The stream is not readable. Maybe has already been consumed, and now it was drained. See: https://github.com/huan/file-box/issues/50')
797
+ }
798
+
799
+ stream = this.stream
800
+ break
801
+
802
+ case FileBoxType.QRCode:
803
+ if (!this.qrCode) {
804
+ throw new Error('no QR Code')
805
+ }
806
+ stream = await this._transformQRCodeToStream()
807
+ break
808
+
809
+ case FileBoxType.Base64:
810
+ if (!this.base64) {
811
+ throw new Error('no base64 data')
812
+ }
813
+ stream = this._transformBase64ToStream()
814
+ break
815
+
816
+ case FileBoxType.Uuid: {
817
+ if (!this.uuid) {
818
+ throw new Error('no uuid data')
819
+ }
820
+ const FileBoxKlass = instanceToClass(this, FileBox)
821
+
822
+ if (!FileBoxKlass.uuidToStream) {
823
+ throw new Error('need to call FileBox.setUuidLoader() to set UUID loader first.')
824
+ }
825
+
826
+ stream = await FileBoxKlass.uuidToStream.call(this, this.uuid)
827
+ break
828
+ }
829
+
830
+ default:
831
+ throw new Error('not supported FileBoxType: ' + FileBoxType[this.type])
832
+ }
833
+
834
+ return stream
835
+ }
836
+
837
+ /**
838
+ * https://stackoverflow.com/a/16044400/1123955
839
+ */
840
+ private _transformBufferToStream (buffer?: Buffer): Readable {
841
+ const bufferStream = new PassThrough()
842
+ bufferStream.end(buffer || this.buffer)
843
+
844
+ /**
845
+ * Use small `chunks` with `toStream()` #44
846
+ * https://github.com/huan/file-box/issues/44
847
+ */
848
+ return bufferStream.pipe(sizedChunkTransformer())
849
+ }
850
+
851
+ private _transformBase64ToStream (): Readable {
852
+ if (!this.base64) {
853
+ throw new Error('no base64 data')
854
+ }
855
+ const buffer = Buffer.from(this.base64, 'base64')
856
+ return this._transformBufferToStream(buffer)
857
+ }
858
+
859
+ private _transformFileToStream (): Readable {
860
+ if (!this.localPath) {
861
+ throw new Error('no url(path)')
862
+ }
863
+ return FS.createReadStream(this.localPath)
864
+ }
865
+
866
+ private async _transformUrlToStream (): Promise<Readable> {
867
+ return new Promise<Readable>((resolve, reject) => {
868
+ if (this.remoteUrl) {
869
+ httpStream(this.remoteUrl, this.headers)
870
+ .then(resolve)
871
+ .catch(reject)
872
+ } else {
873
+ reject(new Error('no url'))
874
+ }
875
+ })
876
+ }
877
+
878
+ private async _transformQRCodeToStream (): Promise<Readable> {
879
+ if (!this.qrCode) {
880
+ throw new Error('no QR Code Value found')
881
+ }
882
+ const stream = qrValueToStream(this.qrCode)
883
+ return stream
884
+ }
885
+
886
+ /**
887
+ * save file
888
+ *
889
+ * @param filePath save file
890
+ */
891
+ async toFile (
892
+ filePath?: string,
893
+ overwrite = false,
894
+ ): Promise<void> {
895
+ if (this.type === FileBoxType.Url) {
896
+ if (!this.mediaType || !this.name) {
897
+ await this._syncUrlMetadata()
898
+ }
899
+ }
900
+ const fullFilePath = PATH.resolve(filePath || this.name)
901
+
902
+ const exist = FS.existsSync(fullFilePath)
903
+
904
+ if (exist && !overwrite) {
905
+ throw new Error(`FileBox.toFile(${fullFilePath}): file exist. use FileBox.toFile(${fullFilePath}, true) to force overwrite.`)
906
+ }
907
+
908
+ const writeStream = FS.createWriteStream(fullFilePath)
909
+
910
+ /**
911
+ * Huan(202109): make sure the file can be opened for writting
912
+ * before we pipe the stream to it
913
+ */
914
+ await new Promise((resolve, reject) => writeStream
915
+ .once('open', resolve)
916
+ .once('error', reject),
917
+ )
918
+ /**
919
+ * Start pipe
920
+ */
921
+ await new Promise((resolve, reject) => {
922
+ writeStream
923
+ .once('close', resolve)
924
+ .once('error', reject)
925
+
926
+ this.pipe(writeStream)
927
+ })
928
+ }
929
+
930
+ async toBase64 (): Promise<string> {
931
+ if (this.type === FileBoxType.Base64) {
932
+ if (!this.base64) {
933
+ throw new Error('no base64 data')
934
+ }
935
+ return this.base64
936
+ }
937
+
938
+ const buffer = await this.toBuffer()
939
+ return buffer.toString('base64')
940
+ }
941
+
942
+ /**
943
+ * dataUrl: `data:image/png;base64,${base64Text}',
944
+ */
945
+ async toDataURL (): Promise<string> {
946
+ const base64Text = await this.toBase64()
947
+
948
+ if (!this.mediaType) {
949
+ throw new Error('no mediaType found')
950
+ }
951
+
952
+ const dataUrl = [
953
+ 'data:',
954
+ this.mediaType,
955
+ ';base64,',
956
+ base64Text,
957
+ ].join('')
958
+
959
+ return dataUrl
960
+ }
961
+
962
+ async toBuffer (): Promise<Buffer> {
963
+ if (this.type === FileBoxType.Buffer) {
964
+ if (!this.buffer) {
965
+ throw new Error('no buffer!')
966
+ }
967
+ return this.buffer
968
+ }
969
+
970
+ const stream = new PassThrough()
971
+ this.pipe(stream)
972
+
973
+ const buffer: Buffer = await streamToBuffer(stream)
974
+ return buffer
975
+ }
976
+
977
+ async toQRCode (): Promise<string> {
978
+ if (this.type === FileBoxType.QRCode) {
979
+ if (!this.qrCode) {
980
+ throw new Error('no QR Code!')
981
+ }
982
+ return this.qrCode
983
+ }
984
+
985
+ const buf = await this.toBuffer()
986
+ const qrValue = await bufferToQrValue(buf)
987
+
988
+ return qrValue
989
+ }
990
+
991
+ async toUuid (): Promise<string> {
992
+ if (this.type === FileBoxType.Uuid) {
993
+ if (!this.uuid) {
994
+ throw new Error('no uuid found for a UUID type file box!')
995
+ }
996
+ return this.uuid
997
+ }
998
+
999
+ const FileBoxKlass = instanceToClass(this, FileBox)
1000
+
1001
+ if (!FileBoxKlass.uuidFromStream) {
1002
+ throw new Error('need to use FileBox.setUuidSaver() before dealing with UUID')
1003
+ }
1004
+
1005
+ const stream = new PassThrough()
1006
+ this.pipe(stream)
1007
+
1008
+ return FileBoxKlass.uuidFromStream.call(this, stream)
1009
+ }
1010
+
1011
+ /**
1012
+ *
1013
+ * toXXX methods END
1014
+ *
1015
+ */
1016
+
1017
+ pipe<T extends Writable> (
1018
+ destination: T,
1019
+ ): T {
1020
+ this.toStream()
1021
+ .then(stream => {
1022
+ stream.on('error', e => {
1023
+ destination.emit('error', e)
1024
+ })
1025
+ return stream.pipe(destination)
1026
+ })
1027
+ .catch(e => destination.emit('error', e))
1028
+ return destination
1029
+ }
1030
+
1031
+ }
1032
+
1033
+ /**
1034
+ * Huan(202110): lazy initialize `interfaceOfClass(FileBox)`
1035
+ * because we only can reference a class after its declaration
1036
+ */
1037
+ interfaceOfFileBox = interfaceOfClass(FileBox)<FileBoxInterface>()
1038
+ looseInstanceOfFileBox = looseInstanceOfClass(FileBox)
1039
+
1040
+ export {
1041
+ FileBox,
1042
+ }