@remit/storage-service 0.0.15 → 0.0.17

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.
@@ -1,5 +1,5 @@
1
1
  import { createWriteStream } from "node:fs";
2
- import { mkdir, readdir, readFile, rm, stat, unlink, writeFile, } from "node:fs/promises";
2
+ import { mkdir, readdir, readFile, rm, rmdir, stat, unlink, writeFile, } from "node:fs/promises";
3
3
  import { dirname, join, resolve } from "node:path";
4
4
  import { Readable } from "node:stream";
5
5
  import { pipeline } from "node:stream/promises";
@@ -10,6 +10,17 @@ import { UPLOAD_ROUTE_PREFIX, UPLOAD_URL_SIGNING_LABEL, } from "../outbox-upload
10
10
  import { signStoragePath } from "../signed-path.js";
11
11
  import { buildBodyPartKey, buildDeduplicatedKey, buildExportArchiveKey, buildMessageBodyKey, buildOutboxAttachmentKey, buildOutboxAttachmentPrefix, buildParsedBodyKey, computeChecksum, isStorageNotFoundError, } from "../storage.js";
12
12
  const gzipAsync = promisify(gzip);
13
+ const DIRECTORY_IN_USE = new Set(["ENOENT", "ENOTEMPTY", "EEXIST"]);
14
+ const keepDirectoryInUse = (error) => {
15
+ if (typeof error === "object" &&
16
+ error !== null &&
17
+ "code" in error &&
18
+ typeof error.code === "string" &&
19
+ DIRECTORY_IN_USE.has(error.code)) {
20
+ return;
21
+ }
22
+ throw error;
23
+ };
13
24
  export const createFilesystemStorageService = (basePath, uploadUrls) => {
14
25
  const storeInternal = async (params) => {
15
26
  const { key, content, compress = true } = params;
@@ -168,8 +179,18 @@ export const createFilesystemStorageService = (basePath, uploadUrls) => {
168
179
  };
169
180
  };
170
181
  const deleteOutboxAttachments = async (accountConfigId, accountId, outboxMessageId) => {
171
- const dirPath = join(basePath, buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId));
172
- await rm(dirPath, { recursive: true, force: true });
182
+ await deletePrefix(buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId));
183
+ };
184
+ const deletePrefix = async (prefix) => {
185
+ await rm(join(basePath, prefix), { recursive: true, force: true });
186
+ };
187
+ const deleteKeys = async (keys) => {
188
+ const paths = keys.map((key) => join(basePath, key));
189
+ await Promise.all(paths.map((path) => rm(path, { force: true })));
190
+ const directories = [...new Set(paths.map((path) => dirname(path)))].sort((a, b) => b.length - a.length);
191
+ for (const directory of directories) {
192
+ await rmdir(directory).catch(keepDirectoryInUse);
193
+ }
173
194
  };
