@visulima/storage 1.0.0-alpha.1 → 1.0.0-alpha.11
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.
- package/CHANGELOG.md +170 -0
- package/LICENSE.md +6 -6
- package/README.md +33 -14
- package/dist/adapter/nuxt/module.d.ts +39 -0
- package/dist/adapter/nuxt/module.js +93 -0
- package/dist/adapter/nuxt/package.json +20 -0
- package/dist/handler/base/base-handler-core.d.ts +91 -0
- package/dist/handler/base/base-handler-fetch.d.ts +76 -0
- package/dist/handler/base/base-handler-node.d.ts +137 -0
- package/dist/handler/http/fetch/index.d.ts +4 -0
- package/dist/handler/http/fetch/index.js +4 -0
- package/dist/handler/http/hono/index.d.ts +67 -0
- package/dist/handler/http/hono/index.js +65 -0
- package/dist/handler/http/nextjs/index.d.ts +61 -0
- package/dist/handler/http/nextjs/index.js +37 -0
- package/dist/handler/http/node/index.d.ts +4 -0
- package/dist/handler/http/node/index.js +8 -0
- package/dist/handler/http/solid-start/index.d.ts +60 -0
- package/dist/handler/http/solid-start/index.js +44 -0
- package/dist/handler/multipart/multipart-base.d.ts +84 -0
- package/dist/handler/multipart/multipart-fetch.d.ts +64 -0
- package/dist/handler/multipart/multipart.d.ts +49 -0
- package/dist/handler/rest/rest-base.d.ts +106 -0
- package/dist/handler/rest/rest-fetch.d.ts +88 -0
- package/dist/handler/rest/rest.d.ts +93 -0
- package/dist/handler/tus/tus-base.d.ts +152 -0
- package/dist/handler/tus/tus-fetch.d.ts +72 -0
- package/dist/handler/tus/tus.d.ts +78 -0
- package/dist/handler/types.d.ts +53 -0
- package/dist/handler/utils/request-parser.d.ts +72 -0
- package/dist/handler/utils/response-builder.d.ts +83 -0
- package/dist/handler/utils/storage-utils.d.ts +10 -0
- package/dist/handler/utils/stream-utils.d.ts +29 -0
- package/dist/handler/utils/upload-handlers.d.ts +76 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +13 -0
- package/dist/metrics/index.d.ts +2 -0
- package/dist/metrics/no-op-metrics.d.ts +15 -0
- package/dist/metrics/opentelemetry-metrics.d.ts +55 -0
- package/dist/openapi/index.d.ts +5 -0
- package/dist/openapi/index.js +5 -0
- package/dist/openapi/rest.d.ts +7 -0
- package/dist/openapi/shared.d.ts +13 -0
- package/dist/openapi/transform.d.ts +3 -0
- package/dist/openapi/tus.d.ts +7 -0
- package/dist/openapi/xhr.d.ts +7 -0
- package/dist/packem_shared/AwsLightFile-tTneXZgG.js +11 -0
- package/dist/packem_shared/AwsLightMetaStorage-D9qWQt6q.js +4 -0
- package/dist/packem_shared/AwsLightStorage-CEJTYBVv.js +133 -0
- package/dist/packem_shared/AzureFile-CesgFzps.js +8 -0
- package/dist/packem_shared/AzureSMetaStorage-BEbXX9Uk.js +89 -0
- package/dist/packem_shared/AzureStorage-CH6nvQx8.js +357 -0
- package/dist/packem_shared/BaseTransformer-9965D4pb.js +83 -0
- package/dist/packem_shared/DiskStorage-DziI2mwX.js +12 -0
- package/dist/packem_shared/DiskStorageWithChecksum-B_XY8_cO.js +240 -0
- package/dist/packem_shared/ERRORS-D0apMqnc.js +97 -0
- package/dist/packem_shared/File-Bb3P23dr.js +69 -0
- package/dist/packem_shared/GCSConfig-vPP22kN6.js +11 -0
- package/dist/packem_shared/GCSFile-BIEunhAN.js +8 -0
- package/dist/packem_shared/GCSMetaStorage-FypK8Tn1.js +7 -0
- package/dist/packem_shared/GCStorage-CasTo9rF.js +408 -0
- package/dist/packem_shared/LocalMetaStorage-BnllcjV9.js +7 -0
- package/dist/packem_shared/MediaTransformer-BErvw2vE.js +1196 -0
- package/dist/packem_shared/MetaStorage-pECeFOad.js +46 -0
- package/dist/packem_shared/Metadata-DRLXeJ0F.js +89 -0
- package/dist/packem_shared/Multipart-CDDlDOs7.js +163 -0
- package/dist/packem_shared/Multipart-vxBL2VXm.js +128 -0
- package/dist/packem_shared/NetlifyBlobFile-CXzyjqrD.js +14 -0
- package/dist/packem_shared/NetlifyBlobMetaStorage-DIkZ2umW.js +9 -0
- package/dist/packem_shared/NetlifyBlobStorage-DEpx_4q-.js +376 -0
- package/dist/packem_shared/NoOpMetrics-DhAk5rXc.js +10 -0
- package/dist/packem_shared/OpenTelemetryMetrics-BnxhqIaH.js +67 -0
- package/dist/packem_shared/Rest-CEj3EhMj.js +228 -0
- package/dist/packem_shared/Rest-D14vM7aJ.js +267 -0
- package/dist/packem_shared/S3File-DZiyk9Qt.js +11 -0
- package/dist/packem_shared/S3MetaStorage-yT4CznlV.js +77 -0
- package/dist/packem_shared/S3Storage-B0scgwPa.js +317 -0
- package/dist/packem_shared/TUS_RESUMABLE-Dtfsffnj.js +434 -0
- package/dist/packem_shared/Tus-Cjz87bpz.js +200 -0
- package/dist/packem_shared/Tus-WOVg-YnB.js +195 -0
- package/dist/packem_shared/ValidationError-BfF1aE4h.js +26 -0
- package/dist/packem_shared/VercelBlobFile-BCg4aTEq.js +18 -0
- package/dist/packem_shared/VercelBlobMetaStorage-c0M-TvAR.js +9 -0
- package/dist/packem_shared/VercelBlobStorage-wOYxv8F7.js +261 -0
- package/dist/packem_shared/_commonjsHelpers-BqLXS_qQ.js +5 -0
- package/dist/packem_shared/aws-light-meta-storage-Ba2b_h-o.js +447 -0
- package/dist/packem_shared/backblaze-BlMnIcBC.js +20 -0
- package/dist/packem_shared/base-handler-core-BoAqTyz7.js +303 -0
- package/dist/packem_shared/base-handler-fetch-CinvR65S.js +291 -0
- package/dist/packem_shared/base-handler-node-c02H0AxN.js +731 -0
- package/dist/packem_shared/cache-B88MXQ_2.js +18 -0
- package/dist/packem_shared/cloudflare-Bi1q8wXE.js +21 -0
- package/dist/packem_shared/defaultCloudStorageFileNameValidation-CGoYrGB2.js +11 -0
- package/dist/packem_shared/digitalOcean-CWQRJM3L.js +21 -0
- package/dist/packem_shared/disk-storage-DeivH-J5.js +816 -0
- package/dist/packem_shared/gcs-meta-storage-B0Baj0Xr.js +159 -0
- package/dist/packem_shared/has-content-CY66ehMK.js +3 -0
- package/dist/packem_shared/headers-YLRp1dLo.js +1318 -0
- package/dist/packem_shared/is-expired-CTThU1q5.js +8 -0
- package/dist/packem_shared/isRetryableError-Dycp7127.js +82 -0
- package/dist/packem_shared/isValidMediaType-BeDgiObq.js +35 -0
- package/dist/packem_shared/local-meta-storage-BlOk6m7F.js +647 -0
- package/dist/packem_shared/minio-C2YBZQOw.js +22 -0
- package/dist/packem_shared/multipart-base-C994naBi.js +102 -0
- package/dist/packem_shared/part-match-BMNqHDYD.js +96 -0
- package/dist/packem_shared/path-aV9lMdcn-DMU5H1-h.js +162 -0
- package/dist/packem_shared/response-builder-BtnRiBUI.js +45 -0
- package/dist/packem_shared/rest-base-D9URIFkc.js +457 -0
- package/dist/packem_shared/restOpenApiSpec-D1zbmid7.js +800 -0
- package/dist/packem_shared/s3-base-storage-CG21uusd.js +513 -0
- package/dist/packem_shared/sharedGet-DAyJfjhl.js +1018 -0
- package/dist/packem_shared/storage-CpJpqLdn.js +1184 -0
- package/dist/packem_shared/tigris-Hac8TKnX.js +21 -0
- package/dist/packem_shared/transformOpenApiSpec-BC0r6PzI.js +975 -0
- package/dist/packem_shared/tusOpenApiSpec-BLIjKf0s.js +740 -0
- package/dist/packem_shared/update-size-CCGm6i1J.js +8 -0
- package/dist/packem_shared/validator-DuO17wFF.js +79 -0
- package/dist/packem_shared/waitForStorage-Cscw85sx.js +18 -0
- package/dist/packem_shared/wasabi-DIyflHSd.js +21 -0
- package/dist/packem_shared/xhrOpenApiSpec-kItrSAsc.js +264 -0
- package/dist/storage/aws/clients/backblaze.d.ts +12 -0
- package/dist/storage/aws/clients/cloudflare.d.ts +13 -0
- package/dist/storage/aws/clients/digital-ocean.d.ts +12 -0
- package/dist/storage/aws/clients/index.d.ts +6 -0
- package/dist/storage/aws/clients/index.js +6 -0
- package/dist/storage/aws/clients/minio.d.ts +13 -0
- package/dist/storage/aws/clients/tigris.d.ts +12 -0
- package/dist/storage/aws/clients/types.d.ts +95 -0
- package/dist/storage/aws/clients/wasabi.d.ts +12 -0
- package/dist/storage/aws/index.d.ts +4 -0
- package/dist/storage/aws/index.js +3 -0
- package/dist/storage/aws/s3-base-storage.d.ts +248 -0
- package/dist/storage/aws/s3-client-adapter.d.ts +110 -0
- package/dist/storage/aws/s3-file.d.ts +10 -0
- package/dist/storage/aws/s3-meta-storage.d.ts +15 -0
- package/dist/storage/aws/s3-storage.d.ts +70 -0
- package/dist/storage/aws/types.d.ts +119 -0
- package/dist/storage/aws-light/aws-light-api-adapter.d.ts +130 -0
- package/dist/storage/aws-light/aws-light-file.d.ts +10 -0
- package/dist/storage/aws-light/aws-light-meta-storage.d.ts +19 -0
- package/dist/storage/aws-light/aws-light-storage.d.ts +65 -0
- package/dist/storage/aws-light/index.d.ts +4 -0
- package/dist/storage/aws-light/index.js +3 -0
- package/dist/storage/aws-light/types.d.ts +37 -0
- package/dist/storage/azure/azure-file.d.ts +6 -0
- package/dist/storage/azure/azure-meta-storage.d.ts +15 -0
- package/dist/storage/azure/azure-storage.d.ts +69 -0
- package/dist/storage/azure/index.d.ts +4 -0
- package/dist/storage/azure/index.js +3 -0
- package/dist/storage/azure/types.d.ts +62 -0
- package/dist/storage/gcs/fetch-error.d.ts +11 -0
- package/dist/storage/gcs/gcs-config.d.ts +2 -0
- package/dist/storage/gcs/gcs-file.d.ts +6 -0
- package/dist/storage/gcs/gcs-meta-storage.d.ts +27 -0
- package/dist/storage/gcs/gcs-storage.d.ts +91 -0
- package/dist/storage/gcs/index.d.ts +5 -0
- package/dist/storage/gcs/index.js +4 -0
- package/dist/storage/gcs/types.d.ts +55 -0
- package/dist/storage/gcs/utils.d.ts +7 -0
- package/dist/storage/local/disk-storage-with-checksum.d.ts +15 -0
- package/dist/storage/local/disk-storage.d.ts +135 -0
- package/dist/storage/local/index.d.ts +4 -0
- package/dist/storage/local/index.js +3 -0
- package/dist/storage/local/local-meta-storage.d.ts +32 -0
- package/dist/storage/meta-storage.d.ts +30 -0
- package/dist/storage/netlify-blob/index.d.ts +4 -0
- package/dist/storage/netlify-blob/index.js +3 -0
- package/dist/storage/netlify-blob/netlify-blob-file.d.ts +12 -0
- package/dist/storage/netlify-blob/netlify-blob-meta-storage.d.ts +7 -0
- package/dist/storage/netlify-blob/netlify-blob-storage.d.ts +93 -0
- package/dist/storage/netlify-blob/types.d.ts +30 -0
- package/dist/storage/storage.d.ts +353 -0
- package/dist/storage/types.d.ts +221 -0
- package/dist/storage/utils/file/file.d.ts +25 -0
- package/dist/storage/utils/file/get-file-status.d.ts +9 -0
- package/dist/storage/utils/file/has-content.d.ts +9 -0
- package/dist/storage/utils/file/index.d.ts +10 -0
- package/dist/storage/utils/file/is-expired.d.ts +8 -0
- package/dist/storage/utils/file/metadata.d.ts +18 -0
- package/dist/storage/utils/file/part-match.d.ts +11 -0
- package/dist/storage/utils/file/types.d.ts +37 -0
- package/dist/storage/utils/file/update-metadata.d.ts +10 -0
- package/dist/storage/utils/file/update-size.d.ts +10 -0
- package/dist/storage/vercel-blob/index.d.ts +4 -0
- package/dist/storage/vercel-blob/index.js +3 -0
- package/dist/storage/vercel-blob/types.d.ts +32 -0
- package/dist/storage/vercel-blob/vercel-blob-file.d.ts +16 -0
- package/dist/storage/vercel-blob/vercel-blob-meta-storage.d.ts +7 -0
- package/dist/storage/vercel-blob/vercel-blob-storage.d.ts +76 -0
- package/dist/transformer/audio-transformer.d.ts +128 -0
- package/dist/transformer/audio-transformer.js +278 -0
- package/dist/transformer/base-transformer.d.ts +63 -0
- package/dist/transformer/image-transformer.d.ts +571 -0
- package/dist/transformer/image-transformer.js +1100 -0
- package/dist/transformer/index.d.ts +5 -0
- package/dist/transformer/index.js +4 -0
- package/dist/transformer/media-transformer.d.ts +332 -0
- package/dist/transformer/types.d.ts +652 -0
- package/dist/transformer/utils.d.ts +33 -0
- package/dist/transformer/validation-error.d.ts +23 -0
- package/dist/transformer/video-transformer.d.ts +145 -0
- package/dist/transformer/video-transformer.js +310 -0
- package/dist/utils/cache.d.ts +32 -0
- package/dist/utils/chunked-upload.d.ts +65 -0
- package/dist/utils/detect-file-type.d.ts +28 -0
- package/dist/utils/errors.d.ts +74 -0
- package/dist/utils/file-path-url-matcher.d.ts +18 -0
- package/dist/utils/headers.d.ts +111 -0
- package/dist/utils/http.d.ts +92 -0
- package/dist/utils/locker.d.ts +26 -0
- package/dist/utils/pipes/stream-checksum.d.ts +47 -0
- package/dist/utils/pipes/stream-length.d.ts +22 -0
- package/dist/utils/primitives/get-last-one.d.ts +3 -0
- package/dist/utils/primitives/is-record.d.ts +2 -0
- package/dist/utils/primitives/map-values.d.ts +9 -0
- package/dist/utils/primitives/pick.d.ts +2 -0
- package/dist/utils/primitives/to-milliseconds.d.ts +12 -0
- package/dist/utils/primitives/to-seconds.d.ts +12 -0
- package/dist/utils/range-checksum.d.ts +33 -0
- package/dist/utils/range-hasher.d.ts +46 -0
- package/dist/utils/retry.d.ts +64 -0
- package/dist/utils/types.d.ts +108 -0
- package/dist/utils/validation-error.d.ts +22 -0
- package/dist/utils/validator.d.ts +36 -0
- package/package.json +9 -9
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { createReadStream, createWriteStream } from 'node:fs';
|
|
2
|
+
import { truncate } from 'node:fs/promises';
|
|
3
|
+
import { Transform, pipeline } from 'node:stream';
|
|
4
|
+
import { p as partMatch, d as detectFileTypeFromStream, g as getFileStatus } from './part-match-BMNqHDYD.js';
|
|
5
|
+
import { throwErrorCode, ERRORS } from './ERRORS-D0apMqnc.js';
|
|
6
|
+
import { D as DiskStorage, e as ensureFile, S as StreamLength, s as streamChecksum } from './disk-storage-DeivH-J5.js';
|
|
7
|
+
import { LRUCache } from 'lru-cache';
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { r as remove } from './local-meta-storage-BlOk6m7F.js';
|
|
10
|
+
import { u as updateSize } from './update-size-CCGm6i1J.js';
|
|
11
|
+
import { h as hasContent } from './has-content-CY66ehMK.js';
|
|
12
|
+
|
|
13
|
+
class RangeChecksum extends Transform {
|
|
14
|
+
/**
|
|
15
|
+
* Creates a new RangeChecksum transform stream.
|
|
16
|
+
* @param hashes Range hasher instance managing multiple file hashes
|
|
17
|
+
* @param path File path identifier for this checksum operation
|
|
18
|
+
*/
|
|
19
|
+
constructor(hashes, path) {
|
|
20
|
+
super();
|
|
21
|
+
this.path = path;
|
|
22
|
+
this.hashes = hashes;
|
|
23
|
+
this.hash = this.hashes.get(path) || createHash(this.hashes.algorithm);
|
|
24
|
+
this.hashCopy = this.hash.copy();
|
|
25
|
+
}
|
|
26
|
+
path;
|
|
27
|
+
hash;
|
|
28
|
+
hashCopy;
|
|
29
|
+
hashes;
|
|
30
|
+
/**
|
|
31
|
+
* Resets the hash state to its initial value.
|
|
32
|
+
* Useful for reusing the checksum calculator for multiple operations.
|
|
33
|
+
*/
|
|
34
|
+
reset() {
|
|
35
|
+
this.hashes.set(this.path, this.hashCopy);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Returns the current hash digest in the specified encoding.
|
|
39
|
+
* @param encoding Output encoding format (default: 'hex')
|
|
40
|
+
* @returns Hash digest as a string
|
|
41
|
+
*/
|
|
42
|
+
digest(encoding = "hex") {
|
|
43
|
+
return this.hash.copy().digest(encoding);
|
|
44
|
+
}
|
|
45
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
46
|
+
_transform(chunk, _encoding, done) {
|
|
47
|
+
this.push(chunk);
|
|
48
|
+
this.hash.update(chunk);
|
|
49
|
+
done();
|
|
50
|
+
}
|
|
51
|
+
// eslint-disable-next-line class-methods-use-this,no-underscore-dangle
|
|
52
|
+
_flush(callback) {
|
|
53
|
+
callback();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
class RangeHasher extends LRUCache {
|
|
58
|
+
/**
|
|
59
|
+
* Creates a new RangeHasher instance.
|
|
60
|
+
* @param algorithm Hash algorithm to use (default: 'sha1')
|
|
61
|
+
* @param options LRU cache configuration options
|
|
62
|
+
*/
|
|
63
|
+
constructor(algorithm = "sha1", options) {
|
|
64
|
+
super({
|
|
65
|
+
ttl: 1e3,
|
|
66
|
+
ttlAutopurge: true,
|
|
67
|
+
...options
|
|
68
|
+
});
|
|
69
|
+
this.algorithm = algorithm;
|
|
70
|
+
}
|
|
71
|
+
algorithm;
|
|
72
|
+
/**
|
|
73
|
+
* Returns the hex-encoded digest for a previously initialized path.
|
|
74
|
+
* @param path File path identifier
|
|
75
|
+
* @returns Hex-encoded hash digest, or empty string if not found
|
|
76
|
+
*/
|
|
77
|
+
hex(path) {
|
|
78
|
+
return this.get(path)?.copy().digest("hex") || "";
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Returns the base64-encoded digest for a previously initialized path.
|
|
82
|
+
* @param path File path identifier
|
|
83
|
+
* @returns Base64-encoded hash digest, or empty string if not found
|
|
84
|
+
*/
|
|
85
|
+
base64(path) {
|
|
86
|
+
return this.get(path)?.copy().digest("base64") || "";
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Initializes or continues a hasher for a file from the specified offset.
|
|
90
|
+
* Returns cached hash if available, otherwise calculates from filesystem.
|
|
91
|
+
* @param path File path to hash
|
|
92
|
+
* @param start Starting offset in bytes (default: 0)
|
|
93
|
+
* @returns Promise resolving to the hash instance
|
|
94
|
+
*/
|
|
95
|
+
async init(path, start = 0) {
|
|
96
|
+
return this.get(path)?.copy() || this.updateFromFs(path, start);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Creates a transform stream that updates the rolling hash for a file path.
|
|
100
|
+
* Useful for incremental hashing during streaming operations.
|
|
101
|
+
* @param path File path identifier for hash tracking
|
|
102
|
+
* @returns RangeChecksum transform stream instance
|
|
103
|
+
*/
|
|
104
|
+
digester(path) {
|
|
105
|
+
return new RangeChecksum(this, path);
|
|
106
|
+
}
|
|
107
|
+
async updateFromFs(path, start = 0, initial) {
|
|
108
|
+
const hash = await this.fromFs(path, start, initial);
|
|
109
|
+
this.set(path, hash);
|
|
110
|
+
return hash;
|
|
111
|
+
}
|
|
112
|
+
fromFs(path, start = 0, initial) {
|
|
113
|
+
return new Promise((resolve, reject) => {
|
|
114
|
+
const digester = this.digester(path);
|
|
115
|
+
if (initial !== void 0) {
|
|
116
|
+
digester.hash = initial;
|
|
117
|
+
}
|
|
118
|
+
createReadStream(path, { start }).on("error", reject).on("end", () => resolve(digester.hash)).pipe(digester).resume();
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
class DiskStorageWithChecksum extends DiskStorage {
|
|
124
|
+
hashes;
|
|
125
|
+
constructor(config) {
|
|
126
|
+
super(config);
|
|
127
|
+
this.hashes = new RangeHasher(config?.checksum === "sha1" ? "sha1" : "md5");
|
|
128
|
+
}
|
|
129
|
+
async delete({ id }) {
|
|
130
|
+
try {
|
|
131
|
+
const file = await this.getMeta(id);
|
|
132
|
+
const path = this.getFilePath(file.name);
|
|
133
|
+
this.hashes.delete(path);
|
|
134
|
+
await remove(path);
|
|
135
|
+
await this.deleteMeta(id);
|
|
136
|
+
const deletedFile = { ...file, status: "deleted" };
|
|
137
|
+
await this.onDelete(deletedFile);
|
|
138
|
+
return deletedFile;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
this.logger?.error("[error]: Could not delete file: %O", error);
|
|
141
|
+
const httpError = this.normalizeError(error instanceof Error ? error : new Error(String(error)));
|
|
142
|
+
await this.onError(httpError);
|
|
143
|
+
}
|
|
144
|
+
return { id };
|
|
145
|
+
}
|
|
146
|
+
async write(part) {
|
|
147
|
+
let file;
|
|
148
|
+
if ("contentType" in part && "metadata" in part && !("body" in part) && !("start" in part)) {
|
|
149
|
+
file = part;
|
|
150
|
+
} else {
|
|
151
|
+
file = await this.getMeta(part.id);
|
|
152
|
+
await this.checkIfExpired(file);
|
|
153
|
+
}
|
|
154
|
+
if (file.status === "completed") {
|
|
155
|
+
return file;
|
|
156
|
+
}
|
|
157
|
+
if (part.size !== void 0) {
|
|
158
|
+
updateSize(file, part.size);
|
|
159
|
+
}
|
|
160
|
+
if (!partMatch(part, file)) {
|
|
161
|
+
return throwErrorCode(ERRORS.FILE_CONFLICT);
|
|
162
|
+
}
|
|
163
|
+
const path = this.getFilePath(file.name);
|
|
164
|
+
try {
|
|
165
|
+
const startPosition = part.start || 0;
|
|
166
|
+
await ensureFile(path);
|
|
167
|
+
if (file.bytesWritten === 0 || Number.isNaN(file.bytesWritten)) {
|
|
168
|
+
file.bytesWritten = startPosition;
|
|
169
|
+
}
|
|
170
|
+
await this.hashes.init(path);
|
|
171
|
+
if (hasContent(part)) {
|
|
172
|
+
if (this.isUnsupportedChecksum(part.checksumAlgorithm)) {
|
|
173
|
+
return throwErrorCode(ERRORS.UNSUPPORTED_CHECKSUM_ALGORITHM);
|
|
174
|
+
}
|
|
175
|
+
const isFirstChunk = part.start === 0 || part.start === void 0;
|
|
176
|
+
if (isFirstChunk && (file.bytesWritten === 0 || Number.isNaN(file.bytesWritten)) && (!file.contentType || file.contentType === "application/octet-stream")) {
|
|
177
|
+
try {
|
|
178
|
+
const { fileType, stream: detectedStream } = await detectFileTypeFromStream(part.body);
|
|
179
|
+
if (fileType?.mime) {
|
|
180
|
+
file.contentType = fileType.mime;
|
|
181
|
+
}
|
|
182
|
+
part.body = detectedStream;
|
|
183
|
+
} catch {
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const [bytesWritten, errorCode] = await this.lazyWrite({ ...part, ...file });
|
|
187
|
+
if (errorCode) {
|
|
188
|
+
await truncate(path, file.bytesWritten);
|
|
189
|
+
return throwErrorCode(errorCode);
|
|
190
|
+
}
|
|
191
|
+
if (typeof bytesWritten !== "number") {
|
|
192
|
+
await this.hashes.updateFromFs(path, file.bytesWritten);
|
|
193
|
+
}
|
|
194
|
+
file.bytesWritten = bytesWritten;
|
|
195
|
+
}
|
|
196
|
+
file.status = getFileStatus(file);
|
|
197
|
+
file.hash = {
|
|
198
|
+
algorithm: this.hashes.algorithm,
|
|
199
|
+
value: this.hashes.hex(path)
|
|
200
|
+
};
|
|
201
|
+
if (file.status === "completed") {
|
|
202
|
+
this.hashes.delete(path);
|
|
203
|
+
}
|
|
204
|
+
await this.saveMeta(file);
|
|
205
|
+
return file;
|
|
206
|
+
} catch (error) {
|
|
207
|
+
await this.hashes.updateFromFs(path, file.bytesWritten);
|
|
208
|
+
const httpError = this.normalizeError(error instanceof Error ? error : new Error(String(error)));
|
|
209
|
+
await this.onError(httpError);
|
|
210
|
+
return throwErrorCode(ERRORS.FILE_ERROR, (typeof httpError.message === "string" ? httpError.message : String(error)) || String(error));
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
lazyWrite(part) {
|
|
214
|
+
return new Promise((resolve, reject) => {
|
|
215
|
+
const path = this.getFilePath(part.name);
|
|
216
|
+
const destination = createWriteStream(path, { flags: "r+", start: part.start });
|
|
217
|
+
const lengthChecker = new StreamLength(part.contentLength || Number(part.size) - part.start);
|
|
218
|
+
const checksumChecker = streamChecksum(part.checksum, part.checksumAlgorithm);
|
|
219
|
+
const digester = this.hashes.digester(path);
|
|
220
|
+
const keepPartial = !part.checksum;
|
|
221
|
+
const failWithCode = (code) => {
|
|
222
|
+
digester.reset();
|
|
223
|
+
destination.close();
|
|
224
|
+
resolve([Number.NaN, code]);
|
|
225
|
+
};
|
|
226
|
+
lengthChecker.on("error", () => failWithCode(ERRORS.FILE_CONFLICT));
|
|
227
|
+
checksumChecker.on("error", () => failWithCode(ERRORS.CHECKSUM_MISMATCH));
|
|
228
|
+
part.body.on("aborted", () => failWithCode(keepPartial ? void 0 : ERRORS.REQUEST_ABORTED));
|
|
229
|
+
pipeline(part.body, lengthChecker, checksumChecker, digester, destination, (error) => {
|
|
230
|
+
if (error) {
|
|
231
|
+
digester.reset();
|
|
232
|
+
return reject(error);
|
|
233
|
+
}
|
|
234
|
+
return resolve([part.start + destination.bytesWritten]);
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export { DiskStorageWithChecksum as default };
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
var ERRORS = /* @__PURE__ */ ((ERRORS2) => {
|
|
2
|
+
ERRORS2["BAD_REQUEST"] = "BadRequest";
|
|
3
|
+
ERRORS2["CHECKSUM_MISMATCH"] = "ChecksumMismatch";
|
|
4
|
+
ERRORS2["FILE_CONFLICT"] = "FileConflict";
|
|
5
|
+
ERRORS2["FILE_ERROR"] = "FileError";
|
|
6
|
+
ERRORS2["FILE_LOCKED"] = "FileLocked";
|
|
7
|
+
ERRORS2["FILE_NOT_ALLOWED"] = "FileNotAllowed";
|
|
8
|
+
ERRORS2["FILE_NOT_FOUND"] = "FileNotFound";
|
|
9
|
+
ERRORS2["FORBIDDEN"] = "Forbidden";
|
|
10
|
+
ERRORS2["GONE"] = "Gone";
|
|
11
|
+
ERRORS2["INVALID_FILE_NAME"] = "InvalidFileName";
|
|
12
|
+
ERRORS2["INVALID_FILE_SIZE"] = "InvalidFileSize";
|
|
13
|
+
ERRORS2["INVALID_RANGE"] = "InvalidRange";
|
|
14
|
+
ERRORS2["INVALID_TYPE"] = "Invalidtype";
|
|
15
|
+
ERRORS2["METHOD_NOT_ALLOWED"] = "MethodNotAllowed";
|
|
16
|
+
ERRORS2["REQUEST_ABORTED"] = "RequestAborted";
|
|
17
|
+
ERRORS2["REQUEST_ENTITY_TOO_LARGE"] = "RequestEntityTooLarge";
|
|
18
|
+
ERRORS2["STORAGE_BUSY"] = "StorageBusy";
|
|
19
|
+
ERRORS2["STORAGE_ERROR"] = "StorageError";
|
|
20
|
+
ERRORS2["TOO_MANY_REQUESTS"] = "TooManyRequests";
|
|
21
|
+
ERRORS2["UNKNOWN_ERROR"] = "UnknownError";
|
|
22
|
+
ERRORS2["UNPROCESSABLE_ENTITY"] = "UnprocessableEntity";
|
|
23
|
+
ERRORS2["UNSUPPORTED_CHECKSUM_ALGORITHM"] = "UnsupportedChecksumAlgorithm";
|
|
24
|
+
ERRORS2["UNSUPPORTED_MEDIA_TYPE"] = "UnsupportedMediaType";
|
|
25
|
+
return ERRORS2;
|
|
26
|
+
})(ERRORS || {});
|
|
27
|
+
const ErrorMap = (() => {
|
|
28
|
+
const errors = {
|
|
29
|
+
BadRequest: [400, "Bad request"],
|
|
30
|
+
ChecksumMismatch: [460, "Checksum mismatch"],
|
|
31
|
+
FileConflict: [409, "File conflict"],
|
|
32
|
+
FileError: [500, "Something went wrong writing the file"],
|
|
33
|
+
FileLocked: [423, "File locked"],
|
|
34
|
+
FileNotAllowed: [403, "File not allowed"],
|
|
35
|
+
FileNotFound: [404, "Not found"],
|
|
36
|
+
Forbidden: [403, "Authenticated user is not allowed access"],
|
|
37
|
+
Gone: [410, "The file for this url no longer exists"],
|
|
38
|
+
InvalidFileName: [400, "Invalid file name or it cannot be retrieved"],
|
|
39
|
+
InvalidFileSize: [400, "File size cannot be retrieved"],
|
|
40
|
+
InvalidRange: [400, "Invalid or missing content-range header"],
|
|
41
|
+
Invalidtype: [400, 'Invalid or missing "content-type" header'],
|
|
42
|
+
MethodNotAllowed: [405, "Method not allowed"],
|
|
43
|
+
RequestAborted: [499, "Request aborted"],
|
|
44
|
+
RequestEntityTooLarge: [413, "Request entity too large"],
|
|
45
|
+
StorageBusy: [503, "Storage is busy"],
|
|
46
|
+
StorageError: [503, "Storage error"],
|
|
47
|
+
TooManyRequests: [429, "Too many requests"],
|
|
48
|
+
UnknownError: [500, "Something went wrong"],
|
|
49
|
+
UnprocessableEntity: [422, "Validation failed"],
|
|
50
|
+
UnsupportedChecksumAlgorithm: [400, "Unsupported checksum algorithm"],
|
|
51
|
+
UnsupportedMediaType: [415, "Unsupported media type"]
|
|
52
|
+
};
|
|
53
|
+
const errorMap = {};
|
|
54
|
+
Object.keys(errors).forEach((code) => {
|
|
55
|
+
const [statusCode, message] = errors[code];
|
|
56
|
+
errorMap[code] = { code, message, statusCode };
|
|
57
|
+
});
|
|
58
|
+
return errorMap;
|
|
59
|
+
})();
|
|
60
|
+
class UploadError extends Error {
|
|
61
|
+
name = "UploadError";
|
|
62
|
+
/** The standardized error code from the ERRORS enum */
|
|
63
|
+
UploadErrorCode = "UnknownError" /* UNKNOWN_ERROR */;
|
|
64
|
+
/** Optional additional error details */
|
|
65
|
+
detail;
|
|
66
|
+
/**
|
|
67
|
+
* Creates a new UploadError instance.
|
|
68
|
+
* @param code Standardized error code (defaults to UNKNOWN_ERROR)
|
|
69
|
+
* @param message Human-readable error message (defaults to the code)
|
|
70
|
+
* @param detail Optional additional error details
|
|
71
|
+
*/
|
|
72
|
+
constructor(code = "UnknownError" /* UNKNOWN_ERROR */, message, detail) {
|
|
73
|
+
super(message || code);
|
|
74
|
+
this.name = "UploadError";
|
|
75
|
+
this.detail = detail;
|
|
76
|
+
if (Object.values(ERRORS).includes(code)) {
|
|
77
|
+
this.UploadErrorCode = code;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
const isUploadError = (error) => !!error.UploadErrorCode;
|
|
82
|
+
const throwErrorCode = (UploadErrorCode, detail) => {
|
|
83
|
+
const errorCode = UploadErrorCode;
|
|
84
|
+
let errorResponse;
|
|
85
|
+
if (errorCode && typeof errorCode === "string" && errorCode in ErrorMap) {
|
|
86
|
+
errorResponse = ErrorMap[errorCode];
|
|
87
|
+
}
|
|
88
|
+
const message = detail || (errorResponse?.body && typeof errorResponse.body === "object" && "message" in errorResponse.body ? errorResponse.body.message : void 0) || errorResponse?.message || "UnknownError" /* UNKNOWN_ERROR */;
|
|
89
|
+
const error = new UploadError(errorCode, message);
|
|
90
|
+
error.UploadErrorCode = errorCode;
|
|
91
|
+
if (typeof detail === "string") {
|
|
92
|
+
error.detail = detail;
|
|
93
|
+
}
|
|
94
|
+
throw error;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
export { ERRORS, ErrorMap, UploadError, isUploadError, throwErrorCode };
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fnv1a from '@sindresorhus/fnv1a';
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
|
|
4
|
+
const hash = (value) => fnv1a(value, { size: 64 }).toString(16);
|
|
5
|
+
const extractMimeType = (meta) => {
|
|
6
|
+
if (typeof meta.mimeType === "string") {
|
|
7
|
+
return meta.mimeType;
|
|
8
|
+
}
|
|
9
|
+
if (typeof meta.type === "string") {
|
|
10
|
+
return meta.type;
|
|
11
|
+
}
|
|
12
|
+
if (typeof meta.filetype === "string") {
|
|
13
|
+
return meta.filetype;
|
|
14
|
+
}
|
|
15
|
+
return void 0;
|
|
16
|
+
};
|
|
17
|
+
const extractOriginalName = (meta) => {
|
|
18
|
+
if (typeof meta.name === "string") {
|
|
19
|
+
return meta.name;
|
|
20
|
+
}
|
|
21
|
+
if (typeof meta.title === "string") {
|
|
22
|
+
return meta.title;
|
|
23
|
+
}
|
|
24
|
+
if (typeof meta.originalName === "string") {
|
|
25
|
+
return meta.originalName;
|
|
26
|
+
}
|
|
27
|
+
if (typeof meta.filename === "string") {
|
|
28
|
+
return meta.filename;
|
|
29
|
+
}
|
|
30
|
+
return void 0;
|
|
31
|
+
};
|
|
32
|
+
const generateFileId = (file) => {
|
|
33
|
+
const { metadata, originalName, size } = file;
|
|
34
|
+
const mtime = String(metadata.lastModified ?? Date.now());
|
|
35
|
+
return [originalName, size, mtime].filter(Boolean).map(String).map((value) => hash(value)).join("-");
|
|
36
|
+
};
|
|
37
|
+
class File {
|
|
38
|
+
bytesWritten = Number.NaN;
|
|
39
|
+
contentType;
|
|
40
|
+
originalName;
|
|
41
|
+
id;
|
|
42
|
+
metadata;
|
|
43
|
+
name = "";
|
|
44
|
+
size;
|
|
45
|
+
status;
|
|
46
|
+
expiredAt;
|
|
47
|
+
createdAt;
|
|
48
|
+
modifiedAt;
|
|
49
|
+
hash;
|
|
50
|
+
content;
|
|
51
|
+
ETag;
|
|
52
|
+
constructor({ contentType, expiredAt, metadata = {}, originalName, size }) {
|
|
53
|
+
this.metadata = metadata;
|
|
54
|
+
this.originalName = originalName || extractOriginalName(metadata) || (this.id = nanoid());
|
|
55
|
+
this.contentType = contentType || extractMimeType(metadata) || "application/octet-stream";
|
|
56
|
+
this.expiredAt = expiredAt;
|
|
57
|
+
if (typeof size === "string" || typeof size === "number") {
|
|
58
|
+
this.size = Number(size);
|
|
59
|
+
} else if (typeof metadata.size === "string" || typeof metadata.size === "number") {
|
|
60
|
+
this.size = Number(metadata.size);
|
|
61
|
+
}
|
|
62
|
+
if (typeof this.size === "number" && this.size <= 0) {
|
|
63
|
+
this.size = void 0;
|
|
64
|
+
}
|
|
65
|
+
this.id ||= generateFileId(this);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export { File as default };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const GCSConfig = {
|
|
2
|
+
authScopes: [
|
|
3
|
+
"https://www.googleapis.com/auth/iam",
|
|
4
|
+
"https://www.googleapis.com/auth/cloud-platform",
|
|
5
|
+
"https://www.googleapis.com/auth/devstorage.full_control"
|
|
6
|
+
],
|
|
7
|
+
storageAPI: "https://storage.googleapis.com/storage/v1/b",
|
|
8
|
+
uploadAPI: "https://storage.googleapis.com/upload/storage/v1/b"
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export { GCSConfig as default };
|