@visulima/storage 1.0.0-alpha.1 → 1.0.0-alpha.3
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 +30 -0
- 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 +4 -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-BWSOtVaN.js +4 -0
- package/dist/packem_shared/AwsLightStorage-Blv-GP4v.js +133 -0
- package/dist/packem_shared/AzureFile-CesgFzps.js +8 -0
- package/dist/packem_shared/AzureSMetaStorage-CFs-OYJT.js +88 -0
- package/dist/packem_shared/AzureStorage-B0v048hG.js +357 -0
- package/dist/packem_shared/BaseTransformer-C2gLib6v.js +82 -0
- package/dist/packem_shared/DiskStorage-BlltBst6.js +12 -0
- package/dist/packem_shared/DiskStorageWithChecksum-BJTmMklc.js +238 -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-BMFEdzJ7.js +7 -0
- package/dist/packem_shared/GCStorage-DnhiwaS9.js +406 -0
- package/dist/packem_shared/LocalMetaStorage-CZHhKkMd.js +7 -0
- package/dist/packem_shared/MediaTransformer-D6658DL1.js +1197 -0
- package/dist/packem_shared/MetaStorage-pECeFOad.js +46 -0
- package/dist/packem_shared/Metadata-DRLXeJ0F.js +89 -0
- package/dist/packem_shared/Multipart-CNFK_Okz.js +163 -0
- package/dist/packem_shared/Multipart-CtUL7BQw.js +128 -0
- package/dist/packem_shared/NetlifyBlobFile-CXzyjqrD.js +14 -0
- package/dist/packem_shared/NetlifyBlobMetaStorage-DxZ5aDvD.js +9 -0
- package/dist/packem_shared/NetlifyBlobStorage-CLPtjHqd.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-CAOAEkOj.js +267 -0
- package/dist/packem_shared/Rest-DNuLwBrK.js +228 -0
- package/dist/packem_shared/S3File-DZiyk9Qt.js +11 -0
- package/dist/packem_shared/S3MetaStorage-Dz9aDabA.js +76 -0
- package/dist/packem_shared/S3Storage-DHmTk-hU.js +316 -0
- package/dist/packem_shared/TUS_RESUMABLE-GJzZ9R-f.js +434 -0
- package/dist/packem_shared/Tus-B8PmlMgR.js +200 -0
- package/dist/packem_shared/Tus-C4F1aYOl.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-Bd9F-VFm.js +9 -0
- package/dist/packem_shared/VercelBlobStorage-DD9LMOS6.js +261 -0
- package/dist/packem_shared/_commonjsHelpers-B85MJLTf.js +5 -0
- package/dist/packem_shared/aws-light-meta-storage-DKJBgbR6.js +446 -0
- package/dist/packem_shared/backblaze-BlMnIcBC.js +20 -0
- package/dist/packem_shared/base-handler-core-BKuf4YLT.js +303 -0
- package/dist/packem_shared/base-handler-fetch-Cr0hLiqg.js +291 -0
- package/dist/packem_shared/base-handler-node-gk5aN9Cx.js +730 -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-DUuacjWv.js +11 -0
- package/dist/packem_shared/digitalOcean-CWQRJM3L.js +21 -0
- package/dist/packem_shared/disk-storage-Bh2wy3YW.js +810 -0
- package/dist/packem_shared/gcs-meta-storage-0tgMNnPO.js +158 -0
- package/dist/packem_shared/has-content-CY66ehMK.js +3 -0
- package/dist/packem_shared/headers-DoS5nwM-.js +3220 -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-By-3SNBI.js +649 -0
- package/dist/packem_shared/minio-C2YBZQOw.js +22 -0
- package/dist/packem_shared/multipart-base-B3i67SSX.js +102 -0
- package/dist/packem_shared/part-match-BMNqHDYD.js +96 -0
- package/dist/packem_shared/path-CR6YkPXX-7R1-9CMk.js +161 -0
- package/dist/packem_shared/response-builder-BtnRiBUI.js +45 -0
- package/dist/packem_shared/rest-base-GRCcnan7.js +457 -0
- package/dist/packem_shared/restOpenApiSpec-CwcHHY2C.js +800 -0
- package/dist/packem_shared/s3-base-storage-C1DIHTYY.js +510 -0
- package/dist/packem_shared/sharedGet-Cpo7QUyu.js +1019 -0
- package/dist/packem_shared/storage-C6NSXb8h.js +1175 -0
- package/dist/packem_shared/tigris-Hac8TKnX.js +21 -0
- package/dist/packem_shared/transformOpenApiSpec-DWM5WtnN.js +975 -0
- package/dist/packem_shared/tusOpenApiSpec-6ohiToBy.js +740 -0
- package/dist/packem_shared/update-size-CCGm6i1J.js +8 -0
- package/dist/packem_shared/validator-BeX_lJet.js +78 -0
- package/dist/packem_shared/waitForStorage-Cscw85sx.js +18 -0
- package/dist/packem_shared/wasabi-DIyflHSd.js +21 -0
- package/dist/packem_shared/xhrOpenApiSpec-DS17brnx.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 +5 -5
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import MetaStorage from './MetaStorage-pECeFOad.js';
|
|
2
|
+
import { parseMetadata, stringifyMetadata } from './Metadata-DRLXeJ0F.js';
|
|
3
|
+
import { Readable } from 'node:stream';
|
|
4
|
+
import { AwsClient } from 'aws4fetch';
|
|
5
|
+
import { i as isExpired } from './is-expired-CTThU1q5.js';
|
|
6
|
+
|
|
7
|
+
const parseXml = (text) => {
|
|
8
|
+
const result = {};
|
|
9
|
+
const cleanText = text.replaceAll(/<\?xml[^>]*\?>/g, "").replaceAll(/xmlns[^=]*="[^"]*"/g, "");
|
|
10
|
+
const tagRegex = /<([^>]+)>([^<]*)<\/\1>/g;
|
|
11
|
+
const matches = [...cleanText.matchAll(tagRegex)];
|
|
12
|
+
for (const match of matches) {
|
|
13
|
+
const [, tagName, content] = match;
|
|
14
|
+
if (tagName && content && content.trim()) {
|
|
15
|
+
if (result[tagName]) {
|
|
16
|
+
if (!Array.isArray(result[tagName])) {
|
|
17
|
+
result[tagName] = [result[tagName]];
|
|
18
|
+
}
|
|
19
|
+
result[tagName].push(content.trim());
|
|
20
|
+
} else {
|
|
21
|
+
result[tagName] = content.trim();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
const nestedRegex = /<([^>]+)>([\s\S]*?)<\/\1>/g;
|
|
26
|
+
let nestedMatch;
|
|
27
|
+
while ((nestedMatch = nestedRegex.exec(cleanText)) !== null) {
|
|
28
|
+
const [, tagName, innerContent] = nestedMatch;
|
|
29
|
+
if (tagName && innerContent && innerContent.includes("<")) {
|
|
30
|
+
const nested = parseXml(innerContent);
|
|
31
|
+
if (Object.keys(nested).length > 0) {
|
|
32
|
+
result[tagName] = nested;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
};
|
|
38
|
+
class AwsLightApiAdapter {
|
|
39
|
+
aws;
|
|
40
|
+
bucket;
|
|
41
|
+
endpoint;
|
|
42
|
+
constructor(config) {
|
|
43
|
+
this.bucket = config.bucket;
|
|
44
|
+
this.endpoint = config.endpoint || `https://${config.bucket}.s3.${config.region}.amazonaws.com`;
|
|
45
|
+
this.aws = new AwsClient({
|
|
46
|
+
accessKeyId: config.accessKeyId,
|
|
47
|
+
region: config.region,
|
|
48
|
+
secretAccessKey: config.secretAccessKey,
|
|
49
|
+
service: config.service || "s3",
|
|
50
|
+
sessionToken: config.sessionToken
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
async createMultipartUpload(params) {
|
|
54
|
+
const queryParams = { uploads: "" };
|
|
55
|
+
const headers = {};
|
|
56
|
+
if (params.ContentType) {
|
|
57
|
+
headers["Content-Type"] = params.ContentType;
|
|
58
|
+
}
|
|
59
|
+
if (params.ACL) {
|
|
60
|
+
headers["x-amz-acl"] = params.ACL;
|
|
61
|
+
}
|
|
62
|
+
if (params.Metadata) {
|
|
63
|
+
for (const [key, value] of Object.entries(params.Metadata)) {
|
|
64
|
+
headers[`x-amz-meta-${key}`] = value;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const url = this.buildUrl(params.Key, queryParams);
|
|
68
|
+
const response = await this.aws.fetch(url, {
|
|
69
|
+
headers,
|
|
70
|
+
method: "POST"
|
|
71
|
+
});
|
|
72
|
+
const xmlText = await response.text();
|
|
73
|
+
if (!response.ok) {
|
|
74
|
+
throw new Error(`Failed to create multipart upload: ${response.status} ${xmlText}`);
|
|
75
|
+
}
|
|
76
|
+
const xml = parseXml(xmlText);
|
|
77
|
+
const uploadId = xml.UploadId || xml.InitiateMultipartUploadResult?.UploadId;
|
|
78
|
+
if (!uploadId) {
|
|
79
|
+
const error = new Error("Failed to parse UploadId from response");
|
|
80
|
+
error.retryable = false;
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
return { UploadId: uploadId };
|
|
84
|
+
}
|
|
85
|
+
async uploadPart(params) {
|
|
86
|
+
const queryParams = {
|
|
87
|
+
partNumber: String(params.PartNumber),
|
|
88
|
+
uploadId: params.UploadId
|
|
89
|
+
};
|
|
90
|
+
const headers = {};
|
|
91
|
+
if (params.ContentLength) {
|
|
92
|
+
headers["Content-Length"] = String(params.ContentLength);
|
|
93
|
+
}
|
|
94
|
+
if (params.ContentMD5) {
|
|
95
|
+
headers["Content-MD5"] = params.ContentMD5;
|
|
96
|
+
}
|
|
97
|
+
const body = params.Body instanceof Readable ? Readable.toWeb(params.Body) : params.Body;
|
|
98
|
+
const url = this.buildUrl(params.Key, queryParams);
|
|
99
|
+
const response = await this.aws.fetch(url, {
|
|
100
|
+
body,
|
|
101
|
+
headers,
|
|
102
|
+
method: "PUT"
|
|
103
|
+
});
|
|
104
|
+
if (!response.ok) {
|
|
105
|
+
const text = await response.text();
|
|
106
|
+
throw new Error(`Failed to upload part: ${response.status} ${text}`);
|
|
107
|
+
}
|
|
108
|
+
const etag = response.headers.get("ETag");
|
|
109
|
+
if (!etag) {
|
|
110
|
+
throw new Error("Failed to get ETag from response");
|
|
111
|
+
}
|
|
112
|
+
return { ETag: etag.replaceAll(/(^"|"$)/g, "") };
|
|
113
|
+
}
|
|
114
|
+
async completeMultipartUpload(params) {
|
|
115
|
+
const queryParams = { uploadId: params.UploadId };
|
|
116
|
+
const partsXml = params.Parts.map(({ ETag, PartNumber }) => `<Part><PartNumber>${PartNumber}</PartNumber><ETag>"${ETag}"</ETag></Part>`).join("");
|
|
117
|
+
const xmlBody = `<?xml version="1.0" encoding="UTF-8"?>
|
|
118
|
+
<CompleteMultipartUpload xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
|
|
119
|
+
${partsXml}
|
|
120
|
+
</CompleteMultipartUpload>`;
|
|
121
|
+
const url = this.buildUrl(params.Key, queryParams);
|
|
122
|
+
const response = await this.aws.fetch(url, {
|
|
123
|
+
body: xmlBody,
|
|
124
|
+
headers: {
|
|
125
|
+
"Content-Type": "application/xml"
|
|
126
|
+
},
|
|
127
|
+
method: "POST"
|
|
128
|
+
});
|
|
129
|
+
const xmlText = await response.text();
|
|
130
|
+
if (!response.ok) {
|
|
131
|
+
throw new Error(`Failed to complete multipart upload: ${response.status} ${xmlText}`);
|
|
132
|
+
}
|
|
133
|
+
const xml = parseXml(xmlText);
|
|
134
|
+
const result = xml.CompleteMultipartUploadResult || xml;
|
|
135
|
+
const location = result.Location || `${this.endpoint}/${params.Key}`;
|
|
136
|
+
const etag = result.ETag;
|
|
137
|
+
return {
|
|
138
|
+
ETag: etag?.replaceAll(/(^"|"$)/g, ""),
|
|
139
|
+
Location: location
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
async abortMultipartUpload(params) {
|
|
143
|
+
const queryParams = { uploadId: params.UploadId };
|
|
144
|
+
const url = this.buildUrl(params.Key, queryParams);
|
|
145
|
+
const response = await this.aws.fetch(url, {
|
|
146
|
+
method: "DELETE"
|
|
147
|
+
});
|
|
148
|
+
if (!response.ok) {
|
|
149
|
+
const text = await response.text();
|
|
150
|
+
throw new Error(`Failed to abort multipart upload: ${response.status} ${text}`);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async listParts(params) {
|
|
154
|
+
const queryParams = { uploadId: params.UploadId };
|
|
155
|
+
const url = this.buildUrl(params.Key, queryParams);
|
|
156
|
+
const response = await this.aws.fetch(url, {
|
|
157
|
+
method: "GET"
|
|
158
|
+
});
|
|
159
|
+
const xmlText = await response.text();
|
|
160
|
+
if (!response.ok) {
|
|
161
|
+
throw new Error(`Failed to list parts: ${response.status} ${xmlText}`);
|
|
162
|
+
}
|
|
163
|
+
const xml = parseXml(xmlText);
|
|
164
|
+
const listPartsResult = xml.ListPartsResult || xml;
|
|
165
|
+
let parts = [];
|
|
166
|
+
if (listPartsResult.Part) {
|
|
167
|
+
parts = Array.isArray(listPartsResult.Part) ? listPartsResult.Part : [listPartsResult.Part];
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
Parts: parts.map((part) => {
|
|
171
|
+
return {
|
|
172
|
+
ETag: part.ETag?.replaceAll(/(^"|"$)/g, ""),
|
|
173
|
+
PartNumber: Number(part.PartNumber) || 0,
|
|
174
|
+
Size: part.Size ? Number(part.Size) : void 0
|
|
175
|
+
};
|
|
176
|
+
})
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
async getObject(params) {
|
|
180
|
+
const url = this.buildUrl(params.Key);
|
|
181
|
+
const response = await this.aws.fetch(url, {
|
|
182
|
+
method: "GET"
|
|
183
|
+
});
|
|
184
|
+
if (!response.ok) {
|
|
185
|
+
const text = await response.text();
|
|
186
|
+
throw new Error(`Failed to get object: ${response.status} ${text}`);
|
|
187
|
+
}
|
|
188
|
+
const contentLength = response.headers.get("Content-Length");
|
|
189
|
+
const contentType = response.headers.get("Content-Type");
|
|
190
|
+
const etag = response.headers.get("ETag");
|
|
191
|
+
const expires = response.headers.get("Expires");
|
|
192
|
+
const lastModified = response.headers.get("Last-Modified");
|
|
193
|
+
const metadata = {};
|
|
194
|
+
for (const [key, value] of response.headers.entries()) {
|
|
195
|
+
if (key.toLowerCase().startsWith("x-amz-meta-")) {
|
|
196
|
+
const metaKey = key.toLowerCase().replace("x-amz-meta-", "");
|
|
197
|
+
metadata[metaKey] = value;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const { body } = response;
|
|
201
|
+
return {
|
|
202
|
+
Body: body ? this.streamToReadable(body) : void 0,
|
|
203
|
+
ContentLength: contentLength ? Number(contentLength) : void 0,
|
|
204
|
+
ContentType: contentType || void 0,
|
|
205
|
+
ETag: etag?.replaceAll(/(^"|"$)/g, "") || void 0,
|
|
206
|
+
Expires: expires ? new Date(expires) : void 0,
|
|
207
|
+
LastModified: lastModified ? new Date(lastModified) : void 0,
|
|
208
|
+
Metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
async headObject(params) {
|
|
212
|
+
const url = this.buildUrl(params.Key);
|
|
213
|
+
const response = await this.aws.fetch(url, {
|
|
214
|
+
method: "HEAD"
|
|
215
|
+
});
|
|
216
|
+
if (!response.ok) {
|
|
217
|
+
const text = await response.text();
|
|
218
|
+
throw new Error(`Failed to head object: ${response.status} ${text}`);
|
|
219
|
+
}
|
|
220
|
+
const contentLength = response.headers.get("Content-Length");
|
|
221
|
+
const contentType = response.headers.get("Content-Type");
|
|
222
|
+
const etag = response.headers.get("ETag");
|
|
223
|
+
const expires = response.headers.get("Expires");
|
|
224
|
+
const lastModified = response.headers.get("Last-Modified");
|
|
225
|
+
const metadata = {};
|
|
226
|
+
for (const [key, value] of response.headers.entries()) {
|
|
227
|
+
if (key.toLowerCase().startsWith("x-amz-meta-")) {
|
|
228
|
+
const metaKey = key.toLowerCase().replace("x-amz-meta-", "");
|
|
229
|
+
metadata[metaKey] = value;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
ContentLength: contentLength ? Number(contentLength) : void 0,
|
|
234
|
+
ContentType: contentType || void 0,
|
|
235
|
+
ETag: etag?.replaceAll(/(^"|"$)/g, "") || void 0,
|
|
236
|
+
Expires: expires ? new Date(expires) : void 0,
|
|
237
|
+
LastModified: lastModified ? new Date(lastModified) : void 0,
|
|
238
|
+
Metadata: Object.keys(metadata).length > 0 ? metadata : void 0
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
async deleteObject(params) {
|
|
242
|
+
const url = this.buildUrl(params.Key);
|
|
243
|
+
const response = await this.aws.fetch(url, {
|
|
244
|
+
method: "DELETE"
|
|
245
|
+
});
|
|
246
|
+
if (!response.ok) {
|
|
247
|
+
const text = await response.text();
|
|
248
|
+
throw new Error(`Failed to delete object: ${response.status} ${text}`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
async copyObject(params) {
|
|
252
|
+
const headers = {
|
|
253
|
+
"x-amz-copy-source": params.CopySource
|
|
254
|
+
};
|
|
255
|
+
if (params.StorageClass) {
|
|
256
|
+
headers["x-amz-storage-class"] = params.StorageClass;
|
|
257
|
+
}
|
|
258
|
+
let url = this.buildUrl(params.Key);
|
|
259
|
+
if (params.Bucket !== this.bucket) {
|
|
260
|
+
url = url.replace(this.bucket, params.Bucket);
|
|
261
|
+
}
|
|
262
|
+
const response = await this.aws.fetch(url, {
|
|
263
|
+
headers,
|
|
264
|
+
method: "PUT"
|
|
265
|
+
});
|
|
266
|
+
if (!response.ok) {
|
|
267
|
+
const text = await response.text();
|
|
268
|
+
throw new Error(`Failed to copy object: ${response.status} ${text}`);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
async listObjectsV2(params) {
|
|
272
|
+
const queryParams = {
|
|
273
|
+
"list-type": "2"
|
|
274
|
+
};
|
|
275
|
+
if (params.MaxKeys) {
|
|
276
|
+
queryParams["max-keys"] = String(params.MaxKeys);
|
|
277
|
+
}
|
|
278
|
+
if (params.ContinuationToken) {
|
|
279
|
+
queryParams["continuation-token"] = params.ContinuationToken;
|
|
280
|
+
}
|
|
281
|
+
const url = this.buildUrl("", queryParams);
|
|
282
|
+
const response = await this.aws.fetch(url, {
|
|
283
|
+
method: "GET"
|
|
284
|
+
});
|
|
285
|
+
const xmlText = await response.text();
|
|
286
|
+
if (!response.ok) {
|
|
287
|
+
throw new Error(`Failed to list objects: ${response.status} ${xmlText}`);
|
|
288
|
+
}
|
|
289
|
+
const xml = parseXml(xmlText);
|
|
290
|
+
const listResult = xml.ListBucketResult || xml;
|
|
291
|
+
const contents = listResult.Contents || (Array.isArray(listResult.Contents) ? listResult.Contents : []);
|
|
292
|
+
return {
|
|
293
|
+
Contents: Array.isArray(contents) ? contents.map((item) => {
|
|
294
|
+
return {
|
|
295
|
+
Key: item.Key,
|
|
296
|
+
LastModified: item.LastModified ? new Date(String(item.LastModified)) : void 0
|
|
297
|
+
};
|
|
298
|
+
}) : [],
|
|
299
|
+
IsTruncated: listResult.IsTruncated === "true" || listResult.IsTruncated === true,
|
|
300
|
+
NextContinuationToken: listResult.NextContinuationToken
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
async getPresignedUrl(params) {
|
|
304
|
+
const queryParams = {
|
|
305
|
+
partNumber: String(params.PartNumber),
|
|
306
|
+
uploadId: params.UploadId,
|
|
307
|
+
"X-Amz-Expires": String(params.expiresIn)
|
|
308
|
+
};
|
|
309
|
+
const url = this.buildUrl(params.Key, queryParams);
|
|
310
|
+
return url;
|
|
311
|
+
}
|
|
312
|
+
async putObject(params) {
|
|
313
|
+
const url = this.buildUrl(params.Key);
|
|
314
|
+
const headers = {};
|
|
315
|
+
if (params.ContentType) {
|
|
316
|
+
headers["Content-Type"] = params.ContentType;
|
|
317
|
+
}
|
|
318
|
+
if (params.ContentLength !== void 0) {
|
|
319
|
+
headers["Content-Length"] = String(params.ContentLength);
|
|
320
|
+
}
|
|
321
|
+
if (params.Metadata) {
|
|
322
|
+
for (const [key, value] of Object.entries(params.Metadata)) {
|
|
323
|
+
headers[`x-amz-meta-${key}`] = value;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
let body;
|
|
327
|
+
if (params.Body === void 0) {
|
|
328
|
+
body = void 0;
|
|
329
|
+
} else if (params.Body instanceof Uint8Array) {
|
|
330
|
+
body = new Uint8Array(params.Body);
|
|
331
|
+
} else if (params.Body instanceof ReadableStream) {
|
|
332
|
+
body = params.Body;
|
|
333
|
+
} else {
|
|
334
|
+
body = void 0;
|
|
335
|
+
}
|
|
336
|
+
const response = await this.aws.fetch(url, {
|
|
337
|
+
body,
|
|
338
|
+
headers,
|
|
339
|
+
method: "PUT"
|
|
340
|
+
});
|
|
341
|
+
if (!response.ok) {
|
|
342
|
+
const text = await response.text();
|
|
343
|
+
throw new Error(`Failed to put object: ${response.status} ${text}`);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
async checkBucketAccess(_params) {
|
|
347
|
+
const url = this.buildUrl("");
|
|
348
|
+
const response = await this.aws.fetch(url, {
|
|
349
|
+
method: "HEAD"
|
|
350
|
+
});
|
|
351
|
+
if (!response.ok && response.status !== 404) {
|
|
352
|
+
const text = await response.text();
|
|
353
|
+
throw new Error(`Failed to access bucket: ${response.status} ${text}`);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
/**
|
|
357
|
+
* Builds S3 API URL.
|
|
358
|
+
*/
|
|
359
|
+
buildUrl(key, queryParams) {
|
|
360
|
+
const url = new URL(key, this.endpoint);
|
|
361
|
+
if (queryParams) {
|
|
362
|
+
for (const [parameterKey, value] of Object.entries(queryParams)) {
|
|
363
|
+
url.searchParams.set(parameterKey, value);
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
return url.toString();
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Converts ReadableStream to Readable for Node.js compatibility.
|
|
370
|
+
*/
|
|
371
|
+
// eslint-disable-next-line class-methods-use-this
|
|
372
|
+
streamToReadable(stream) {
|
|
373
|
+
return Readable.fromWeb(stream);
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
class AwsLightMetaStorage extends MetaStorage {
|
|
378
|
+
constructor(config) {
|
|
379
|
+
super(config);
|
|
380
|
+
this.config = config;
|
|
381
|
+
const bucket = config.bucket || process.env.S3_BUCKET || process.env.AWS_S3_BUCKET;
|
|
382
|
+
if (!bucket) {
|
|
383
|
+
throw new Error("S3 bucket is not defined");
|
|
384
|
+
}
|
|
385
|
+
this.bucket = bucket;
|
|
386
|
+
this.adapter = new AwsLightApiAdapter({
|
|
387
|
+
accessKeyId: config.accessKeyId,
|
|
388
|
+
bucket,
|
|
389
|
+
endpoint: config.endpoint,
|
|
390
|
+
region: config.region,
|
|
391
|
+
secretAccessKey: config.secretAccessKey,
|
|
392
|
+
service: config.service,
|
|
393
|
+
sessionToken: config.sessionToken
|
|
394
|
+
});
|
|
395
|
+
this.adapter.checkBucketAccess({ Bucket: bucket }).catch((error) => {
|
|
396
|
+
throw error;
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
adapter;
|
|
400
|
+
bucket;
|
|
401
|
+
async get(id) {
|
|
402
|
+
const Key = this.getMetaName(id);
|
|
403
|
+
const { Expires, Metadata } = await this.adapter.headObject({
|
|
404
|
+
Bucket: this.bucket,
|
|
405
|
+
Key
|
|
406
|
+
});
|
|
407
|
+
if (Expires && isExpired({ expiredAt: Expires })) {
|
|
408
|
+
await this.delete(Key);
|
|
409
|
+
throw new Error(`Metafile ${id} not found`);
|
|
410
|
+
}
|
|
411
|
+
if (Metadata?.metadata !== void 0) {
|
|
412
|
+
const file = JSON.parse(decodeURIComponent(Metadata.metadata));
|
|
413
|
+
if (file.metadata && typeof file.metadata === "string") {
|
|
414
|
+
file.metadata = parseMetadata(file.metadata);
|
|
415
|
+
}
|
|
416
|
+
return file;
|
|
417
|
+
}
|
|
418
|
+
throw new Error(`Metafile ${id} not found`);
|
|
419
|
+
}
|
|
420
|
+
async touch(id, file) {
|
|
421
|
+
return this.save(id, file);
|
|
422
|
+
}
|
|
423
|
+
async delete(id) {
|
|
424
|
+
await this.adapter.deleteObject({
|
|
425
|
+
Bucket: this.bucket,
|
|
426
|
+
Key: this.getMetaName(id)
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
async save(id, file) {
|
|
430
|
+
const transformedMetadata = { ...file };
|
|
431
|
+
if (transformedMetadata.metadata) {
|
|
432
|
+
transformedMetadata.metadata = stringifyMetadata(file.metadata);
|
|
433
|
+
}
|
|
434
|
+
const metadata = encodeURIComponent(JSON.stringify(transformedMetadata));
|
|
435
|
+
await this.adapter.putObject({
|
|
436
|
+
Bucket: this.bucket,
|
|
437
|
+
ContentLength: 0,
|
|
438
|
+
ContentType: "application/json",
|
|
439
|
+
Key: this.getMetaName(id),
|
|
440
|
+
Metadata: { metadata }
|
|
441
|
+
});
|
|
442
|
+
return file;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
export { AwsLightMetaStorage as A, AwsLightApiAdapter as a };
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const backblaze = (parameters) => {
|
|
2
|
+
const { applicationKey, applicationKeyId, region } = parameters ?? {
|
|
3
|
+
applicationKey: process.env.AWS_SECRET_ACCESS_KEY || process.env.B2_APP_KEY || process.env.BACKBLAZE_APP_KEY,
|
|
4
|
+
applicationKeyId: process.env.AWS_ACCESS_KEY_ID || process.env.B2_APP_KEY_ID || process.env.BACKBLAZE_APP_KEY_ID,
|
|
5
|
+
region: process.env.AWS_REGION || process.env.B2_REGION || process.env.BACKBLAZE_REGION
|
|
6
|
+
};
|
|
7
|
+
if (!region || !applicationKeyId || !applicationKey) {
|
|
8
|
+
throw new Error("Missing required parameters for Backblaze B2 client.");
|
|
9
|
+
}
|
|
10
|
+
return {
|
|
11
|
+
credentials: {
|
|
12
|
+
accessKeyId: applicationKeyId,
|
|
13
|
+
secretAccessKey: applicationKey
|
|
14
|
+
},
|
|
15
|
+
endpoint: `https://s3.${region}.backblazeb2.com`,
|
|
16
|
+
region
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export { backblaze, backblaze as default };
|