@remit/storage-service 0.0.13 → 0.0.14
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/dist/backends/filesystem.js +5 -25
- package/dist/backends/s3.js +4 -47
- package/dist/storage.d.ts +0 -14
- package/dist/storage.js +0 -9
- package/package.json +1 -1
|
@@ -1,14 +1,15 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
1
|
import { createWriteStream } from "node:fs";
|
|
3
2
|
import { mkdir, readdir, readFile, rm, stat, unlink, writeFile, } from "node:fs/promises";
|
|
4
3
|
import { dirname, join, resolve } from "node:path";
|
|
5
|
-
import {
|
|
4
|
+
import { Readable } from "node:stream";
|
|
6
5
|
import { pipeline } from "node:stream/promises";
|
|
7
|
-
import {
|
|
6
|
+
import { promisify } from "node:util";
|
|
7
|
+
import { createGunzip, gunzipSync, gzip } from "node:zlib";
|
|
8
8
|
import { ContentEncoding, StorageType } from "@remit/domain-enums";
|
|
9
9
|
import { UPLOAD_ROUTE_PREFIX, UPLOAD_URL_SIGNING_LABEL, } from "../outbox-upload-url.js";
|
|
10
10
|
import { signStoragePath } from "../signed-path.js";
|
|
11
11
|
import { buildBodyPartKey, buildDeduplicatedKey, buildExportArchiveKey, buildMessageBodyKey, buildOutboxAttachmentKey, buildOutboxAttachmentPrefix, buildParsedBodyKey, computeChecksum, isStorageNotFoundError, } from "../storage.js";
|
|
12
|
+
const gzipAsync = promisify(gzip);
|
|
12
13
|
export const createFilesystemStorageService = (basePath, uploadUrls) => {
|
|
13
14
|
const storeInternal = async (params) => {
|
|
14
15
|
const { key, content, compress = true } = params;
|
|
@@ -16,7 +17,7 @@ export const createFilesystemStorageService = (basePath, uploadUrls) => {
|
|
|
16
17
|
const contentEncoding = compress
|
|
17
18
|
? ContentEncoding.Gzip
|
|
18
19
|
: ContentEncoding.None;
|
|
19
|
-
const body = compress ?
|
|
20
|
+
const body = compress ? await gzipAsync(content) : content;
|
|
20
21
|
const fullPath = join(basePath, key);
|
|
21
22
|
await mkdir(dirname(fullPath), { recursive: true });
|
|
22
23
|
await writeFile(fullPath, body);
|
|
@@ -37,26 +38,6 @@ export const createFilesystemStorageService = (basePath, uploadUrls) => {
|
|
|
37
38
|
content,
|
|
38
39
|
});
|
|
39
40
|
};
|
|
40
|
-
const storeMessageBodyStream = async (params) => {
|
|
41
|
-
const { accountConfigId, accountId, messageId, content } = params;
|
|
42
|
-
const key = buildMessageBodyKey(accountConfigId, accountId, messageId);
|
|
43
|
-
const fullPath = join(basePath, key);
|
|
44
|
-
await mkdir(dirname(fullPath), { recursive: true });
|
|
45
|
-
const hash = createHash("sha256");
|
|
46
|
-
const hashTap = new PassThrough();
|
|
47
|
-
hashTap.on("data", (chunk) => hash.update(chunk));
|
|
48
|
-
await pipeline(content, hashTap, createGzip(), createWriteStream(fullPath));
|
|
49
|
-
const { size } = await stat(fullPath);
|
|
50
|
-
return {
|
|
51
|
-
uri: `file://${fullPath}`,
|
|
52
|
-
storageType: StorageType.Filesystem,
|
|
53
|
-
storageLocation: basePath,
|
|
54
|
-
storageKey: key,
|
|
55
|
-
sizeBytes: size,
|
|
56
|
-
checksumSha256: hash.digest("hex"),
|
|
57
|
-
contentEncoding: ContentEncoding.Gzip,
|
|
58
|
-
};
|
|
59
|
-
};
|
|
60
41
|
const storeBodyPart = (params) => {
|
|
61
42
|
const { accountConfigId, accountId, messageId, partPath, content } = params;
|
|
62
43
|
return storeInternal({
|
|
@@ -254,7 +235,6 @@ export const createFilesystemStorageService = (basePath, uploadUrls) => {
|
|
|
254
235
|
};
|
|
255
236
|
return {
|
|
256
237
|
storeMessageBody,
|
|
257
|
-
storeMessageBodyStream,
|
|
258
238
|
storeBodyPart,
|
|
259
239
|
bodyPartExists,
|
|
260
240
|
retrieveBodyPart,
|
package/dist/backends/s3.js
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import { pipeline } from "node:stream/promises";
|
|
4
|
-
import { createGunzip, createGzip, gunzipSync, gzipSync } from "node:zlib";
|
|
1
|
+
import { promisify } from "node:util";
|
|
2
|
+
import { createGunzip, gunzipSync, gzip } from "node:zlib";
|
|
5
3
|
import { DeleteObjectCommand, DeleteObjectsCommand, GetObjectCommand, HeadObjectCommand, ListObjectsV2Command, PutObjectCommand, } from "@aws-sdk/client-s3";
|
|
6
4
|
import { Upload } from "@aws-sdk/lib-storage";
|
|
7
5
|
import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
|
|
8
6
|
import { ContentEncoding, StorageType } from "@remit/domain-enums";
|
|
9
7
|
import { buildBodyPartKey, buildDeduplicatedKey, buildExportArchiveKey, buildMessageBodyKey, buildOutboxAttachmentKey, buildOutboxAttachmentPrefix, buildParsedBodyKey, computeChecksum, isStorageNotFoundError, } from "../storage.js";
|
|
10
8
|
import { parseStorageUri } from "../uri.js";
|
|
9
|
+
const gzipAsync = promisify(gzip);
|
|
11
10
|
export const createS3StorageService = (client, bucketName) => {
|
|
12
11
|
const storeInternal = async (params) => {
|
|
13
12
|
const { key, content, contentType, compress = true } = params;
|
|
@@ -15,7 +14,7 @@ export const createS3StorageService = (client, bucketName) => {
|
|
|
15
14
|
const contentEncoding = compress
|
|
16
15
|
? ContentEncoding.Gzip
|
|
17
16
|
: ContentEncoding.None;
|
|
18
|
-
const body = compress ?
|
|
17
|
+
const body = compress ? await gzipAsync(content) : content;
|
|
19
18
|
await client.send(new PutObjectCommand({
|
|
20
19
|
Bucket: bucketName,
|
|
21
20
|
Key: key,
|
|
@@ -41,47 +40,6 @@ export const createS3StorageService = (client, bucketName) => {
|
|
|
41
40
|
contentType: "message/rfc822",
|
|
42
41
|
});
|
|
43
42
|
};
|
|
44
|
-
const storeMessageBodyStream = async (params) => {
|
|
45
|
-
const { accountConfigId, accountId, messageId, content } = params;
|
|
46
|
-
const key = buildMessageBodyKey(accountConfigId, accountId, messageId);
|
|
47
|
-
// Hash the logical (pre-gzip) bytes as they flow, gzip them, and feed
|
|
48
|
-
// the gzipped stream to a multipart Upload — the whole body is never
|
|
49
|
-
// held in memory. `Upload` buffers only one part at a time.
|
|
50
|
-
const hash = createHash("sha256");
|
|
51
|
-
const hashTap = new PassThrough();
|
|
52
|
-
hashTap.on("data", (chunk) => hash.update(chunk));
|
|
53
|
-
const gzip = createGzip();
|
|
54
|
-
const uploadBody = new PassThrough();
|
|
55
|
-
let storedBytes = 0;
|
|
56
|
-
uploadBody.on("data", (chunk) => {
|
|
57
|
-
storedBytes += chunk.length;
|
|
58
|
-
});
|
|
59
|
-
const upload = new Upload({
|
|
60
|
-
client,
|
|
61
|
-
params: {
|
|
62
|
-
Bucket: bucketName,
|
|
63
|
-
Key: key,
|
|
64
|
-
Body: uploadBody,
|
|
65
|
-
ContentType: "message/rfc822",
|
|
66
|
-
ContentEncoding: "gzip",
|
|
67
|
-
},
|
|
68
|
-
});
|
|
69
|
-
const pumped = pipeline(content, hashTap, gzip, uploadBody);
|
|
70
|
-
await Promise.all([upload.done(), pumped]).catch(async (error) => {
|
|
71
|
-
// Abort the multipart upload so a failed pump doesn't leak parts.
|
|
72
|
-
await upload.abort().catch(() => { });
|
|
73
|
-
throw error;
|
|
74
|
-
});
|
|
75
|
-
return {
|
|
76
|
-
uri: `s3://${bucketName}/${key}`,
|
|
77
|
-
storageType: StorageType.S3,
|
|
78
|
-
storageLocation: bucketName,
|
|
79
|
-
storageKey: key,
|
|
80
|
-
sizeBytes: storedBytes,
|
|
81
|
-
checksumSha256: hash.digest("hex"),
|
|
82
|
-
contentEncoding: ContentEncoding.Gzip,
|
|
83
|
-
};
|
|
84
|
-
};
|
|
85
43
|
const storeBodyPart = (params) => {
|
|
86
44
|
const { accountConfigId, accountId, messageId, partPath, content, contentType, } = params;
|
|
87
45
|
return storeInternal({
|
|
@@ -387,7 +345,6 @@ export const createS3StorageService = (client, bucketName) => {
|
|
|
387
345
|
const getPresignedDownloadUrl = (key, expiresInSeconds) => getSignedUrl(client, new GetObjectCommand({ Bucket: bucketName, Key: key }), { expiresIn: expiresInSeconds });
|
|
388
346
|
return {
|
|
389
347
|
storeMessageBody,
|
|
390
|
-
storeMessageBodyStream,
|
|
391
348
|
storeBodyPart,
|
|
392
349
|
bodyPartExists,
|
|
393
350
|
retrieveBodyPart,
|
package/dist/storage.d.ts
CHANGED
|
@@ -25,14 +25,6 @@ export interface StoreMessageBodyParams {
|
|
|
25
25
|
messageId: string;
|
|
26
26
|
content: Buffer;
|
|
27
27
|
}
|
|
28
|
-
/** Parameters for streaming a message body (raw RFC822 content) to storage */
|
|
29
|
-
export interface StoreMessageBodyStreamParams {
|
|
30
|
-
accountConfigId: string;
|
|
31
|
-
accountId: string;
|
|
32
|
-
messageId: string;
|
|
33
|
-
/** Readable stream of the raw message bytes; streamed to storage, never buffered whole. */
|
|
34
|
-
content: Readable;
|
|
35
|
-
}
|
|
36
28
|
/** Parameters for storing a body part (attachment, inline content) */
|
|
37
29
|
export interface StoreBodyPartParams {
|
|
38
30
|
accountConfigId: string;
|
|
@@ -115,12 +107,6 @@ export interface StoreParsedBodyParams {
|
|
|
115
107
|
export interface StorageService {
|
|
116
108
|
/** Store a message body (raw RFC822 content) */
|
|
117
109
|
storeMessageBody(params: StoreMessageBodyParams): Promise<StorageReference>;
|
|
118
|
-
/**
|
|
119
|
-
* Store a message body from a readable stream, never buffering the whole
|
|
120
|
-
* body. Use this on the sync hot path so a ranged FETCH streams straight to
|
|
121
|
-
* storage.
|
|
122
|
-
*/
|
|
123
|
-
storeMessageBodyStream(params: StoreMessageBodyStreamParams): Promise<StorageReference>;
|
|
124
110
|
/** Store a body part (attachment, inline content) */
|
|
125
111
|
storeBodyPart(params: StoreBodyPartParams): Promise<StorageReference>;
|
|
126
112
|
/**
|
package/dist/storage.js
CHANGED
|
@@ -85,14 +85,6 @@ export const createMockStorageService = () => {
|
|
|
85
85
|
const { accountConfigId, accountId, messageId, content } = params;
|
|
86
86
|
return storeInternal(buildMessageBodyKey(accountConfigId, accountId, messageId), content);
|
|
87
87
|
};
|
|
88
|
-
const storeMessageBodyStream = async (params) => {
|
|
89
|
-
const { accountConfigId, accountId, messageId, content } = params;
|
|
90
|
-
const chunks = [];
|
|
91
|
-
for await (const chunk of content) {
|
|
92
|
-
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
93
|
-
}
|
|
94
|
-
return storeInternal(buildMessageBodyKey(accountConfigId, accountId, messageId), Buffer.concat(chunks));
|
|
95
|
-
};
|
|
96
88
|
const storeBodyPart = async (params) => {
|
|
97
89
|
const { accountConfigId, accountId, messageId, partPath, content } = params;
|
|
98
90
|
return storeInternal(buildBodyPartKey(accountConfigId, accountId, messageId, partPath), content);
|
|
@@ -184,7 +176,6 @@ export const createMockStorageService = () => {
|
|
|
184
176
|
};
|
|
185
177
|
return {
|
|
186
178
|
storeMessageBody,
|
|
187
|
-
storeMessageBodyStream,
|
|
188
179
|
storeBodyPart,
|
|
189
180
|
bodyPartExists,
|
|
190
181
|
retrieveBodyPart,
|