@powerhousedao/reactor-attachments 6.2.3-dev.4 → 6.2.3-dev.41
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/client.d.ts +51 -36
- package/dist/client.d.ts.map +1 -1
- package/dist/client.js +20 -4
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts +74 -85
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +100 -157
- package/dist/index.js.map +1 -1
- package/dist/{null-attachment-transport-CrsUafCi.js → null-attachment-transport-CUDNBfd5.js} +33 -14
- package/dist/null-attachment-transport-CUDNBfd5.js.map +1 -0
- package/dist/{null-attachment-transport-CMrO_ZKA.d.ts → null-attachment-transport-jZmzFncu.d.ts} +40 -18
- package/dist/null-attachment-transport-jZmzFncu.d.ts.map +1 -0
- package/package.json +8 -6
- package/dist/null-attachment-transport-CMrO_ZKA.d.ts.map +0 -1
- package/dist/null-attachment-transport-CrsUafCi.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { C as
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
1177
|
-
|
|
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
|