@voltro/plugin-storage 0.83.1 → 0.85.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -9,11 +9,14 @@ import { mixin } from '@voltro/database';
9
9
  import { PluginEgressDeclaration } from '@voltro/protocol';
10
10
  import { Readable } from 'node:stream';
11
11
  import { Schema } from 'effect';
12
+ import { StorageAccessPolicy } from '@voltro/protocol';
13
+ import { StorageAccessRule } from '@voltro/protocol';
12
14
  import { Stream } from 'effect';
13
15
  import { Subject } from '@voltro/protocol';
14
16
  import { Table } from '@voltro/database';
15
17
  import { TableLike } from '@voltro/database';
16
18
  import { VoltroPlugin } from '@voltro/protocol';
19
+ import { YieldableError } from 'effect/Cause';
17
20
 
18
21
  export declare interface AccessOptions {
19
22
  /** Resolve a subject's group ids. Default: `subject.metadata.groups`. */
@@ -22,7 +25,7 @@ export declare interface AccessOptions {
22
25
  readonly guards?: Record<string, StorageGuard>;
23
26
  }
24
27
 
25
- export declare type AccessPolicy = ReadonlyArray<AccessRule>;
28
+ export declare type AccessPolicy = StorageAccessPolicy;
26
29
 
27
30
  /**
28
31
  * One access rule. A rule GRANTS when EVERY present condition is satisfied
@@ -30,24 +33,7 @@ export declare type AccessPolicy = ReadonlyArray<AccessRule>;
30
33
  * grants (OR). An empty policy on a private object means owner-only (the
31
34
  * implicit owner check always applies).
32
35
  */
33
- export declare interface AccessRule {
34
- /** `subject.id === ref.ownerId`. */
35
- readonly owner?: boolean;
36
- /** `subject.metadata.roles` intersects these. */
37
- readonly roles?: ReadonlyArray<string>;
38
- /** resolved groups (default `subject.metadata.groups`) intersect these. */
39
- readonly groups?: ReadonlyArray<string>;
40
- /** `subject.scopes` intersects these. */
41
- readonly scopes?: ReadonlyArray<string>;
42
- /** `subject.tenantId === ref.tenantId`. */
43
- readonly tenant?: boolean;
44
- /** subject is any authenticated api-key. */
45
- readonly apiKey?: boolean;
46
- /** a correct password was supplied (checked at mint, against passwordHash). */
47
- readonly password?: boolean;
48
- /** a named custom guard (`options.guards[name]`) returns true. */
49
- readonly guard?: string;
50
- }
36
+ export declare type AccessRule = StorageAccessRule;
51
37
 
52
38
  /**
53
39
  * The provider the APP configured, falling back to the env-derived default when no
@@ -193,6 +179,12 @@ export declare const decodeDataUri: (dataUri: string) => {
193
179
  */
194
180
  export declare const durabilityNotice: (path: string) => string | undefined;
195
181
 
182
+ /**
183
+ * Apply a creative edit; null when the input is not an image, `sharp` is not
184
+ * installed, the edit is empty or fails. The format stays the input's.
185
+ */
186
+ export declare const editImage: (bytes: Uint8Array, contentType: string, edit: ImageEdit) => Promise<Transformed | null>;
187
+
196
188
  /** Frame bytes for clamd `INSTREAM`: `zINSTREAM\0` then `<u32 len><data>`
197
189
  * chunks, terminated by a zero-length chunk. Exposed for testing. */
198
190
  export declare const encodeInstream: (bytes: Uint8Array, chunkSize?: number) => Buffer;
@@ -286,6 +278,19 @@ export declare interface GrantTokenPayload {
286
278
  /** Hash a file password as `salt:scryptHash` (hex). */
287
279
  export declare const hashPassword: (password: string) => string;
288
280
 
281
+ /** A creative edit: the result is new bytes, stored as a new version — unlike a delivery transform. */
282
+ export declare interface ImageEdit {
283
+ /** Clockwise, in degrees. */
284
+ readonly rotate?: 90 | 180 | 270;
285
+ /** Mirror top to bottom. */
286
+ readonly flip?: boolean;
287
+ /** Mirror left to right. */
288
+ readonly flop?: boolean;
289
+ readonly grayscale?: boolean;
290
+ /** Cut this region out, in pixels of the original. */
291
+ readonly crop?: Region;
292
+ }
293
+
289
294
  /** Where `ingestUrl` may fetch from — see `ssrfReason` in `@voltro/runtime`. */
290
295
  export declare interface IngestPolicy {
291
296
  /** Hosts (or `host:port`, or `*.suffix`) reachable although internal — a
@@ -339,9 +344,9 @@ export declare const makeStorageObjectMetadata: (store: DataStore) => {
339
344
  identity: {
340
345
  readonly tenantId: string | null;
341
346
  readonly bucket: string;
347
+ readonly operationId: string;
342
348
  readonly fingerprint: string;
343
349
  readonly checksum: string;
344
- readonly operationId: string;
345
350
  readonly logicalKey: string;
346
351
  };
347
352
  ref: StorageRef;
@@ -363,30 +368,30 @@ export declare const makeStorageObjectMetadata: (store: DataStore) => {
363
368
  identity: {
364
369
  readonly tenantId: string | null;
365
370
  readonly bucket: string;
371
+ readonly operationId: string;
366
372
  readonly fingerprint: string;
367
373
  readonly checksum: string;
368
- readonly operationId: string;
369
374
  readonly logicalKey: string;
370
375
  };
371
376
  operation?: never;
372
377
  } | {
373
378
  released: true;
374
379
  operation: {
375
- readonly key: string;
376
380
  readonly id: string;
381
+ readonly objectId: string;
377
382
  readonly fingerprint: string;
378
383
  readonly generation: string;
379
- readonly state: "ready" | "reserved" | "deleted" | "deleting";
384
+ readonly key: string;
380
385
  readonly refId: string;
381
- readonly objectId: string;
386
+ readonly state: "ready" | "reserved" | "deleted" | "deleting";
382
387
  readonly deleteKey: string | null;
383
388
  };
384
389
  identity: {
385
390
  readonly tenantId: string | null;
386
391
  readonly bucket: string;
392
+ readonly operationId: string;
387
393
  readonly fingerprint: string;
388
394
  readonly checksum: string;
389
- readonly operationId: string;
390
395
  readonly logicalKey: string;
391
396
  };
392
397
  }>;
@@ -593,6 +598,14 @@ export declare interface RefStore {
593
598
  readonly countPublicByKey?: ((bucket: string, key: string) => Effect.Effect<number, StorageError>) | undefined;
594
599
  }
595
600
 
601
+ /** A region of an image in pixels. */
602
+ declare interface Region {
603
+ readonly left: number;
604
+ readonly top: number;
605
+ readonly width: number;
606
+ readonly height: number;
607
+ }
608
+
596
609
  /** One derived output a `Transcoder` produces from a source asset. Each becomes
597
610
  * a normal storage ref linked back to the original via `derivedFrom`. */
598
611
  export declare interface Rendition {
@@ -659,19 +672,19 @@ export declare interface S3ProviderOptions {
659
672
  export declare const setActiveStorageProvider: (provider: StorageProvider | undefined) => void;
660
673
 
661
674
  /** Sign a finalize token valid for `ttlSeconds`. */
662
- export declare const signFinalizeTicket: (fields: Omit<FinalizeTicketPayload, "exp">, ttlSeconds: number, secret?: string) => string;
675
+ export declare const signFinalizeTicket: (fields: Omit<FinalizeTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
663
676
 
664
677
  /** Mint a token granting read access to `refId` for `ttlSeconds`. */
665
678
  export declare const signGrant: (refId: string, ttlSeconds: number, secret?: string, audience?: string) => string;
666
679
 
667
680
  /** Sign a multipart ticket valid for `ttlSeconds`. */
668
- export declare const signMultipartTicket: (fields: Omit<MultipartTicketPayload, "exp">, ttlSeconds: number, secret?: string) => string;
681
+ export declare const signMultipartTicket: (fields: Omit<MultipartTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
669
682
 
670
683
  /** Sign a resumable ticket valid for `ttlSeconds`. */
671
- export declare const signResumableTicket: (fields: Omit<ResumableTicketPayload, "exp">, ttlSeconds: number, secret?: string) => string;
684
+ export declare const signResumableTicket: (fields: Omit<ResumableTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
672
685
 
673
686
  /** Sign an upload ticket valid for `ttlSeconds`. */
674
- export declare const signUploadTicket: (fields: Omit<UploadTicketPayload, "exp">, ttlSeconds: number, secret?: string) => string;
687
+ export declare const signUploadTicket: (fields: Omit<UploadTicketPayload, "exp" | "version">, ttlSeconds: number, secret?: string) => string;
675
688
 
676
689
  /** True if the bytes match the declared content type's magic number, OR we
677
690
  * have no signature for that type (can't verify → don't block). */
@@ -693,12 +706,10 @@ export declare const STORAGE_USAGE_TABLE = "_voltro_storage_usage";
693
706
  export declare class StorageAccessDenied extends StorageAccessDenied_base {
694
707
  }
695
708
 
696
- declare const StorageAccessDenied_base: Schema.TaggedErrorClass<StorageAccessDenied, "StorageAccessDenied", {
697
- readonly _tag: Schema.tag<"StorageAccessDenied">;
698
- } & {
699
- refId: typeof Schema.String;
700
- reason: typeof Schema.String;
701
- }>;
709
+ declare const StorageAccessDenied_base: Schema.Class<StorageAccessDenied, Schema.TaggedStruct<"StorageAccessDenied", {
710
+ readonly refId: Schema.String;
711
+ readonly reason: Schema.String;
712
+ }>, YieldableError>;
702
713
 
703
714
  /**
704
715
  * Where the bytes go, read from the same options and env `resolveProvider`
@@ -715,14 +726,12 @@ export declare const storageEgressOf: (options: StoragePluginOptions) => Readonl
715
726
  export declare class StorageError extends StorageError_base {
716
727
  }
717
728
 
718
- declare const StorageError_base: Schema.TaggedErrorClass<StorageError, "StorageError", {
719
- readonly _tag: Schema.tag<"StorageError">;
720
- } & {
721
- provider: typeof Schema.String;
722
- message: typeof Schema.String;
723
- transient: typeof Schema.Boolean;
724
- status: Schema.optional<typeof Schema.Number>;
725
- }>;
729
+ declare const StorageError_base: Schema.Class<StorageError, Schema.TaggedStruct<"StorageError", {
730
+ readonly provider: Schema.String;
731
+ readonly message: Schema.String;
732
+ readonly transient: Schema.Boolean;
733
+ readonly status: Schema.optional<Schema.Number>;
734
+ }>, YieldableError>;
726
735
 
727
736
  /** A persisted, explicit share of one object with one principal. */
728
737
  export declare interface StorageGrant {
@@ -784,7 +793,7 @@ export declare const storageObjectLifecycleTables: ( Table<"_voltro_storage_oper
784
793
  readonly id: ColumnBuilder<string, "text", boolean>;
785
794
  readonly generation: ColumnBuilder<string, "text", boolean>;
786
795
  readonly key: ColumnBuilder<string, "text", boolean>;
787
- readonly reservations: ColumnBuilder<number, "integer", boolean>;
796
+ readonly reservations: ColumnBuilder<string, "bigint", boolean>;
788
797
  }>, false, never> | Table<"_voltro_storage_operations", FieldDefinitions<{
789
798
  readonly id: ColumnBuilder<string, "text", boolean>;
790
799
  readonly objectId: ColumnBuilder<string, "text", boolean>;
@@ -866,12 +875,25 @@ export declare interface StoragePluginOptions {
866
875
  /** Access engine config — a group resolver + named custom guards. */
867
876
  readonly access?: AccessOptions;
868
877
  /**
869
- * The access policy every PRIVATE client upload (an upload ticket) is
870
- * stored with — declared by the app, never chosen by the client. Without
878
+ * The default policy for PRIVATE client uploads, or an Effect function
879
+ * authorizing self-service upload visibility and requested policies before the server signs a ticket. Without
871
880
  * it only the uploader (the implicit owner rule) can read a private upload;
872
881
  * `[{ tenant: true }]` lets everyone in the uploader's tenant read it.
873
882
  */
874
- readonly uploadAccess?: AccessPolicy;
883
+ readonly uploadAccess?: AccessPolicy | ((request: {
884
+ readonly subject: Subject;
885
+ readonly requested: AccessPolicy | undefined;
886
+ readonly visibility: 'public' | 'private';
887
+ readonly contentType: string;
888
+ }) => Effect.Effect<AccessPolicy, StorageError>);
889
+ /**
890
+ * How long a browser or shared cache may keep a PUBLIC object the serve
891
+ * route delivers itself (and each transform of it). Default: a year,
892
+ * `immutable`. An object can turn private later; a copy cached before then
893
+ * keeps opening until this runs out — set it to bound that window (the
894
+ * bucket's own caching, behind a direct URL, is the provider's).
895
+ */
896
+ readonly publicCacheSeconds?: number;
875
897
  /** Upload constraints — size cap, content-type allow-list, magic-byte sniff. */
876
898
  readonly limits?: UploadLimits;
877
899
  /**
@@ -1086,15 +1108,14 @@ export declare interface StorageRef {
1086
1108
  /** An upload was rejected by a constraint (size / content-type / magic-byte
1087
1109
  * mismatch). `reason` is a stable code; `detail` is human-readable. */
1088
1110
  export declare class StorageRejected extends StorageRejected_base {
1111
+ get message(): string;
1089
1112
  }
1090
1113
 
1091
- declare const StorageRejected_base: Schema.TaggedErrorClass<StorageRejected, "StorageRejected", {
1092
- readonly _tag: Schema.tag<"StorageRejected">;
1093
- } & {
1114
+ declare const StorageRejected_base: Schema.Class<StorageRejected, Schema.TaggedStruct<"StorageRejected", {
1094
1115
  /** `'too-large' | 'content-type-not-allowed' | 'content-mismatch'` */
1095
- reason: typeof Schema.String;
1096
- detail: typeof Schema.String;
1097
- }>;
1116
+ readonly reason: Schema.String;
1117
+ readonly detail: Schema.String;
1118
+ }>, YieldableError>;
1098
1119
 
1099
1120
  /** Scans bytes before they're stored. Return `{ clean:false, threat }` to
1100
1121
  * reject the upload (fails with `StorageScanRejected`, nothing is stored). */
@@ -1108,11 +1129,9 @@ export declare type StorageScanner = (input: {
1108
1129
  export declare class StorageScanRejected extends StorageScanRejected_base {
1109
1130
  }
1110
1131
 
1111
- declare const StorageScanRejected_base: Schema.TaggedErrorClass<StorageScanRejected, "StorageScanRejected", {
1112
- readonly _tag: Schema.tag<"StorageScanRejected">;
1113
- } & {
1114
- threat: typeof Schema.String;
1115
- }>;
1132
+ declare const StorageScanRejected_base: Schema.Class<StorageScanRejected, Schema.TaggedStruct<"StorageScanRejected", {
1133
+ readonly threat: Schema.String;
1134
+ }>, YieldableError>;
1116
1135
 
1117
1136
  export declare interface StorageScanResult {
1118
1137
  readonly clean: boolean;
@@ -1123,7 +1142,7 @@ export declare interface StorageScanResult {
1123
1142
  export declare class StorageService extends StorageService_base {
1124
1143
  }
1125
1144
 
1126
- declare const StorageService_base: Context.TagClass<StorageService, "@voltro/plugin-storage/StorageService", StorageServiceShape>;
1145
+ declare const StorageService_base: Context.ServiceClass<StorageService, "@voltro/plugin-storage/StorageService", StorageServiceShape>;
1127
1146
 
1128
1147
  export declare const storageServiceLayer: (options: StorageServiceOptions) => Layer.Layer<StorageService>;
1129
1148
 
@@ -1370,6 +1389,7 @@ export declare interface StorageServiceShape {
1370
1389
  readonly tenantId?: string | null;
1371
1390
  readonly ownerId?: string | null;
1372
1391
  readonly visibility?: StorageVisibility;
1392
+ readonly access?: AccessPolicy;
1373
1393
  readonly folder?: string;
1374
1394
  readonly tags?: ReadonlyArray<string>;
1375
1395
  readonly alt?: string;
@@ -1403,6 +1423,7 @@ export declare interface StorageServiceShape {
1403
1423
  readonly tenantId?: string | null;
1404
1424
  readonly ownerId?: string | null;
1405
1425
  readonly visibility?: StorageVisibility;
1426
+ readonly access?: AccessPolicy;
1406
1427
  readonly folder?: string;
1407
1428
  readonly tags?: ReadonlyArray<string>;
1408
1429
  readonly alt?: string;
@@ -1452,6 +1473,7 @@ export declare interface StorageServiceShape {
1452
1473
  readonly tenantId?: string | null;
1453
1474
  readonly ownerId?: string | null;
1454
1475
  readonly visibility?: StorageVisibility;
1476
+ readonly access?: AccessPolicy;
1455
1477
  readonly folder?: string;
1456
1478
  readonly tags?: ReadonlyArray<string>;
1457
1479
  readonly alt?: string;
@@ -1564,6 +1586,8 @@ export declare interface UploadLimits {
1564
1586
  /** What an upload ticket authorizes: a single through-app upload bound to an
1565
1587
  * owner/tenant/content-type/visibility, until `exp`. */
1566
1588
  export declare interface UploadTicketPayload {
1589
+ /** Frozen-policy ticket contract. Previous tickets must be minted again. */
1590
+ readonly version: 1;
1567
1591
  /** Optional durable write identity, preserved across ticket renewal. */
1568
1592
  readonly operationId?: string;
1569
1593
  /** Inspect uploads may be pinned to their issuing process. */
@@ -1577,6 +1601,8 @@ export declare interface UploadTicketPayload {
1577
1601
  /** Authorized content-type — the stored object uses this. */
1578
1602
  readonly ct: string;
1579
1603
  readonly vis: 'public' | 'private';
1604
+ /** Server-authorized policy, frozen into the signed ticket. */
1605
+ readonly access?: AccessPolicy;
1580
1606
  /** Optional fixed logical key (else content-addressed). */
1581
1607
  readonly key?: string;
1582
1608
  /** Max bytes the route will accept for this ticket (0/undefined = no cap). */