@powerhousedao/reactor-attachments 6.2.3-dev.5 → 6.2.3-dev.51

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.
@@ -0,0 +1,955 @@
1
+ import { O as TransportFetchResult, _ as AttachmentResponse, a as IAttachmentTransport, g as AttachmentMetadata, h as AttachmentHeader, i as IAttachmentStore } from "./interfaces-jHW9JXv1.js";
2
+ import { a as IAttachmentReferenceScanner, c as IAttachmentSchemaCompiler, i as IAttachmentReferenceReader, n as AttachmentReferencePageResult, r as AttachmentReferenceRow } from "./types-AagIcjTD.js";
3
+ import { AttachmentHash, AttachmentRef, IDocumentModelRegistry, IEventBus, IReactorClient, ISyncManager, JwtHandler, LocalChannelPort } from "@powerhousedao/reactor";
4
+ import { AuthSubject, OperationWithContext } from "@powerhousedao/shared/document-model";
5
+ //#region src/access/attachment-read-gate.d.ts
6
+ /** The reactor's read gate, as an attachment read consults it. */
7
+ type AttachmentReadGate = Pick<IReactorClient, "isServed" | "get">;
8
+ /** Scopes a subject may read, by document id; `SyncScopeGate` satisfies it. */
9
+ interface IDocumentScopeGate {
10
+ scopePredicateById(documentId: string, subject: {
11
+ address?: string;
12
+ key?: string;
13
+ }, branch: string, signal?: AbortSignal): Promise<(scope: string) => boolean>;
14
+ }
15
+ /** The scope an attachment's bytes belong to: the document's own domain state. */
16
+ export declare const ATTACHMENT_READ_SCOPE = "global";
17
+ /** The branch an attachment reference is resolved against. */
18
+ export declare const ATTACHMENT_READ_BRANCH = "main";
19
+ /** Whether `value` is a canonical v1 hash: 64 lowercase SHA-256 hex chars. */
20
+ export declare function isAttachmentHash(value: unknown): value is AttachmentHash;
21
+ /** Rethrows a failure: a read side that is down must not read as a denial. */
22
+ export declare function scopeGateAllowsAttachmentRead(scopeGate: IDocumentScopeGate, documentId: string, subject: AuthSubject): Promise<boolean>;
23
+ /** Served to the subject, references `ref`, and a referencing scope is readable. */
24
+ export declare function readGateAllowsAttachmentRead(readGate: AttachmentReadGate, references: Pick<IAttachmentReferenceReader, "referencingScopes">, documentId: string, ref: AttachmentRef, subject: AuthSubject): Promise<boolean>;
25
+ //#endregion
26
+ //#region src/local/local-attachment-server.d.ts
27
+ /** The peer a server answers: which reactor, over which channel. */
28
+ type AttachmentPeerLink = {
29
+ peerId: string;
30
+ channelName: string;
31
+ };
32
+ /**
33
+ * Decides whether the linked peer may read `hash` through `documentId`. False
34
+ * is answered as `not-found`, indistinguishable from absent bytes.
35
+ */
36
+ type LocalAttachmentAuthorizer = (link: AttachmentPeerLink, hash: AttachmentHash, documentId: string) => Promise<boolean>;
37
+ type LocalAttachmentServerOptions = {
38
+ /** The brokered port to the peer; the same one the transport half uses. */
39
+ port: LocalChannelPort;
40
+ /** Who is asking; handed to {@link authorize}. */
41
+ link: AttachmentPeerLink;
42
+ /** The store bytes are served FROM. Only locally held bytes are served. */
43
+ store: IAttachmentStore;
44
+ /** Defaults to refusing every read; see {@link readGateAttachmentAuthorizer}. */
45
+ authorize?: LocalAttachmentAuthorizer;
46
+ /** Bytes per `chunk` message; defaults to {@link DEFAULT_LOCAL_CHUNK_BYTES}. */
47
+ chunkSizeBytes?: number;
48
+ /** Requests served at once; one past it is answered `pending`. */
49
+ maxConcurrentServes?: number;
50
+ onDiagnostic?: (message: string, error?: unknown) => void;
51
+ };
52
+ /** Default {@link LocalAttachmentServerOptions.maxConcurrentServes}. */
53
+ export declare const DEFAULT_LOCAL_MAX_CONCURRENT_SERVES = 4;
54
+ type ReadGateAttachmentAuthorizerOptions = {
55
+ readGate: AttachmentReadGate;
56
+ /** `SyncScopeGate`; a reactor without a policy model passes one over `BareReadGate`. */
57
+ scopeGate: IDocumentScopeGate;
58
+ references: Pick<IAttachmentReferenceReader, "referencingScopes">;
59
+ /** The subject a link reads as; undefined refuses every read on it. */
60
+ subjectOf: (link: AttachmentPeerLink) => AuthSubject | undefined;
61
+ };
62
+ /**
63
+ * Authorizes a peer read the way the Switchboard's attachment route does: the
64
+ * link's subject may read the document's `global` scope, the reactor serves
65
+ * the document to it, and a scope it may read references the hash.
66
+ */
67
+ export declare function readGateAttachmentAuthorizer(options: ReadGateAttachmentAuthorizerOptions): LocalAttachmentAuthorizer;
68
+ /**
69
+ * Serves attachment bytes to a brokered local peer from this reactor's own
70
+ * store.
71
+ *
72
+ * The answering half of {@link LocalAttachmentTransport}; both run on the same
73
+ * `LocalChannelPort` and ignore each other's messages.
74
+ *
75
+ * It never chains. The store is read WITHOUT a document id, which means a
76
+ * `LocalAttachmentStore` serves only bytes it actually holds and cannot reach
77
+ * for its own transport on the requester's behalf. Without that, two peers
78
+ * linked to each other would answer a miss by asking each other, and a hash
79
+ * neither held would bounce between them.
80
+ */
81
+ export declare class LocalAttachmentServer {
82
+ private readonly port;
83
+ private readonly link;
84
+ private readonly store;
85
+ private readonly authorize;
86
+ private readonly chunkSizeBytes;
87
+ private readonly maxConcurrentServes;
88
+ private readonly onDiagnostic;
89
+ private readonly cancelled;
90
+ private readonly inFlight;
91
+ private readonly detachPort;
92
+ private served;
93
+ private bytesServed;
94
+ private refused;
95
+ private closed;
96
+ constructor(options: LocalAttachmentServerOptions);
97
+ /** What this peer has handed out. */
98
+ stats(): {
99
+ served: number;
100
+ bytesServed: number;
101
+ refused: number;
102
+ };
103
+ /** Detaches from the port. In-flight serves stop at their next slice. */
104
+ close(): void;
105
+ private onMessage;
106
+ private stopped;
107
+ private serve;
108
+ private serveInner;
109
+ private stream;
110
+ /** Refused and absent are one answer, so a peer learns nothing from it. */
111
+ private refuse;
112
+ private failRequest;
113
+ private post;
114
+ }
115
+ //#endregion
116
+ //#region src/local/local-attachment-transport.d.ts
117
+ type LocalAttachmentTransportOptions = {
118
+ /**
119
+ * The brokered port to the peer -- the same `LocalChannelPort` abstraction
120
+ * `LocalChannel` sync runs over (W1.1), on its own channel so byte traffic
121
+ * never interleaves with the sync wire.
122
+ */
123
+ port: LocalChannelPort;
124
+ /**
125
+ * How long to wait for the NEXT message of a request before abandoning it.
126
+ * Reset by every message, so a slow but progressing transfer is not cut off
127
+ * while a silent peer is. Defaults to
128
+ * {@link DEFAULT_LOCAL_REQUEST_TIMEOUT_MS}.
129
+ */
130
+ requestTimeoutMs?: number;
131
+ /** Swapped in tests; defaults to the realm's timers. */
132
+ setTimer?: (callback: () => void, delayMs: number) => unknown;
133
+ clearTimer?: (handle: unknown) => void;
134
+ /** Identifies this instance's request ids; defaults to a random nonce. */
135
+ instanceId?: string;
136
+ /** Refuses a body the peer declares larger than this. */
137
+ maxBytes?: number;
138
+ };
139
+ /**
140
+ * `IAttachmentTransport` over a brokered `LocalChannelPort`: one linked reactor
141
+ * pulling attachment bytes straight from its peer, with no Switchboard and no
142
+ * HTTP.
143
+ *
144
+ * The counterpart of {@link LocalAttachmentServer}, and a pair of them runs on
145
+ * each end of one port: a reactor both asks for bytes and serves them. The two
146
+ * halves share the port and ignore each other's messages (see
147
+ * `isLocalAttachmentRequest` / `isLocalAttachmentResponse`), and request ids
148
+ * are namespaced per instance so two peers that both start counting at 1 never
149
+ * match each other's replies.
150
+ *
151
+ * Pull-only by design. The agreed byte-movement model is lazy
152
+ * fetch-on-reference, so {@link announce} is a no-op and {@link push} refuses
153
+ * by name rather than silently succeeding: a push that reported success while
154
+ * moving nothing would make an eager-replication caller believe bytes had
155
+ * landed on a peer.
156
+ */
157
+ export declare class LocalAttachmentTransport implements IAttachmentTransport {
158
+ private readonly port;
159
+ private readonly requestTimeoutMs;
160
+ private readonly setTimer;
161
+ private readonly clearTimer;
162
+ private readonly instanceId;
163
+ private readonly maxBytes;
164
+ private readonly pending;
165
+ private readonly detachPort;
166
+ private nextRequest;
167
+ private closed;
168
+ constructor(options: LocalAttachmentTransportOptions);
169
+ fetch(hash: AttachmentHash, documentId: string, signal?: AbortSignal): Promise<TransportFetchResult>;
170
+ /**
171
+ * No-op: the agreed model is the peer PULLING on reference, so there is
172
+ * nothing useful to tell it -- it will ask when one of its own operations
173
+ * names the hash. An eager-announce protocol would need a peer that acts on
174
+ * the notice, which nothing does.
175
+ */
176
+ announce(): Promise<void>;
177
+ /** Refuses: this transport moves bytes only in answer to a peer's request. */
178
+ push(): Promise<void>;
179
+ /** Detaches from the port and fails every in-flight request. */
180
+ close(): void;
181
+ private onMessage;
182
+ private handle;
183
+ /** A reply that breaks the protocol: tell the peer to stop, fail the request. */
184
+ private violate;
185
+ /** Resets this request's silence timer. */
186
+ private arm;
187
+ /** Forgets a request that completed normally. */
188
+ private release;
189
+ /**
190
+ * Forgets a request that failed, pushing the failure to whichever side is
191
+ * still listening: the fetch promise before `begin`, the body stream after.
192
+ */
193
+ private abandon;
194
+ private post;
195
+ }
196
+ //#endregion
197
+ //#region src/local/protocol.d.ts
198
+ /**
199
+ * Wire tag on every message, so one `LocalChannelPort` can carry this
200
+ * request/response protocol without being confused with anything else on the
201
+ * port, and so a version skew is a readable refusal rather than a crash.
202
+ */
203
+ export declare const LOCAL_ATTACHMENT_PROTOCOL = "ph-attachment/v1";
204
+ /** Default bytes per `chunk` message. */
205
+ export declare const DEFAULT_LOCAL_CHUNK_BYTES: number;
206
+ /** Default wait for the next message of a request before it is abandoned. */
207
+ export declare const DEFAULT_LOCAL_REQUEST_TIMEOUT_MS = 30000;
208
+ /** Ask a peer for the bytes of `hash`, authorized by `documentId`. */
209
+ type LocalAttachmentFetchRequest = {
210
+ protocol: typeof LOCAL_ATTACHMENT_PROTOCOL;
211
+ kind: "fetch";
212
+ /** Unique per requesting instance; the peer echoes it on every reply. */
213
+ id: string;
214
+ hash: AttachmentHash;
215
+ documentId: string;
216
+ };
217
+ /** Abandon an in-flight `fetch` so the peer stops reading and sending. */
218
+ type LocalAttachmentCancelRequest = {
219
+ protocol: typeof LOCAL_ATTACHMENT_PROTOCOL;
220
+ kind: "cancel";
221
+ id: string;
222
+ };
223
+ type LocalAttachmentRequest = LocalAttachmentFetchRequest | LocalAttachmentCancelRequest;
224
+ /**
225
+ * The peer holds the bytes and is about to stream them. `metadata` arrives up
226
+ * front so the requester can start a body stream before the first byte.
227
+ */
228
+ type LocalAttachmentBeginResponse = {
229
+ protocol: typeof LOCAL_ATTACHMENT_PROTOCOL;
230
+ kind: "begin";
231
+ id: string;
232
+ hash: AttachmentHash;
233
+ metadata: AttachmentMetadata;
234
+ };
235
+ /**
236
+ * One slice of the body. `bytes` is a `Uint8Array`, which both a browser
237
+ * `MessagePort` and a `node:worker_threads` one structured-clone natively.
238
+ * `seq` lets a receiver assert ordering rather than assume it.
239
+ */
240
+ type LocalAttachmentChunkResponse = {
241
+ protocol: typeof LOCAL_ATTACHMENT_PROTOCOL;
242
+ kind: "chunk";
243
+ id: string;
244
+ seq: number;
245
+ bytes: Uint8Array;
246
+ };
247
+ /** The body is complete. */
248
+ type LocalAttachmentEndResponse = {
249
+ protocol: typeof LOCAL_ATTACHMENT_PROTOCOL;
250
+ kind: "end";
251
+ id: string;
252
+ };
253
+ /** Retry later: the peer holds a reservation without bytes, or is at its serve cap. */
254
+ type LocalAttachmentPendingResponse = {
255
+ protocol: typeof LOCAL_ATTACHMENT_PROTOCOL;
256
+ kind: "pending";
257
+ id: string;
258
+ hash: AttachmentHash;
259
+ expiresAtUtc: string;
260
+ retryAfterMs: number;
261
+ /** At its serve cap: the peer did not authorize or look up the hash. */
262
+ busy?: true;
263
+ };
264
+ /**
265
+ * The peer will not serve the hash: it does not hold the bytes, or the
266
+ * document does not authorize the read.
267
+ *
268
+ * The two are deliberately one answer. Distinguishing them would tell a peer
269
+ * which hashes exist without authorization, and the requester's handling is
270
+ * the same either way -- a bounded retry, because an unauthorized answer is
271
+ * most often the serving reactor's attachment reference index lagging its own
272
+ * sync (see `AttachmentRetryPolicy.notFoundAttempts`).
273
+ */
274
+ type LocalAttachmentNotFoundResponse = {
275
+ protocol: typeof LOCAL_ATTACHMENT_PROTOCOL;
276
+ kind: "not-found";
277
+ id: string;
278
+ };
279
+ /** The peer failed while serving; the requester treats this as a retryable error. */
280
+ type LocalAttachmentErrorResponse = {
281
+ protocol: typeof LOCAL_ATTACHMENT_PROTOCOL;
282
+ kind: "error";
283
+ id: string;
284
+ message: string;
285
+ };
286
+ type LocalAttachmentResponse = LocalAttachmentBeginResponse | LocalAttachmentChunkResponse | LocalAttachmentEndResponse | LocalAttachmentPendingResponse | LocalAttachmentNotFoundResponse | LocalAttachmentErrorResponse;
287
+ type LocalAttachmentMessage = LocalAttachmentRequest | LocalAttachmentResponse;
288
+ /**
289
+ * Whether `value` is a request for this protocol.
290
+ *
291
+ * Both halves live on one port -- each peer runs a transport AND a server over
292
+ * the same `LocalChannelPort` -- so each half has to recognise only its own
293
+ * side of the conversation and ignore the rest rather than treat it as
294
+ * malformed.
295
+ */
296
+ export declare function isLocalAttachmentRequest(value: unknown): value is LocalAttachmentRequest;
297
+ /** Whether `value` is a response for this protocol. */
298
+ export declare function isLocalAttachmentResponse(value: unknown): value is LocalAttachmentResponse;
299
+ //#endregion
300
+ //#region src/peers/peered-attachment-transport.d.ts
301
+ type PeeredAttachmentTransportOptions = {
302
+ /** Read at fetch time for the Switchboard origins of its gql remotes. */
303
+ syncManager?: Pick<ISyncManager, "list">;
304
+ /** An attachment host that is not one of the sync remotes. */
305
+ switchboardUrl?: string;
306
+ jwtHandler?: JwtHandler;
307
+ fetchFn?: typeof fetch;
308
+ };
309
+ /**
310
+ * Peers in parallel, then Switchboards in turn; verified data > pending >
311
+ * busy > error > unanimous not-found.
312
+ */
313
+ export declare class PeeredAttachmentTransport implements IAttachmentTransport {
314
+ private readonly syncManager;
315
+ private readonly switchboardUrl;
316
+ private readonly jwtHandler;
317
+ private readonly fetchFn;
318
+ private readonly peers;
319
+ private readonly switchboards;
320
+ constructor(options?: PeeredAttachmentTransportOptions);
321
+ /** Keyed by (peerId, channelName), so one peer may hold several links. */
322
+ addPeer(peerId: string, channelName: string, transport: IAttachmentTransport): void;
323
+ /** Forgets one peer link; the caller owns closing its transport. */
324
+ removePeer(peerId: string, channelName: string): IAttachmentTransport | undefined;
325
+ /** Distinct linked peer ids. */
326
+ peerNames(): readonly string[];
327
+ /** The explicit Switchboard, then every gql remote's origin. */
328
+ switchboardSources(): readonly string[];
329
+ fetch(hash: AttachmentHash, documentId: string, signal?: AbortSignal): Promise<TransportFetchResult>;
330
+ private ask;
331
+ /** The first verified data wins and cancels the other peers. */
332
+ private askPeers;
333
+ /** Best effort across every source. */
334
+ announce(hash: AttachmentHash): Promise<void>;
335
+ push(): Promise<void>;
336
+ private sources;
337
+ private switchboardFor;
338
+ }
339
+ /** Attachments are served from the origin, not under the GraphQL path. */
340
+ export declare function attachmentOriginOf(graphqlUrl: string): string;
341
+ //#endregion
342
+ //#region src/peers/attachment-peer-links.d.ts
343
+ type AttachmentServedStats = {
344
+ served: number;
345
+ bytesServed: number;
346
+ refused: number;
347
+ };
348
+ type AttachmentPeerLinksOptions = {
349
+ /** What linked peers are served from. */
350
+ store: IAttachmentStore;
351
+ /** Where each link's pulling half is registered. */
352
+ transport: PeeredAttachmentTransport;
353
+ /** Absent, and absent on the link, every read is refused. */
354
+ authorize?: LocalAttachmentAuthorizer;
355
+ chunkSizeBytes?: number;
356
+ requestTimeoutMs?: number;
357
+ /** Each link's transport refuses a body declared larger than this. */
358
+ maxBytes?: number;
359
+ /** Requests each link's server serves at once. */
360
+ maxConcurrentServes?: number;
361
+ /** Called after a link is added; a host re-chases terminal hashes here. */
362
+ onPeerAdded?: (peerId: string, channelName: string) => void;
363
+ onDiagnostic?: (message: string, error?: unknown) => void;
364
+ };
365
+ type AttachmentPeerLinkOptions = {
366
+ /** Replaces the links-wide authorizer for this link. */
367
+ authorize?: LocalAttachmentAuthorizer;
368
+ };
369
+ /** Per-peer server and transport pairs, each on its own port, not the sync port. */
370
+ export declare class AttachmentPeerLinks {
371
+ private readonly options;
372
+ private readonly links;
373
+ private readonly onDiagnostic;
374
+ constructor(options: AttachmentPeerLinksOptions);
375
+ has(peerId: string, channelName: string): boolean;
376
+ /** Takes ownership of `port` on success; on failure the port is untouched. */
377
+ addPeer(peerId: string, channelName: string, port: LocalChannelPort, linkOptions?: AttachmentPeerLinkOptions): void;
378
+ /** Closes the link's server, transport and port. False when absent. */
379
+ removePeer(peerId: string, channelName: string): boolean;
380
+ servedStats(): AttachmentServedStats;
381
+ close(): void;
382
+ }
383
+ //#endregion
384
+ //#region src/replication/types.d.ts
385
+ /**
386
+ * Pulls the attachment refs an operation carries.
387
+ *
388
+ * The replicator depends on this rather than on the schema compiler directly
389
+ * so a host that has no compiled document models (a test, a reactor whose
390
+ * models declare no `AttachmentRef` field but carry refs by another
391
+ * convention) can still feed it. The production implementation is
392
+ * `SchemaCompiledOperationRefs`, which is the same compiler + registry pair
393
+ * `AttachmentReferenceReadModel` uses, so the refs the replicator chases and
394
+ * the refs the reference index records are extracted by one piece of code.
395
+ */
396
+ interface IOperationAttachmentRefs {
397
+ refsOf(item: OperationWithContext): readonly AttachmentRef[];
398
+ }
399
+ /**
400
+ * What the replicator is doing about one hash.
401
+ *
402
+ * - `queued`: known missing locally, waiting for a concurrency slot.
403
+ * - `fetching`: a transport fetch is in flight.
404
+ * - `held`: the bytes are in the local store. Terminal and the goal; a held
405
+ * hash leaves the report.
406
+ * - `waiting`: the last answer was an unexpired `pending`, a bounded
407
+ * `not-found` (see {@link AttachmentRetryPolicy.notFoundAttempts}), or a
408
+ * transport error; a retry is scheduled.
409
+ * - `not-found`: the `not-found` budget is spent (an expired `pending` counts
410
+ * as a `not-found`), no document is left unasked and no `pending` is live.
411
+ * - `failed`: the transport kept erroring and no document is left unasked.
412
+ *
413
+ * Both terminal states wait for `retry()`, or for a document not yet seen to
414
+ * reference the hash, which earns one more attempt through that document.
415
+ */
416
+ type AttachmentReplicationState = "queued" | "fetching" | "held" | "waiting" | "not-found" | "failed";
417
+ /**
418
+ * Retry shape for the three answers a fetch can give, honouring the
419
+ * `pending | not-found | data` triad that
420
+ * {@link IAttachmentTransport.fetch} exists to express.
421
+ *
422
+ * `notFoundAttempts` is the reference-index race budget and the reason
423
+ * `not-found` is retried at all: a peer authorizes a byte fetch through its
424
+ * OWN attachment reference index, and that index lags that peer's sync. So the
425
+ * first `not-found` for a freshly synced ref is far more likely to mean "that
426
+ * peer has not indexed this operation yet" than "these bytes do not exist".
427
+ * Retrying it a bounded number of times covers the lag without turning a
428
+ * genuinely missing hash into an unbounded loop -- and the counts keep both
429
+ * outcomes visible rather than papering over either.
430
+ */
431
+ type AttachmentRetryPolicy = {
432
+ /** Fallback wait when a `pending` answer carries no `retryAfterMs`. */
433
+ pendingRetryMs: number;
434
+ /** Shortest wait honoured after a `pending`, whatever it asks for. */
435
+ minPendingRetryMs: number;
436
+ /** Cap for a run of `pending` answers, whose wait doubles per answer. */
437
+ maxPendingRetryMs: number;
438
+ /** `not-found` answers absorbed as index lag; cumulative until `retry()`. */
439
+ notFoundAttempts: number;
440
+ /** Base wait between `not-found` retries; doubles per attempt. */
441
+ notFoundRetryMs: number;
442
+ /** Transport errors in a row before giving up; any answer ends the run. */
443
+ errorAttempts: number;
444
+ /** Base wait between error retries; doubles per attempt. */
445
+ errorRetryMs: number;
446
+ /** Cap for not-found and error waits. */
447
+ maxRetryMs: number;
448
+ };
449
+ export declare const DEFAULT_ATTACHMENT_RETRY_POLICY: AttachmentRetryPolicy;
450
+ /** Default bounded concurrency for in-flight attachment fetches. */
451
+ export declare const DEFAULT_ATTACHMENT_REPLICATION_CONCURRENCY = 3;
452
+ /** Default number of held hashes a replicator remembers after dropping their entries. */
453
+ export declare const DEFAULT_ATTACHMENT_HELD_HASH_LIMIT = 10000;
454
+ /** Default page size for the boot re-scan over the reference backlog. */
455
+ export declare const DEFAULT_ATTACHMENT_BACKLOG_PAGE_SIZE = 200;
456
+ /**
457
+ * The replicator's observable state: what it has seen, what it holds, and what
458
+ * it is still arguing about.
459
+ *
460
+ * Reported rather than inferred, because the two honest failure modes --
461
+ * a peer that never had the bytes, and a peer whose reference index has not
462
+ * caught up -- are indistinguishable from the outside at the moment they
463
+ * happen and only separable over time. `notFound` and `waiting` are both
464
+ * surfaced so an operator sees which one a reactor is in.
465
+ */
466
+ type AttachmentReplicatorStatus = {
467
+ /** Whether the replicator is subscribed and scheduling. */
468
+ running: boolean;
469
+ /** Distinct hashes tracked: every entry plus the remembered held hashes. */
470
+ refsSeen: number;
471
+ /** Remembered held hashes, at most `heldHashLimit`. */
472
+ held: number;
473
+ /** `storageUsed()` of the local store, in bytes. */
474
+ bytesHeld: number;
475
+ /** Hashes waiting for a concurrency slot. */
476
+ queued: number;
477
+ /** Hashes with a fetch in flight. */
478
+ fetching: number;
479
+ /** Hashes with a retry scheduled (pending, lagging index, or error). */
480
+ waiting: number;
481
+ /** Hashes the peer answered `not-found` for, past the lag budget. */
482
+ notFound: number;
483
+ /** Hashes whose transport kept erroring. */
484
+ failed: number;
485
+ /** Whether the boot re-scan over the reference backlog has completed. */
486
+ backlogScanned: boolean;
487
+ /** The most recent transport error message, for the inspector. */
488
+ lastError: string | undefined;
489
+ };
490
+ /** One hash's replication record, as reported for diagnostics. */
491
+ type AttachmentReplicationEntry = {
492
+ hash: AttachmentHash;
493
+ state: AttachmentReplicationState;
494
+ /** Documents whose operations referenced the hash; any of them may authorize. */
495
+ documentIds: readonly string[];
496
+ attempts: number;
497
+ notFoundAnswers: number;
498
+ /** Epoch ms of the next scheduled attempt, when one is scheduled. */
499
+ nextAttemptAtMs: number | undefined;
500
+ lastError: string | undefined;
501
+ };
502
+ //#endregion
503
+ //#region src/replication/attachment-replicator.d.ts
504
+ /**
505
+ * Clock and timer seam. Injected rather than taken from globals so a test can
506
+ * drive every retry deadline deterministically, without fake timers racing the
507
+ * replicator's own promises.
508
+ */
509
+ type ReplicationTimers = {
510
+ /** Epoch ms; a pending answer's `expiresAtUtc` is compared against it. */
511
+ now: () => number;
512
+ setTimer: (callback: () => void, delayMs: number) => unknown;
513
+ clearTimer: (handle: unknown) => void;
514
+ };
515
+ export declare const SYSTEM_REPLICATION_TIMERS: ReplicationTimers;
516
+ type AttachmentReplicatorOptions = {
517
+ /** Where bytes land, and the authority on what is already held. */
518
+ store: IAttachmentStore;
519
+ /** Where bytes come from. */
520
+ transport: IAttachmentTransport;
521
+ /** Pulls refs out of a committed operation. */
522
+ refs: IOperationAttachmentRefs;
523
+ /** The reactor's bus; the replicator subscribes to `JOB_READ_READY`. */
524
+ eventBus: IEventBus;
525
+ /**
526
+ * The durable reference list re-scanned on {@link AttachmentReplicator.start}.
527
+ * Omitted, the replicator only ever learns about refs from live operations --
528
+ * correct for a reactor with no reference index, and NOT resumable across a
529
+ * restart, which {@link AttachmentReplicatorStatus.backlogScanned} reports.
530
+ */
531
+ backlog?: IAttachmentReferenceScanner;
532
+ concurrency?: number;
533
+ retry?: Partial<AttachmentRetryPolicy>;
534
+ backlogPageSize?: number;
535
+ /**
536
+ * Held hashes remembered after their entry is dropped, oldest evicted first.
537
+ * An evicted hash referenced again costs one `store.has()`, never a fetch.
538
+ */
539
+ heldHashLimit?: number;
540
+ /**
541
+ * Whether fetched bytes are hashed and checked against the hash they were
542
+ * requested under. Defaults to true: a local peer transport means the bytes
543
+ * come from another reactor rather than from a trusted server, and a
544
+ * content-addressed store that accepts bytes it never verified is no longer
545
+ * content-addressed.
546
+ */
547
+ verifyHash?: boolean;
548
+ timers?: ReplicationTimers;
549
+ onDiagnostic?: (message: string, error?: unknown) => void;
550
+ };
551
+ /**
552
+ * Lazy fetch-on-reference: pulls attachment bytes a reactor's own operations
553
+ * reference but whose hash its local store lacks.
554
+ *
555
+ * The seam is `ReactorEventTypes.JOB_READ_READY` on the reactor's event bus,
556
+ * and that choice matters in two ways. It is AFTER the pre-ready read models,
557
+ * so by the time the replicator asks a peer for a hash, this reactor's own
558
+ * attachment reference index has recorded the reference -- the same reactor is
559
+ * therefore able to serve the hash onward, and a reactor cannot be in the
560
+ * position of chasing bytes it would refuse to authorize itself. And it is off
561
+ * the write path: a slow or unreachable peer cannot delay a commit, because
562
+ * the subscriber only notes hashes and returns.
563
+ *
564
+ * Resume is a re-scan of the reference index ({@link IAttachmentReferenceScanner}),
565
+ * not a cursor: the index plus `store.has()` re-derives the exact outstanding
566
+ * work set on every boot, idempotently, and a persisted cursor could only add a
567
+ * second truth that is wrong in the one direction that loses data.
568
+ *
569
+ * Loop-safety is structural: one entry per hash, created once, and every
570
+ * retry decision is {@link nextAfter}'s. A terminal entry (`not-found`,
571
+ * `failed`) is never re-queued by a further reference from a document it
572
+ * already knows; a document it has not seen earns it one more attempt, through
573
+ * that document. Otherwise only {@link AttachmentReplicator.retry} moves a
574
+ * terminal entry back. A held hash drops its entry and is remembered in a
575
+ * bounded set, so the map holds only unfinished work.
576
+ */
577
+ export declare class AttachmentReplicator {
578
+ private readonly store;
579
+ private readonly transport;
580
+ private readonly refs;
581
+ private readonly eventBus;
582
+ private readonly backlog;
583
+ private readonly concurrency;
584
+ private readonly policy;
585
+ private readonly backlogPageSize;
586
+ private readonly heldHashLimit;
587
+ private readonly verifyHash;
588
+ private readonly timers;
589
+ private readonly onDiagnostic;
590
+ private readonly entries;
591
+ private readonly heldHashes;
592
+ private readonly queue;
593
+ private readonly idleWaiters;
594
+ private readonly aborts;
595
+ private unsubscribe;
596
+ private running;
597
+ private inFlight;
598
+ private timerHandle;
599
+ private timerAtMs;
600
+ private backlogScan;
601
+ private backlogDone;
602
+ private lastError;
603
+ constructor(options: AttachmentReplicatorOptions);
604
+ /**
605
+ * Subscribes to committed operations and starts the boot re-scan.
606
+ *
607
+ * The subscription is taken BEFORE the scan so no operation committed during
608
+ * the scan can slip between the two. A hash both paths find is one entry.
609
+ *
610
+ * A restart resumes its own unfinished work: a `stop()` empties the queue but
611
+ * keeps the entries, so entries left `queued` or `waiting` are re-armed here.
612
+ * Without that, a hash that was outstanding at `stop()` would sit in the map
613
+ * forever unless an unrelated live operation happened to pump the queue --
614
+ * the backlog re-scan does not rescue it, because `observeReference` treats
615
+ * an already-known hash as nothing to do.
616
+ */
617
+ start(): void;
618
+ /**
619
+ * Stops scheduling, drops the subscription and aborts in-flight fetches.
620
+ *
621
+ * Entries are kept: the in-memory retry counters are not durable state and a
622
+ * restart legitimately starts the budgets over (a hash that was `not-found`
623
+ * because a peer's index lagged deserves another look after a reboot), but a
624
+ * `stop()` that is not a restart should not forget what is held either --
625
+ * `store.has()` is re-checked before every fetch regardless.
626
+ */
627
+ stop(): Promise<void>;
628
+ /** Notes every ref carried by `items`. Synchronous: it only enqueues. */
629
+ observeOperations(items: readonly OperationWithContext[]): void;
630
+ /** Notes one (document, ref) pair, queueing the hash if it is new. */
631
+ observeReference(documentId: string, ref: AttachmentRef): void;
632
+ status(): Promise<AttachmentReplicatorStatus>;
633
+ /** Every hash not yet held and what is happening to it. */
634
+ report(): AttachmentReplicationEntry[];
635
+ /**
636
+ * Re-queues terminal entries so a `not-found` or `failed` hash is chased
637
+ * again, resetting their budgets. `hash` narrows it to one; absent, every
638
+ * terminal entry is retried.
639
+ *
640
+ * The deliberate manual lever over the reference-index race: when an
641
+ * operator can see that a peer has caught up, nothing should make them wait
642
+ * for a reboot to find out.
643
+ */
644
+ retry(hash?: AttachmentHash): void;
645
+ /**
646
+ * Resolves when nothing is queued or in flight.
647
+ *
648
+ * Entries `waiting` on a retry deadline do NOT hold this open -- their next
649
+ * attempt is the timer's business, and a caller that wants to reach it
650
+ * advances the injected clock. A test seam, and what a load harness waits on.
651
+ */
652
+ idle(): Promise<void>;
653
+ /** Awaits the boot re-scan; resolves immediately when there is no backlog. */
654
+ backlogScanned(): Promise<void>;
655
+ /** Re-queues every non-terminal entry a previous `stop()` stranded. */
656
+ private resumeOutstanding;
657
+ private scanBacklog;
658
+ /** Starts as many queued fetches as the concurrency bound allows. */
659
+ private pump;
660
+ private fetchOne;
661
+ private attempt;
662
+ private apply;
663
+ private markHeld;
664
+ private recordError;
665
+ private schedule;
666
+ /** Keeps exactly one timer, set to the earliest outstanding deadline. */
667
+ private armTimer;
668
+ private onTimer;
669
+ private cancelTimer;
670
+ private settleIdle;
671
+ }
672
+ /** A scanner over a plain list, for a host without a reference index. */
673
+ export declare function staticAttachmentReferenceScanner(references: readonly AttachmentReferenceRow[]): IAttachmentReferenceScanner;
674
+ //#endregion
675
+ //#region src/replication/hash.d.ts
676
+ /**
677
+ * Lowercase SHA-256 hex of `bytes` -- the `attachment://v1:` hash format
678
+ * (`packages/reactor/src/attachments/types.ts`).
679
+ *
680
+ * WebCrypto, so the same code runs in Node and in a browser realm. There is no
681
+ * streaming digest in WebCrypto, which is why verification happens over
682
+ * collected bytes.
683
+ */
684
+ export declare function sha256Hex(bytes: Uint8Array): Promise<AttachmentHash>;
685
+ //#endregion
686
+ //#region src/replication/operation-attachment-refs.d.ts
687
+ /**
688
+ * The production {@link IOperationAttachmentRefs}: the compiled, schema-driven
689
+ * extractor, reused rather than reimplemented.
690
+ *
691
+ * This is the same `(registry, compiler)` pair `AttachmentReferenceReadModel`
692
+ * runs, so a ref the index records and a ref the replicator chases are found by
693
+ * one piece of code -- including the compiler's per-module/per-action cache,
694
+ * which is held by the compiler instance a host shares between the two. A host
695
+ * with no read model to share one with takes the default compiler.
696
+ *
697
+ * Extraction failures do NOT propagate. A malformed ref or an unregistered
698
+ * document type must not take down the event-bus subscriber the replicator
699
+ * runs on: that would turn an attachment problem into a reactor-wide write
700
+ * failure, since `IEventBus.emit` aggregates subscriber errors back to the
701
+ * emitter. The read model is the component whose job is to refuse a bad ref
702
+ * loudly; this one is a best-effort byte chaser, so it reports and moves on.
703
+ */
704
+ export declare class SchemaCompiledOperationRefs implements IOperationAttachmentRefs {
705
+ private readonly registry;
706
+ private readonly compiler;
707
+ private readonly onDiagnostic;
708
+ constructor(registry: IDocumentModelRegistry, compiler?: IAttachmentSchemaCompiler, onDiagnostic?: (message: string, error: unknown) => void);
709
+ refsOf(item: OperationWithContext): readonly AttachmentRef[];
710
+ }
711
+ //#endregion
712
+ //#region src/storage/local/bytes.d.ts
713
+ /**
714
+ * Drains `stream` into one `Uint8Array`.
715
+ *
716
+ * The local stores hold whole blobs (see {@link ILocalAttachmentBackend}), so
717
+ * the stream an `IAttachmentStore.put` is handed has to be collected before it
718
+ * can be written. Realm-neutral: no `node:buffer`, no `Blob`.
719
+ */
720
+ export declare function collectStream(stream: ReadableStream<Uint8Array>): Promise<Uint8Array>;
721
+ /** A one-chunk `ReadableStream` over `bytes`. */
722
+ export declare function streamFromBytes(bytes: Uint8Array): ReadableStream<Uint8Array>;
723
+ //#endregion
724
+ //#region src/storage/local/types.d.ts
725
+ /**
726
+ * One attachment's metadata row in a local, realm-resident store.
727
+ *
728
+ * The committed subset of {@link AttachmentHeader}: no `pending` status and no
729
+ * `expiresAtUtc`, because a local store of this kind holds no reservation
730
+ * table. Pending is a property of an in-flight UPLOAD, which belongs to the
731
+ * server-side `KyselyAttachmentStore`/`IReservationStore` pair; a replicating
732
+ * store only ever learns "pending" from a transport answer and never persists
733
+ * it.
734
+ */
735
+ type LocalAttachmentRecord = {
736
+ hash: AttachmentHash;
737
+ mimeType: string;
738
+ fileName: string;
739
+ /**
740
+ * The byte length actually held (or last held, for an evicted record), not
741
+ * the producer's claim. `AttachmentMetadata.sizeBytes` arrives over a wire
742
+ * and is only as honest as its sender; `storageUsed()` sums this field, so
743
+ * it is measured from the bytes received.
744
+ */
745
+ sizeBytes: number;
746
+ extension: string | null;
747
+ status: "available" | "evicted";
748
+ source: "local" | "sync";
749
+ createdAtUtc: string;
750
+ lastAccessedAtUtc: string;
751
+ };
752
+ /**
753
+ * The persistence seam under {@link LocalAttachmentStore}: a content-addressed
754
+ * record store plus a blob store, and nothing else.
755
+ *
756
+ * Every `IAttachmentStore` semantic -- dedup on put, evicted-then-restored,
757
+ * access-time bumping, transport re-fetch, what `has()` means -- lives in
758
+ * {@link LocalAttachmentStore} above this interface, so the two backends
759
+ * (IndexedDB for a browser, a memory twin for Node) cannot drift on behaviour.
760
+ * A backend only has to move bytes and rows.
761
+ *
762
+ * Bytes cross this interface as whole `Uint8Array`s rather than streams on
763
+ * purpose: IndexedDB has no streaming value API, so a chunked backend would
764
+ * have to reassemble anyway, and a browser-side attachment store is bounded by
765
+ * what the realm can hold regardless.
766
+ */
767
+ interface ILocalAttachmentBackend {
768
+ /** The record for `hash`, or undefined when the hash is unknown. */
769
+ readRecord(hash: AttachmentHash): Promise<LocalAttachmentRecord | undefined>;
770
+ /**
771
+ * Writes `record` and `bytes` as ONE unit, replacing whatever was there.
772
+ *
773
+ * Atomicity matters: a record claiming `available` with no blob behind it
774
+ * reads as a store that holds bytes it cannot serve, which is exactly the
775
+ * state the replicator would never retry.
776
+ */
777
+ write(record: LocalAttachmentRecord, bytes: Uint8Array): Promise<void>;
778
+ /** The bytes for `hash`, or undefined when none are held. */
779
+ readBytes(hash: AttachmentHash): Promise<Uint8Array | undefined>;
780
+ /**
781
+ * Writes `record` (expected to carry `status: "evicted"`) and deletes its
782
+ * blob as one unit. The record is retained so the hash stays known.
783
+ */
784
+ evict(record: LocalAttachmentRecord): Promise<void>;
785
+ /** Sets `lastAccessedAtUtc` on an existing record; a no-op for an unknown hash. */
786
+ touch(hash: AttachmentHash, lastAccessedAtUtc: string): Promise<void>;
787
+ /** Summed `sizeBytes` of every record whose status is `available`. */
788
+ availableBytes(): Promise<number>;
789
+ /** Releases the underlying handle. Idempotent. */
790
+ close(): Promise<void>;
791
+ }
792
+ //#endregion
793
+ //#region src/storage/local/idb-attachment-backend.d.ts
794
+ /** Metadata rows, keyed by content hash. */
795
+ export declare const IDB_RECORD_STORE = "records";
796
+ /** Blob rows (`{ hash, bytes }`), keyed by the same content hash. */
797
+ export declare const IDB_BLOB_STORE = "blobs";
798
+ /** Index over {@link LocalAttachmentRecord.status}; what `storageUsed()` scans. */
799
+ export declare const IDB_STATUS_INDEX = "by_status";
800
+ /** Default database name. One database holds one reactor's attachment store. */
801
+ export declare const DEFAULT_IDB_DATABASE = "ph-attachments";
802
+ type IdbAttachmentBackendOptions = {
803
+ /**
804
+ * Database name; defaults to {@link DEFAULT_IDB_DATABASE}. A host running
805
+ * several reactors in one origin must namespace this per reactor, exactly as
806
+ * the operation store is namespaced -- two reactors sharing one attachment
807
+ * database would share evictions and storage accounting.
808
+ */
809
+ databaseName?: string;
810
+ /**
811
+ * The factory to open against; defaults to the realm's `indexedDB`. The seam
812
+ * a shim (or a test double) is injected through, and the reason this backend
813
+ * takes no dependency on a global being present at import time.
814
+ */
815
+ indexedDB?: IDBFactory;
816
+ };
817
+ /**
818
+ * An IndexedDB-backed {@link ILocalAttachmentBackend}: the browser-resident
819
+ * attachment store a reactor in a tab or a SharedWorker replicates into.
820
+ *
821
+ * Two object stores, written in one transaction so a record and its blob land
822
+ * or fail together (the atomicity {@link ILocalAttachmentBackend.write}
823
+ * requires). Bytes are kept as `ArrayBuffer` rather than `Blob`: both are
824
+ * structured-clone-safe values IndexedDB stores natively, and an ArrayBuffer
825
+ * needs no async read to get back to the `Uint8Array` the store serves.
826
+ *
827
+ * OPFS is the obvious later alternative for large blobs -- it streams and does
828
+ * not hold a whole attachment in the realm's heap -- and nothing above this
829
+ * interface would change: it is one more backend.
830
+ *
831
+ * The database handle is opened lazily on first use and reused, so constructing
832
+ * this in a realm that has no `indexedDB` (a Node import of the barrel) costs
833
+ * nothing and fails only if something actually reads or writes.
834
+ */
835
+ export declare class IdbAttachmentBackend implements ILocalAttachmentBackend {
836
+ private readonly databaseName;
837
+ private readonly factory;
838
+ private opening;
839
+ private database;
840
+ private closed;
841
+ constructor(options?: IdbAttachmentBackendOptions);
842
+ readRecord(hash: AttachmentHash): Promise<LocalAttachmentRecord | undefined>;
843
+ write(record: LocalAttachmentRecord, bytes: Uint8Array): Promise<void>;
844
+ readBytes(hash: AttachmentHash): Promise<Uint8Array | undefined>;
845
+ evict(record: LocalAttachmentRecord): Promise<void>;
846
+ touch(hash: AttachmentHash, lastAccessedAtUtc: string): Promise<void>;
847
+ availableBytes(): Promise<number>;
848
+ close(): Promise<void>;
849
+ private open;
850
+ private openDatabase;
851
+ }
852
+ //#endregion
853
+ //#region src/storage/local/local-attachment-store.d.ts
854
+ /**
855
+ * A realm-resident, content-addressed {@link IAttachmentStore} over a
856
+ * {@link ILocalAttachmentBackend} -- the browser-capable half of attachment
857
+ * byte movement.
858
+ *
859
+ * The same class serves both backends, so an IndexedDB store in a tab and the
860
+ * memory twin a Node test runs cannot diverge in behaviour: every semantic the
861
+ * `IAttachmentStore` contract states is implemented once, here.
862
+ *
863
+ * Differences from `KyselyAttachmentStore`, all of them deliberate:
864
+ *
865
+ * - No reservation table, so `stat()`/`get()` never synthesize `pending` from
866
+ * local state. `pending` only ever arrives as a TRANSPORT answer, which
867
+ * `get()` raises as {@link AttachmentPending} exactly as the Kysely store
868
+ * does for a remote pending.
869
+ * - `get()` reads the whole blob before it hands back a stream, so there is no
870
+ * such thing as an in-flight read for `evict()` to destroy. The contract's
871
+ * "skip hashes with active readers" clause is satisfied by construction
872
+ * rather than by a refcount; a caller already holding a stream holds a
873
+ * snapshot.
874
+ * - `sizeBytes` is the measured length of the bytes received, not the
875
+ * producer's `AttachmentMetadata.sizeBytes` claim, so `storageUsed()` cannot
876
+ * be talked into a wrong number by a remote.
877
+ */
878
+ export declare class LocalAttachmentStore implements IAttachmentStore {
879
+ private readonly backend;
880
+ private readonly transport;
881
+ private readonly now;
882
+ constructor(backend: ILocalAttachmentBackend, transport: IAttachmentTransport, now?: () => Date);
883
+ stat(hash: AttachmentHash): Promise<AttachmentHeader>;
884
+ has(hash: AttachmentHash): Promise<boolean>;
885
+ get(hash: AttachmentHash, signal?: AbortSignal, documentId?: string): Promise<AttachmentResponse>;
886
+ put(hash: AttachmentHash, metadata: AttachmentMetadata, data: ReadableStream<Uint8Array>): Promise<void>;
887
+ evict(hash: AttachmentHash): Promise<void>;
888
+ storageUsed(): Promise<number>;
889
+ /** Releases the backend handle. */
890
+ close(): Promise<void>;
891
+ /**
892
+ * Stores bytes this realm produced itself, as `source: "local"`.
893
+ *
894
+ * `put()` is the transport's entry point and marks everything `sync`, which
895
+ * is the right provenance for a replicated blob and the wrong one for a blob
896
+ * that originated here. The server-side store gets `source: "local"` rows
897
+ * from its upload handles; a local store has no upload handle, so this is
898
+ * how a producer (an editor that just hashed a file, a test that seeds a
899
+ * peer) says the bytes are its own.
900
+ */
901
+ putLocal(hash: AttachmentHash, metadata: AttachmentMetadata, data: ReadableStream<Uint8Array>): Promise<void>;
902
+ /**
903
+ * `overwrite` skips the available-hash dedup check.
904
+ *
905
+ * Only {@link fetchRemote} sets it, and only because it is reached when the
906
+ * local copy is unusable -- including the torn case where the record says
907
+ * `available` over a missing blob. Without it, that record would deduplicate
908
+ * away its own repair and the hash could never be restored.
909
+ */
910
+ private store;
911
+ /**
912
+ * Restores `hash` from the transport on behalf of `documentId`, persists it
913
+ * through the same write path `put` uses (so provenance follows one rule)
914
+ * and serves the result from the store rather than from the transport's
915
+ * stream, which has already been consumed.
916
+ *
917
+ * The fetched bytes are hashed and refused when they are not what was asked
918
+ * for, mirroring {@link AttachmentReplicator}: a transport peer is another
919
+ * reactor rather than a trusted server, and a content-addressed store that
920
+ * accepts bytes it never verified is no longer content-addressed. A mismatch
921
+ * is surfaced as an error and nothing is written, so a lying peer cannot
922
+ * poison the store.
923
+ */
924
+ private fetchRemote;
925
+ }
926
+ //#endregion
927
+ //#region src/storage/local/memory-attachment-backend.d.ts
928
+ /**
929
+ * The in-realm memory twin of {@link IdbAttachmentBackend}.
930
+ *
931
+ * It exists so the whole {@link LocalAttachmentStore} contract -- and the
932
+ * replicator above it -- is testable in Node without an IndexedDB shim, and so
933
+ * a reactor that wants a deliberately ephemeral attachment store (a test, a
934
+ * throwaway reactor) has one. The backend contract suite runs against
935
+ * this and against the IndexedDB backend, so the two answer identically.
936
+ *
937
+ * Records are copied in and out: a caller mutating a returned record must not
938
+ * be able to change what the store holds, which is the behaviour a real
939
+ * persistence layer has for free.
940
+ */
941
+ export declare class MemoryAttachmentBackend implements ILocalAttachmentBackend {
942
+ private readonly records;
943
+ private readonly blobs;
944
+ readRecord(hash: AttachmentHash): Promise<LocalAttachmentRecord | undefined>;
945
+ write(record: LocalAttachmentRecord, bytes: Uint8Array): Promise<void>;
946
+ readBytes(hash: AttachmentHash): Promise<Uint8Array | undefined>;
947
+ evict(record: LocalAttachmentRecord): Promise<void>;
948
+ touch(hash: AttachmentHash, lastAccessedAtUtc: string): Promise<void>;
949
+ availableBytes(): Promise<number>;
950
+ close(): Promise<void>;
951
+ }
952
+ //#endregion
953
+ export type { AttachmentPeerLink, AttachmentPeerLinkOptions, AttachmentPeerLinksOptions, AttachmentReadGate, AttachmentReferencePageResult, AttachmentReferenceRow, AttachmentReplicationEntry, AttachmentReplicationState, AttachmentReplicatorOptions, AttachmentReplicatorStatus, AttachmentRetryPolicy, AttachmentServedStats, IAttachmentReferenceScanner, IDocumentScopeGate, ILocalAttachmentBackend, IOperationAttachmentRefs, IdbAttachmentBackendOptions, LocalAttachmentAuthorizer, LocalAttachmentBeginResponse, LocalAttachmentCancelRequest, LocalAttachmentChunkResponse, LocalAttachmentEndResponse, LocalAttachmentErrorResponse, LocalAttachmentFetchRequest, LocalAttachmentMessage, LocalAttachmentNotFoundResponse, LocalAttachmentPendingResponse, LocalAttachmentRecord, LocalAttachmentRequest, LocalAttachmentResponse, LocalAttachmentServerOptions, LocalAttachmentTransportOptions, PeeredAttachmentTransportOptions, ReadGateAttachmentAuthorizerOptions, ReplicationTimers };
954
+ export {};
955
+ //# sourceMappingURL=replication.d.ts.map