@powerhousedao/reactor-attachments 6.2.3-dev.50 → 6.2.3-dev.52

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,114 +1,4 @@
1
- import { AttachmentHash, AttachmentRef, JwtHandler } from "@powerhousedao/reactor";
2
- //#region src/errors.d.ts
3
- /**
4
- * Thrown when an attachment ref or hash is not known to the store.
5
- */
6
- declare class AttachmentNotFound extends Error {
7
- constructor(identifier: string);
8
- }
9
- /**
10
- * Thrown when a reservation ID is not found in the reservation store.
11
- */
12
- declare class ReservationNotFound extends Error {
13
- constructor(reservationId: string);
14
- }
15
- /**
16
- * Thrown when an attachment ref string does not match the expected format.
17
- */
18
- declare class InvalidAttachmentRef extends Error {
19
- constructor(ref: string);
20
- }
21
- /** The reserve metadata field that failed validation. */
22
- type AttachmentMetadataField = "mimeType" | "fileName" | "extension";
23
- /**
24
- * Thrown when reserve metadata (MIME type, file name or extension) is not
25
- * acceptable. Route handlers should map this to HTTP 400 Bad Request.
26
- */
27
- declare class InvalidAttachmentMetadata extends Error {
28
- readonly field: AttachmentMetadataField;
29
- constructor(field: AttachmentMetadataField);
30
- }
31
- /**
32
- * Thrown when an upload exceeds the configured maximum byte cap.
33
- * Route handlers should map this to HTTP 413 Payload Too Large.
34
- */
35
- declare class UploadTooLarge extends Error {
36
- readonly maxBytes: number;
37
- constructor(maxBytes: number);
38
- }
39
- /**
40
- * Thrown by reserve() when the claimed hash is already available in the store.
41
- * The caller should use err.ref directly and upload nothing -- this is the
42
- * dedup fast path: duplicate content never leaves the client.
43
- */
44
- declare class AttachmentAlreadyExists extends Error {
45
- readonly hash: AttachmentHash;
46
- readonly ref: AttachmentRef;
47
- constructor(hash: AttachmentHash, ref: AttachmentRef);
48
- }
49
- /**
50
- * Thrown by send() when the server-computed hash of the uploaded bytes
51
- * does not match the hash claimed at reservation time. Nothing is committed;
52
- * the reservation is retained so the client can retry with correct bytes.
53
- */
54
- declare class HashMismatch extends Error {
55
- readonly claimed: AttachmentHash;
56
- readonly actual: AttachmentHash;
57
- constructor(claimed: AttachmentHash, actual: AttachmentHash);
58
- }
59
- /**
60
- * Thrown by send() when the uploaded byte count does not equal the
61
- * sizeBytes declared at reservation time. The handle may reject
62
- * mid-stream as soon as the count exceeds the declaration.
63
- * Nothing is committed; the reservation is retained for retry.
64
- *
65
- * "actual" is the byte count received from the stream before aborting --
66
- * it includes the chunk that crossed the declaration and can exceed bytes
67
- * persisted. On mid-stream aborts the true total is unknown; at least
68
- * "actual" bytes were sent.
69
- */
70
- declare class SizeMismatch extends Error {
71
- readonly declared: number;
72
- readonly actual: number;
73
- constructor(declared: number, actual: number);
74
- }
75
- /**
76
- * Thrown by get() when the hash is reserved by an in-flight upload and
77
- * bytes are not yet available anywhere. Deliberately NOT a subclass of
78
- * AttachmentNotFound -- callers must distinguish "retry later" from "unknown".
79
- * After expiresAtUtc has passed the hash reads as not found.
80
- *
81
- * metadata is populated when the reservation is local and its fields are
82
- * known (mimeType, fileName, sizeBytes). It is undefined when the pending
83
- * state is learned from a remote transport that did not supply the full
84
- * Attachment-Pending header (transport-pending / degraded wire case).
85
- */
86
- type AttachmentTransferStage = "download-target" | "switchboard-get" | "presigned-get" | "presigned-put";
87
- /**
88
- * Thrown when a remote transfer step fails. Identifies the stage only; it
89
- * intentionally carries no URL, signature query, bucket key, or bearer
90
- * material so it is always safe to log or surface.
91
- */
92
- declare class AttachmentTransferError extends Error {
93
- readonly stage: AttachmentTransferStage;
94
- readonly status: number | undefined;
95
- constructor(stage: AttachmentTransferStage, status?: number);
96
- }
97
- declare class AttachmentPending extends Error {
98
- readonly hash: AttachmentHash;
99
- readonly expiresAtUtc: string;
100
- readonly metadata: {
101
- readonly mimeType: string;
102
- readonly fileName: string;
103
- readonly sizeBytes: number;
104
- } | undefined;
105
- constructor(hash: AttachmentHash, expiresAtUtc: string, meta?: {
106
- mimeType: string;
107
- fileName: string;
108
- sizeBytes: number;
109
- });
110
- }
111
- //#endregion
1
+ import { AttachmentHash, AttachmentRef } from "@powerhousedao/reactor";
112
2
  //#region src/types.d.ts
