@visulima/storage 1.0.0-alpha.17 → 1.0.0-alpha.19

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 (185) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/LICENSE.md +3 -0
  3. package/dist/adapter/nuxt/module.d.ts +3 -3
  4. package/dist/adapter/nuxt/module.js +1 -93
  5. package/dist/handler/http/fetch/index.d.ts +6 -6
  6. package/dist/handler/http/fetch/index.js +1 -4
  7. package/dist/handler/http/hono/index.d.ts +3 -3
  8. package/dist/handler/http/hono/index.js +1 -65
  9. package/dist/handler/http/nextjs/index.d.ts +4 -4
  10. package/dist/handler/http/nextjs/index.js +1 -37
  11. package/dist/handler/http/node/index.d.ts +6 -6
  12. package/dist/handler/http/node/index.js +1 -8
  13. package/dist/handler/http/solid-start/index.d.ts +4 -4
  14. package/dist/handler/http/solid-start/index.js +1 -44
  15. package/dist/index.js +1 -13
  16. package/dist/openapi/index.js +1 -5
  17. package/dist/packem_shared/AwsLightFile-BTLiRxXj.js +1 -0
  18. package/dist/packem_shared/AwsLightMetaStorage-DHz1nf_H.js +1 -0
  19. package/dist/packem_shared/AwsLightStorage-CA9cdfJ6.js +1 -0
  20. package/dist/packem_shared/AzureFile-BKHru1zB.js +1 -0
  21. package/dist/packem_shared/AzureSMetaStorage-BqA4jUDJ.js +1 -0
  22. package/dist/packem_shared/AzureStorage-BKUjCymW.js +1 -0
  23. package/dist/packem_shared/BaseTransformer-D68eLyz1.js +1 -0
  24. package/dist/packem_shared/DiskStorage-DNjpDMX9.js +1 -0
  25. package/dist/packem_shared/DiskStorageWithChecksum-Bb6JX_tC.js +1 -0
  26. package/dist/packem_shared/ERRORS-B9jkvKwx.js +1 -0
  27. package/dist/packem_shared/File-alcSNpLq.js +1 -0
  28. package/dist/packem_shared/GCSConfig-BQ0OpVhe.js +1 -0
  29. package/dist/packem_shared/GCSFile-CHjX_fJk.js +1 -0
  30. package/dist/packem_shared/GCSMetaStorage-CpxQ8wM9.js +1 -0
  31. package/dist/packem_shared/GCStorage-DAg1V55T.js +1 -0
  32. package/dist/packem_shared/LocalMetaStorage-BWuFvWn4.js +1 -0
  33. package/dist/packem_shared/MediaTransformer-2C8MJeAg.js +1 -0
  34. package/dist/packem_shared/MetaStorage-Y4m1dWgd.js +1 -0
  35. package/dist/packem_shared/Metadata-B6Ir0KO7.js +1 -0
  36. package/dist/packem_shared/Multipart-Bd9YFFn0.js +1 -0
  37. package/dist/packem_shared/Multipart-D5bcEvga.js +1 -0
  38. package/dist/packem_shared/NetlifyBlobFile-CpzkMKng.js +1 -0
  39. package/dist/packem_shared/NetlifyBlobMetaStorage-dD0oBLom.js +1 -0
  40. package/dist/packem_shared/NetlifyBlobStorage-ByTwCN-h.js +1 -0
  41. package/dist/packem_shared/NoOpMetrics-Dky06sf1.js +1 -0
  42. package/dist/packem_shared/OpenTelemetryMetrics-9uxQMSlg.js +1 -0
  43. package/dist/packem_shared/Rest-CYEBbtCD.js +1 -0
  44. package/dist/packem_shared/Rest-DoonVU7B.js +1 -0
  45. package/dist/packem_shared/S3File-Du6wP0Sz.js +1 -0
  46. package/dist/packem_shared/S3MetaStorage-vcriiosn.js +1 -0
  47. package/dist/packem_shared/S3Storage-Fx6-OSc-.js +1 -0
  48. package/dist/packem_shared/TUS_RESUMABLE-B7KtPVeC.js +1 -0
  49. package/dist/packem_shared/Tus-Bd_2Ki2G.js +1 -0
  50. package/dist/packem_shared/Tus-CUBCkuPE.js +1 -0
  51. package/dist/packem_shared/ValidationError-D8KKJiKA.js +1 -0
  52. package/dist/packem_shared/VercelBlobFile-CL9MjKcY.js +1 -0
  53. package/dist/packem_shared/VercelBlobMetaStorage-BsOTx-Su.js +1 -0
  54. package/dist/packem_shared/VercelBlobStorage-kNkk5Cn4.js +1 -0
  55. package/dist/packem_shared/_commonjsHelpers-D6W6KoPK.js +1 -0
  56. package/dist/packem_shared/aws-light-meta-storage-TPyQwLvk.js +4 -0
  57. package/dist/packem_shared/backblaze-DEflv87m.js +1 -0
  58. package/dist/packem_shared/base-handler-core-DaggCVsq.js +1 -0
  59. package/dist/packem_shared/base-handler-fetch-DnarP64X.js +1 -0
  60. package/dist/packem_shared/base-handler-node-BTes6sql.js +1 -0
  61. package/dist/packem_shared/cache-qV_5Umeq.js +1 -0
  62. package/dist/packem_shared/cloudflare-DLAeQvgD.js +1 -0
  63. package/dist/packem_shared/defaultCloudStorageFileNameValidation-yiM9GxHr.js +1 -0
  64. package/dist/packem_shared/digitalOcean-nhZUds87.js +1 -0
  65. package/dist/packem_shared/disk-storage-BvdK4bqF.js +5 -0
  66. package/dist/packem_shared/gcs-meta-storage-CvGkpzVE.js +1 -0
  67. package/dist/packem_shared/has-content-Bx2p8Okx.js +1 -0
  68. package/dist/packem_shared/headers-DC2MEOrK.js +27 -0
  69. package/dist/packem_shared/is-expired-1JuZjDtb.js +1 -0
  70. package/dist/packem_shared/isRetryableError-DQNSZiBc.js +1 -0
  71. package/dist/packem_shared/isValidMediaType-B-2UKjZx.js +1 -0
  72. package/dist/packem_shared/local-meta-storage-Bkwqi_gi.js +1 -0
  73. package/dist/packem_shared/{media-transformer.d-ChmjTvjc.d.ts → media-transformer.d-CY9CsEOe.d.ts} +1 -1
  74. package/dist/packem_shared/minio-BWwgGXeE.js +1 -0
  75. package/dist/packem_shared/multipart-base-C6OjBwMS.js +1 -0
  76. package/dist/packem_shared/part-match-DqD5U7An.js +1 -0
  77. package/dist/packem_shared/path-D7rMc9nq-B718mbm6.js +1 -0
  78. package/dist/packem_shared/response-builder-Wdo_fvcF.js +1 -0
  79. package/dist/packem_shared/rest-base-B2Dv7esY.js +1 -0
  80. package/dist/packem_shared/restOpenApiSpec-PaIVbESh.js +1 -0
  81. package/dist/packem_shared/s3-base-storage-xHZNtsGe.js +1 -0
  82. package/dist/packem_shared/sharedGet-Ce7WGAsc.js +1 -0
  83. package/dist/packem_shared/storage-m_Cxgiih.js +1 -0
  84. package/dist/packem_shared/tigris-DZoyrMNd.js +1 -0
  85. package/dist/packem_shared/transformOpenApiSpec-C0vEbDTO.js +1 -0
  86. package/dist/packem_shared/{tus-base.d-CpLzyd-S.d.ts → tus-base.d-DNZRXR9X.d.ts} +2 -2
  87. package/dist/packem_shared/tusOpenApiSpec-DF0-hlWV.js +1 -0
  88. package/dist/packem_shared/{types.d-DBjmJvIT.d.ts → types.d-BCN94sB_.d.ts} +1 -1
  89. package/dist/packem_shared/{types.d-DviSTLOg.d.ts → types.d-D4caL2Fp.d.ts} +1 -1
  90. package/dist/packem_shared/update-size-DANV7aa2.js +1 -0
  91. package/dist/packem_shared/validator-DK41xp8N.js +1 -0
  92. package/dist/packem_shared/waitForStorage-ChSBLhLc.js +1 -0
  93. package/dist/packem_shared/wasabi-Dmce4x9s.js +1 -0
  94. package/dist/packem_shared/xhrOpenApiSpec-nyoboz83.js +1 -0
  95. package/dist/storage/aws/clients/index.js +1 -6
  96. package/dist/storage/aws/index.js +1 -3
  97. package/dist/storage/aws-light/index.js +1 -3
  98. package/dist/storage/azure/index.js +1 -3
  99. package/dist/storage/gcs/index.js +1 -4
  100. package/dist/storage/local/index.js +1 -3
  101. package/dist/storage/netlify-blob/index.js +1 -3
  102. package/dist/storage/vercel-blob/index.js +1 -3
  103. package/dist/transformer/audio-transformer.d.ts +1 -1
  104. package/dist/transformer/audio-transformer.js +1 -278
  105. package/dist/transformer/image-transformer.d.ts +1 -1
  106. package/dist/transformer/image-transformer.js +1 -1100
  107. package/dist/transformer/index.d.ts +2 -2
  108. package/dist/transformer/index.js +1 -4
  109. package/dist/transformer/video-transformer.d.ts +1 -1
  110. package/dist/transformer/video-transformer.js +1 -310
  111. package/package.json +18 -18
  112. package/dist/packem_shared/AwsLightFile-C-Nd5rtl.js +0 -11
  113. package/dist/packem_shared/AwsLightMetaStorage-BIDb5EPg.js +0 -4
  114. package/dist/packem_shared/AwsLightStorage-CMuKw4bI.js +0 -133
  115. package/dist/packem_shared/AzureFile-C0kug4w4.js +0 -8
  116. package/dist/packem_shared/AzureSMetaStorage-BEbXX9Uk.js +0 -89
  117. package/dist/packem_shared/AzureStorage-98-LRZ1e.js +0 -359
  118. package/dist/packem_shared/BaseTransformer-9965D4pb.js +0 -83
  119. package/dist/packem_shared/DiskStorage-COMe_G42.js +0 -12
  120. package/dist/packem_shared/DiskStorageWithChecksum-BVaaZiyS.js +0 -249
  121. package/dist/packem_shared/ERRORS-D0apMqnc.js +0 -97
  122. package/dist/packem_shared/File-CPUhj3DI.js +0 -69
  123. package/dist/packem_shared/GCSConfig-vPP22kN6.js +0 -11
  124. package/dist/packem_shared/GCSFile-DaDiCKE2.js +0 -8
  125. package/dist/packem_shared/GCSMetaStorage-BygVH0Ee.js +0 -7
  126. package/dist/packem_shared/GCStorage-4hSx_bN1.js +0 -410
  127. package/dist/packem_shared/LocalMetaStorage-DvnZqf61.js +0 -7
  128. package/dist/packem_shared/MediaTransformer-BRRqYA0E.js +0 -1202
  129. package/dist/packem_shared/MetaStorage-pECeFOad.js +0 -46
  130. package/dist/packem_shared/Metadata-DRLXeJ0F.js +0 -89
  131. package/dist/packem_shared/Multipart-BzzvNxt4.js +0 -128
  132. package/dist/packem_shared/Multipart-DLtFLQ8s.js +0 -163
  133. package/dist/packem_shared/NetlifyBlobFile-CfyJtGWN.js +0 -14
  134. package/dist/packem_shared/NetlifyBlobMetaStorage-eJT6RFy9.js +0 -9
  135. package/dist/packem_shared/NetlifyBlobStorage-nPv7iIAG.js +0 -376
  136. package/dist/packem_shared/NoOpMetrics-DhAk5rXc.js +0 -10
  137. package/dist/packem_shared/OpenTelemetryMetrics-BnxhqIaH.js +0 -67
  138. package/dist/packem_shared/Rest-DEa7IA9R.js +0 -267
  139. package/dist/packem_shared/Rest-qhMbCn5a.js +0 -228
  140. package/dist/packem_shared/S3File-BXCuJaQZ.js +0 -11
  141. package/dist/packem_shared/S3MetaStorage-yT4CznlV.js +0 -77
  142. package/dist/packem_shared/S3Storage-wi5XT-yu.js +0 -317
  143. package/dist/packem_shared/TUS_RESUMABLE-Dtfsffnj.js +0 -434
  144. package/dist/packem_shared/Tus-Ck0Hdle5.js +0 -195
  145. package/dist/packem_shared/Tus-_0vCdHyB.js +0 -200
  146. package/dist/packem_shared/ValidationError-BfF1aE4h.js +0 -26
  147. package/dist/packem_shared/VercelBlobFile-DkFIIb9Q.js +0 -18
  148. package/dist/packem_shared/VercelBlobMetaStorage-DjFmi4vp.js +0 -9
  149. package/dist/packem_shared/VercelBlobStorage-CmMgiDgr.js +0 -261
  150. package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +0 -5
  151. package/dist/packem_shared/aws-light-meta-storage-BGZEiY3y.js +0 -447
  152. package/dist/packem_shared/backblaze-BlMnIcBC.js +0 -20
  153. package/dist/packem_shared/base-handler-core-lVB-D59L.js +0 -306
  154. package/dist/packem_shared/base-handler-fetch-CitgN-Aw.js +0 -291
  155. package/dist/packem_shared/base-handler-node-BiO2DF87.js +0 -731
  156. package/dist/packem_shared/cache-B88MXQ_2.js +0 -18
  157. package/dist/packem_shared/cloudflare-Bi1q8wXE.js +0 -21
  158. package/dist/packem_shared/defaultCloudStorageFileNameValidation-Brw40ZEP.js +0 -11
  159. package/dist/packem_shared/digitalOcean-CWQRJM3L.js +0 -21
  160. package/dist/packem_shared/disk-storage-D9RfoKjW.js +0 -832
  161. package/dist/packem_shared/gcs-meta-storage-8FovEDbW.js +0 -159
  162. package/dist/packem_shared/has-content-CY66ehMK.js +0 -3
  163. package/dist/packem_shared/headers-YLRp1dLo.js +0 -1318
  164. package/dist/packem_shared/is-expired-CTThU1q5.js +0 -8
  165. package/dist/packem_shared/isRetryableError-Dycp7127.js +0 -82
  166. package/dist/packem_shared/isValidMediaType-BeDgiObq.js +0 -35
  167. package/dist/packem_shared/local-meta-storage-D-pPc3FI.js +0 -658
  168. package/dist/packem_shared/minio-C2YBZQOw.js +0 -22
  169. package/dist/packem_shared/multipart-base-C994naBi.js +0 -102
  170. package/dist/packem_shared/part-match-Dt738Ddb.js +0 -96
  171. package/dist/packem_shared/path-aV9lMdcn-DMU5H1-h.js +0 -162
  172. package/dist/packem_shared/response-builder-BtnRiBUI.js +0 -45
  173. package/dist/packem_shared/rest-base-k1ory8uB.js +0 -457
  174. package/dist/packem_shared/restOpenApiSpec-BBJ_SiPE.js +0 -800
  175. package/dist/packem_shared/s3-base-storage-zUO8u8Cp.js +0 -510
  176. package/dist/packem_shared/sharedGet-CkSLSnMd.js +0 -1022
  177. package/dist/packem_shared/storage-5XW1E7XU.js +0 -1188
  178. package/dist/packem_shared/tigris-Hac8TKnX.js +0 -21
  179. package/dist/packem_shared/transformOpenApiSpec-BodFhFls.js +0 -975
  180. package/dist/packem_shared/tusOpenApiSpec-DzppyJTW.js +0 -740
  181. package/dist/packem_shared/update-size-CCGm6i1J.js +0 -8
  182. package/dist/packem_shared/validator-DuO17wFF.js +0 -79
  183. package/dist/packem_shared/waitForStorage-Cscw85sx.js +0 -18
  184. package/dist/packem_shared/wasabi-DIyflHSd.js +0 -21
  185. package/dist/packem_shared/xhrOpenApiSpec-CXz7BC5J.js +0 -264
