@remit/storage-service 0.0.14 → 0.0.16
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 +8 -2
- package/dist/backends/s3.js +10 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/storage.d.ts +11 -0
- package/dist/storage.js +31 -17
- package/package.json +1 -1
|
@@ -168,8 +168,10 @@ export const createFilesystemStorageService = (basePath, uploadUrls) => {
|
|
|
168
168
|
};
|
|
169
169
|
};
|
|
170
170
|
const deleteOutboxAttachments = async (accountConfigId, accountId, outboxMessageId) => {
|
|
171
|
-
|
|
172
|
-
|
|
171
|
+
await deletePrefix(buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId));
|
|
172
|
+
};
|
|
173
|
+
const deletePrefix = async (prefix) => {
|
|
174
|
+
await rm(join(basePath, prefix), { recursive: true, force: true });
|
|
173
175
|
};
|
|
174
176
|
const storeDeduplicated = (params) => {
|
|
175
177
|
const { accountConfigId, accountId, content } = params;
|
|
@@ -242,6 +244,7 @@ export const createFilesystemStorageService = (basePath, uploadUrls) => {
|
|
|
242
244
|
listOutboxAttachments,
|
|
243
245
|
listOutboxDraftsWithAttachments,
|
|
244
246
|
deleteOutboxAttachments,
|
|
247
|
+
deletePrefix,
|
|
245
248
|
deleteOutboxAttachment,
|
|
246
249
|
statOutboxAttachment,
|
|
247
250
|
retrieveOutboxAttachment,
|
|
@@ -289,6 +292,9 @@ export const createFilesystemStorageService = (basePath, uploadUrls) => {
|
|
|
289
292
|
await pipeline(body, createWriteStream(fullPath));
|
|
290
293
|
return key;
|
|
291
294
|
},
|
|
295
|
+
deleteExportArchive: (accountConfigId, exportRequestId) => rm(join(basePath, buildExportArchiveKey(accountConfigId, exportRequestId)), {
|
|
296
|
+
force: true,
|
|
297
|
+
}),
|
|
292
298
|
getPresignedDownloadUrl: (_key, _expiresInSeconds) => {
|
|
293
299
|
throw new Error("getPresignedDownloadUrl is not supported on the filesystem backend");
|
|
294
300
|
},
|
package/dist/backends/s3.js
CHANGED
|
@@ -194,8 +194,7 @@ export const createS3StorageService = (client, bucketName) => {
|
|
|
194
194
|
}), { expiresIn });
|
|
195
195
|
return { uploadUrl };
|
|
196
196
|
};
|
|
197
|
-
const
|
|
198
|
-
const prefix = buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId);
|
|
197
|
+
const deletePrefix = async (prefix) => {
|
|
199
198
|
let continuationToken;
|
|
200
199
|
do {
|
|
201
200
|
const response = await client.send(new ListObjectsV2Command({
|
|
@@ -217,6 +216,7 @@ export const createS3StorageService = (client, bucketName) => {
|
|
|
217
216
|
: undefined;
|
|
218
217
|
} while (continuationToken);
|
|
219
218
|
};
|
|
219
|
+
const deleteOutboxAttachments = (accountConfigId, accountId, outboxMessageId) => deletePrefix(buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId));
|
|
220
220
|
const storeDeduplicated = (params) => {
|
|
221
221
|
const { accountConfigId, accountId, content, contentType } = params;
|
|
222
222
|
const checksumSha256 = computeChecksum(content);
|
|
@@ -342,6 +342,12 @@ export const createS3StorageService = (client, bucketName) => {
|
|
|
342
342
|
});
|
|
343
343
|
return key;
|
|
344
344
|
};
|
|
345
|
+
const deleteExportArchive = async (accountConfigId, exportRequestId) => {
|
|
346
|
+
await client.send(new DeleteObjectCommand({
|
|
347
|
+
Bucket: bucketName,
|
|
348
|
+
Key: buildExportArchiveKey(accountConfigId, exportRequestId),
|
|
349
|
+
}));
|
|
350
|
+
};
|
|
345
351
|
const getPresignedDownloadUrl = (key, expiresInSeconds) => getSignedUrl(client, new GetObjectCommand({ Bucket: bucketName, Key: key }), { expiresIn: expiresInSeconds });
|
|
346
352
|
return {
|
|
347
353
|
storeMessageBody,
|
|
@@ -352,6 +358,7 @@ export const createS3StorageService = (client, bucketName) => {
|
|
|
352
358
|
listOutboxAttachments,
|
|
353
359
|
listOutboxDraftsWithAttachments,
|
|
354
360
|
deleteOutboxAttachments,
|
|
361
|
+
deletePrefix,
|
|
355
362
|
deleteOutboxAttachment,
|
|
356
363
|
statOutboxAttachment,
|
|
357
364
|
retrieveOutboxAttachment,
|
|
@@ -365,6 +372,7 @@ export const createS3StorageService = (client, bucketName) => {
|
|
|
365
372
|
exists,
|
|
366
373
|
delete: del,
|
|
367
374
|
storeExportArchiveStream,
|
|
375
|
+
deleteExportArchive,
|
|
368
376
|
getPresignedDownloadUrl,
|
|
369
377
|
};
|
|
370
378
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { type OutboxSweepResult, sweepAbandonedOutboxAttachments, } from "./outbox-sweep.js";
|
|
2
2
|
export { type AuthorizeUploadInput, type AuthorizeUploadResult, authorizeUploadRequest, UPLOAD_ROUTE_PREFIX, UPLOAD_URL_SIGNING_LABEL, UPLOAD_URL_TTL_SECONDS, } from "./outbox-upload-url.js";
|
|
3
3
|
export { type SignedPathFailure, type SignedPathResult, signStoragePath, verifyStoragePath, } from "./signed-path.js";
|
|
4
|
-
export { buildBodyPartKey, buildDeduplicatedKey, buildExportArchiveKey, buildMessageBodyKey, buildOutboxAttachmentKey, buildOutboxAttachmentPrefix, buildParsedBodyKey, type ContentEncodingValue, type CreateOutboxAttachmentUploadUrlParams, computeChecksum, createMockStorageService, isStorageNotFoundError, type OutboxAttachmentListItem, type OutboxAttachmentUploadTarget, type ParsedAttachmentMeta, type ParsedBody, type ParsedContentStorageKey, type ParsedOutboxAttachmentKey, parseContentStorageKey, parseOutboxAttachmentKey, type StorageReference, type StorageService, type StorageTypeValue, type StoreBodyPartParams, type StoreDeduplicatedParams, type StoreMessageBodyParams, type StoreOutboxAttachmentParams, type StoreParsedBodyParams, } from "./storage.js";
|
|
4
|
+
export { buildBodyPartKey, buildDeduplicatedKey, buildExportArchiveKey, buildMessageBodyKey, buildOutboxAttachmentKey, buildOutboxAttachmentPrefix, buildParsedBodyKey, type ContentEncodingValue, type CreateOutboxAttachmentUploadUrlParams, computeChecksum, createMockStorageService, isStorageNotFoundError, messageObjectPrefixOf, type OutboxAttachmentListItem, type OutboxAttachmentUploadTarget, type ParsedAttachmentMeta, type ParsedBody, type ParsedContentStorageKey, type ParsedOutboxAttachmentKey, parseContentStorageKey, parseOutboxAttachmentKey, type StorageReference, type StorageService, type StorageTypeValue, type StoreBodyPartParams, type StoreDeduplicatedParams, type StoreMessageBodyParams, type StoreOutboxAttachmentParams, type StoreParsedBodyParams, } from "./storage.js";
|
|
5
5
|
export { buildStorageUri, type ParsedStorageUri, parseStorageUri, } from "./uri.js";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { sweepAbandonedOutboxAttachments, } from "./outbox-sweep.js";
|
|
2
2
|
export { authorizeUploadRequest, UPLOAD_ROUTE_PREFIX, UPLOAD_URL_SIGNING_LABEL, UPLOAD_URL_TTL_SECONDS, } from "./outbox-upload-url.js";
|
|
3
3
|
export { signStoragePath, verifyStoragePath, } from "./signed-path.js";
|
|
4
|
-
export { buildBodyPartKey, buildDeduplicatedKey, buildExportArchiveKey, buildMessageBodyKey, buildOutboxAttachmentKey, buildOutboxAttachmentPrefix, buildParsedBodyKey, computeChecksum, createMockStorageService, isStorageNotFoundError, parseContentStorageKey, parseOutboxAttachmentKey, } from "./storage.js";
|
|
4
|
+
export { buildBodyPartKey, buildDeduplicatedKey, buildExportArchiveKey, buildMessageBodyKey, buildOutboxAttachmentKey, buildOutboxAttachmentPrefix, buildParsedBodyKey, computeChecksum, createMockStorageService, isStorageNotFoundError, messageObjectPrefixOf, parseContentStorageKey, parseOutboxAttachmentKey, } from "./storage.js";
|
|
5
5
|
export { buildStorageUri, parseStorageUri, } from "./uri.js";
|
package/dist/storage.d.ts
CHANGED
|
@@ -190,6 +190,11 @@ export interface StorageService {
|
|
|
190
190
|
exists(uri: string): Promise<boolean>;
|
|
191
191
|
/** Delete content by URI */
|
|
192
192
|
delete(uri: string): Promise<void>;
|
|
193
|
+
/**
|
|
194
|
+
* Delete every object whose key starts with `keyPrefix`, a key path ending
|
|
195
|
+
* in `/`. Deleting a prefix that holds nothing is a no-op.
|
|
196
|
+
*/
|
|
197
|
+
deletePrefix(keyPrefix: string): Promise<void>;
|
|
193
198
|
/**
|
|
194
199
|
* Stream a finished export archive (already-compressed ZIP) to the export
|
|
195
200
|
* key for the given config + request, uncompressed at rest. The body is
|
|
@@ -197,6 +202,11 @@ export interface StorageService {
|
|
|
197
202
|
* storage key, which `getPresignedDownloadUrl` can then sign.
|
|
198
203
|
*/
|
|
199
204
|
storeExportArchiveStream(accountConfigId: string, exportRequestId: string, body: Readable): Promise<string>;
|
|
205
|
+
/**
|
|
206
|
+
* Delete the export archive of the given config + request. Deleting a
|
|
207
|
+
* missing archive is a no-op.
|
|
208
|
+
*/
|
|
209
|
+
deleteExportArchive(accountConfigId: string, exportRequestId: string): Promise<void>;
|
|
200
210
|
/**
|
|
201
211
|
* Generate a presigned URL that grants temporary GET access to a raw S3 key.
|
|
202
212
|
* Use for export archives — the key must already exist in the bucket.
|
|
@@ -235,6 +245,7 @@ export interface ParsedContentStorageKey {
|
|
|
235
245
|
* above). Returns null when the shape doesn't match.
|
|
236
246
|
*/
|
|
237
247
|
export declare const parseContentStorageKey: (storageKey: string) => ParsedContentStorageKey | null;
|
|
248
|
+
export declare const messageObjectPrefixOf: (bodyStorageUri: string) => string;
|
|
238
249
|
export declare const computeChecksum: (content: Buffer) => string;
|
|
239
250
|
export declare const buildExportArchiveKey: (accountConfigId: string, exportRequestId: string) => string;
|
|
240
251
|
export declare const isStorageNotFoundError: (error: unknown) => boolean;
|
package/dist/storage.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
import { Readable } from "node:stream";
|
|
3
3
|
import { ContentEncoding, StorageType } from "@remit/domain-enums";
|
|
4
|
+
import { parseStorageUri } from "./uri.js";
|
|
4
5
|
// Path builders - centralized path formatting per RFC 011 + #224.
|
|
5
6
|
// The outer segment is `accountConfigId` (Cognito-derived tenant scope) so
|
|
6
7
|
// the edge can verify cross-tenant isolation at the URL prefix without
|
|
@@ -51,6 +52,14 @@ export const parseContentStorageKey = (storageKey) => {
|
|
|
51
52
|
messageId: match[3],
|
|
52
53
|
};
|
|
53
54
|
};
|
|
55
|
+
const MESSAGE_OBJECT_PREFIX = /(?:^|\/)(accounts\/[^/]+\/(?:[^/]+\/)?messages\/[^/]+\/)[^/]+$/;
|
|
56
|
+
export const messageObjectPrefixOf = (bodyStorageUri) => {
|
|
57
|
+
const match = parseStorageUri(bodyStorageUri).storageKey.match(MESSAGE_OBJECT_PREFIX);
|
|
58
|
+
if (!match) {
|
|
59
|
+
throw new Error(`${bodyStorageUri} is not a message body key; its objects cannot be found to delete`);
|
|
60
|
+
}
|
|
61
|
+
return match[1];
|
|
62
|
+
};
|
|
54
63
|
export const computeChecksum = (content) => createHash("sha256").update(content).digest("hex");
|
|
55
64
|
export const buildExportArchiveKey = (accountConfigId, exportRequestId) => `exports/${accountConfigId}/${exportRequestId}/export.zip`;
|
|
56
65
|
export const isStorageNotFoundError = (error) => {
|
|
@@ -65,15 +74,16 @@ export const isStorageNotFoundError = (error) => {
|
|
|
65
74
|
return true;
|
|
66
75
|
return false;
|
|
67
76
|
};
|
|
77
|
+
const MOCK_ROOT = "s3://mock/";
|
|
68
78
|
export const createMockStorageService = () => {
|
|
69
79
|
const storage = new Map();
|
|
70
80
|
const storeInternal = (key, content) => {
|
|
71
81
|
const checksumSha256 = computeChecksum(content);
|
|
72
|
-
const uri =
|
|
82
|
+
const uri = `${MOCK_ROOT}${key}`;
|
|
73
83
|
storage.set(uri, content);
|
|
74
84
|
return {
|
|
75
85
|
uri,
|
|
76
|
-
storageType: StorageType.
|
|
86
|
+
storageType: StorageType.S3,
|
|
77
87
|
storageLocation: "mock",
|
|
78
88
|
storageKey: key,
|
|
79
89
|
sizeBytes: content.length,
|
|
@@ -94,21 +104,21 @@ export const createMockStorageService = () => {
|
|
|
94
104
|
return storeInternal(buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId), content);
|
|
95
105
|
};
|
|
96
106
|
const listOutboxAttachments = async (accountConfigId, accountId, outboxMessageId) => {
|
|
97
|
-
const prefix =
|
|
107
|
+
const prefix = `${MOCK_ROOT}${buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId)}`;
|
|
98
108
|
const items = [];
|
|
99
109
|
for (const [uri, content] of storage.entries()) {
|
|
100
110
|
if (!uri.startsWith(prefix))
|
|
101
111
|
continue;
|
|
102
112
|
items.push({
|
|
103
113
|
outboxAttachmentId: uri.slice(prefix.length),
|
|
104
|
-
key: uri.slice(
|
|
114
|
+
key: uri.slice(MOCK_ROOT.length),
|
|
105
115
|
sizeBytes: content.length,
|
|
106
116
|
});
|
|
107
117
|
}
|
|
108
118
|
return items;
|
|
109
119
|
};
|
|
110
120
|
const listOutboxDraftsWithAttachments = async (accountConfigId, accountId) => {
|
|
111
|
-
const root =
|
|
121
|
+
const root = `${MOCK_ROOT}accounts/${accountConfigId}/${accountId}/outbox/`;
|
|
112
122
|
const drafts = new Set();
|
|
113
123
|
for (const uri of storage.keys()) {
|
|
114
124
|
if (!uri.startsWith(root))
|
|
@@ -121,23 +131,23 @@ export const createMockStorageService = () => {
|
|
|
121
131
|
return [...drafts];
|
|
122
132
|
};
|
|
123
133
|
const deleteOutboxAttachment = async (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => {
|
|
124
|
-
storage.delete(
|
|
134
|
+
storage.delete(`${MOCK_ROOT}${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`);
|
|
125
135
|
};
|
|
126
136
|
const statOutboxAttachment = async (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => {
|
|
127
|
-
const content = storage.get(
|
|
137
|
+
const content = storage.get(`${MOCK_ROOT}${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`);
|
|
128
138
|
return content ? { sizeBytes: content.length } : null;
|
|
129
139
|
};
|
|
130
|
-
const retrieveOutboxAttachment = async (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => storage.get(
|
|
140
|
+
const retrieveOutboxAttachment = async (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => storage.get(`${MOCK_ROOT}${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`) ?? null;
|
|
131
141
|
const createOutboxAttachmentUploadUrl = async (params) => ({
|
|
132
142
|
uploadUrl: `mock://upload/${buildOutboxAttachmentKey(params.accountConfigId, params.accountId, params.outboxMessageId, params.outboxAttachmentId)}?max=${params.sizeBytes}&exp=${params.expiresAt}`,
|
|
133
143
|
});
|
|
134
|
-
const
|
|
135
|
-
const prefix = `mock://${buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId)}`;
|
|
144
|
+
const deletePrefix = async (keyPrefix) => {
|
|
136
145
|
for (const uri of [...storage.keys()]) {
|
|
137
|
-
if (uri.startsWith(
|
|
146
|
+
if (uri.startsWith(`${MOCK_ROOT}${keyPrefix}`))
|
|
138
147
|
storage.delete(uri);
|
|
139
148
|
}
|
|
140
149
|
};
|
|
150
|
+
const deleteOutboxAttachments = (accountConfigId, accountId, outboxMessageId) => deletePrefix(buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId));
|
|
141
151
|
const storeDeduplicated = async (params) => {
|
|
142
152
|
const { accountConfigId, accountId, content } = params;
|
|
143
153
|
const checksumSha256 = computeChecksum(content);
|
|
@@ -149,29 +159,29 @@ export const createMockStorageService = () => {
|
|
|
149
159
|
return storeInternal(buildParsedBodyKey(accountConfigId, accountId, messageId), content);
|
|
150
160
|
};
|
|
151
161
|
const retrieveParsedBody = async (accountConfigId, accountId, messageId) => {
|
|
152
|
-
const uri =
|
|
162
|
+
const uri = `${MOCK_ROOT}${buildParsedBodyKey(accountConfigId, accountId, messageId)}`;
|
|
153
163
|
const content = storage.get(uri);
|
|
154
164
|
if (!content)
|
|
155
165
|
return null;
|
|
156
166
|
return JSON.parse(content.toString("utf8"));
|
|
157
167
|
};
|
|
158
168
|
const retrieveBodyPart = async (accountConfigId, accountId, messageId, partPath) => {
|
|
159
|
-
const uri =
|
|
169
|
+
const uri = `${MOCK_ROOT}${buildBodyPartKey(accountConfigId, accountId, messageId, partPath)}`;
|
|
160
170
|
return storage.get(uri) ?? null;
|
|
161
171
|
};
|
|
162
172
|
const retrieveMessageBody = async (accountConfigId, accountId, messageId) => {
|
|
163
|
-
const uri =
|
|
173
|
+
const uri = `${MOCK_ROOT}${buildMessageBodyKey(accountConfigId, accountId, messageId)}`;
|
|
164
174
|
return storage.get(uri) ?? null;
|
|
165
175
|
};
|
|
166
176
|
const retrieveMessageBodyStream = async (accountConfigId, accountId, messageId) => {
|
|
167
|
-
const uri =
|
|
177
|
+
const uri = `${MOCK_ROOT}${buildMessageBodyKey(accountConfigId, accountId, messageId)}`;
|
|
168
178
|
const content = storage.get(uri);
|
|
169
179
|
if (!content)
|
|
170
180
|
return null;
|
|
171
181
|
return Readable.from(content);
|
|
172
182
|
};
|
|
173
183
|
const bodyPartExists = async (accountConfigId, accountId, messageId, partPath) => {
|
|
174
|
-
const uri =
|
|
184
|
+
const uri = `${MOCK_ROOT}${buildBodyPartKey(accountConfigId, accountId, messageId, partPath)}`;
|
|
175
185
|
return storage.has(uri);
|
|
176
186
|
};
|
|
177
187
|
return {
|
|
@@ -183,6 +193,7 @@ export const createMockStorageService = () => {
|
|
|
183
193
|
listOutboxAttachments,
|
|
184
194
|
listOutboxDraftsWithAttachments,
|
|
185
195
|
deleteOutboxAttachments,
|
|
196
|
+
deletePrefix,
|
|
186
197
|
deleteOutboxAttachment,
|
|
187
198
|
statOutboxAttachment,
|
|
188
199
|
retrieveOutboxAttachment,
|
|
@@ -211,9 +222,12 @@ export const createMockStorageService = () => {
|
|
|
211
222
|
for await (const chunk of body) {
|
|
212
223
|
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
213
224
|
}
|
|
214
|
-
storage.set(
|
|
225
|
+
storage.set(`${MOCK_ROOT}${key}`, Buffer.concat(chunks));
|
|
215
226
|
return key;
|
|
216
227
|
},
|
|
228
|
+
deleteExportArchive: async (accountConfigId, exportRequestId) => {
|
|
229
|
+
storage.delete(`${MOCK_ROOT}${buildExportArchiveKey(accountConfigId, exportRequestId)}`);
|
|
230
|
+
},
|
|
217
231
|
getPresignedDownloadUrl: async (key, _expiresInSeconds) => `mock://presigned/${key}`,
|
|
218
232
|
};
|
|
219
233
|
};
|