@powerhousedao/reactor-attachments 6.2.3-dev.4 → 6.2.3-dev.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { C as UploadTooLarge, S as SizeMismatch, _ as AttachmentPending, a as RemoteAttachmentUpload, b as InvalidAttachmentRef, c as createFetchUploadTransport, d as createRef, f as parseRef, g as AttachmentNotFound, h as AttachmentAlreadyExists, i as RemoteAttachmentUploadFactory, l as SwitchboardAttachmentTransport, m as parseAttachmentUploadTarget, n as createRemoteAttachmentService, o as RemoteReservationStore, p as parseAttachmentDownloadTarget, r as RemoteAttachmentStore, s as createXhrUploadTransport, t as NullAttachmentTransport, u as AttachmentService, v as AttachmentTransferError, x as ReservationNotFound, y as HashMismatch } from "./null-attachment-transport-CrsUafCi.js";
1
+ import { C as SizeMismatch, S as ReservationNotFound, _ as AttachmentPending, a as RemoteAttachmentUpload, b as InvalidAttachmentMetadata, c as createFetchUploadTransport, d as createRef, f as parseRef, g as AttachmentNotFound, h as AttachmentAlreadyExists, i as RemoteAttachmentUploadFactory, l as SwitchboardAttachmentTransport, m as parseAttachmentUploadTarget, n as createRemoteAttachmentService, o as RemoteReservationStore, p as parseAttachmentDownloadTarget, r as RemoteAttachmentStore, s as createXhrUploadTransport, t as NullAttachmentTransport, u as AttachmentService, v as AttachmentTransferError, w as UploadTooLarge, x as InvalidAttachmentRef, y as HashMismatch } from "./null-attachment-transport-CUDNBfd5.js";
2
2
  import { dirname, join } from "node:path";
3
3
  import { Migrator, sql } from "kysely";
4
4
  import { mkdir, rename, rm } from "node:fs/promises";
@@ -9,7 +9,8 @@ import { finished } from "node:stream/promises";
9
9
  import { generatorTypeDefs } from "@powerhousedao/document-engineering/graphql";
10
10
  import { constantCase, pascalCase } from "change-case";
11
11
  import { Kind, buildASTSchema, getNamedType, isInputObjectType, isListType, isNonNullType, parse, print } from "graphql";
12
- import { BaseReadModel } from "@powerhousedao/reactor";
12
+ import { BaseReadModel, defaultReadModelIndexingConfig } from "@powerhousedao/reactor";
13
+ import { isPurgeMarker } from "@powerhousedao/shared/document-model";
13
14
  import { Buffer as Buffer$1 } from "node:buffer";