@@ -1,1188 +0,0 @@
1
- import { Readable } from 'node:stream';
2
- import { setInterval } from 'node:timers';
3
- import { inspect } from 'node:util';
4
- import typeis from 'type-is';
5
- import NoOpMetrics from './NoOpMetrics-DhAk5rXc.js';
6
- import { N as NoOpCache } from './cache-B88MXQ_2.js';
7
- import { ErrorMap, throwErrorCode, ERRORS } from './ERRORS-D0apMqnc.js';
8
- import { LRUCache } from 'lru-cache';
9
- import { a as Validator } from './validator-DuO17wFF.js';
10
- import { n as normalize, i as isAbsolute } from './path-aV9lMdcn-DMU5H1-h.js';
11
- import { i as isExpired } from './is-expired-CTThU1q5.js';
12
-
13
- const PARSE_BYTES_REGEX = /^(?<value>-?\d+(?:[.,]\d+)*) *(?<type>[a-z]+)?$/i;
14
- const KIBI_REGEX = /^KIBI/;
15
- const MIBI_REGEX = /^MIBI/;
16
- const GIBI_REGEX = /^GIBI/;
17
- const TEBI_REGEX = /^TEBI/;
18
- const PEBI_REGEX = /^PEBI/;
19
- const EXBI_REGEX = /^EXBI/;
20
- const ZEBI_REGEX = /^ZEBI/;
21
- const YIBI_REGEX = /^YIBI/;
22
- const IB_SUFFIX_REGEX = /^(.)IB$/;
23
- const BYTE_SIZES = {
24
- iec: [
25
- {
26
- long: "Bytes",
27
- short: "B"
28
- },
29
- {
30
- long: "Kibibytes",
31
- short: "KiB"
32
- },
33
- {
34
- long: "Mebibytes",
35
- short: "MiB"
36
- },
37
- {
38
- long: "Gibibytes",
39
- short: "GiB"
40
- },
41
- {
42
- long: "Tebibytes",
43
- short: "TiB"
44
- },
45
- {
46
- long: "Pebibytes",
47
- short: "PiB"
48
- },
49
- {
50
- long: "Exbibytes",
51
- short: "EiB"
52
- },
53
- {
54
- long: "Zebibytes",
55
- short: "ZiB"
56
- },
57
- {
58
- long: "Yobibytes",
59
- short: "YiB"
60
- }
61
- ],
62
- iec_octet: [
63
- {
64
- long: "Octets",
65
- short: "o"
66
- },
67
- {
68
- long: "Kibioctets",
69
- short: "Kio"
70
- },
71
- {
72
- long: "Mebioctets",
73
- short: "Mio"
74
- },
75
- {
76
- long: "Gibioctets",
77
- short: "Gio"
78
- },
79
- {
80
- long: "Tebioctets",
81
- short: "Tio"
82
- },
83
- {
84
- long: "Pebioctets",
85
- short: "Pio"
86
- },
87
- {
88
- long: "Exbioctets",
89
- short: "Eio"
90
- },
91
- {
92
- long: "Zebioctets",
93
- short: "Zio"
94
- },
95
- {
96
- long: "Yobioctets",
97
- short: "Yio"
98
- }
99
- ],
100
- metric: [
101
- {
102
- long: "Bytes",
103
- short: "Bytes"
104
- },
105
- {
106
- long: "Kilobytes",
107
- short: "KB"
108
- },
109
- {
110
- long: "Megabytes",
111
- short: "MB"
112
- },
113
- {
114
- long: "Gigabytes",
115
- short: "GB"
116
- },
117
- {
118
- long: "Terabytes",
119
- short: "TB"
120
- },
121
- {
122
- long: "Petabytes",
123
- short: "PB"
124
- },
125
- {
126
- long: "Exabytes",
127
- short: "EB"
128
- },
129
- {
130
- long: "Zettabytes",
131
- short: "ZB"
132
- },
133
- {
134
- long: "Yottabytes",
135
- short: "YB"
136
- }
137
- ],
138
- metric_octet: [
139
- {
140
- long: "Octets",
141
- short: "o"
142
- },
143
- {
144
- long: "Kilo-octets",
145
- short: "ko"
146
- },
147
- {
148
- long: "Mega-octets",
149
- short: "Mo"
150
- },
151
- {
152
- long: "Giga-octets",
153
- short: "Go"
154
- },
155
- {
156
- long: "Tera-octets",
157
- short: "To"
158
- },
159
- {
160
- long: "Peta-octets",
161
- short: "Po"
162
- },
163
- {
164
- long: "Exa-octets",
165
- short: "Eo"
166
- },
167
- {
168
- long: "Zetta-octets",
169
- short: "Zo"
170
- },
171
- {
172
- long: "Yotta-octets",
173
- short: "Yo"
174
- }
175
- ]
176
- };
177
- const parseLocalizedNumber = (stringNumber, locale) => {
178
- const thousandSeparator = new Intl.NumberFormat(locale).format(11111).replaceAll(new RegExp("\\p{Number}", "gu"), "");
179
- const decimalSeparator = new Intl.NumberFormat(locale).format(1.1).replaceAll(new RegExp("\\p{Number}", "gu"), "");
180
- return Number.parseFloat(stringNumber.replaceAll(new RegExp(`\\${thousandSeparator}`, "g"), "").replace(new RegExp(`\\${decimalSeparator}`), "."));
181
- };
182
- const fromBase = (base) => {
183
- switch (base) {
184
- case 2: {
185
- return 1024;
186
- }
187
- case 10: {
188
- return 1e3;
189
- }
190
- default: {
191
- throw new Error("Unsupported base.");
192
- }
193
- }
194
- };
195
- const parseBytes = (value, options) => {
196
- const config = {
197
- base: 2,
198
- locale: "en-US",
199
- units: "metric",
200
- ...options
201
- };
202
- if (typeof value !== "string" || value.length === 0) {
203
- throw new TypeError("Value is not a string or is empty.");
204
- }
205
- if (value.length > 100) {
206
- throw new TypeError("Value exceeds the maximum length of 100 characters.");
207
- }
208
- const match = PARSE_BYTES_REGEX.exec(value);
209
- const groups = match?.groups;
210
- if (!groups) {
211
- return Number.NaN;
212
- }
213
- const localizedNumber = parseLocalizedNumber(groups.value, config.locale);
214
- const type = (groups.type ?? "Bytes").toUpperCase().replace(KIBI_REGEX, "KILO").replace(MIBI_REGEX, "MEGA").replace(GIBI_REGEX, "GIGA").replace(TEBI_REGEX, "TERA").replace(PEBI_REGEX, "PETA").replace(EXBI_REGEX, "EXA").replace(ZEBI_REGEX, "ZETTA").replace(YIBI_REGEX, "YOTTA").replace(IB_SUFFIX_REGEX, "$1B");
215
- const level = BYTE_SIZES[config.units].findIndex((unit) => unit.short[0].toUpperCase() === type[0]);
216
- const base = fromBase(config.base);
217
- return localizedNumber * base ** level;
218
- };
219
-
220
- const createDurationLanguage = (y, mo, w, d, h, m, s, ms, future, past, decimal, unitMap, groupSeparator, placeholderSeparator) => {
221
- const result = {
222
- d,
223
- h,
224
- m,
225
- mo,
226
- ms,
227
- s,
228
- w,
229
- y
230
- };
231
- {
232
- result.future = future;
233
- }
234
- {
235
- result.past = past;
236
- }
237
- {
238
- result.decimal = decimal;
239
- }
240
- if (unitMap !== void 0) {
241
- result.unitMap = unitMap;
242
- }
243
- {
244
- result.groupSeparator = groupSeparator;
245
- }
246
- {
247
- result.placeholderSeparator = placeholderSeparator;
248
- }
249
- return result;
250
- };
251
-
252
- const englishUnitMap = {
253
- d: "d",
254
- day: "d",
255
- days: "d",
256
- h: "h",
257
- hour: "h",
258
- hours: "h",
259
- hr: "h",
260
- hrs: "h",
261
- m: "m",
262
- millisecond: "ms",
263
- milliseconds: "ms",
264
- min: "m",
265
- mins: "m",
266
- minute: "m",
267
- minutes: "m",
268
- mo: "mo",
269
- month: "mo",
270
- months: "mo",
271
- ms: "ms",
272
- s: "s",
273
- sec: "s",
274
- second: "s",
275
- seconds: "s",
276
- secs: "s",
277
- w: "w",
278
- week: "w",
279
- weeks: "w",
280
- y: "y",
281
- year: "y",
282
- years: "y",
283
- yr: "y",
284
- yrs: "y"
285
- };
286
- const durationLanguage = createDurationLanguage(
287
- (counter) => `year${counter === 1 ? "" : "s"}`,
288
- (counter) => `month${counter === 1 ? "" : "s"}`,
289
- (counter) => `week${counter === 1 ? "" : "s"}`,
290
- (counter) => `day${counter === 1 ? "" : "s"}`,
291
- (counter) => `hour${counter === 1 ? "" : "s"}`,
292
- (counter) => `minute${counter === 1 ? "" : "s"}`,
293
- (counter) => `second${counter === 1 ? "" : "s"}`,
294
- (counter) => `millisecond${counter === 1 ? "" : "s"}`,
295
- "in %s",
296
- "%s ago",
297
- ".",
298
- // decimal
299
- englishUnitMap,
300
- ",",
301
- // groupSeparator
302
- "_"
303
- // placeholderSeparator
304
- );
305
-
306
- const validateDurationLanguage = (language) => {
307
- const requiredProperties = ["y", "mo", "w", "d", "h", "m", "s", "ms", "future", "past"];
308
- for (const property of requiredProperties) {
309
- if (!Object.hasOwn(language, property)) {
310
- throw new TypeError(`Missing required property: ${property}`);
311
- }
312
- }
313
- if (typeof language.future !== "string" || typeof language.past !== "string") {
314
- throw new TypeError("Properties future and past must be of type string");
315
- }
316
- const unitProperties = ["y", "mo", "w", "d", "h", "m", "s", "ms"];
317
- for (const property of unitProperties) {
318
- if (typeof language[property] !== "string" && typeof language[property] !== "function") {
319
- throw new TypeError(`Property ${property} must be of type string or function`);
320
- }
321
- }
322
- if (language.decimal && typeof language.decimal !== "string") {
323
- throw new TypeError("Property decimal must be of type string");
324
- }
325
- if (language.delimiter && typeof language.delimiter !== "string") {
326
- throw new TypeError("Property delimiter must be of type string");
327
- }
328
- if (language._digitReplacements && !Array.isArray(language._digitReplacements)) {
329
- throw new TypeError("Property _digitReplacements must be an array");
330
- }
331
- if (language._numberFirst && typeof language._numberFirst !== "boolean") {
332
- throw new TypeError("Property _numberFirst must be of type boolean");
333
- }
334
- if (language.unitMap && typeof language.unitMap !== "object") {
335
- throw new TypeError("Property unitMap must be an object");
336
- }
337
- if (language.unitMap && Object.values(language.unitMap).some((value) => typeof value !== "string")) {
338
- throw new TypeError("All values in unitMap must be of type string");
339
- }
340
- };
341
-
342
- const STANDARD_UNIT_MEASURES = {
343
- d: 864e5,
344
- h: 36e5,
345
- m: 6e4,
346
- mo: 2629746e3,
347
- ms: 1,
348
- s: 1e3,
349
- w: 6048e5,
350
- y: 31556952e3
351
- };
352
- const ESCAPE_REGEX = /[-/\\^$*+?.()|[\]{}]/g;
353
- const ISO_FORMAT = /^PT(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?$/i;
354
- const COLON_FORMAT = /^(?:(\d+):)?(?:(\d+):)?(\d+)$/;
355
- const NUMERIC_STRING_REGEX = /^[+-]?\d+(?:\.\d+)?$/;
356
- const SIGN_PREFIX_REGEX = /^[-+]/;
357
- const parseDuration = (value, options) => {
358
- if (typeof value !== "string" || value.length === 0) {
359
- return void 0;
360
- }
361
- const { defaultUnit = "ms", language = durationLanguage } = {};
362
- validateDurationLanguage(language);
363
- const decimalSeparator = language.decimal ?? ".";
364
- const groupSeparator = language.groupSeparator ?? ",";
365
- const placeholderSeparator = language.placeholderSeparator ?? "_";
366
- const escapedDecimal = decimalSeparator.replaceAll(ESCAPE_REGEX, String.raw`\$&`);
367
- const escapedGroup = groupSeparator.replaceAll(ESCAPE_REGEX, String.raw`\$&`);
368
- const escapedPlaceholder = placeholderSeparator.replaceAll(ESCAPE_REGEX, String.raw`\$&`);
369
- const currentUnitMap = language.unitMap ?? englishUnitMap;
370
- let processedValue = value.replaceAll(new RegExp(String.raw`(\d)[${escapedPlaceholder}${escapedGroup}](\d)`, "g"), "$1$2");
371
- if (decimalSeparator !== ".") {
372
- processedValue = processedValue.replace(escapedDecimal, ".");
373
- }
374
- if (NUMERIC_STRING_REGEX.test(value)) {
375
- const numberOnly = Number.parseFloat(processedValue.trim());
376
- if (!Number.isNaN(numberOnly)) {
377
- const unitKey = currentUnitMap[defaultUnit];
378
- if (unitKey !== void 0) {
379
- return numberOnly * STANDARD_UNIT_MEASURES[unitKey];
380
- }
381
- }
382
- return void 0;
383
- }
384
- const isoMatch = ISO_FORMAT.exec(value);
385
- if (isoMatch) {
386
- const hours = Number.parseInt(isoMatch[1] ?? "0", 10);
387
- const minutes = Number.parseInt(isoMatch[2] ?? "0", 10);
388
- const seconds = Number.parseInt(isoMatch[3] ?? "0", 10);
389
- return hours * 36e5 + minutes * 6e4 + seconds * 1e3;
390
- }
391
- const colonMatch = COLON_FORMAT.exec(value);
392
- if (colonMatch) {
393
- let hours = 0;
394
- let minutes = 0;
395
- if (colonMatch[2] !== void 0) {
396
- hours = Number.parseInt(colonMatch[1] ?? "0", 10);
397
- minutes = Number.parseInt(colonMatch[2], 10);
398
- } else if (colonMatch[1] !== void 0) {
399
- minutes = Number.parseInt(colonMatch[1], 10);
400
- }
401
- const seconds = Number.parseInt(colonMatch[3] ?? "0", 10);
402
- return hours * 36e5 + minutes * 6e4 + seconds * 1e3;
403
- }
404
- const currentUnitMapKeys = Object.keys(currentUnitMap);
405
- const regexKeys = currentUnitMapKeys.toSorted((a, b) => b.length - a.length).map((k) => k.replaceAll(ESCAPE_REGEX, String.raw`\$&`)).join("|");
406
- const durationRegex = new RegExp(String.raw`(-?\d*\.?\d+)\s*(${regexKeys})`, "gi");
407
- let totalMs = 0;
408
- let match;
409
- let unitsFound = false;
410
- let firstMatchIndex = -1;
411
- let lastMatchEndIndex = 0;
412
- durationRegex.lastIndex = 0;
413
- while ((match = durationRegex.exec(processedValue)) !== null) {
414
- if (!unitsFound) {
415
- firstMatchIndex = match.index;
416
- }
417
- unitsFound = true;
418
- const numberString = match[1];
419
- const unitString = match[2];
420
- if (!numberString || !unitString) {
421
- continue;
422
- }
423
- const trimmedNumberString = numberString.trim();
424
- const sign = trimmedNumberString.startsWith("-") ? -1 : 1;
425
- const absNumberString = trimmedNumberString.replace(SIGN_PREFIX_REGEX, "");
426
- const parsedNumber = Number.parseFloat(absNumberString);
427
- const unitKey = currentUnitMap[unitString.toLowerCase()];
428
- if (unitKey === void 0) {
429
- continue;
430
- }
431
- const unitValue = STANDARD_UNIT_MEASURES[unitKey];
432
- if (Number.isNaN(parsedNumber)) {
433
- return void 0;
434
- }
435
- totalMs += sign * parsedNumber * unitValue;
436
- lastMatchEndIndex = durationRegex.lastIndex;
437
- }
438
- const leadingText = processedValue.slice(0, firstMatchIndex).trim();
439
- const trailingText = processedValue.slice(lastMatchEndIndex).trim();
440
- if (unitsFound && (leadingText.length > 0 || trailingText.length > 0)) {
441
- return void 0;
442
- }
443
- if (!unitsFound) {
444
- return void 0;
445
- }
446
- return totalMs;
447
- };
448
-
449
- const toMilliseconds = (value) => {
450
- if (value === Number(value)) {
451
- return value;
452
- }
453
- if (!value) {
454
- return void 0;
455
- }
456
- const parsed = parseDuration(value);
457
- if (parsed === void 0) {
458
- return void 0;
459
- }
460
- return parsed;
461
- };
462
-
463
- class Locker extends LRUCache {
464
- /**
465
- * Creates a new Locker instance with configurable TTL and cache options.
466
- * @param options LRU cache configuration options
467
- */
468
- constructor(options) {
469
- super({
470
- ttl: 1e3,
471
- ttlAutopurge: true,
472
- ...options
473
- });
474
- }
475
- /**
476
- * Acquires a lock for the specified key.
477
- * Throws an error if the key is already locked.
478
- * @param key The key to lock
479
- * @returns The lock token (same as the key)
480
- * @throws Error if the key is already locked
481
- */
482
- lock(key) {
483
- const locked = this.get(key);
484
- if (locked) {
485
- throw new Error(`${key} is locked`);
486
- }
487
- this.set(key, key);
488
- return key;
489
- }
490
- /**
491
- * Releases the lock for the specified key.
492
- * @param key The key to unlock
493
- */
494
- unlock(key) {
495
- this.delete(key);
496
- }
497
- }
498
-
499
- const extractOriginalName = (meta) => {
500
- if (typeof meta.name === "string") {
501
- return meta.name;
502
- }
503
- if (typeof meta.title === "string") {
504
- return meta.title;
505
- }
506
- if (typeof meta.originalName === "string") {
507
- return meta.originalName;
508
- }
509
- if (typeof meta.filename === "string") {
510
- return meta.filename;
511
- }
512
- return void 0;
513
- };
514
- const deepMerge = (target, source) => {
515
- const result = { ...target };
516
- Object.keys(source).forEach((key) => {
517
- const sourceValue = source[key];
518
- if (sourceValue !== null && typeof sourceValue === "object" && !Array.isArray(sourceValue)) {
519
- const targetValue = result[key];
520
- const targetObject = targetValue && typeof targetValue === "object" && !Array.isArray(targetValue) ? targetValue : {};
521
- result[key] = deepMerge(targetObject, sourceValue);
522
- } else {
523
- result[key] = sourceValue;
524
- }
525
- });
526
- return result;
527
- };
528
- const updateMetadata = (file, metadata) => {
529
- const merged = deepMerge(file, metadata);
530
- Object.assign(file, merged);
531
- file.originalName = extractOriginalName(file.metadata) || file.originalName;
532
- };
533
-
534
- const defaults = {
535
- allowMIME: ["*/*"],
536
- filename: ({ id }) => id,
537
- maxMetadataSize: "8MB",
538
- maxUploadSize: "5TB",
539
- onComplete: () => {
540
- },
541
- onCreate: () => {
542
- },
543
- onDelete: () => {
544
- },
545
- onError: () => {
546
- },
547
- onUpdate: () => {
548
- },
549
- useRelativeLocation: false,
550
- validation: {}
551
- };
552
- const defaultCloudStorageFileNameValidation = (name) => {
553
- if (!name || name.length < 3 || name.length > 255 || isAbsolute(name)) {
554
- return false;
555
- }
556
- const upperCase = name.toUpperCase();
557
- return !(upperCase.includes("../") || name.includes("\0"));
558
- };
559
- const defaultFilesystemFileNameValidation = (name) => {
560
- if (!name || name.length < 3 || name.length > 255 || isAbsolute(name)) {
561
- return false;
562
- }
563
- const upperCase = name.toUpperCase();
564
- const filesystemInvalidChars = ['"', "*", ":", "<", ">", "?", "\\", "|", "../", "\0"];
565
- return !filesystemInvalidChars.some((char) => upperCase.includes(char));
566
- };
567
- class BaseStorage {
568
- /**
569
- * Hook called when a new file is created.
570
- * @param file The newly created file object.
571
- * @remarks This hook is called after file metadata is saved but before returning the file.
572
- * Can be used for side effects like logging, notifications, or custom processing.
573
- */
574
- onCreate;
575
- /**
576
- * Hook called when file metadata is updated.
577
- * @param file The updated file object.
578
- * @remarks This hook is called after metadata is updated and saved.
579
- * Can be used for side effects like logging or custom processing.
580
- */
581
- onUpdate;
582
- /**
583
- * Hook called when a file upload is completed.
584
- * @param file The completed file object.
585
- * @param response The response object that can be modified in place (headers, statusCode, body).
586
- * @param request Optional request object for additional context.
587
- * @remarks This hook is called when file status becomes "completed".
588
- * The response object can be modified directly to add headers or change the status code.
589
- */
590
- onComplete;
591
- /**
592
- * Hook called when a file is deleted.
593
- * @param file The deleted file object.
594
- * @remarks This hook is called after the file is deleted but before returning.
595
- * Can be used for side effects like cleanup or logging.
596
- */
597
- onDelete;
598
- /**
599
- * Hook called when an error occurs during storage operations.
600
- * @param error The HTTP error object that can be modified in place.
601
- * @remarks This hook allows customizing error responses by modifying the error object.
602
- * The error object can be modified to change headers, statusCode, or body properties.
603
- * Error formatting happens in handlers after this hook is called.
604
- */
605
- onError;
606
- isReady = true;
607
- errorResponses = {};
608
- cache;
609
- logger;
610
- metrics;
611
- genericConfig;
612
- maxMetadataSize;
613
- checksumTypes = [];
614
- maxUploadSize;
615
- expiration;
616
- locker;
617
- namingFunction;
618
- validation = new Validator();
619
- assetFolder = void 0;
620
- /**
621
- * Limits the number of concurrent upload requests
622
- */
623
- concurrency;
624
- constructor(config) {
625
- const options = { ...defaults, ...config };
626
- this.onCreate = options.onCreate;
627
- this.onUpdate = options.onUpdate;
628
- this.onComplete = options.onComplete;
629
- this.onDelete = options.onDelete;
630
- this.onError = options.onError;
631
- this.namingFunction = options.filename;
632
- this.maxUploadSize = typeof options.maxUploadSize === "string" ? parseBytes(options.maxUploadSize) : options.maxUploadSize;
633
- this.maxMetadataSize = typeof options.maxMetadataSize === "string" ? parseBytes(options.maxMetadataSize) : options.maxMetadataSize;
634
- this.expiration = options.expiration;
635
- this.genericConfig = options;
636
- if (options.assetFolder !== void 0) {
637
- this.assetFolder = normalize(options.assetFolder);
638
- }
639
- this.locker = new Locker({
640
- max: 1e3,
641
- ttl: 3e4,
642
- ttlAutopurge: true
643
- });
644
- this.cache = options.cache ?? new NoOpCache();
645
- this.logger = options.logger;
646
- this.metrics = options.metrics ?? new NoOpMetrics();
647
- this.logger?.debug(`${this.constructor.name} config: ${inspect({ ...config, logger: this.logger.constructor })}`);
648
- const purgeInterval = toMilliseconds(options.expiration?.purgeInterval);
649
- if (purgeInterval) {
650
- this.startAutoPurge(purgeInterval);
651
- }
652
- const size = {
653
- isValid(file) {
654
- if (file.size === void 0) {
655
- return true;
656
- }
657
- const fileSizeValue = file.size;
658
- if (fileSizeValue === void 0 || fileSizeValue === null) {
659
- return false;
660
- }
661
- const fileSize = typeof fileSizeValue === "number" ? fileSizeValue : Number(fileSizeValue);
662
- if (Number.isNaN(fileSize) || fileSize < 0) {
663
- return false;
664
- }
665
- const maxSize = typeof this.value === "number" ? this.value : Number(this.value);
666
- return !Number.isNaN(maxSize) && fileSize <= maxSize;
667
- },
668
- response: ErrorMap.RequestEntityTooLarge,
669
- value: this.maxUploadSize
670
- };
671
- const mime = {
672
- isValid(file) {
673
- return !!typeis.is(file.contentType, this.value);
674
- },
675
- // @TODO: add better error handling for mime types
676
- response: ErrorMap.UnsupportedMediaType,
677
- value: options.allowMIME
678
- };
679
- const fileNameValidation = options.fileNameValidation ?? defaultCloudStorageFileNameValidation;
680
- const filename = {
681
- isValid(file) {
682
- return fileNameValidation(file.name);
683
- },
684
- response: ErrorMap.InvalidFileName
685
- };
686
- this.validation.add({ filename, mime, size });
687
- this.validation.add({ ...options.validation });
688
- }
689
- get tusExtension() {
690
- const extensions = ["creation", "creation-with-upload", "termination", "checksum", "creation-defer-length", "concatenation"];
691
- if (this.expiration) {
692
- extensions.push("expiration");
693
- }
694
- return extensions;
695
- }
696
- /**
697
- * Validates a file against configured validation rules.
698
- * @param file File object to validate.
699
- * @returns Promise resolving to undefined if file is valid, throws ValidationError otherwise.
700
- * @throws {ValidationError} If validation fails
701
- */
702
- async validate(file) {
703
- await this.validation.verify(file);
704
- }
705
- /**
706
- * Checks if a file exists by querying its metadata.
707
- * @param query File query containing the file ID to check.
708
- * @param query.id File ID to check.
709
- * @returns Promise resolving to true if file exists, false otherwise.
710
- * @remarks This method does not throw errors - it returns false if the file is not found.
711
- */
712
- async exists(query) {
713
- return this.instrumentOperation("exists", async () => {
714
- try {
715
- await this.getMeta(query.id);
716
- return true;
717
- } catch {
718
- return false;
719
- }
720
- });
721
- }
722
- /**
723
- * Normalizes errors with storage-specific context.
724
- * @param error The error to normalize.
725
- * @returns Normalized HTTP error with storage class context added to the message.
726
- * @remarks Errors are enhanced with the storage class name for better debugging.
727
- */
728
- normalizeError(error) {
729
- const baseError = {
730
- code: error.name,
731
- message: error.message,
732
- name: error.name,
733
- statusCode: 500
734
- };
735
- return {
736
- ...baseError,
737
- message: `[${this.constructor.name}] ${baseError.message}`
738
- };
739
- }
740
- /**
741
- * Gets the storage configuration.
742
- * @returns The current storage configuration options.
743
- */
744
- get config() {
745
- return this.genericConfig;
746
- }
747
- /**
748
- * Saves upload metadata to the metadata storage.
749
- * @param file File object containing metadata to save.
750
- * @returns Promise resolving to the saved file object.
751
- * @remarks Updates timestamps and caches the file metadata.
752
- */
753
- async saveMeta(file) {
754
- this.updateTimestamps(file);
755
- this.cache.set(file.id, file);
756
- return this.meta.save(file.id, file);
757
- }
758
- /**
759
- * Deletes upload metadata from the metadata storage.
760
- * @param id File ID whose metadata should be deleted.
761
- * @returns Promise resolving when metadata is deleted.
762
- * @remarks Also removes the file from the cache.
763
- */
764
- async deleteMeta(id) {
765
- this.cache.delete(id);
766
- return this.meta.delete(id);
767
- }
768
- /**
769
- * Retrieves upload metadata by file ID.
770
- * @param id File ID to retrieve metadata for.
771
- * @returns Promise resolving to the file metadata object.
772
- * @throws {UploadError} If the file metadata cannot be found (ERRORS.FILE_NOT_FOUND).
773
- * @remarks Caches the retrieved metadata for faster subsequent access.
774
- */
775
- async getMeta(id) {
776
- try {
777
- const file = await this.meta.get(id);
778
- this.cache.set(file.id, file);
779
- return { ...file };
780
- } catch (error) {
781
- const httpError = this.normalizeError(error instanceof Error ? error : new Error(String(error)));
782
- await this.onError(httpError);
783
- return throwErrorCode(ERRORS.FILE_NOT_FOUND);
784
- }
785
- }
786
- /**
787
- * Checks if a file has expired and deletes it if so.
788
- * @param file File object to check for expiration.
789
- * @returns Promise resolving to the file object if not expired.
790
- * @throws {UploadError} If the file has expired (ERRORS.GONE).
791
- * @remarks If the file is expired, it is automatically deleted and the metadata is removed.
792
- */
793
- async checkIfExpired(file) {
794
- if (isExpired(file)) {
795
- this.delete(file).catch(() => void 0);
796
- this.deleteMeta(file.id).catch(() => void 0);
797
- return throwErrorCode(ERRORS.GONE);
798
- }
799
- return file;
800
- }
801
- /**
802
- * Searches for and purges expired uploads.
803
- * @param maxAge Maximum age of files to keep (files older than this will be purged).
804
- * Can be a number (milliseconds) or string (e.g., "1h", "30m", "7d").
805
- * If not provided, uses the expiration.maxAge from configuration.
806
- * @returns Promise resolving to a list of purged files.
807
- * @remarks
808
- * Errors during individual file deletions are logged but do not stop the purge process.
809
- * Files with corrupted metadata are skipped with a warning.
810
- * Uses rolling expiration if configured (based on modifiedAt) or fixed expiration (based on createdAt).
811
- */
812
- async purge(maxAge) {
813
- return this.instrumentOperation("purge", async () => {
814
- const maxAgeMs = toMilliseconds(maxAge || this.expiration?.maxAge);
815
- const purged = { items: [], maxAgeMs };
816
- if (maxAgeMs) {
817
- const before = Date.now() - maxAgeMs;
818
- const list = await this.list();
819
- const expired = list.filter(
820
- (item) => Number(new Date(this.expiration?.rolling ? item.modifiedAt || item.createdAt : item.createdAt)) < before
821
- );
822
- for await (const { id, ...rest } of expired) {
823
- try {
824
- const deleted = await this.delete({ id });
825
- purged.items.push({ ...deleted, ...rest });
826
- } catch (error) {
827
- this.logger?.warn(`Failed to delete file ${id} during purge: ${error instanceof Error ? error.message : String(error)}`);
828
- }
829
- }
830
- if (purged.items.length > 0) {
831
- this.logger?.info(`Purge: removed ${purged.items.length} uploads`);
832
- this.metrics.gauge("storage.operations.purge.items_count", purged.items.length, {
833
- storage: this.constructor.name.toLowerCase().replace("storage", "")
834
- });
835
- }
836
- }
837
- return purged;
838
- });
839
- }
840
- /**
841
- * Gets an uploaded file as a readable stream for efficient large file handling.
842
- * @param query File query containing the file ID to stream.
843
- * @param query.id File ID to stream.
844
- * @returns Promise resolving to an object containing the stream, headers, and size.
845
- * @throws {UploadError} If the file cannot be found (ERRORS.FILE_NOT_FOUND) or has expired (ERRORS.GONE).
846
- * @remarks
847
- * Default implementation falls back to get() and creates a stream from the buffer.
848
- * Storage implementations should override this for better streaming performance.
849
- * Headers include Content-Type, Content-Length, ETag, and Last-Modified.
850
- */
851
- async getStream({ id }) {
852
- return this.instrumentOperation("getStream", async () => {
853
- const file = await this.get({ id });
854
- const stream = Readable.from(file.content);
855
- return {
856
- headers: {
857
- "Content-Length": String(file.size),
858
- "Content-Type": file.contentType,
859
- ...file.ETag && { ETag: file.ETag },
860
- ...file.modifiedAt && { "Last-Modified": file.modifiedAt.toString() }
861
- },
862
- size: typeof file.size === "number" ? file.size : void 0,
863
- stream
864
- };
865
- });
866
- }
867
- /**
868
- * Retrieves a list of uploaded files.
869
- * @param _limit Maximum number of files to return (default: 1000).
870
- * @returns Promise resolving to an array of file metadata objects.
871
- * @throws {Error} If not implemented by the storage backend.
872
- * @remarks Storage implementations must override this method.
873
- */
874
- async list(_limit = 1e3) {
875
- return this.instrumentOperation("list", async () => {
876
- throw new Error("Not implemented");
877
- });
878
- }
879
- /**
880
- * Updates file metadata with user-provided key-value pairs.
881
- * @param query File query containing the file ID to update.
882
- * @param query.id File ID to update.
883
- * @param metadata Partial file object containing fields to update.
884
- * @returns Promise resolving to the updated file object.
885
- * @throws {UploadError} If the file cannot be found (ERRORS.FILE_NOT_FOUND).
886
- * @remarks
887
- * Supports TTL (time-to-live) option: if metadata contains a 'ttl' field,
888
- * it will be converted to an 'expiredAt' timestamp.
889
- * TTL can be a number (milliseconds) or string (e.g., "1h", "30m", "7d").
890
- */
891
- async update({ id }, metadata) {
892
- return this.instrumentOperation("update", async () => {
893
- const file = await this.getMeta(id);
894
- const processedMetadata = { ...metadata };
895
- if ("ttl" in processedMetadata && processedMetadata.ttl !== void 0) {
896
- const ttlValue = processedMetadata.ttl;
897
- const ttlMs = typeof ttlValue === "string" ? toMilliseconds(ttlValue) : ttlValue;
898
- if (ttlMs !== void 0) {
899
- processedMetadata.expiredAt = Date.now() + ttlMs;
900
- }
901
- delete processedMetadata.ttl;
902
- }
903
- updateMetadata(file, processedMetadata);
904
- await this.saveMeta(file);
905
- const updatedFile = { ...file, status: "updated" };
906
- await this.onUpdate(updatedFile);
907
- return updatedFile;
908
- });
909
- }
910
- /**
911
- * Deletes multiple files in a single batch operation.
912
- * @param ids Array of file IDs to delete.
913
- * @returns Promise resolving to batch operation response with successful and failed deletions.
914
- * @remarks
915
- * Processes all deletions in parallel using Promise.allSettled.
916
- * Individual failures do not stop the batch operation.
917
- * Each deletion is wrapped in error handling to capture failures.
918
- * Metrics are recorded for the batch operation and individual failures.
919
- * Returns both successful and failed operations with detailed error information.
920
- */
921
- async deleteBatch(ids) {
922
- const startTime = Date.now();
923
- const storageType = this.constructor.name.toLowerCase().replace("storage", "");
924
- this.metrics.increment("storage.operations.batch.delete.count", 1, {
925
- batch_size: ids.length,
926
- storage: storageType
927
- });
928
- const successful = [];
929
- const failed = [];
930
- const deletePromises = ids.map(async (id) => {
931
- try {
932
- const file = await this.delete({ id });
933
- return { file, id, success: true };
934
- } catch (error) {
935
- const errorMessage = error instanceof Error ? error.message : "Delete failed";
936
- this.metrics.increment("storage.operations.delete.error.count", 1, {
937
- error: errorMessage,
938
- storage: storageType
939
- });
940
- return { error: errorMessage, id, success: false };
941
- }
942
- });
943
- const results = await Promise.allSettled(deletePromises);
944
- for (const result of results) {
945
- if (result.status === "fulfilled") {
946
- if (result.value.success) {
947
- successful.push(result.value.file);
948
- } else {
949
- failed.push({ error: result.value.error, id: result.value.id });
950
- }
951
- } else {
952
- failed.push({ error: result.reason?.message || "Delete failed", id: "" });
953
- }
954
- }
955
- const duration = Date.now() - startTime;
956
- this.metrics.timing("storage.operations.batch.delete.duration", duration, {
957
- batch_size: ids.length,
958
- storage: storageType
959
- });
960
- this.metrics.gauge("storage.operations.batch.delete.success_count", successful.length, {
961
- storage: storageType
962
- });
963
- this.metrics.gauge("storage.operations.batch.delete.failed_count", failed.length, {
964
- storage: storageType
965
- });
966
- return {
967
- failed,
968
- failedCount: failed.length,
969
- successful,
970
- successfulCount: successful.length
971
- };
972
- }
973
- /**
974
- * Copies multiple files in a single batch operation.
975
- * @param operations Array of copy operations, each containing:
976
- * source: Source file ID.
977
- * destination: Destination file ID or path.
978
- * options: Optional copy options including storage class.
979
- * @returns Promise resolving to batch operation response with successful and failed copies.
980
- * @remarks
981
- * Processes all copies in parallel using Promise.allSettled.
982
- * Individual failures do not stop the batch operation.
983
- * Each copy operation is wrapped in error handling to capture failures.
984
- * Metrics are recorded for the batch operation and individual failures.
985
- * Returns both successful and failed operations with detailed error information.
986
- */
987
- async copyBatch(operations) {
988
- const startTime = Date.now();
989
- const storageType = this.constructor.name.toLowerCase().replace("storage", "");
990
- this.metrics.increment("storage.operations.batch.copy.count", 1, {
991
- batch_size: operations.length,
992
- storage: storageType
993
- });
994
- const successful = [];
995
- const failed = [];
996
- const copyPromises = operations.map(async ({ destination, options, source }) => {
997
- try {
998
- const copiedFile = await this.copy(source, destination, options);
999
- return { file: copiedFile, id: destination, success: true };
1000
- } catch (error) {
1001
- const errorMessage = error instanceof Error ? error.message : "Copy failed";
1002
- this.metrics.increment("storage.operations.copy.error.count", 1, {
1003
- error: errorMessage,
1004
- storage: storageType
1005
- });
1006
- return { error: errorMessage, id: destination, success: false };
1007
- }
1008
- });
1009
- const results = await Promise.allSettled(copyPromises);
1010
- for (const result of results) {
1011
- if (result.status === "fulfilled") {
1012
- if (result.value.success && result.value.file) {
1013
- successful.push(result.value.file);
1014
- } else {
1015
- failed.push({ error: result.value.error || "Copy failed", id: result.value.id });
1016
- }
1017
- } else {
1018
- failed.push({ error: result.reason?.message || "Copy failed", id: "" });
1019
- }
1020
- }
1021
- const duration = Date.now() - startTime;
1022
- this.metrics.timing("storage.operations.batch.copy.duration", duration, {
1023
- batch_size: operations.length,
1024
- storage: storageType
1025
- });
1026
- this.metrics.gauge("storage.operations.batch.copy.success_count", successful.length, {
1027
- storage: storageType
1028
- });
1029
- this.metrics.gauge("storage.operations.batch.copy.failed_count", failed.length, {
1030
- storage: storageType
1031
- });
1032
- return {
1033
- failed,
1034
- failedCount: failed.length,
1035
- successful,
1036
- successfulCount: successful.length
1037
- };
1038
- }
1039
- /**
1040
- * Moves multiple files in a single batch operation.
1041
- * @param operations Array of move operations, each containing:
1042
- * source: Source file ID.
1043
- * destination: Destination file ID or path.
1044
- * @returns Promise resolving to batch operation response with successful and failed moves.
1045
- * @remarks
1046
- * Processes all moves in parallel using Promise.allSettled.
1047
- * Individual failures do not stop the batch operation.
1048
- * Each move operation is wrapped in error handling to capture failures.
1049
- * Metrics are recorded for the batch operation and individual failures.
1050
- * Returns both successful and failed operations with detailed error information.
1051
- */
1052
- async moveBatch(operations) {
1053
- const startTime = Date.now();
1054
- const storageType = this.constructor.name.toLowerCase().replace("storage", "");
1055
- this.metrics.increment("storage.operations.batch.move.count", 1, {
1056
- batch_size: operations.length,
1057
- storage: storageType
1058
- });
1059
- const successful = [];
1060
- const failed = [];
1061
- const movePromises = operations.map(async ({ destination, source }) => {
1062
- try {
1063
- const movedFile = await this.move(source, destination);
1064
- return { file: movedFile, id: destination, success: true };
1065
- } catch (error) {
1066
- const errorMessage = error instanceof Error ? error.message : "Move failed";
1067
- this.metrics.increment("storage.operations.move.error.count", 1, {
1068
- error: errorMessage,
1069
- storage: storageType
1070
- });
1071
- return { error: errorMessage, id: destination, success: false };
1072
- }
1073
- });
1074
- const results = await Promise.allSettled(movePromises);
1075
- for (const result of results) {
1076
- if (result.status === "fulfilled") {
1077
- if (result.value.success && result.value.file) {
1078
- successful.push(result.value.file);
1079
- } else {
1080
- failed.push({ error: result.value.error || "Move failed", id: result.value.id });
1081
- }
1082
- } else {
1083
- failed.push({ error: result.reason?.message || "Move failed", id: result.reason?.id || "" });
1084
- }
1085
- }
1086
- const duration = Date.now() - startTime;
1087
- this.metrics.timing("storage.operations.batch.move.duration", duration, {
1088
- batch_size: operations.length,
1089
- storage: storageType
1090
- });
1091
- this.metrics.gauge("storage.operations.batch.move.success_count", successful.length, {
1092
- storage: storageType
1093
- });
1094
- this.metrics.gauge("storage.operations.batch.move.failed_count", failed.length, {
1095
- storage: storageType
1096
- });
1097
- return {
1098
- failed,
1099
- failedCount: failed.length,
1100
- successful,
1101
- successfulCount: successful.length
1102
- };
1103
- }
1104
- /**
1105
- * Prevent upload from being accessed by multiple requests
1106
- */
1107
- async lock(key) {
1108
- const activeUploads = [...this.locker.keys()];
1109
- if (activeUploads.includes(key)) {
1110
- return throwErrorCode(ERRORS.FILE_LOCKED);
1111
- }
1112
- if (this.config.concurrency && typeof this.config.concurrency === "number" && this.config.concurrency < activeUploads.length) {
1113
- return throwErrorCode(ERRORS.STORAGE_BUSY);
1114
- }
1115
- this.locker.set(key, key);
1116
- return key;
1117
- }
1118
- async unlock(key) {
1119
- this.locker.unlock(key);
1120
- }
1121
- isUnsupportedChecksum(algorithm = "") {
1122
- return !!algorithm && !this.checksumTypes.includes(algorithm);
1123
- }
1124
- startAutoPurge(purgeInterval) {
1125
- if (purgeInterval >= 2147483647) {
1126
- throw new Error("“purgeInterval” must be less than 2147483647 ms");
1127
- }
1128
- setInterval(() => {
1129
- this.purge().catch((error) => this.logger?.error(error));
1130
- }, purgeInterval);
1131
- }
1132
- updateTimestamps(file) {
1133
- file.createdAt ??= (/* @__PURE__ */ new Date()).toISOString();
1134
- const maxAgeMs = toMilliseconds(this.expiration?.maxAge);
1135
- if (maxAgeMs && !file.expiredAt) {
1136
- file.expiredAt = this.expiration?.rolling ? Date.now() + maxAgeMs : +new Date(file.createdAt) + maxAgeMs;
1137
- }
1138
- return file;
1139
- }
1140
- /**
1141
- * Instruments a storage operation with metrics and error tracking.
1142
- * @param operation Operation name (e.g., "create", "delete", "copy").
1143
- * @param function_ The operation function to execute.
1144
- * @param attributes Additional attributes to include in metrics.
1145
- * @returns Promise resolving to the operation result.
1146
- * @throws Re-throws any errors from the operation function.
1147
- * @remarks
1148
- * Records operation count, duration, and error metrics.
1149
- * Tracks file sizes for operations returning file objects.
1150
- * Error metrics include error messages for debugging.
1151
- * All public methods should use this wrapper for consistent instrumentation.
1152
- */
1153
- async instrumentOperation(operation, function_, attributes) {
1154
- const startTime = Date.now();
1155
- const storageType = this.constructor.name.toLowerCase().replace("storage", "");
1156
- const baseAttributes = {
1157
- storage: storageType,
1158
- ...attributes
1159
- };
1160
- try {
1161
- this.metrics.increment(`storage.operations.${operation}.count`, 1, baseAttributes);
1162
- const result = await function_();
1163
- const duration = Date.now() - startTime;
1164
- this.metrics.timing(`storage.operations.${operation}.duration`, duration, baseAttributes);
1165
- if (result && typeof result === "object" && "size" in result && typeof result.size === "number") {
1166
- this.metrics.gauge("storage.files.size", result.size, {
1167
- ...baseAttributes,
1168
- operation
1169
- });
1170
- }
1171
- return result;
1172
- } catch (error) {
1173
- const duration = Date.now() - startTime;
1174
- const errorMessage = error instanceof Error ? error.message : "Unknown error";
1175
- this.metrics.timing(`storage.operations.${operation}.duration`, duration, {
1176
- ...baseAttributes,
1177
- error: "true"
1178
- });
1179
- this.metrics.increment(`storage.operations.${operation}.error.count`, 1, {
1180
- ...baseAttributes,
1181
- error: errorMessage
1182
- });
1183
- throw error;
1184
- }
1185
- }
1186
- }
1187
-
1188
- export { BaseStorage as B, defaultFilesystemFileNameValidation as a, parseBytes as b, defaultCloudStorageFileNameValidation as d, parseDuration as p, toMilliseconds as t, updateMetadata as u };