@trokky/trokky 3.0.0 → 3.2.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/cloudflare-d1/cloudflare-d1-adapter.d.ts +132 -0
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.d.ts.map +1 -0
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.js +811 -0
- package/dist/adapters/cloudflare-d1/cloudflare-d1-adapter.js.map +1 -0
- package/dist/adapters/cloudflare-d1/index.d.ts +5 -0
- package/dist/adapters/cloudflare-d1/index.d.ts.map +1 -0
- package/dist/adapters/cloudflare-d1/index.js +12 -0
- package/dist/adapters/cloudflare-d1/index.js.map +1 -0
- package/dist/adapters/cloudflare-d1/rate-limit-store.d.ts +46 -0
- package/dist/adapters/cloudflare-d1/rate-limit-store.d.ts.map +1 -0
- package/dist/adapters/cloudflare-d1/rate-limit-store.js +96 -0
- package/dist/adapters/cloudflare-d1/rate-limit-store.js.map +1 -0
- package/dist/adapters/cloudflare-d1/types.d.ts +69 -0
- package/dist/adapters/cloudflare-d1/types.d.ts.map +1 -0
- package/dist/adapters/cloudflare-d1/types.js +2 -0
- package/dist/adapters/cloudflare-d1/types.js.map +1 -0
- package/dist/adapters/cloudflare-r2/cloudflare-r2-adapter.d.ts +86 -0
- package/dist/adapters/cloudflare-r2/cloudflare-r2-adapter.d.ts.map +1 -0
- package/dist/adapters/cloudflare-r2/cloudflare-r2-adapter.js +493 -0
- package/dist/adapters/cloudflare-r2/cloudflare-r2-adapter.js.map +1 -0
- package/dist/adapters/cloudflare-r2/index.d.ts +3 -0
- package/dist/adapters/cloudflare-r2/index.d.ts.map +1 -0
- package/dist/adapters/cloudflare-r2/index.js +11 -0
- package/dist/adapters/cloudflare-r2/index.js.map +1 -0
- package/dist/adapters/cloudflare-r2/types.d.ts +43 -0
- package/dist/adapters/cloudflare-r2/types.d.ts.map +1 -0
- package/dist/adapters/cloudflare-r2/types.js +2 -0
- package/dist/adapters/cloudflare-r2/types.js.map +1 -0
- package/dist/core/media/image-processor.d.ts.map +1 -1
- package/dist/core/media/image-processor.js +5 -13
- package/dist/core/media/image-processor.js.map +1 -1
- package/dist/core/media/processors/sharp.d.ts.map +1 -1
- package/dist/core/media/processors/sharp.js +13 -2
- package/dist/core/media/processors/sharp.js.map +1 -1
- package/dist/core/media/processors/types.d.ts +1 -1
- package/dist/core/media/processors/types.d.ts.map +1 -1
- package/dist/core/security/rate-limit-store.d.ts +56 -0
- package/dist/core/security/rate-limit-store.d.ts.map +1 -0
- package/dist/core/security/rate-limit-store.js +55 -0
- package/dist/core/security/rate-limit-store.js.map +1 -0
- package/dist/core/security/rate-limiter.d.ts +30 -2
- package/dist/core/security/rate-limiter.d.ts.map +1 -1
- package/dist/core/security/rate-limiter.js +50 -19
- package/dist/core/security/rate-limiter.js.map +1 -1
- package/dist/core/services/media-service.d.ts +34 -0
- package/dist/core/services/media-service.d.ts.map +1 -1
- package/dist/core/services/media-service.js +172 -120
- package/dist/core/services/media-service.js.map +1 -1
- package/dist/core/utils/retry.d.ts +45 -0
- package/dist/core/utils/retry.d.ts.map +1 -0
- package/dist/core/utils/retry.js +47 -0
- package/dist/core/utils/retry.js.map +1 -0
- package/dist/integrations/express/config.d.ts +31 -14
- package/dist/integrations/express/config.d.ts.map +1 -1
- package/dist/integrations/express/config.js +25 -8
- package/dist/integrations/express/config.js.map +1 -1
- package/dist/integrations/express/integration.d.ts.map +1 -1
- package/dist/integrations/express/integration.js +13 -6
- package/dist/integrations/express/integration.js.map +1 -1
- package/dist/integrations/express/server.d.ts.map +1 -1
- package/dist/integrations/express/server.js +5 -2
- package/dist/integrations/express/server.js.map +1 -1
- package/dist/integrations/express/types.d.ts +0 -5
- package/dist/integrations/express/types.d.ts.map +1 -1
- package/dist/integrations/workers/adapter.d.ts +28 -0
- package/dist/integrations/workers/adapter.d.ts.map +1 -0
- package/dist/integrations/workers/adapter.js +111 -0
- package/dist/integrations/workers/adapter.js.map +1 -0
- package/dist/integrations/workers/index.d.ts +22 -0
- package/dist/integrations/workers/index.d.ts.map +1 -0
- package/dist/integrations/workers/index.js +60 -0
- package/dist/integrations/workers/index.js.map +1 -0
- package/dist/routes/auth/auth-flow-store.d.ts +17 -0
- package/dist/routes/auth/auth-flow-store.d.ts.map +1 -1
- package/dist/routes/auth/auth-flow-store.js +27 -0
- package/dist/routes/auth/auth-flow-store.js.map +1 -1
- package/dist/routes/auth/oauth.d.ts.map +1 -1
- package/dist/routes/auth/oauth.js +5 -12
- package/dist/routes/auth/oauth.js.map +1 -1
- package/dist/routes/auth/passkey.d.ts.map +1 -1
- package/dist/routes/auth/passkey.js +5 -8
- package/dist/routes/auth/passkey.js.map +1 -1
- package/dist/structure/validation/schemas.d.ts +69 -69
- package/package.json +20 -1
- package/dist/core/media/processors/cloudflare-images.d.ts +0 -83
- package/dist/core/media/processors/cloudflare-images.d.ts.map +0 -1
- package/dist/core/media/processors/cloudflare-images.js +0 -241
- package/dist/core/media/processors/cloudflare-images.js.map +0 -1
- package/dist/core/media/processors/cloudflare-transform-store.d.ts +0 -127
- package/dist/core/media/processors/cloudflare-transform-store.d.ts.map +0 -1
- package/dist/core/media/processors/cloudflare-transform-store.js +0 -237
- package/dist/core/media/processors/cloudflare-transform-store.js.map +0 -1
- package/dist/core/media/processors/cloudflare-transformations.d.ts +0 -81
- package/dist/core/media/processors/cloudflare-transformations.d.ts.map +0 -1
- package/dist/core/media/processors/cloudflare-transformations.js +0 -144
- package/dist/core/media/processors/cloudflare-transformations.js.map +0 -1
- package/dist/core/media/processors/workers-images.d.ts +0 -112
- package/dist/core/media/processors/workers-images.d.ts.map +0 -1
- package/dist/core/media/processors/workers-images.js +0 -257
- package/dist/core/media/processors/workers-images.js.map +0 -1
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cloudflare R2 media storage adapter.
|
|
3
|
+
*
|
|
4
|
+
* R2 is a flat key/value object store with no directories, no metadata queries and no
|
|
5
|
+
* transactions, which drives every choice below:
|
|
6
|
+
*
|
|
7
|
+
* - **Bytes and description are two objects.** `files/<id>` holds the content and
|
|
8
|
+
* `meta/<id>.json` holds the record. Updating a title must not rewrite a 12MB photo,
|
|
9
|
+
* and serving the photo must not parse JSON.
|
|
10
|
+
* - **The listing carries its own index.** The sortable and filterable fields are
|
|
11
|
+
* mirrored into each record object's `customMetadata`, so `listMedia` can filter, sort
|
|
12
|
+
* and count from one `list()` sweep and then read full records only for the page it is
|
|
13
|
+
* about to return. Without that, listing 900 files would mean 900 GETs.
|
|
14
|
+
* - **The record object is the source of truth for existence.** It is written last on
|
|
15
|
+
* upload and deleted first on delete, so a half-finished operation can leave an
|
|
16
|
+
* orphaned blob — which `cleanup()` reclaims — but never a listing entry pointing at
|
|
17
|
+
* bytes that are not there.
|
|
18
|
+
* - **Variant keys carry no format.** `variants/<id>/<name>` with the format in
|
|
19
|
+
* `customMetadata` means re-encoding a variant to another format overwrites it instead
|
|
20
|
+
* of leaving both behind, and reading one needs no lookup in the parent's metadata.
|
|
21
|
+
* - **Caller fields are namespaced under `custom`.** A caller passing `{ size: 0 }` to
|
|
22
|
+
* `updateFile` must not be able to shadow the real size in the listing index.
|
|
23
|
+
*/
|
|
24
|
+
import { InvalidInputError, SecurityValidator, createLogger } from '../../core/index.js';
|
|
25
|
+
/** Matches the filesystem adapter's ceiling, so a file that uploads on one uploads on both. */
|
|
26
|
+
const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024;
|
|
27
|
+
/** R2 caps a listing page at 1000; asking for fewer keeps each response small when metadata is included. */
|
|
28
|
+
const LIST_PAGE_SIZE = 500;
|
|
29
|
+
/** An extension goes into no key, but it is still validated so both adapters reject the same input. */
|
|
30
|
+
const EXTENSION_PATTERN = /^[A-Za-z0-9]{1,10}$/;
|
|
31
|
+
/** Fields the caller may set through `updateFile` that belong to the record, not to `custom`. */
|
|
32
|
+
const RECORD_FIELDS = new Set(['filename', 'contentType', 'size', 'extension']);
|
|
33
|
+
export class CloudflareR2Adapter {
|
|
34
|
+
bucket;
|
|
35
|
+
prefix;
|
|
36
|
+
publicBaseUrl;
|
|
37
|
+
maxFileSize;
|
|
38
|
+
silent;
|
|
39
|
+
logger = createLogger('adapter', 'CloudflareR2Adapter');
|
|
40
|
+
constructor(config) {
|
|
41
|
+
if (!config?.bucket) {
|
|
42
|
+
throw new InvalidInputError('CloudflareR2Adapter requires an R2 bucket binding', 'bucket');
|
|
43
|
+
}
|
|
44
|
+
this.bucket = config.bucket;
|
|
45
|
+
this.prefix = config.prefix ? config.prefix.replace(/\/*$/, '/') : '';
|
|
46
|
+
this.publicBaseUrl = config.publicBaseUrl?.replace(/\/+$/, '');
|
|
47
|
+
this.maxFileSize = config.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
|
|
48
|
+
this.silent = config.silent ?? false;
|
|
49
|
+
if (!this.silent) {
|
|
50
|
+
this.logger.info('CloudflareR2Adapter initialized', { prefix: this.prefix || '(none)' });
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// ==========================================================================
|
|
54
|
+
// KEYS
|
|
55
|
+
// ==========================================================================
|
|
56
|
+
fileKey(id) {
|
|
57
|
+
return `${this.prefix}files/${id}`;
|
|
58
|
+
}
|
|
59
|
+
recordKey(id) {
|
|
60
|
+
return `${this.prefix}meta/${id}.json`;
|
|
61
|
+
}
|
|
62
|
+
variantKey(parentId, variantName) {
|
|
63
|
+
return `${this.prefix}variants/${parentId}/${variantName}`;
|
|
64
|
+
}
|
|
65
|
+
variantPrefix(parentId) {
|
|
66
|
+
return `${this.prefix}variants/${parentId}/`;
|
|
67
|
+
}
|
|
68
|
+
// ==========================================================================
|
|
69
|
+
// FILE OPERATIONS
|
|
70
|
+
// ==========================================================================
|
|
71
|
+
async uploadFile(file, metadata) {
|
|
72
|
+
this.validateMediaMetadata(metadata);
|
|
73
|
+
const buffer = await file.arrayBuffer();
|
|
74
|
+
if (buffer.byteLength > this.maxFileSize) {
|
|
75
|
+
throw new InvalidInputError(`File too large (${buffer.byteLength} bytes). Maximum size is ${this.maxFileSize} bytes.`, 'size');
|
|
76
|
+
}
|
|
77
|
+
// New bytes make any existing variant a description of content that is gone.
|
|
78
|
+
await this.deleteVariantFiles(metadata.id);
|
|
79
|
+
const now = new Date();
|
|
80
|
+
const record = {
|
|
81
|
+
id: metadata.id,
|
|
82
|
+
filename: metadata.filename,
|
|
83
|
+
contentType: metadata.contentType,
|
|
84
|
+
size: metadata.size,
|
|
85
|
+
extension: metadata.extension,
|
|
86
|
+
createdAt: now.toISOString(),
|
|
87
|
+
updatedAt: now.toISOString(),
|
|
88
|
+
custom: {}
|
|
89
|
+
};
|
|
90
|
+
await this.bucket.put(this.fileKey(metadata.id), buffer, {
|
|
91
|
+
httpMetadata: { contentType: metadata.contentType }
|
|
92
|
+
});
|
|
93
|
+
// The record is written last: until it lands, nothing lists the file as present.
|
|
94
|
+
await this.writeRecord(record);
|
|
95
|
+
if (!this.silent) {
|
|
96
|
+
this.logger.info('File uploaded', {
|
|
97
|
+
id: metadata.id,
|
|
98
|
+
filename: metadata.filename,
|
|
99
|
+
size: metadata.size
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return this.toMediaFile(record);
|
|
103
|
+
}
|
|
104
|
+
async getFile(id) {
|
|
105
|
+
SecurityValidator.validateDocumentId(id);
|
|
106
|
+
const record = await this.readRecord(id);
|
|
107
|
+
return record ? this.toMediaFile(record) : null;
|
|
108
|
+
}
|
|
109
|
+
async updateFile(id, metadata) {
|
|
110
|
+
SecurityValidator.validateDocumentId(id);
|
|
111
|
+
const record = await this.readRecord(id);
|
|
112
|
+
if (!record) {
|
|
113
|
+
throw new Error(`Media file ${id} not found`);
|
|
114
|
+
}
|
|
115
|
+
const updated = {
|
|
116
|
+
...record,
|
|
117
|
+
custom: { ...record.custom },
|
|
118
|
+
updatedAt: new Date().toISOString()
|
|
119
|
+
};
|
|
120
|
+
for (const [key, value] of Object.entries(metadata ?? {})) {
|
|
121
|
+
// The id identifies the object; letting it be rewritten would orphan the bytes.
|
|
122
|
+
if (key === 'id')
|
|
123
|
+
continue;
|
|
124
|
+
if (RECORD_FIELDS.has(key)) {
|
|
125
|
+
;
|
|
126
|
+
updated[key] = value;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
updated.custom[key] = value;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
await this.writeRecord(updated);
|
|
133
|
+
if (!this.silent) {
|
|
134
|
+
this.logger.info('File metadata updated', { id });
|
|
135
|
+
}
|
|
136
|
+
return this.toMediaFile(updated);
|
|
137
|
+
}
|
|
138
|
+
async getFileContent(id) {
|
|
139
|
+
SecurityValidator.validateDocumentId(id);
|
|
140
|
+
const object = (await this.bucket.get(this.fileKey(id)));
|
|
141
|
+
if (!object)
|
|
142
|
+
return null;
|
|
143
|
+
return object.arrayBuffer();
|
|
144
|
+
}
|
|
145
|
+
async getFileUrl(id) {
|
|
146
|
+
SecurityValidator.validateDocumentId(id);
|
|
147
|
+
// An R2 bucket binding cannot presign; without a public base there is no URL to give.
|
|
148
|
+
if (!this.publicBaseUrl)
|
|
149
|
+
return null;
|
|
150
|
+
if (!(await this.fileExists(id)))
|
|
151
|
+
return null;
|
|
152
|
+
return `${this.publicBaseUrl}/${this.fileKey(id)}`;
|
|
153
|
+
}
|
|
154
|
+
async fileExists(id) {
|
|
155
|
+
SecurityValidator.validateDocumentId(id);
|
|
156
|
+
return (await this.bucket.head(this.recordKey(id))) !== null;
|
|
157
|
+
}
|
|
158
|
+
async getFileSize(id) {
|
|
159
|
+
SecurityValidator.validateDocumentId(id);
|
|
160
|
+
const record = await this.readRecord(id);
|
|
161
|
+
return record ? record.size : null;
|
|
162
|
+
}
|
|
163
|
+
async deleteFile(id) {
|
|
164
|
+
SecurityValidator.validateDocumentId(id);
|
|
165
|
+
const record = await this.readRecord(id);
|
|
166
|
+
if (!record) {
|
|
167
|
+
throw new Error(`Media file ${id} not found`);
|
|
168
|
+
}
|
|
169
|
+
await this.deleteVariantFiles(id);
|
|
170
|
+
// The record goes first: if the blob delete fails, the file is already invisible to
|
|
171
|
+
// every listing and `cleanup()` can reclaim the bytes later.
|
|
172
|
+
await this.bucket.delete(this.recordKey(id));
|
|
173
|
+
await this.bucket.delete(this.fileKey(id));
|
|
174
|
+
if (!this.silent) {
|
|
175
|
+
this.logger.debug('File deleted', { id });
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
// ==========================================================================
|
|
179
|
+
// LISTING
|
|
180
|
+
// ==========================================================================
|
|
181
|
+
async listMedia(options = {}) {
|
|
182
|
+
let entries = await this.listIndex();
|
|
183
|
+
if (options.contentType) {
|
|
184
|
+
const wanted = options.contentType;
|
|
185
|
+
entries = entries.filter(entry => entry.contentType.includes(wanted));
|
|
186
|
+
}
|
|
187
|
+
if (options.sizeRange) {
|
|
188
|
+
const { min, max } = options.sizeRange;
|
|
189
|
+
entries = entries.filter(entry => {
|
|
190
|
+
if (min !== undefined && entry.size < min)
|
|
191
|
+
return false;
|
|
192
|
+
if (max !== undefined && entry.size > max)
|
|
193
|
+
return false;
|
|
194
|
+
return true;
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (options.dateRange) {
|
|
198
|
+
const { from, to } = options.dateRange;
|
|
199
|
+
entries = entries.filter(entry => {
|
|
200
|
+
if (from && entry.createdAt < from)
|
|
201
|
+
return false;
|
|
202
|
+
if (to && entry.createdAt > to)
|
|
203
|
+
return false;
|
|
204
|
+
return true;
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
// No sort means newest first, matching the filesystem adapter and what the Studio shows.
|
|
208
|
+
this.sortIndex(entries, options.sort ?? 'date', options.sortDirection ?? (options.sort ? 'asc' : 'desc'));
|
|
209
|
+
const total = entries.length;
|
|
210
|
+
const offset = options.offset ?? 0;
|
|
211
|
+
const page = options.limit === undefined
|
|
212
|
+
? entries.slice(offset)
|
|
213
|
+
: entries.slice(offset, offset + options.limit);
|
|
214
|
+
// Only the page is read in full; the filters and the total came from the listing alone.
|
|
215
|
+
const records = await Promise.all(page.map(entry => this.readRecord(entry.id)));
|
|
216
|
+
const items = records
|
|
217
|
+
.filter((record) => record !== null)
|
|
218
|
+
.map(record => this.toMediaFile(record));
|
|
219
|
+
return { items, total };
|
|
220
|
+
}
|
|
221
|
+
// ==========================================================================
|
|
222
|
+
// VARIANT OPERATIONS
|
|
223
|
+
// ==========================================================================
|
|
224
|
+
async saveVariantFile(parentId, variantName, buffer, format) {
|
|
225
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
226
|
+
SecurityValidator.validateDocumentId(variantName);
|
|
227
|
+
if (!format || typeof format !== 'string' || !EXTENSION_PATTERN.test(format)) {
|
|
228
|
+
throw new InvalidInputError('Invalid variant format', 'format');
|
|
229
|
+
}
|
|
230
|
+
const key = this.variantKey(parentId, variantName);
|
|
231
|
+
// Buffer is a Uint8Array view; copy out so R2 stores exactly this variant's bytes and
|
|
232
|
+
// never a window onto a larger pooled allocation.
|
|
233
|
+
const bytes = new Uint8Array(buffer.byteLength);
|
|
234
|
+
bytes.set(buffer);
|
|
235
|
+
await this.bucket.put(key, bytes, {
|
|
236
|
+
httpMetadata: { contentType: `image/${format}` },
|
|
237
|
+
customMetadata: { format }
|
|
238
|
+
});
|
|
239
|
+
if (!this.silent) {
|
|
240
|
+
this.logger.info('Variant file saved', { parentId, variantName, format });
|
|
241
|
+
}
|
|
242
|
+
return key;
|
|
243
|
+
}
|
|
244
|
+
async getVariantContent(parentId, variantName) {
|
|
245
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
246
|
+
SecurityValidator.validateDocumentId(variantName);
|
|
247
|
+
const object = (await this.bucket.get(this.variantKey(parentId, variantName)));
|
|
248
|
+
if (!object)
|
|
249
|
+
return null;
|
|
250
|
+
return object.arrayBuffer();
|
|
251
|
+
}
|
|
252
|
+
async getVariantUrl(parentId, variantName) {
|
|
253
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
254
|
+
SecurityValidator.validateDocumentId(variantName);
|
|
255
|
+
if (!this.publicBaseUrl)
|
|
256
|
+
return null;
|
|
257
|
+
if (!(await this.bucket.head(this.variantKey(parentId, variantName))))
|
|
258
|
+
return null;
|
|
259
|
+
return `${this.publicBaseUrl}/${this.variantKey(parentId, variantName)}`;
|
|
260
|
+
}
|
|
261
|
+
async listVariants(parentId) {
|
|
262
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
263
|
+
const prefix = this.variantPrefix(parentId);
|
|
264
|
+
// The format lives only in customMetadata, which R2 omits from a listing unless asked.
|
|
265
|
+
const objects = await this.listAll(prefix, true);
|
|
266
|
+
return objects.map(object => ({
|
|
267
|
+
name: object.key.slice(prefix.length),
|
|
268
|
+
format: object.customMetadata?.format ?? 'unknown',
|
|
269
|
+
size: object.size,
|
|
270
|
+
url: this.publicBaseUrl ? `${this.publicBaseUrl}/${object.key}` : undefined,
|
|
271
|
+
createdAt: object.uploaded
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
async deleteVariantFiles(parentId) {
|
|
275
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
276
|
+
const objects = await this.listAll(this.variantPrefix(parentId));
|
|
277
|
+
if (objects.length === 0)
|
|
278
|
+
return;
|
|
279
|
+
await this.deleteKeys(objects.map(object => object.key));
|
|
280
|
+
if (!this.silent) {
|
|
281
|
+
this.logger.debug('Variant files deleted', { parentId, count: objects.length });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
async deleteVariant(parentId, variantName) {
|
|
285
|
+
SecurityValidator.validateDocumentId(parentId);
|
|
286
|
+
SecurityValidator.validateDocumentId(variantName);
|
|
287
|
+
await this.bucket.delete(this.variantKey(parentId, variantName));
|
|
288
|
+
}
|
|
289
|
+
// ==========================================================================
|
|
290
|
+
// UTILITY OPERATIONS
|
|
291
|
+
// ==========================================================================
|
|
292
|
+
async healthCheck() {
|
|
293
|
+
try {
|
|
294
|
+
// A head on a key that need not exist proves the binding answers without writing.
|
|
295
|
+
await this.bucket.head(`${this.prefix}.trokky-health`);
|
|
296
|
+
return true;
|
|
297
|
+
}
|
|
298
|
+
catch (error) {
|
|
299
|
+
this.logger.error('Health check failed', error);
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
async getStorageInfo() {
|
|
304
|
+
const entries = await this.listIndex();
|
|
305
|
+
const totalSize = entries.reduce((sum, entry) => sum + entry.size, 0);
|
|
306
|
+
return {
|
|
307
|
+
totalFiles: entries.length,
|
|
308
|
+
totalSize,
|
|
309
|
+
// R2 is unmetered in size, so there is no quota to report and no free space to compute.
|
|
310
|
+
usedSpace: totalSize
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
/**
|
|
314
|
+
* Delete blobs and variants no record points at any more.
|
|
315
|
+
*
|
|
316
|
+
* These accumulate when a delete or an upload is interrupted between its two writes. The
|
|
317
|
+
* record objects are the authority: anything under `files/` or `variants/` whose id has no
|
|
318
|
+
* record is unreachable and safe to drop.
|
|
319
|
+
*/
|
|
320
|
+
async cleanup() {
|
|
321
|
+
const live = new Set((await this.listIndex()).map(entry => entry.id));
|
|
322
|
+
const orphans = [];
|
|
323
|
+
const filePrefix = `${this.prefix}files/`;
|
|
324
|
+
for (const object of await this.listAll(filePrefix)) {
|
|
325
|
+
if (!live.has(object.key.slice(filePrefix.length)))
|
|
326
|
+
orphans.push(object.key);
|
|
327
|
+
}
|
|
328
|
+
const variantRoot = `${this.prefix}variants/`;
|
|
329
|
+
for (const object of await this.listAll(variantRoot)) {
|
|
330
|
+
const parentId = object.key.slice(variantRoot.length).split('/')[0];
|
|
331
|
+
if (!live.has(parentId))
|
|
332
|
+
orphans.push(object.key);
|
|
333
|
+
}
|
|
334
|
+
if (orphans.length > 0) {
|
|
335
|
+
await this.deleteKeys(orphans);
|
|
336
|
+
if (!this.silent) {
|
|
337
|
+
this.logger.info('Cleaned up orphaned objects', { count: orphans.length });
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return orphans.length;
|
|
341
|
+
}
|
|
342
|
+
// ==========================================================================
|
|
343
|
+
// PRIVATE HELPERS
|
|
344
|
+
// ==========================================================================
|
|
345
|
+
async writeRecord(record) {
|
|
346
|
+
await this.bucket.put(this.recordKey(record.id), JSON.stringify(record), {
|
|
347
|
+
httpMetadata: { contentType: 'application/json' },
|
|
348
|
+
customMetadata: {
|
|
349
|
+
id: record.id,
|
|
350
|
+
filename: record.filename,
|
|
351
|
+
contentType: record.contentType,
|
|
352
|
+
size: String(record.size),
|
|
353
|
+
createdAt: record.createdAt
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
async readRecord(id) {
|
|
358
|
+
const object = (await this.bucket.get(this.recordKey(id)));
|
|
359
|
+
if (!object)
|
|
360
|
+
return null;
|
|
361
|
+
const text = await object.text();
|
|
362
|
+
try {
|
|
363
|
+
const parsed = JSON.parse(text);
|
|
364
|
+
return { ...parsed, custom: parsed.custom ?? {} };
|
|
365
|
+
}
|
|
366
|
+
catch (error) {
|
|
367
|
+
this.logger.warn(`Media record ${id} is not valid JSON`, error);
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
/**
|
|
372
|
+
* Every record in the bucket, read from the listing's customMetadata rather than from the
|
|
373
|
+
* objects themselves. A record written without that index (an older adapter, a manual copy)
|
|
374
|
+
* falls back to a real read so it is not silently invisible.
|
|
375
|
+
*/
|
|
376
|
+
async listIndex() {
|
|
377
|
+
const recordPrefix = `${this.prefix}meta/`;
|
|
378
|
+
const objects = await this.listAll(recordPrefix, true);
|
|
379
|
+
const entries = [];
|
|
380
|
+
const needsRead = [];
|
|
381
|
+
for (const object of objects) {
|
|
382
|
+
const id = object.key.slice(recordPrefix.length).replace(/\.json$/, '');
|
|
383
|
+
const indexed = object.customMetadata;
|
|
384
|
+
if (indexed?.contentType && indexed.size !== undefined && indexed.createdAt) {
|
|
385
|
+
entries.push({
|
|
386
|
+
id,
|
|
387
|
+
filename: indexed.filename ?? id,
|
|
388
|
+
contentType: indexed.contentType,
|
|
389
|
+
size: Number(indexed.size),
|
|
390
|
+
createdAt: new Date(indexed.createdAt)
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
else {
|
|
394
|
+
needsRead.push(id);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
for (const record of await Promise.all(needsRead.map(id => this.readRecord(id)))) {
|
|
398
|
+
if (!record)
|
|
399
|
+
continue;
|
|
400
|
+
entries.push({
|
|
401
|
+
id: record.id,
|
|
402
|
+
filename: record.filename,
|
|
403
|
+
contentType: record.contentType,
|
|
404
|
+
size: record.size,
|
|
405
|
+
createdAt: new Date(record.createdAt)
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return entries;
|
|
409
|
+
}
|
|
410
|
+
/** Walk every page of a prefix; R2 truncates, and a partial listing would mis-count totals. */
|
|
411
|
+
async listAll(prefix, includeCustomMetadata = false) {
|
|
412
|
+
const objects = [];
|
|
413
|
+
let cursor;
|
|
414
|
+
do {
|
|
415
|
+
const page = await this.bucket.list({
|
|
416
|
+
prefix,
|
|
417
|
+
limit: LIST_PAGE_SIZE,
|
|
418
|
+
cursor,
|
|
419
|
+
...(includeCustomMetadata ? { include: ['customMetadata'] } : {})
|
|
420
|
+
});
|
|
421
|
+
objects.push(...page.objects);
|
|
422
|
+
cursor = page.truncated ? page.cursor : undefined;
|
|
423
|
+
} while (cursor);
|
|
424
|
+
return objects;
|
|
425
|
+
}
|
|
426
|
+
/** R2 accepts up to 1000 keys per delete; anything longer is chunked. */
|
|
427
|
+
async deleteKeys(keys) {
|
|
428
|
+
for (let index = 0; index < keys.length; index += 1000) {
|
|
429
|
+
await this.bucket.delete(keys.slice(index, index + 1000));
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
sortIndex(entries, sortBy, direction) {
|
|
433
|
+
const pick = (entry) => {
|
|
434
|
+
switch (sortBy) {
|
|
435
|
+
case 'name':
|
|
436
|
+
return entry.filename.toLowerCase();
|
|
437
|
+
case 'size':
|
|
438
|
+
return entry.size;
|
|
439
|
+
case 'type':
|
|
440
|
+
return entry.contentType;
|
|
441
|
+
case 'date':
|
|
442
|
+
default:
|
|
443
|
+
return entry.createdAt.getTime();
|
|
444
|
+
}
|
|
445
|
+
};
|
|
446
|
+
entries.sort((a, b) => {
|
|
447
|
+
const left = pick(a);
|
|
448
|
+
const right = pick(b);
|
|
449
|
+
let comparison = 0;
|
|
450
|
+
if (left < right)
|
|
451
|
+
comparison = -1;
|
|
452
|
+
else if (left > right)
|
|
453
|
+
comparison = 1;
|
|
454
|
+
return direction === 'desc' ? -comparison : comparison;
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
toMediaFile(record) {
|
|
458
|
+
return {
|
|
459
|
+
id: record.id,
|
|
460
|
+
filename: record.filename,
|
|
461
|
+
contentType: record.contentType,
|
|
462
|
+
size: record.size,
|
|
463
|
+
metadata: {
|
|
464
|
+
// Caller fields first: the derived ones below are facts about the object and must win.
|
|
465
|
+
...record.custom,
|
|
466
|
+
path: this.fileKey(record.id),
|
|
467
|
+
extension: record.extension,
|
|
468
|
+
originalFilename: record.filename
|
|
469
|
+
},
|
|
470
|
+
_createdAt: new Date(record.createdAt)
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
validateMediaMetadata(metadata) {
|
|
474
|
+
if (!metadata || typeof metadata !== 'object') {
|
|
475
|
+
throw new InvalidInputError('Media metadata is required', 'metadata');
|
|
476
|
+
}
|
|
477
|
+
// Rejects an empty, overlong or path-like id before it can become a key.
|
|
478
|
+
SecurityValidator.validateDocumentId(metadata.id);
|
|
479
|
+
if (!metadata.filename || typeof metadata.filename !== 'string') {
|
|
480
|
+
throw new InvalidInputError('Media metadata must have a valid filename', 'filename');
|
|
481
|
+
}
|
|
482
|
+
if (!metadata.contentType || typeof metadata.contentType !== 'string') {
|
|
483
|
+
throw new InvalidInputError('Media metadata must have a valid content type', 'contentType');
|
|
484
|
+
}
|
|
485
|
+
if (!metadata.extension || typeof metadata.extension !== 'string' || !EXTENSION_PATTERN.test(metadata.extension)) {
|
|
486
|
+
throw new InvalidInputError('Media metadata must have a valid extension', 'extension');
|
|
487
|
+
}
|
|
488
|
+
if (typeof metadata.size !== 'number' || metadata.size <= 0) {
|
|
489
|
+
throw new InvalidInputError('Media metadata must have a valid size', 'size');
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
//# sourceMappingURL=cloudflare-r2-adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudflare-r2-adapter.js","sourceRoot":"","sources":["../../../src/adapters/cloudflare-r2/cloudflare-r2-adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/adapters/cloudflare-r2/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAChE,YAAY,EAAE,yBAAyB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { CloudflareR2Adapter } from './cloudflare-r2-adapter.js';
|
|
2
|
+
// Auto-register in the adapter registry when imported, like the other adapters.
|
|
3
|
+
import { registerAdapter } from '../../core/index.js';
|
|
4
|
+
import { CloudflareR2Adapter } from './cloudflare-r2-adapter.js';
|
|
5
|
+
registerAdapter({
|
|
6
|
+
name: 'cloudflare-r2',
|
|
7
|
+
type: 'media',
|
|
8
|
+
environments: ['edge', 'cloudflare'],
|
|
9
|
+
factory: (config) => new CloudflareR2Adapter(config)
|
|
10
|
+
});
|
|
11
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/adapters/cloudflare-r2/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAGhE,gFAAgF;AAChF,OAAO,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAA;AACrD,OAAO,EAAE,mBAAmB,EAAE,MAAM,4BAA4B,CAAA;AAGhE,eAAe,CAAC;IACd,IAAI,EAAE,eAAe;IACrB,IAAI,EAAE,OAAO;IACb,YAAY,EAAE,CAAC,MAAM,EAAE,YAAY,CAAC;IACpC,OAAO,EAAE,CAAC,MAAiC,EAAE,EAAE,CAAC,IAAI,mBAAmB,CAAC,MAAM,CAAC;CAChF,CAAC,CAAA"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { R2Bucket } from '@cloudflare/workers-types';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for the Cloudflare R2 media adapter.
|
|
4
|
+
*/
|
|
5
|
+
export interface CloudflareR2AdapterConfig {
|
|
6
|
+
/** The R2 bucket binding from the Worker environment. */
|
|
7
|
+
bucket: R2Bucket;
|
|
8
|
+
/**
|
|
9
|
+
* Key prefix, so one bucket can host several installs. Normalised to end with `/`.
|
|
10
|
+
* Everything the adapter writes lives under it, and listings never look outside it.
|
|
11
|
+
*/
|
|
12
|
+
prefix?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Base URL of a public bucket or a custom domain in front of it. When set, `getFileUrl`
|
|
15
|
+
* and `getVariantUrl` return direct URLs; when not, they return null and the caller
|
|
16
|
+
* serves bytes through Trokky's own media routes.
|
|
17
|
+
*
|
|
18
|
+
* R2 bucket bindings cannot presign, so `expiresIn` is not honoured either way.
|
|
19
|
+
*/
|
|
20
|
+
publicBaseUrl?: string;
|
|
21
|
+
/** Reject uploads larger than this. Defaults to 100MB, matching the filesystem adapter. */
|
|
22
|
+
maxFileSize?: number;
|
|
23
|
+
/** Suppress the adapter's own informational logging. */
|
|
24
|
+
silent?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The stored form of a media record: one JSON object per file, alongside the bytes.
|
|
28
|
+
*
|
|
29
|
+
* The free-form fields a caller writes through `updateFile` (title, alt, tags,
|
|
30
|
+
* imageVariants, …) are held under `custom` rather than spread at the top level, so a
|
|
31
|
+
* caller can never shadow `id`, `size` or `createdAt` by choosing a colliding key.
|
|
32
|
+
*/
|
|
33
|
+
export interface R2MediaRecord {
|
|
34
|
+
id: string;
|
|
35
|
+
filename: string;
|
|
36
|
+
contentType: string;
|
|
37
|
+
size: number;
|
|
38
|
+
extension: string;
|
|
39
|
+
createdAt: string;
|
|
40
|
+
updatedAt: string;
|
|
41
|
+
custom: Record<string, unknown>;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/adapters/cloudflare-r2/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAA;AAEzD;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,yDAAyD;IACzD,MAAM,EAAE,QAAQ,CAAA;IAEhB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAA;IAEf;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,CAAA;IAEtB,2FAA2F;IAC3F,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,wDAAwD;IACxD,MAAM,CAAC,EAAE,OAAO,CAAA;CACjB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAA;IACV,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CAChC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/adapters/cloudflare-r2/types.ts"],"names":[],"mappings":""}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-processor.d.ts","sourceRoot":"","sources":["../../../src/core/media/image-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,YAAY,EACV,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,oBAAoB,EACrB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,cAAc,EACd,sBAAsB,EACvB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,kBAAkB,EACnB,MAAM,uBAAuB,CAAA;AAE9B;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"image-processor.d.ts","sourceRoot":"","sources":["../../../src/core/media/image-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAA;AACjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAA;AAE3D,YAAY,EACV,YAAY,EACZ,qBAAqB,EACrB,cAAc,EACd,oBAAoB,EACrB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,cAAc,EACd,sBAAsB,EACvB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,kBAAkB,EACnB,MAAM,uBAAuB,CAAA;AAE9B;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,MAAM,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAsDhG"}
|
|
@@ -36,18 +36,10 @@ export async function createImageProcessor(config) {
|
|
|
36
36
|
`For edge environments (Cloudflare Workers, Vercel Edge), use processor: "none" instead.`);
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
// return new CloudflareImagesProcessor(config as any)
|
|
44
|
-
// } catch (error) {
|
|
45
|
-
// throw new Error(
|
|
46
|
-
// `Failed to load Cloudflare Images processor: ${error instanceof Error ? error.message : String(error)}\n\n` +
|
|
47
|
-
// `This processor is not yet implemented. Use processor: "none" for now.`
|
|
48
|
-
// )
|
|
49
|
-
// }
|
|
50
|
-
// }
|
|
39
|
+
case 'cloudflare-images': {
|
|
40
|
+
throw new Error('Cloudflare Images processor is not yet implemented.\n' +
|
|
41
|
+
'Use processor: "none" on Workers: uploads are stored as-is and no variants are generated.');
|
|
42
|
+
}
|
|
51
43
|
case 'imagekit': {
|
|
52
44
|
throw new Error('ImageKit processor is not yet implemented.\n' +
|
|
53
45
|
'Use processor: "none" for edge environments or "sharp" for Node.js environments.');
|
|
@@ -61,7 +53,7 @@ export async function createImageProcessor(config) {
|
|
|
61
53
|
`Available processors:\n` +
|
|
62
54
|
`- "none": Store originals only (recommended for edge environments)\n` +
|
|
63
55
|
`- "sharp": Local processing with Sharp (Node.js only)\n` +
|
|
64
|
-
`- "cloudflare-images": Cloudflare Images
|
|
56
|
+
`- "cloudflare-images": Cloudflare Images binding (coming soon)\n` +
|
|
65
57
|
`- "imagekit": ImageKit API (coming soon)\n` +
|
|
66
58
|
`- "imgix": Imgix API (coming soon)`);
|
|
67
59
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"image-processor.js","sourceRoot":"","sources":["../../../src/core/media/image-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAaH,OAAO,EACL,cAAc,EACd,sBAAsB,EACvB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,kBAAkB,EACnB,MAAM,uBAAuB,CAAA;AAE9B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAA4B;IACrE,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,yEAAyE;YACzE,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAA;YACnE,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,mEAAmE;YACnE,IAAI,CAAC;gBACH,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAA;gBACrE,OAAO,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAA;YACxC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CACb,mCAAmC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM;oBAC/F,8DAA8D;oBAC9D,yFAAyF,CAC1F,CAAA;YACH,CAAC;QACH,CAAC;QAED,
|
|
1
|
+
{"version":3,"file":"image-processor.js","sourceRoot":"","sources":["../../../src/core/media/image-processor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAaH,OAAO,EACL,cAAc,EACd,sBAAsB,EACvB,MAAM,uBAAuB,CAAA;AAE9B,OAAO,EACL,kBAAkB,EACnB,MAAM,uBAAuB,CAAA;AAE9B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CAAC,MAA4B;IACrE,QAAQ,MAAM,CAAC,IAAI,EAAE,CAAC;QACpB,KAAK,MAAM,CAAC,CAAC,CAAC;YACZ,yEAAyE;YACzE,MAAM,EAAE,kBAAkB,EAAE,GAAG,MAAM,MAAM,CAAC,sBAAsB,CAAC,CAAA;YACnE,OAAO,IAAI,kBAAkB,CAAC,MAAM,CAAC,CAAA;QACvC,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,mEAAmE;YACnE,IAAI,CAAC;gBACH,MAAM,EAAE,mBAAmB,EAAE,GAAG,MAAM,MAAM,CAAC,uBAAuB,CAAC,CAAA;gBACrE,OAAO,IAAI,mBAAmB,CAAC,MAAM,CAAC,CAAA;YACxC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,IAAI,KAAK,CACb,mCAAmC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM;oBAC/F,8DAA8D;oBAC9D,yFAAyF,CAC1F,CAAA;YACH,CAAC;QACH,CAAC;QAED,KAAK,mBAAmB,CAAC,CAAC,CAAC;YACzB,MAAM,IAAI,KAAK,CACb,uDAAuD;gBACvD,2FAA2F,CAC5F,CAAA;QACH,CAAC;QAED,KAAK,UAAU,CAAC,CAAC,CAAC;YAChB,MAAM,IAAI,KAAK,CACb,8CAA8C;gBAC9C,kFAAkF,CACnF,CAAA;QACH,CAAC;QAED,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,IAAI,KAAK,CACb,2CAA2C;gBAC3C,kFAAkF,CACnF,CAAA;QACH,CAAC;QAED;YACE,MAAM,IAAI,KAAK,CACb,iCAAiC,MAAM,CAAC,IAAI,MAAM;gBAClD,yBAAyB;gBACzB,sEAAsE;gBACtE,yDAAyD;gBACzD,kEAAkE;gBAClE,4CAA4C;gBAC5C,oCAAoC,CACrC,CAAA;IACL,CAAC;AACH,CAAC"}
|