@voltro/plugin-storage 0.86.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.
@@ -1,53 +1,91 @@
1
- import { join as e } from "node:path";
2
- import { spawn as t } from "node:child_process";
3
- import { mkdtemp as n, readFile as r, rm as i, writeFile as a } from "node:fs/promises";
4
- import { tmpdir as o } from "node:os";
1
+ import { createHash as e } from "node:crypto";
2
+ import { join as t } from "node:path";
3
+ import { canonicalJson as n } from "@voltro/protocol";
4
+ import { mkdtemp as r, readFile as i, rm as a, writeFile as o } from "node:fs/promises";
5
+ import { tmpdir as s } from "node:os";
6
+ import { spawn as c } from "node:child_process";
5
7
  //#region src/transcodeVideo.ts
6
- var s = (e, n, r) => new Promise((i, a) => {
7
- let o = t(e, [...n], { stdio: [
8
- "ignore",
8
+ var l = (e, t, n) => new Promise((r, i) => {
9
+ let a = c(e, [...t], { stdio: [
9
10
  "ignore",
11
+ "pipe",
10
12
  "pipe"
11
- ] }), s = "", c = setTimeout(() => {
12
- o.kill("SIGKILL"), a(/* @__PURE__ */ Error("ffmpeg timed out"));
13
- }, r);
14
- o.stderr?.on("data", (e) => {
15
- s += String(e);
16
- }), o.on("error", (e) => {
17
- clearTimeout(c), a(e);
18
- }), o.on("close", (e) => {
19
- clearTimeout(c), e === 0 ? i() : a(/* @__PURE__ */ Error(`ffmpeg exited ${String(e)}: ${s.slice(-400)}`));
13
+ ] }), o = "", s = "", l, u = (e) => {
14
+ l === void 0 && (l = Error(e), a.kill("SIGKILL"));
15
+ }, d = setTimeout(() => u("ffmpeg timed out"), n);
16
+ a.stdout?.on("data", (e) => {
17
+ if (o.length + e.length > 65536) return u("ffmpeg output exceeds its metadata bound");
18
+ o += String(e);
19
+ }), a.stderr?.on("data", (e) => {
20
+ s = (s + String(e)).slice(-400);
21
+ }), a.on("error", (e) => {
22
+ clearTimeout(d), i(e);
23
+ }), a.on("close", (e) => {
24
+ clearTimeout(d), l === void 0 ? e === 0 ? r(o) : i(/* @__PURE__ */ Error(`ffmpeg exited ${String(e)}: ${s}`)) : i(l);
20
25
  });
21
- }), c = (t) => async ({ bytes: c }) => {
22
- let l = t.ffmpegPath ?? "ffmpeg", u = t.timeoutMs ?? 3e5, d = await n(e(o(), "voltro-tc-")), f = e(d, "in"), p = [];
23
- try {
24
- await a(f, c);
25
- for (let n of t.renditions) {
26
- let t = e(d, `out.${n.ext}`);
27
- try {
28
- await s(l, [
29
- "-y",
30
- "-i",
31
- f,
32
- ...n.args,
33
- t
34
- ], u);
35
- let e = await r(t);
36
- p.push({
37
- bytes: new Uint8Array(e),
38
- contentType: n.contentType,
39
- kind: n.kind,
40
- ...n.label === void 0 ? {} : { label: n.label }
41
- }), await i(t, { force: !0 });
42
- } catch {}
26
+ }), u = (t) => e("sha256").update(t).digest("hex"), d = (e) => {
27
+ let t = e.replace(/\r\n/g, "\n").trim(), n = t.split("\n")[0] ?? "";
28
+ if (!n.startsWith("ffmpeg version ") || n.length > 512) throw Error("ffmpeg version metadata unavailable");
29
+ return {
30
+ name: "ffmpeg",
31
+ version: n.slice(15),
32
+ digest: u(t)
33
+ };
34
+ }, f = (e) => {
35
+ let c = e.ffmpegPath ?? "ffmpeg", f = e.timeoutMs ?? 3e5, p = e.renditions.map((e) => ({
36
+ ...e,
37
+ args: [...e.args]
38
+ }));
39
+ return async ({ bytes: e }) => {
40
+ let m = await r(t(s(), "voltro-tc-")), h = t(m, "in"), g = [];
41
+ try {
42
+ await o(h, e);
43
+ for (let e of p) {
44
+ let r = t(m, `out.${e.ext}`);
45
+ try {
46
+ let t = performance.now() + f, o = (e) => {
47
+ let n = Math.ceil(t - performance.now());
48
+ if (n <= 0) throw Error("ffmpeg rendition timed out");
49
+ return l(c, e, n);
50
+ }, s = d(await o(["-version"]));
51
+ await o([
52
+ "-y",
53
+ "-i",
54
+ h,
55
+ ...e.args,
56
+ r
57
+ ]);
58
+ let p = d(await o(["-version"]));
59
+ if (s.digest !== p.digest) throw Error("ffmpeg build changed during rendition");
60
+ let m = await i(r);
61
+ g.push({
62
+ bytes: new Uint8Array(m),
63
+ contentType: e.contentType,
64
+ kind: e.kind,
65
+ derivation: {
66
+ engine: s,
67
+ recipe: {
68
+ format: "ffmpeg-cli/v1",
69
+ digest: u(n({
70
+ kind: e.kind,
71
+ contentType: e.contentType,
72
+ ext: e.ext,
73
+ args: e.args
74
+ }))
75
+ }
76
+ },
77
+ ...e.label === void 0 ? {} : { label: e.label }
78
+ }), await a(r, { force: !0 });
79
+ } catch {}
80
+ }
81
+ } finally {
82
+ await a(m, {
83
+ recursive: !0,
84
+ force: !0
85
+ }).catch(() => void 0);
43
86
  }
44
- } finally {
45
- await i(d, {
46
- recursive: !0,
47
- force: !0
48
- }).catch(() => void 0);
49
- }
50
- return p;
87
+ return g;
88
+ };
51
89
  };
52
90
  //#endregion
53
- export { c as ffmpegTranscoder };
91
+ export { f as ffmpegTranscoder };
package/dist/transform.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { createTransformCache as e } from "./transformCache.js";
2
+ import { createHash as t } from "node:crypto";
2
3
  //#region src/transform.ts
3
- var t = (e, t, n, r, i, a) => {
4
+ var n = (e, t, n, r, i, a) => {
4
5
  let o = n / r, s = Math.min(e, Math.round(t * o)), c = Math.min(t, Math.round(s / o)), l = (e, t) => Math.max(0, Math.min(t, e));
5
6
  return {
6
7
  left: l(Math.round(i * e - s / 2), e - s),
@@ -8,13 +9,13 @@ var t = (e, t, n, r, i, a) => {
8
9
  width: s,
9
10
  height: c
10
11
  };
11
- }, n = {
12
+ }, r = {
12
13
  webp: "image/webp",
13
14
  avif: "image/avif",
14
15
  jpeg: "image/jpeg",
15
16
  jpg: "image/jpeg",
16
17
  png: "image/png"
17
- }, r = (e) => {
18
+ }, i = (e) => {
18
19
  if (!e) return null;
19
20
  let t = new URLSearchParams(e), n = (e) => {
20
21
  let n = t.get(e);
@@ -33,28 +34,28 @@ var t = (e, t, n, r, i, a) => {
33
34
  ...s ? { fit: s } : {},
34
35
  ...l ? { fp: l } : {}
35
36
  };
36
- }, i = (e) => e.startsWith("image/") && !e.includes("svg"), a, o = async () => (a ??= import("sharp").then((e) => e.default ?? e, () => null), a), s = async (e, t) => t ? null : i(e) ? await o() === null ? "no-sharp" : "failed" : "not-an-image", c = async (e, r, a) => {
37
- if (!i(r)) return null;
38
- let s = await o();
39
- if (!s) return null;
37
+ }, a = (e) => e.startsWith("image/") && !e.includes("svg"), o, s = async () => (o ??= import("sharp").then((e) => e.default ?? e, () => null), o), c = async (e, t) => t ? null : a(e) ? await s() === null ? "no-sharp" : "failed" : "not-an-image", l = async (e, t, i) => {
38
+ if (!a(t)) return null;
39
+ let o = await s();
40
+ if (!o) return null;
40
41
  try {
41
- let i = s(Buffer.from(e), { failOn: "none" }), o = a.fit ?? "cover";
42
- if (a.fp !== void 0 && a.w !== void 0 && a.h !== void 0 && o === "cover") {
43
- let e = await i.metadata();
44
- e.width !== void 0 && e.height !== void 0 && (i = i.extract(t(e.width, e.height, a.w, a.h, a.fp.x, a.fp.y)));
42
+ let a = o(Buffer.from(e), { failOn: "none" }), s = i.fit ?? "cover";
43
+ if (i.fp !== void 0 && i.w !== void 0 && i.h !== void 0 && s === "cover") {
44
+ let e = await a.metadata();
45
+ e.width !== void 0 && e.height !== void 0 && (a = a.extract(n(e.width, e.height, i.w, i.h, i.fp.x, i.fp.y)));
45
46
  }
46
- (a.w !== void 0 || a.h !== void 0) && (i = i.resize({
47
- ...a.w === void 0 ? {} : { width: a.w },
48
- ...a.h === void 0 ? {} : { height: a.h },
49
- fit: a.fit ?? "cover",
47
+ (i.w !== void 0 || i.h !== void 0) && (a = a.resize({
48
+ ...i.w === void 0 ? {} : { width: i.w },
49
+ ...i.h === void 0 ? {} : { height: i.h },
50
+ fit: i.fit ?? "cover",
50
51
  withoutEnlargement: !0
51
52
  }));
52
- let c = a.format && n[a.format] ? a.format : void 0, l = r;
53
+ let c = i.format && r[i.format] ? i.format : void 0, l = t;
53
54
  if (c) {
54
55
  let e = c === "jpg" ? "jpeg" : c;
55
- i = i.toFormat(e, a.q === void 0 ? {} : { quality: a.q }), l = n[c];
56
- } else a.q !== void 0 && (r === "image/jpeg" || r === "image/webp") && (i = i.toFormat(r === "image/webp" ? "webp" : "jpeg", { quality: a.q }));
57
- let u = await i.toBuffer();
56
+ a = a.toFormat(e, i.q === void 0 ? {} : { quality: i.q }), l = r[c];
57
+ } else i.q !== void 0 && (t === "image/jpeg" || t === "image/webp") && (a = a.toFormat(t === "image/webp" ? "webp" : "jpeg", { quality: i.q }));
58
+ let u = await a.toBuffer();
58
59
  return {
59
60
  bytes: new Uint8Array(u),
60
61
  contentType: l
@@ -62,9 +63,9 @@ var t = (e, t, n, r, i, a) => {
62
63
  } catch {
63
64
  return null;
64
65
  }
65
- }, l = async (e, t, n) => {
66
- if (!i(t) || n.rotate === void 0 && n.flip !== !0 && n.flop !== !0 && n.grayscale !== !0 && n.crop === void 0) return null;
67
- let r = await o();
66
+ }, u = async (e, t, n) => {
67
+ if (!a(t) || n.rotate === void 0 && n.flip !== !0 && n.flop !== !0 && n.grayscale !== !0 && n.crop === void 0) return null;
68
+ let r = await s();
68
69
  if (!r) return null;
69
70
  try {
70
71
  let i = r(Buffer.from(e), { failOn: "none" });
@@ -75,7 +76,7 @@ var t = (e, t, n, r, i, a) => {
75
76
  } catch {
76
77
  return null;
77
78
  }
78
- }, u = e(), d = (e, t, n) => JSON.stringify([
79
+ }, d = (e, t, n) => JSON.stringify([
79
80
  e,
80
81
  t,
81
82
  n.w,
@@ -85,6 +86,9 @@ var t = (e, t, n, r, i, a) => {
85
86
  n.fit,
86
87
  n.fp?.x,
87
88
  n.fp?.y
88
- ]), f = async (e, t, n, r) => u.getOrCreate(d(e, n, r), () => c(t, n, r));
89
+ ]), f = () => {
90
+ let n = e();
91
+ return async (e, r, i, a) => n.getOrCreate(d(JSON.stringify([e, t("sha256").update(r).digest("hex")]), i, a), () => l(r, i, a));
92
+ }, p = f();
89
93
  //#endregion
90
- export { l as editImage, t as focalRegion, i as isTransformable, o as loadSharp, r as parseTransformParams, f as transformCached, s as transformFallbackReason, c as transformImage, d as transformKey };
94
+ export { f as createTransformCache, u as editImage, n as focalRegion, a as isTransformable, s as loadSharp, i as parseTransformParams, p as transformCached, c as transformFallbackReason, l as transformImage, d as transformKey };
package/dist/types.d.ts CHANGED
@@ -1,9 +1,21 @@
1
+ import { ByteStream } from '@voltro/kv/storage';
1
2
  import { Context } from 'effect';
2
3
  import { Effect } from 'effect';
4
+ import { isStorageKey } from '@voltro/kv/storage';
5
+ import { ObjectMeta } from '@voltro/kv/storage';
3
6
  import { Schema } from 'effect';
4
7
  import { StorageAccessPolicy } from '@voltro/protocol';
5
8
  import { StorageAccessRule } from '@voltro/protocol';
6
- import { Stream } from 'effect';
9
+ import { StorageAddress } from '@voltro/kv/storage';
10
+ import { storageAddressIdentity } from '@voltro/kv/storage';
11
+ import { StorageDestination } from '@voltro/kv/storage';
12
+ import { StorageDestinationRouter } from '@voltro/kv/storage';
13
+ import { StorageError } from '@voltro/kv/storage';
14
+ import { StorageObjectLocation } from '@voltro/kv/storage';
15
+ import { StorageProvider } from '@voltro/kv/storage';
16
+ import { StorageProviderName } from '@voltro/kv/storage';
17
+ import { StoredObject } from '@voltro/kv/storage';
18
+ import { StoredStream } from '@voltro/kv/storage';
7
19
  import { Subject } from '@voltro/protocol';
8
20
  import { YieldableError } from 'effect/Cause';
9
21
 
@@ -17,14 +29,7 @@ export declare type AccessPolicy = StorageAccessPolicy;
17
29
  */
18
30
  export declare type AccessRule = StorageAccessRule;
19
31
 
20
- /**
21
- * A lazy stream of bytes — the streaming counterpart to {@link StoredObject}'s
22
- * eager `bytes`. Backpressured: chunks are pulled on demand, so a multi-GB blob
23
- * never fully materialises in memory. The error channel is {@link StorageError}
24
- * so a stream handed to `putStream` carries the same typed failure the rest of
25
- * the transport does (callers map their source errors into it).
26
- */
27
- export declare type ByteStream = Stream.Stream<Uint8Array, StorageError>;
32
+ export { ByteStream }
28
33
 
29
34
  /** Input to `StorageService.grant`. */
30
35
  export declare interface GrantInput {
@@ -40,6 +45,8 @@ export declare type GrantPermission = 'read' | 'write';
40
45
 
41
46
  export declare type GrantPrincipalType = 'user' | 'group' | 'apiKey';
42
47
 
48
+ export { isStorageKey }
49
+
43
50
  /**
44
51
  * Filter + page a `StorageService.listRefs` / media-library query. Every
45
52
  * filter is optional and ANDed; omit them all to page the whole (tenant-scoped)
@@ -75,17 +82,15 @@ export declare interface ListRefsResult {
75
82
  readonly nextOffset: number | null;
76
83
  }
77
84
 
78
- /** Result of a provider `put` / `head`. */
79
- export declare interface ObjectMeta {
80
- readonly size: number;
81
- readonly etag?: string;
82
- }
85
+ export { ObjectMeta }
83
86
 
84
87
  /** A ref with the server-only `passwordHash` removed — safe to serialize. */
85
88
  export declare type PublicStorageRef = Omit<StorageRef, 'passwordHash'>;
86
89
 
87
90
  /** Input to `StorageService.put`. */
88
91
  export declare interface PutInput {
92
+ /** Server-owned logical destination; omitted new writes use this plugin's selection. */
93
+ readonly destination?: string;
89
94
  readonly bytes: Uint8Array;
90
95
  readonly contentType: string;
91
96
  /** Active org id — scopes the key prefix + the ref row. */
@@ -148,6 +153,8 @@ export declare type PutOutcome = 'created' | 'resumed' | 'joined';
148
153
  /** One derived output a `Transcoder` produces from a source asset. Each becomes
149
154
  * a normal storage ref linked back to the original via `derivedFrom`. */
150
155
  export declare interface Rendition {
156
+ /** Optional structured producer attestation; unproven outputs keep it absent. */
157
+ readonly derivation?: RenditionDerivation;
151
158
  readonly bytes: Uint8Array;
152
159
  readonly contentType: string;
153
160
  /** Derivative class — e.g. `'rendition'` (a playable variant) or `'poster'`. */
@@ -156,6 +163,39 @@ export declare interface Rendition {
156
163
  readonly label?: string;
157
164
  }
158
165
 
166
+ /** Producer attestation. Digests bind a normalized recipe and the observed
167
+ * engine build; the consumer still admits allowed recipes, engines and rights. */
168
+ export declare interface RenditionDerivation {
169
+ readonly recipe: {
170
+ readonly format: string;
171
+ readonly digest: string;
172
+ };
173
+ readonly engine: {
174
+ readonly name: string;
175
+ readonly version: string;
176
+ readonly digest: string;
177
+ };
178
+ }
179
+
180
+ /** Storage-verified input/output identity around a producer attestation.
181
+ * Not an approval, rights grant or proof of complete external dependencies. */
182
+ export declare interface RenditionProof extends RenditionDerivation {
183
+ readonly version: 1;
184
+ readonly source: {
185
+ readonly id: string;
186
+ readonly metadataDigest: string;
187
+ readonly checksum: string;
188
+ readonly size: number;
189
+ readonly contentType: string;
190
+ };
191
+ readonly output: {
192
+ readonly checksum: string;
193
+ readonly size: number;
194
+ readonly contentType: string;
195
+ };
196
+ readonly digest: string;
197
+ }
198
+
159
199
  /**
160
200
  * Access to a private object was refused. Distinct from `StorageError` so
161
201
  * handlers can tell "not allowed" (403) from "backend blew up" (5xx) and
@@ -169,20 +209,15 @@ declare const StorageAccessDenied_base: Schema.Class<StorageAccessDenied, Schema
169
209
  readonly reason: Schema.String;
170
210
  }>, YieldableError>;
171
211
 
172
- /**
173
- * Storage failure. `transient` drives retry — `true` for 429 / 5xx /
174
- * network blips, `false` for 4xx / config / not-found. Surfaced typed so
175
- * handlers can `Effect.catchTag('StorageError', …)`.
176
- */
177
- export declare class StorageError extends StorageError_base {
178
- }
212
+ export { StorageAddress }
179
213
 
180
- declare const StorageError_base: Schema.Class<StorageError, Schema.TaggedStruct<"StorageError", {
181
- readonly provider: Schema.String;
182
- readonly message: Schema.String;
183
- readonly transient: Schema.Boolean;
184
- readonly status: Schema.optional<Schema.Number>;
185
- }>, YieldableError>;
214
+ export { storageAddressIdentity }
215
+
216
+ export { StorageDestination }
217
+
218
+ export { StorageDestinationRouter }
219
+
220
+ export { StorageError }
186
221
 
187
222
  /** A persisted, explicit share of one object with one principal. */
188
223
  export declare interface StorageGrant {
@@ -202,100 +237,27 @@ export declare type StorageGuard = (ctx: {
202
237
  readonly ref: StorageRef;
203
238
  }) => boolean | Promise<boolean> | Effect.Effect<boolean>;
204
239
 
205
- /**
206
- * A backend transport. Keyed by an opaque object key (the service owns key
207
- * derivation + tenant prefixing); knows nothing about refs, tenants, or
208
- * access policy.
209
- */
210
- export declare interface StorageProvider {
211
- /** Stable physical object-key namespace; exclude credentials and plugin aliases. */
212
- readonly objectNamespace: Effect.Effect<string, StorageError>;
213
- /**
214
- * Where a FILESYSTEM provider writes. Absent for every other provider.
215
- *
216
- * It exists so a caller can ask a question this interface otherwise cannot
217
- * answer: does what I write here survive the process? A rollback capture
218
- * written for a pod that then dies is worthless, and `filesystem` is the one
219
- * provider that can be either — a mounted volume or an ephemeral container
220
- * directory. A deployment pointed this out: their check for "storage is
221
- * configured" passed on a `/tmp` path with no volume behind it.
222
- */
223
- readonly root?: string;
224
- readonly name: string;
225
- /** True when `getUrl` returns a real presigned URL (s3/minio). When
226
- * false the service serves bytes via the `/_voltro/storage/:id`
227
- * endpoint instead (filesystem/memory). */
228
- readonly presigns: boolean;
229
- readonly put: (key: string, bytes: Uint8Array, meta: {
230
- readonly contentType: string;
231
- readonly public?: boolean;
232
- }) => Effect.Effect<ObjectMeta, StorageError>;
233
- readonly get: (key: string) => Effect.Effect<StoredObject, StorageError>;
234
- readonly delete: (key: string) => Effect.Effect<void, StorageError>;
235
- /** Presigned GET (s3/minio) for private reads. */
236
- readonly getUrl: (key: string, expiresInSec: number) => Effect.Effect<string, StorageError>;
237
- /** A direct, cacheable URL for a PUBLIC object — virtual-hosted bucket
238
- * URL or a configured CDN base. `null` when the provider can't serve
239
- * publicly without the app (filesystem/memory) → the service falls
240
- * back to the `/_voltro/storage/:id` route. */
241
- readonly publicUrl?: (key: string) => string | null;
242
- /** Make an object publicly readable at the provider, or not (s3/minio with
243
- * per-object ACLs). Absent where the app decides per request (database,
244
- * filesystem, memory) — there a ref's visibility is the whole answer. */
245
- readonly setObjectPublic?: (key: string, isPublic: boolean) => Effect.Effect<void, StorageError>;
246
- /** Presigned PUT for direct-to-bucket uploads (s3/minio). Absent on
247
- * providers that can't presign uploads. */
248
- readonly getUploadUrl?: (key: string, expiresInSec: number, contentType: string) => Effect.Effect<string, StorageError>;
249
- readonly createMultipartUpload?: (key: string, contentType: string, opts?: {
250
- readonly public?: boolean;
251
- }) => Effect.Effect<{
252
- readonly uploadId: string;
253
- }, StorageError>;
254
- readonly presignUploadPart?: (key: string, uploadId: string, partNumber: number, expiresInSec: number) => Effect.Effect<string, StorageError>;
255
- readonly completeMultipartUpload?: (key: string, uploadId: string, parts: ReadonlyArray<{
256
- readonly partNumber: number;
257
- readonly etag: string;
258
- }>) => Effect.Effect<{
259
- readonly etag?: string;
260
- }, StorageError>;
261
- readonly abortMultipartUpload?: (key: string, uploadId: string) => Effect.Effect<void, StorageError>;
262
- readonly head: (key: string) => Effect.Effect<ObjectMeta | null, StorageError>;
263
- /**
264
- * Stream an object's bytes in chunks instead of buffering the whole blob
265
- * ({@link get}). Present on backends with a real streaming read (filesystem,
266
- * s3, azure). OPTIONAL: when a provider omits it, {@link getObjectStream}
267
- * transparently falls back to `get` + a chunked in-memory stream — correct
268
- * for the small-blob backends (memory / database) that have nothing to
269
- * stream from.
270
- */
271
- readonly getStream?: (key: string) => Effect.Effect<StoredStream, StorageError>;
272
- /**
273
- * Consume a {@link ByteStream} and store it via the backend's chunked /
274
- * multipart upload, so a large upload never fully materialises in memory.
275
- * Present on filesystem (streamed atomic write), s3 (multipart via
276
- * `@aws-sdk/lib-storage`), azure (`uploadStream`). OPTIONAL: when absent,
277
- * {@link putObjectStream} buffers the stream and calls `put`. `totalSize`,
278
- * when the caller knows it (e.g. from an export manifest), lets the backend
279
- * report an accurate size without a follow-up HEAD.
280
- */
281
- readonly putStream?: (key: string, stream: ByteStream, meta: {
282
- readonly contentType: string;
283
- readonly public?: boolean;
284
- readonly totalSize?: number;
285
- }) => Effect.Effect<ObjectMeta, StorageError>;
286
- /**
287
- * Read a byte range `[start, endInclusive]` (HTTP Range / fd offset). Enables
288
- * seekable + resumable reads. OPTIONAL and best-effort: whole-object resume
289
- * already works via content-addressing (skip an asset whose hash is present
290
- * at the target), so this is an optimisation, not a correctness requirement.
291
- */
292
- readonly getRange?: (key: string, start: number, endInclusive: number) => Effect.Effect<Uint8Array, StorageError>;
293
- /** Providers that store bytes via the app's DataStore (e.g. `database`)
294
- * receive it here once it exists — bound by the plugin at boot. */
295
- readonly bindDataStore?: (store: unknown) => void;
296
- }
240
+ export { StorageObjectLocation }
241
+
242
+ /** A durable metadata observation, not a claim on the object or a provider HEAD. */
243
+ export declare type StorageOperationState = {
244
+ readonly state: 'absent' | 'reserved' | 'deleting' | 'deleted';
245
+ } | {
246
+ readonly state: 'ready';
247
+ readonly ref: StorageRef;
248
+ };
249
+
250
+ /** Metadata state qualified by the actual selected storage destination. */
251
+ export declare type StorageOperationStatus = StorageOperationState & {
252
+ /** Configured instance, actual catalogue, physical namespace and logical bucket. */
253
+ readonly scope: string;
254
+ /** Selected logical destination; retain it with the scope before minting a ticket. */
255
+ readonly destination: string;
256
+ };
297
257
 
298
- export declare type StorageProviderName = 's3' | 'minio' | 'azure' | 'filesystem' | 'memory' | 'database';
258
+ export { StorageProvider }
259
+
260
+ export { StorageProviderName }
299
261
 
300
262
  /**
301
263
  * Metadata row for a stored blob — the bytes live in object storage; this
@@ -335,8 +297,13 @@ export declare interface StorageRef {
335
297
  readonly derivedFrom?: string | null;
336
298
  /** Derivative label — e.g. `'rendition'` / `'poster'`; null on an original. */
337
299
  readonly kind?: string | null;
300
+ /** Immutable provenance for an attested transcode; absent on older/unproven refs. */
301
+ readonly renditionProof?: RenditionProof | null;
338
302
  }
339
303
 
304
+ /** Persisted destination ownership is independent of the current write selection. */
305
+ export declare const storageRefAddress: (ref: Pick<StorageRef, "bucket" | "tenantId" | "key">) => StorageAddress;
306
+
340
307
  /** An upload was rejected by a constraint (size / content-type / magic-byte
341
308
  * mismatch). `reason` is a stable code; `detail` is human-readable. */
342
309
  export declare class StorageRejected extends StorageRejected_base {
@@ -378,8 +345,20 @@ declare const StorageService_base: Context.ServiceClass<StorageService, "@voltro
378
345
 
379
346
  /** The service handlers consume: `const storage = yield* StorageService`. */
380
347
  export declare interface StorageServiceShape {
348
+ /** Trusted server read of one raw durable put identity in this configured
349
+ * instance/physical namespace/bucket and the explicitly supplied tenant.
350
+ * No access-policy check, claim, byte read, retry write or provider HEAD.
351
+ * Unbound/unsupported/corrupt metadata refuses; it never becomes absence. */
352
+ readonly operationStatus: (input: {
353
+ readonly operationId: string;
354
+ readonly tenantId: string | null;
355
+ readonly destination?: string;
356
+ }) => Effect.Effect<StorageOperationStatus, StorageError>;
381
357
  /** Opaque destination binding for manually signed upload-family credentials. */
382
- readonly uploadTicketScope: () => Effect.Effect<string, StorageError>;
358
+ readonly uploadTicketScope: (input?: {
359
+ readonly tenantId?: string | null;
360
+ readonly destination?: string;
361
+ }) => Effect.Effect<string, StorageError>;
383
362
  /** Store bytes. The result is the ref plus this call's `outcome`. */
384
363
  readonly put: (input: PutInput) => Effect.Effect<StoredRef, StorageError | StorageRejected | StorageScanRejected>;
385
364
  /** Fetch an object's bytes by id. Enforces the tenant guard ONLY — it does
@@ -550,9 +529,9 @@ export declare interface StorageServiceShape {
550
529
  readonly contentType: string;
551
530
  readonly tenantId?: string | null;
552
531
  readonly expiresInSec?: number;
553
- }) => Effect.Effect<{
532
+ }) => Effect.Effect<StorageAddress & {
533
+ readonly writeScope: string;
554
534
  readonly uploadUrl: string;
555
- readonly key: string;
556
535
  readonly expiresInSec: number;
557
536
  }, StorageError>;
558
537
  /** Promote a blob the client PUT directly to a TEMP `key` into a real ref:
@@ -561,6 +540,7 @@ export declare interface StorageServiceShape {
561
540
  * scanned + a non-degraded ref — out of band from the direct PUT. On a scan
562
541
  * reject the temp bytes are deleted (fail-closed, nothing promoted). */
563
542
  readonly finalizeUpload: (input: {
543
+ readonly destination: string;
564
544
  readonly key: string;
565
545
  readonly contentType: string;
566
546
  readonly tenantId?: string | null;
@@ -575,6 +555,7 @@ export declare interface StorageServiceShape {
575
555
  /** Store one chunk of a resumable upload as its own durable `_incoming/<id>/
576
556
  * <index>` object. Idempotent — re-PUTting the same index overwrites it. */
577
557
  readonly putResumableChunk: (input: {
558
+ readonly destination: string;
578
559
  readonly uploadId: string;
579
560
  readonly index: number;
580
561
  readonly bytes: Uint8Array;
@@ -584,6 +565,7 @@ export declare interface StorageServiceShape {
584
565
  * 0 and returns the received byte `offset` + the `nextIndex` to send. Lets a
585
566
  * reconnecting client resume instead of restarting. */
586
567
  readonly resumableStatus: (input: {
568
+ readonly destination: string;
587
569
  readonly uploadId: string;
588
570
  readonly tenantId?: string | null;
589
571
  }) => Effect.Effect<{
@@ -594,6 +576,7 @@ export declare interface StorageServiceShape {
594
576
  * concatenate, run the FULL `put()` pipeline, then delete the chunk objects
595
577
  * (on success AND on reject — fail-closed). */
596
578
  readonly finalizeResumable: (input: {
579
+ readonly destination: string;
597
580
  readonly uploadId: string;
598
581
  readonly count: number;
599
582
  readonly contentType: string;
@@ -625,12 +608,14 @@ export declare interface StorageServiceShape {
625
608
  readonly tenantId?: string | null;
626
609
  readonly visibility?: StorageVisibility;
627
610
  readonly expiresInSec?: number;
628
- }) => Effect.Effect<{
611
+ }) => Effect.Effect<StorageAddress & {
612
+ readonly writeScope: string;
629
613
  readonly uploadId: string;
630
- readonly key: string;
631
614
  }, StorageError>;
632
615
  /** Presign a PUT for one part (1-based `partNumber`). */
633
616
  readonly signMultipartPart: (input: {
617
+ readonly destination: string;
618
+ readonly tenantId: string | null;
634
619
  readonly key: string;
635
620
  readonly uploadId: string;
636
621
  readonly partNumber: number;
@@ -640,6 +625,7 @@ export declare interface StorageServiceShape {
640
625
  * registered straight from it (size via HEAD, etag as checksum). No fetch-back,
641
626
  * so NO scan/derivatives — scan multi-GB out of band. */
642
627
  readonly completeMultipart: (input: {
628
+ readonly destination: string;
643
629
  readonly key: string;
644
630
  readonly uploadId: string;
645
631
  readonly parts: ReadonlyArray<{
@@ -658,6 +644,8 @@ export declare interface StorageServiceShape {
658
644
  }) => Effect.Effect<StorageRef, StorageError | StorageRejected>;
659
645
  /** Abort a multipart session (client cancelled) — frees the bucket's parts. */
660
646
  readonly abortMultipart: (input: {
647
+ readonly destination: string;
648
+ readonly tenantId: string | null;
661
649
  readonly key: string;
662
650
  readonly uploadId: string;
663
651
  }) => Effect.Effect<void, StorageError>;
@@ -667,11 +655,7 @@ export declare interface StorageServiceShape {
667
655
  * the ref's access policy + grants. Default for a new put is `private`. */
668
656
  export declare type StorageVisibility = 'public' | 'private';
669
657
 
670
- /** Bytes + content type as returned by a provider `get`. */
671
- export declare interface StoredObject {
672
- readonly bytes: Uint8Array;
673
- readonly contentType: string;
674
- }
658
+ export { StoredObject }
675
659
 
676
660
  /** What `put` returns: the stored ref, plus what this call did to it. The
677
661
  * `outcome` is a fact about the CALL, not the row — it is never persisted and
@@ -680,13 +664,7 @@ export declare type StoredRef = StorageRef & {
680
664
  readonly outcome: PutOutcome;
681
665
  };
682
666
 
683
- /** A streamed read from a provider — the object as a {@link ByteStream} plus
684
- * its content type and, when the backend reports it up front, its size. */
685
- export declare interface StoredStream {
686
- readonly stream: ByteStream;
687
- readonly contentType: string;
688
- readonly size?: number;
689
- }
667
+ export { StoredStream }
690
668
 
691
669
  /**
692
670
  * App-supplied (or first-party `ffmpegTranscoder`) transcode step. Given a