@powerhousedao/reactor-attachments 6.2.3-dev.53 → 6.2.3-dev.55

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,4 +1,4 @@
1
- import { C as AttachmentUploadResult, D as ReserveAttachmentOptions, E as Reservation, O as TransportFetchResult, _ as AttachmentResponse, a as IAttachmentTransport, c as IAttachmentUploadFactory, f as AttachmentDownloadOptions, h as AttachmentHeader, l as IReservationStore, m as AttachmentDownloadTargetOptions, n as IAttachmentReader, p as AttachmentDownloadTarget, r as IAttachmentService, s as IAttachmentUpload, t as IAttachmentBackend, v as AttachmentSendOptions, w as AttachmentUploadTarget, y as AttachmentStatOptions } from "./interfaces-jHW9JXv1.js";
1
+ import { C as AttachmentUploadResult, D as ReserveAttachmentOptions, E as Reservation, O as TransportFetchResult, _ as AttachmentResponse, a as IAttachmentTransport, c as IAttachmentUploadFactory, f as AttachmentDownloadOptions, h as AttachmentHeader, l as IReservationStore, m as AttachmentDownloadTargetOptions, n as IAttachmentReader, p as AttachmentDownloadTarget, r as IAttachmentService, s as IAttachmentUpload, t as IAttachmentBackend, v as AttachmentSendOptions, w as AttachmentUploadTarget, y as AttachmentStatOptions } from "./interfaces-C4GRlKeB.js";
2
2
  import { AttachmentHash, AttachmentRef, JwtHandler } from "@powerhousedao/reactor";
3
3
  //#region src/errors.d.ts
4
4
  /**
@@ -258,7 +258,7 @@ declare class RemoteAttachmentUpload implements IAttachmentUpload {
258
258
  private readonly jwtHandler?;
259
259
  private readonly uploadTransport;
260
260
  constructor(reservation: Reservation, config: SwitchboardClientConfig);
261
- send(data: ReadableStream<Uint8Array>, options?: AttachmentSendOptions): Promise<AttachmentUploadResult>;
261
+ send(data: ReadableStream<Uint8Array> | Blob, options?: AttachmentSendOptions): Promise<AttachmentUploadResult>;
262
262
  /**
263
263
  * Direct provider upload: PUT the bytes to the presigned URL with exactly
264
264
  * the returned headers — never the Switchboard JWT — and treat any 2xx as
@@ -324,4 +324,4 @@ declare class NullAttachmentTransport implements IAttachmentTransport {
324
324
  //#endregion
325
325
  export { InvalidAttachmentRef as A, AttachmentMetadataField as C, AttachmentTransferStage as D, AttachmentTransferError as E, SizeMismatch as M, UploadTooLarge as N, HashMismatch as O, AttachmentAlreadyExists as S, AttachmentPending as T, createRef as _, RemoteAttachmentUpload as a, parseAttachmentDownloadTarget as b, XhrUploadTransportOptions as c, AttachmentUploadResponse as d, AttachmentUploadTransport as f, ParsedRef as g, SwitchboardTransportConfig as h, RemoteAttachmentUploadFactory as i, ReservationNotFound as j, InvalidAttachmentMetadata as k, createXhrUploadTransport as l, SwitchboardAttachmentTransport as m, createRemoteAttachmentService as n, RemoteReservationStore as o, createFetchUploadTransport as p, RemoteAttachmentStore as r, SwitchboardClientConfig as s, NullAttachmentTransport as t, AttachmentUploadRequest as u, parseRef as v, AttachmentNotFound as w, parseAttachmentUploadTarget as x, AttachmentService as y };
326
326
  export {};
327
- //# sourceMappingURL=null-attachment-transport-dg7vi6F4.d.ts.map
327
+ //# sourceMappingURL=null-attachment-transport-Bs4JpmFx.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"null-attachment-transport-dg7vi6F4.d.ts","names":[],"sources":["../src/errors.ts","../src/targets.ts","../src/attachment-service.ts","../src/ref.ts","../src/switchboard/switchboard-attachment-transport.ts","../src/switchboard/upload-transport.ts","../src/switchboard/xhr-upload-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
+ {"version":3,"file":"null-attachment-transport-Bs4JpmFx.d.ts","names":[],"sources":["../src/errors.ts","../src/targets.ts","../src/attachment-service.ts","../src/ref.ts","../src/switchboard/switchboard-attachment-transport.ts","../src/switchboard/upload-transport.ts","../src/switchboard/xhr-upload-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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"}
@@ -163,6 +163,12 @@ var AttachmentService = class {
163
163
  }
164
164
  };
165
165
  //#endregion
166
+ //#region src/blob.ts
167
+ /** Tells a Blob from a stream by shape: `instanceof Blob` misses other realms and polyfills. */
168
+ function isBlob(data) {
169
+ return !("getReader" in data);
170
+ }
171
+ //#endregion
166
172
  //#region src/switchboard/upload-transport.ts
