@powerhousedao/reactor-attachments 6.2.2-dev.5 → 6.2.2-dev.50

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.
@@ -74,6 +74,17 @@ declare class SizeMismatch extends Error {
74
74
  * state is learned from a remote transport that did not supply the full
75
75
  * Attachment-Pending header (transport-pending / degraded wire case).
76
76
  */
77
+ type AttachmentTransferStage = "download-target" | "switchboard-get" | "presigned-get" | "presigned-put";
78
+ /**
79
+ * Thrown when a remote transfer step fails. Identifies the stage only; it
80
+ * intentionally carries no URL, signature query, bucket key, or bearer
81
+ * material so it is always safe to log or surface.
82
+ */
83
+ declare class AttachmentTransferError extends Error {
84
+ readonly stage: AttachmentTransferStage;
85
+ readonly status: number | undefined;
86
+ constructor(stage: AttachmentTransferStage, status?: number);
87
+ }
77
88
  declare class AttachmentPending extends Error {
78
89
  readonly hash: AttachmentHash;
79
90
  readonly expiresAtUtc: string;
@@ -90,6 +101,11 @@ declare class AttachmentPending extends Error {
90
101
  }
91
102
  //#endregion
92
103
  //#region src/types.d.ts
104
+ type AttachmentBackendKind = "filesystem" | "s3";
105
+ type AttachmentBackendHealth = {
106
+ kind: AttachmentBackendKind;
107
+ ready: boolean;
108
+ };
93
109
  /**
94
110
  * Status of attachment data in the local store.
95
111
  * 'pending' is a virtual status synthesized at query time from live,
@@ -193,6 +209,60 @@ type AttachmentUploadResult = {
193
209
  ref: AttachmentRef;
194
210
  header: AttachmentHeader;
195
211
  };
212
+ /**
213
+ * Options form of IAttachmentService.get. `documentId` anchors the download
214
+ * authorization for remote readers; it never becomes part of the ref or any
215
+ * global configuration.
216
+ */
217
+ type AttachmentDownloadOptions = {
218
+ documentId?: string;
219
+ signal?: AbortSignal;
220
+ };
221
+ /**
222
+ * Options for minting a raw download target without transferring bytes
223
+ * (previews, share links). `documentId` is required: targets are always
224
+ * document-authorized. `expiresIn` (seconds) requests a caller-chosen
225
+ * target lifetime; the server clamps it to its configured maximum.
226
+ */
227
+ type AttachmentDownloadTargetOptions = {
228
+ documentId: string;
229
+ expiresIn?: number;
230
+ signal?: AbortSignal;
231
+ };
232
+ /** Exact HTTP headers that must be sent with an attachment transfer target. */
233
+ type AttachmentTargetHeaders = Readonly<Record<string, string>>;
234
+ type SwitchboardAttachmentUploadTarget = {
235
+ kind: "switchboard";
236
+ method: "PUT";
237
+ url: string;
238
+ headers: AttachmentTargetHeaders;
239
+ expiresAtUtc?: string;
240
+ };
241
+ type PresignedPutAttachmentUploadTarget = {
242
+ kind: "presigned-put";
243
+ method: "PUT";
244
+ url: string;
245
+ headers: AttachmentTargetHeaders;
246
+ expiresAtUtc: string;
247
+ };
248
+ /** Provider-neutral target returned to a client that needs to upload bytes. */
249
+ type AttachmentUploadTarget = SwitchboardAttachmentUploadTarget | PresignedPutAttachmentUploadTarget;
250
+ type SwitchboardAttachmentDownloadTarget = {
251
+ kind: "switchboard";
252
+ method: "GET";
253
+ url: string;
254
+ headers: AttachmentTargetHeaders;
255
+ expiresAtUtc?: string;
256
+ };
257
+ type PresignedGetAttachmentDownloadTarget = {
258
+ kind: "presigned-get";
259
+ method: "GET";
260
+ url: string;
261
+ headers: AttachmentTargetHeaders;
262
+ expiresAtUtc: string;
263
+ };
264
+ /** Provider-neutral target returned to a client that needs to download bytes. */
265
+ type AttachmentDownloadTarget = SwitchboardAttachmentDownloadTarget | PresignedGetAttachmentDownloadTarget;
196
266
  /**
197
267
  * Response when retrieving attachment data from the local store.
198
268
  */
@@ -251,6 +321,12 @@ type Reservation = {
251
321
  expiresAtUtc: string;
252
322
  clientHash: string | null;
253
323
  sizeBytes: number | null;
324
+ /**
325
+ * Additive wire contract. Older Switchboards omit this field; current
326
+ * upload handles therefore continue to support their established send()
327
+ * behavior while target-aware clients are introduced incrementally.
328
+ */
329
+ uploadTarget?: AttachmentUploadTarget;
254
330
  };
255
331
  //#endregion
256
332
  //#region src/interfaces.d.ts
@@ -299,8 +375,23 @@ interface IAttachmentService {
299
375
  * expiresAtUtc. A wait inside get() would hold request handlers
300
376
  * open across multi-second windows and hide retry policy where
301
377
  * callers cannot tune it.
378
+ *
379
+ * The options form carries the document id that authorizes a remote
380
+ * download; local/direct readers ignore it. The bare-signal form remains
381
+ * supported for source compatibility.
302
382
  */
303
- get(ref: AttachmentRef, signal?: AbortSignal): Promise<AttachmentResponse>;
383
+ get(ref: AttachmentRef, options?: AbortSignal | AttachmentDownloadOptions): Promise<AttachmentResponse>;
384
+ /**
385
+ * Obtain the raw, document-authorized download target for a ref without
386
+ * transferring any bytes. Used to mint direct URLs (previews, share
387
+ * links): a presigned target URL is a self-contained public capability
388
+ * until its expiry. `expiresIn` (seconds) requests a caller-chosen
389
+ * lifetime; the server clamps it to its configured maximum.
390
+ *
391
+ * @throws when the underlying store cannot produce download targets
392
+ * (local/direct stores serve bytes, not URLs).
393
+ */
394
+ getDownloadTarget(ref: AttachmentRef, options: AttachmentDownloadTargetOptions): Promise<AttachmentDownloadTarget>;
304
395
  }
305
396
  /**
306
397
  * Upload handle returned by reserve(). Encapsulates all transport-specific
@@ -323,6 +414,8 @@ interface IAttachmentUpload {
323
414
  * Clients use this to bound retry windows and populate the pending-upload queue.
324
415
  */
325
416
  readonly expiresAtUtc: string;
417
+ /** Direct transfer target returned by target-capable server backends. */
418
+ readonly uploadTarget?: AttachmentUploadTarget;
326
419
  /**
327
420
  * Stream attachment data through this handle.
328
421
  *
@@ -383,8 +476,17 @@ interface IAttachmentReader {
383
476
  * @throws AttachmentPending if the hash is reserved by an in-flight
384
477
  * upload; bytes are not yet available. There is no store-level
385
478
  * wait -- polling is the caller's responsibility.
479
+ *
480
+ * `documentId` is the authorization anchor for remote readers that must
481
+ * negotiate a download target; local/direct readers ignore it.
386
482
  */
387
- get(hash: AttachmentHash, signal?: AbortSignal): Promise<AttachmentResponse>;
483
+ get(hash: AttachmentHash, signal?: AbortSignal, documentId?: string): Promise<AttachmentResponse>;
484
+ /**
485
+ * Optional capability: readers that negotiate download targets with a
486
+ * remote control plane can expose the raw target so callers may mint
487
+ * direct URLs (previews, share links). Local/direct readers omit it.
488
+ */
489
+ getDownloadTarget?(hash: AttachmentHash, options: AttachmentDownloadTargetOptions): Promise<AttachmentDownloadTarget>;
388
490
  }
389
491
  /**
390
492
  * Reactor-facing interface for managing local attachment data.
@@ -506,16 +608,39 @@ interface IReservationStore {
506
608
  interface IAttachmentUploadFactory {
507
609
  createUpload(reservation: Reservation): IAttachmentUpload;
508
610
  }
611
+ /**
612
+ * Server-side storage capability boundary. Authorization happens before these
613
+ * methods are called; actor, policy, and document concepts intentionally do
614
+ * not cross into the byte-storage backend.
615
+ */
616
+ interface IAttachmentBackend {
617
+ readonly kind: AttachmentBackendKind;
618
+ prepareUploadTarget(reservation: Reservation): Promise<AttachmentUploadTarget>;
619
+ /**
620
+ * `ttlSeconds` overrides the backend's configured download TTL for this
621
+ * one target (used for caller-chosen share-link lifetimes). Callers are
622
+ * responsible for bounding it; backends may still reject absurd values.
623
+ */
624
+ prepareDownloadTarget(hash: AttachmentHash, ttlSeconds?: number): Promise<AttachmentDownloadTarget>;
625
+ exists(hash: AttachmentHash): Promise<boolean>;
626
+ health(): Promise<AttachmentBackendHealth>;
627
+ }
628
+ //#endregion
629
+ //#region src/targets.d.ts
630
+ declare function parseAttachmentUploadTarget(value: unknown): AttachmentUploadTarget;
631
+ declare function parseAttachmentDownloadTarget(value: unknown): AttachmentDownloadTarget;
509
632
  //#endregion
510
633
  //#region src/attachment-service.d.ts
511
634
  declare class AttachmentService implements IAttachmentService {
512
635
  private readonly store;
513
636
  private readonly reservations;
514
637
  private readonly uploadFactory;
515
- constructor(store: IAttachmentReader, reservations: IReservationStore, uploadFactory: IAttachmentUploadFactory);
638
+ private readonly backend?;
639
+ constructor(store: IAttachmentReader, reservations: IReservationStore, uploadFactory: IAttachmentUploadFactory, backend?: IAttachmentBackend | undefined);
516
640
  reserve(options: ReserveAttachmentOptions): Promise<IAttachmentUpload>;
517
641
  stat(ref: AttachmentRef): Promise<AttachmentHeader>;
518
- get(ref: AttachmentRef, signal?: AbortSignal): Promise<AttachmentResponse>;
642
+ get(ref: AttachmentRef, options?: AbortSignal | AttachmentDownloadOptions): Promise<AttachmentResponse>;
643
+ getDownloadTarget(ref: AttachmentRef, options: AttachmentDownloadTargetOptions): Promise<AttachmentDownloadTarget>;
519
644
  private reserveHashFirst;
520
645
  }
521
646
  //#endregion
@@ -567,11 +692,21 @@ declare class RemoteAttachmentUpload implements IAttachmentUpload {
567
692
  readonly reservationId: string;
568
693
  readonly ref: AttachmentRef | null;
569
694
  readonly expiresAtUtc: string;
695
+ readonly uploadTarget?: AttachmentUploadTarget;
696
+ private readonly reservation;
570
697
  private readonly remoteUrl;
571
698
  private readonly jwtHandler?;
572
699
  private readonly fetchFn;
573
700
  constructor(reservation: Reservation, config: SwitchboardClientConfig);
574
701
  send(data: ReadableStream<Uint8Array>): Promise<AttachmentUploadResult>;
702
+ /**
703
+ * Direct provider upload: PUT the bytes to the presigned URL with exactly
704
+ * the returned headers — never the Switchboard JWT — and treat any 2xx as
705
+ * final success with no follow-up control request. The result is
706
+ * synthesized from the hash-first reservation, which is the only path that
707
+ * can produce a presigned target.
708
+ */
709
+ private sendPresigned;
575
710
  }
576
711
  //#endregion
577
712
  //#region src/switchboard/remote-attachment-upload-factory.d.ts
@@ -595,7 +730,21 @@ declare class RemoteAttachmentStore implements IAttachmentReader {
595
730
  * AttachmentPending throw is the degraded-wire case.
596
731
  */
597
732
  stat(hash: AttachmentHash): Promise<AttachmentHeader>;
598
- get(hash: AttachmentHash, signal?: AbortSignal): Promise<AttachmentResponse>;
733
+ get(hash: AttachmentHash, signal?: AbortSignal, documentId?: string): Promise<AttachmentResponse>;
734
+ /**
735
+ * Asks Switchboard for an authorized download target. The request carries
736
+ * the JWT; the response is runtime-validated before any byte transfer.
737
+ * Public so callers can mint direct URLs (previews, share links) without
738
+ * transferring bytes; `expiresIn` requests a caller-chosen lifetime.
739
+ */
740
+ getDownloadTarget(hash: AttachmentHash, options: AttachmentDownloadTargetOptions): Promise<AttachmentDownloadTarget>;
741
+ /**
742
+ * Executes a presigned GET with exactly the returned headers and no JWT.
743
+ * A provider 404 means the object is missing despite available metadata
744
+ * (the accepted abandoned-upload trade-off) and surfaces as the same typed
745
+ * not-found error callers already handle.
746
+ */
747
+ private fetchPresigned;
599
748
  private fetchAttachment;
600
749
  }
601
750
  //#endregion
@@ -613,5 +762,5 @@ declare class NullAttachmentTransport implements IAttachmentTransport {
613
762
  push(): Promise<void>;
614
763
  }
615
764
  //#endregion
616
- export { ReserveAttachmentOptions as A, SizeMismatch as B, AttachmentMetadata as C, AttachmentUploadResult as D, AttachmentTransportConfig as E, AttachmentNotFound as F, AttachmentPending as I, HashMismatch as L, TransportResponse as M, UploadFirstReserveAttachmentOptions as N, HashFirstReserveAttachmentOptions as O, AttachmentAlreadyExists as P, InvalidAttachmentRef as R, AttachmentHeader as S, AttachmentStatus as T, UploadTooLarge as V, IAttachmentTransport as _, RemoteAttachmentUpload as a, IAttachmentUploadFactory as b, SwitchboardAttachmentTransport as c, createRef as d, parseRef as f, IAttachmentStore as g, IAttachmentService as h, RemoteAttachmentUploadFactory as i, TransportFetchResult as j, Reservation as k, SwitchboardTransportConfig as l, IAttachmentReader as m, createRemoteAttachmentService as n, RemoteReservationStore as o, AttachmentService as p, RemoteAttachmentStore as r, SwitchboardClientConfig as s, NullAttachmentTransport as t, ParsedRef as u, IAttachmentTransportFactory as v, AttachmentResponse as w, IReservationStore as x, IAttachmentUpload as y, ReservationNotFound as z };
617
- //# sourceMappingURL=null-attachment-transport-BBhQIk5A.d.ts.map
765
+ export { UploadTooLarge as $, AttachmentHeader as A, ReserveAttachmentOptions as B, IAttachmentUploadFactory as C, AttachmentDownloadOptions as D, AttachmentBackendKind as E, AttachmentTransportConfig as F, AttachmentNotFound as G, TransportResponse as H, AttachmentUploadResult as I, AttachmentTransferStage as J, AttachmentPending as K, AttachmentUploadTarget as L, AttachmentResponse as M, AttachmentStatus as N, AttachmentDownloadTarget as O, AttachmentTargetHeaders as P, SizeMismatch as Q, HashFirstReserveAttachmentOptions as R, IAttachmentUpload as S, AttachmentBackendHealth as T, UploadFirstReserveAttachmentOptions as U, TransportFetchResult as V, AttachmentAlreadyExists as W, InvalidAttachmentRef as X, HashMismatch as Y, ReservationNotFound as Z, IAttachmentReader as _, RemoteAttachmentUpload as a, IAttachmentTransport as b, SwitchboardAttachmentTransport as c, createRef as d, parseRef as f, IAttachmentBackend as g, parseAttachmentUploadTarget as h, RemoteAttachmentUploadFactory as i, AttachmentMetadata as j, AttachmentDownloadTargetOptions as k, SwitchboardTransportConfig as l, parseAttachmentDownloadTarget as m, createRemoteAttachmentService as n, RemoteReservationStore as o, AttachmentService as p, AttachmentTransferError as q, RemoteAttachmentStore as r, SwitchboardClientConfig as s, NullAttachmentTransport as t, ParsedRef as u, IAttachmentService as v, IReservationStore as w, IAttachmentTransportFactory as x, IAttachmentStore as y, Reservation as z };
766
+ //# sourceMappingURL=null-attachment-transport-D1Bj3hLU.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-attachment-transport-D1Bj3hLU.d.ts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/interfaces.ts","../src/targets.ts","../src/attachment-service.ts","../src/ref.ts","../src/switchboard/switchboard-attachment-transport.ts","../src/switchboard/remote-reservation-store.ts","../src/switchboard/remote-attachment-upload.ts","../src/switchboard/remote-attachment-upload-factory.ts","../src/switchboard/remote-attachment-store.ts","../src/switchboard/create-remote-attachment-service.ts","../src/null-attachment-transport.ts"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/reactor-attachments",
3
- "version": "6.2.2-dev.5",
3
+ "version": "6.2.2-dev.50",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "repository": {
@@ -28,13 +28,20 @@
28
28
  },
29
29
  "sideEffects": false,
30
30
  "dependencies": {
31
+ "@aws-sdk/client-s3": "3.1087.0",
32
+ "@aws-sdk/s3-request-presigner": "3.1087.0",
33
+ "@powerhousedao/document-engineering": "1.40.5",
34
+ "change-case": "5.4.4",
35
+ "graphql": "^16",
31
36
  "kysely": "0.28.16",
32
- "@powerhousedao/reactor": "6.2.2-dev.5"
37
+ "@powerhousedao/reactor": "6.2.2-dev.50",
38
+ "@powerhousedao/shared": "6.2.2-dev.50"
33
39
  },
34
40
  "devDependencies": {
35
41
  "@electric-sql/pglite": "0.3.15",
36
42
  "kysely-pglite-dialect": "1.2.0",
37
43
  "tsdown": "0.21.1",
44
+ "tsx": "4.21.0",
38
45
  "vitest": "4.1.1"
39
46
  },
40
47
  "description": "Content-addressed attachment service for the Powerhouse reactor — decouples large binaries from the action pipeline using opaque refs.",
@@ -55,6 +62,7 @@
55
62
  "tsc": "tsc",
56
63
  "lint": "eslint",
57
64
  "build": "tsdown",
58
- "test": "vitest --run"
65
+ "test": "vitest --run",
66
+ "verify:s3-compatibility": "tsx scripts/verify-s3-compatibility.ts"
59
67
  }
