@voltro/plugin-storage 0.70.0 → 0.71.1

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
@@ -3,12 +3,14 @@ import { ColumnType } from '@voltro/database';
3
3
  import { Context } from 'effect';
4
4
  import { DataStore } from '@voltro/database';
5
5
  import { Effect } from 'effect';
6
+ import { FieldDefinitions } from '@voltro/database';
6
7
  import { Layer } from 'effect';
7
8
  import { mixin } from '@voltro/database';
8
9
  import { Readable } from 'node:stream';
9
10
  import { Schema } from 'effect';
10
11
  import { Stream } from 'effect';
11
12
  import { Subject } from '@voltro/protocol';
13
+ import { Table } from '@voltro/database';
12
14
  import { TableLike } from '@voltro/database';
13
15
  import { VoltroPlugin } from '@voltro/protocol';
14
16
 
@@ -420,7 +422,20 @@ export declare const putObjectStream: (provider: StorageProvider, key: string, s
420
422
  export declare const refMatchesListQuery: (ref: StorageRef, q: ResolvedListQuery) => boolean;
421
423
 
422
424
  /** Where blob METADATA rows live (the bytes are in the provider). */
425
+ export declare interface RefStatistics {
426
+ readonly byTenant: Record<string, {
427
+ count: number;
428
+ bytes: number;
429
+ }>;
430
+ readonly visibility: {
431
+ public: number;
432
+ private: number;
433
+ };
434
+ }
435
+
423
436
  export declare interface RefStore {
437
+ /** Aggregate the complete reference index without a recent-object limit. */
438
+ readonly statistics: () => Effect.Effect<RefStatistics, StorageError>;
424
439
  readonly insert: (ref: StorageRef) => Effect.Effect<void, StorageError>;
425
440
  readonly getById: (id: string) => Effect.Effect<StorageRef | null, StorageError>;
426
441
  /** Remove one ref row. Returns whether a row was ACTUALLY removed — the
@@ -479,6 +494,7 @@ export declare interface Rendition {
479
494
  /** A `ListRefsInput` after the service normalizes its defaults / clamps — what
480
495
  * a `RefStore.listRefs` impl receives. `limit` is already the `+1` probe. */
481
496
  export declare interface ResolvedListQuery {
497
+ readonly search?: string;
482
498
  readonly tenantId: string | null | undefined;
483
499
  readonly folder: string | undefined;
484
500
  readonly tags: ReadonlyArray<string>;
@@ -606,6 +622,39 @@ export declare type StorageGuard = (ctx: {
606
622
  readonly ref: StorageRef;
607
623
  }) => boolean | Promise<boolean> | Effect.Effect<boolean>;
608
624
 
625
+ /**
626
+ * The instance name an upload ticket must carry, for THIS installation.
627
+ *
628
+ * `signUploadTicket` takes `instance: string`, and the upload route compares it
629
+ * against the plugin's own instance — so a wrong value is not a type error, it
630
+ * is a bare `401` on every upload. The value has always been derivable
631
+ * (`storagePlugin(options).name` is this same expression), but nothing said so
632
+ * and `BASE_NAME` is not exported, so a caller minting a ticket outside the
633
+ * plugin had to guess it. Guessing is exactly what a `string` parameter invites.
634
+ *
635
+ * const ticket = signUploadTicket(
636
+ * { instance: storageInstanceName(), key, contentType },
637
+ * 300,
638
+ * )
639
+ *
640
+ * Pass the SAME options the installation was built with — `alias` and `name`
641
+ * both move the instance, and a ticket minted for the default name does not
642
+ * open a door installed under an alias.
643
+ */
644
+ export declare const storageInstanceName: (options?: StoragePluginOptions) => string;
645
+
646
+ declare interface StorageObjectIdentity {
647
+ readonly tenantId: string | null;
648
+ readonly bucket: string;
649
+ readonly operationId: string;
650
+ /** Hash of all immutable put inputs, including content and access metadata. */
651
+ readonly fingerprint: string;
652
+ /** SHA-256 of the actual normalized provider bytes, separate from metadata. */
653
+ readonly checksum: string;
654
+ /** Already tenant-scoped logical provider key. */
655
+ readonly logicalKey: string;
656
+ }
657
+
609
658
  export declare const storagePlugin: (options?: StoragePluginOptions) => VoltroPlugin;
610
659
 
611
660
  export declare interface StoragePluginOptions {
@@ -815,6 +864,13 @@ export declare interface StorageProvider {
815
864
 
816
865
  export declare type StorageProviderName = 's3' | 'minio' | 'azure' | 'filesystem' | 'memory' | 'database';
817
866
 
867
+ export declare const storagePutIntentsTable: Table<"_voltro_storage_put_intents", FieldDefinitions<{
868
+ readonly id: ColumnBuilder<string, "text", boolean>;
869
+ readonly refId: ColumnBuilder<string, "text", boolean>;
870
+ readonly identity: ColumnBuilder<StorageObjectIdentity, "json", boolean>;
871
+ readonly ref: ColumnBuilder<StorageRef, "json", boolean>;
872
+ }>, false, never>;
873
+
818
874
  /** Per-tenant upload budget (see `StorageServiceOptions.quota`). */
819
875
  export declare interface StorageQuota {
820
876
  readonly maxBytes?: number;
@@ -938,6 +994,8 @@ export declare interface StorageServiceOptions {
938
994
  * the app: a browser `<img>` and server-to-server AI-gateway fetches both
939
995
  * need an absolute, reachable URL. Omit for same-origin (relative URL). */
940
996
  readonly publicBaseUrl?: string;
997
+ /** Instance-specific HTTP mount supplied by the storage plugin. */
998
+ readonly httpPath?: string;
941
999
  /** Normalize raster images on upload: apply EXIF orientation, then strip ALL
942
1000
  * metadata (GPS/camera) and re-encode. Off by default (it re-encodes, which
943
1001
  * changes the bytes + content-address). Width/height/placeholder are always
@@ -965,6 +1023,15 @@ export declare interface StorageServiceShape {
965
1023
  readonly bytes: Uint8Array;
966
1024
  readonly ref: StorageRef;
967
1025
  }, StorageError>;
1026
+ /** Trusted server-side streamed read with the same tenant guard as get.
1027
+ * Provider acquisition is lazy; callers must authorize private-object access.
1028
+ * Providers without getStream fall back to a buffered provider read. */
1029
+ readonly getStream: (id: string, opts?: {
1030
+ readonly tenantId?: string | null;
1031
+ }) => Effect.Effect<{
1032
+ readonly stream: ByteStream;
1033
+ readonly ref: StorageRef;
1034
+ }, StorageError>;
968
1035
  /** Direct/presigned URL for an object. For PUBLIC objects this is the
969
1036
  * CDN/bucket URL (no app round-trip).
970
1037
  *
@@ -1054,8 +1121,12 @@ export declare interface StorageServiceShape {
1054
1121
  * migration / "adopt this remote image"). The content-type comes from the
1055
1122
  * response unless overridden. NOTE: this fetches an arbitrary URL from the
1056
1123
  * server — pass only trusted URLs (SSRF); front it with an allow-list if the
1057
- * URL is user-supplied. */
1124
+ * URL is user-supplied. Reads are bounded by `limits.maxBytes` (64 MiB when
1125
+ * absent) before the scanning/checksum/put pipeline. This is bounded
1126
+ * buffering, not a zero-copy provider stream. Effect interruption cancels
1127
+ * the HTTP read; an optional caller signal interrupts the entire ingest. */
1058
1128
  readonly ingestUrl: (url: string, opts?: {
1129
+ readonly abortSignal?: AbortSignal;
1059
1130
  readonly contentType?: string;
1060
1131
  readonly tenantId?: string | null;
1061
1132
  readonly ownerId?: string | null;
@@ -1247,7 +1318,8 @@ export declare interface TransformParams {
1247
1318
  }
1248
1319
 
1249
1320
  export declare interface UploadLimits {
1250
- /** Reject uploads larger than this many bytes. */
1321
+ /** Reject uploads larger than this many bytes. URL ingestion enforces this
1322
+ * while reading the response; absent a limit, ingestion caps at 64 MiB. */
1251
1323
  readonly maxBytes?: number;
1252
1324
  /** Allowed content types — exact (`application/pdf`) or wildcard
1253
1325
  * (`image/*`). Omit to allow any. */
@@ -1260,6 +1332,8 @@ export declare interface UploadLimits {
1260
1332
  /** What an upload ticket authorizes: a single through-app upload bound to an
1261
1333
  * owner/tenant/content-type/visibility, until `exp`. */
1262
1334
  export declare interface UploadTicketPayload {
1335
+ /** Exact storage plugin instance authorized to accept the bytes. */
1336
+ readonly instance: string;
1263
1337
  /** ownerId (subject id) the stored object is attributed to. */
1264
1338
  readonly sub: string | null;
1265
1339
  /** tenantId that scopes the key prefix + ref row. */