14
15
  import { GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
15
16
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
@@ -25,6 +26,27 @@ var __exportAll = (all, no_symbols) => {
25
26
  return target;
26
27
  };
27
28
  //#endregion
29
+ //#region src/reserve-metadata.ts
30
+ const CONTROL_CHARS = /[\x00-\x1f\x7f]/;
31
+ const NON_HEADER_CHARS = /[^\t\x20-\x7e\x80-\xff]/;
32
+ const MIME_TYPE_PATTERN = /^[!#$%&'*+\-.^_`|~\w]+\/[!#$%&'*+\-.^_`|~\w]+(?:[ \t]*;[ \t]*[!#$%&'*+\-.^_`|~\w]+=(?:[!#$%&'*+\-.^_`|~\w]+|"(?:[^"\\\r\n]|\\[^\r\n])*"))*$/;
33
+ const MAX_FILENAME_LEN = 255;
34
+ const MAX_MIMETYPE_LEN = 255;
35
+ /**
36
+ * Validates reserve metadata with the rules the switchboard reserve route
37
+ * applies.
38
+ *
39
+ * @throws InvalidAttachmentMetadata naming the first field that fails.
40
+ */
41
+ function validateReserveMetadata(meta) {
42
+ const { mimeType, fileName, extension } = meta;
43
+ if (typeof mimeType !== "string" || mimeType.length === 0 || mimeType.length > MAX_MIMETYPE_LEN || NON_HEADER_CHARS.test(mimeType) || !MIME_TYPE_PATTERN.test(mimeType)) throw new InvalidAttachmentMetadata("mimeType");
44
+ if (typeof fileName !== "string" || fileName.length === 0 || fileName.length > MAX_FILENAME_LEN || CONTROL_CHARS.test(fileName)) throw new InvalidAttachmentMetadata("fileName");
45
+ if (typeof extension === "string") {
46
+ if (extension.length === 0 || /[\\/]/.test(extension)) throw new InvalidAttachmentMetadata("extension");
47
+ } else if (extension !== void 0 && extension !== null) throw new InvalidAttachmentMetadata("extension");
48
+ }
49
+ //#endregion
28
50
  //#region src/storage/fs/attachment-fs.ts
29
51
  /**
30
52
  * Compute the relative storage path for an attachment hash.
@@ -262,6 +284,9 @@ function wrapStreamWithCleanup(source, cleanup) {
262
284
  });
263
285
  }
264
286
  var KyselyAttachmentStore = class {
287
+ db;
288
+ transport;
289
+ basePath;
265
290
  activeReaders = /* @__PURE__ */ new Map();
266
291
  constructor(db, transport, basePath) {
267
292
  this.db = db;
@@ -290,18 +315,10 @@ var KyselyAttachmentStore = class {
290
315
  async has(hash) {
291
316
  return (await this.db.selectFrom("attachment").select("status").where("hash", "=", hash).executeTakeFirst())?.status === "available";
292
317
  }
293
- async get(hash, signal) {
318
+ async get(hash, signal, documentId) {
294
319
  const row = await this.db.selectFrom("attachment").selectAll().where("hash", "=", hash).executeTakeFirst();
295
320
  if (row) {
296
- if (row.status === "evicted") {
297
- const remote = await this.transport.fetch(hash, signal);
298
- if (remote.kind === "data") {
299
- await this.put(hash, remote.response.metadata, remote.response.body);
300
- return this.get(hash, signal);
301
- }
302
- if (remote.kind === "pending") throw new AttachmentPending(hash, remote.expiresAtUtc);
303
- throw new AttachmentNotFound(hash);
304
- }
321
+ if (row.status === "evicted") return this.fetchRemote(hash, signal, documentId);
305
322
  const now = (/* @__PURE__ */ new Date()).toISOString();
306
323
  await this.db.updateTable("attachment").set({ last_accessed_at_utc: now }).where("hash", "=", hash).execute();
307
324
  const header = rowToHeader$1(row);
@@ -319,13 +336,7 @@ var KyselyAttachmentStore = class {
319
336
  fileName: pending.file_name,
320
337
  sizeBytes: pending.size_bytes
321
338
  });
322
- const remote = await this.transport.fetch(hash, signal);
323
- if (remote.kind === "data") {
324
- await this.put(hash, remote.response.metadata, remote.response.body);
325
- return this.get(hash, signal);
326
- }
327
- if (remote.kind === "pending") throw new AttachmentPending(hash, remote.expiresAtUtc);
328
- throw new AttachmentNotFound(hash);
339
+ return this.fetchRemote(hash, signal, documentId);
329
340
  }
330
341
  async put(hash, metadata, data) {
331
342
  const existing = await this.db.selectFrom("attachment").select(["hash", "status"]).where("hash", "=", hash).executeTakeFirst();
@@ -365,6 +376,16 @@ var KyselyAttachmentStore = class {
365
376
  const result = await this.db.selectFrom("attachment").select(sql`COALESCE(SUM(size_bytes), 0)`.as("total")).where("status", "=", "available").executeTakeFirst();
366
377
  return Number(result?.total ?? 0);
367
378
  }
379
+ async fetchRemote(hash, signal, documentId) {
380
+ if (documentId === void 0) throw new AttachmentNotFound(hash);
381
+ const remote = await this.transport.fetch(hash, documentId, signal);
382
+ if (remote.kind === "data") {
383
+ await this.put(hash, remote.response.metadata, remote.response.body);
384
+ return this.get(hash, signal);
385
+ }
386
+ if (remote.kind === "pending") throw new AttachmentPending(hash, remote.expiresAtUtc);
387
+ throw new AttachmentNotFound(hash);
388
+ }
368
389
  async findPendingReservation(hash, now) {
369
390
  const row = await this.db.selectFrom("attachment_reservation as r").leftJoin("attachment as a", "a.hash", "r.client_hash").select([
370
391
  "r.mime_type",
@@ -398,7 +419,7 @@ var KyselyAttachmentStore = class {
398
419
  };
399
420
  //#endregion
400
421
  //#region src/storage/kysely/reservation-store.ts
401
- const DEFAULT_RESERVATION_TTL_MS = 1440 * 60 * 1e3;
422
+ const DEFAULT_RESERVATION_TTL_MS = 864e5;
402
423
  function rowToReservation(row) {
403
424
  return {
404
425
  reservationId: row.reservation_id,
@@ -412,6 +433,7 @@ function rowToReservation(row) {
412
433
  };
413
434
  }
414
435
  var KyselyReservationStore = class {
436
+ db;
415
437
  ttlMs;
416
438
  constructor(db, ttlMs = DEFAULT_RESERVATION_TTL_MS) {
417
439
  this.db = db;
@@ -601,6 +623,11 @@ function rowToHeader(row) {
601
623
  };
602
624
  }
603
625
  var DirectAttachmentUpload = class {
626
+ reservation;
627
+ db;
628
+ basePath;
629
+ reservations;
630
+ maxBytes;
604
631
  reservationId;
605
632
  ref;
606
633
  expiresAtUtc;
@@ -670,6 +697,10 @@ var DirectAttachmentUpload = class {
670
697
  //#endregion
671
698
  //#region src/direct/direct-attachment-upload-factory.ts
672
699
  var DirectAttachmentUploadFactory = class {
700
+ db;
701
+ basePath;
702
+ reservations;
703
+ maxBytes;
673
704
  constructor(db, basePath, reservations, maxBytes) {
674
705
  this.db = db;
675
706
  this.basePath = basePath;
@@ -688,6 +719,8 @@ var DirectAttachmentUploadFactory = class {
688
719
  * can never accidentally return a direct-provider target.
689
720
  */
690
721
  var FilesystemAttachmentBackend = class {
722
+ store;
723
+ config;
691
724
  kind = "filesystem";
692
725
  constructor(store, config) {
693
726
  this.store = store;
@@ -739,6 +772,8 @@ var S3AttachmentUploadFactory = class {
739
772
  //#endregion
740
773
  //#region src/attachment-builder.ts
741
774
  var AttachmentBuilder = class {
775
+ db;
776
+ storagePath;
742
777
  transport = new NullAttachmentTransport();
743
778
  customUploadFactory;
744
779
  maxUploadBytes;
@@ -811,7 +846,7 @@ var AttachmentBuilder = class {
811
846
  //#endregion
812
847
  //#region src/reference-index/attachment-schema-compiler.ts
813
848
  const ATTACHMENT_REF_TYPE = "AttachmentRef";
814
- const CODEGEN_SCALAR_NAMES = new Set([
849
+ const CODEGEN_SCALAR_NAMES = /* @__PURE__ */ new Set([
815
850
  "Unknown",
816
851
  "DateTime",
817
852
  "Address",
@@ -859,11 +894,12 @@ function buildEffectiveSchema(specification, context) {
859
894
  const stateSchemas = Object.values(specification.state).map((state) => state.schema);
860
895
  const operationSchemas = specification.modules.flatMap((moduleSpecification) => moduleSpecification.operations.flatMap((operation) => operation.schema === null ? [] : [operation.schema]));
861
896
  try {
862
- return buildASTSchema(dedupeTypeDefinitions(parse([
897
+ const document = parse([
863
898
  ...scalarSchemas,
864
899
  ...stateSchemas,
865
900
  ...operationSchemas
866
- ].filter(Boolean).join("\n\n"))));
901
+ ].filter(Boolean).join("\n\n"));
902
+ return buildASTSchema(dedupeTypeDefinitions(document));
867
903
  } catch {
868
904
  throw compilationError(context, "the effective GraphQL schema is invalid");
869
905
  }
@@ -1001,6 +1037,8 @@ function extractValue(plan, value, path, context, refs, seenRefs, activeObjects)
1001
1037
  }
1002
1038
  }
1003
1039
  var SchemaCompiledAttachmentExtractor = class {
1040
+ context;
1041
+ rootPlan;
1004
1042
  constructor(context, rootPlan) {
1005
1043
  this.context = context;
1006
1044
  this.rootPlan = rootPlan;
@@ -1063,12 +1101,16 @@ var AttachmentSchemaCompiler = class {
1063
1101
  //#endregion
1064
1102
  //#region src/read-models/attachment-reference/kysely-attachment-reference-store.ts
1065
1103
  var KyselyAttachmentReferenceStore = class {
1104
+ db;
1066
1105
  constructor(db) {
1067
1106
  this.db = db;
1068
1107
  }
1069
1108
  async hasReference(documentId, ref) {
1070
1109
  return await this.db.selectFrom("attachment_reference").select("document_id").where("document_id", "=", documentId).where("attachment_ref", "=", ref).executeTakeFirst() !== void 0;
1071
1110
  }
1111
+ async referencingScopes(documentId, ref) {
1112
+ return (await this.db.selectFrom("attachment_reference").select("scope").distinct().where("document_id", "=", documentId).where("attachment_ref", "=", ref).execute()).map((row) => row.scope);
1113
+ }
1072
1114
  async addReferences(references) {
1073
1115
  if (references.length === 0) return;
1074
1116
  await this.db.insertInto("attachment_reference").values(references.map((reference) => ({
@@ -1082,6 +1124,10 @@ var KyselyAttachmentReferenceStore = class {
1082
1124
  created_at_utc: (/* @__PURE__ */ new Date()).toISOString()
1083
1125
  }))).onConflict((oc) => oc.columns(["document_id", "attachment_ref"]).doNothing()).execute();
1084
1126
  }
1127
+ async removeDocuments(documentIds) {
1128
+ if (documentIds.length === 0) return;
1129
+ await this.db.deleteFrom("attachment_reference").where("document_id", "in", [...documentIds]).execute();
1130
+ }
1085
1131
  };
1086
1132
  //#endregion
1087
1133
  //#region src/read-models/attachment-reference/storage/migrations/001_create_attachment_reference_table.ts
@@ -1160,6 +1206,7 @@ async function getAttachmentReferenceMigrationStatus(db, schema = ATTACHMENT_REF
1160
1206
  //#endregion
1161
1207
  //#region src/read-models/attachment-reference/index-builder.ts
1162
1208
  var AttachmentReferenceIndexBuilder = class {
1209
+ db;
1163
1210
  constructor(db) {
1164
1211
  this.db = db;
1165
1212
  }
@@ -1172,52 +1219,40 @@ var AttachmentReferenceIndexBuilder = class {
1172
1219
  //#endregion
1173
1220
  //#region src/read-models/attachment-reference/attachment-reference-read-model.ts
1174
1221
  const ATTACHMENT_REFERENCE_READ_MODEL_ID = "attachment-reference-read-model";
1222
+ /**
1223
+ * Indexes attachment references in chunks.
1224
+ *
1225
+ * Each reference row stands on its own and is written insert-or-do-nothing, so
1226
+ * a batch that commits in pieces exposes no partial structure, and re-indexing
1227
+ * the same range writes nothing new: the catch-up sweep needs no stream suffix.
1228
+ *
1229
+ * The rows live on another handle, so the purge fence is "skip": a tombstoned
1230
+ * id's operations are dropped at commit and its marker deletes its rows.
1231
+ */
1175
1232
  var AttachmentReferenceReadModel = class extends BaseReadModel {
1176
- indexingQueue = Promise.resolve();
1177
- checkpointTarget;
1178
- /**
1179
- * How far a replay may skip ahead of the cursor: the highest ordinal already
1180
- * pulled from the index while the cursor was parked.
1181
- *
1182
- * NOT a claim that every ordinal below it committed -- the parked ordinal is
1183
- * precisely the one that did not -- so it is a hint, and {@link replayFrom}
1184
- * re-probes the gap before honouring it.
1185
- */
1186
- replayedThrough;
1187
- warnedCheckpoint;
1233
+ documentModelRegistry;
1234
+ schemaCompiler;
1235
+ referenceWriter;
1188
1236
  constructor(db, operationIndex, writeCache, consistencyTracker, documentModelRegistry, schemaCompiler, referenceWriter) {
1189
1237
  super(db, operationIndex, writeCache, consistencyTracker, {
1190
1238
  readModelId: ATTACHMENT_REFERENCE_READ_MODEL_ID,
1191
- rebuildStateOnInit: false
1239
+ rebuildStateOnInit: false,
1240
+ indexing: defaultReadModelIndexingConfig,
1241
+ replayStreamSuffix: false,
1242
+ purgeFence: "skip"
1192
1243
  });
1193
1244
  this.documentModelRegistry = documentModelRegistry;
1194
1245
  this.schemaCompiler = schemaCompiler;
1195
1246
  this.referenceWriter = referenceWriter;
1196
1247
  }
1197
- indexOperations(items) {
1198
- return this.enqueue(() => this.indexOperationsInOrdinalOrder(items));
1199
- }
1200
- init() {
1201
- return this.enqueue(async () => {
1202
- const viewState = await this.loadState();
1203
- if (viewState !== void 0) this.lastOrdinal = viewState;
1204
- else await this.initializeState();
1205
- let page = await this.operationIndex.getSinceOrdinal(this.lastOrdinal);
1206
- while (page.results.length > 0) {
1207
- await this.indexOperationsInOrdinalOrder(page.results);
1208
- if (!page.next) break;
1209
- page = await page.next();
1210
- }
1211
- });
1212
- }
1213
- enqueue(work) {
1214
- const result = this.indexingQueue.then(work);
1215
- this.indexingQueue = result.catch(() => void 0);
1216
- return result;
1217
- }
1218
1248
  async commitOperations(items) {
1219
1249
  const references = [];
1250
+ const purged = /* @__PURE__ */ new Set();
1220
1251
  for (const { operation, context } of items) {
1252
+ if (isPurgeMarker(operation)) {
1253
+ purged.add(context.documentId);
1254
+ continue;
1255
+ }
1221
1256
  if (operation.error !== void 0) continue;
1222
1257
  const module = this.documentModelRegistry.getModule(context.documentType);
1223
1258
  const refs = this.schemaCompiler.forModuleAction(module, operation.action.type).extract(operation.action);
@@ -1231,102 +1266,7 @@ var AttachmentReferenceReadModel = class extends BaseReadModel {
1231
1266
  });
1232
1267
  }
1233
1268
  if (references.length > 0) await this.referenceWriter.addReferences(references);
1234
- }
1235
- async indexOperationsInOrdinalOrder(incoming) {
1236
- let candidates = this.sortAndDedupe(incoming);
1237
- if (candidates.length === 0) return;
1238
- const incomingMax = candidates[candidates.length - 1].context.ordinal;
1239
- if (this.contiguousEnd(candidates) < incomingMax) {
1240
- const replayed = await this.loadThroughOrdinal(incomingMax);
1241
- candidates = this.sortAndDedupe([...replayed, ...candidates]);
1242
- }
1243
- const checkpoint = this.contiguousEnd(candidates);
1244
- if (checkpoint < incomingMax && checkpoint !== this.warnedCheckpoint) {
1245
- this.warnedCheckpoint = checkpoint;
1246
- console.warn(`[${this.config.readModelId}] indexed through ordinal ${incomingMax} but parked the cursor at ${checkpoint}: ordinal ${checkpoint + 1} is missing`);
1247
- }
1248
- const previousOrdinal = this.lastOrdinal;
1249
- this.checkpointTarget = checkpoint;
1250
- try {
1251
- await super.indexOperations(candidates);
1252
- } catch (error) {
1253
- this.lastOrdinal = previousOrdinal;
1254
- this.replayedThrough = void 0;
1255
- throw error;
1256
- } finally {
1257
- this.checkpointTarget = void 0;
1258
- }
1259
- if (checkpoint > previousOrdinal) this.replayedThrough = void 0;
1260
- else if (checkpoint < incomingMax) this.replayedThrough = Math.max(this.replayedThrough ?? checkpoint, incomingMax);
1261
- }
1262
- /**
1263
- * Writes the cursor parked by indexOperationsInOrdinalOrder instead of the
1264
- * batch maximum, so indexing an operation above a gap never advances past it.
1265
- */
1266
- async saveState(trx, items) {
1267
- const target = this.checkpointTarget;
1268
- if (target === void 0) {
1269
- await super.saveState(trx, items);
1270
- return;
1271
- }
1272
- this.lastOrdinal = target;
1273
- await trx.updateTable("ViewState").set({
1274
- lastOrdinal: target,
1275
- lastOperationTimestamp: /* @__PURE__ */ new Date()
1276
- }).where("readModelId", "=", this.config.readModelId).execute();
1277
- }
1278
- /** Last ordinal of the contiguous run starting at lastOrdinal + 1. */
1279
- contiguousEnd(items) {
1280
- let expectedOrdinal = this.lastOrdinal + 1;
1281
- for (const item of items) {
1282
- const ordinal = item.context.ordinal;
1283
- if (ordinal < expectedOrdinal) continue;
1284
- if (ordinal > expectedOrdinal) break;
1285
- expectedOrdinal++;
1286
- }
1287
- return expectedOrdinal - 1;
1288
- }
1289
- /**
1290
- * Opens a replay at the lowest ordinal still worth reading.
1291
- *
1292
- * {@link replayedThrough} is what keeps a permanently held hole -- a
1293
- * rolled-back insert, which never fills -- from re-reading the whole tail on
1294
- * every batch. It cannot be trusted on its own: a hole that fills without
1295
- * being delivered here is visible only in the index, and a mark that is
1296
- * never questioned would hide that operation for the life of the process.
1297
- * That happens whenever another writer commits the gap (a second reactor on
1298
- * the same database), and it leaves the cursor parked below an operation
1299
- * whose references were never written -- `hasReference` then answers false
1300
- * for an attachment that is genuinely referenced.
1301
- *
1302
- * So a parked batch spends one page probing the gap: the first row above the
1303
- * cursor is the missing ordinal itself once it commits. Finding it drops the
1304
- * mark and replays from the cursor; not finding it leaves the mark standing,
1305
- * which is the cheap path and the common one.
1306
- */
1307
- async replayFrom() {
1308
- const fromCursor = await this.operationIndex.getSinceOrdinal(this.lastOrdinal);
1309
- if (this.replayedThrough === void 0) return fromCursor;
1310
- if (fromCursor.results[0]?.context.ordinal === this.lastOrdinal + 1) {
1311
- this.replayedThrough = void 0;
1312
- return fromCursor;
1313
- }
1314
- return this.operationIndex.getSinceOrdinal(this.replayedThrough);
1315
- }
1316
- async loadThroughOrdinal(maxOrdinal) {
1317
- const operations = [];
1318
- let page = await this.replayFrom();
1319
- for (;;) {
1320
- for (const item of page.results) if (item.context.ordinal <= maxOrdinal) operations.push(item);
1321
- if (page.results.some(({ context }) => context.ordinal >= maxOrdinal) || !page.next) break;
1322
- page = await page.next();
1323
- }
1324
- return operations;
1325
- }
1326
- sortAndDedupe(items) {
1327
- const byOrdinal = /* @__PURE__ */ new Map();
1328
- for (const item of items) byOrdinal.set(item.context.ordinal, item);
1329
- return [...byOrdinal.values()].sort((left, right) => left.context.ordinal - right.context.ordinal);
1269
+ if (purged.size > 0) await this.referenceWriter.removeDocuments([...purged]);
1330
1270
  }
1331
1271
  };
1332
1272
  //#endregion
@@ -1341,7 +1281,7 @@ function required(env, name) {
1341
1281
  if (value.trim() !== value) throw new Error(`${name} must not have leading or trailing whitespace`);
1342
1282
  return value;
1343
1283
  }
1344
- const LOOPBACK_HOSTNAMES = new Set([
1284
+ const LOOPBACK_HOSTNAMES = /* @__PURE__ */ new Set([
1345
1285
  "127.0.0.1",
1346
1286
  "localhost",
1347
1287
  "[::1]"
@@ -1423,9 +1363,10 @@ function sha256HexToBase64(hash) {
1423
1363
  //#region src/storage/s3/primitives.ts
1424
1364
  const defaultPresigner = (client, command, expiresInSeconds) => getSignedUrl(client, command, {
1425
1365
  expiresIn: expiresInSeconds,
1426
- unhoistableHeaders: new Set(["x-amz-checksum-sha256"])
1366
+ unhoistableHeaders: /* @__PURE__ */ new Set(["x-amz-checksum-sha256"])
1427
1367
  });
1428
1368
  var S3AttachmentPrimitives = class {
1369
+ config;
1429
1370
  client;
1430
1371
  presign;
1431
1372
  now;
@@ -1496,7 +1437,7 @@ function createS3AttachmentPrimitives(config, dependencies = {}) {
1496
1437
  //#endregion
1497
1438
  //#region src/storage/s3/backend.ts
1498
1439
  const READINESS_PROBE_HASH = "0".repeat(64);
1499
- const OBJECT_NOT_FOUND_NAMES = new Set([
1440
+ const OBJECT_NOT_FOUND_NAMES = /* @__PURE__ */ new Set([
1500
1441
  "NotFound",
1501
1442
  "NoSuchKey",
1502
1443
  "NoSuchObject"
@@ -1514,6 +1455,8 @@ function requireHashFirstReservation(reservation) {
1514
1455
  }
1515
1456
  /** Server-only S3 capability. Callers complete authorization before download. */
1516
1457
  var S3AttachmentBackend = class {
1458
+ db;
1459
+ config;
1517
1460
  kind = "s3";
1518
1461
  primitives;
1519
1462
  now;
@@ -1600,6 +1543,6 @@ function createS3AttachmentBackend(db, config, dependencies = {}) {
1600
1543
  return new S3AttachmentBackend(db, config, dependencies);
1601
1544
  }
1602
1545
  //#endregion
1603
- export { ATTACHMENT_REFERENCE_MIGRATION_LOCK_TABLE, ATTACHMENT_REFERENCE_MIGRATION_TABLE, ATTACHMENT_REFERENCE_READ_MODEL_ID, ATTACHMENT_REFERENCE_SCHEMA, ATTACHMENT_SCHEMA, AttachmentAlreadyExists, AttachmentBuilder, AttachmentNotFound, AttachmentPending, AttachmentReferenceIndexBuilder, AttachmentReferenceReadModel, AttachmentSchemaCompiler, AttachmentService, AttachmentTransferError, DEFAULT_RESERVATION_TTL_MS, DEFAULT_S3_ATTACHMENT_PREFIX, DEFAULT_S3_DOWNLOAD_TTL_SECONDS, DEFAULT_S3_UPLOAD_TTL_SECONDS, DirectAttachmentUpload, DirectAttachmentUploadFactory, FilesystemAttachmentBackend, HashMismatch, InvalidAttachmentRef, KyselyAttachmentReferenceStore, KyselyAttachmentStore, KyselyReservationStore, MAX_S3_PRESIGN_TTL_SECONDS, NullAttachmentTransport, RemoteAttachmentStore, RemoteAttachmentUpload, RemoteAttachmentUploadFactory, RemoteReservationStore, ReservationNotFound, S3AttachmentBackend, S3AttachmentPrimitives, S3AttachmentUploadFactory, SizeMismatch, SwitchboardAttachmentTransport, UploadTooLarge, createFetchUploadTransport, createRef, createRemoteAttachmentService, createS3AttachmentBackend, createS3AttachmentPrimitives, createXhrUploadTransport, deriveS3AttachmentKey, getAttachmentReferenceMigrationStatus, normalizeS3AttachmentPrefix, parseAttachmentDownloadTarget, parseAttachmentStorageConfig, parseAttachmentUploadTarget, parseRef, rollbackAttachmentReferenceMigration, runAttachmentMigrations, runAttachmentReferenceMigrations, sha256HexToBase64 };
1546
+ export { ATTACHMENT_REFERENCE_MIGRATION_LOCK_TABLE, ATTACHMENT_REFERENCE_MIGRATION_TABLE, ATTACHMENT_REFERENCE_READ_MODEL_ID, ATTACHMENT_REFERENCE_SCHEMA, ATTACHMENT_SCHEMA, AttachmentAlreadyExists, AttachmentBuilder, AttachmentNotFound, AttachmentPending, AttachmentReferenceIndexBuilder, AttachmentReferenceReadModel, AttachmentSchemaCompiler, AttachmentService, AttachmentTransferError, DEFAULT_RESERVATION_TTL_MS, DEFAULT_S3_ATTACHMENT_PREFIX, DEFAULT_S3_DOWNLOAD_TTL_SECONDS, DEFAULT_S3_UPLOAD_TTL_SECONDS, DirectAttachmentUpload, DirectAttachmentUploadFactory, FilesystemAttachmentBackend, HashMismatch, InvalidAttachmentMetadata, InvalidAttachmentRef, KyselyAttachmentReferenceStore, KyselyAttachmentStore, KyselyReservationStore, MAX_S3_PRESIGN_TTL_SECONDS, NullAttachmentTransport, RemoteAttachmentStore, RemoteAttachmentUpload, RemoteAttachmentUploadFactory, RemoteReservationStore, ReservationNotFound, S3AttachmentBackend, S3AttachmentPrimitives, S3AttachmentUploadFactory, SizeMismatch, SwitchboardAttachmentTransport, UploadTooLarge, createFetchUploadTransport, createRef, createRemoteAttachmentService, createS3AttachmentBackend, createS3AttachmentPrimitives, createXhrUploadTransport, deriveS3AttachmentKey, getAttachmentReferenceMigrationStatus, normalizeS3AttachmentPrefix, parseAttachmentDownloadTarget, parseAttachmentStorageConfig, parseAttachmentUploadTarget, parseRef, rollbackAttachmentReferenceMigration, runAttachmentMigrations, runAttachmentReferenceMigrations, sha256HexToBase64, validateReserveMetadata };
1604
1547
 
1605
1548
  //# sourceMappingURL=index.js.map