60
68
  }
@@ -1 +0,0 @@
1
- {"version":3,"file":"null-attachment-transport-BBhQIk5A.d.ts","names":[],"sources":["../src/errors.ts","../src/types.ts","../src/interfaces.ts","../src/attachment-service.ts","../src/ref.ts","../src/switchboard/switchboard-attachment-transport.ts","../src/switchboard/remote-reservation-store.ts","../src/switchboard/remote-attachment-upload.ts","../src/switchboard/remote-attachment-upload-factory.ts","../src/switchboard/remote-attachment-store.ts","../src/switchboard/create-remote-attachment-service.ts","../src/null-attachment-transport.ts"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"null-attachment-transport-Drx03s02.js","names":["isRecord","contentTypeFallback","isAttachmentMetadata","isRecord","isRecord"],"sources":["../src/errors.ts","../src/ref.ts","../src/attachment-service.ts","../src/switchboard/build-auth-headers.ts","../src/switchboard/switchboard-attachment-transport.ts","../src/switchboard/remote-reservation-store.ts","../src/switchboard/remote-attachment-upload.ts","../src/switchboard/remote-attachment-upload-factory.ts","../src/switchboard/remote-attachment-store.ts","../src/switchboard/create-remote-attachment-service.ts","../src/null-attachment-transport.ts"],"sourcesContent":["import type { AttachmentHash, AttachmentRef } from \"@powerhousedao/reactor\";\n\n/**\n * Thrown when an attachment ref or hash is not known to the store.\n */\nexport class AttachmentNotFound extends Error {\n constructor(identifier: string) {\n super(`Attachment not found: ${identifier}`);\n this.name = \"AttachmentNotFound\";\n }\n}\n\n/**\n * Thrown when a reservation ID is not found in the reservation store.\n */\nexport class ReservationNotFound extends Error {\n constructor(reservationId: string) {\n super(`Reservation not found: ${reservationId}`);\n this.name = \"ReservationNotFound\";\n }\n}\n\n/**\n * Thrown when an attachment ref string does not match the expected format.\n */\nexport class InvalidAttachmentRef extends Error {\n constructor(ref: string) {\n super(`Invalid attachment ref: ${ref}`);\n this.name = \"InvalidAttachmentRef\";\n }\n}\n\n/**\n * Thrown when an upload exceeds the configured maximum byte cap.\n * Route handlers should map this to HTTP 413 Payload Too Large.\n */\nexport class UploadTooLarge extends Error {\n readonly maxBytes: number;\n constructor(maxBytes: number) {\n super(`Upload exceeds maximum size of ${maxBytes} bytes`);\n this.name = \"UploadTooLarge\";\n this.maxBytes = maxBytes;\n }\n}\n\n/**\n * Thrown by reserve() when the claimed hash is already available in the store.\n * The caller should use err.ref directly and upload nothing -- this is the\n * dedup fast path: duplicate content never leaves the client.\n */\nexport class AttachmentAlreadyExists extends Error {\n readonly hash: AttachmentHash;\n readonly ref: AttachmentRef;\n constructor(hash: AttachmentHash, ref: AttachmentRef) {\n super(`Attachment already exists for hash: ${hash}`);\n this.name = \"AttachmentAlreadyExists\";\n this.hash = hash;\n this.ref = ref;\n }\n}\n\n/**\n * Thrown by send() when the server-computed hash of the uploaded bytes\n * does not match the hash claimed at reservation time. Nothing is committed;\n * the reservation is retained so the client can retry with correct bytes.\n */\nexport class HashMismatch extends Error {\n readonly claimed: AttachmentHash;\n readonly actual: AttachmentHash;\n constructor(claimed: AttachmentHash, actual: AttachmentHash) {\n super(`Hash mismatch: claimed ${claimed} but computed ${actual}`);\n this.name = \"HashMismatch\";\n this.claimed = claimed;\n this.actual = actual;\n }\n}\n\n/**\n * Thrown by send() when the uploaded byte count does not equal the\n * sizeBytes declared at reservation time. The handle may reject\n * mid-stream as soon as the count exceeds the declaration.\n * Nothing is committed; the reservation is retained for retry.\n *\n * \"actual\" is the byte count received from the stream before aborting --\n * it includes the chunk that crossed the declaration and can exceed bytes\n * persisted. On mid-stream aborts the true total is unknown; at least\n * \"actual\" bytes were sent.\n */\nexport class SizeMismatch extends Error {\n readonly declared: number;\n readonly actual: number;\n constructor(declared: number, actual: number) {\n super(`Size mismatch: declared ${declared} bytes but received ${actual}`);\n this.name = \"SizeMismatch\";\n this.declared = declared;\n this.actual = actual;\n }\n}\n\n/**\n * Thrown by get() when the hash is reserved by an in-flight upload and\n * bytes are not yet available anywhere. Deliberately NOT a subclass of\n * AttachmentNotFound -- callers must distinguish \"retry later\" from \"unknown\".\n * After expiresAtUtc has passed the hash reads as not found.\n *\n * metadata is populated when the reservation is local and its fields are\n * known (mimeType, fileName, sizeBytes). It is undefined when the pending\n * state is learned from a remote transport that did not supply the full\n * Attachment-Pending header (transport-pending / degraded wire case).\n */\nexport class AttachmentPending extends Error {\n readonly hash: AttachmentHash;\n readonly expiresAtUtc: string;\n readonly metadata:\n | {\n readonly mimeType: string;\n readonly fileName: string;\n readonly sizeBytes: number;\n }\n | undefined;\n\n constructor(\n hash: AttachmentHash,\n expiresAtUtc: string,\n meta?: { mimeType: string; fileName: string; sizeBytes: number },\n ) {\n super(\n `Attachment pending upload for hash: ${hash}, expires: ${expiresAtUtc}`,\n );\n this.name = \"AttachmentPending\";\n this.hash = hash;\n this.expiresAtUtc = expiresAtUtc;\n this.metadata = meta;\n }\n}\n","import type { AttachmentHash, AttachmentRef } from \"@powerhousedao/reactor\";\nimport { InvalidAttachmentRef } from \"./errors.js\";\n\nconst REF_PATTERN = /^attachment:\\/\\/v(\\d+):(.+)$/;\nconst DEFAULT_VERSION = 1;\n\nexport type ParsedRef = {\n version: number;\n hash: AttachmentHash;\n};\n\nexport function parseRef(ref: AttachmentRef): ParsedRef {\n const match = REF_PATTERN.exec(ref);\n if (!match) {\n throw new InvalidAttachmentRef(ref);\n }\n return {\n version: Number(match[1]),\n hash: match[2],\n };\n}\n\nexport function createRef(\n hash: AttachmentHash,\n version: number = DEFAULT_VERSION,\n): AttachmentRef {\n return `attachment://v${version}:${hash}`;\n}\n","import type { AttachmentHash, AttachmentRef } from \"@powerhousedao/reactor\";\nimport {\n AttachmentAlreadyExists,\n AttachmentNotFound,\n AttachmentPending,\n} from \"./errors.js\";\nimport type {\n IAttachmentReader,\n IAttachmentService,\n IAttachmentUpload,\n IAttachmentUploadFactory,\n IReservationStore,\n} from \"./interfaces.js\";\nimport { createRef, parseRef } from \"./ref.js\";\nimport type {\n AttachmentHeader,\n AttachmentResponse,\n ReserveAttachmentOptions,\n} from \"./types.js\";\n\nconst CLIENT_HASH_PATTERN = /^[a-f0-9]{64}$/;\n\nexport class AttachmentService implements IAttachmentService {\n constructor(\n private readonly store: IAttachmentReader,\n private readonly reservations: IReservationStore,\n private readonly uploadFactory: IAttachmentUploadFactory,\n ) {}\n\n async reserve(options: ReserveAttachmentOptions): Promise<IAttachmentUpload> {\n if (options.clientHash !== undefined) {\n return this.reserveHashFirst(options);\n }\n const reservation = await this.reservations.create(options);\n return this.uploadFactory.createUpload(reservation);\n }\n\n async stat(ref: AttachmentRef): Promise<AttachmentHeader> {\n const { hash } = parseRef(ref);\n return this.store.stat(hash);\n }\n\n async get(\n ref: AttachmentRef,\n signal?: AbortSignal,\n ): Promise<AttachmentResponse> {\n const { hash } = parseRef(ref);\n return this.store.get(hash, signal);\n }\n\n private async reserveHashFirst(\n options: ReserveAttachmentOptions,\n ): Promise<IAttachmentUpload> {\n const normalized = options.clientHash!.toLowerCase() as AttachmentHash;\n if (!CLIENT_HASH_PATTERN.test(normalized)) {\n throw new Error(\n `clientHash must be a 64-character lowercase hex string, got: ${options.clientHash}`,\n );\n }\n if (\n options.sizeBytes === undefined ||\n !Number.isInteger(options.sizeBytes) ||\n options.sizeBytes <= 0 ||\n !Number.isSafeInteger(options.sizeBytes)\n ) {\n throw new Error(\n \"sizeBytes must be a positive safe integer when clientHash is provided\",\n );\n }\n\n const normalizedOptions: ReserveAttachmentOptions = {\n ...options,\n clientHash: normalized,\n };\n\n let existingHeader: AttachmentHeader | null = null;\n try {\n existingHeader = await this.store.stat(normalized);\n } catch (err) {\n if (\n !(err instanceof AttachmentNotFound) &&\n !(err instanceof AttachmentPending)\n ) {\n throw err;\n }\n }\n\n if (existingHeader !== null && existingHeader.status === \"available\") {\n throw new AttachmentAlreadyExists(normalized, createRef(normalized));\n }\n\n const reservation = await this.reservations.create(normalizedOptions);\n return this.uploadFactory.createUpload(reservation);\n }\n}\n","import type { JwtHandler } from \"@powerhousedao/reactor\";\n\nexport async function buildAuthHeaders(\n url: string,\n jwtHandler: JwtHandler | undefined,\n): Promise<Record<string, string>> {\n const headers: Record<string, string> = {};\n if (jwtHandler) {\n const token = await jwtHandler(url);\n if (token) {\n headers[\"Authorization\"] = `Bearer ${token}`;\n }\n }\n return headers;\n}\n","import type { AttachmentHash } from \"@powerhousedao/reactor\";\nimport type { JwtHandler } from \"@powerhousedao/reactor\";\nimport type { IAttachmentTransport } from \"../interfaces.js\";\nimport type { AttachmentMetadata, TransportFetchResult } from \"../types.js\";\nimport { buildAuthHeaders } from \"./build-auth-headers.js\";\n\nexport type SwitchboardTransportConfig = {\n remoteUrl: string;\n jwtHandler?: JwtHandler;\n fetchFn?: typeof fetch;\n};\n\nexport class SwitchboardAttachmentTransport implements IAttachmentTransport {\n private readonly remoteUrl: string;\n private readonly jwtHandler?: JwtHandler;\n private readonly fetchFn: typeof fetch;\n\n constructor(config: SwitchboardTransportConfig) {\n this.remoteUrl = config.remoteUrl;\n this.jwtHandler = config.jwtHandler;\n this.fetchFn = (config.fetchFn ?? globalThis.fetch).bind(globalThis);\n }\n\n async fetch(\n hash: AttachmentHash,\n signal?: AbortSignal,\n ): Promise<TransportFetchResult> {\n const url = `${this.remoteUrl}/attachments/${hash}`;\n const headers = await buildAuthHeaders(url, this.jwtHandler);\n\n const response = await this.fetchFn(url, { signal, headers });\n\n if (response.status === 202) {\n const expiresAtUtc = this.parsePendingExpiry(response);\n if (!expiresAtUtc) {\n throw new Error(\n \"Attachment fetch returned 202 with missing or malformed Attachment-Pending header\",\n );\n }\n const retryAfterMs = parseRetryAfterMs(response);\n return { kind: \"pending\", hash, expiresAtUtc, retryAfterMs };\n }\n\n if (response.status === 404) {\n return { kind: \"not-found\" };\n }\n\n if (!response.ok) {\n throw new Error(\n `Attachment fetch failed: ${response.status} ${response.statusText}`,\n );\n }\n\n const metadata = this.parseMetadataHeaders(response);\n const body = response.body;\n if (!body) {\n throw new Error(\"Response body is null\");\n }\n\n return { kind: \"data\", response: { hash, metadata, body } };\n }\n\n async announce(_hash: AttachmentHash): Promise<void> {\n // No-op for switchboard -- data is already on the server after upload.\n }\n\n async push(\n hash: AttachmentHash,\n remote: string,\n data: ReadableStream<Uint8Array>,\n ): Promise<void> {\n const url = `${remote}/attachments/${hash}`;\n const headers = await buildAuthHeaders(url, this.jwtHandler);\n\n const response = await this.fetchFn(url, {\n method: \"PUT\",\n body: data,\n headers,\n // @ts-expect-error Node fetch requires duplex for streaming request bodies\n duplex: \"half\",\n });\n\n if (!response.ok) {\n throw new Error(\n `Attachment push failed: ${response.status} ${response.statusText}`,\n );\n }\n }\n\n private parsePendingExpiry(response: Response): string | null {\n const header = response.headers.get(\"Attachment-Pending\");\n if (!header) return null;\n try {\n const parsed: unknown = JSON.parse(header);\n if (!isRecord(parsed)) return null;\n if (typeof parsed.expiresAtUtc !== \"string\") return null;\n return parsed.expiresAtUtc;\n } catch {\n return null;\n }\n }\n\n private parseMetadataHeaders(response: Response): AttachmentMetadata {\n // Compute the fallback at most once; both the recovery path inside the\n // header parser and the outer \"no header / parse failed\" path share it.\n let fallbackCache: AttachmentMetadata | undefined;\n const fallback = (): AttachmentMetadata => {\n if (fallbackCache === undefined) {\n fallbackCache = contentTypeFallback(response);\n }\n return fallbackCache;\n };\n\n const metaHeader = response.headers.get(\"Attachment-Metadata\");\n if (metaHeader) {\n try {\n const parsed: unknown = JSON.parse(metaHeader);\n if (isRecord(parsed)) {\n if (parsed.extension === undefined) {\n parsed.extension = null;\n }\n if (parsed.createdAtUtc === undefined) {\n parsed.createdAtUtc = fallback().createdAtUtc;\n }\n if (parsed.lastAccessedAtUtc === undefined) {\n parsed.lastAccessedAtUtc = fallback().lastAccessedAtUtc;\n }\n }\n if (isAttachmentMetadata(parsed)) {\n return parsed;\n }\n } catch {\n // fall through to Content-Type fallback\n }\n }\n return fallback();\n }\n}\n\nconst DEFAULT_RETRY_AFTER_MS = 5000;\n\nfunction parseRetryAfterMs(response: Response): number {\n const retryAfter = response.headers.get(\"Retry-After\");\n if (!retryAfter) return DEFAULT_RETRY_AFTER_MS;\n const seconds = Number(retryAfter);\n if (!Number.isFinite(seconds) || seconds < 0) return DEFAULT_RETRY_AFTER_MS;\n return Math.round(seconds * 1000);\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isAttachmentMetadata(value: unknown): value is AttachmentMetadata {\n if (!isRecord(value)) return false;\n if (typeof value.mimeType !== \"string\") return false;\n if (typeof value.fileName !== \"string\") return false;\n if (\n typeof value.sizeBytes !== \"number\" ||\n !Number.isFinite(value.sizeBytes) ||\n value.sizeBytes < 0\n ) {\n return false;\n }\n if (value.extension !== null && typeof value.extension !== \"string\") {\n return false;\n }\n if (typeof value.createdAtUtc !== \"string\") return false;\n if (\n value.lastAccessedAtUtc !== undefined &&\n typeof value.lastAccessedAtUtc !== \"string\"\n ) {\n return false;\n }\n return true;\n}\n\nfunction contentTypeFallback(response: Response): AttachmentMetadata {\n const contentLength = response.headers.get(\"Content-Length\");\n if (contentLength === null) {\n throw new Error(\n \"Switchboard response missing both Attachment-Metadata and Content-Length headers\",\n );\n }\n const sizeBytes = Number(contentLength);\n if (!Number.isInteger(sizeBytes) || sizeBytes < 0) {\n throw new Error(\n `Switchboard response has invalid Content-Length header: ${JSON.stringify(contentLength)}`,\n );\n }\n // Last-Modified is the closest legitimate signal we have for an original\n // creation time when Attachment-Metadata is absent. If that's missing too,\n // fall back to the response Date header (still server-attributed). This is\n // imperfect — Last-Modified reflects the most recent change, not the\n // original upload — but unlike sizeBytes there is no zero-equivalent\n // sentinel for a date, and downstream consumers expect a value.\n const lastModified = response.headers.get(\"Last-Modified\");\n const dateHeader = response.headers.get(\"Date\");\n const createdAtUtc = lastModified\n ? new Date(lastModified).toISOString()\n : dateHeader\n ? new Date(dateHeader).toISOString()\n : new Date().toISOString();\n\n return {\n // application/octet-stream is the RFC 2046 sentinel for \"unknown binary\",\n // and \"unknown\" is a non-real filename sentinel; neither is a fabricated\n // semantic value the way Content-Length=0 would be.\n mimeType:\n response.headers.get(\"Content-Type\") ?? \"application/octet-stream\",\n fileName: \"unknown\",\n sizeBytes,\n extension: null,\n createdAtUtc,\n lastAccessedAtUtc: createdAtUtc,\n };\n}\n","import type { AttachmentRef, JwtHandler } from \"@powerhousedao/reactor\";\nimport { AttachmentAlreadyExists, ReservationNotFound } from \"../errors.js\";\nimport type { IReservationStore } from \"../interfaces.js\";\nimport { createRef, parseRef } from \"../ref.js\";\nimport type { Reservation, ReserveAttachmentOptions } from \"../types.js\";\nimport { buildAuthHeaders } from \"./build-auth-headers.js\";\n\nexport type SwitchboardClientConfig = {\n remoteUrl: string;\n jwtHandler?: JwtHandler;\n fetchFn?: typeof fetch;\n};\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction deriveExtension(fileName: string): string | null {\n const idx = fileName.lastIndexOf(\".\");\n if (idx <= 0 || idx === fileName.length - 1) return null;\n return fileName.slice(idx + 1).toLowerCase();\n}\n\nfunction isReservationBase(value: unknown): value is Record<string, unknown> & {\n reservationId: string;\n mimeType: string;\n fileName: string;\n extension: string | null;\n createdAtUtc: string;\n expiresAtUtc: string;\n} {\n if (!isRecord(value)) return false;\n if (typeof value.reservationId !== \"string\") return false;\n if (typeof value.mimeType !== \"string\") return false;\n if (typeof value.fileName !== \"string\") return false;\n if (value.extension !== null && typeof value.extension !== \"string\") {\n return false;\n }\n if (typeof value.createdAtUtc !== \"string\") return false;\n if (typeof value.expiresAtUtc !== \"string\") return false;\n return true;\n}\n\nfunction isReservation(value: unknown): value is Reservation {\n if (!isReservationBase(value)) return false;\n // clientHash and sizeBytes may be absent on responses from older\n // switchboards; treat missing as null (normalized below in get()).\n if (\n value.clientHash !== undefined &&\n value.clientHash !== null &&\n typeof value.clientHash !== \"string\"\n ) {\n return false;\n }\n if (\n value.sizeBytes !== undefined &&\n value.sizeBytes !== null &&\n typeof value.sizeBytes !== \"number\"\n ) {\n return false;\n }\n return true;\n}\n\nexport class RemoteReservationStore implements IReservationStore {\n private readonly remoteUrl: string;\n private readonly jwtHandler?: JwtHandler;\n private readonly fetchFn: typeof fetch;\n\n constructor(config: SwitchboardClientConfig) {\n this.remoteUrl = config.remoteUrl;\n this.jwtHandler = config.jwtHandler;\n this.fetchFn = (config.fetchFn ?? globalThis.fetch).bind(globalThis);\n }\n\n async create(options: ReserveAttachmentOptions): Promise<Reservation> {\n const url = `${this.remoteUrl}/attachments/reservations`;\n const authHeaders = await buildAuthHeaders(url, this.jwtHandler);\n const extension = options.extension ?? deriveExtension(options.fileName);\n\n const bodyObj: Record<string, unknown> = {\n mimeType: options.mimeType,\n fileName: options.fileName,\n extension,\n };\n if (options.clientHash !== undefined) {\n bodyObj.clientHash = options.clientHash;\n }\n if (options.sizeBytes !== undefined) {\n bodyObj.sizeBytes = options.sizeBytes;\n }\n\n const response = await this.fetchFn(url, {\n method: \"POST\",\n headers: { ...authHeaders, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(bodyObj),\n });\n\n if (response.status === 409) {\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new Error(\n `Reservation create failed: ${response.status} ${response.statusText}`,\n );\n }\n if (\n isRecord(body) &&\n body.error === \"already_exists\" &&\n options.clientHash !== undefined\n ) {\n let ref: AttachmentRef;\n if (typeof body.ref === \"string\") {\n try {\n parseRef(body.ref as AttachmentRef);\n ref = body.ref as AttachmentRef;\n } catch {\n ref = createRef(options.clientHash);\n }\n } else {\n ref = createRef(options.clientHash);\n }\n throw new AttachmentAlreadyExists(options.clientHash, ref);\n }\n throw new Error(\n `Reservation create failed: ${response.status} ${response.statusText}`,\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Reservation create failed: ${response.status} ${response.statusText}`,\n );\n }\n\n let json: unknown;\n try {\n json = await response.json();\n } catch {\n throw new Error(\"Reservation create returned non-JSON response\");\n }\n if (\n typeof json !== \"object\" ||\n json === null ||\n typeof (json as Record<string, unknown>).reservationId !== \"string\" ||\n ((json as Record<string, unknown>).reservationId as string).length === 0\n ) {\n throw new Error(\n \"Reservation create returned a payload missing a non-empty reservationId string\",\n );\n }\n const body = json as {\n reservationId: string;\n ref?: string | null;\n createdAtUtc?: string;\n expiresAtUtc?: string;\n };\n // The server is the source of truth for both timestamps. We synthesize\n // only as a last-resort fallback for older switchboards that don't\n // include them in the response; in that case the client cannot know the\n // server's TTL, so expiresAtUtc is a best-effort placeholder.\n const now = new Date();\n return {\n reservationId: body.reservationId,\n mimeType: options.mimeType,\n fileName: options.fileName,\n extension,\n createdAtUtc: body.createdAtUtc ?? now.toISOString(),\n expiresAtUtc:\n body.expiresAtUtc ??\n new Date(now.getTime() + 24 * 60 * 60 * 1000).toISOString(),\n clientHash: options.clientHash ?? null,\n sizeBytes: options.sizeBytes ?? null,\n };\n }\n\n async get(reservationId: string): Promise<Reservation> {\n const url = `${this.remoteUrl}/attachments/reservations/${encodeURIComponent(reservationId)}`;\n const authHeaders = await buildAuthHeaders(url, this.jwtHandler);\n\n const response = await this.fetchFn(url, { headers: authHeaders });\n\n if (response.status === 404) {\n throw new ReservationNotFound(reservationId);\n }\n if (!response.ok) {\n throw new Error(\n `Reservation get failed: ${response.status} ${response.statusText}`,\n );\n }\n\n let parsed: unknown;\n try {\n parsed = await response.json();\n } catch {\n throw new Error(\"Reservation get returned non-JSON response\");\n }\n if (!isReservation(parsed)) {\n throw new Error(\n \"Reservation get returned a payload that does not match the Reservation shape\",\n );\n }\n return {\n reservationId: parsed.reservationId,\n mimeType: parsed.mimeType,\n fileName: parsed.fileName,\n extension: parsed.extension,\n createdAtUtc: parsed.createdAtUtc,\n expiresAtUtc: parsed.expiresAtUtc,\n // Normalize fields that may be absent on older switchboard responses.\n clientHash: parsed.clientHash ?? null,\n sizeBytes: parsed.sizeBytes ?? null,\n };\n }\n\n async delete(reservationId: string): Promise<void> {\n const url = `${this.remoteUrl}/attachments/reservations/${encodeURIComponent(reservationId)}`;\n const authHeaders = await buildAuthHeaders(url, this.jwtHandler);\n\n const response = await this.fetchFn(url, {\n method: \"DELETE\",\n headers: authHeaders,\n });\n\n // 2xx = success; 404 / 410 = already gone, treat as idempotent success.\n if (!response.ok && response.status !== 404 && response.status !== 410) {\n throw new Error(\n `Reservation delete failed: ${response.status} ${response.statusText}`,\n );\n }\n }\n\n // Sweeping is the server's responsibility; clients have no authority to\n // delete reservations on a remote switchboard.\n deleteExpired(): Promise<number> {\n return Promise.reject(\n new Error(\"RemoteReservationStore.deleteExpired is not supported\"),\n );\n }\n}\n","import type { AttachmentRef, JwtHandler } from \"@powerhousedao/reactor\";\nimport { HashMismatch, SizeMismatch } from \"../errors.js\";\nimport type { IAttachmentUpload } from \"../interfaces.js\";\nimport { createRef } from \"../ref.js\";\nimport type { AttachmentUploadResult, Reservation } from \"../types.js\";\nimport { buildAuthHeaders } from \"./build-auth-headers.js\";\nimport type { SwitchboardClientConfig } from \"./remote-reservation-store.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nexport class RemoteAttachmentUpload implements IAttachmentUpload {\n readonly reservationId: string;\n readonly ref: AttachmentRef | null;\n readonly expiresAtUtc: string;\n private readonly remoteUrl: string;\n private readonly jwtHandler?: JwtHandler;\n private readonly fetchFn: typeof fetch;\n\n constructor(reservation: Reservation, config: SwitchboardClientConfig) {\n this.reservationId = reservation.reservationId;\n this.ref =\n reservation.clientHash !== null\n ? createRef(reservation.clientHash)\n : null;\n this.expiresAtUtc = reservation.expiresAtUtc;\n this.remoteUrl = config.remoteUrl;\n this.jwtHandler = config.jwtHandler;\n this.fetchFn = (config.fetchFn ?? globalThis.fetch).bind(globalThis);\n }\n\n async send(\n data: ReadableStream<Uint8Array>,\n ): Promise<AttachmentUploadResult> {\n const url = `${this.remoteUrl}/attachments/reservations/${this.reservationId}`;\n const authHeaders = await buildAuthHeaders(url, this.jwtHandler);\n\n // Buffer the stream to a Blob. Streaming request bodies aren't universally\n // supported in browsers (Firefox stringifies the stream to \"[object\n // ReadableStream]\" even with duplex: \"half\"); buffering is the only\n // portable option for attachment uploads.\n const body = await new Response(data).blob();\n\n // Always upload as octet-stream. The server reads the real mime type from\n // the reservation row; sending the user's mime type here (e.g. application/json)\n // would let Express body-parser drain the request body before our handler runs,\n // silently writing zero bytes.\n const response = await this.fetchFn(url, {\n method: \"PUT\",\n headers: { ...authHeaders, \"Content-Type\": \"application/octet-stream\" },\n body,\n });\n\n if (response.status === 422) {\n let errorBody: unknown;\n try {\n errorBody = await response.json();\n } catch {\n throw new Error(\n `Attachment upload failed: ${response.status} ${response.statusText}`,\n );\n }\n if (isRecord(errorBody)) {\n if (\n errorBody.error === \"hash_mismatch\" &&\n typeof errorBody.claimed === \"string\" &&\n typeof errorBody.actual === \"string\"\n ) {\n throw new HashMismatch(errorBody.claimed, errorBody.actual);\n }\n if (\n errorBody.error === \"size_mismatch\" &&\n typeof errorBody.declared === \"number\" &&\n typeof errorBody.actual === \"number\"\n ) {\n throw new SizeMismatch(errorBody.declared, errorBody.actual);\n }\n }\n throw new Error(\n `Attachment upload failed: ${response.status} ${response.statusText}`,\n );\n }\n\n if (!response.ok) {\n throw new Error(\n `Attachment upload failed: ${response.status} ${response.statusText}`,\n );\n }\n\n return (await response.json()) as AttachmentUploadResult;\n }\n}\n","import type {\n IAttachmentUpload,\n IAttachmentUploadFactory,\n} from \"../interfaces.js\";\nimport type { Reservation } from \"../types.js\";\nimport { RemoteAttachmentUpload } from \"./remote-attachment-upload.js\";\nimport type { SwitchboardClientConfig } from \"./remote-reservation-store.js\";\n\nexport class RemoteAttachmentUploadFactory implements IAttachmentUploadFactory {\n constructor(private readonly config: SwitchboardClientConfig) {}\n\n createUpload(reservation: Reservation): IAttachmentUpload {\n return new RemoteAttachmentUpload(reservation, this.config);\n }\n}\n","import type { AttachmentHash } from \"@powerhousedao/reactor\";\nimport type { JwtHandler } from \"@powerhousedao/reactor\";\nimport { AttachmentNotFound, AttachmentPending } from \"../errors.js\";\nimport type { IAttachmentReader } from \"../interfaces.js\";\nimport type {\n AttachmentHeader,\n AttachmentMetadata,\n AttachmentResponse,\n} from \"../types.js\";\nimport { buildAuthHeaders } from \"./build-auth-headers.js\";\nimport type { SwitchboardClientConfig } from \"./remote-reservation-store.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction isAttachmentMetadata(value: unknown): value is AttachmentMetadata {\n if (!isRecord(value)) return false;\n if (typeof value.mimeType !== \"string\") return false;\n if (typeof value.fileName !== \"string\") return false;\n if (\n typeof value.sizeBytes !== \"number\" ||\n !Number.isFinite(value.sizeBytes) ||\n value.sizeBytes < 0\n ) {\n return false;\n }\n if (value.extension !== null && typeof value.extension !== \"string\") {\n return false;\n }\n if (typeof value.createdAtUtc !== \"string\") return false;\n if (\n value.lastAccessedAtUtc !== undefined &&\n typeof value.lastAccessedAtUtc !== \"string\"\n ) {\n return false;\n }\n return true;\n}\n\nfunction contentTypeFallback(response: Response): AttachmentMetadata {\n const contentLength = response.headers.get(\"Content-Length\");\n if (contentLength === null) {\n throw new Error(\n \"Switchboard response missing both Attachment-Metadata and Content-Length headers\",\n );\n }\n const sizeBytes = Number(contentLength);\n if (!Number.isInteger(sizeBytes) || sizeBytes < 0) {\n throw new Error(\n `Switchboard response has invalid Content-Length header: ${JSON.stringify(contentLength)}`,\n );\n }\n // Last-Modified is the closest legitimate signal we have for an original\n // creation time when Attachment-Metadata is absent. If that's missing too,\n // fall back to the response date (still server-attributed). This is\n // imperfect — Last-Modified reflects the most recent change, not the\n // original upload — but unlike sizeBytes there is no zero-equivalent\n // sentinel for a date, and downstream consumers expect a value.\n const lastModified = response.headers.get(\"Last-Modified\");\n const dateHeader = response.headers.get(\"Date\");\n const createdAtUtc = lastModified\n ? new Date(lastModified).toISOString()\n : dateHeader\n ? new Date(dateHeader).toISOString()\n : new Date().toISOString();\n\n return {\n // application/octet-stream is the RFC 2046 sentinel for \"unknown binary\",\n // and \"unknown\" is a non-real filename sentinel; neither is a fabricated\n // semantic value the way Content-Length=0 would be.\n mimeType:\n response.headers.get(\"Content-Type\") ?? \"application/octet-stream\",\n fileName: \"unknown\",\n sizeBytes,\n extension: null,\n createdAtUtc,\n lastAccessedAtUtc: createdAtUtc,\n };\n}\n\nfunction parseMetadata(response: Response): AttachmentMetadata {\n // Compute the fallback at most once; both the recovery path inside the\n // header parser and the outer \"no header / parse failed\" path share it.\n let fallbackCache: AttachmentMetadata | undefined;\n const fallback = (): AttachmentMetadata => {\n if (fallbackCache === undefined) {\n fallbackCache = contentTypeFallback(response);\n }\n return fallbackCache;\n };\n\n const metaHeader = response.headers.get(\"Attachment-Metadata\");\n if (metaHeader) {\n try {\n const parsed: unknown = JSON.parse(metaHeader);\n if (isRecord(parsed)) {\n if (parsed.extension === undefined) {\n parsed.extension = null;\n }\n // Older switchboards may omit these timestamps; fall back to the\n // Date/Last-Modified header so we never produce client-clock-stamped\n // values when the server has authority.\n if (parsed.createdAtUtc === undefined) {\n parsed.createdAtUtc = fallback().createdAtUtc;\n }\n if (parsed.lastAccessedAtUtc === undefined) {\n parsed.lastAccessedAtUtc = fallback().lastAccessedAtUtc;\n }\n }\n if (isAttachmentMetadata(parsed)) {\n return parsed;\n }\n } catch {\n // fall through to Content-Type fallback\n }\n }\n return fallback();\n}\n\ntype PendingInfo = {\n expiresAtUtc: string;\n mimeType: string;\n fileName: string;\n sizeBytes: number;\n};\n\ntype PartialPendingInfo = {\n expiresAtUtc: string;\n mimeType?: string;\n fileName?: string;\n sizeBytes?: number;\n};\n\nfunction parsePendingExpiry(response: Response): PartialPendingInfo | null {\n const header = response.headers.get(\"Attachment-Pending\");\n if (!header) return null;\n try {\n const parsed: unknown = JSON.parse(header);\n if (!isRecord(parsed)) return null;\n if (typeof parsed.expiresAtUtc !== \"string\") return null;\n const result: PartialPendingInfo = { expiresAtUtc: parsed.expiresAtUtc };\n if (typeof parsed.mimeType === \"string\") result.mimeType = parsed.mimeType;\n if (typeof parsed.fileName === \"string\") result.fileName = parsed.fileName;\n if (\n typeof parsed.sizeBytes === \"number\" &&\n Number.isFinite(parsed.sizeBytes) &&\n parsed.sizeBytes >= 0\n ) {\n result.sizeBytes = parsed.sizeBytes;\n }\n return result;\n } catch {\n return null;\n }\n}\n\nfunction parsePendingHeader(response: Response): PendingInfo | null {\n const partial = parsePendingExpiry(response);\n if (!partial) return null;\n if (\n typeof partial.mimeType !== \"string\" ||\n typeof partial.fileName !== \"string\" ||\n partial.sizeBytes === undefined\n ) {\n return null;\n }\n return {\n expiresAtUtc: partial.expiresAtUtc,\n mimeType: partial.mimeType,\n fileName: partial.fileName,\n sizeBytes: partial.sizeBytes,\n };\n}\n\nexport class RemoteAttachmentStore implements IAttachmentReader {\n private readonly remoteUrl: string;\n private readonly jwtHandler?: JwtHandler;\n private readonly fetchFn: typeof fetch;\n\n constructor(config: SwitchboardClientConfig) {\n this.remoteUrl = config.remoteUrl;\n this.jwtHandler = config.jwtHandler;\n this.fetchFn = (config.fetchFn ?? globalThis.fetch).bind(globalThis);\n }\n\n /**\n * Get attachment metadata. Normally returns a pending AttachmentHeader\n * (status: 'pending') when the server responds 202 with a full\n * Attachment-Pending header. When only expiresAtUtc is present in the\n * header (degraded wire), throws AttachmentPending instead -- the\n * AttachmentPending throw is the degraded-wire case.\n */\n async stat(hash: AttachmentHash): Promise<AttachmentHeader> {\n const url = `${this.remoteUrl}/attachments/${hash}`;\n const authHeaders = await buildAuthHeaders(url, this.jwtHandler);\n\n const response = await this.fetchFn(url, {\n method: \"HEAD\",\n headers: authHeaders,\n });\n\n if (response.status === 202) {\n const fullPending = parsePendingHeader(response);\n if (fullPending) {\n return buildPendingHeader(hash, fullPending);\n }\n const partial = parsePendingExpiry(response);\n if (partial) {\n throw new AttachmentPending(hash, partial.expiresAtUtc);\n }\n throw new Error(\n \"Attachment stat returned 202 with missing or malformed Attachment-Pending header\",\n );\n }\n\n if (response.status === 404) {\n throw new AttachmentNotFound(hash);\n }\n if (!response.ok) {\n throw new Error(\n `Attachment stat failed: ${response.status} ${response.statusText}`,\n );\n }\n\n const metadata = parseMetadata(response);\n return buildHeader(hash, metadata);\n }\n\n async get(\n hash: AttachmentHash,\n signal?: AbortSignal,\n ): Promise<AttachmentResponse> {\n return this.fetchAttachment(hash, signal);\n }\n\n private async fetchAttachment(\n hash: AttachmentHash,\n signal?: AbortSignal,\n ): Promise<AttachmentResponse> {\n const url = `${this.remoteUrl}/attachments/${hash}`;\n const headers = await buildAuthHeaders(url, this.jwtHandler);\n\n const response = await this.fetchFn(url, { signal, headers });\n\n if (response.status === 202) {\n const pending = parsePendingExpiry(response);\n if (!pending) {\n throw new Error(\n \"Attachment fetch returned 202 with missing or malformed Attachment-Pending header\",\n );\n }\n throw new AttachmentPending(hash, pending.expiresAtUtc);\n }\n\n if (response.status === 404) {\n throw new AttachmentNotFound(hash);\n }\n if (!response.ok) {\n throw new Error(\n `Attachment fetch failed: ${response.status} ${response.statusText}`,\n );\n }\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n\n const metadata = parseMetadata(response);\n return { header: buildHeader(hash, metadata), body: response.body };\n }\n}\n\nfunction buildHeader(\n hash: AttachmentHash,\n metadata: AttachmentMetadata,\n): AttachmentHeader {\n return {\n hash,\n mimeType: metadata.mimeType,\n fileName: metadata.fileName,\n sizeBytes: metadata.sizeBytes,\n extension: metadata.extension,\n status: \"available\",\n source: \"sync\",\n createdAtUtc: metadata.createdAtUtc,\n lastAccessedAtUtc: metadata.lastAccessedAtUtc ?? metadata.createdAtUtc,\n expiresAtUtc: null,\n };\n}\n\nfunction buildPendingHeader(\n hash: AttachmentHash,\n pending: PendingInfo,\n): AttachmentHeader {\n const now = new Date().toISOString();\n return {\n hash,\n mimeType: pending.mimeType,\n fileName: pending.fileName,\n sizeBytes: pending.sizeBytes,\n extension: null,\n status: \"pending\",\n source: \"sync\",\n createdAtUtc: now,\n lastAccessedAtUtc: now,\n expiresAtUtc: pending.expiresAtUtc,\n };\n}\n","import { AttachmentService } from \"../attachment-service.js\";\nimport type { IAttachmentService } from \"../interfaces.js\";\nimport { RemoteAttachmentStore } from \"./remote-attachment-store.js\";\nimport { RemoteAttachmentUploadFactory } from \"./remote-attachment-upload-factory.js\";\nimport {\n RemoteReservationStore,\n type SwitchboardClientConfig,\n} from \"./remote-reservation-store.js\";\n\nexport function createRemoteAttachmentService(\n config: SwitchboardClientConfig,\n): IAttachmentService {\n const reservations = new RemoteReservationStore(config);\n const uploadFactory = new RemoteAttachmentUploadFactory(config);\n const store = new RemoteAttachmentStore(config);\n return new AttachmentService(store, reservations, uploadFactory);\n}\n","import type { IAttachmentTransport } from \"./interfaces.js\";\nimport type { TransportFetchResult } from \"./types.js\";\n\n/**\n * No-op transport for deployments without remote sync.\n * fetch() always returns not-found, announce() and push() are no-ops.\n */\nexport class NullAttachmentTransport implements IAttachmentTransport {\n fetch(): Promise<TransportFetchResult> {\n return Promise.resolve({ kind: \"not-found\" });\n }\n\n announce(): Promise<void> {\n return Promise.resolve();\n }\n\n push(): Promise<void> {\n return Promise.resolve();\n }\n}\n"],"mappings":"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"}