@voltro/plugin-storage 0.85.0 → 0.87.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 +5987 -0
- package/README.md +77 -0
- package/THIRD-PARTY-NOTICES.md +860 -249
- package/dist/access.js +47 -0
- package/dist/assetRef.js +13 -0
- package/dist/avatar.js +39 -0
- package/dist/byteSecurity.d.ts +3 -0
- package/dist/byteSecurity.js +7 -0
- package/dist/connection.js +99 -0
- package/dist/constraints.js +93 -0
- package/dist/custody.js +132 -0
- package/dist/derivatives.js +30 -0
- package/dist/env.js +162 -0
- package/dist/grant.js +132 -0
- package/dist/index.d.ts +525 -203
- package/dist/index.js +765 -3196
- package/dist/ingest.js +87 -0
- package/dist/ingestPolicy.js +1 -0
- package/dist/metadataError.js +19 -0
- package/dist/objectAbandon.js +16 -0
- package/dist/objectLifecycle.js +243 -0
- package/dist/objectMetadata.js +308 -0
- package/dist/objectWriter.js +87 -0
- package/dist/pathDurability.js +16 -0
- package/dist/physicalReferences.js +54 -0
- package/dist/providers.js +638 -0
- package/dist/referenceCatalogue.js +31 -0
- package/dist/renditionProof.js +82 -0
- package/dist/retainedCapture.js +391 -0
- package/dist/retainedObjects.js +190 -0
- package/dist/rpc.d.ts +3 -15
- package/dist/rpc.js +3 -1
- package/dist/scanners.js +46 -0
- package/dist/server.d.ts +153 -0
- package/dist/server.js +4 -0
- package/dist/service.js +1418 -0
- package/dist/sourceIdentity.js +14 -0
- package/dist/sourcePin.js +35 -0
- package/dist/storageNamespaces.js +80 -0
- package/dist/storageNumbers.js +13 -0
- package/dist/storageObjectNamespaces.js +243 -0
- package/dist/storageReferenceInstances.js +52 -0
- package/dist/storageRequest.js +10 -0
- package/dist/storageRouting.js +46 -0
- package/dist/streamBridge.js +33 -0
- package/dist/table.js +54 -0
- package/dist/transcodeVideo.js +91 -0
- package/dist/transform.js +94 -0
- package/dist/transformCache.js +31 -0
- package/dist/types.d.ts +116 -134
- package/dist/types.js +14 -14
- package/package.json +17 -6
package/dist/access.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Effect as e } from "effect";
|
|
2
|
+
import { randomBytes as t, scryptSync as n, timingSafeEqual as r } from "node:crypto";
|
|
3
|
+
//#region src/access.ts
|
|
4
|
+
var i = (e) => {
|
|
5
|
+
let r = t(16).toString("hex");
|
|
6
|
+
return `${r}:${n(e, r, 32).toString("hex")}`;
|
|
7
|
+
}, a = (e, t) => {
|
|
8
|
+
if (!e || !t) return !1;
|
|
9
|
+
let [i, a] = t.split(":");
|
|
10
|
+
if (!i || !a) return !1;
|
|
11
|
+
let o = Buffer.from(a, "hex"), s = n(e, i, o.length);
|
|
12
|
+
return s.length === o.length && r(s, o);
|
|
13
|
+
}, o = (e) => Array.isArray(e) ? e.filter((e) => typeof e == "string") : [], s = (e) => o(e.metadata?.roles), c = (e) => o(e.scopes), l = (e, t) => e.some((e) => t.includes(e)), u = (e) => o(e.metadata?.groups), d = (t) => typeof t == "boolean" ? e.succeed(t) : e.isEffect(t) ? t : e.promise(() => t), f = (t, n, r = {}) => e.gen(function* () {
|
|
14
|
+
if (n.id && t.ownerId && n.id === t.ownerId) return !0;
|
|
15
|
+
let i = t.accessPolicy ?? [];
|
|
16
|
+
if (i.length === 0) return !1;
|
|
17
|
+
let a = r.resolveGroups ? yield* e.promise(async () => await r.resolveGroups(n)) : u(n);
|
|
18
|
+
for (let e of i) if (yield* p(e, t, n, a, r)) return !0;
|
|
19
|
+
return !1;
|
|
20
|
+
}), p = (t, n, r, i, o) => e.gen(function* () {
|
|
21
|
+
let e = !1, u = (t) => (e = !0, t);
|
|
22
|
+
if (t.owner !== void 0 && t.owner && !u(!!r.id && !!n.ownerId && r.id === n.ownerId) || t.tenant !== void 0 && t.tenant && !u(!!r.tenantId && r.tenantId === n.tenantId) || t.apiKey !== void 0 && t.apiKey && !u(r.type === "apiKey") || t.roles && t.roles.length > 0 && !u(l(s(r), t.roles)) || t.groups && t.groups.length > 0 && !u(l(i, t.groups)) || t.scopes && t.scopes.length > 0 && !u(l(c(r), t.scopes)) || t.password !== void 0 && t.password && !u(a(o.password, n.passwordHash))) return !1;
|
|
23
|
+
if (t.guard) {
|
|
24
|
+
let e = o.guards?.[t.guard];
|
|
25
|
+
if (!u(e ? yield* d(e({
|
|
26
|
+
subject: r,
|
|
27
|
+
ref: n
|
|
28
|
+
})) : !1)) return !1;
|
|
29
|
+
}
|
|
30
|
+
return e;
|
|
31
|
+
}), m = (e, t) => {
|
|
32
|
+
let n = [];
|
|
33
|
+
e.id && n.push({
|
|
34
|
+
type: e.type === "apiKey" ? "apiKey" : "user",
|
|
35
|
+
id: e.id
|
|
36
|
+
});
|
|
37
|
+
for (let e of t) n.push({
|
|
38
|
+
type: "group",
|
|
39
|
+
id: e
|
|
40
|
+
});
|
|
41
|
+
return n;
|
|
42
|
+
}, h = (e, t, n, r, i) => {
|
|
43
|
+
let a = m(t, n);
|
|
44
|
+
return e.some((e) => e.expiresAt && e.expiresAt.getTime() < i || r === "write" && e.permission !== "write" ? !1 : a.some((t) => t.type === e.principalType && t.id === e.principalId));
|
|
45
|
+
}, g = (t, n) => n.resolveGroups ? e.promise(async () => await n.resolveGroups(t)) : e.succeed(u(t));
|
|
46
|
+
//#endregion
|
|
47
|
+
export { f as evaluatePolicy, h as grantsAllow, i as hashPassword, g as resolveSubjectGroups, m as subjectPrincipals, a as verifyPassword };
|
package/dist/assetRef.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { _voltroStorageRefsTable as e } from "./table.js";
|
|
2
|
+
import { mixin as t, reference as n, text as r } from "@voltro/database";
|
|
3
|
+
//#region src/assetRef.ts
|
|
4
|
+
function i(t = {}) {
|
|
5
|
+
let i = t.nullable ?? !0, a = t.fk === !1 ? r().externalId() : n(() => e, { onDelete: t.onDelete ?? "setNull" });
|
|
6
|
+
return i ? a.nullable() : a;
|
|
7
|
+
}
|
|
8
|
+
var a = () => t({
|
|
9
|
+
id: "@voltro/plugin-storage/withStorage",
|
|
10
|
+
fields: { asset: i() }
|
|
11
|
+
});
|
|
12
|
+
//#endregion
|
|
13
|
+
export { i as assetRef, a as withStorage };
|
package/dist/avatar.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { StorageError as e } from "./types.js";
|
|
2
|
+
import { Effect as t } from "effect";
|
|
3
|
+
//#region src/avatar.ts
|
|
4
|
+
var n = {
|
|
5
|
+
"image/png": "png",
|
|
6
|
+
"image/jpeg": "jpg",
|
|
7
|
+
"image/jpg": "jpg",
|
|
8
|
+
"image/gif": "gif",
|
|
9
|
+
"image/webp": "webp",
|
|
10
|
+
"image/svg+xml": "svg"
|
|
11
|
+
}, r = (e) => {
|
|
12
|
+
let t = /^data:([^;,]+)(;base64)?,(.*)$/s.exec(e);
|
|
13
|
+
if (!t) throw Error("not a data URI");
|
|
14
|
+
let n = t[1] || "application/octet-stream", r = !!t[2], i = t[3] ?? "";
|
|
15
|
+
return {
|
|
16
|
+
bytes: r ? new Uint8Array(Buffer.from(i, "base64")) : new Uint8Array(Buffer.from(decodeURIComponent(i), "utf8")),
|
|
17
|
+
contentType: n
|
|
18
|
+
};
|
|
19
|
+
}, i = (i, a) => t.gen(function* () {
|
|
20
|
+
let o = yield* t.try({
|
|
21
|
+
try: () => r(a.dataUri),
|
|
22
|
+
catch: (t) => new e({
|
|
23
|
+
provider: "avatar",
|
|
24
|
+
message: String(t?.message ?? t),
|
|
25
|
+
transient: !1,
|
|
26
|
+
status: 400
|
|
27
|
+
})
|
|
28
|
+
}), s = n[o.contentType] ?? "bin";
|
|
29
|
+
return yield* i.put({
|
|
30
|
+
bytes: o.bytes,
|
|
31
|
+
contentType: o.contentType,
|
|
32
|
+
tenantId: a.tenantId,
|
|
33
|
+
ownerId: a.userId,
|
|
34
|
+
visibility: "public",
|
|
35
|
+
key: `avatars/${a.userId}/avatar.${s}`
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
//#endregion
|
|
39
|
+
export { r as decodeDataUri, i as putAvatar };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
//#region src/byteSecurity.ts
|
|
2
|
+
var e = /^(?:image\/(?!svg)|video\/|audio\/|application\/pdf(?:;|$)|text\/plain(?:;|$))/i, t = (t) => ({
|
|
3
|
+
"x-content-type-options": "nosniff",
|
|
4
|
+
"content-disposition": e.test(t) ? "inline" : "attachment"
|
|
5
|
+
});
|
|
6
|
+
//#endregion
|
|
7
|
+
export { t as storageByteSecurityHeaders };
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { storageObjectNamespace as e } from "./storageNamespaces.js";
|
|
2
|
+
import { azureConnectionTransport as t, databaseProvider as n, filesystemProvider as r, memoryProviderWithState as i, s3ConnectionTransport as a } from "./providers.js";
|
|
3
|
+
import { StorageError as o } from "@voltro/kv/storage";
|
|
4
|
+
import { Effect as s } from "effect";
|
|
5
|
+
//#region src/connection.ts
|
|
6
|
+
var c = (t, n = "") => {
|
|
7
|
+
if (!n) return t;
|
|
8
|
+
let r = (e) => `${n}${e}`;
|
|
9
|
+
return {
|
|
10
|
+
...t,
|
|
11
|
+
objectNamespace: t.objectNamespace.pipe(s.map((t) => e([
|
|
12
|
+
"prefixed-object-namespace",
|
|
13
|
+
t,
|
|
14
|
+
n
|
|
15
|
+
]))),
|
|
16
|
+
...t.objectLocation === void 0 ? {} : { objectLocation: (e) => t.objectLocation(r(e)) },
|
|
17
|
+
put: (e, ...n) => t.put(r(e), ...n),
|
|
18
|
+
get: (e) => t.get(r(e)),
|
|
19
|
+
delete: (e) => t.delete(r(e)),
|
|
20
|
+
head: (e) => t.head(r(e)),
|
|
21
|
+
getUrl: (e, ...n) => t.getUrl(r(e), ...n),
|
|
22
|
+
...t.publicUrl === void 0 ? {} : { publicUrl: (e) => t.publicUrl(r(e)) },
|
|
23
|
+
...t.setObjectPublic === void 0 ? {} : { setObjectPublic: (e, n) => t.setObjectPublic(r(e), n) },
|
|
24
|
+
...t.getUploadUrl === void 0 ? {} : { getUploadUrl: (e, n, i) => t.getUploadUrl(r(e), n, i) },
|
|
25
|
+
...t.createMultipartUpload === void 0 ? {} : { createMultipartUpload: (e, n, i) => t.createMultipartUpload(r(e), n, i) },
|
|
26
|
+
...t.presignUploadPart === void 0 ? {} : { presignUploadPart: (e, n, i, a) => t.presignUploadPart(r(e), n, i, a) },
|
|
27
|
+
...t.completeMultipartUpload === void 0 ? {} : { completeMultipartUpload: (e, n, i) => t.completeMultipartUpload(r(e), n, i) },
|
|
28
|
+
...t.abortMultipartUpload === void 0 ? {} : { abortMultipartUpload: (e, n) => t.abortMultipartUpload(r(e), n) },
|
|
29
|
+
...t.getStream === void 0 ? {} : { getStream: (e) => t.getStream(r(e)) },
|
|
30
|
+
...t.putStream === void 0 ? {} : { putStream: (e, n, i) => t.putStream(r(e), n, i) },
|
|
31
|
+
...t.getRange === void 0 ? {} : { getRange: (e, n, i) => t.getRange(r(e), n, i) }
|
|
32
|
+
};
|
|
33
|
+
}, l = (e, l) => s.suspend(() => {
|
|
34
|
+
let u;
|
|
35
|
+
switch (e.provider) {
|
|
36
|
+
case "s3":
|
|
37
|
+
case "minio":
|
|
38
|
+
u = a({
|
|
39
|
+
bucket: e.bucket,
|
|
40
|
+
name: e.provider,
|
|
41
|
+
...e.region === void 0 ? {} : { region: e.region },
|
|
42
|
+
...e.endpoint === void 0 ? {} : { endpoint: e.endpoint },
|
|
43
|
+
...e.accessKeyId === void 0 ? {} : { accessKeyId: e.accessKeyId },
|
|
44
|
+
...e.secretAccessKey === void 0 ? {} : { secretAccessKey: e.secretAccessKey },
|
|
45
|
+
...e.cdnBaseUrl === void 0 ? {} : { cdnBaseUrl: e.cdnBaseUrl },
|
|
46
|
+
...e.publicAcl === void 0 ? {} : { publicAcl: e.publicAcl },
|
|
47
|
+
forcePathStyle: e.forcePathStyle ?? e.provider === "minio"
|
|
48
|
+
});
|
|
49
|
+
break;
|
|
50
|
+
case "filesystem":
|
|
51
|
+
u = {
|
|
52
|
+
provider: r({ root: e.root }),
|
|
53
|
+
close: s.void,
|
|
54
|
+
probe: s.tryPromise({
|
|
55
|
+
try: async () => {
|
|
56
|
+
let t = await import("node:fs/promises");
|
|
57
|
+
if (!(await t.stat(e.root)).isDirectory()) throw Error("Storage root must be a directory");
|
|
58
|
+
await t.access(e.root);
|
|
59
|
+
},
|
|
60
|
+
catch: () => new o({
|
|
61
|
+
provider: "filesystem",
|
|
62
|
+
message: "Storage root is not readable",
|
|
63
|
+
transient: !1
|
|
64
|
+
})
|
|
65
|
+
})
|
|
66
|
+
};
|
|
67
|
+
break;
|
|
68
|
+
case "memory":
|
|
69
|
+
u = {
|
|
70
|
+
provider: i(l.memory ?? /* @__PURE__ */ new Map()),
|
|
71
|
+
probe: s.void,
|
|
72
|
+
close: s.void
|
|
73
|
+
};
|
|
74
|
+
break;
|
|
75
|
+
case "database": {
|
|
76
|
+
let e = n();
|
|
77
|
+
u = {
|
|
78
|
+
provider: e,
|
|
79
|
+
probe: e.head("_connection_readiness").pipe(s.asVoid),
|
|
80
|
+
close: s.void
|
|
81
|
+
};
|
|
82
|
+
break;
|
|
83
|
+
}
|
|
84
|
+
case "azure": u = t({
|
|
85
|
+
container: e.bucket,
|
|
86
|
+
...e.accountName === void 0 ? {} : { accountName: e.accountName },
|
|
87
|
+
...e.accountKey === void 0 ? {} : { accountKey: e.accountKey },
|
|
88
|
+
...e.connectionString === void 0 ? {} : { connectionString: e.connectionString },
|
|
89
|
+
...e.endpoint === void 0 ? {} : { endpoint: e.endpoint },
|
|
90
|
+
...e.cdnBaseUrl === void 0 ? {} : { cdnBaseUrl: e.cdnBaseUrl }
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return s.succeed({
|
|
94
|
+
...u,
|
|
95
|
+
provider: c(u.provider, e.keyPrefix)
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
//#endregion
|
|
99
|
+
export { l as connectStorage, c as withStorageKeyPrefix };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
//#region src/constraints.ts
|
|
2
|
+
var e = (e, t) => {
|
|
3
|
+
if (!t || t.length === 0) return !0;
|
|
4
|
+
let n = e.toLowerCase();
|
|
5
|
+
return t.some((e) => {
|
|
6
|
+
let t = e.toLowerCase();
|
|
7
|
+
return t.endsWith("/*") ? n.startsWith(t.slice(0, -1)) : n === t;
|
|
8
|
+
});
|
|
9
|
+
}, t = [
|
|
10
|
+
{
|
|
11
|
+
ct: /^image\/png$/,
|
|
12
|
+
offset: 0,
|
|
13
|
+
magic: [
|
|
14
|
+
137,
|
|
15
|
+
80,
|
|
16
|
+
78,
|
|
17
|
+
71
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
ct: /^image\/jpe?g$/,
|
|
22
|
+
offset: 0,
|
|
23
|
+
magic: [
|
|
24
|
+
255,
|
|
25
|
+
216,
|
|
26
|
+
255
|
|
27
|
+
]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
ct: /^image\/gif$/,
|
|
31
|
+
offset: 0,
|
|
32
|
+
magic: [
|
|
33
|
+
71,
|
|
34
|
+
73,
|
|
35
|
+
70,
|
|
36
|
+
56
|
|
37
|
+
]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
ct: /^image\/webp$/,
|
|
41
|
+
offset: 0,
|
|
42
|
+
magic: [
|
|
43
|
+
82,
|
|
44
|
+
73,
|
|
45
|
+
70,
|
|
46
|
+
70
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
ct: /^image\/bmp$/,
|
|
51
|
+
offset: 0,
|
|
52
|
+
magic: [66, 77]
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
ct: /^application\/pdf$/,
|
|
56
|
+
offset: 0,
|
|
57
|
+
magic: [
|
|
58
|
+
37,
|
|
59
|
+
80,
|
|
60
|
+
68,
|
|
61
|
+
70
|
|
62
|
+
]
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
ct: /^application\/zip$/,
|
|
66
|
+
offset: 0,
|
|
67
|
+
magic: [
|
|
68
|
+
80,
|
|
69
|
+
75,
|
|
70
|
+
3,
|
|
71
|
+
4
|
|
72
|
+
]
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
ct: /^application\/gzip$/,
|
|
76
|
+
offset: 0,
|
|
77
|
+
magic: [31, 139]
|
|
78
|
+
}
|
|
79
|
+
], n = (e, n) => {
|
|
80
|
+
let r = n.toLowerCase(), i = t.find((e) => e.ct.test(r));
|
|
81
|
+
return i ? e.length < i.offset + i.magic.length ? !1 : i.magic.every((t, n) => e[i.offset + n] === t) : !0;
|
|
82
|
+
}, r = (t, r, i) => i ? i.maxBytes !== void 0 && t.byteLength > i.maxBytes ? {
|
|
83
|
+
reason: "too-large",
|
|
84
|
+
detail: `${t.byteLength} bytes exceeds the ${i.maxBytes}-byte limit`
|
|
85
|
+
} : e(r, i.allowedContentTypes) ? i.sniff && !n(t, r) ? {
|
|
86
|
+
reason: "content-mismatch",
|
|
87
|
+
detail: `bytes do not match the declared content type '${r}'`
|
|
88
|
+
} : null : {
|
|
89
|
+
reason: "content-type-not-allowed",
|
|
90
|
+
detail: `content type '${r}' is not in the allow-list`
|
|
91
|
+
} : null;
|
|
92
|
+
//#endregion
|
|
93
|
+
export { r as checkLimits, e as contentTypeAllowed, n as sniffMatches };
|
package/dist/custody.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { StorageError as e } from "./types.js";
|
|
2
|
+
import { storageMetadataError as t } from "./metadataError.js";
|
|
3
|
+
import { readRetainedStorageObject as n } from "./retainedObjects.js";
|
|
4
|
+
import { abandonRetainedCapture as r, admitRetainedCaptureObject as i, inspectRetainedSource as a, registerRetainedCapture as o, retainedCaptureItems as s, runRetainedCapture as c, sealRetainedCapture as l } from "./retainedCapture.js";
|
|
5
|
+
import { Context as u, Effect as d, Layer as f, Schema as p, Stream as m } from "effect";
|
|
6
|
+
import { canonicalJson as h, pluginInstanceName as g } from "@voltro/protocol";
|
|
7
|
+
//#region src/custody.ts
|
|
8
|
+
var _ = "@voltro/plugin-storage", v = (e) => g({
|
|
9
|
+
base: _,
|
|
10
|
+
alias: e.alias,
|
|
11
|
+
instance: e.name
|
|
12
|
+
}), y = (e) => `${_}/StorageCustody:${JSON.stringify(e)}`, b = class extends u.Service()(y(_)) {
|
|
13
|
+
static withSource(e) {
|
|
14
|
+
return d.suspend(() => {
|
|
15
|
+
let t = E.get(e.source), n = E.get(e.destination);
|
|
16
|
+
return t === void 0 || n === void 0 ? d.fail(x("Custody transfer requires configured storage services")) : t !== n && v(t.selection) === v(n.selection) ? d.fail(x("Custody source and destination need distinct configured instance identities")) : d.succeed(D(n, t));
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
static forInstance(e = {}) {
|
|
20
|
+
return u.Service(y(v(e)));
|
|
21
|
+
}
|
|
22
|
+
}, x = (t, n = 409) => new e({
|
|
23
|
+
provider: "custody",
|
|
24
|
+
message: t,
|
|
25
|
+
status: n,
|
|
26
|
+
transient: !1
|
|
27
|
+
}), S = (n) => p.is(e)(n) ? n : t(n, "custody"), C = (e) => d.tryPromise({
|
|
28
|
+
try: e,
|
|
29
|
+
catch: S
|
|
30
|
+
}), w = (e) => ({
|
|
31
|
+
id: e.id,
|
|
32
|
+
instance: e.instance,
|
|
33
|
+
objectNamespace: e.objectNamespace,
|
|
34
|
+
tenantId: e.tenantId,
|
|
35
|
+
bucket: e.bucket,
|
|
36
|
+
contextDigest: e.contextDigest,
|
|
37
|
+
...e.source === void 0 ? {} : { source: {
|
|
38
|
+
instance: e.source.instance,
|
|
39
|
+
objectNamespace: e.source.objectNamespace,
|
|
40
|
+
bucket: e.source.bucket
|
|
41
|
+
} }
|
|
42
|
+
}), T = (e) => ({
|
|
43
|
+
...w(e),
|
|
44
|
+
count: e.count,
|
|
45
|
+
digest: e.digest
|
|
46
|
+
}), E = /* @__PURE__ */ new WeakMap(), D = (e, t = e) => {
|
|
47
|
+
let u = v(t.selection), f = v(e.selection), p = e.bucket, g = e.provider, _ = (t) => d.gen(function* () {
|
|
48
|
+
let n = yield* g.objectNamespace;
|
|
49
|
+
if (t !== void 0 && (t.instance !== f || t.bucket !== p || t.objectNamespace !== n)) return yield* d.fail(x("Capture belongs to another storage instance or bucket", 403));
|
|
50
|
+
let r = e.store();
|
|
51
|
+
return yield* r === void 0 ? d.fail(x("Storage custody requires a bound database and the default reference and grant stores")) : d.succeed(r);
|
|
52
|
+
}), y = () => d.gen(function* () {
|
|
53
|
+
let e = yield* t.provider.objectNamespace;
|
|
54
|
+
return u === f && t.bucket === p ? void 0 : {
|
|
55
|
+
instance: u,
|
|
56
|
+
objectNamespace: e,
|
|
57
|
+
bucket: t.bucket
|
|
58
|
+
};
|
|
59
|
+
}), b = () => d.gen(function* () {
|
|
60
|
+
let e = yield* _();
|
|
61
|
+
return t.store() === e ? e : yield* d.fail(x("Custody source and destination must share the bound application database"));
|
|
62
|
+
}), D = {
|
|
63
|
+
inspect: (e, n) => d.gen(function* () {
|
|
64
|
+
let r = n.id, i = n.tenantId;
|
|
65
|
+
yield* b();
|
|
66
|
+
let o = yield* t.provider.objectNamespace;
|
|
67
|
+
return yield* C(() => a(e, {
|
|
68
|
+
instance: u,
|
|
69
|
+
objectNamespace: o,
|
|
70
|
+
bucket: t.bucket,
|
|
71
|
+
tenantId: i
|
|
72
|
+
}, r));
|
|
73
|
+
}),
|
|
74
|
+
register: (e, t) => d.gen(function* () {
|
|
75
|
+
let n = yield* y(), r = {
|
|
76
|
+
id: t.id,
|
|
77
|
+
instance: f,
|
|
78
|
+
objectNamespace: yield* g.objectNamespace,
|
|
79
|
+
tenantId: t.tenantId,
|
|
80
|
+
bucket: p,
|
|
81
|
+
contextDigest: t.contextDigest,
|
|
82
|
+
...n === void 0 ? {} : { source: n }
|
|
83
|
+
}, i = t.maxBytes, a = t.sources;
|
|
84
|
+
return yield* b(), {
|
|
85
|
+
key: r,
|
|
86
|
+
count: (yield* C(() => o(e, r, {
|
|
87
|
+
maxBytes: i,
|
|
88
|
+
sources: a
|
|
89
|
+
}))).count
|
|
90
|
+
};
|
|
91
|
+
}),
|
|
92
|
+
run: (n) => d.gen(function* () {
|
|
93
|
+
let r = w(n);
|
|
94
|
+
yield* _(r);
|
|
95
|
+
let i = yield* b();
|
|
96
|
+
yield* c(i, g, r, e.quota, {
|
|
97
|
+
instance: u,
|
|
98
|
+
bucket: t.bucket,
|
|
99
|
+
provider: t.provider
|
|
100
|
+
}).pipe(d.mapError(S));
|
|
101
|
+
}),
|
|
102
|
+
seal: (e, t) => d.gen(function* () {
|
|
103
|
+
let n = w(t);
|
|
104
|
+
return yield* _(n), yield* C(() => l(e, n));
|
|
105
|
+
}),
|
|
106
|
+
items: (e, t) => m.unwrap(d.gen(function* () {
|
|
107
|
+
let n = T(e), r = yield* _(n);
|
|
108
|
+
return m.fromAsyncIterable(s(t ?? r, n), S);
|
|
109
|
+
})),
|
|
110
|
+
read: (e, t, r) => d.gen(function* () {
|
|
111
|
+
let a = T(e), o = yield* d.try({
|
|
112
|
+
try: () => JSON.parse(h(t)),
|
|
113
|
+
catch: S
|
|
114
|
+
}), s = {
|
|
115
|
+
storageInstance: f,
|
|
116
|
+
tenantId: r.tenantId,
|
|
117
|
+
maxBytes: r.maxBytes,
|
|
118
|
+
bucket: p
|
|
119
|
+
};
|
|
120
|
+
if (a.tenantId !== s.tenantId || o.tenantId !== s.tenantId) return yield* d.fail(x("Capture tenant does not match admitted tenant", 403));
|
|
121
|
+
let c = yield* _(a);
|
|
122
|
+
return yield* C(() => i(c, a, o)), yield* n(c, g, o, s).pipe(d.mapError(S));
|
|
123
|
+
}),
|
|
124
|
+
abandon: (e) => d.gen(function* () {
|
|
125
|
+
let t = w(e), n = yield* _(t);
|
|
126
|
+
yield* r(n, g, t).pipe(d.mapError(S));
|
|
127
|
+
})
|
|
128
|
+
};
|
|
129
|
+
return E.set(D, e), D;
|
|
130
|
+
}, O = (e) => f.succeed(b.forInstance(e.selection), D(e));
|
|
131
|
+
//#endregion
|
|
132
|
+
export { b as StorageCustody, O as storageCustodyLayer };
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { isTransformable as e, loadSharp as t } from "./transform.js";
|
|
2
|
+
//#region src/derivatives.ts
|
|
3
|
+
var n = async (n, r, i) => {
|
|
4
|
+
if (!e(r)) return { bytes: n };
|
|
5
|
+
let a = await t();
|
|
6
|
+
if (!a) return { bytes: n };
|
|
7
|
+
try {
|
|
8
|
+
let e = n;
|
|
9
|
+
if (i?.normalize) {
|
|
10
|
+
let t = await a(Buffer.from(n), { failOn: "none" }).rotate().toBuffer();
|
|
11
|
+
e = new Uint8Array(t);
|
|
12
|
+
}
|
|
13
|
+
let t = await a(Buffer.from(e), { failOn: "none" }).metadata(), r = await a(Buffer.from(e), { failOn: "none" }).resize({
|
|
14
|
+
width: 24,
|
|
15
|
+
height: 24,
|
|
16
|
+
fit: "inside",
|
|
17
|
+
withoutEnlargement: !0
|
|
18
|
+
}).webp({ quality: 40 }).toBuffer().catch(() => null);
|
|
19
|
+
return {
|
|
20
|
+
bytes: e,
|
|
21
|
+
...typeof t.width == "number" ? { width: t.width } : {},
|
|
22
|
+
...typeof t.height == "number" ? { height: t.height } : {},
|
|
23
|
+
...r ? { placeholder: `data:image/webp;base64,${r.toString("base64")}` } : {}
|
|
24
|
+
};
|
|
25
|
+
} catch {
|
|
26
|
+
return { bytes: n };
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
export { n as extractImageMetadata };
|
package/dist/env.js
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { pluginEnv as e } from "@voltro/env";
|
|
2
|
+
//#region src/env.ts
|
|
3
|
+
var t = e([
|
|
4
|
+
{
|
|
5
|
+
name: "STORAGE_PROVIDER",
|
|
6
|
+
required: !1,
|
|
7
|
+
secret: !1,
|
|
8
|
+
description: "Provider name: 's3' | 'minio' | 'azure' | 'database' | 'filesystem' | 'memory'. Default 'memory'."
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: "S3_BUCKET",
|
|
12
|
+
required: !1,
|
|
13
|
+
secret: !1,
|
|
14
|
+
description: "S3/MinIO bucket name. Default STORAGE_BUCKET, else \"voltro\"."
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
name: "STORAGE_BUCKET",
|
|
18
|
+
required: !1,
|
|
19
|
+
secret: !1,
|
|
20
|
+
description: "Fallback bucket name when S3_BUCKET is unset."
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "S3_REGION",
|
|
24
|
+
required: !1,
|
|
25
|
+
secret: !1,
|
|
26
|
+
description: "S3 region. Falls back to AWS_REGION."
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
name: "AWS_REGION",
|
|
30
|
+
required: !1,
|
|
31
|
+
secret: !1,
|
|
32
|
+
description: "AWS region fallback for the S3 provider."
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: "S3_ENDPOINT",
|
|
36
|
+
required: !1,
|
|
37
|
+
secret: !1,
|
|
38
|
+
description: "Custom S3 endpoint URL. Falls back to MINIO_ENDPOINT."
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
name: "MINIO_ENDPOINT",
|
|
42
|
+
required: !1,
|
|
43
|
+
secret: !1,
|
|
44
|
+
description: "MinIO endpoint URL fallback for the S3 provider."
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
name: "S3_ACCESS_KEY_ID",
|
|
48
|
+
required: !1,
|
|
49
|
+
secret: !0,
|
|
50
|
+
description: "S3 access key id. Falls back to AWS_ACCESS_KEY_ID."
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "AWS_ACCESS_KEY_ID",
|
|
54
|
+
required: !1,
|
|
55
|
+
secret: !0,
|
|
56
|
+
description: "AWS access key id fallback for the S3 provider."
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
name: "S3_SECRET_ACCESS_KEY",
|
|
60
|
+
required: !1,
|
|
61
|
+
secret: !0,
|
|
62
|
+
description: "S3 secret access key. Falls back to AWS_SECRET_ACCESS_KEY."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
name: "AWS_SECRET_ACCESS_KEY",
|
|
66
|
+
required: !1,
|
|
67
|
+
secret: !0,
|
|
68
|
+
description: "AWS secret access key fallback for the S3 provider."
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
name: "S3_FORCE_PATH_STYLE",
|
|
72
|
+
required: !1,
|
|
73
|
+
secret: !1,
|
|
74
|
+
description: "Set to \"1\" to force path-style S3 addressing."
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
name: "STORAGE_CDN_URL",
|
|
78
|
+
required: !1,
|
|
79
|
+
secret: !1,
|
|
80
|
+
description: "Public base URL (CDN) served in front of the bucket for public objects."
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "VOLTRO_PUBLIC_URL",
|
|
84
|
+
required: !1,
|
|
85
|
+
secret: !1,
|
|
86
|
+
description: "Absolute public base URL of THIS api (scheme://host[:port]) — prepended to the /_voltro/storage/:id serve URL so getUrl/mintUrl/the upload route emit ABSOLUTE URLs. Required when the api is a different origin than the app."
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "STORAGE_ALLOWED_ORIGINS",
|
|
90
|
+
required: !1,
|
|
91
|
+
secret: !1,
|
|
92
|
+
description: "Comma-separated origins allowed to read the serve route + POST the upload route cross-origin (CORS). Use '*' for any. Omit for same-origin only."
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
name: "AZURE_STORAGE_ACCOUNT",
|
|
96
|
+
required: !1,
|
|
97
|
+
secret: !1,
|
|
98
|
+
description: "Azure storage account name (provider \"azure\")."
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
name: "AZURE_STORAGE_KEY",
|
|
102
|
+
required: !1,
|
|
103
|
+
secret: !0,
|
|
104
|
+
description: "Azure account key — needed to mint SAS URLs."
|
|
105
|
+
},
|
|
106
|
+
{
|
|
107
|
+
name: "AZURE_STORAGE_CONNECTION_STRING",
|
|
108
|
+
required: !1,
|
|
109
|
+
secret: !0,
|
|
110
|
+
description: "Azure connection string (carries the account key)."
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
name: "AZURE_STORAGE_ENDPOINT",
|
|
114
|
+
required: !1,
|
|
115
|
+
secret: !1,
|
|
116
|
+
description: "Custom Azure blob endpoint (e.g. Azurite)."
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: "AZURE_STORAGE_CONTAINER",
|
|
120
|
+
required: !1,
|
|
121
|
+
secret: !1,
|
|
122
|
+
description: "Azure blob container name (the \"bucket\")."
|
|
123
|
+
},
|
|
124
|
+
{
|
|
125
|
+
name: "STORAGE_ROOT",
|
|
126
|
+
required: !1,
|
|
127
|
+
secret: !1,
|
|
128
|
+
description: "Filesystem provider root directory. Default \".voltro-storage\"."
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
name: "VOLTRO_STORAGE_SECRET",
|
|
132
|
+
required: !1,
|
|
133
|
+
secret: !0,
|
|
134
|
+
description: "HMAC secret for signed storage links and upload tickets. Falls back to the session secret (and its VOLTRO_SESSION_SECRET_PREVIOUS)."
|
|
135
|
+
},
|
|
136
|
+
{
|
|
137
|
+
name: "VOLTRO_STORAGE_SECRET_PREVIOUS",
|
|
138
|
+
required: !1,
|
|
139
|
+
secret: !0,
|
|
140
|
+
description: "During a VOLTRO_STORAGE_SECRET rotation: the secret links and tickets were signed with until now. They keep verifying until they expire; then remove it."
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: "VOLTRO_SESSION_COOKIE",
|
|
144
|
+
required: !1,
|
|
145
|
+
secret: !1,
|
|
146
|
+
description: "Session cookie name used to resolve private-object access. Default \"voltro:session\"."
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: "CLAMAV_HOST",
|
|
150
|
+
required: !1,
|
|
151
|
+
secret: !1,
|
|
152
|
+
description: "clamd host for the optional clamav virus scanner. Default 127.0.0.1."
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
name: "CLAMAV_PORT",
|
|
156
|
+
required: !1,
|
|
157
|
+
secret: !1,
|
|
158
|
+
description: "clamd port for the optional clamav virus scanner. Default 3310."
|
|
159
|
+
}
|
|
160
|
+
]);
|
|
161
|
+
//#endregion
|
|
162
|
+
export { t as storageEnv };
|