174
195
  const storeDeduplicated = (params) => {
175
196
  const { accountConfigId, accountId, content } = params;
@@ -242,6 +263,8 @@ export const createFilesystemStorageService = (basePath, uploadUrls) => {
242
263
  listOutboxAttachments,
243
264
  listOutboxDraftsWithAttachments,
244
265
  deleteOutboxAttachments,
266
+ deletePrefix,
267
+ deleteKeys,
245
268
  deleteOutboxAttachment,
246
269
  statOutboxAttachment,
247
270
  retrieveOutboxAttachment,
@@ -7,6 +7,7 @@ import { ContentEncoding, StorageType } from "@remit/domain-enums";
7
7
  import { buildBodyPartKey, buildDeduplicatedKey, buildExportArchiveKey, buildMessageBodyKey, buildOutboxAttachmentKey, buildOutboxAttachmentPrefix, buildParsedBodyKey, computeChecksum, isStorageNotFoundError, } from "../storage.js";
8
8
  import { parseStorageUri } from "../uri.js";
9
9
  const gzipAsync = promisify(gzip);
10
+ const STORAGE_DELETE_BATCH_SIZE = 1000;
10
11
  export const createS3StorageService = (client, bucketName) => {
11
12
  const storeInternal = async (params) => {
12
13
  const { key, content, contentType, compress = true } = params;
@@ -194,8 +195,7 @@ export const createS3StorageService = (client, bucketName) => {
194
195
  }), { expiresIn });
195
196
  return { uploadUrl };
196
197
  };
197
- const deleteOutboxAttachments = async (accountConfigId, accountId, outboxMessageId) => {
198
- const prefix = buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId);
198
+ const deletePrefix = async (prefix) => {
199
199
  let continuationToken;
200
200
  do {
201
201
  const response = await client.send(new ListObjectsV2Command({
@@ -217,6 +217,20 @@ export const createS3StorageService = (client, bucketName) => {
217
217
  : undefined;
218
218
  } while (continuationToken);
219
219
  };
220
+ const deleteKeys = async (keys) => {
221
+ for (let start = 0; start < keys.length; start += STORAGE_DELETE_BATCH_SIZE) {
222
+ const batch = keys.slice(start, start + STORAGE_DELETE_BATCH_SIZE);
223
+ const response = await client.send(new DeleteObjectsCommand({
224
+ Bucket: bucketName,
225
+ Delete: { Objects: batch.map((Key) => ({ Key })), Quiet: true },
226
+ }));
227
+ const failed = response.Errors ?? [];
228
+ if (failed.length > 0) {
229
+ throw new Error(`S3 refused to delete ${failed.length} of ${batch.length} objects, first ${failed[0].Key}: ${failed[0].Code} ${failed[0].Message}`);
230
+ }
231
+ }
232
+ };
233
+ const deleteOutboxAttachments = (accountConfigId, accountId, outboxMessageId) => deletePrefix(buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId));
220
234
  const storeDeduplicated = (params) => {
221
235
  const { accountConfigId, accountId, content, contentType } = params;
222
236
  const checksumSha256 = computeChecksum(content);
@@ -358,6 +372,8 @@ export const createS3StorageService = (client, bucketName) => {
358
372
  listOutboxAttachments,
359
373
  listOutboxDraftsWithAttachments,
360
374
  deleteOutboxAttachments,
375
+ deletePrefix,
376
+ deleteKeys,
361
377
  deleteOutboxAttachment,
362
378
  statOutboxAttachment,
363
379
  retrieveOutboxAttachment,
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, MATERIALIZED_SENTINEL_PART_PATH, MATERIALIZED_SENTINEL_V2_PART_PATH, type MessageObjectKeys, messageObjectKeysOf, 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, MATERIALIZED_SENTINEL_PART_PATH, MATERIALIZED_SENTINEL_V2_PART_PATH, messageObjectKeysOf, parseContentStorageKey, parseOutboxAttachmentKey, } from "./storage.js";
5
5
  export { buildStorageUri, parseStorageUri, } from "./uri.js";
package/dist/storage.d.ts CHANGED
@@ -190,6 +190,16 @@ 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>;
198
+ /**
199
+ * Delete the objects at `keys`, issuing one backend call per 1000 keys and
200
+ * no listing. A key that holds nothing is skipped.
201
+ */
202
+ deleteKeys(keys: readonly string[]): Promise<void>;
193
203
  /**
194
204
  * Stream a finished export archive (already-compressed ZIP) to the export
195
205
  * key for the given config + request, uncompressed at rest. The body is
@@ -240,6 +250,35 @@ export interface ParsedContentStorageKey {
240
250
  * above). Returns null when the shape doesn't match.
241
251
  */
242
252
  export declare const parseContentStorageKey: (storageKey: string) => ParsedContentStorageKey | null;
253
+ /**
254
+ * Sentinel object written under a message's parts prefix once lazy
255
+ * materialization has stored EVERY leaf. The read path checks it first with a
256
+ * single HEAD and early-returns, skipping the body.eml GET, the MIME re-parse,
257
+ * and the per-leaf HEADs on every warm re-open.
258
+ *
259
+ * Deliberately an S3-only marker (no Message-row flag). A leading dot keeps it
260
+ * out of any real IMAP section path (those are dot-separated digits).
261
+ */
262
+ export declare const MATERIALIZED_SENTINEL_PART_PATH = ".materialized";
263
+ /**
264
+ * The sentinel for a message carrying a forwarded message. Those parts were
265
+ * once stored under a layout in which the forwarded message's root overwrote
266
+ * its own row, so a `.materialized` written then vouches for objects at the
267
+ * wrong paths. Its own name makes every such message materialize once more,
268
+ * overwriting what is there; no other message is touched.
269
+ */
270
+ export declare const MATERIALIZED_SENTINEL_V2_PART_PATH = ".materialized.2";
271
+ export interface MessageObjectKeys {
272
+ own: string[];
273
+ shared: string[];
274
+ }
275
+ /**
276
+ * The storage keys a message's objects live at. `own` sit under the message's
277
+ * own id and no other message reads them. `shared` is the raw body
278
+ * `bodyStorageKey` names, which a copy shares with its source and every other
279
+ * copy, so it goes only once no row names it.
280
+ */
281
+ export declare const messageObjectKeysOf: (bodyStorageKey: string, messageId: string, partPaths: Iterable<string>) => MessageObjectKeys;
243
282
  export declare const computeChecksum: (content: Buffer) => string;
244
283
  export declare const buildExportArchiveKey: (accountConfigId: string, exportRequestId: string) => string;
245
284
  export declare const isStorageNotFoundError: (error: unknown) => boolean;
package/dist/storage.js CHANGED
@@ -1,14 +1,18 @@
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
7
8
  // loading any database record. The inner `accountId` distinguishes between
8
9
  // multiple IMAP-account records owned by the same tenant.
9
- export const buildMessageBodyKey = (accountConfigId, accountId, messageId) => `accounts/${accountConfigId}/${accountId}/messages/${messageId}/body.eml`;
10
- export const buildParsedBodyKey = (accountConfigId, accountId, messageId) => `accounts/${accountConfigId}/${accountId}/messages/${messageId}/parsed.json.gz`;
11
- export const buildBodyPartKey = (accountConfigId, accountId, messageId, partPath) => `accounts/${accountConfigId}/${accountId}/messages/${messageId}/parts/${partPath}`;
10
+ const MESSAGE_BODY_OBJECT = "body.eml";
11
+ const PARSED_BODY_OBJECT = "parsed.json.gz";
12
+ const BODY_PARTS_PREFIX = "parts/";
13
+ export const buildMessageBodyKey = (accountConfigId, accountId, messageId) => `accounts/${accountConfigId}/${accountId}/messages/${messageId}/${MESSAGE_BODY_OBJECT}`;
14
+ export const buildParsedBodyKey = (accountConfigId, accountId, messageId) => `accounts/${accountConfigId}/${accountId}/messages/${messageId}/${PARSED_BODY_OBJECT}`;
15
+ export const buildBodyPartKey = (accountConfigId, accountId, messageId, partPath) => `accounts/${accountConfigId}/${accountId}/messages/${messageId}/${BODY_PARTS_PREFIX}${partPath}`;
12
16
  export const buildOutboxAttachmentPrefix = (accountConfigId, accountId, outboxMessageId) => `accounts/${accountConfigId}/${accountId}/outbox/${outboxMessageId}/attachments/`;
13
17
  export const buildOutboxAttachmentKey = (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => `${buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId)}${outboxAttachmentId}`;
14
18
  // Every segment is a generated id, so the shape is closed rather than
@@ -51,6 +55,50 @@ export const parseContentStorageKey = (storageKey) => {
51
55
  messageId: match[3],
52
56
  };
53
57
  };
58
+ /**
59
+ * Sentinel object written under a message's parts prefix once lazy
60
+ * materialization has stored EVERY leaf. The read path checks it first with a
61
+ * single HEAD and early-returns, skipping the body.eml GET, the MIME re-parse,
62
+ * and the per-leaf HEADs on every warm re-open.
63
+ *
64
+ * Deliberately an S3-only marker (no Message-row flag). A leading dot keeps it
65
+ * out of any real IMAP section path (those are dot-separated digits).
66
+ */
67
+ export const MATERIALIZED_SENTINEL_PART_PATH = ".materialized";
68
+ /**
69
+ * The sentinel for a message carrying a forwarded message. Those parts were
70
+ * once stored under a layout in which the forwarded message's root overwrote
71
+ * its own row, so a `.materialized` written then vouches for objects at the
72
+ * wrong paths. Its own name makes every such message materialize once more,
73
+ * overwriting what is there; no other message is touched.
74
+ */
75
+ export const MATERIALIZED_SENTINEL_V2_PART_PATH = ".materialized.2";
76
+ const MESSAGE_BODY_KEY = /(?:^|\/)(accounts\/[^/]+\/(?:[^/]+\/)?messages\/)([^/]+)\/([^/]+)$/;
77
+ /**
78
+ * The storage keys a message's objects live at. `own` sit under the message's
79
+ * own id and no other message reads them. `shared` is the raw body
80
+ * `bodyStorageKey` names, which a copy shares with its source and every other
81
+ * copy, so it goes only once no row names it.
82
+ */
83
+ export const messageObjectKeysOf = (bodyStorageKey, messageId, partPaths) => {
84
+ const match = parseStorageUri(bodyStorageKey).storageKey.match(MESSAGE_BODY_KEY);
85
+ if (!match) {
86
+ throw new Error(`${bodyStorageKey} is not a message body key; its objects cannot be found to delete`);
87
+ }
88
+ const [, messages, ownerId, body] = match;
89
+ const own = `${messages}${messageId}/`;
90
+ return {
91
+ own: [
92
+ `${own}${PARSED_BODY_OBJECT}`,
93
+ ...[
94
+ MATERIALIZED_SENTINEL_PART_PATH,
95
+ MATERIALIZED_SENTINEL_V2_PART_PATH,
96
+ ...partPaths,
97
+ ].map((partPath) => `${own}${BODY_PARTS_PREFIX}${partPath}`),
98
+ ],
99
+ shared: [`${messages}${ownerId}/${body}`],
100
+ };
101
+ };
54
102
  export const computeChecksum = (content) => createHash("sha256").update(content).digest("hex");
55
103
  export const buildExportArchiveKey = (accountConfigId, exportRequestId) => `exports/${accountConfigId}/${exportRequestId}/export.zip`;
56
104
  export const isStorageNotFoundError = (error) => {
@@ -65,15 +113,16 @@ export const isStorageNotFoundError = (error) => {
65
113
  return true;
66
114
  return false;
67
115
  };
116
+ const MOCK_ROOT = "s3://mock/";
68
117
  export const createMockStorageService = () => {
69
118
  const storage = new Map();
70
119
  const storeInternal = (key, content) => {
71
120
  const checksumSha256 = computeChecksum(content);
72
- const uri = `mock://${key}`;
121
+ const uri = `${MOCK_ROOT}${key}`;
73
122
  storage.set(uri, content);
74
123
  return {
75
124
  uri,
76
- storageType: StorageType.Filesystem,
125
+ storageType: StorageType.S3,
77
126
  storageLocation: "mock",
78
127
  storageKey: key,
79
128
  sizeBytes: content.length,
@@ -94,21 +143,21 @@ export const createMockStorageService = () => {
94
143
  return storeInternal(buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId), content);
95
144
  };
96
145
  const listOutboxAttachments = async (accountConfigId, accountId, outboxMessageId) => {
97
- const prefix = `mock://${buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId)}`;
146
+ const prefix = `${MOCK_ROOT}${buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId)}`;
98
147
  const items = [];
99
148
  for (const [uri, content] of storage.entries()) {
100
149
  if (!uri.startsWith(prefix))
101
150
  continue;
102
151
  items.push({
103
152
  outboxAttachmentId: uri.slice(prefix.length),
104
- key: uri.slice("mock://".length),
153
+ key: uri.slice(MOCK_ROOT.length),
105
154
  sizeBytes: content.length,
106
155
  });
107
156
  }
108
157
  return items;
109
158
  };
110
159
  const listOutboxDraftsWithAttachments = async (accountConfigId, accountId) => {
111
- const root = `mock://accounts/${accountConfigId}/${accountId}/outbox/`;
160
+ const root = `${MOCK_ROOT}accounts/${accountConfigId}/${accountId}/outbox/`;
112
161
  const drafts = new Set();
113
162
  for (const uri of storage.keys()) {
114
163
  if (!uri.startsWith(root))
@@ -121,23 +170,27 @@ export const createMockStorageService = () => {
121
170
  return [...drafts];
122
171
  };
123
172
  const deleteOutboxAttachment = async (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => {
124
- storage.delete(`mock://${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`);
173
+ storage.delete(`${MOCK_ROOT}${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`);
125
174
  };
126
175
  const statOutboxAttachment = async (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => {
127
- const content = storage.get(`mock://${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`);
176
+ const content = storage.get(`${MOCK_ROOT}${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`);
128
177
  return content ? { sizeBytes: content.length } : null;
129
178
  };
130
- const retrieveOutboxAttachment = async (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => storage.get(`mock://${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`) ?? null;
179
+ const retrieveOutboxAttachment = async (accountConfigId, accountId, outboxMessageId, outboxAttachmentId) => storage.get(`${MOCK_ROOT}${buildOutboxAttachmentKey(accountConfigId, accountId, outboxMessageId, outboxAttachmentId)}`) ?? null;
131
180
  const createOutboxAttachmentUploadUrl = async (params) => ({
132
181
  uploadUrl: `mock://upload/${buildOutboxAttachmentKey(params.accountConfigId, params.accountId, params.outboxMessageId, params.outboxAttachmentId)}?max=${params.sizeBytes}&exp=${params.expiresAt}`,
133
182
  });
134
- const deleteOutboxAttachments = async (accountConfigId, accountId, outboxMessageId) => {
135
- const prefix = `mock://${buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId)}`;
183
+ const deletePrefix = async (keyPrefix) => {
136
184
  for (const uri of [...storage.keys()]) {
137
- if (uri.startsWith(prefix))
185
+ if (uri.startsWith(`${MOCK_ROOT}${keyPrefix}`))
138
186
  storage.delete(uri);
139
187
  }
140
188
  };
189
+ const deleteKeys = async (keys) => {
190
+ for (const key of keys)
191
+ storage.delete(`${MOCK_ROOT}${key}`);
192
+ };
193
+ const deleteOutboxAttachments = (accountConfigId, accountId, outboxMessageId) => deletePrefix(buildOutboxAttachmentPrefix(accountConfigId, accountId, outboxMessageId));
141
194
  const storeDeduplicated = async (params) => {
142
195
  const { accountConfigId, accountId, content } = params;
143
196
  const checksumSha256 = computeChecksum(content);
@@ -149,29 +202,29 @@ export const createMockStorageService = () => {
149
202
  return storeInternal(buildParsedBodyKey(accountConfigId, accountId, messageId), content);
150
203
  };
151
204
  const retrieveParsedBody = async (accountConfigId, accountId, messageId) => {
152
- const uri = `mock://${buildParsedBodyKey(accountConfigId, accountId, messageId)}`;
205
+ const uri = `${MOCK_ROOT}${buildParsedBodyKey(accountConfigId, accountId, messageId)}`;
153
206
  const content = storage.get(uri);
154
207
  if (!content)
155
208
  return null;
156
209
  return JSON.parse(content.toString("utf8"));
157
210
  };
158
211
  const retrieveBodyPart = async (accountConfigId, accountId, messageId, partPath) => {
159
- const uri = `mock://${buildBodyPartKey(accountConfigId, accountId, messageId, partPath)}`;
212
+ const uri = `${MOCK_ROOT}${buildBodyPartKey(accountConfigId, accountId, messageId, partPath)}`;
160
213
  return storage.get(uri) ?? null;
161
214
  };
162
215
  const retrieveMessageBody = async (accountConfigId, accountId, messageId) => {
163
- const uri = `mock://${buildMessageBodyKey(accountConfigId, accountId, messageId)}`;
216
+ const uri = `${MOCK_ROOT}${buildMessageBodyKey(accountConfigId, accountId, messageId)}`;
164
217
  return storage.get(uri) ?? null;
165
218
  };
166
219
  const retrieveMessageBodyStream = async (accountConfigId, accountId, messageId) => {
167
- const uri = `mock://${buildMessageBodyKey(accountConfigId, accountId, messageId)}`;
220
+ const uri = `${MOCK_ROOT}${buildMessageBodyKey(accountConfigId, accountId, messageId)}`;
168
221
  const content = storage.get(uri);
169
222
  if (!content)
170
223
  return null;
171
224
  return Readable.from(content);
172
225
  };
173
226
  const bodyPartExists = async (accountConfigId, accountId, messageId, partPath) => {
174
- const uri = `mock://${buildBodyPartKey(accountConfigId, accountId, messageId, partPath)}`;
227
+ const uri = `${MOCK_ROOT}${buildBodyPartKey(accountConfigId, accountId, messageId, partPath)}`;
175
228
  return storage.has(uri);
176
229
  };
177
230
  return {
@@ -183,6 +236,8 @@ export const createMockStorageService = () => {
183
236
  listOutboxAttachments,
184
237
  listOutboxDraftsWithAttachments,
185
238
  deleteOutboxAttachments,
239
+ deletePrefix,
240
+ deleteKeys,
186
241
  deleteOutboxAttachment,
187
242
  statOutboxAttachment,
188
243
  retrieveOutboxAttachment,
@@ -211,11 +266,11 @@ export const createMockStorageService = () => {
211
266
  for await (const chunk of body) {
212
267
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
213
268
  }
214
- storage.set(`mock://${key}`, Buffer.concat(chunks));
269
+ storage.set(`${MOCK_ROOT}${key}`, Buffer.concat(chunks));
215
270
  return key;
216
271
  },
217
272
  deleteExportArchive: async (accountConfigId, exportRequestId) => {
218
- storage.delete(`mock://${buildExportArchiveKey(accountConfigId, exportRequestId)}`);
273
+ storage.delete(`${MOCK_ROOT}${buildExportArchiveKey(accountConfigId, exportRequestId)}`);
219
274
  },
220
275
  getPresignedDownloadUrl: async (key, _expiresInSeconds) => `mock://presigned/${key}`,
221
276
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remit/storage-service",
3
- "version": "0.0.15",
3
+ "version": "0.0.17",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",