113
3
  type AttachmentBackendKind = "filesystem" | "s3";
114
4
  type AttachmentBackendHealth = {
@@ -320,6 +210,10 @@ type TransportFetchResult = {
320
210
  hash: AttachmentHash;
321
211
  expiresAtUtc: string;
322
212
  retryAfterMs: number;
213
+ /** The source is at capacity and did not look; not a reservation. */
214
+ busy?: true;
215
+ /** With `busy`: every source that was not busy answered not-found. */
216
+ othersNotFound?: true;
323
217
  } | {
324
218
  kind: "not-found";
325
219
  };
@@ -669,218 +563,6 @@ interface IAttachmentBackend {
669
563
  health(): Promise<AttachmentBackendHealth>;
670
564
  }
671
565
  //#endregion
672
- //#region src/targets.d.ts
673
- declare function parseAttachmentUploadTarget(value: unknown): AttachmentUploadTarget;
674
- declare function parseAttachmentDownloadTarget(value: unknown): AttachmentDownloadTarget;
675
- //#endregion
676
- //#region src/attachment-service.d.ts
677
- declare class AttachmentService implements IAttachmentService {
678
- private readonly store;
679
- private readonly reservations;
680
- private readonly uploadFactory;
681
- private readonly backend?;
682
- constructor(store: IAttachmentReader, reservations: IReservationStore, uploadFactory: IAttachmentUploadFactory, backend?: IAttachmentBackend | undefined);
683
- reserve(options: ReserveAttachmentOptions): Promise<IAttachmentUpload>;
684
- stat(ref: AttachmentRef, options?: AttachmentStatOptions): Promise<AttachmentHeader>;
685
- get(ref: AttachmentRef, options?: AbortSignal | AttachmentDownloadOptions): Promise<AttachmentResponse>;
686
- getDownloadTarget(ref: AttachmentRef, options: AttachmentDownloadTargetOptions): Promise<AttachmentDownloadTarget>;
687
- private reserveHashFirst;
688
- }
689
- //#endregion
690
- //#region src/ref.d.ts
691
- type ParsedRef = {
692
- version: number;
693
- hash: AttachmentHash;
694
- };
695
- declare function parseRef(ref: AttachmentRef): ParsedRef;
696
- declare function createRef(hash: AttachmentHash, version?: number): AttachmentRef;
697
- //#endregion
698
- //#region src/switchboard/switchboard-attachment-transport.d.ts
699
- type SwitchboardTransportConfig = {
700
- remoteUrl: string;
701
- jwtHandler?: JwtHandler;
702
- fetchFn?: typeof fetch;
703
- };
704
- declare class SwitchboardAttachmentTransport implements IAttachmentTransport {
705
- private readonly remoteUrl;
706
- private readonly jwtHandler?;
707
- private readonly fetchFn;
708
- constructor(config: SwitchboardTransportConfig);
709
- fetch(hash: AttachmentHash, documentId: string, signal?: AbortSignal): Promise<TransportFetchResult>;
710
- announce(_hash: AttachmentHash): Promise<void>;
711
- push(hash: AttachmentHash, remote: string, data: ReadableStream<Uint8Array>): Promise<void>;
712
- private parsePendingExpiry;
713
- private parseMetadataHeaders;
714
- }
715
- //#endregion
716
- //#region src/switchboard/upload-transport.d.ts
717
- /**
718
- * The seam every attachment upload PUT goes through.
719
- *
720
- * It exists because `fetch` cannot report upload-side progress: there is no
721
- * event between "request issued" and "response received". Naming the transport
722
- * lets an implementation that *can* count bytes (see
723
- * `createXhrUploadTransport`) be substituted without the upload code knowing
724
- * which one it holds — and it is also the right seam for a future multipart
725
- * upload, which needs one request per part.
726
- */
727
- /**
728
- * Headers are forwarded VERBATIM. A presigned target's headers are part of a
729
- * signature: normalizing their casing, or adding so much as one header,
730
- * invalidates it.
731
- */
732
- type AttachmentUploadRequest = {
733
- url: string;
734
- method: "PUT";
735
- headers: Record<string, string>;
736
- body: Blob;
737
- /** Called with cumulative bytes handed to the socket, when observable. */
738
- onProgress?: (loaded: number, total: number) => void;
739
- signal?: AbortSignal;
740
- };
741
- /**
742
- * The only response surface the upload code reads. A real `Response`
743
- * satisfies this structurally, which is what lets the fetch transport be an
744
- * identity pass-through rather than an adapter.
745
- */
746
- type AttachmentUploadResponse = {
747
- readonly status: number;
748
- readonly statusText: string;
749
- readonly ok: boolean;
750
- json(): Promise<unknown>;
751
- };
752
- type AttachmentUploadTransport = (request: AttachmentUploadRequest) => Promise<AttachmentUploadResponse>;
753
- /**
754
- * The default transport. Ignores `onProgress` — fetch has no upload-side
755
- * progress event, and silence is how a transport reports that bytes are
756
- * unobservable.
757
- */
758
- declare function createFetchUploadTransport(fetchFn: typeof fetch): AttachmentUploadTransport;
759
- //#endregion
760
- //#region src/switchboard/xhr-upload-transport.d.ts
761
- type XhrUploadTransportOptions = {
762
- /**
763
- * Used where `XMLHttpRequest` does not exist (Node). Defaults to the global
764
- * fetch, resolved at call time.
765
- */
766
- fetchFn?: typeof fetch;
767
- };
768
- /**
769
- * Upload transport backed by `XMLHttpRequest`, the only browser API that
770
- * reports upload-side byte progress (`xhr.upload.onprogress`). Everything else
771
- * about the request is identical to the fetch path.
772
- *
773
- * Capability detection happens **inside** the returned function, never at
774
- * module load: this module is reachable from the client entry, which is
775
- * executed in Node by the entrypoint tests, and Node has no
776
- * `XMLHttpRequest`. Detecting per call also means a test can install a fake on
777
- * `globalThis` without any module-registry reset.
778
- */
779
- declare function createXhrUploadTransport(options?: XhrUploadTransportOptions): AttachmentUploadTransport;
780
- //#endregion
781
- //#region src/switchboard/remote-reservation-store.d.ts
782
- type SwitchboardClientConfig = {
783
- remoteUrl: string;
784
- jwtHandler?: JwtHandler;
785
- fetchFn?: typeof fetch;
786
- /**
787
- * Transport for upload PUTs only, so a browser host can opt into
788
- * XMLHttpRequest and get real upload progress.
789
- *
790
- * An explicit `fetchFn` always wins over this. The precedence is
791
- * one-directional on purpose: under this rule, adding `uploadTransport` to a
792
- * config that already pins `fetchFn` is a provable no-op, whereas the
793
- * reverse rule would let `{ ...config, uploadTransport }` silently route the
794
- * largest request in the system around a test's mock and onto the network.
795
- */
796
- uploadTransport?: AttachmentUploadTransport;
797
- };
798
- declare class RemoteReservationStore implements IReservationStore {
799
- private readonly remoteUrl;
800
- private readonly jwtHandler?;
801
- private readonly fetchFn;
802
- constructor(config: SwitchboardClientConfig);
803
- create(options: ReserveAttachmentOptions): Promise<Reservation>;
804
- get(reservationId: string): Promise<Reservation>;
805
- delete(reservationId: string): Promise<void>;
806
- deleteExpired(): Promise<number>;
807
- }
808
- //#endregion
809
- //#region src/switchboard/remote-attachment-upload.d.ts
810
- declare class RemoteAttachmentUpload implements IAttachmentUpload {
811
- readonly reservationId: string;
812
- readonly ref: AttachmentRef | null;
813
- readonly expiresAtUtc: string;
814
- readonly uploadTarget?: AttachmentUploadTarget;
815
- private readonly reservation;
816
- private readonly remoteUrl;
817
- private readonly jwtHandler?;
818
- private readonly uploadTransport;
819
- constructor(reservation: Reservation, config: SwitchboardClientConfig);
820
- send(data: ReadableStream<Uint8Array>, options?: AttachmentSendOptions): Promise<AttachmentUploadResult>;
821
- /**
822
- * Direct provider upload: PUT the bytes to the presigned URL with exactly
823
- * the returned headers — never the Switchboard JWT — and treat any 2xx as
824
- * final success with no follow-up control request. The result is
825
- * synthesized from the hash-first reservation, which is the only path that
826
- * can produce a presigned target.
827
- */
828
- private sendPresigned;
829
- }
830
- //#endregion
831
- //#region src/switchboard/remote-attachment-upload-factory.d.ts
832
- declare class RemoteAttachmentUploadFactory implements IAttachmentUploadFactory {
833
- private readonly config;
834
- constructor(config: SwitchboardClientConfig);
835
- createUpload(reservation: Reservation): IAttachmentUpload;
836
- }
837
- //#endregion
838
- //#region src/switchboard/remote-attachment-store.d.ts
839
- declare class RemoteAttachmentStore implements IAttachmentReader {
840
- private readonly remoteUrl;
841
- private readonly jwtHandler?;
842
- private readonly fetchFn;
843
- constructor(config: SwitchboardClientConfig);
844
- /**
845
- * Get attachment metadata. Normally returns a pending AttachmentHeader
846
- * (status: 'pending') when the server responds 202 with a full
847
- * Attachment-Pending header. When only expiresAtUtc is present in the
848
- * header (degraded wire), throws AttachmentPending instead -- the
849
- * AttachmentPending throw is the degraded-wire case.
850
- */
851
- stat(hash: AttachmentHash, documentId?: string): Promise<AttachmentHeader>;
852
- get(hash: AttachmentHash, signal?: AbortSignal, documentId?: string): Promise<AttachmentResponse>;
853
- /**
854
- * Asks Switchboard for an authorized download target. The request carries
855
- * the JWT; the response is runtime-validated before any byte transfer.
856
- * Public so callers can mint direct URLs (previews, share links) without
857
- * transferring bytes; `expiresIn` requests a caller-chosen lifetime.
858
- */
859
- getDownloadTarget(hash: AttachmentHash, options: AttachmentDownloadTargetOptions): Promise<AttachmentDownloadTarget>;
860
- /**
861
- * Executes a presigned GET with exactly the returned headers and no JWT.
862
- * A provider 404 means the object is missing despite available metadata
863
- * (the accepted abandoned-upload trade-off) and surfaces as the same typed
864
- * not-found error callers already handle.
865
- */
866
- private fetchPresigned;
867
- private fetchAttachment;
868
- }
869
- //#endregion
870
- //#region src/switchboard/create-remote-attachment-service.d.ts
871
- declare function createRemoteAttachmentService(config: SwitchboardClientConfig): IAttachmentService;
872
- //#endregion
873
- //#region src/null-attachment-transport.d.ts
874
- /**
875
- * No-op transport for deployments without remote sync.
876
- * fetch() always returns not-found, announce() and push() are no-ops.
877
- */
878
- declare class NullAttachmentTransport implements IAttachmentTransport {
879
- fetch(): Promise<TransportFetchResult>;
880
- announce(): Promise<void>;
881
- push(): Promise<void>;
882
- }
883
- //#endregion
884
- export { AttachmentMetadataField as $, IReservationStore as A, AttachmentStatOptions as B, IAttachmentReader as C, IAttachmentTransportFactory as D, IAttachmentTransport as E, AttachmentDownloadTargetOptions as F, AttachmentUploadTarget as G, AttachmentTargetHeaders as H, AttachmentHeader as I, ReserveAttachmentOptions as J, HashFirstReserveAttachmentOptions as K, AttachmentMetadata as L, AttachmentBackendKind as M, AttachmentDownloadOptions as N, IAttachmentUpload as O, AttachmentDownloadTarget as P, AttachmentAlreadyExists as Q, AttachmentResponse as R, IAttachmentBackend as S, IAttachmentStore as T, AttachmentTransportConfig as U, AttachmentStatus as V, AttachmentUploadResult as W, TransportResponse as X, TransportFetchResult as Y, UploadFirstReserveAttachmentOptions as Z, createRef as _, RemoteAttachmentUpload as a, InvalidAttachmentMetadata as at, parseAttachmentDownloadTarget as b, XhrUploadTransportOptions as c, SizeMismatch as ct, AttachmentUploadResponse as d, AttachmentNotFound as et, AttachmentUploadTransport as f, ParsedRef as g, SwitchboardTransportConfig as h, RemoteAttachmentUploadFactory as i, HashMismatch as it, AttachmentBackendHealth as j, IAttachmentUploadFactory as k, createXhrUploadTransport as l, UploadTooLarge as lt, SwitchboardAttachmentTransport as m, createRemoteAttachmentService as n, AttachmentTransferError as nt, RemoteReservationStore as o, InvalidAttachmentRef as ot, createFetchUploadTransport as p, Reservation as q, RemoteAttachmentStore as r, AttachmentTransferStage as rt, SwitchboardClientConfig as s, ReservationNotFound as st, NullAttachmentTransport as t, AttachmentPending as tt, AttachmentUploadRequest as u, parseRef as v, IAttachmentService as w, parseAttachmentUploadTarget as x, AttachmentService as y, AttachmentSendOptions as z };
566
+ export { UploadFirstReserveAttachmentOptions as A, AttachmentUploadResult as C, ReserveAttachmentOptions as D, Reservation as E, TransportFetchResult as O, AttachmentTransportConfig as S, HashFirstReserveAttachmentOptions as T, AttachmentResponse as _, IAttachmentTransport as a, AttachmentStatus as b, IAttachmentUploadFactory as c, AttachmentBackendKind as d, AttachmentDownloadOptions as f, AttachmentMetadata as g, AttachmentHeader as h, IAttachmentStore as i, TransportResponse as k, IReservationStore as l, AttachmentDownloadTargetOptions as m, IAttachmentReader as n, IAttachmentTransportFactory as o, AttachmentDownloadTarget as p, IAttachmentService as r, IAttachmentUpload as s, IAttachmentBackend as t, AttachmentBackendHealth as u, AttachmentSendOptions as v, AttachmentUploadTarget as w, AttachmentTargetHeaders as x, AttachmentStatOptions as y };
885
567
  export {};
886
- //# sourceMappingURL=null-attachment-transport-jZmzFncu.d.ts.map
568
+ //# sourceMappingURL=interfaces-jHW9JXv1.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces-jHW9JXv1.d.ts","names":[],"sources":["../src/types.ts","../src/interfaces.ts"],"mappings":";;KAEY;KAEA;EACV,MAAM;EACN;;;;;;;KAQU;;;;;;;KAQA;EACV,MAAM;EACN;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA;EACA;EACA;;;;;;;;;;;;;;;;;;;;;KAsBU;EACV;EACA;EACA;EACA;EACA;EACA;;;;;;KAOU;EACV;EACA;EACA;EACA;EACA;;;;;;;;;KAUU;EACV;EACA;EACA;;;;EAIA,YAAY;;;;;;EAMZ;;;;;;;;;;;;KAaU,2BACR,sCACA;;;;KAKQ;EACV,MAAM;EACN,KAAK;EACL,QAAQ;;;;;;;;;;KAWE;EACV,cAAc,gBAAgB;EAC9B,SAAS;;;;;;;KAQC;EACV;EACA,SAAS;;;KAIC;EACV;;;;;;;;KASU;EACV;EACA;EACA,SAAS;;;KAIC,0BAA0B,SAAS;KAEnC;EACV;EACA;EACA;EACA,SAAS;EACT;;KAGU;EACV;EACA;EACA;EACA,SAAS;EACT;;;KAIU,yBACR,oCACA;KAEQ;EACV;EACA;EACA;EACA,SAAS;EACT;;KAGU;EACV;EACA;EACA;EACA,SAAS;EACT;;;KAIU,2BACR,sCACA;;;;KAKQ;EACV,QAAQ;EACR,MAAM,eAAe;;;;;;;;KASX;EACV,MAAM;EACN,UAAU;EACV,MAAM,eAAe;;;;;;;;KASX;EACN;EAAc,UAAU;;EAExB;EACA,MAAM;EACN;EACA;;EAEA;;EAEA;;EAEA;;;;;KAKM;EACV;EACA,YAAY;;;;;;;;;KAUF;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;;;;;;EAMA,eAAe;;;;;;;;UC3PA;;;;;;;;;;;;;;;;;;;EAmBf,QAAQ,SAAS,2BAA2B,QAAQ;;;;;;;;;;;;EAapD,KACE,KAAK,eACL,UAAU,wBACT,QAAQ;;;;;;;;;;;;;;;;;EAkBX,IACE,KAAK,eACL,UAAU,cAAc,4BACvB,QAAQ;;;;;;;;;;;EAYX,kBACE,KAAK,eACL,SAAS,kCACR,QAAQ;;;;;;UAOI;;;;EAIf;;;;;;EAOA,KAAK;;;;;;WAOI;;WAGA,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoCxB,KACE,MAAM,eAAe,aACrB,UAAU,wBACT,QAAQ;;;;;;;;;;;UAYI;;;;;;;;;;;;;;EAcf,KAAK,MAAM,gBAAgB,sBAAsB,QAAQ;;;;;;;;;;;;;;;;;;;EAoBzD,IACE,MAAM,gBACN,SAAS,aACT,sBACC,QAAQ;;;;;;EAOX,mBACE,MAAM,gBACN,SAAS,kCACR,QAAQ;;;;;;;;UASI,yBAAyB;;;;;;;EAOxC,IAAI,MAAM,iBAAiB;;;;;;;;;;;;EAa3B,IACE,MAAM,gBACN,UAAU,oBACV,MAAM,eAAe,cACpB;;;;;;;;;;;;;;;;EAiBH,MAAM,MAAM,iBAAiB;;;;;EAM7B,eAAe;;;;;;;;;UAUA;;;;;;;;;;;;;;;EAef,MACE,MAAM,gBACN,oBACA,SAAS,cACR,QAAQ;;;;;;;EAQX,SAAS,MAAM,iBAAiB;;;;;;;;;;;EAYhC,KACE,MAAM,gBACN,gBACA,MAAM,eAAe,cACpB;;;;;;UAOY;EACf,SAAS,QAAQ,4BAA4B;;;;;;UAO9B;EACf,OAAO,SAAS,2BAA2B,QAAQ;EACnD,IAAI,wBAAwB,QAAQ;EACpC,OAAO,wBAAwB;;;;;;;;EAS/B,cAAc,MAAM,OAAO;;;;;;;UAQZ;EACf,aAAa,aAAa,cAAc;;;;;;;UAQzB;WACN,MAAM;EACf,oBACE,aAAa,cACZ,QAAQ;;;;;;EAMX,sBACE,MAAM,gBACN,sBACC,QAAQ;EACX,OAAO,MAAM,iBAAiB;EAC9B,UAAU,QAAQ"}
@@ -1,136 +1,6 @@
1
- //#region src/errors.ts
2
- /**
3
- * Thrown when an attachment ref or hash is not known to the store.
4
- */
5
- var AttachmentNotFound = class extends Error {
6
- constructor(identifier) {
7
- super(`Attachment not found: ${identifier}`);
8
- this.name = "AttachmentNotFound";
9
- }
10
- };
11
- /**
12
- * Thrown when a reservation ID is not found in the reservation store.
13
- */
14
- var ReservationNotFound = class extends Error {
15
- constructor(reservationId) {
16
- super(`Reservation not found: ${reservationId}`);
17
- this.name = "ReservationNotFound";
18
- }
19
- };
20
- /**
21
- * Thrown when an attachment ref string does not match the expected format.
22
- */
23
- var InvalidAttachmentRef = class extends Error {
24
- constructor(ref) {
25
- super(`Invalid attachment ref: ${ref}`);
26
- this.name = "InvalidAttachmentRef";
27
- }
28
- };
29
- /**
30
- * Thrown when reserve metadata (MIME type, file name or extension) is not
31
- * acceptable. Route handlers should map this to HTTP 400 Bad Request.
32
- */
33
- var InvalidAttachmentMetadata = class extends Error {
34
- field;
35
- constructor(field) {
36
- super(`Invalid attachment ${field}`);
37
- this.name = "InvalidAttachmentMetadata";
38
- this.field = field;
39
- }
40
- };
41
- /**
42
- * Thrown when an upload exceeds the configured maximum byte cap.
43
- * Route handlers should map this to HTTP 413 Payload Too Large.
44
- */
45
- var UploadTooLarge = class extends Error {
46
- maxBytes;
47
- constructor(maxBytes) {
48
- super(`Upload exceeds maximum size of ${maxBytes} bytes`);
49
- this.name = "UploadTooLarge";
50
- this.maxBytes = maxBytes;
51
- }
52
- };
53
- /**
54
- * Thrown by reserve() when the claimed hash is already available in the store.
55
- * The caller should use err.ref directly and upload nothing -- this is the
56
- * dedup fast path: duplicate content never leaves the client.
57
- */
58
- var AttachmentAlreadyExists = class extends Error {
59
- hash;
60
- ref;
61
- constructor(hash, ref) {
62
- super(`Attachment already exists for hash: ${hash}`);
63
- this.name = "AttachmentAlreadyExists";
64
- this.hash = hash;
65
- this.ref = ref;
66
- }
67
- };
68
- /**
69
- * Thrown by send() when the server-computed hash of the uploaded bytes
70
- * does not match the hash claimed at reservation time. Nothing is committed;
71
- * the reservation is retained so the client can retry with correct bytes.
72
- */
73
- var HashMismatch = class extends Error {
74
- claimed;
75
- actual;
76
- constructor(claimed, actual) {
77
- super(`Hash mismatch: claimed ${claimed} but computed ${actual}`);
78
- this.name = "HashMismatch";
79
- this.claimed = claimed;
80
- this.actual = actual;
81
- }
82
- };
83
- /**
84
- * Thrown by send() when the uploaded byte count does not equal the
85
- * sizeBytes declared at reservation time. The handle may reject
86
- * mid-stream as soon as the count exceeds the declaration.
87
- * Nothing is committed; the reservation is retained for retry.
88
- *
89
- * "actual" is the byte count received from the stream before aborting --
90
- * it includes the chunk that crossed the declaration and can exceed bytes
91
- * persisted. On mid-stream aborts the true total is unknown; at least
92
- * "actual" bytes were sent.
93
- */
94
- var SizeMismatch = class extends Error {
95
- declared;
96
- actual;
97
- constructor(declared, actual) {
98
- super(`Size mismatch: declared ${declared} bytes but received ${actual}`);
99
- this.name = "SizeMismatch";
100
- this.declared = declared;
101
- this.actual = actual;
102
- }
103
- };
104
- /**
105
- * Thrown when a remote transfer step fails. Identifies the stage only; it
106
- * intentionally carries no URL, signature query, bucket key, or bearer
107
- * material so it is always safe to log or surface.
108
- */
109
- var AttachmentTransferError = class extends Error {
110
- stage;
111
- status;
112
- constructor(stage, status) {
113
- super(status === void 0 ? `Attachment ${stage} request failed` : `Attachment ${stage} request failed with status ${status}`);
114
- this.name = "AttachmentTransferError";
115
- this.stage = stage;
116
- this.status = status;
117
- }
118
- };
119
- var AttachmentPending = class extends Error {
120
- hash;
121
- expiresAtUtc;
122
- metadata;
123
- constructor(hash, expiresAtUtc, meta) {
124
- super(`Attachment pending upload for hash: ${hash}, expires: ${expiresAtUtc}`);
125
- this.name = "AttachmentPending";
126
- this.hash = hash;
127
- this.expiresAtUtc = expiresAtUtc;
128
- this.metadata = meta;
129
- }
130
- };
131
- //#endregion
1
+ import { a as parseRef, c as AttachmentPending, i as createRef, l as AttachmentTransferError, m as SizeMismatch, n as buildAuthHeaders, o as AttachmentAlreadyExists, p as ReservationNotFound, s as AttachmentNotFound, u as HashMismatch } from "./switchboard-attachment-transport-BH3ZAmxE.js";
132
2
  //#region src/targets.ts
133
- function isRecord$4(value) {
3
+ function isRecord$3(value) {
134
4
  return typeof value === "object" && value !== null && !Array.isArray(value);
135
5
  }
136
6
  function parseUrl(value) {
@@ -146,7 +16,7 @@ function parseUrl(value) {
146
16
  return value;
147
17
  }
148
18
  function parseHeaders(value) {
149
- if (!isRecord$4(value)) throw new Error("Attachment target headers must be an object");
19
+ if (!isRecord$3(value)) throw new Error("Attachment target headers must be an object");
150
20
  const headers = Object.create(null);
151
21
  const headerName = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;
152
22
  for (const [name, headerValue] of Object.entries(value)) {
@@ -178,7 +48,7 @@ function parseExpiry(value, required) {
178
48
  return value;
179
49
  }
180
50
  function parseAttachmentUploadTarget(value) {
181
- if (!isRecord$4(value)) throw new Error("Attachment upload target must be an object");
51
+ if (!isRecord$3(value)) throw new Error("Attachment upload target must be an object");
182
52
  const url = parseUrl(value.url);
183
53
  const headers = parseHeaders(value.headers);
184
54
  switch (value.kind) {
@@ -204,7 +74,7 @@ function parseAttachmentUploadTarget(value) {
204
74
  }
205
75
  }
206
76
  function parseAttachmentDownloadTarget(value) {
207
- if (!isRecord$4(value)) throw new Error("Attachment download target must be an object");
77
+ if (!isRecord$3(value)) throw new Error("Attachment download target must be an object");
208
78
  const url = parseUrl(value.url);
209
79
  const headers = parseHeaders(value.headers);
210
80
  switch (value.kind) {
@@ -230,21 +100,6 @@ function parseAttachmentDownloadTarget(value) {
230
100
  }
231
101
  }
232
102
  //#endregion
233
- //#region src/ref.ts
234
- const REF_PATTERN = /^attachment:\/\/v(\d+):(.+)$/;
235
- const DEFAULT_VERSION = 1;
236
- function parseRef(ref) {
237
- const match = REF_PATTERN.exec(ref);
238
- if (!match) throw new InvalidAttachmentRef(ref);
239
- return {
240
- version: Number(match[1]),
241
- hash: match[2]
242
- };
243
- }
244
- function createRef(hash, version = DEFAULT_VERSION) {
245
- return `attachment://v${version}:${hash}`;
246
- }
247
- //#endregion
248
103
  //#region src/attachment-service.ts
249
104
  const CLIENT_HASH_PATTERN = /^[a-f0-9]{64}$/;
250
105
  var AttachmentService = class {
@@ -308,139 +163,6 @@ var AttachmentService = class {
308
163
  }
309
164
  };
310
165
  //#endregion
311
- //#region src/switchboard/build-auth-headers.ts
312
- async function buildAuthHeaders(url, jwtHandler) {
313
- const headers = {};
314
- if (jwtHandler) {
315
- const token = await jwtHandler(url);
316
- if (token) headers["Authorization"] = `Bearer ${token}`;
317
- }
318
- return headers;
319
- }
320
- //#endregion
321
- //#region src/switchboard/switchboard-attachment-transport.ts
322
- var SwitchboardAttachmentTransport = class {
323
- remoteUrl;
324
- jwtHandler;
325
- fetchFn;
326
- constructor(config) {
327
- this.remoteUrl = config.remoteUrl;
328
- this.jwtHandler = config.jwtHandler;
329
- this.fetchFn = (config.fetchFn ?? globalThis.fetch).bind(globalThis);
330
- }
331
- async fetch(hash, documentId, signal) {
332
- const url = `${this.remoteUrl}/attachments/${hash}?documentId=${encodeURIComponent(documentId)}`;
333
- const headers = await buildAuthHeaders(url, this.jwtHandler);
334
- const response = await this.fetchFn(url, {
335
- signal,
336
- headers
337
- });
338
- if (response.status === 202) {
339
- const expiresAtUtc = this.parsePendingExpiry(response);
340
- if (!expiresAtUtc) throw new Error("Attachment fetch returned 202 with missing or malformed Attachment-Pending header");
341
- return {
342
- kind: "pending",
343
- hash,
344
- expiresAtUtc,
345
- retryAfterMs: parseRetryAfterMs(response)
346
- };
347
- }
348
- if (response.status === 404) return { kind: "not-found" };
349
- if (!response.ok) throw new Error(`Attachment fetch failed: ${response.status} ${response.statusText}`);
350
- const metadata = this.parseMetadataHeaders(response);
351
- const body = response.body;
352
- if (!body) throw new Error("Response body is null");
353
- return {
354
- kind: "data",
355
- response: {
356
- hash,
357
- metadata,
358
- body
359
- }
360
- };
361
- }
362
- async announce(_hash) {}
363
- async push(hash, remote, data) {
364
- const url = `${remote}/attachments/${hash}`;
365
- const headers = await buildAuthHeaders(url, this.jwtHandler);
366
- const response = await this.fetchFn(url, {
367
- method: "PUT",
368
- body: data,
369
- headers,
370
- duplex: "half"
371
- });
372
- if (!response.ok) throw new Error(`Attachment push failed: ${response.status} ${response.statusText}`);
373
- }
374
- parsePendingExpiry(response) {
375
- const header = response.headers.get("Attachment-Pending");
376
- if (!header) return null;
377
- try {
378
- const parsed = JSON.parse(header);
379
- if (!isRecord$3(parsed)) return null;
380
- if (typeof parsed.expiresAtUtc !== "string") return null;
381
- return parsed.expiresAtUtc;
382
- } catch {
383
- return null;
384
- }
385
- }
386
- parseMetadataHeaders(response) {
387
- let fallbackCache;
388
- const fallback = () => {
389
- if (fallbackCache === void 0) fallbackCache = contentTypeFallback$1(response);
390
- return fallbackCache;
391
- };
392
- const metaHeader = response.headers.get("Attachment-Metadata");
393
- if (metaHeader) try {
394
- const parsed = JSON.parse(metaHeader);
395
- if (isRecord$3(parsed)) {
396
- if (parsed.extension === void 0) parsed.extension = null;
397
- if (parsed.createdAtUtc === void 0) parsed.createdAtUtc = fallback().createdAtUtc;
398
- if (parsed.lastAccessedAtUtc === void 0) parsed.lastAccessedAtUtc = fallback().lastAccessedAtUtc;
399
- }
400
- if (isAttachmentMetadata$1(parsed)) return parsed;
401
- } catch {}
402
- return fallback();
403
- }
404
- };
405
- const DEFAULT_RETRY_AFTER_MS = 5e3;
406
- function parseRetryAfterMs(response) {
407
- const retryAfter = response.headers.get("Retry-After");
408
- if (!retryAfter) return DEFAULT_RETRY_AFTER_MS;
409
- const seconds = Number(retryAfter);
410
- if (!Number.isFinite(seconds) || seconds < 0) return DEFAULT_RETRY_AFTER_MS;
411
- return Math.round(seconds * 1e3);
412
- }
413
- function isRecord$3(value) {
414
- return typeof value === "object" && value !== null && !Array.isArray(value);
415
- }
416
- function isAttachmentMetadata$1(value) {
417
- if (!isRecord$3(value)) return false;
418
- if (typeof value.mimeType !== "string") return false;
419
- if (typeof value.fileName !== "string") return false;
420
- if (typeof value.sizeBytes !== "number" || !Number.isFinite(value.sizeBytes) || value.sizeBytes < 0) return false;
421
- if (value.extension !== null && typeof value.extension !== "string") return false;
422
- if (typeof value.createdAtUtc !== "string") return false;
423
- if (value.lastAccessedAtUtc !== void 0 && typeof value.lastAccessedAtUtc !== "string") return false;
424
- return true;
425
- }
426
- function contentTypeFallback$1(response) {
427
- const contentLength = response.headers.get("Content-Length");
428
- if (contentLength === null) throw new Error("Switchboard response missing both Attachment-Metadata and Content-Length headers");
429
- const sizeBytes = Number(contentLength);
430
- if (!Number.isInteger(sizeBytes) || sizeBytes < 0) throw new Error(`Switchboard response has invalid Content-Length header: ${JSON.stringify(contentLength)}`);
431
- const lastModified = response.headers.get("Last-Modified");
432
- const dateHeader = response.headers.get("Date");
433
- const createdAtUtc = lastModified ? new Date(lastModified).toISOString() : dateHeader ? new Date(dateHeader).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
434
- return {
435
- mimeType: response.headers.get("Content-Type") ?? "application/octet-stream",
436
- fileName: "unknown",
437
- sizeBytes,
438
- extension: null,
439
- createdAtUtc,
440
- lastAccessedAtUtc: createdAtUtc
441
- };
442
- }
443
- //#endregion
444
166
  //#region src/switchboard/upload-transport.ts
445
167
  /**
446
168
  * The default transport. Ignores `onProgress` — fetch has no upload-side
@@ -1034,6 +756,6 @@ var NullAttachmentTransport = class {
1034
756
  }
1035
757
  };
1036
758
  //#endregion
1037
- export { SizeMismatch as C, ReservationNotFound as S, AttachmentPending as _, RemoteAttachmentUpload as a, InvalidAttachmentMetadata as b, createFetchUploadTransport as c, createRef as d, parseRef as f, AttachmentNotFound as g, AttachmentAlreadyExists as h, RemoteAttachmentUploadFactory as i, SwitchboardAttachmentTransport as l, parseAttachmentUploadTarget as m, createRemoteAttachmentService as n, RemoteReservationStore as o, parseAttachmentDownloadTarget as p, RemoteAttachmentStore as r, createXhrUploadTransport as s, NullAttachmentTransport as t, AttachmentService as u, AttachmentTransferError as v, UploadTooLarge as w, InvalidAttachmentRef as x, HashMismatch as y };
759
+ export { RemoteAttachmentUpload as a, createFetchUploadTransport as c, parseAttachmentUploadTarget as d, RemoteAttachmentUploadFactory as i, AttachmentService as l, createRemoteAttachmentService as n, RemoteReservationStore as o, RemoteAttachmentStore as r, createXhrUploadTransport as s, NullAttachmentTransport as t, parseAttachmentDownloadTarget as u };
1038
760
 
1039
- //# sourceMappingURL=null-attachment-transport-CUDNBfd5.js.map
761
+ //# sourceMappingURL=null-attachment-transport-BGG9cRC-.js.map