@voltro/plugin-storage 0.85.0 → 0.86.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/CHANGELOG.md +666 -0
- package/README.md +50 -0
- package/THIRD-PARTY-NOTICES.md +5 -33
- package/dist/access.js +47 -0
- package/dist/assetRef.js +13 -0
- package/dist/avatar.js +39 -0
- package/dist/constraints.js +93 -0
- package/dist/derivatives.js +30 -0
- package/dist/env.js +156 -0
- package/dist/grant.js +112 -0
- package/dist/index.d.ts +93 -9
- package/dist/index.js +581 -3185
- package/dist/ingest.js +81 -0
- package/dist/ingestPolicy.js +1 -0
- package/dist/metadataError.js +19 -0
- package/dist/objectLifecycle.js +209 -0
- package/dist/objectMetadata.js +150 -0
- package/dist/objectWriter.js +58 -0
- package/dist/pathDurability.js +16 -0
- package/dist/providers.js +493 -0
- package/dist/referenceCatalogue.js +31 -0
- package/dist/rpc.d.ts +2 -0
- package/dist/rpc.js +3 -1
- package/dist/scanners.js +46 -0
- package/dist/service.js +1084 -0
- package/dist/storageNamespaces.js +56 -0
- package/dist/storageNumbers.js +13 -0
- package/dist/storageObjectNamespaces.js +194 -0
- package/dist/storageReferenceInstances.js +52 -0
- package/dist/streamBridge.js +30 -0
- package/dist/table.js +53 -0
- package/dist/transcodeVideo.js +53 -0
- package/dist/transform.js +90 -0
- package/dist/transformCache.js +31 -0
- package/dist/types.d.ts +4 -0
- package/package.json +5 -5
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
import { canonicalStorageRoot as e, filesystemObjectNamespace as t, storageEndpointIdentity as n, storageObjectNamespace as r } from "./storageNamespaces.js";
|
|
2
|
+
import { STORAGE_BLOBS_TABLE as i } from "./table.js";
|
|
3
|
+
import { StorageError as a } from "./types.js";
|
|
4
|
+
import { storageInteger as o } from "./storageNumbers.js";
|
|
5
|
+
import { concatBytes as s, fromNodeReadable as c, toNodeReadable as l } from "./streamBridge.js";
|
|
6
|
+
import { storageEnv as u } from "./env.js";
|
|
7
|
+
import { storageMetadataError as d } from "./metadataError.js";
|
|
8
|
+
import { randomUUID as f } from "node:crypto";
|
|
9
|
+
import { authoredError as p, generateId as m } from "@voltro/database";
|
|
10
|
+
import { Effect as h } from "effect";
|
|
11
|
+
//#region src/providers.ts
|
|
12
|
+
var g = (e, t, n, r) => new a({
|
|
13
|
+
provider: e,
|
|
14
|
+
message: t,
|
|
15
|
+
transient: n,
|
|
16
|
+
...r === void 0 ? {} : { status: r }
|
|
17
|
+
}), _ = (e) => e === void 0 || e === 408 || e === 429 || e >= 500, v = f(), y = /* @__PURE__ */ new Map(), b = (e, t) => y.get(e)?.get(t)?.bytes, x = (e) => {
|
|
18
|
+
y.get(e)?.clear();
|
|
19
|
+
}, S = (e = "default") => {
|
|
20
|
+
let t = y.get(e) ?? /* @__PURE__ */ new Map();
|
|
21
|
+
return y.set(e, t), {
|
|
22
|
+
name: "memory",
|
|
23
|
+
objectNamespace: h.succeed(r([
|
|
24
|
+
"memory",
|
|
25
|
+
v,
|
|
26
|
+
e
|
|
27
|
+
])),
|
|
28
|
+
presigns: !1,
|
|
29
|
+
put: (e, n, r) => h.sync(() => (t.set(e, {
|
|
30
|
+
bytes: n,
|
|
31
|
+
contentType: r.contentType
|
|
32
|
+
}), { size: n.byteLength })),
|
|
33
|
+
get: (e) => h.suspend(() => {
|
|
34
|
+
let n = t.get(e);
|
|
35
|
+
return n ? h.succeed({
|
|
36
|
+
bytes: n.bytes,
|
|
37
|
+
contentType: n.contentType
|
|
38
|
+
}) : h.fail(g("memory", `no object at key '${e}'`, !1, 404));
|
|
39
|
+
}),
|
|
40
|
+
delete: (e) => h.sync(() => {
|
|
41
|
+
t.delete(e);
|
|
42
|
+
}),
|
|
43
|
+
publicUrl: () => null,
|
|
44
|
+
getUrl: () => h.succeed(""),
|
|
45
|
+
head: (e) => h.sync(() => {
|
|
46
|
+
let n = t.get(e);
|
|
47
|
+
return n ? { size: n.bytes.byteLength } : null;
|
|
48
|
+
})
|
|
49
|
+
};
|
|
50
|
+
}, C = (n) => {
|
|
51
|
+
let r = e(n.root), i = async (e) => {
|
|
52
|
+
if (e.split("/")[0]?.startsWith(".voltro-object-namespace")) throw Error("Storage namespace metadata is not an object key");
|
|
53
|
+
let { join: t, resolve: n, sep: i } = await import("node:path"), a = n(r), o = n(a, e);
|
|
54
|
+
if (o.startsWith(n(a, ".voltro-object-namespace"))) throw Error("Storage namespace metadata is not an object key");
|
|
55
|
+
if (o !== a && !o.startsWith(a + i)) throw Error(`key '${e}' escapes the storage root`);
|
|
56
|
+
return t(o);
|
|
57
|
+
}, a = (e, t, n = !1) => h.tryPromise({
|
|
58
|
+
try: e,
|
|
59
|
+
catch: (e) => e?.code === "ENOENT" ? g("filesystem", `no object at key '${t}'`, !1, 404) : g("filesystem", String(e?.message ?? e), n)
|
|
60
|
+
});
|
|
61
|
+
return {
|
|
62
|
+
root: r,
|
|
63
|
+
name: "filesystem",
|
|
64
|
+
objectNamespace: h.try({
|
|
65
|
+
try: () => t(r),
|
|
66
|
+
catch: (e) => g("filesystem", String(e instanceof Error ? e.message : e), !1)
|
|
67
|
+
}),
|
|
68
|
+
presigns: !1,
|
|
69
|
+
put: (e, t, n) => a(async () => {
|
|
70
|
+
let n = await import("node:fs/promises"), { dirname: r } = await import("node:path"), { randomUUID: a } = await import("node:crypto"), o = await i(e), s = r(o);
|
|
71
|
+
await n.mkdir(s, { recursive: !0 });
|
|
72
|
+
let c = `${o}.tmp-${a()}`;
|
|
73
|
+
try {
|
|
74
|
+
await n.writeFile(c, t), await n.rename(c, o);
|
|
75
|
+
} catch (e) {
|
|
76
|
+
throw await n.rm(c, { force: !0 }).catch(() => {}), e;
|
|
77
|
+
}
|
|
78
|
+
return { size: t.byteLength };
|
|
79
|
+
}, e, !0),
|
|
80
|
+
get: (e) => a(async () => {
|
|
81
|
+
let t = await (await import("node:fs/promises")).readFile(await i(e));
|
|
82
|
+
return {
|
|
83
|
+
bytes: new Uint8Array(t),
|
|
84
|
+
contentType: "application/octet-stream"
|
|
85
|
+
};
|
|
86
|
+
}, e),
|
|
87
|
+
delete: (e) => a(async () => {
|
|
88
|
+
await (await import("node:fs/promises")).rm(await i(e), { force: !0 });
|
|
89
|
+
}, e),
|
|
90
|
+
publicUrl: () => null,
|
|
91
|
+
getUrl: () => h.succeed(""),
|
|
92
|
+
head: (e) => h.tryPromise({
|
|
93
|
+
try: async () => {
|
|
94
|
+
let t = await import("node:fs/promises");
|
|
95
|
+
try {
|
|
96
|
+
return { size: (await t.stat(await i(e))).size };
|
|
97
|
+
} catch (e) {
|
|
98
|
+
if (e?.code === "ENOENT") return null;
|
|
99
|
+
throw e;
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
catch: (e) => g("filesystem", String(e?.message ?? e), !1)
|
|
103
|
+
}),
|
|
104
|
+
getStream: (e) => a(async () => {
|
|
105
|
+
let t = await import("node:fs/promises"), { createReadStream: n } = await import("node:fs"), r = await i(e), a = await t.stat(r);
|
|
106
|
+
return {
|
|
107
|
+
stream: c("filesystem", () => n(r)),
|
|
108
|
+
contentType: "application/octet-stream",
|
|
109
|
+
size: a.size
|
|
110
|
+
};
|
|
111
|
+
}, e),
|
|
112
|
+
putStream: (e, t, n) => a(async () => {
|
|
113
|
+
let n = await import("node:fs/promises"), { dirname: r } = await import("node:path"), { randomUUID: a } = await import("node:crypto"), { createWriteStream: o } = await import("node:fs"), { pipeline: s } = await import("node:stream/promises"), c = await i(e);
|
|
114
|
+
await n.mkdir(r(c), { recursive: !0 });
|
|
115
|
+
let u = `${c}.tmp-${a()}`;
|
|
116
|
+
try {
|
|
117
|
+
await s(await l(t), o(u));
|
|
118
|
+
let e = await n.stat(u);
|
|
119
|
+
return await n.rename(u, c), { size: e.size };
|
|
120
|
+
} catch (e) {
|
|
121
|
+
throw await n.rm(u, { force: !0 }).catch(() => {}), e;
|
|
122
|
+
}
|
|
123
|
+
}, e, !0),
|
|
124
|
+
getRange: (e, t, n) => a(async () => {
|
|
125
|
+
let { createReadStream: r } = await import("node:fs"), a = await i(e), o = [];
|
|
126
|
+
return await new Promise((e, i) => {
|
|
127
|
+
let s = r(a, {
|
|
128
|
+
start: t,
|
|
129
|
+
end: n
|
|
130
|
+
});
|
|
131
|
+
s.on("data", (e) => o.push(e)), s.on("end", () => e()), s.on("error", i);
|
|
132
|
+
}), s(o);
|
|
133
|
+
}, e)
|
|
134
|
+
};
|
|
135
|
+
}, w = (e) => {
|
|
136
|
+
let t = e.name ?? "s3", i = e.publicAcl ?? !0, a = null, o = () => a || (a = (async () => {
|
|
137
|
+
let t, n;
|
|
138
|
+
try {
|
|
139
|
+
t = await import("@aws-sdk/client-s3"), n = await import("@aws-sdk/s3-request-presigner");
|
|
140
|
+
} catch (e) {
|
|
141
|
+
throw Error("the 's3'/'minio' storage provider requires the '@aws-sdk/client-s3' + '@aws-sdk/s3-request-presigner' optional dependencies. Install them to enable it.", { cause: e });
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
client: new t.S3Client({
|
|
145
|
+
...e.region ? { region: e.region } : { region: "us-east-1" },
|
|
146
|
+
...e.endpoint ? { endpoint: e.endpoint } : {},
|
|
147
|
+
...e.forcePathStyle ? { forcePathStyle: !0 } : {},
|
|
148
|
+
...e.accessKeyId && e.secretAccessKey ? { credentials: {
|
|
149
|
+
accessKeyId: e.accessKeyId,
|
|
150
|
+
secretAccessKey: e.secretAccessKey
|
|
151
|
+
} } : {}
|
|
152
|
+
}),
|
|
153
|
+
cmds: t,
|
|
154
|
+
presign: n.getSignedUrl
|
|
155
|
+
};
|
|
156
|
+
})(), a), s = (e) => h.tryPromise({
|
|
157
|
+
try: e,
|
|
158
|
+
catch: (e) => {
|
|
159
|
+
let n = e?.$metadata?.httpStatusCode;
|
|
160
|
+
return g(t, String(e?.message ?? e), _(n), n);
|
|
161
|
+
}
|
|
162
|
+
}), u = async (e) => {
|
|
163
|
+
let t = e;
|
|
164
|
+
if (typeof t.transformToByteArray == "function") return t.transformToByteArray();
|
|
165
|
+
let n = [];
|
|
166
|
+
for await (let e of t) n.push(e);
|
|
167
|
+
let r = n.reduce((e, t) => e + t.byteLength, 0), i = new Uint8Array(r), a = 0;
|
|
168
|
+
for (let e of n) i.set(e, a), a += e.byteLength;
|
|
169
|
+
return i;
|
|
170
|
+
};
|
|
171
|
+
return {
|
|
172
|
+
name: t,
|
|
173
|
+
objectNamespace: h.succeed(r([
|
|
174
|
+
"s3",
|
|
175
|
+
e.endpoint ? n(e.endpoint) : "aws-s3",
|
|
176
|
+
e.bucket
|
|
177
|
+
])),
|
|
178
|
+
presigns: !0,
|
|
179
|
+
put: (t, n, r) => s(async () => {
|
|
180
|
+
let { client: a, cmds: s } = await o();
|
|
181
|
+
return await a.send(new s.PutObjectCommand({
|
|
182
|
+
Bucket: e.bucket,
|
|
183
|
+
Key: t,
|
|
184
|
+
Body: n,
|
|
185
|
+
ContentType: r.contentType,
|
|
186
|
+
...r.public && i ? { ACL: "public-read" } : {}
|
|
187
|
+
})), { size: n.byteLength };
|
|
188
|
+
}),
|
|
189
|
+
get: (t) => s(async () => {
|
|
190
|
+
let { client: n, cmds: r } = await o(), i = await n.send(new r.GetObjectCommand({
|
|
191
|
+
Bucket: e.bucket,
|
|
192
|
+
Key: t
|
|
193
|
+
}));
|
|
194
|
+
return {
|
|
195
|
+
bytes: await u(i.Body),
|
|
196
|
+
contentType: i.ContentType ?? "application/octet-stream"
|
|
197
|
+
};
|
|
198
|
+
}),
|
|
199
|
+
delete: (t) => s(async () => {
|
|
200
|
+
let { client: n, cmds: r } = await o();
|
|
201
|
+
await n.send(new r.DeleteObjectCommand({
|
|
202
|
+
Bucket: e.bucket,
|
|
203
|
+
Key: t
|
|
204
|
+
}));
|
|
205
|
+
}),
|
|
206
|
+
publicUrl: (t) => e.cdnBaseUrl ? `${e.cdnBaseUrl.replace(/\/$/, "")}/${t}` : null,
|
|
207
|
+
...i ? { setObjectPublic: (t, n) => s(async () => {
|
|
208
|
+
let { client: r, cmds: i } = await o();
|
|
209
|
+
await r.send(new i.PutObjectAclCommand({
|
|
210
|
+
Bucket: e.bucket,
|
|
211
|
+
Key: t,
|
|
212
|
+
ACL: n ? "public-read" : "private"
|
|
213
|
+
}));
|
|
214
|
+
}) } : {},
|
|
215
|
+
getUrl: (t, n) => s(async () => {
|
|
216
|
+
let { client: r, cmds: i, presign: a } = await o();
|
|
217
|
+
return a(r, new i.GetObjectCommand({
|
|
218
|
+
Bucket: e.bucket,
|
|
219
|
+
Key: t
|
|
220
|
+
}), { expiresIn: n });
|
|
221
|
+
}),
|
|
222
|
+
getUploadUrl: (t, n, r) => s(async () => {
|
|
223
|
+
let { client: i, cmds: a, presign: s } = await o();
|
|
224
|
+
return s(i, new a.PutObjectCommand({
|
|
225
|
+
Bucket: e.bucket,
|
|
226
|
+
Key: t,
|
|
227
|
+
ContentType: r
|
|
228
|
+
}), { expiresIn: n });
|
|
229
|
+
}),
|
|
230
|
+
createMultipartUpload: (t, n, r) => s(async () => {
|
|
231
|
+
let { client: a, cmds: s } = await o(), c = await a.send(new s.CreateMultipartUploadCommand({
|
|
232
|
+
Bucket: e.bucket,
|
|
233
|
+
Key: t,
|
|
234
|
+
ContentType: n,
|
|
235
|
+
...r?.public && i ? { ACL: "public-read" } : {}
|
|
236
|
+
}));
|
|
237
|
+
if (!c.UploadId) throw Error("s3 createMultipartUpload returned no UploadId");
|
|
238
|
+
return { uploadId: c.UploadId };
|
|
239
|
+
}),
|
|
240
|
+
presignUploadPart: (t, n, r, i) => s(async () => {
|
|
241
|
+
let { client: a, cmds: s, presign: c } = await o();
|
|
242
|
+
return c(a, new s.UploadPartCommand({
|
|
243
|
+
Bucket: e.bucket,
|
|
244
|
+
Key: t,
|
|
245
|
+
UploadId: n,
|
|
246
|
+
PartNumber: r
|
|
247
|
+
}), { expiresIn: i });
|
|
248
|
+
}),
|
|
249
|
+
completeMultipartUpload: (t, n, r) => s(async () => {
|
|
250
|
+
let { client: i, cmds: a } = await o(), s = await i.send(new a.CompleteMultipartUploadCommand({
|
|
251
|
+
Bucket: e.bucket,
|
|
252
|
+
Key: t,
|
|
253
|
+
UploadId: n,
|
|
254
|
+
MultipartUpload: { Parts: r.map((e) => ({
|
|
255
|
+
PartNumber: e.partNumber,
|
|
256
|
+
ETag: e.etag
|
|
257
|
+
})) }
|
|
258
|
+
}));
|
|
259
|
+
return s.ETag === void 0 ? {} : { etag: s.ETag };
|
|
260
|
+
}),
|
|
261
|
+
abortMultipartUpload: (t, n) => s(async () => {
|
|
262
|
+
let { client: r, cmds: i } = await o();
|
|
263
|
+
await r.send(new i.AbortMultipartUploadCommand({
|
|
264
|
+
Bucket: e.bucket,
|
|
265
|
+
Key: t,
|
|
266
|
+
UploadId: n
|
|
267
|
+
}));
|
|
268
|
+
}),
|
|
269
|
+
head: (n) => h.tryPromise({
|
|
270
|
+
try: async () => {
|
|
271
|
+
let { client: t, cmds: r } = await o();
|
|
272
|
+
try {
|
|
273
|
+
let i = await t.send(new r.HeadObjectCommand({
|
|
274
|
+
Bucket: e.bucket,
|
|
275
|
+
Key: n
|
|
276
|
+
}));
|
|
277
|
+
return {
|
|
278
|
+
size: i.ContentLength ?? 0,
|
|
279
|
+
...i.ETag === void 0 ? {} : { etag: i.ETag }
|
|
280
|
+
};
|
|
281
|
+
} catch (e) {
|
|
282
|
+
if (e?.$metadata?.httpStatusCode === 404) return null;
|
|
283
|
+
throw e;
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
catch: (e) => {
|
|
287
|
+
let n = e?.$metadata?.httpStatusCode;
|
|
288
|
+
return g(t, String(e?.message ?? e), _(n), n);
|
|
289
|
+
}
|
|
290
|
+
}),
|
|
291
|
+
getStream: (n) => s(async () => {
|
|
292
|
+
let { client: r, cmds: i } = await o(), a = await r.send(new i.GetObjectCommand({
|
|
293
|
+
Bucket: e.bucket,
|
|
294
|
+
Key: n
|
|
295
|
+
})), s = a.Body;
|
|
296
|
+
return {
|
|
297
|
+
stream: c(t, () => s),
|
|
298
|
+
contentType: a.ContentType ?? "application/octet-stream",
|
|
299
|
+
...a.ContentLength === void 0 ? {} : { size: a.ContentLength }
|
|
300
|
+
};
|
|
301
|
+
}),
|
|
302
|
+
putStream: (t, n, r) => s(async () => {
|
|
303
|
+
let { client: a, cmds: s } = await o(), c;
|
|
304
|
+
try {
|
|
305
|
+
c = await import("@aws-sdk/lib-storage");
|
|
306
|
+
} catch (e) {
|
|
307
|
+
throw Error("s3/minio `putStream` requires the '@aws-sdk/lib-storage' optional dependency (multipart upload). Install it to enable streamed uploads.", { cause: e });
|
|
308
|
+
}
|
|
309
|
+
let u = await l(n);
|
|
310
|
+
return await new c.Upload({
|
|
311
|
+
client: a,
|
|
312
|
+
params: {
|
|
313
|
+
Bucket: e.bucket,
|
|
314
|
+
Key: t,
|
|
315
|
+
Body: u,
|
|
316
|
+
ContentType: r.contentType,
|
|
317
|
+
...r.public && i ? { ACL: "public-read" } : {}
|
|
318
|
+
}
|
|
319
|
+
}).done(), { size: r.totalSize ?? (await a.send(new s.HeadObjectCommand({
|
|
320
|
+
Bucket: e.bucket,
|
|
321
|
+
Key: t
|
|
322
|
+
}))).ContentLength ?? 0 };
|
|
323
|
+
}),
|
|
324
|
+
getRange: (t, n, r) => s(async () => {
|
|
325
|
+
let { client: i, cmds: a } = await o(), s = await i.send(new a.GetObjectCommand({
|
|
326
|
+
Bucket: e.bucket,
|
|
327
|
+
Key: t,
|
|
328
|
+
Range: `bytes=${n}-${r}`
|
|
329
|
+
}));
|
|
330
|
+
return u(s.Body);
|
|
331
|
+
})
|
|
332
|
+
};
|
|
333
|
+
}, T = (e) => e instanceof Uint8Array ? e : typeof e == "string" ? new Uint8Array(Buffer.from(e, "base64")) : new Uint8Array(e), E = () => {
|
|
334
|
+
let e = null, t = () => {
|
|
335
|
+
if (!e) throw p("the 'database' storage provider isn't bound to a DataStore yet");
|
|
336
|
+
return e;
|
|
337
|
+
}, n = (e) => h.tryPromise({
|
|
338
|
+
try: e,
|
|
339
|
+
catch: (e) => d(e, "database", !0)
|
|
340
|
+
}), a = async (e, n) => (await t().query({
|
|
341
|
+
table: i,
|
|
342
|
+
predicate: {
|
|
343
|
+
column: "key",
|
|
344
|
+
op: "eq",
|
|
345
|
+
value: e
|
|
346
|
+
},
|
|
347
|
+
order: [],
|
|
348
|
+
take: 1,
|
|
349
|
+
skip: void 0,
|
|
350
|
+
projection: n
|
|
351
|
+
}))[0];
|
|
352
|
+
return {
|
|
353
|
+
name: "database",
|
|
354
|
+
objectNamespace: h.succeed(r(["database", i])),
|
|
355
|
+
presigns: !1,
|
|
356
|
+
bindDataStore: (t) => {
|
|
357
|
+
e = t;
|
|
358
|
+
},
|
|
359
|
+
put: (e, r, o) => n(async () => {
|
|
360
|
+
let n = await a(e);
|
|
361
|
+
return n ? await t().update(i, String(n.id), {
|
|
362
|
+
data: r,
|
|
363
|
+
contentType: o.contentType,
|
|
364
|
+
size: String(r.byteLength)
|
|
365
|
+
}) : await t().insert(i, {
|
|
366
|
+
id: m({
|
|
367
|
+
kind: "typeid",
|
|
368
|
+
prefix: "blob"
|
|
369
|
+
}, i),
|
|
370
|
+
key: e,
|
|
371
|
+
data: r,
|
|
372
|
+
contentType: o.contentType,
|
|
373
|
+
size: String(r.byteLength)
|
|
374
|
+
}), { size: r.byteLength };
|
|
375
|
+
}),
|
|
376
|
+
get: (e) => h.suspend(() => n(() => a(e)).pipe(h.flatMap((t) => t ? h.succeed({
|
|
377
|
+
bytes: T(t.data),
|
|
378
|
+
contentType: String(t.contentType)
|
|
379
|
+
}) : h.fail(g("database", `no object at key '${e}'`, !1, 404))))),
|
|
380
|
+
delete: (e) => n(async () => {
|
|
381
|
+
let n = await a(e, ["id"]);
|
|
382
|
+
n && await t().delete(i, String(n.id));
|
|
383
|
+
}),
|
|
384
|
+
publicUrl: () => null,
|
|
385
|
+
getUrl: () => h.succeed(""),
|
|
386
|
+
head: (e) => n(async () => {
|
|
387
|
+
let t = await a(e, ["size"]);
|
|
388
|
+
return t ? { size: o(t.size, "blob size") } : null;
|
|
389
|
+
})
|
|
390
|
+
};
|
|
391
|
+
}, D = (e) => {
|
|
392
|
+
let t = null, i = () => t || (t = (async () => {
|
|
393
|
+
let t;
|
|
394
|
+
try {
|
|
395
|
+
t = await import("@azure/storage-blob");
|
|
396
|
+
} catch (e) {
|
|
397
|
+
throw Error("the 'azure' storage provider requires the '@azure/storage-blob' optional dependency. Install it to enable it.", { cause: e });
|
|
398
|
+
}
|
|
399
|
+
let n = u.read("AZURE_STORAGE_ACCOUNT", e.accountName), r = u.read("AZURE_STORAGE_KEY", e.accountKey), i = n && r ? new t.StorageSharedKeyCredential(n, r) : null, a = u.read("AZURE_STORAGE_CONNECTION_STRING", e.connectionString);
|
|
400
|
+
return {
|
|
401
|
+
container: (a ? t.BlobServiceClient.fromConnectionString(a) : new t.BlobServiceClient(e.endpoint ?? `https://${n}.blob.core.windows.net`, i ?? void 0)).getContainerClient(e.container),
|
|
402
|
+
sdk: t,
|
|
403
|
+
cred: i
|
|
404
|
+
};
|
|
405
|
+
})(), t), a = (e) => h.tryPromise({
|
|
406
|
+
try: e,
|
|
407
|
+
catch: (e) => {
|
|
408
|
+
let t = e?.statusCode;
|
|
409
|
+
return g("azure", String(e?.message ?? e), _(t), t);
|
|
410
|
+
}
|
|
411
|
+
}), o = async (e) => {
|
|
412
|
+
if (!e) return /* @__PURE__ */ new Uint8Array();
|
|
413
|
+
let t = [];
|
|
414
|
+
for await (let n of e) t.push(typeof n == "string" ? new TextEncoder().encode(n) : new Uint8Array(n));
|
|
415
|
+
let n = t.reduce((e, t) => e + t.byteLength, 0), r = new Uint8Array(n), i = 0;
|
|
416
|
+
for (let e of t) r.set(e, i), i += e.byteLength;
|
|
417
|
+
return r;
|
|
418
|
+
}, s = async (t, n, r) => {
|
|
419
|
+
let { container: a, sdk: o, cred: s } = await i();
|
|
420
|
+
if (!s) throw Error("azure SAS requires accountName + accountKey (set them or AZURE_STORAGE_ACCOUNT/AZURE_STORAGE_KEY)");
|
|
421
|
+
let c = a.getBlockBlobClient(t), l = o.generateBlobSASQueryParameters({
|
|
422
|
+
containerName: e.container,
|
|
423
|
+
blobName: t,
|
|
424
|
+
permissions: o.BlobSASPermissions.parse(n),
|
|
425
|
+
startsOn: /* @__PURE__ */ new Date(Date.now() - 6e4),
|
|
426
|
+
expiresOn: new Date(Date.now() + r * 1e3)
|
|
427
|
+
}, s).toString();
|
|
428
|
+
return `${c.url}?${l}`;
|
|
429
|
+
};
|
|
430
|
+
return {
|
|
431
|
+
name: "azure",
|
|
432
|
+
objectNamespace: a(async () => {
|
|
433
|
+
let { container: e } = await i();
|
|
434
|
+
return r(["azure", n(e.url)]);
|
|
435
|
+
}),
|
|
436
|
+
presigns: !0,
|
|
437
|
+
put: (e, t, n) => a(async () => {
|
|
438
|
+
let { container: r } = await i();
|
|
439
|
+
return await r.getBlockBlobClient(e).uploadData(Buffer.from(t), { blobHTTPHeaders: { blobContentType: n.contentType } }), { size: t.byteLength };
|
|
440
|
+
}),
|
|
441
|
+
get: (e) => a(async () => {
|
|
442
|
+
let { container: t } = await i(), n = await t.getBlockBlobClient(e).download();
|
|
443
|
+
return {
|
|
444
|
+
bytes: await o(n.readableStreamBody),
|
|
445
|
+
contentType: n.contentType ?? "application/octet-stream"
|
|
446
|
+
};
|
|
447
|
+
}),
|
|
448
|
+
delete: (e) => a(async () => {
|
|
449
|
+
let { container: t } = await i();
|
|
450
|
+
await t.getBlockBlobClient(e).deleteIfExists();
|
|
451
|
+
}),
|
|
452
|
+
publicUrl: (t) => e.cdnBaseUrl ? `${e.cdnBaseUrl.replace(/\/$/, "")}/${t}` : null,
|
|
453
|
+
getUrl: (e, t) => a(() => s(e, "r", t)),
|
|
454
|
+
getUploadUrl: (e, t, n) => a(() => s(e, "cw", t)),
|
|
455
|
+
head: (e) => h.tryPromise({
|
|
456
|
+
try: async () => {
|
|
457
|
+
let { container: t } = await i();
|
|
458
|
+
try {
|
|
459
|
+
let n = await t.getBlockBlobClient(e).getProperties();
|
|
460
|
+
return {
|
|
461
|
+
size: n.contentLength ?? 0,
|
|
462
|
+
...n.etag === void 0 ? {} : { etag: n.etag }
|
|
463
|
+
};
|
|
464
|
+
} catch (e) {
|
|
465
|
+
if (e?.statusCode === 404) return null;
|
|
466
|
+
throw e;
|
|
467
|
+
}
|
|
468
|
+
},
|
|
469
|
+
catch: (e) => {
|
|
470
|
+
let t = e?.statusCode;
|
|
471
|
+
return g("azure", String(e?.message ?? e), _(t), t);
|
|
472
|
+
}
|
|
473
|
+
}),
|
|
474
|
+
getStream: (e) => a(async () => {
|
|
475
|
+
let { container: t } = await i(), n = await t.getBlockBlobClient(e).download();
|
|
476
|
+
return {
|
|
477
|
+
stream: c("azure", () => n.readableStreamBody),
|
|
478
|
+
contentType: n.contentType ?? "application/octet-stream",
|
|
479
|
+
...n.contentLength === void 0 ? {} : { size: n.contentLength }
|
|
480
|
+
};
|
|
481
|
+
}),
|
|
482
|
+
putStream: (e, t, n) => a(async () => {
|
|
483
|
+
let { container: r } = await i(), a = await l(t);
|
|
484
|
+
return await r.getBlockBlobClient(e).uploadStream(a, 4194304, 5, { blobHTTPHeaders: { blobContentType: n.contentType } }), { size: n.totalSize ?? (await r.getBlockBlobClient(e).getProperties()).contentLength ?? 0 };
|
|
485
|
+
}),
|
|
486
|
+
getRange: (e, t, n) => a(async () => {
|
|
487
|
+
let { container: r } = await i(), a = await r.getBlockBlobClient(e).download(t, n - t + 1);
|
|
488
|
+
return o(a.readableStreamBody);
|
|
489
|
+
})
|
|
490
|
+
};
|
|
491
|
+
};
|
|
492
|
+
//#endregion
|
|
493
|
+
export { D as azureProvider, x as clearStorageBuffer, E as databaseProvider, C as filesystemProvider, S as memoryProvider, b as readStorageBuffer, w as s3Provider };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { storageObjectNamespace as e } from "./storageNamespaces.js";
|
|
2
|
+
import { randomUUID as t } from "node:crypto";
|
|
3
|
+
import { authoredError as n, eq as r, table as i, text as a } from "@voltro/database";
|
|
4
|
+
//#region src/referenceCatalogue.ts
|
|
5
|
+
var o = "_voltro_storage_reference_catalogue", s = i(o, {
|
|
6
|
+
id: a().maxLength(64),
|
|
7
|
+
namespace: a().maxLength(64)
|
|
8
|
+
}).primaryKey(["id"]).nonReactive(), c = async (n) => {
|
|
9
|
+
await n.insertIgnoreWithOutcome(o, {
|
|
10
|
+
id: "catalogue",
|
|
11
|
+
namespace: e(["reference-catalogue", t()])
|
|
12
|
+
}, { conflictColumns: ["id"] }), await l(n);
|
|
13
|
+
}, l = async (t, i) => {
|
|
14
|
+
let a = await t.query({
|
|
15
|
+
table: o,
|
|
16
|
+
predicate: r("id", "catalogue"),
|
|
17
|
+
order: [],
|
|
18
|
+
take: 2,
|
|
19
|
+
skip: void 0,
|
|
20
|
+
projection: void 0
|
|
21
|
+
}), s = a[0]?.namespace;
|
|
22
|
+
if (a.length !== 1 || typeof s != "string" || !/^[a-f0-9]{64}$/.test(s)) throw n("Storage reference catalogue identity is missing or invalid; initialize it after normal schema reconciliation before serving inspection");
|
|
23
|
+
return i ? e([
|
|
24
|
+
"reference-view",
|
|
25
|
+
s,
|
|
26
|
+
i.storageInstance,
|
|
27
|
+
i.objectNamespace
|
|
28
|
+
]) : s;
|
|
29
|
+
};
|
|
30
|
+
//#endregion
|
|
31
|
+
export { o as STORAGE_REFERENCE_CATALOGUE_TABLE, c as ensureStorageReferenceCatalogue, l as storageReferenceCatalogueId, s as storageReferenceCatalogueTable };
|
package/dist/rpc.d.ts
CHANGED
|
@@ -178,6 +178,7 @@ export declare const mintPresignedUploadDescriptor: ActionProcedureDescriptor<"s
|
|
|
178
178
|
}>, typeof StorageError>;
|
|
179
179
|
|
|
180
180
|
export declare const mintUploadTicketDescriptor: ActionProcedureDescriptor<"storage.mintUploadTicket", Schema.Struct<{
|
|
181
|
+
readonly operationId: Schema.optional<Schema.String>;
|
|
181
182
|
readonly contentType: Schema.String;
|
|
182
183
|
readonly visibility: Schema.optional<Schema.Literals<readonly ["public", "private"]>>;
|
|
183
184
|
readonly access: Schema.optional<Schema.$Array<Schema.Struct<{
|
|
@@ -199,6 +200,7 @@ export declare const mintUploadTicketDescriptor: ActionProcedureDescriptor<"stor
|
|
|
199
200
|
}>, Schema.Struct<{
|
|
200
201
|
readonly uploadUrl: Schema.String;
|
|
201
202
|
readonly expiresInSec: Schema.Number;
|
|
203
|
+
readonly operationId: Schema.optional<Schema.String>;
|
|
202
204
|
}>, typeof StorageError>;
|
|
203
205
|
|
|
204
206
|
export declare const mintUploadUrlDescriptor: ActionProcedureDescriptor<"storage.mintUploadUrl", Schema.Struct<{
|
package/dist/rpc.js
CHANGED
|
@@ -76,6 +76,7 @@ var l = i.Literals([
|
|
|
76
76
|
}), g = o({
|
|
77
77
|
name: "storage.mintUploadTicket",
|
|
78
78
|
input: i.Struct({
|
|
79
|
+
operationId: i.optional(i.String.pipe(i.check(i.makeFilter((e) => e.trim().length > 0)), i.check(i.isMaxLength(200)))),
|
|
79
80
|
contentType: i.String,
|
|
80
81
|
visibility: i.optional(i.Literals(["public", "private"])),
|
|
81
82
|
access: i.optional(a),
|
|
@@ -88,7 +89,8 @@ var l = i.Literals([
|
|
|
88
89
|
}),
|
|
89
90
|
output: i.Struct({
|
|
90
91
|
uploadUrl: i.String,
|
|
91
|
-
expiresInSec: i.Number
|
|
92
|
+
expiresInSec: i.Number,
|
|
93
|
+
operationId: i.optional(i.String)
|
|
92
94
|
}),
|
|
93
95
|
error: t,
|
|
94
96
|
openAccess: "self-service upload: the ticket binds the calling subject + tenant, caps size, and the finalize path runs limits + scan; an unidentified caller is refused in-handler unless the app set `anonymousUploads`"
|
package/dist/scanners.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { storageEnv as e } from "./env.js";
|
|
2
|
+
//#region src/scanners.ts
|
|
3
|
+
var t = (e, t = 65536) => {
|
|
4
|
+
let n = [Buffer.from("zINSTREAM\0", "utf8")];
|
|
5
|
+
for (let r = 0; r < e.length; r += t) {
|
|
6
|
+
let i = e.subarray(r, Math.min(r + t, e.length)), a = Buffer.alloc(4);
|
|
7
|
+
a.writeUInt32BE(i.length, 0), n.push(a, Buffer.from(i));
|
|
8
|
+
}
|
|
9
|
+
let r = Buffer.alloc(4);
|
|
10
|
+
return r.writeUInt32BE(0, 0), n.push(r), Buffer.concat(n);
|
|
11
|
+
}, n = (e) => {
|
|
12
|
+
let t = e.replace(/\0+$/, "").trim();
|
|
13
|
+
if (/\bOK$/.test(t)) return { clean: !0 };
|
|
14
|
+
let n = /:\s*(.+?)\s+FOUND$/.exec(t);
|
|
15
|
+
if (n) return {
|
|
16
|
+
clean: !1,
|
|
17
|
+
threat: n[1] ?? "unknown"
|
|
18
|
+
};
|
|
19
|
+
throw Error(`clamav: unexpected response: ${t}`);
|
|
20
|
+
}, r = async (r, i) => {
|
|
21
|
+
let a = await import("node:net"), o = e.read("CLAMAV_HOST", i.host) ?? "127.0.0.1", s = e.read("CLAMAV_PORT"), c = i.port ?? (s ? Number(s) : 3310), l = i.timeoutMs ?? 3e4;
|
|
22
|
+
return new Promise((e, s) => {
|
|
23
|
+
let u = a.createConnection({
|
|
24
|
+
host: o,
|
|
25
|
+
port: c
|
|
26
|
+
}), d = "", f = setTimeout(() => {
|
|
27
|
+
u.destroy(), s(/* @__PURE__ */ Error("clamav scan timed out"));
|
|
28
|
+
}, l);
|
|
29
|
+
u.on("error", (e) => {
|
|
30
|
+
clearTimeout(f), s(e);
|
|
31
|
+
}), u.on("connect", () => {
|
|
32
|
+
u.end(t(r, i.chunkSize));
|
|
33
|
+
}), u.on("data", (e) => {
|
|
34
|
+
d += e.toString("utf8");
|
|
35
|
+
}), u.on("close", () => {
|
|
36
|
+
clearTimeout(f);
|
|
37
|
+
try {
|
|
38
|
+
e(n(d));
|
|
39
|
+
} catch (e) {
|
|
40
|
+
s(e);
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
}, i = (e = {}) => (t) => r(t.bytes, e);
|
|
45
|
+
//#endregion
|
|
46
|
+
export { i as clamavScanner, t as encodeInstream, n as parseClamavResponse };
|