@trokky/trokky 3.4.1 → 3.5.0
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/adapters/s3-media/index-key.d.ts +52 -0
- package/dist/adapters/s3-media/index-key.d.ts.map +1 -0
- package/dist/adapters/s3-media/index-key.js +91 -0
- package/dist/adapters/s3-media/index-key.js.map +1 -0
- package/dist/adapters/s3-media/index.d.ts +3 -0
- package/dist/adapters/s3-media/index.d.ts.map +1 -0
- package/dist/adapters/s3-media/index.js +12 -0
- package/dist/adapters/s3-media/index.js.map +1 -0
- package/dist/adapters/s3-media/s3-client.d.ts +112 -0
- package/dist/adapters/s3-media/s3-client.d.ts.map +1 -0
- package/dist/adapters/s3-media/s3-client.js +261 -0
- package/dist/adapters/s3-media/s3-client.js.map +1 -0
- package/dist/adapters/s3-media/s3-media-adapter.d.ts +118 -0
- package/dist/adapters/s3-media/s3-media-adapter.d.ts.map +1 -0
- package/dist/adapters/s3-media/s3-media-adapter.js +631 -0
- package/dist/adapters/s3-media/s3-media-adapter.js.map +1 -0
- package/dist/adapters/s3-media/types.d.ts +63 -0
- package/dist/adapters/s3-media/types.d.ts.map +1 -0
- package/dist/adapters/s3-media/types.js +2 -0
- package/dist/adapters/s3-media/types.js.map +1 -0
- package/dist/core/types/storage-adapters.d.ts +7 -0
- package/dist/core/types/storage-adapters.d.ts.map +1 -1
- package/dist/integrations/express/config.d.ts +6 -1
- package/dist/integrations/express/config.d.ts.map +1 -1
- package/dist/integrations/express/config.js.map +1 -1
- package/dist/integrations/express/integration.js +3 -3
- package/dist/integrations/express/integration.js.map +1 -1
- package/package.json +6 -1
|
@@ -0,0 +1,631 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* S3-compatible media storage adapter.
|
|
3
|
+
*
|
|
4
|
+
* Talks SigV4 over HTTP, so it reaches R2 from a Node process, and MinIO, Backblaze B2, Spaces,
|
|
5
|
+
* Ceph or AWS from anywhere. `CloudflareR2Adapter` is the better choice *inside* a Worker — a
|
|
6
|
+
* binding is faster and needs no credentials — and the two share a key layout on purpose, so
|
|
7
|
+
* one bucket can be handed from a Worker to a Node server and back.
|
|
8
|
+
*
|
|
9
|
+
* The layout, identical to the R2 adapter's:
|
|
10
|
+
*
|
|
11
|
+
* files/<id> the bytes
|
|
12
|
+
* meta/<id>.json the record — the only authority for existence
|
|
13
|
+
* variants/<id>/<name> variant bytes, format in user metadata
|
|
14
|
+
* index/... the listing cache, see index-key.ts
|
|
15
|
+
*
|
|
16
|
+
* What differs, and why:
|
|
17
|
+
*
|
|
18
|
+
* - **The listing is a join, not a sweep.** R2's `list()` can return `customMetadata`;
|
|
19
|
+
* `ListObjectsV2` cannot return user metadata at all. So the sortable fields are encoded into
|
|
20
|
+
* keys under `index/`, and `listMedia` enumerates ids from `meta/`, reads fields from the
|
|
21
|
+
* index, and falls back to reading the record for any id the index does not cover.
|
|
22
|
+
* - **The index must never be the enumeration.** Writes go bytes → record → index and deletes
|
|
23
|
+
* reverse that, so an interrupted operation leaves a record with no index entry. If the index
|
|
24
|
+
* were authoritative that file would exist and never be listed — a ghost. Enumerating from
|
|
25
|
+
* `meta/` makes a missing entry cost one GET instead of hiding a file, and `cleanup()`
|
|
26
|
+
* rebuilds it.
|
|
27
|
+
* - **`listVariants` HEADs each variant.** The format lives in user metadata and a listing does
|
|
28
|
+
* not carry it. A file has a handful of variants, so this is a handful of requests, and it
|
|
29
|
+
* keeps the R2 invariant that variant keys carry no format — re-encoding a variant to another
|
|
30
|
+
* format overwrites it instead of leaving both behind.
|
|
31
|
+
*/
|
|
32
|
+
import { InvalidInputError, SecurityValidator, createLogger } from '../../core/index.js';
|
|
33
|
+
import { S3Client } from './s3-client.js';
|
|
34
|
+
import { decodeIndexKey, encodeIndexKey, entryPrefix, indexPrefix } from './index-key.js';
|
|
35
|
+
/** Matches the other media adapters' ceiling, so a file that uploads on one uploads on all. */
|
|
36
|
+
const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024;
|
|
37
|
+
/** Long enough to be useful in a page, short enough that a leaked URL expires. */
|
|
38
|
+
const DEFAULT_URL_EXPIRY_SECONDS = 900;
|
|
39
|
+
/** An extension goes into no key, but it is still validated so every adapter rejects the same input. */
|
|
40
|
+
const EXTENSION_PATTERN = /^[A-Za-z0-9]{1,10}$/;
|
|
41
|
+
/** Fields the caller may set through `updateFile` that belong to the record, not to `custom`. */
|
|
42
|
+
const RECORD_FIELDS = new Set(['filename', 'contentType', 'size', 'extension']);
|
|
43
|
+
/** Reads that fan out (records behind a listing, variant HEADs) run this many at a time. */
|
|
44
|
+
const READ_CONCURRENCY = 16;
|
|
45
|
+
export class S3MediaAdapter {
|
|
46
|
+
s3;
|
|
47
|
+
prefix;
|
|
48
|
+
publicBaseUrl;
|
|
49
|
+
urlExpiry;
|
|
50
|
+
maxFileSize;
|
|
51
|
+
silent;
|
|
52
|
+
logger = createLogger('adapter', 'S3MediaAdapter');
|
|
53
|
+
constructor(config) {
|
|
54
|
+
for (const field of ['endpoint', 'bucket', 'accessKeyId', 'secretAccessKey']) {
|
|
55
|
+
if (!config?.[field] || typeof config[field] !== 'string') {
|
|
56
|
+
throw new InvalidInputError(`S3MediaAdapter requires a ${field}`, field);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
this.s3 = new S3Client({
|
|
60
|
+
endpoint: config.endpoint,
|
|
61
|
+
bucket: config.bucket,
|
|
62
|
+
accessKeyId: config.accessKeyId,
|
|
63
|
+
secretAccessKey: config.secretAccessKey,
|
|
64
|
+
region: config.region ?? 'auto',
|
|
65
|
+
forcePathStyle: config.forcePathStyle ?? true
|
|
66
|
+
});
|
|
67
|
+
// A prefix becomes URL path segments, where `..` is an operator rather than a name: writes
|
|
68
|
+
// would climb out of the bucket while `list()` (which goes through URLSearchParams) would
|
|
69
|
+
// not, leaving a store whose reads and writes disagree.
|
|
70
|
+
if (config.prefix && !/^[A-Za-z0-9._~\-]+(?:\/[A-Za-z0-9._~\-]+)*\/?$/.test(config.prefix)) {
|
|
71
|
+
throw new InvalidInputError('prefix may contain only letters, digits, dot, dash, underscore, tilde and slash', 'prefix');
|
|
72
|
+
}
|
|
73
|
+
if (config.prefix?.split('/').includes('..')) {
|
|
74
|
+
throw new InvalidInputError('prefix may not contain a ".." segment', 'prefix');
|
|
75
|
+
}
|
|
76
|
+
this.prefix = config.prefix ? config.prefix.replace(/\/*$/, '/') : '';
|
|
77
|
+
this.publicBaseUrl = config.publicBaseUrl?.replace(/\/+$/, '');
|
|
78
|
+
this.urlExpiry = config.defaultUrlExpirySeconds ?? DEFAULT_URL_EXPIRY_SECONDS;
|
|
79
|
+
this.maxFileSize = config.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
80
|
+
this.silent = config.silent ?? false;
|
|
81
|
+
if (!this.silent) {
|
|
82
|
+
this.logger.info('S3MediaAdapter initialized', {
|
|
83
|
+
bucket: config.bucket,
|
|
84
|
+
prefix: this.prefix || '(none)'
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ==========================================================================
|
|
89
|
+
// KEYS
|
|
90
|
+
// ==========================================================================
|
|
91
|
+
fileKey(id) {
|
|
92
|
+
return `${this.prefix}files/${id}`;
|
|
93
|
+
}
|
|
94
|
+
recordKey(id) {
|
|
95
|
+
return `${this.prefix}meta/${id}.json`;
|
|
96
|
+
}
|
|
97
|
+
variantKey(parentId, variantName) {
|
|
98
|
+
return `${this.prefix}variants/${parentId}/${variantName}`;
|
|
99
|
+
}
|
|
100
|
+
variantPrefix(parentId) {
|
|
101
|
+
return `${this.prefix}variants/${parentId}/`;
|
|
102
|
+
}
|
|
103
|
+
recordPrefix() {
|
|
104
|
+
return `${this.prefix}meta/`;
|
|
105
|
+
}
|
|
106
|
+
// ==========================================================================
|
|
107
|
+
// FILE OPERATIONS
|
|
108
|
+
// ==========================================================================
|
|
109
|
+
async uploadFile(file, metadata) {
|
|
110
|
+
this.validateMediaMetadata(metadata);
|
|
111
|
+
const buffer = await file.arrayBuffer();
|
|
112
|
+
if (buffer.byteLength > this.maxFileSize) {
|
|
113
|
+
throw new InvalidInputError(`File too large (${buffer.byteLength} bytes). Maximum size is ${this.maxFileSize} bytes.`, 'size');
|
|
114
|
+
}
|
|
115
|
+
// New bytes make any existing variant a description of content that is gone.
|
|
116
|
+
await this.deleteVariantFiles(metadata.id);
|
|
117
|
+
// The previous index entry described the previous upload; its epoch no longer matches.
|
|
118
|
+
await this.deleteIndexEntries(metadata.id);
|
|
119
|
+
const now = new Date();
|
|
120
|
+
const record = {
|
|
121
|
+
id: metadata.id,
|
|
122
|
+
filename: metadata.filename,
|
|
123
|
+
contentType: metadata.contentType,
|
|
124
|
+
size: metadata.size,
|
|
125
|
+
extension: metadata.extension,
|
|
126
|
+
createdAt: now.toISOString(),
|
|
127
|
+
updatedAt: now.toISOString(),
|
|
128
|
+
custom: {}
|
|
129
|
+
};
|
|
130
|
+
await this.s3.put(this.fileKey(metadata.id), buffer, { contentType: metadata.contentType });
|
|
131
|
+
// The record lands before the index: until it does, nothing lists the file as present.
|
|
132
|
+
await this.writeRecord(record);
|
|
133
|
+
await this.writeIndexEntry(record);
|
|
134
|
+
if (!this.silent) {
|
|
135
|
+
this.logger.info('File uploaded', {
|
|
136
|
+
id: metadata.id,
|
|
137
|
+
filename: metadata.filename,
|
|
138
|
+
size: metadata.size
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
return this.toMediaFile(record);
|
|
142
|
+
}
|
|
143
|
+
async getFile(id) {
|
|
144
|
+
SecurityValidator.validateDocumentId(id);
|
|
145
|
+
const record = await this.readRecord(id);
|
|
146
|
+
return record ? this.toMediaFile(record) : null;
|
|
147
|
+
}
|
|
148
|
+
async updateFile(id, metadata) {
|
|
149
|
+
SecurityValidator.validateDocumentId(id);
|
|
150
|
+
const record = await this.readRecord(id);
|
|
151
|
+
if (!record) {
|
|
152
|
+
throw new Error(`Media file ${id} not found`);
|
|
153
|
+
}
|
|
154
|
+
const updated = {
|
|
155
|
+
...record,
|
|
156
|
+
custom: { ...record.custom },
|
|
157
|
+
updatedAt: new Date().toISOString()
|
|
158
|
+
};
|
|
159
|
+
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
160
|
+
// The id identifies the object; letting it be rewritten would orphan the bytes.
|
|
161
|
+
if (key === 'id')
|
|
162
|
+
continue;
|
|
163
|
+
if (RECORD_FIELDS.has(key)) {
|
|
164
|
+
// Coerced, because these are encoded into the index key and `decodeIndexKey` rejects an
|
|
165
|
+
// entry whose `size` is not a number. An uncoerced `size: '4242'` un-indexed the file
|
|
166
|
+
// permanently and made cleanup() non-convergent: it rebuilt an equally undecodable
|
|
167
|
+
// entry every run and deleted it again the next.
|
|
168
|
+
;
|
|
169
|
+
updated[key] =
|
|
170
|
+
key === 'size' ? toSize(value, record.size) : String(value);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
updated.custom[key] = value;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
await this.writeRecord(updated);
|
|
177
|
+
// The index encodes filename, contentType and size — `updateFile` can change all three, and
|
|
178
|
+
// a stale entry means a wrong filter, a wrong sort and a wrong total with nothing to heal it.
|
|
179
|
+
if (updated.filename !== record.filename ||
|
|
180
|
+
updated.contentType !== record.contentType ||
|
|
181
|
+
updated.size !== record.size) {
|
|
182
|
+
await this.deleteIndexEntries(id);
|
|
183
|
+
await this.writeIndexEntry(updated);
|
|
184
|
+
}
|
|
185
|
+
if (!this.silent) {
|
|
186
|
+
this.logger.info('File metadata updated', { id });
|
|
187
|
+
}
|
|
188
|
+
return this.toMediaFile(updated);
|
|
189
|
+
}
|
|
190
|
+
async getFileContent(id) {
|
|
191
|
+
SecurityValidator.validateDocumentId(id);
|
|
192
|
+
const response = await this.s3.get(this.fileKey(id));
|
|
193
|
+
return response ? response.arrayBuffer() : null;
|
|
194
|
+
}
|
|
195
|
+
async getFileUrl(id, options = {}) {
|
|
196
|
+
SecurityValidator.validateDocumentId(id);
|
|
197
|
+
if (!(await this.fileExists(id)))
|
|
198
|
+
return null;
|
|
199
|
+
if (this.publicBaseUrl)
|
|
200
|
+
return `${this.publicBaseUrl}/${this.fileKey(id)}`;
|
|
201
|
+
// Unlike a bucket binding, SigV4 can sign a URL — so an install with no public base still
|
|
202
|
+
// has one to give.
|
|
203
|
+
return this.s3.presign(this.fileKey(id), options.expiresIn ?? this.urlExpiry);
|
|
204
|
+
}
|
|
205
|
+
async fileExists(id) {
|
|
206
|
+
SecurityValidator.validateDocumentId(id);
|
|
207
|
+
return (await this.s3.head(this.recordKey(id))) !== null;
|
|
208
|
+
}
|
|
209
|
+
async getFileSize(id) {
|
|
210
|
+
SecurityValidator.validateDocumentId(id);
|
|
211
|
+
const record = await this.readRecord(id);
|
|
212
|
+
return record ? record.size : null;
|
|
213
|
+
}
|
|
214
|
+
async deleteFile(id) {
|
|
215
|
+
SecurityValidator.validateDocumentId(id);
|
|
216
|
+
const record = await this.readRecord(id);
|
|
217
|
+
if (!record) {
|
|
218
|
+
throw new Error(`Media file ${id} not found`);
|
|
219
|
+
}
|
|
220
|
+
await this.deleteVariantFiles(id);
|
|
221
|
+
// Index first, then the record, then the bytes: every step makes the file less visible than
|
|
222
|
+
// the last, so an interruption can only leave something cleanup() can reclaim.
|
|
223
|
+
await this.deleteIndexEntries(id);
|
|
224
|
+
await this.s3.delete(this.recordKey(id));
|
|
225
|
+
await this.s3.delete(this.fileKey(id));
|
|
226
|
+
if (!this.silent) {
|
|
227
|
+
this.logger.debug('File deleted', { id });
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// ==========================================================================
|
|
231
|
+
// LISTING
|
|
232
|
+
// ==========================================================================
|
|
233
|
+
async listMedia(options = {}) {
|
|
234
|
+
let entries = await this.listIndex();
|
|
235
|
+
if (options.contentType) {
|
|
236
|
+
const wanted = options.contentType;
|
|
237
|
+
entries = entries.filter(entry => entry.contentType.includes(wanted));
|
|
238
|
+
}
|
|
239
|
+
if (options.sizeRange) {
|
|
240
|
+
const { min, max } = options.sizeRange;
|
|
241
|
+
entries = entries.filter(entry => {
|
|
242
|
+
if (min !== undefined && entry.size < min)
|
|
243
|
+
return false;
|
|
244
|
+
if (max !== undefined && entry.size > max)
|
|
245
|
+
return false;
|
|
246
|
+
return true;
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
if (options.dateRange) {
|
|
250
|
+
const { from, to } = options.dateRange;
|
|
251
|
+
entries = entries.filter(entry => {
|
|
252
|
+
if (from && entry.createdAt < from)
|
|
253
|
+
return false;
|
|
254
|
+
if (to && entry.createdAt > to)
|
|
255
|
+
return false;
|
|
256
|
+
return true;
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
// No sort means newest first, matching the other media adapters and what the Studio shows.
|
|
260
|
+
this.sortIndex(entries, options.sort ?? 'date', options.sortDirection ?? (options.sort ? 'asc' : 'desc'));
|
|
261
|
+
const total = entries.length;
|
|
262
|
+
const offset = options.offset ?? 0;
|
|
263
|
+
const page = options.limit === undefined
|
|
264
|
+
? entries.slice(offset)
|
|
265
|
+
: entries.slice(offset, offset + options.limit);
|
|
266
|
+
// Only the page is read in full; the filters and the total came from the two listings alone.
|
|
267
|
+
const records = await this.mapWithConcurrency(page, entry => this.readRecord(entry.id));
|
|
268
|
+
const items = records
|
|
269
|
+
.filter((record) => record !== null)
|
|
270
|
+
.map(record => this.toMediaFile(record));
|
|
271
|
+
return { items, total };
|
|
272
|
+
}
|
|
273
|
+
// ==========================================================================
|
|
274
|
+
// VARIANT OPERATIONS
|
|
275
|
+
// ==========================================================================
|
|
276
|
+
async saveVariantFile(parentId, variantName, buffer, format) {
|
|
277
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
278
|
+
SecurityValidator.validateDocumentId(variantName);
|
|
279
|
+
if (!format || typeof format !== 'string' || !EXTENSION_PATTERN.test(format)) {
|
|
280
|
+
throw new InvalidInputError('Invalid variant format', 'format');
|
|
281
|
+
}
|
|
282
|
+
const key = this.variantKey(parentId, variantName);
|
|
283
|
+
// Buffer is a Uint8Array view; copy out so the store gets exactly this variant's bytes and
|
|
284
|
+
// never a window onto a larger pooled allocation.
|
|
285
|
+
const bytes = new Uint8Array(buffer.byteLength);
|
|
286
|
+
bytes.set(buffer);
|
|
287
|
+
await this.s3.put(key, bytes, {
|
|
288
|
+
contentType: `image/${format}`,
|
|
289
|
+
metadata: { format }
|
|
290
|
+
});
|
|
291
|
+
if (!this.silent) {
|
|
292
|
+
this.logger.info('Variant file saved', { parentId, variantName, format });
|
|
293
|
+
}
|
|
294
|
+
return key;
|
|
295
|
+
}
|
|
296
|
+
async getVariantContent(parentId, variantName) {
|
|
297
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
298
|
+
SecurityValidator.validateDocumentId(variantName);
|
|
299
|
+
const response = await this.s3.get(this.variantKey(parentId, variantName));
|
|
300
|
+
return response ? response.arrayBuffer() : null;
|
|
301
|
+
}
|
|
302
|
+
async getVariantUrl(parentId, variantName, options = {}) {
|
|
303
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
304
|
+
SecurityValidator.validateDocumentId(variantName);
|
|
305
|
+
const key = this.variantKey(parentId, variantName);
|
|
306
|
+
if (!(await this.s3.head(key)))
|
|
307
|
+
return null;
|
|
308
|
+
if (this.publicBaseUrl)
|
|
309
|
+
return `${this.publicBaseUrl}/${key}`;
|
|
310
|
+
return this.s3.presign(key, options.expiresIn ?? this.urlExpiry);
|
|
311
|
+
}
|
|
312
|
+
async listVariants(parentId) {
|
|
313
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
314
|
+
const prefix = this.variantPrefix(parentId);
|
|
315
|
+
const objects = await this.s3.listAll(prefix);
|
|
316
|
+
// A listing carries no user metadata, and the format lives there. A file has a handful of
|
|
317
|
+
// variants, so a HEAD each is proportionate — and the alternative, putting the format in the
|
|
318
|
+
// key, would let one variant exist twice in two formats.
|
|
319
|
+
return this.mapWithConcurrency(objects, async (object) => {
|
|
320
|
+
const head = await this.s3.head(object.key);
|
|
321
|
+
return {
|
|
322
|
+
name: object.key.slice(prefix.length),
|
|
323
|
+
format: head?.metadata.format ?? 'unknown',
|
|
324
|
+
size: object.size,
|
|
325
|
+
url: this.publicBaseUrl ? `${this.publicBaseUrl}/${object.key}` : undefined,
|
|
326
|
+
createdAt: object.lastModified
|
|
327
|
+
};
|
|
328
|
+
});
|
|
329
|
+
}
|
|
330
|
+
async deleteVariantFiles(parentId) {
|
|
331
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
332
|
+
const objects = await this.s3.listAll(this.variantPrefix(parentId));
|
|
333
|
+
if (objects.length === 0)
|
|
334
|
+
return;
|
|
335
|
+
await this.s3.deleteMany(objects.map(object => object.key));
|
|
336
|
+
if (!this.silent) {
|
|
337
|
+
this.logger.debug('Variant files deleted', { parentId, count: objects.length });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
async deleteVariant(parentId, variantName) {
|
|
341
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
342
|
+
SecurityValidator.validateDocumentId(variantName);
|
|
343
|
+
await this.s3.delete(this.variantKey(parentId, variantName));
|
|
344
|
+
}
|
|
345
|
+
// ==========================================================================
|
|
346
|
+
// UTILITY OPERATIONS
|
|
347
|
+
// ==========================================================================
|
|
348
|
+
async healthCheck() {
|
|
349
|
+
try {
|
|
350
|
+
// A one-key listing, not a HEAD on a missing key: a HEAD 404s identically whether the key
|
|
351
|
+
// is absent or the whole bucket is, so a mistyped bucket reported healthy and then
|
|
352
|
+
// reported an empty media library.
|
|
353
|
+
await this.s3.list(this.prefix, { maxKeys: 1 });
|
|
354
|
+
return true;
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
this.logger.error('Health check failed', error);
|
|
358
|
+
return false;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
async getStorageInfo() {
|
|
362
|
+
const entries = await this.listIndex();
|
|
363
|
+
const totalSize = entries.reduce((sum, entry) => sum + entry.size, 0);
|
|
364
|
+
return {
|
|
365
|
+
totalFiles: entries.length,
|
|
366
|
+
totalSize,
|
|
367
|
+
// Object storage is unmetered in size, so there is no quota and no free space to compute.
|
|
368
|
+
usedSpace: totalSize
|
|
369
|
+
};
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Reconcile the bucket against its records, which are the only authority.
|
|
373
|
+
*
|
|
374
|
+
* Three jobs, and the order they are listed in is the order of how much they matter:
|
|
375
|
+
*
|
|
376
|
+
* 1. **Rebuild missing index entries.** An upload interrupted after the record but before the
|
|
377
|
+
* index — or a delete interrupted the other way — leaves a file that `getFile` serves and
|
|
378
|
+
* no listing shows. `listMedia` already heals the symptom by reading the record; this
|
|
379
|
+
* removes the cost.
|
|
380
|
+
* 2. **Drop orphaned bytes.** Anything under `files/` or `variants/` whose id has no record.
|
|
381
|
+
* 3. **Drop stale index entries.** Unreachable by an interruption under these write orders,
|
|
382
|
+
* but reachable by a partial restore or by a re-upload whose old-entry delete failed.
|
|
383
|
+
*
|
|
384
|
+
* Returns the number of objects written or deleted.
|
|
385
|
+
*/
|
|
386
|
+
async cleanup() {
|
|
387
|
+
const records = await this.listRecordIds();
|
|
388
|
+
const live = new Set(records);
|
|
389
|
+
let touched = 0;
|
|
390
|
+
// 1. Derive the ONE key each record should have, and write it if it is not already there.
|
|
391
|
+
//
|
|
392
|
+
// Every record is read, not just the unindexed ones: an entry that disagrees with its
|
|
393
|
+
// record is invisible without the record to compare against, and testing only for
|
|
394
|
+
// absence left such an entry permanent — `listMedia` considers that id covered and never
|
|
395
|
+
// re-reads, so every filter, sort and total is served from the stale copy forever. That
|
|
396
|
+
// is what an `updateFile` interrupted between its two writes leaves behind.
|
|
397
|
+
const keep = new Map();
|
|
398
|
+
const present = new Set((await this.s3.listAll(indexPrefix(this.prefix))).map(object => object.key));
|
|
399
|
+
for (const record of await this.mapWithConcurrency(records, id => this.readRecord(id))) {
|
|
400
|
+
if (!record)
|
|
401
|
+
continue;
|
|
402
|
+
const canonical = encodeIndexKey(this.prefix, entryOf(record));
|
|
403
|
+
keep.set(record.id, canonical);
|
|
404
|
+
if (present.has(canonical))
|
|
405
|
+
continue;
|
|
406
|
+
await this.writeIndexEntry(record);
|
|
407
|
+
touched++;
|
|
408
|
+
}
|
|
409
|
+
// 2. Orphaned bytes: anything under files/ or variants/ whose id has no record.
|
|
410
|
+
const orphans = [];
|
|
411
|
+
const filePrefix = `${this.prefix}files/`;
|
|
412
|
+
for (const object of await this.s3.listAll(filePrefix)) {
|
|
413
|
+
if (!live.has(object.key.slice(filePrefix.length)))
|
|
414
|
+
orphans.push(object.key);
|
|
415
|
+
}
|
|
416
|
+
const variantRoot = `${this.prefix}variants/`;
|
|
417
|
+
for (const object of await this.s3.listAll(variantRoot)) {
|
|
418
|
+
const parentId = object.key.slice(variantRoot.length).split('/')[0];
|
|
419
|
+
if (!live.has(parentId))
|
|
420
|
+
orphans.push(object.key);
|
|
421
|
+
}
|
|
422
|
+
// 3. Every index object that is not the canonical key for a live record: unparseable,
|
|
423
|
+
// recordless, superseded, or a duplicate. The record decides, so this converges in one
|
|
424
|
+
// pass and does not depend on which of two tied entries a listing happens to return
|
|
425
|
+
// first.
|
|
426
|
+
for (const object of await this.s3.listAll(indexPrefix(this.prefix))) {
|
|
427
|
+
const entry = decodeIndexKey(this.prefix, object.key);
|
|
428
|
+
if (!entry || keep.get(entry.id) !== object.key)
|
|
429
|
+
orphans.push(object.key);
|
|
430
|
+
}
|
|
431
|
+
if (orphans.length > 0) {
|
|
432
|
+
await this.s3.deleteMany(orphans);
|
|
433
|
+
touched += orphans.length;
|
|
434
|
+
}
|
|
435
|
+
if (touched > 0 && !this.silent) {
|
|
436
|
+
this.logger.info('Cleanup reconciled the bucket', {
|
|
437
|
+
rebuilt: touched - orphans.length,
|
|
438
|
+
deleted: orphans.length
|
|
439
|
+
});
|
|
440
|
+
}
|
|
441
|
+
return touched;
|
|
442
|
+
}
|
|
443
|
+
// ==========================================================================
|
|
444
|
+
// PRIVATE HELPERS
|
|
445
|
+
// ==========================================================================
|
|
446
|
+
async writeRecord(record) {
|
|
447
|
+
await this.s3.put(this.recordKey(record.id), JSON.stringify(record), {
|
|
448
|
+
contentType: 'application/json'
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
async readRecord(id) {
|
|
452
|
+
const response = await this.s3.get(this.recordKey(id));
|
|
453
|
+
if (!response)
|
|
454
|
+
return null;
|
|
455
|
+
const text = await response.text();
|
|
456
|
+
try {
|
|
457
|
+
const parsed = JSON.parse(text);
|
|
458
|
+
return { ...parsed, custom: parsed.custom ?? {} };
|
|
459
|
+
}
|
|
460
|
+
catch (error) {
|
|
461
|
+
this.logger.warn(`Media record ${id} is not valid JSON`, error);
|
|
462
|
+
return null;
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async writeIndexEntry(record) {
|
|
466
|
+
await this.s3.put(encodeIndexKey(this.prefix, entryOf(record)), new Uint8Array(0));
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Every index key for an id, whatever epoch it carries.
|
|
470
|
+
*
|
|
471
|
+
* One prefix listing rather than a sweep of the whole index — which is the entire reason the
|
|
472
|
+
* key puts the id before the timestamp. This runs on every upload.
|
|
473
|
+
*/
|
|
474
|
+
async deleteIndexEntries(id) {
|
|
475
|
+
const keys = (await this.s3.listAll(entryPrefix(this.prefix, id))).map(object => object.key);
|
|
476
|
+
if (keys.length > 0)
|
|
477
|
+
await this.s3.deleteMany(keys);
|
|
478
|
+
}
|
|
479
|
+
async listRecordIds() {
|
|
480
|
+
const prefix = this.recordPrefix();
|
|
481
|
+
return (await this.s3.listAll(prefix))
|
|
482
|
+
.map(object => object.key.slice(prefix.length))
|
|
483
|
+
.filter(name => name.endsWith('.json'))
|
|
484
|
+
.map(name => name.slice(0, -'.json'.length));
|
|
485
|
+
}
|
|
486
|
+
/**
|
|
487
|
+
* Every record in the bucket, with its listable fields.
|
|
488
|
+
*
|
|
489
|
+
* Two key-only listings — `meta/` for the ids, `index/` for the fields — joined by id. The
|
|
490
|
+
* records are the authority for *which* files exist; the index only saves reading them. An id
|
|
491
|
+
* the index does not cover (a bucket written by the R2 adapter, a hand-copied object, an
|
|
492
|
+
* interrupted upload) falls back to a real read, so it is never silently invisible.
|
|
493
|
+
*/
|
|
494
|
+
async listIndex() {
|
|
495
|
+
const [ids, indexObjects] = await Promise.all([
|
|
496
|
+
this.listRecordIds(),
|
|
497
|
+
this.s3.listAll(indexPrefix(this.prefix))
|
|
498
|
+
]);
|
|
499
|
+
const byId = winningEntries(this.prefix, indexObjects);
|
|
500
|
+
const entries = [];
|
|
501
|
+
const needsRead = [];
|
|
502
|
+
for (const id of ids) {
|
|
503
|
+
const entry = byId.get(id);
|
|
504
|
+
if (entry)
|
|
505
|
+
entries.push(entry);
|
|
506
|
+
else
|
|
507
|
+
needsRead.push(id);
|
|
508
|
+
}
|
|
509
|
+
for (const record of await this.mapWithConcurrency(needsRead, id => this.readRecord(id))) {
|
|
510
|
+
if (!record)
|
|
511
|
+
continue;
|
|
512
|
+
entries.push({
|
|
513
|
+
id: record.id,
|
|
514
|
+
filename: record.filename,
|
|
515
|
+
contentType: record.contentType,
|
|
516
|
+
size: record.size,
|
|
517
|
+
createdAt: new Date(record.createdAt)
|
|
518
|
+
});
|
|
519
|
+
}
|
|
520
|
+
return entries;
|
|
521
|
+
}
|
|
522
|
+
/** Every request here is a network round trip, so fan out — but not without a ceiling. */
|
|
523
|
+
async mapWithConcurrency(items, map) {
|
|
524
|
+
const results = [];
|
|
525
|
+
for (let index = 0; index < items.length; index += READ_CONCURRENCY) {
|
|
526
|
+
results.push(...(await Promise.all(items.slice(index, index + READ_CONCURRENCY).map(map))));
|
|
527
|
+
}
|
|
528
|
+
return results;
|
|
529
|
+
}
|
|
530
|
+
sortIndex(entries, sortBy, direction) {
|
|
531
|
+
const pick = (entry) => {
|
|
532
|
+
switch (sortBy) {
|
|
533
|
+
case 'name':
|
|
534
|
+
return entry.filename.toLowerCase();
|
|
535
|
+
case 'size':
|
|
536
|
+
return entry.size;
|
|
537
|
+
case 'type':
|
|
538
|
+
return entry.contentType;
|
|
539
|
+
case 'date':
|
|
540
|
+
default:
|
|
541
|
+
return entry.createdAt.getTime();
|
|
542
|
+
}
|
|
543
|
+
};
|
|
544
|
+
entries.sort((a, b) => {
|
|
545
|
+
const left = pick(a);
|
|
546
|
+
const right = pick(b);
|
|
547
|
+
let comparison = 0;
|
|
548
|
+
if (left < right)
|
|
549
|
+
comparison = -1;
|
|
550
|
+
else if (left > right)
|
|
551
|
+
comparison = 1;
|
|
552
|
+
return direction === 'desc' ? -comparison : comparison;
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
toMediaFile(record) {
|
|
556
|
+
return {
|
|
557
|
+
id: record.id,
|
|
558
|
+
filename: record.filename,
|
|
559
|
+
contentType: record.contentType,
|
|
560
|
+
size: record.size,
|
|
561
|
+
metadata: {
|
|
562
|
+
// Caller fields first: the derived ones below are facts about the object and must win.
|
|
563
|
+
...record.custom,
|
|
564
|
+
path: this.fileKey(record.id),
|
|
565
|
+
extension: record.extension,
|
|
566
|
+
originalFilename: record.filename
|
|
567
|
+
},
|
|
568
|
+
_createdAt: new Date(record.createdAt)
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
validateMediaMetadata(metadata) {
|
|
572
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
573
|
+
throw new InvalidInputError('Media metadata is required', 'metadata');
|
|
574
|
+
}
|
|
575
|
+
// Rejects an empty, overlong or path-like id before it can become a key.
|
|
576
|
+
SecurityValidator.validateDocumentId(metadata.id);
|
|
577
|
+
if (!metadata.filename || typeof metadata.filename !== 'string') {
|
|
578
|
+
throw new InvalidInputError('Media metadata must have a valid filename', 'filename');
|
|
579
|
+
}
|
|
580
|
+
if (!metadata.contentType || typeof metadata.contentType !== 'string') {
|
|
581
|
+
throw new InvalidInputError('Media metadata must have a valid content type', 'contentType');
|
|
582
|
+
}
|
|
583
|
+
if (!metadata.extension ||
|
|
584
|
+
typeof metadata.extension !== 'string' ||
|
|
585
|
+
!EXTENSION_PATTERN.test(metadata.extension)) {
|
|
586
|
+
throw new InvalidInputError('Media metadata must have a valid extension', 'extension');
|
|
587
|
+
}
|
|
588
|
+
if (typeof metadata.size !== 'number' || metadata.size <= 0) {
|
|
589
|
+
throw new InvalidInputError('Media metadata must have a valid size', 'size');
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
/** `updateFile` may be handed anything; the index cannot encode a non-number size. */
|
|
594
|
+
function toSize(value, fallback) {
|
|
595
|
+
const size = Number(value);
|
|
596
|
+
return Number.isFinite(size) && size >= 0 ? size : fallback;
|
|
597
|
+
}
|
|
598
|
+
/** The index entry a record should have. One record, one canonical key. */
|
|
599
|
+
function entryOf(record) {
|
|
600
|
+
return {
|
|
601
|
+
id: record.id,
|
|
602
|
+
filename: record.filename,
|
|
603
|
+
contentType: record.contentType,
|
|
604
|
+
size: record.size,
|
|
605
|
+
createdAt: new Date(record.createdAt)
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* One entry per id, chosen the same way by every reader.
|
|
610
|
+
*
|
|
611
|
+
* Newest wins; equal epochs break on the key. Ties are ordinary rather than exotic —
|
|
612
|
+
* `updateFile` re-encodes with the record's original `createdAt` — and a strict `<` on its own
|
|
613
|
+
* left the winner up to listing order and made `cleanup()` delete none of the duplicates.
|
|
614
|
+
*/
|
|
615
|
+
function winningEntries(prefix, objects) {
|
|
616
|
+
const byId = new Map();
|
|
617
|
+
for (const object of objects) {
|
|
618
|
+
const entry = decodeIndexKey(prefix, object.key);
|
|
619
|
+
if (!entry)
|
|
620
|
+
continue;
|
|
621
|
+
const existing = byId.get(entry.id);
|
|
622
|
+
const better = !existing ||
|
|
623
|
+
existing.entry.createdAt < entry.createdAt ||
|
|
624
|
+
(existing.entry.createdAt.getTime() === entry.createdAt.getTime() &&
|
|
625
|
+
existing.key < object.key);
|
|
626
|
+
if (better)
|
|
627
|
+
byId.set(entry.id, { entry, key: object.key });
|
|
628
|
+
}
|
|
629
|
+
return new Map([...byId].map(([id, winner]) => [id, winner.entry]));
|
|
630
|
+
}
|
|
631
|
+
//# sourceMappingURL=s3-media-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"s3-media-adapter.js","sourceRoot":"","sources":["../../../src/adapters/s3-media/s3-media-adapter.ts"],"names":[],"mappings":"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"}
|