167
173
  /**
168
174
  * The default transport. Ignores `onProgress` — fetch has no upload-side
@@ -410,6 +416,13 @@ var RemoteReservationStore = class {
410
416
  function isRecord$1(value) {
411
417
  return typeof value === "object" && value !== null && !Array.isArray(value);
412
418
  }
419
+ /**
420
+ * A Blob goes out untouched, so a File streams from disk. A stream is
421
+ * buffered: browsers don't portably accept a ReadableStream request body.
422
+ */
423
+ async function toBody(data) {
424
+ return isBlob(data) ? data : new Response(data).blob();
425
+ }
413
426
  var RemoteAttachmentUpload = class {
414
427
  reservationId;
415
428
  ref;
@@ -433,7 +446,7 @@ var RemoteAttachmentUpload = class {
433
446
  if (this.uploadTarget?.kind === "presigned-put") return this.sendPresigned(this.uploadTarget, data, options);
434
447
  const url = `${this.remoteUrl}/attachments/reservations/${this.reservationId}`;
435
448
  const authHeaders = await buildAuthHeaders(url, this.jwtHandler);
436
- const body = await new Response(data).blob();
449
+ const body = await toBody(data);
437
450
  const response = await this.uploadTransport({
438
451
  url,
439
452
  method: "PUT",
@@ -470,7 +483,7 @@ var RemoteAttachmentUpload = class {
470
483
  */
471
484
  async sendPresigned(target, data, options) {
472
485
  if (this.reservation.clientHash === null || this.ref === null) throw new Error("Presigned upload targets require a hash-first reservation");
473
- const body = await new Response(data).blob();
486
+ const body = await toBody(data);
474
487
  const response = await this.uploadTransport({
475
488
  url: target.url,
476
489
  method: target.method,
@@ -756,6 +769,6 @@ var NullAttachmentTransport = class {
756
769
  }
757
770
  };
758
771
  //#endregion
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 };
772
+ export { RemoteAttachmentUpload as a, createFetchUploadTransport as c, parseAttachmentDownloadTarget as d, parseAttachmentUploadTarget as f, RemoteAttachmentUploadFactory as i, isBlob as l, createRemoteAttachmentService as n, RemoteReservationStore as o, RemoteAttachmentStore as r, createXhrUploadTransport as s, NullAttachmentTransport as t, AttachmentService as u };
760
773
 
761
- //# sourceMappingURL=null-attachment-transport-BGG9cRC-.js.map
774
+ //# sourceMappingURL=null-attachment-transport-Cm_kb5ww.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"null-attachment-transport-Cm_kb5ww.js","names":["isRecord","isRecord","isRecord"],"sources":["../src/targets.ts","../src/attachment-service.ts","../src/blob.ts","../src/switchboard/upload-transport.ts","../src/switchboard/xhr-upload-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 {\n AttachmentDownloadTarget,\n AttachmentTargetHeaders,\n AttachmentUploadTarget,\n} from \"./types.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction parseUrl(value: unknown): string {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(\"Attachment target URL must be a non-empty string\");\n }\n if (value.trim() !== value || hasForbiddenUrlWhitespace(value)) {\n throw new Error(\"Attachment target URL must not contain raw whitespace\");\n }\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n throw new Error(\"Attachment target URL is invalid\");\n }\n if (\n (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") ||\n parsed.username !== \"\" ||\n parsed.password !== \"\"\n ) {\n throw new Error(\n \"Attachment target URL must be HTTP(S) and must not contain credentials\",\n );\n }\n // Return the original string: presigned URLs are opaque and must never be\n // normalized, decoded, reordered, or otherwise reconstructed.\n return value;\n}\n\nfunction parseHeaders(value: unknown): AttachmentTargetHeaders {\n if (!isRecord(value)) {\n throw new Error(\"Attachment target headers must be an object\");\n }\n // A null prototype preserves valid names such as `__proto__` as ordinary\n // own properties instead of invoking Object.prototype setters.\n const headers = Object.create(null) as Record<string, string>;\n const headerName = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\n for (const [name, headerValue] of Object.entries(value)) {\n if (!headerName.test(name)) {\n throw new Error(`Attachment target header name is invalid: ${name}`);\n }\n if (\n typeof headerValue !== \"string\" ||\n hasForbiddenHeaderCharacter(headerValue)\n ) {\n throw new Error(`Attachment target header value is invalid: ${name}`);\n }\n headers[name] = headerValue;\n }\n return headers;\n}\n\nfunction hasForbiddenHeaderCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index++) {\n const code = value.charCodeAt(index);\n // Horizontal tab is valid field whitespace. Other C0 controls, DEL, and\n // especially CR/LF are forbidden to prevent header injection.\n if (code <= 8 || (code >= 10 && code <= 31) || code === 127 || code > 255) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasForbiddenUrlWhitespace(value: string): boolean {\n for (let index = 0; index < value.length; index++) {\n const code = value.charCodeAt(index);\n // URL parsing may silently trim or percent-encode raw ASCII whitespace.\n // Reject it so the validated opaque string is exactly what Fetch receives.\n if (code <= 32 || code === 127) return true;\n }\n return false;\n}\n\nfunction parseExpiry(value: unknown, required: boolean): string | undefined {\n if (value === undefined && !required) return undefined;\n if (typeof value !== \"string\") {\n throw new Error(\"Attachment target expiry must be an ISO 8601 UTC string\");\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value) {\n throw new Error(\"Attachment target expiry must be an ISO 8601 UTC string\");\n }\n return value;\n}\n\nexport function parseAttachmentUploadTarget(\n value: unknown,\n): AttachmentUploadTarget {\n if (!isRecord(value)) {\n throw new Error(\"Attachment upload target must be an object\");\n }\n const url = parseUrl(value.url);\n const headers = parseHeaders(value.headers);\n switch (value.kind) {\n case \"switchboard\":\n if (value.method !== \"PUT\") {\n throw new Error(\"Switchboard upload target method must be PUT\");\n }\n return {\n kind: \"switchboard\",\n method: \"PUT\",\n url,\n headers,\n ...(value.expiresAtUtc === undefined\n ? {}\n : { expiresAtUtc: parseExpiry(value.expiresAtUtc, false) }),\n };\n case \"presigned-put\":\n if (value.method !== \"PUT\") {\n throw new Error(\"Presigned upload target method must be PUT\");\n }\n return {\n kind: \"presigned-put\",\n method: \"PUT\",\n url,\n headers,\n expiresAtUtc: parseExpiry(value.expiresAtUtc, true)!,\n };\n default:\n throw new Error(\"Attachment upload target kind is unknown\");\n }\n}\n\nexport function parseAttachmentDownloadTarget(\n value: unknown,\n): AttachmentDownloadTarget {\n if (!isRecord(value)) {\n throw new Error(\"Attachment download target must be an object\");\n }\n const url = parseUrl(value.url);\n const headers = parseHeaders(value.headers);\n switch (value.kind) {\n case \"switchboard\":\n if (value.method !== \"GET\") {\n throw new Error(\"Switchboard download target method must be GET\");\n }\n return {\n kind: \"switchboard\",\n method: \"GET\",\n url,\n headers,\n ...(value.expiresAtUtc === undefined\n ? {}\n : { expiresAtUtc: parseExpiry(value.expiresAtUtc, false) }),\n };\n case \"presigned-get\":\n if (value.method !== \"GET\") {\n throw new Error(\"Presigned download target method must be GET\");\n }\n return {\n kind: \"presigned-get\",\n method: \"GET\",\n url,\n headers,\n expiresAtUtc: parseExpiry(value.expiresAtUtc, true)!,\n };\n default:\n throw new Error(\"Attachment download target kind is unknown\");\n }\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 IAttachmentBackend,\n IAttachmentService,\n IAttachmentUpload,\n IAttachmentUploadFactory,\n IReservationStore,\n} from \"./interfaces.js\";\nimport { createRef, parseRef } from \"./ref.js\";\nimport type {\n AttachmentDownloadOptions,\n AttachmentDownloadTarget,\n AttachmentDownloadTargetOptions,\n AttachmentHeader,\n AttachmentResponse,\n AttachmentStatOptions,\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 private readonly backend?: IAttachmentBackend,\n ) {}\n\n async reserve(options: ReserveAttachmentOptions): Promise<IAttachmentUpload> {\n if (options.clientHash !== undefined) {\n return this.reserveHashFirst(options);\n }\n if (this.backend?.kind === \"s3\") {\n throw new Error(\"S3 attachment reservations require a client hash\");\n }\n const reservation = await this.reservations.create(options);\n return this.uploadFactory.createUpload(reservation);\n }\n\n async stat(\n ref: AttachmentRef,\n options?: AttachmentStatOptions,\n ): Promise<AttachmentHeader> {\n const { hash } = parseRef(ref);\n return options?.documentId === undefined\n ? this.store.stat(hash)\n : this.store.stat(hash, options.documentId);\n }\n\n async get(\n ref: AttachmentRef,\n options?: AbortSignal | AttachmentDownloadOptions,\n ): Promise<AttachmentResponse> {\n const { hash } = parseRef(ref);\n const normalized =\n options === undefined || options instanceof AbortSignal\n ? { signal: options }\n : options;\n return normalized.documentId === undefined\n ? this.store.get(hash, normalized.signal)\n : this.store.get(hash, normalized.signal, normalized.documentId);\n }\n\n getDownloadTarget(\n ref: AttachmentRef,\n options: AttachmentDownloadTargetOptions,\n ): Promise<AttachmentDownloadTarget> {\n const { hash } = parseRef(ref);\n const getTarget = this.store.getDownloadTarget?.bind(this.store);\n if (getTarget === undefined) {\n return Promise.reject(\n new Error(\n \"Download targets are not supported by this attachment store: only remote stores negotiate direct URLs\",\n ),\n );\n }\n return getTarget(hash, options);\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) {\n if (this.backend?.kind === \"s3\") {\n if (await this.backend.exists(normalized)) {\n throw new AttachmentAlreadyExists(normalized, createRef(normalized));\n }\n } else if (existingHeader.status === \"available\") {\n throw new AttachmentAlreadyExists(normalized, createRef(normalized));\n }\n }\n\n const reservation = await this.reservations.create(normalizedOptions);\n if (this.backend?.kind !== \"s3\") {\n return this.uploadFactory.createUpload(reservation);\n }\n const uploadTarget = await this.backend.prepareUploadTarget(reservation);\n return this.uploadFactory.createUpload({ ...reservation, uploadTarget });\n }\n}\n","/** Tells a Blob from a stream by shape: `instanceof Blob` misses other realms and polyfills. */\nexport function isBlob(data: ReadableStream<Uint8Array> | Blob): data is Blob {\n return !(\"getReader\" in data);\n}\n","/**\n * The seam every attachment upload PUT goes through.\n *\n * It exists because `fetch` cannot report upload-side progress: there is no\n * event between \"request issued\" and \"response received\". Naming the transport\n * lets an implementation that *can* count bytes (see\n * `createXhrUploadTransport`) be substituted without the upload code knowing\n * which one it holds — and it is also the right seam for a future multipart\n * upload, which needs one request per part.\n */\n\n/**\n * Headers are forwarded VERBATIM. A presigned target's headers are part of a\n * signature: normalizing their casing, or adding so much as one header,\n * invalidates it.\n */\nexport type AttachmentUploadRequest = {\n url: string;\n method: \"PUT\";\n headers: Record<string, string>;\n body: Blob;\n /** Called with cumulative bytes handed to the socket, when observable. */\n onProgress?: (loaded: number, total: number) => void;\n signal?: AbortSignal;\n};\n\n/**\n * The only response surface the upload code reads. A real `Response`\n * satisfies this structurally, which is what lets the fetch transport be an\n * identity pass-through rather than an adapter.\n */\nexport type AttachmentUploadResponse = {\n readonly status: number;\n readonly statusText: string;\n readonly ok: boolean;\n json(): Promise<unknown>;\n};\n\nexport type AttachmentUploadTransport = (\n request: AttachmentUploadRequest,\n) => Promise<AttachmentUploadResponse>;\n\n/**\n * The default transport. Ignores `onProgress` — fetch has no upload-side\n * progress event, and silence is how a transport reports that bytes are\n * unobservable.\n */\nexport function createFetchUploadTransport(\n fetchFn: typeof fetch,\n): AttachmentUploadTransport {\n return (request) =>\n fetchFn(request.url, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n ...(request.signal ? { signal: request.signal } : {}),\n });\n}\n","import {\n createFetchUploadTransport,\n type AttachmentUploadRequest,\n type AttachmentUploadResponse,\n type AttachmentUploadTransport,\n} from \"./upload-transport.js\";\n\nexport type XhrUploadTransportOptions = {\n /**\n * Used where `XMLHttpRequest` does not exist (Node). Defaults to the global\n * fetch, resolved at call time.\n */\n fetchFn?: typeof fetch;\n};\n\n/**\n * Upload transport backed by `XMLHttpRequest`, the only browser API that\n * reports upload-side byte progress (`xhr.upload.onprogress`). Everything else\n * about the request is identical to the fetch path.\n *\n * Capability detection happens **inside** the returned function, never at\n * module load: this module is reachable from the client entry, which is\n * executed in Node by the entrypoint tests, and Node has no\n * `XMLHttpRequest`. Detecting per call also means a test can install a fake on\n * `globalThis` without any module-registry reset.\n */\nexport function createXhrUploadTransport(\n options?: XhrUploadTransportOptions,\n): AttachmentUploadTransport {\n return (request) => {\n const XhrCtor = globalThis.XMLHttpRequest;\n if (typeof XhrCtor !== \"function\") {\n const fetchFn = options?.fetchFn ?? globalThis.fetch;\n return createFetchUploadTransport(fetchFn.bind(globalThis))(request);\n }\n return sendWithXhr(new XhrCtor(), request);\n };\n}\n\n/**\n * Mirrors fetch's observable behavior closely enough that\n * `RemoteAttachmentUpload` cannot tell the two apart:\n *\n * - `responseType` is left at `\"\"`, so a non-JSON 422 body still reaches the\n * caller's `json()` and fails there rather than being swallowed.\n * - status 0 never resolves. XHR reports 0 for network failure, CORS refusal\n * and abort alike; resolving it would fabricate a transfer error claiming\n * the provider answered 0.\n * - failures reject with fetch's error shapes: `TypeError` for network,\n * `AbortError` for abort.\n */\nfunction sendWithXhr(\n xhr: XMLHttpRequest,\n request: AttachmentUploadRequest,\n): Promise<AttachmentUploadResponse> {\n return new Promise<AttachmentUploadResponse>((resolve, reject) => {\n const signal = request.signal;\n let settled = false;\n\n const abortListener = () => xhr.abort();\n const detach = () => signal?.removeEventListener(\"abort\", abortListener);\n const succeed = (response: AttachmentUploadResponse) => {\n if (settled) return;\n settled = true;\n detach();\n resolve(response);\n };\n const fail = (error: Error) => {\n if (settled) return;\n settled = true;\n detach();\n reject(error);\n };\n\n if (signal?.aborted) {\n fail(abortError());\n return;\n }\n\n xhr.open(request.method, request.url, true);\n // Verbatim: a presigned target's headers are part of its signature.\n for (const [name, value] of Object.entries(request.headers)) {\n xhr.setRequestHeader(name, value);\n }\n\n // Attached before send(), and only when a caller is listening — merely\n // registering an upload listener forces a CORS preflight.\n if (request.onProgress) {\n xhr.upload.addEventListener(\"progress\", (event: ProgressEvent) => {\n if (!event.lengthComputable) return;\n request.onProgress?.(event.loaded, event.total);\n });\n }\n\n xhr.addEventListener(\"load\", () => {\n if (xhr.status === 0) {\n fail(networkError());\n return;\n }\n succeed(toResponse(xhr));\n });\n xhr.addEventListener(\"error\", () => fail(networkError()));\n xhr.addEventListener(\"timeout\", () => fail(networkError()));\n xhr.addEventListener(\"abort\", () => fail(abortError()));\n\n signal?.addEventListener(\"abort\", abortListener);\n xhr.send(request.body);\n });\n}\n\nfunction toResponse(xhr: XMLHttpRequest): AttachmentUploadResponse {\n return {\n status: xhr.status,\n statusText: xhr.statusText,\n ok: xhr.status >= 200 && xhr.status < 300,\n json: () => {\n try {\n return Promise.resolve(JSON.parse(xhr.responseText));\n } catch (err) {\n return Promise.reject(\n err instanceof Error ? err : new Error(String(err)),\n );\n }\n },\n };\n}\n\nfunction networkError(): TypeError {\n return new TypeError(\"Failed to fetch\");\n}\n\nfunction abortError(): DOMException {\n return new DOMException(\"The operation was aborted\", \"AbortError\");\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 { parseAttachmentUploadTarget } from \"../targets.js\";\nimport { buildAuthHeaders } from \"./build-auth-headers.js\";\nimport type { AttachmentUploadTransport } from \"./upload-transport.js\";\n\nexport type SwitchboardClientConfig = {\n remoteUrl: string;\n jwtHandler?: JwtHandler;\n fetchFn?: typeof fetch;\n /**\n * Transport for upload PUTs only, so a browser host can opt into\n * XMLHttpRequest and get real upload progress.\n *\n * An explicit `fetchFn` always wins over this. The precedence is\n * one-directional on purpose: under this rule, adding `uploadTransport` to a\n * config that already pins `fetchFn` is a provable no-op, whereas the\n * reverse rule would let `{ ...config, uploadTransport }` silently route the\n * largest request in the system around a test's mock and onto the network.\n */\n uploadTransport?: AttachmentUploadTransport;\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 uploadTarget?: unknown;\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 ...(body.uploadTarget === undefined\n ? {}\n : { uploadTarget: parseAttachmentUploadTarget(body.uploadTarget) }),\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 ...(parsed.uploadTarget === undefined\n ? {}\n : {\n uploadTarget: parseAttachmentUploadTarget(parsed.uploadTarget),\n }),\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 {\n AttachmentHash,\n AttachmentRef,\n JwtHandler,\n} from \"@powerhousedao/reactor\";\nimport {\n AttachmentTransferError,\n HashMismatch,\n SizeMismatch,\n} from \"../errors.js\";\nimport type { IAttachmentUpload } from \"../interfaces.js\";\nimport { createRef } from \"../ref.js\";\nimport type {\n AttachmentSendOptions,\n AttachmentUploadResult,\n AttachmentUploadTarget,\n Reservation,\n} from \"../types.js\";\nimport { buildAuthHeaders } from \"./build-auth-headers.js\";\nimport type { SwitchboardClientConfig } from \"./remote-reservation-store.js\";\nimport {\n createFetchUploadTransport,\n type AttachmentUploadTransport,\n} from \"./upload-transport.js\";\nimport { isBlob } from \"../blob.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * A Blob goes out untouched, so a File streams from disk. A stream is\n * buffered: browsers don't portably accept a ReadableStream request body.\n */\nasync function toBody(data: ReadableStream<Uint8Array> | Blob): Promise<Blob> {\n return isBlob(data) ? data : new Response(data).blob();\n}\n\nexport class RemoteAttachmentUpload implements IAttachmentUpload {\n readonly reservationId: string;\n readonly ref: AttachmentRef | null;\n readonly expiresAtUtc: string;\n readonly uploadTarget?: AttachmentUploadTarget;\n private readonly reservation: Reservation;\n private readonly remoteUrl: string;\n private readonly jwtHandler?: JwtHandler;\n private readonly uploadTransport: AttachmentUploadTransport;\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 if (reservation.uploadTarget) {\n this.uploadTarget = reservation.uploadTarget;\n }\n this.reservation = reservation;\n this.remoteUrl = config.remoteUrl;\n this.jwtHandler = config.jwtHandler;\n // fetchFn wins: pinning fetch must not be silently overridden.\n this.uploadTransport = config.fetchFn\n ? createFetchUploadTransport(config.fetchFn.bind(globalThis))\n : (config.uploadTransport ??\n createFetchUploadTransport(globalThis.fetch.bind(globalThis)));\n }\n\n async send(\n data: ReadableStream<Uint8Array> | Blob,\n options?: AttachmentSendOptions,\n ): Promise<AttachmentUploadResult> {\n // Presigned targets bypass Switchboard entirely: bytes go straight to the\n // provider with the exact signed headers. Switchboard targets (and the\n // legacy no-target wire) keep the authenticated reservation PUT below.\n if (this.uploadTarget?.kind === \"presigned-put\") {\n return this.sendPresigned(this.uploadTarget, data, options);\n }\n const url = `${this.remoteUrl}/attachments/reservations/${this.reservationId}`;\n const authHeaders = await buildAuthHeaders(url, this.jwtHandler);\n\n const body = await toBody(data);\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.uploadTransport({\n url,\n method: \"PUT\",\n headers: { ...authHeaders, \"Content-Type\": \"application/octet-stream\" },\n body,\n ...(options?.onProgress\n ? {\n onProgress: (loaded: number, total: number) =>\n options.onProgress?.(loaded, total),\n }\n : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\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 /**\n * Direct provider upload: PUT the bytes to the presigned URL with exactly\n * the returned headers — never the Switchboard JWT — and treat any 2xx as\n * final success with no follow-up control request. The result is\n * synthesized from the hash-first reservation, which is the only path that\n * can produce a presigned target.\n */\n private async sendPresigned(\n target: AttachmentUploadTarget,\n data: ReadableStream<Uint8Array> | Blob,\n options?: AttachmentSendOptions,\n ): Promise<AttachmentUploadResult> {\n if (this.reservation.clientHash === null || this.ref === null) {\n throw new Error(\n \"Presigned upload targets require a hash-first reservation\",\n );\n }\n\n const body = await toBody(data);\n const response = await this.uploadTransport({\n url: target.url,\n method: target.method,\n headers: { ...target.headers },\n body,\n ...(options?.onProgress\n ? {\n onProgress: (loaded: number, total: number) =>\n options.onProgress?.(loaded, total),\n }\n : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n });\n if (!response.ok) {\n throw new AttachmentTransferError(\"presigned-put\", response.status);\n }\n\n const hash = this.reservation.clientHash as AttachmentHash;\n const now = new Date().toISOString();\n return {\n hash,\n ref: this.ref,\n header: {\n hash,\n mimeType: this.reservation.mimeType,\n fileName: this.reservation.fileName,\n sizeBytes: this.reservation.sizeBytes ?? body.size,\n extension: this.reservation.extension,\n status: \"available\",\n source: \"local\",\n createdAtUtc: this.reservation.createdAtUtc || now,\n lastAccessedAtUtc: now,\n expiresAtUtc: null,\n },\n };\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 {\n AttachmentNotFound,\n AttachmentPending,\n AttachmentTransferError,\n} from \"../errors.js\";\nimport type { IAttachmentReader } from \"../interfaces.js\";\nimport { parseAttachmentDownloadTarget } from \"../targets.js\";\nimport type {\n AttachmentDownloadTarget,\n AttachmentDownloadTargetOptions,\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\nconst MISSING_DOCUMENT_ID =\n \"Remote attachment reads require the documentId whose operations reference the attachment\";\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(\n hash: AttachmentHash,\n documentId?: string,\n ): Promise<AttachmentHeader> {\n // The server answers a hash-only HEAD 404; hash-first reserve relies on\n // that to leave the dedup decision to the server's 409.\n if (documentId === undefined) {\n throw new AttachmentNotFound(hash);\n }\n const url = `${this.remoteUrl}/attachments/${hash}?documentId=${encodeURIComponent(documentId)}`;\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 documentId?: string,\n ): Promise<AttachmentResponse> {\n if (documentId === undefined) {\n throw new Error(MISSING_DOCUMENT_ID);\n }\n const target = await this.getDownloadTarget(hash, { documentId, signal });\n if (target.kind === \"presigned-get\") {\n return this.fetchPresigned(hash, target, signal);\n }\n return this.fetchAttachment(hash, target, signal);\n }\n\n /**\n * Asks Switchboard for an authorized download target. The request carries\n * the JWT; the response is runtime-validated before any byte transfer.\n * Public so callers can mint direct URLs (previews, share links) without\n * transferring bytes; `expiresIn` requests a caller-chosen lifetime.\n */\n async getDownloadTarget(\n hash: AttachmentHash,\n options: AttachmentDownloadTargetOptions,\n ): Promise<AttachmentDownloadTarget> {\n const { documentId, expiresIn, signal } = options;\n const expiry =\n expiresIn === undefined\n ? \"\"\n : `&expiresIn=${encodeURIComponent(String(expiresIn))}`;\n const url = `${this.remoteUrl}/attachments/${hash}/download-target?documentId=${encodeURIComponent(documentId)}${expiry}`;\n const headers = await buildAuthHeaders(url, this.jwtHandler);\n const response = await this.fetchFn(url, { signal, headers });\n\n if (response.status === 404) {\n throw new AttachmentNotFound(hash);\n }\n if (!response.ok) {\n throw new AttachmentTransferError(\"download-target\", response.status);\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new AttachmentTransferError(\"download-target\");\n }\n return parseAttachmentDownloadTarget(body);\n }\n\n /**\n * Executes a presigned GET with exactly the returned headers and no JWT.\n * A provider 404 means the object is missing despite available metadata\n * (the accepted abandoned-upload trade-off) and surfaces as the same typed\n * not-found error callers already handle.\n */\n private async fetchPresigned(\n hash: AttachmentHash,\n target: AttachmentDownloadTarget,\n signal?: AbortSignal,\n ): Promise<AttachmentResponse> {\n const response = await this.fetchFn(target.url, {\n signal,\n headers: { ...target.headers },\n });\n if (response.status === 404) {\n throw new AttachmentNotFound(hash);\n }\n if (!response.ok) {\n throw new AttachmentTransferError(\"presigned-get\", response.status);\n }\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n const metadata = parseMetadata(response);\n return { header: buildHeader(hash, metadata), body: response.body };\n }\n\n private async fetchAttachment(\n hash: AttachmentHash,\n target: AttachmentDownloadTarget,\n signal?: AbortSignal,\n ): Promise<AttachmentResponse> {\n const url = target.url;\n const authHeaders = await buildAuthHeaders(url, this.jwtHandler);\n const headers = { ...target.headers, ...authHeaders };\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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"}
@@ -1,4 +1,4 @@
1
- import { O as TransportFetchResult, _ as AttachmentResponse, a as IAttachmentTransport, g as AttachmentMetadata, h as AttachmentHeader, i as IAttachmentStore } from "./interfaces-jHW9JXv1.js";
1
+ import { O as TransportFetchResult, _ as AttachmentResponse, a as IAttachmentTransport, g as AttachmentMetadata, h as AttachmentHeader, i as IAttachmentStore } from "./interfaces-C4GRlKeB.js";
2
2
  import { a as IAttachmentReferenceScanner, c as IAttachmentSchemaCompiler, i as IAttachmentReferenceReader, n as AttachmentReferencePageResult, r as AttachmentReferenceRow } from "./types-AagIcjTD.js";
3
3
  import { AttachmentHash, AttachmentRef, IDocumentModelRegistry, IEventBus, IReactorClient, ISyncManager, JwtHandler, LocalChannelPort } from "@powerhousedao/reactor";
4
4
  import { AuthSubject, OperationWithContext } from "@powerhousedao/shared/document-model";
@@ -1 +1 @@
1
- {"version":3,"file":"replication.d.ts","names":[],"sources":["../src/access/attachment-read-gate.ts","../src/local/local-attachment-server.ts","../src/local/local-attachment-transport.ts","../src/local/protocol.ts","../src/peers/peered-attachment-transport.ts","../src/peers/attachment-peer-links.ts","../src/replication/types.ts","../src/replication/attachment-replicator.ts","../src/replication/hash.ts","../src/replication/operation-attachment-refs.ts","../src/storage/local/bytes.ts","../src/storage/local/types.ts","../src/storage/local/idb-attachment-backend.ts","../src/storage/local/local-attachment-store.ts","../src/storage/local/memory-attachment-backend.ts"],"mappings":"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"}
1
+ {"version":3,"file":"replication.d.ts","names":[],"sources":["../src/access/attachment-read-gate.ts","../src/local/local-attachment-server.ts","../src/local/local-attachment-transport.ts","../src/local/protocol.ts","../src/peers/peered-attachment-transport.ts","../src/peers/attachment-peer-links.ts","../src/replication/types.ts","../src/replication/attachment-replicator.ts","../src/replication/hash.ts","../src/replication/operation-attachment-refs.ts","../src/storage/local/bytes.ts","../src/storage/local/types.ts","../src/storage/local/idb-attachment-backend.ts","../src/storage/local/local-attachment-store.ts","../src/storage/local/memory-attachment-backend.ts"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { a as parseRef, c as AttachmentPending, i as createRef, r as isAttachmentMetadata, s as AttachmentNotFound, t as SwitchboardAttachmentTransport } from "./switchboard-attachment-transport-BH3ZAmxE.js";
2
2
  import { t as AttachmentSchemaCompiler } from "./attachment-schema-compiler-rIaJpiHO.js";
3
- import { t as sha256Hex } from "./hash-CKHeI4AH.js";
3
+ import { t as sha256Hex } from "./hash-BobbR4r4.js";
4
4
  import { GQL_CHANNEL_TYPE, ReactorEventTypes } from "@powerhousedao/reactor";
5
5
  import { isPurgeMarker } from "@powerhousedao/shared/document-model";
6
6
  //#region src/access/attachment-read-gate.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerhousedao/reactor-attachments",
3
- "version": "6.2.3-dev.53",
3
+ "version": "6.2.3-dev.55",
4
4
  "license": "AGPL-3.0-only",
5
5
  "type": "module",
6
6
  "repository": {
@@ -35,9 +35,10 @@
35
35
  "dependencies": {
36
36
  "@aws-sdk/client-s3": "3.1087.0",
37
37
  "@aws-sdk/s3-request-presigner": "3.1087.0",
38
+ "@noble/hashes": "^1.8.0",
38
39
  "@powerhousedao/document-engineering": "1.40.5",
39
- "@powerhousedao/reactor": "6.2.3-dev.53",
40
- "@powerhousedao/shared": "6.2.3-dev.53",
40
+ "@powerhousedao/reactor": "6.2.3-dev.55",
41
+ "@powerhousedao/shared": "6.2.3-dev.55",
41
42
  "change-case": "5.4.4",
42
43
  "graphql": "^16",
43
44
  "kysely": "0.28.16"
@@ -1,26 +0,0 @@
1
- //#region src/replication/hash.ts
2
- /**
3
- * Lowercase SHA-256 hex of `bytes` -- the `attachment://v1:` hash format
4
- * (`packages/reactor/src/attachments/types.ts`).
5
- *
6
- * WebCrypto, so the same code runs in Node and in a browser realm. There is no
7
- * streaming digest in WebCrypto, which is why verification happens over
8
- * collected bytes.
9
- */
10
- async function sha256Hex(bytes) {
11
- const digest = await globalThis.crypto.subtle.digest("SHA-256", digestable(bytes));
12
- return Array.from(new Uint8Array(digest)).map((byte) => byte.toString(16).padStart(2, "0")).join("");
13
- }
14
- /** WebCrypto refuses a SharedArrayBuffer-backed view, so only that one is copied. */
15
- function digestable(bytes) {
16
- if (typeof SharedArrayBuffer !== "undefined" && bytes.buffer instanceof SharedArrayBuffer) {
17
- const copy = new Uint8Array(bytes.byteLength);
18
- copy.set(bytes);
19
- return copy;
20
- }
21
- return bytes;
22
- }
23
- //#endregion
24
- export { sha256Hex as t };
25
-
26
- //# sourceMappingURL=hash-CKHeI4AH.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"hash-CKHeI4AH.js","names":[],"sources":["../src/replication/hash.ts"],"sourcesContent":["import type { AttachmentHash } from \"@powerhousedao/reactor\";\n\n/**\n * Lowercase SHA-256 hex of `bytes` -- the `attachment://v1:` hash format\n * (`packages/reactor/src/attachments/types.ts`).\n *\n * WebCrypto, so the same code runs in Node and in a browser realm. There is no\n * streaming digest in WebCrypto, which is why verification happens over\n * collected bytes.\n */\nexport async function sha256Hex(bytes: Uint8Array): Promise<AttachmentHash> {\n const digest = await globalThis.crypto.subtle.digest(\n \"SHA-256\",\n digestable(bytes),\n );\n return Array.from(new Uint8Array(digest))\n .map((byte) => byte.toString(16).padStart(2, \"0\"))\n .join(\"\");\n}\n\n/** WebCrypto refuses a SharedArrayBuffer-backed view, so only that one is copied. */\nfunction digestable(bytes: Uint8Array): Uint8Array<ArrayBuffer> {\n if (\n typeof SharedArrayBuffer !== \"undefined\" &&\n bytes.buffer instanceof SharedArrayBuffer\n ) {\n const copy = new Uint8Array(bytes.byteLength);\n copy.set(bytes);\n return copy;\n }\n return bytes as Uint8Array<ArrayBuffer>;\n}\n"],"mappings":";;;;;;;;;AAUA,eAAsB,UAAU,OAA4C;CAC1E,MAAM,SAAS,MAAM,WAAW,OAAO,OAAO,OAC5C,WACA,WAAW,KAAK,CAClB;CACA,OAAO,MAAM,KAAK,IAAI,WAAW,MAAM,CAAC,CAAC,CACtC,KAAK,SAAS,KAAK,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CACjD,KAAK,EAAE;AACZ;;AAGA,SAAS,WAAW,OAA4C;CAC9D,IACE,OAAO,sBAAsB,eAC7B,MAAM,kBAAkB,mBACxB;EACA,MAAM,OAAO,IAAI,WAAW,MAAM,UAAU;EAC5C,KAAK,IAAI,KAAK;EACd,OAAO;CACT;CACA,OAAO;AACT"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"null-attachment-transport-BGG9cRC-.js","names":["isRecord","isRecord","isRecord"],"sources":["../src/targets.ts","../src/attachment-service.ts","../src/switchboard/upload-transport.ts","../src/switchboard/xhr-upload-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 {\n AttachmentDownloadTarget,\n AttachmentTargetHeaders,\n AttachmentUploadTarget,\n} from \"./types.js\";\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\nfunction parseUrl(value: unknown): string {\n if (typeof value !== \"string\" || value.length === 0) {\n throw new Error(\"Attachment target URL must be a non-empty string\");\n }\n if (value.trim() !== value || hasForbiddenUrlWhitespace(value)) {\n throw new Error(\"Attachment target URL must not contain raw whitespace\");\n }\n let parsed: URL;\n try {\n parsed = new URL(value);\n } catch {\n throw new Error(\"Attachment target URL is invalid\");\n }\n if (\n (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") ||\n parsed.username !== \"\" ||\n parsed.password !== \"\"\n ) {\n throw new Error(\n \"Attachment target URL must be HTTP(S) and must not contain credentials\",\n );\n }\n // Return the original string: presigned URLs are opaque and must never be\n // normalized, decoded, reordered, or otherwise reconstructed.\n return value;\n}\n\nfunction parseHeaders(value: unknown): AttachmentTargetHeaders {\n if (!isRecord(value)) {\n throw new Error(\"Attachment target headers must be an object\");\n }\n // A null prototype preserves valid names such as `__proto__` as ordinary\n // own properties instead of invoking Object.prototype setters.\n const headers = Object.create(null) as Record<string, string>;\n const headerName = /^[!#$%&'*+.^_`|~0-9A-Za-z-]+$/;\n for (const [name, headerValue] of Object.entries(value)) {\n if (!headerName.test(name)) {\n throw new Error(`Attachment target header name is invalid: ${name}`);\n }\n if (\n typeof headerValue !== \"string\" ||\n hasForbiddenHeaderCharacter(headerValue)\n ) {\n throw new Error(`Attachment target header value is invalid: ${name}`);\n }\n headers[name] = headerValue;\n }\n return headers;\n}\n\nfunction hasForbiddenHeaderCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index++) {\n const code = value.charCodeAt(index);\n // Horizontal tab is valid field whitespace. Other C0 controls, DEL, and\n // especially CR/LF are forbidden to prevent header injection.\n if (code <= 8 || (code >= 10 && code <= 31) || code === 127 || code > 255) {\n return true;\n }\n }\n return false;\n}\n\nfunction hasForbiddenUrlWhitespace(value: string): boolean {\n for (let index = 0; index < value.length; index++) {\n const code = value.charCodeAt(index);\n // URL parsing may silently trim or percent-encode raw ASCII whitespace.\n // Reject it so the validated opaque string is exactly what Fetch receives.\n if (code <= 32 || code === 127) return true;\n }\n return false;\n}\n\nfunction parseExpiry(value: unknown, required: boolean): string | undefined {\n if (value === undefined && !required) return undefined;\n if (typeof value !== \"string\") {\n throw new Error(\"Attachment target expiry must be an ISO 8601 UTC string\");\n }\n const parsed = new Date(value);\n if (Number.isNaN(parsed.getTime()) || parsed.toISOString() !== value) {\n throw new Error(\"Attachment target expiry must be an ISO 8601 UTC string\");\n }\n return value;\n}\n\nexport function parseAttachmentUploadTarget(\n value: unknown,\n): AttachmentUploadTarget {\n if (!isRecord(value)) {\n throw new Error(\"Attachment upload target must be an object\");\n }\n const url = parseUrl(value.url);\n const headers = parseHeaders(value.headers);\n switch (value.kind) {\n case \"switchboard\":\n if (value.method !== \"PUT\") {\n throw new Error(\"Switchboard upload target method must be PUT\");\n }\n return {\n kind: \"switchboard\",\n method: \"PUT\",\n url,\n headers,\n ...(value.expiresAtUtc === undefined\n ? {}\n : { expiresAtUtc: parseExpiry(value.expiresAtUtc, false) }),\n };\n case \"presigned-put\":\n if (value.method !== \"PUT\") {\n throw new Error(\"Presigned upload target method must be PUT\");\n }\n return {\n kind: \"presigned-put\",\n method: \"PUT\",\n url,\n headers,\n expiresAtUtc: parseExpiry(value.expiresAtUtc, true)!,\n };\n default:\n throw new Error(\"Attachment upload target kind is unknown\");\n }\n}\n\nexport function parseAttachmentDownloadTarget(\n value: unknown,\n): AttachmentDownloadTarget {\n if (!isRecord(value)) {\n throw new Error(\"Attachment download target must be an object\");\n }\n const url = parseUrl(value.url);\n const headers = parseHeaders(value.headers);\n switch (value.kind) {\n case \"switchboard\":\n if (value.method !== \"GET\") {\n throw new Error(\"Switchboard download target method must be GET\");\n }\n return {\n kind: \"switchboard\",\n method: \"GET\",\n url,\n headers,\n ...(value.expiresAtUtc === undefined\n ? {}\n : { expiresAtUtc: parseExpiry(value.expiresAtUtc, false) }),\n };\n case \"presigned-get\":\n if (value.method !== \"GET\") {\n throw new Error(\"Presigned download target method must be GET\");\n }\n return {\n kind: \"presigned-get\",\n method: \"GET\",\n url,\n headers,\n expiresAtUtc: parseExpiry(value.expiresAtUtc, true)!,\n };\n default:\n throw new Error(\"Attachment download target kind is unknown\");\n }\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 IAttachmentBackend,\n IAttachmentService,\n IAttachmentUpload,\n IAttachmentUploadFactory,\n IReservationStore,\n} from \"./interfaces.js\";\nimport { createRef, parseRef } from \"./ref.js\";\nimport type {\n AttachmentDownloadOptions,\n AttachmentDownloadTarget,\n AttachmentDownloadTargetOptions,\n AttachmentHeader,\n AttachmentResponse,\n AttachmentStatOptions,\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 private readonly backend?: IAttachmentBackend,\n ) {}\n\n async reserve(options: ReserveAttachmentOptions): Promise<IAttachmentUpload> {\n if (options.clientHash !== undefined) {\n return this.reserveHashFirst(options);\n }\n if (this.backend?.kind === \"s3\") {\n throw new Error(\"S3 attachment reservations require a client hash\");\n }\n const reservation = await this.reservations.create(options);\n return this.uploadFactory.createUpload(reservation);\n }\n\n async stat(\n ref: AttachmentRef,\n options?: AttachmentStatOptions,\n ): Promise<AttachmentHeader> {\n const { hash } = parseRef(ref);\n return options?.documentId === undefined\n ? this.store.stat(hash)\n : this.store.stat(hash, options.documentId);\n }\n\n async get(\n ref: AttachmentRef,\n options?: AbortSignal | AttachmentDownloadOptions,\n ): Promise<AttachmentResponse> {\n const { hash } = parseRef(ref);\n const normalized =\n options === undefined || options instanceof AbortSignal\n ? { signal: options }\n : options;\n return normalized.documentId === undefined\n ? this.store.get(hash, normalized.signal)\n : this.store.get(hash, normalized.signal, normalized.documentId);\n }\n\n getDownloadTarget(\n ref: AttachmentRef,\n options: AttachmentDownloadTargetOptions,\n ): Promise<AttachmentDownloadTarget> {\n const { hash } = parseRef(ref);\n const getTarget = this.store.getDownloadTarget?.bind(this.store);\n if (getTarget === undefined) {\n return Promise.reject(\n new Error(\n \"Download targets are not supported by this attachment store: only remote stores negotiate direct URLs\",\n ),\n );\n }\n return getTarget(hash, options);\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) {\n if (this.backend?.kind === \"s3\") {\n if (await this.backend.exists(normalized)) {\n throw new AttachmentAlreadyExists(normalized, createRef(normalized));\n }\n } else if (existingHeader.status === \"available\") {\n throw new AttachmentAlreadyExists(normalized, createRef(normalized));\n }\n }\n\n const reservation = await this.reservations.create(normalizedOptions);\n if (this.backend?.kind !== \"s3\") {\n return this.uploadFactory.createUpload(reservation);\n }\n const uploadTarget = await this.backend.prepareUploadTarget(reservation);\n return this.uploadFactory.createUpload({ ...reservation, uploadTarget });\n }\n}\n","/**\n * The seam every attachment upload PUT goes through.\n *\n * It exists because `fetch` cannot report upload-side progress: there is no\n * event between \"request issued\" and \"response received\". Naming the transport\n * lets an implementation that *can* count bytes (see\n * `createXhrUploadTransport`) be substituted without the upload code knowing\n * which one it holds — and it is also the right seam for a future multipart\n * upload, which needs one request per part.\n */\n\n/**\n * Headers are forwarded VERBATIM. A presigned target's headers are part of a\n * signature: normalizing their casing, or adding so much as one header,\n * invalidates it.\n */\nexport type AttachmentUploadRequest = {\n url: string;\n method: \"PUT\";\n headers: Record<string, string>;\n body: Blob;\n /** Called with cumulative bytes handed to the socket, when observable. */\n onProgress?: (loaded: number, total: number) => void;\n signal?: AbortSignal;\n};\n\n/**\n * The only response surface the upload code reads. A real `Response`\n * satisfies this structurally, which is what lets the fetch transport be an\n * identity pass-through rather than an adapter.\n */\nexport type AttachmentUploadResponse = {\n readonly status: number;\n readonly statusText: string;\n readonly ok: boolean;\n json(): Promise<unknown>;\n};\n\nexport type AttachmentUploadTransport = (\n request: AttachmentUploadRequest,\n) => Promise<AttachmentUploadResponse>;\n\n/**\n * The default transport. Ignores `onProgress` — fetch has no upload-side\n * progress event, and silence is how a transport reports that bytes are\n * unobservable.\n */\nexport function createFetchUploadTransport(\n fetchFn: typeof fetch,\n): AttachmentUploadTransport {\n return (request) =>\n fetchFn(request.url, {\n method: request.method,\n headers: request.headers,\n body: request.body,\n ...(request.signal ? { signal: request.signal } : {}),\n });\n}\n","import {\n createFetchUploadTransport,\n type AttachmentUploadRequest,\n type AttachmentUploadResponse,\n type AttachmentUploadTransport,\n} from \"./upload-transport.js\";\n\nexport type XhrUploadTransportOptions = {\n /**\n * Used where `XMLHttpRequest` does not exist (Node). Defaults to the global\n * fetch, resolved at call time.\n */\n fetchFn?: typeof fetch;\n};\n\n/**\n * Upload transport backed by `XMLHttpRequest`, the only browser API that\n * reports upload-side byte progress (`xhr.upload.onprogress`). Everything else\n * about the request is identical to the fetch path.\n *\n * Capability detection happens **inside** the returned function, never at\n * module load: this module is reachable from the client entry, which is\n * executed in Node by the entrypoint tests, and Node has no\n * `XMLHttpRequest`. Detecting per call also means a test can install a fake on\n * `globalThis` without any module-registry reset.\n */\nexport function createXhrUploadTransport(\n options?: XhrUploadTransportOptions,\n): AttachmentUploadTransport {\n return (request) => {\n const XhrCtor = globalThis.XMLHttpRequest;\n if (typeof XhrCtor !== \"function\") {\n const fetchFn = options?.fetchFn ?? globalThis.fetch;\n return createFetchUploadTransport(fetchFn.bind(globalThis))(request);\n }\n return sendWithXhr(new XhrCtor(), request);\n };\n}\n\n/**\n * Mirrors fetch's observable behavior closely enough that\n * `RemoteAttachmentUpload` cannot tell the two apart:\n *\n * - `responseType` is left at `\"\"`, so a non-JSON 422 body still reaches the\n * caller's `json()` and fails there rather than being swallowed.\n * - status 0 never resolves. XHR reports 0 for network failure, CORS refusal\n * and abort alike; resolving it would fabricate a transfer error claiming\n * the provider answered 0.\n * - failures reject with fetch's error shapes: `TypeError` for network,\n * `AbortError` for abort.\n */\nfunction sendWithXhr(\n xhr: XMLHttpRequest,\n request: AttachmentUploadRequest,\n): Promise<AttachmentUploadResponse> {\n return new Promise<AttachmentUploadResponse>((resolve, reject) => {\n const signal = request.signal;\n let settled = false;\n\n const abortListener = () => xhr.abort();\n const detach = () => signal?.removeEventListener(\"abort\", abortListener);\n const succeed = (response: AttachmentUploadResponse) => {\n if (settled) return;\n settled = true;\n detach();\n resolve(response);\n };\n const fail = (error: Error) => {\n if (settled) return;\n settled = true;\n detach();\n reject(error);\n };\n\n if (signal?.aborted) {\n fail(abortError());\n return;\n }\n\n xhr.open(request.method, request.url, true);\n // Verbatim: a presigned target's headers are part of its signature.\n for (const [name, value] of Object.entries(request.headers)) {\n xhr.setRequestHeader(name, value);\n }\n\n // Attached before send(), and only when a caller is listening — merely\n // registering an upload listener forces a CORS preflight.\n if (request.onProgress) {\n xhr.upload.addEventListener(\"progress\", (event: ProgressEvent) => {\n if (!event.lengthComputable) return;\n request.onProgress?.(event.loaded, event.total);\n });\n }\n\n xhr.addEventListener(\"load\", () => {\n if (xhr.status === 0) {\n fail(networkError());\n return;\n }\n succeed(toResponse(xhr));\n });\n xhr.addEventListener(\"error\", () => fail(networkError()));\n xhr.addEventListener(\"timeout\", () => fail(networkError()));\n xhr.addEventListener(\"abort\", () => fail(abortError()));\n\n signal?.addEventListener(\"abort\", abortListener);\n xhr.send(request.body);\n });\n}\n\nfunction toResponse(xhr: XMLHttpRequest): AttachmentUploadResponse {\n return {\n status: xhr.status,\n statusText: xhr.statusText,\n ok: xhr.status >= 200 && xhr.status < 300,\n json: () => {\n try {\n return Promise.resolve(JSON.parse(xhr.responseText));\n } catch (err) {\n return Promise.reject(\n err instanceof Error ? err : new Error(String(err)),\n );\n }\n },\n };\n}\n\nfunction networkError(): TypeError {\n return new TypeError(\"Failed to fetch\");\n}\n\nfunction abortError(): DOMException {\n return new DOMException(\"The operation was aborted\", \"AbortError\");\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 { parseAttachmentUploadTarget } from \"../targets.js\";\nimport { buildAuthHeaders } from \"./build-auth-headers.js\";\nimport type { AttachmentUploadTransport } from \"./upload-transport.js\";\n\nexport type SwitchboardClientConfig = {\n remoteUrl: string;\n jwtHandler?: JwtHandler;\n fetchFn?: typeof fetch;\n /**\n * Transport for upload PUTs only, so a browser host can opt into\n * XMLHttpRequest and get real upload progress.\n *\n * An explicit `fetchFn` always wins over this. The precedence is\n * one-directional on purpose: under this rule, adding `uploadTransport` to a\n * config that already pins `fetchFn` is a provable no-op, whereas the\n * reverse rule would let `{ ...config, uploadTransport }` silently route the\n * largest request in the system around a test's mock and onto the network.\n */\n uploadTransport?: AttachmentUploadTransport;\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 uploadTarget?: unknown;\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 ...(body.uploadTarget === undefined\n ? {}\n : { uploadTarget: parseAttachmentUploadTarget(body.uploadTarget) }),\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 ...(parsed.uploadTarget === undefined\n ? {}\n : {\n uploadTarget: parseAttachmentUploadTarget(parsed.uploadTarget),\n }),\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 {\n AttachmentHash,\n AttachmentRef,\n JwtHandler,\n} from \"@powerhousedao/reactor\";\nimport {\n AttachmentTransferError,\n HashMismatch,\n SizeMismatch,\n} from \"../errors.js\";\nimport type { IAttachmentUpload } from \"../interfaces.js\";\nimport { createRef } from \"../ref.js\";\nimport type {\n AttachmentSendOptions,\n AttachmentUploadResult,\n AttachmentUploadTarget,\n Reservation,\n} from \"../types.js\";\nimport { buildAuthHeaders } from \"./build-auth-headers.js\";\nimport type { SwitchboardClientConfig } from \"./remote-reservation-store.js\";\nimport {\n createFetchUploadTransport,\n type AttachmentUploadTransport,\n} from \"./upload-transport.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 readonly uploadTarget?: AttachmentUploadTarget;\n private readonly reservation: Reservation;\n private readonly remoteUrl: string;\n private readonly jwtHandler?: JwtHandler;\n private readonly uploadTransport: AttachmentUploadTransport;\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 if (reservation.uploadTarget) {\n this.uploadTarget = reservation.uploadTarget;\n }\n this.reservation = reservation;\n this.remoteUrl = config.remoteUrl;\n this.jwtHandler = config.jwtHandler;\n // fetchFn wins: pinning fetch must not be silently overridden.\n this.uploadTransport = config.fetchFn\n ? createFetchUploadTransport(config.fetchFn.bind(globalThis))\n : (config.uploadTransport ??\n createFetchUploadTransport(globalThis.fetch.bind(globalThis)));\n }\n\n async send(\n data: ReadableStream<Uint8Array>,\n options?: AttachmentSendOptions,\n ): Promise<AttachmentUploadResult> {\n // Presigned targets bypass Switchboard entirely: bytes go straight to the\n // provider with the exact signed headers. Switchboard targets (and the\n // legacy no-target wire) keep the authenticated reservation PUT below.\n if (this.uploadTarget?.kind === \"presigned-put\") {\n return this.sendPresigned(this.uploadTarget, data, options);\n }\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.uploadTransport({\n url,\n method: \"PUT\",\n headers: { ...authHeaders, \"Content-Type\": \"application/octet-stream\" },\n body,\n ...(options?.onProgress\n ? {\n onProgress: (loaded: number, total: number) =>\n options.onProgress?.(loaded, total),\n }\n : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\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 /**\n * Direct provider upload: PUT the bytes to the presigned URL with exactly\n * the returned headers — never the Switchboard JWT — and treat any 2xx as\n * final success with no follow-up control request. The result is\n * synthesized from the hash-first reservation, which is the only path that\n * can produce a presigned target.\n */\n private async sendPresigned(\n target: AttachmentUploadTarget,\n data: ReadableStream<Uint8Array>,\n options?: AttachmentSendOptions,\n ): Promise<AttachmentUploadResult> {\n if (this.reservation.clientHash === null || this.ref === null) {\n throw new Error(\n \"Presigned upload targets require a hash-first reservation\",\n );\n }\n\n // Buffer for the same browser-compatibility reasons as the proxy path.\n const body = await new Response(data).blob();\n const response = await this.uploadTransport({\n url: target.url,\n method: target.method,\n headers: { ...target.headers },\n body,\n ...(options?.onProgress\n ? {\n onProgress: (loaded: number, total: number) =>\n options.onProgress?.(loaded, total),\n }\n : {}),\n ...(options?.signal ? { signal: options.signal } : {}),\n });\n if (!response.ok) {\n throw new AttachmentTransferError(\"presigned-put\", response.status);\n }\n\n const hash = this.reservation.clientHash as AttachmentHash;\n const now = new Date().toISOString();\n return {\n hash,\n ref: this.ref,\n header: {\n hash,\n mimeType: this.reservation.mimeType,\n fileName: this.reservation.fileName,\n sizeBytes: this.reservation.sizeBytes ?? body.size,\n extension: this.reservation.extension,\n status: \"available\",\n source: \"local\",\n createdAtUtc: this.reservation.createdAtUtc || now,\n lastAccessedAtUtc: now,\n expiresAtUtc: null,\n },\n };\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 {\n AttachmentNotFound,\n AttachmentPending,\n AttachmentTransferError,\n} from \"../errors.js\";\nimport type { IAttachmentReader } from \"../interfaces.js\";\nimport { parseAttachmentDownloadTarget } from \"../targets.js\";\nimport type {\n AttachmentDownloadTarget,\n AttachmentDownloadTargetOptions,\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\nconst MISSING_DOCUMENT_ID =\n \"Remote attachment reads require the documentId whose operations reference the attachment\";\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(\n hash: AttachmentHash,\n documentId?: string,\n ): Promise<AttachmentHeader> {\n // The server answers a hash-only HEAD 404; hash-first reserve relies on\n // that to leave the dedup decision to the server's 409.\n if (documentId === undefined) {\n throw new AttachmentNotFound(hash);\n }\n const url = `${this.remoteUrl}/attachments/${hash}?documentId=${encodeURIComponent(documentId)}`;\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 documentId?: string,\n ): Promise<AttachmentResponse> {\n if (documentId === undefined) {\n throw new Error(MISSING_DOCUMENT_ID);\n }\n const target = await this.getDownloadTarget(hash, { documentId, signal });\n if (target.kind === \"presigned-get\") {\n return this.fetchPresigned(hash, target, signal);\n }\n return this.fetchAttachment(hash, target, signal);\n }\n\n /**\n * Asks Switchboard for an authorized download target. The request carries\n * the JWT; the response is runtime-validated before any byte transfer.\n * Public so callers can mint direct URLs (previews, share links) without\n * transferring bytes; `expiresIn` requests a caller-chosen lifetime.\n */\n async getDownloadTarget(\n hash: AttachmentHash,\n options: AttachmentDownloadTargetOptions,\n ): Promise<AttachmentDownloadTarget> {\n const { documentId, expiresIn, signal } = options;\n const expiry =\n expiresIn === undefined\n ? \"\"\n : `&expiresIn=${encodeURIComponent(String(expiresIn))}`;\n const url = `${this.remoteUrl}/attachments/${hash}/download-target?documentId=${encodeURIComponent(documentId)}${expiry}`;\n const headers = await buildAuthHeaders(url, this.jwtHandler);\n const response = await this.fetchFn(url, { signal, headers });\n\n if (response.status === 404) {\n throw new AttachmentNotFound(hash);\n }\n if (!response.ok) {\n throw new AttachmentTransferError(\"download-target\", response.status);\n }\n\n let body: unknown;\n try {\n body = await response.json();\n } catch {\n throw new AttachmentTransferError(\"download-target\");\n }\n return parseAttachmentDownloadTarget(body);\n }\n\n /**\n * Executes a presigned GET with exactly the returned headers and no JWT.\n * A provider 404 means the object is missing despite available metadata\n * (the accepted abandoned-upload trade-off) and surfaces as the same typed\n * not-found error callers already handle.\n */\n private async fetchPresigned(\n hash: AttachmentHash,\n target: AttachmentDownloadTarget,\n signal?: AbortSignal,\n ): Promise<AttachmentResponse> {\n const response = await this.fetchFn(target.url, {\n signal,\n headers: { ...target.headers },\n });\n if (response.status === 404) {\n throw new AttachmentNotFound(hash);\n }\n if (!response.ok) {\n throw new AttachmentTransferError(\"presigned-get\", response.status);\n }\n if (!response.body) {\n throw new Error(\"Response body is null\");\n }\n const metadata = parseMetadata(response);\n return { header: buildHeader(hash, metadata), body: response.body };\n }\n\n private async fetchAttachment(\n hash: AttachmentHash,\n target: AttachmentDownloadTarget,\n signal?: AbortSignal,\n ): Promise<AttachmentResponse> {\n const url = target.url;\n const authHeaders = await buildAuthHeaders(url, this.jwtHandler);\n const headers = { ...target.headers, ...authHeaders };\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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"}