nuxt-filer 0.0.13 → 0.0.15
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/module.json
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
+
async function mapWithConcurrency(items, limit, fn) {
|
|
3
|
+
const results = new Array(items.length);
|
|
4
|
+
let next = 0;
|
|
5
|
+
const worker = async () => {
|
|
6
|
+
while (next < items.length) {
|
|
7
|
+
const index = next++;
|
|
8
|
+
results[index] = await fn(items[index], index);
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
const workers = Array.from({ length: Math.min(limit, items.length) || 1 }, worker);
|
|
12
|
+
await Promise.all(workers);
|
|
13
|
+
return results;
|
|
14
|
+
}
|
|
2
15
|
export function createS3Provider(options) {
|
|
3
16
|
const prefix = options.prefix ? options.prefix.replace(/\/+$/, "") + "/" : "";
|
|
4
17
|
let clientPromise;
|
|
5
18
|
const getClient = () => clientPromise ??= options.client ? Promise.resolve(options.client) : createAwsS3Client(options);
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
19
|
+
const seg = (value) => value.replace(/^\/+|\/+$/g, "");
|
|
20
|
+
const dataKey = (groupId, id) => `${prefix}${seg(groupId)}/data/${seg(id)}`;
|
|
21
|
+
const metaKey = (groupId, id) => `${prefix}${seg(groupId)}/meta/${seg(id)}`;
|
|
22
|
+
const metaPrefix = (groupId) => `${prefix}${seg(groupId)}/meta/`;
|
|
9
23
|
const groupIdFromMetaKey = (key) => {
|
|
10
24
|
const rel = key.slice(prefix.length);
|
|
11
25
|
return rel.slice(0, rel.lastIndexOf("/meta/"));
|
|
@@ -66,11 +80,14 @@ export function createS3Provider(options) {
|
|
|
66
80
|
},
|
|
67
81
|
async list(groupId) {
|
|
68
82
|
const client = await getClient();
|
|
83
|
+
const keys = [];
|
|
84
|
+
for await (const key of client.listKeys(metaPrefix(groupId))) keys.push(key);
|
|
85
|
+
const metas = await mapWithConcurrency(keys, 32, (key) => readMeta(client, key));
|
|
69
86
|
const files = [];
|
|
70
|
-
for
|
|
71
|
-
const meta =
|
|
87
|
+
for (let i = 0; i < keys.length; i++) {
|
|
88
|
+
const meta = metas[i];
|
|
72
89
|
if (!meta) continue;
|
|
73
|
-
files.push(toStoredFile(idFromKey(
|
|
90
|
+
files.push(toStoredFile(idFromKey(keys[i]), groupId, meta));
|
|
74
91
|
}
|
|
75
92
|
return files;
|
|
76
93
|
},
|
|
@@ -99,7 +116,7 @@ export function createS3Provider(options) {
|
|
|
99
116
|
},
|
|
100
117
|
async clear(groupId) {
|
|
101
118
|
const client = await getClient();
|
|
102
|
-
for await (const key of client.listKeys(`${prefix}${groupId}/`)) {
|
|
119
|
+
for await (const key of client.listKeys(`${prefix}${seg(groupId)}/`)) {
|
|
103
120
|
await client.delete(key);
|
|
104
121
|
}
|
|
105
122
|
},
|
package/package.json
CHANGED
|
@@ -38,6 +38,31 @@ export interface S3ProviderOptions {
|
|
|
38
38
|
|
|
39
39
|
type InternalMeta = FileMeta & { _createdAt?: string; _updatedAt?: string };
|
|
40
40
|
|
|
41
|
+
/**
|
|
42
|
+
* Map over `items` with at most `limit` promises in flight, preserving order.
|
|
43
|
+
* Used to read many meta objects concurrently instead of one-at-a-time — on a
|
|
44
|
+
* remote store (R2/S3) the per-object round-trip latency dominates, so a
|
|
45
|
+
* sequential `for await` over N files is ~N × RTT. Bounded so a huge group
|
|
46
|
+
* doesn't open thousands of sockets at once.
|
|
47
|
+
*/
|
|
48
|
+
async function mapWithConcurrency<T, R>(
|
|
49
|
+
items: T[],
|
|
50
|
+
limit: number,
|
|
51
|
+
fn: (item: T, index: number) => Promise<R>,
|
|
52
|
+
): Promise<R[]> {
|
|
53
|
+
const results = new Array<R>(items.length);
|
|
54
|
+
let next = 0;
|
|
55
|
+
const worker = async () => {
|
|
56
|
+
while (next < items.length) {
|
|
57
|
+
const index = next++;
|
|
58
|
+
results[index] = await fn(items[index], index);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
const workers = Array.from({ length: Math.min(limit, items.length) || 1 }, worker);
|
|
62
|
+
await Promise.all(workers);
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
|
|
41
66
|
/**
|
|
42
67
|
* S3-backed {@link FileStorageProvider}. Binary data and a JSON metadata
|
|
43
68
|
* sidecar are stored as separate objects per file, mirroring the built-in
|
|
@@ -56,9 +81,13 @@ export function createS3Provider(options: S3ProviderOptions): FileStorageProvide
|
|
|
56
81
|
? Promise.resolve(options.client)
|
|
57
82
|
: createAwsS3Client(options));
|
|
58
83
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
84
|
+
// Strip leading/trailing slashes so callers can pass `studio` or `/studio`
|
|
85
|
+
// interchangeably (the IPX integration hands group ids through with a leading
|
|
86
|
+
// slash). Without this, keys like `/studio/data/x` miss the stored object.
|
|
87
|
+
const seg = (value: string) => value.replace(/^\/+|\/+$/g, '');
|
|
88
|
+
const dataKey = (groupId: string, id: string) => `${prefix}${seg(groupId)}/data/${seg(id)}`;
|
|
89
|
+
const metaKey = (groupId: string, id: string) => `${prefix}${seg(groupId)}/meta/${seg(id)}`;
|
|
90
|
+
const metaPrefix = (groupId: string) => `${prefix}${seg(groupId)}/meta/`;
|
|
62
91
|
|
|
63
92
|
/** `${prefix}${groupId}/meta/${id}` → groupId (groups may contain '/'). */
|
|
64
93
|
const groupIdFromMetaKey = (key: string) => {
|
|
@@ -139,11 +168,17 @@ export function createS3Provider(options: S3ProviderOptions): FileStorageProvide
|
|
|
139
168
|
|
|
140
169
|
async list(groupId) {
|
|
141
170
|
const client = await getClient();
|
|
171
|
+
// Enumerate the group's meta keys (cheap paginated LIST), then read every
|
|
172
|
+
// meta object in parallel. The previous sequential read was one blocking
|
|
173
|
+
// GET per file, so listing a group cost ~N round-trips to the store.
|
|
174
|
+
const keys: string[] = [];
|
|
175
|
+
for await (const key of client.listKeys(metaPrefix(groupId))) keys.push(key);
|
|
176
|
+
const metas = await mapWithConcurrency(keys, 32, (key) => readMeta(client, key));
|
|
142
177
|
const files: StoredFile[] = [];
|
|
143
|
-
for
|
|
144
|
-
const meta =
|
|
178
|
+
for (let i = 0; i < keys.length; i++) {
|
|
179
|
+
const meta = metas[i];
|
|
145
180
|
if (!meta) continue;
|
|
146
|
-
files.push(toStoredFile(idFromKey(
|
|
181
|
+
files.push(toStoredFile(idFromKey(keys[i]), groupId, meta));
|
|
147
182
|
}
|
|
148
183
|
return files;
|
|
149
184
|
},
|
|
@@ -175,7 +210,7 @@ export function createS3Provider(options: S3ProviderOptions): FileStorageProvide
|
|
|
175
210
|
|
|
176
211
|
async clear(groupId) {
|
|
177
212
|
const client = await getClient();
|
|
178
|
-
for await (const key of client.listKeys(`${prefix}${groupId}/`)) {
|
|
213
|
+
for await (const key of client.listKeys(`${prefix}${seg(groupId)}/`)) {
|
|
179
214
|
await client.delete(key);
|
|
180
215
|
}
|
|
181
216
|
},
|