// Auto-generated file. Do not edit manually. // Generated by: yarn generate-index-dts declare module "sliftutils/misc/apiKeys" { import preact from "preact"; export declare class APIKeysControl extends preact.Component { render(): preact.JSX.Element; } export declare const getAPIKey: { (key: string): Promise; clear(key: string): void; clearAll(): void; forceSet(key: string, value: Promise): void; getAllKeys(): string[]; get(key: string): Promise | undefined; }; export declare function setAPIKey(key: string, value: string): void; export declare class ManageAPIKeys extends preact.Component { render(): string; } } declare module "sliftutils/misc/cborx" { /// /// export declare function cborEncode(value: T): Buffer; export declare function cborDecode(buffer: Buffer): T; } declare module "sliftutils/misc/environment" { export declare function isInChromeExtension(): string | false; export declare function isInChromeExtensionBackground(): boolean; export declare function isInChromeExtensionContentScript(): boolean | ""; export declare function isInElectron(): string | false | undefined; export declare function triggerIsInBuild(): void; export declare function isInBuild(): boolean; export declare function isInBrowser(): boolean; } declare module "sliftutils/misc/fs" { export declare function getAllFiles(folder: string): AsyncIterableIterator; } declare module "sliftutils/misc/getSecret" { export declare function resetSecret(key: string): void; export declare const getSecret: { (key: string): Promise; clear(key: string): void; clearAll(): void; forceSet(key: string, value: Promise): void; getAllKeys(): string[]; get(key: string): Promise | undefined; }; export declare function setSecret(key: string, value: string): void; } declare module "sliftutils/misc/helpers" { export declare function waitForDiskCollectionFlush(): Promise; } declare module "sliftutils/misc/https/certs" { /// /// /// /// import * as forge from "node-forge"; export declare const CA_NOT_FOUND_ERROR = "18aa7318-f88f-4d2d-b41f-3daf4a433827"; export declare const identityStorageKey = "machineCA_14"; export type IdentityStorageType = { domain: string; certB64: string; keyB64: string; }; export declare function DEV_getIdentityFilePath(domain: string): string; export declare function DEV_listIdentityDomains(): string[]; export interface X509KeyPair { domain: string; cert: Buffer; key: Buffer; } export declare function getCommonName(cert: Buffer | string): string; export declare function createX509(config: { domain: string; issuer: X509KeyPair | "self"; lifeSpan: number; keyPair: { publicKey: forge.Ed25519PublicKey; privateKey: forge.Ed25519PrivateKey; }; }): X509KeyPair; export declare function privateKeyToPem(key: forge.Ed25519PrivateKey): string; export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate; export declare function getPublicIdentifier(PEMorDER: string | Buffer): Buffer; export declare const sign: (keyPair: { key: string | Buffer; }, data: unknown) => string; export declare function verify(cert: string, signature: string, data: unknown): void; export declare function validateCACert(domain: string, cert: string | Buffer): void; export declare function validateCertificate(domain: string, cert: Buffer | string, issuerCert: Buffer | string): void; export declare function generateKeyPair(): { publicKey: forge.Ed25519PublicKey; privateKey: forge.Ed25519PrivateKey; }; export declare function generateCA(domain: string): X509KeyPair; export declare function createCertFromCA(config: { CAKeyPair: X509KeyPair; }): X509KeyPair; export declare function getMachineId(domainNameOrNodeId: string, domain: string): string; export type NodeIdParts = { threadId: string; machineId: string; domain: string; port: number; }; export declare function decodeNodeId(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts | undefined; export declare function decodeNodeIdAssert(nodeId: string, domain: string, allowMissingThreadId?: "allowMissingThreadId"): NodeIdParts; export declare function encodeNodeId(parts: NodeIdParts): string; export declare function setIdentityCARaw(domain: string, json: string): Promise; export declare function loadIdentityCA(domain: string): Promise; export declare function getIdentityCA(domain: string): X509KeyPair; export declare function getIdentityCAPromise(domain: string): X509KeyPair; export declare function getOwnMachineId(domain: string): string; export declare function getOwnThreadId(domain: string): string; /** Part of the machineId comes from the publicKey, so we can use it to verify. Fairly weak: it only proves the id names this key, not that the caller holds the key - usually a better workflow should be used, with a back and forth (ex, validateCertificate over a signed exchange). In some cases it is sufficient, such as exposing source maps to the client. */ export declare function verifyMachineIdForPublicKey(config: { machineId: string; publicKey: Buffer; }): boolean; export declare function getThreadKeyCert(domain: string): X509KeyPair; export declare function getOwnNodeId(): string; export declare function getOwnNodeIdAllowUndefined(): string; } declare module "sliftutils/misc/https/cloudflareHelpers" { /// /// export type CloudflareCreds = { key: string; /** Set for legacy global API keys, which auth via X-Auth-Email/X-Auth-Key. Absent for API tokens, which auth via Authorization: Bearer. */ email?: string; }; export declare const getCloudflareCreds: { (): Promise; reset(): void; set(newValue: Promise): void; }; export declare function cloudflareGETCall(path: string, params?: { [key: string]: string; }): Promise; export declare function cloudflarePOSTCall(path: string, params: { [key: string]: unknown; }): Promise; export declare function cloudflareCall(path: string, payload: Buffer, method: string): Promise; } declare module "sliftutils/misc/https/dns" { /** Parses our tag back out; 0 (i.e. always stale) when it's absent or unparseable. */ export declare function freshnessTime(comment?: string): number; export declare const hasDNSWritePermissions: { (): Promise; reset(): void; set(newValue: Promise): void; }; export declare const getZoneId: { (key: string): Promise; clear(key: string): void; clearAll(): void; forceSet(key: string, value: Promise): void; getAllKeys(): string[]; get(key: string): Promise | undefined; }; export declare function getRecordsRaw(type: string, key: string): Promise<{ id: string; type: string; name: string; content: string; proxied: boolean; modified_on: string; comment?: string | undefined; }[]>; /** Cloudflare's batch endpoint applies deletes, then patches, then posts in a single database transaction. We route edits (patches) through here because the standalone PATCH/PUT verbs aren't usable in our setup, and because it lets "remove others + assert target" happen without a window where the name resolves to nothing. */ export declare function batchRecords(zoneId: string, batch: { deletes?: { id: string; }[]; patches?: { id: string; comment?: string; }[]; posts?: { type: string; name: string; content: string; ttl: number; proxied: boolean; comment?: string; }[]; }): Promise; export declare function getRecords(type: string, key: string): Promise; export declare function deleteRecord(type: string, key: string, value: string): Promise; /** Removes all existing records (unless the record is already present and fresh) */ export declare function setRecord(type: string, key: string, value: string, proxied?: "proxied", staleAfter?: number): Promise; /** Keeps existing records */ export declare function addRecord(type: string, key: string, value: string, proxied?: "proxied", staleAfter?: number): Promise; } declare module "sliftutils/misc/https/hostServer" { export type HostServerConfig = { /** Full domain to host on (e.g. "testsite.example.com"). The HTTPS cert is created for this domain and *.domain, so using a subdomain never touches the root domain (beyond its _acme-challenge TXT record). */ domain: string; port: number; /** Creates an unproxied A record pointing domain at this machine (publicIp, or our detected external IP) */ setDNSRecord?: boolean; publicIp?: string; allowHostnames?: string[]; /** LAN-only: do NOT forward the port (no UPnP/NAT mapping), for a server reachable only on the local network. */ internal?: boolean; /** Serve a TLS cert signed by this machine's CA instead of obtaining a real (ACME) one. See getFreshHTTPSCert. */ selfSigned?: boolean; /** When the port is busy (e.g. the previous deploy still holds it), mount on an alternate port instead (the socket server's built-in free-port scan), and keep trying to take the real port - once it frees, a raw TCP relay on the real port forwards to our listener (SocketFunction can only mount once per process). */ portFallback?: { /** Delay until the next main-port acquisition attempt (tightened around the predecessor's scheduled death) */ getAcquireDelay: () => number; /** Called when the main port turned out to be busy, before we act as its successor in any way. Throwing aborts startup - a busy port with no deploy in progress means something else holds it, which is not a state to keep running in. */ onPortInUse?: () => Promise; /** Reports every port we become reachable on (the alternate at mount, the main port once relayed) */ onListening: (port: number, isMainPort: boolean) => void; }; }; /** Hosts a SocketFunction server on a real domain, with an automatically created and renewed Let's Encrypt HTTPS certificate (cached in the home folder, shared between processes on the machine). Expose your controllers (and any RequireController setup) before calling this. Returns the mounted nodeId. */ export declare function hostServer(config: HostServerConfig): Promise; /** Returns the cached HTTPS cert for the domain, creating/renewing it first if it is past this process's renewal threshold. Reads the disk cache on every call, so a renewal done by a parallel process is picked up instead of renewing again. selfSigned: sign it with this machine's CA instead of getting a real ACME cert. */ export declare function getFreshHTTPSCert(domain: string, selfSigned?: boolean): Promise<{ key: string; cert: string; }>; } declare module "sliftutils/misc/https/hostsFile" { /** Ensures the hosts file maps `hostname` to `ip` (adding our tagged line, or updating it/an existing line for the same hostname). Idempotent. Returns false, with a warning telling the user the line to add by hand, if the file can't be written (writing the hosts file needs admin/root). */ export declare function setHostsEntry(config: { ip: string; hostname: string; }): boolean; /** Removes our managed entry for `hostname` (only lines we added, tagged with the marker). No-op if absent or the file can't be written. */ export declare function removeHostsEntry(hostname: string): void; } declare module "sliftutils/misc/https/httpsCerts" { /// /// /// /// import * as forge from "node-forge"; /** NOTE: We also generate the domain *.domain */ export declare const getHTTPSCert: { (key: string): Promise<{ key: string; cert: string; }>; clear(key: string): void; clearAll(): void; forceSet(key: string, value: Promise<{ key: string; cert: string; }>): void; getAllKeys(): string[]; get(key: string): Promise<{ key: string; cert: string; }> | undefined; }; export declare const getAccountKey: (domain: string) => Promise; export declare function parseCert(PEMorDER: string | Buffer): forge.pki.Certificate; export declare function normalizeCertToPEM(PEMorDER: string | Buffer): string; export declare function generateCert(config: { accountKey: string; domain: string; altDomains?: string[]; }): Promise<{ domains: string[]; key: string; cert: string; }>; } declare module "sliftutils/misc/https/node-forge-ed25519" { declare module "node-forge" { declare type Ed25519PublicKey = { publicKeyBytes: Buffer; keyType: string; verify(message: string | Buffer, signature: string): boolean; }; declare type Ed25519PrivateKey = { privateKeyBytes: Buffer; keyType: string; sign(message: string | Buffer): string; }; class ed25519 { static generateKeyPair(): { publicKey: Ed25519PublicKey, privateKey: Ed25519PrivateKey }; static privateKeyToPem(key: Ed25519PrivateKey): string; static privateKeyFromPem(pem: string): Ed25519PrivateKey; static publicKeyToPem(key: Ed25519PublicKey): string; static publicKeyFromPem(pem: string): Ed25519PublicKey; } } } declare module "sliftutils/misc/https/persistentLocalStorage" { export declare function DEV_getKeyStorePath(config: { appName: string; key: string; }): string; export declare function DEV_listKeyStoreApps(key: string): string[]; export declare function getKeyStore(appName: string, key: string): { get(): T | undefined; set(value: T | null): void; }; } declare module "sliftutils/misc/matchFilter" { export declare function matchFilter(filter: { value: string; }, value: string): boolean; } declare module "sliftutils/misc/openrouter" { export type MessageHistory = { role: "system" | "user" | "assistant"; content: string; }[]; export type MessageHistory2 = { role: "system" | "user" | "assistant"; content: string | { type: "image_url"; image_url: { url: string; }; }[]; }[]; export declare function getTotalCost(): number; type OpenRouterOptions = { apiKey?: string; provider?: { sort?: "throughput" | "price" | "latency"; order?: string[]; }; reasoningEffort?: "low" | "medium" | "high"; }; /** IMPORTANT! Make sure to tell the AI to return yaml. */ export declare function yamlOpenRouterCall(config: { model: string; messages: MessageHistory; retries?: number; options?: OpenRouterOptions; onCost?: (cost: number) => void; validate?: (response: T) => void; }): Promise; export declare function simpleAICall(model: string, message: string): Promise; /** The message must request the result to be returned in YAML (we automatically parse this and return an object). */ export declare function simpleAICallTyped(model: string, message: string): Promise; export declare function openRouterCall(config: { model: string; messages: MessageHistory; options?: OpenRouterOptions; onCost?: (cost: number) => void; retries?: number; }): Promise; export declare function openRouterCallBase(config: { model: string; messages: MessageHistory2; options?: OpenRouterOptions; onCost?: (cost: number) => void; retries?: number; }): Promise; export {}; } declare module "sliftutils/misc/ownIPs" { /** Every address this machine can be reached at, or seen as. Both halves, because which one applies depends on who is doing the seeing: something on the same network sees one of our interface addresses, and anything past a NAT sees the address the NAT presents. A machine cannot work the second one out alone, which is why it is asked for. Loopback and the other internal interfaces are left out - nothing outside this machine ever sees us as those - and so is ipv6, because everything consuming this deals in ipv4. A set, because a machine that is not behind a NAT sees its own address in both halves. */ export declare function getOwnIPs(): Promise; } declare module "sliftutils/misc/random" { export declare function getSeededRandom(seed: number): () => number; export declare function shuffle(array: T[], seed: number): T[]; export declare function secureRandom(): number; } declare module "sliftutils/misc/strings" { export declare function ellipsize(text: string, maxLength: number): string; } declare module "sliftutils/misc/types" { export declare function isDefined(value: T | undefined | null): value is T; export declare function freezeObject(value: unknown): unknown; export declare function deepFreezeObject(value: unknown): void; } declare module "sliftutils/misc/yaml" { export declare function parseYAML(text: string): unknown; export declare function stringifyYAML(value: unknown): string; } declare module "sliftutils/misc/yamlBase" { export declare const parse: (input: string) => unknown; export declare const stringify: (input: unknown) => string; } declare module "sliftutils/misc/zip" { import { Zip } from "socket-function/src/Zip"; export { Zip }; } declare module "sliftutils/render-utils/Anchor" { import preact from "preact"; import { URLParam } from "./URLParam"; export declare class Anchor extends preact.Component<{ className?: string; params: ([URLParam, unknown] | [string, string])[]; button?: boolean; } & Omit, "href">> { render(): preact.JSX.Element; } export declare function createLinkRaw(params: ([URLParam, unknown])[]): string; } declare module "sliftutils/render-utils/ButtonSelector" { import preact from "preact"; export declare class ButtonSelector extends preact.Component<{ title?: string; value: T; options: { value: T; title: preact.ComponentChild; isDefault?: boolean; hotkeys?: string[]; }[]; onChange: (value: T) => void; noPadding?: boolean; noDefault?: boolean; noUI?: boolean; classWrapper?: string; }> { render(): preact.JSX.Element; } } declare module "sliftutils/render-utils/DropdownCustom" { import preact from "preact"; import { LengthOrPercentage } from "typesafecss/cssTypes"; export declare class DropdownCustom extends preact.Component<{ class?: string; optionClass?: string; title?: string; value: T; onChange: (value: T, index: number) => void; maxWidth?: LengthOrPercentage; options: { value: T; label: (isOpen: boolean) => preact.ComponentChild; }[]; }> { synced: { isOpen: boolean; tempIndexSelected: number | null; }; onUnmount: (() => void)[]; componentDidMount(): void; componentDidUnmount(): void; render(): preact.JSX.Element; } } declare module "sliftutils/render-utils/FullscreenModal" { import preact from "preact"; export declare function showFullscreenModal(config: { contents: preact.ComponentChildren; onClose?: () => void; }): void; export declare class FullscreenModal extends preact.Component<{ parentState?: { open: boolean; }; onCancel?: () => void; style?: preact.JSX.CSSProperties; outerStyle?: preact.JSX.CSSProperties; }> { render(): preact.JSX.Element; } } declare module "sliftutils/render-utils/GenericFormat" { import preact from "preact"; export declare const errorMessage: string; export declare const warnMessage: string; export type RowType = { [columnName: string]: unknown; }; export type FormatContext = { row?: RowT; columnName?: RowT extends undefined ? string : keyof RowT; }; export type JSXFormatter = (StringFormatters | `varray:${StringFormatters}` | `link:${string}` | ((value: T, context?: FormatContext) => preact.ComponentChild)); type StringFormatters = ("guess" | "string" | "number" | "timeSpan" | "date" | "error" | "link" | "toSpaceCase"); export declare function toSpaceCase(text: string): string; export declare function formatValue(value: unknown, formatter?: JSXFormatter, context?: FormatContext): preact.ComponentChild; export {}; } declare module "sliftutils/render-utils/Input" { import preact from "preact"; export type InputProps = (preact.JSX.HTMLAttributes & { /** ONLY throttles onChangeValue */ throttle?: number; flavor?: "large" | "small" | "none"; focusOnMount?: boolean; textarea?: boolean; /** Update on key stroke, not on blur (just does onInput = onChange, as onInput already does this) */ hot?: boolean; /** Updates arrow keys with modifier behavior to use larger numbers, instead of decimals. */ integer?: boolean; /** Only works with number/integer */ reverseArrowKeyDirection?: boolean; inputRef?: (x: HTMLInputElement | null) => void; /** Don't blur on enter key */ noEnterKeyBlur?: boolean; noFocusSelect?: boolean; inputKey?: string; fillWidth?: boolean; autocompleteValues?: string[]; /** Forces the input to update when focused. Usually we hold updates, to prevent the user's * typing to be interrupted by background updates. * NOTE: "hot" is usually required when using this. */ forceInputValueUpdatesWhenFocused?: boolean; onChangeValue?: (value: string) => void; }); export declare class Input extends preact.Component { onFocusText: string; firstFocus: boolean; elem: HTMLInputElement | null; lastValue: unknown; lastChecked: unknown; onChangeThrottle: undefined | { throttle: number; run: (newValue: string) => void; }; render(): preact.JSX.Element; } } declare module "sliftutils/render-utils/InputLabel" { import preact from "preact"; export type InputProps = (preact.JSX.HTMLAttributes & { /** ONLY throttles onChangeValue */ throttle?: number; flavor?: "large" | "small" | "none"; focusOnMount?: boolean; textarea?: boolean; /** Update on key stroke, not on blur (just does onInput = onChange, as onInput already does this) */ hot?: boolean; /** Updates arrow keys with modifier behavior to use larger numbers, instead of decimals. */ integer?: boolean; /** Only works with number/integer */ reverseArrowKeyDirection?: boolean; inputRef?: (x: HTMLInputElement | null) => void; /** Don't blur on enter key */ noEnterKeyBlur?: boolean; noFocusSelect?: boolean; inputKey?: string; fillWidth?: boolean; autocompleteValues?: string[]; /** Forces the input to update when focused. Usually we hold updates, to prevent the user's * typing to be interrupted by background updates. * NOTE: "hot" is usually required when using this. */ forceInputValueUpdatesWhenFocused?: boolean; onChangeValue?: (value: string) => void; }); export type InputLabelProps = Omit & { label?: preact.ComponentChild; number?: boolean; /** A number, AND, an integer. Changes behavior arrow arrow keys as well */ integer?: boolean; checkbox?: boolean; edit?: boolean; alwaysShowPencil?: boolean; outerClass?: string; maxDecimals?: number; percent?: boolean; editClass?: string; fontSize?: number; tooltip?: string; fillWidth?: boolean; useDateUI?: boolean; }; export declare const startGuessDateRange: number; export declare const endGuessDateRange: number; export declare class InputLabel extends preact.Component { synced: { editting: boolean; editInputValue: string; editUpdateSeqNum: number; }; render(): preact.JSX.Element; } export declare class InputLabelURL extends preact.Component { render(): preact.JSX.Element; } } declare module "sliftutils/render-utils/InputPicker" { import preact from "preact"; export type InputOption = { value: T; label?: preact.ComponentChild; matchText?: string; }; export type FullInputOption = { value: T; label: preact.ComponentChild; matchText: string; }; export declare class InputPickerURL extends preact.Component<{ label?: preact.ComponentChild; options: (string | InputOption)[]; allowNonOptions?: boolean; value: { value: string; }; }> { render(): preact.JSX.Element; } export declare class InputPicker extends preact.Component<{ label?: preact.ComponentChild; picked: T[]; options: InputOption[]; addPicked: (value: T) => void; removePicked: (value: T) => void; allowNonOptions?: boolean; }> { synced: { pendingText: string; focused: boolean; }; render(): preact.JSX.Element; } } declare module "sliftutils/render-utils/LocalStorageParam" { export declare class LocalStorageParamStr { readonly storageKey: string; private defaultValue; private state; lastSetValue: string; constructor(storageKey: string, defaultValue?: string); forceUpdate(): void; get(): string; set(value: string): void; get value(): string; set value(value: string); } } declare module "sliftutils/render-utils/SyncedController" { import { SocketRegistered } from "socket-function/SocketFunctionTypes"; type RemapFunction = T extends (...args: infer Args) => Promise ? { (...args: Args): Return | undefined; promise(...args: Args): Promise; refresh(...args: Args): void; refreshAll(): void; reset(...args: Args): void; resetAll(): void; isLoading(...args: Args): boolean; setCache(cache: { args: Args; result: Return; }): void; } : T; export declare function getSyncedController(controller: T, config?: { /** When a controller call for a write finishes, we refresh all readers. * - Invalidation is global, across all controllers. */ reads?: { [key in keyof T["nodes"][""]]?: string[]; }; writes?: { [key in keyof T["nodes"][""]]?: string[]; }; }): { (nodeId: string): { [fnc in keyof T["nodes"][""]]: RemapFunction; } & { resetAll(): void; refreshAll(): void; anyPending(): boolean; }; resetAll(): void; refreshAll(): void; anyPending(): boolean; rerenderAll(): void; }; export {}; } declare module "sliftutils/render-utils/SyncedLoadingIndicator" { import * as preact from "preact"; export declare class SyncedLoadingIndicator extends preact.Component<{ controller: { anyPending: () => boolean; }; }> { render(): preact.JSX.Element | null; } } declare module "sliftutils/render-utils/Table" { import preact from "preact"; import { JSXFormatter } from "./GenericFormat"; export type ColumnType = undefined | null | { center?: boolean; title?: preact.ComponentChild; formatter?: JSXFormatter; }; export type RowType = { [columnName: string]: unknown; }; export type ColumnsType = { [columnName: string]: ColumnType; }; export type TableType = { columns: { [columnName in keyof RowT]?: ColumnType; }; rows: RowT[]; }; export declare class Table extends preact.Component & { class?: string; cellClass?: string; initialLimit?: number; lineLimit?: number; characterLimit?: number; excludeEmptyColumns?: boolean; getRowFields?: (row: RowT) => preact.JSX.HTMLAttributes; }> { state: { limit: number; }; render(): preact.JSX.Element; } } declare module "sliftutils/render-utils/URLParam" { export declare class URLParam { readonly key: string; private defaultValue; constructor(key: string, defaultValue?: T); valueSeqNum: { value: number; }; get(): T; set(value: T): void; reset(): void; getOverride(value: T): [string, string]; get value(): T; set value(value: T); } export declare function getResolvedParam(param: [URLParam, unknown] | [string, string]): [string, string]; export declare function batchURLParamUpdate(params: ([URLParam, unknown] | [string, string])[]): void; export declare function getCurrentUrl(): string; } declare module "sliftutils/render-utils/asyncObservable" { export declare function asyncCache(getValue: (args: Args) => Promise): { (args: Args): T | undefined; }; } declare module "sliftutils/render-utils/autoMeasure" { export declare function runAutoMeasure(): void; } declare module "sliftutils/render-utils/colors" { export declare const redButton: string; export declare const yellowButton: string; export declare const greenButton: string; export declare const errorMessage: string; export declare const warnMessage: string; export declare const AnchorClass: string; } declare module "sliftutils/render-utils/mobxTyped" { export { observable, runInAction, computed, autorun, onBecomeObserved, onBecomeUnobserved } from "mobx"; export declare function configureMobxNextFrameScheduler(): void; } declare module "sliftutils/render-utils/modal" { import preact from "preact"; export declare function showModal(config: { contents: preact.ComponentChildren; onClose?: () => void; }): { close: () => void; }; export declare function closeAllModals(): void; } declare module "sliftutils/render-utils/niceStringify" { export declare const niceStringifyTrue = ""; export declare const niceStringifyNan = "{NaN}"; export declare const niceStringifyUndefined = "{Undefined}"; export declare function niceStringify(value: unknown): string; export declare function niceParse(str: string | undefined, noSpecialTrue?: boolean): unknown; } declare module "sliftutils/render-utils/observer" { import * as preact from "preact"; import { Reaction } from "mobx"; export declare function observer void): void; componentWillUnmount?(): void; }; }>(Constructor: T): { new (...args: any[]): { reaction: Reaction; componentWillUnmount(): void; render(...args: any[]): preact.ComponentChild; forceUpdate(callback?: () => void): void; }; readonly name: string; } & T; } declare module "sliftutils/storage/ArchivesDisk" { /// /// import { IArchives, ArchiveFileInfo, ArchivesConfig, ChangesAfterConfig, DelConfig, FindConfig, GetConfig, GetInfoConfig, MoveFileConfig, SetConfig, SetLargeFileConfig } from "./IArchives"; export declare class ArchivesDisk implements IArchives { private folder; constructor(folder: string); private filesDir; private uploadsDir; private handles; private largeUploads; private nextLargeUploadId; init: { (): Promise; reset(): void; set(newValue: Promise): void; }; getDebugName(): string; getConfig(): Promise; getChangesAfter2(config: ChangesAfterConfig): Promise; hasWriteAccess(): Promise; private filePath; set(key: string, data: Buffer, config?: SetConfig): Promise; del(key: string, config?: DelConfig): Promise; move(config: MoveFileConfig): Promise; get(key: string, config?: GetConfig): Promise; get2(key: string, config?: GetConfig): Promise<{ data: Buffer; writeTime: number; size: number; } | undefined>; getInfo(key: string, config?: GetInfoConfig): Promise<{ writeTime: number; size: number; } | undefined>; find(prefix: string, config?: FindConfig): Promise; findInfo(prefix: string, config?: FindConfig): Promise; private collectFiles; setLargeFile(config: SetLargeFileConfig): Promise; startLargeUpload(): Promise; /** offset makes the write POSITIONAL instead of appending: a retried part lands exactly where the failed attempt would have, so part retries are idempotent. One upload must use either appends or offsets throughout, never both - the cached handle keeps its first flags, and O_APPEND ignores the position argument. */ appendLargeUpload(id: string, data: Buffer, offset?: number): Promise; finishLargeUpload(id: string, key: string, lastModified?: number): Promise; cancelLargeUpload(id: string): Promise; getURL(path: string): Promise; } export declare function applyFindInfoShape(files: ArchiveFileInfo[], prefix: string, config?: FindConfig): ArchiveFileInfo[]; } declare module "sliftutils/storage/BulkDatabase2/BulkDatabase2" { import { BulkDatabaseBase, ReactiveDeps, BulkDatabase2Config, BulkFileInfoListing, MergeAttemptResult, CompactionPlan } from "./BulkDatabaseBase"; export { BulkDatabaseBase, noopReactiveDeps, bulkDatabase2Timing } from "./BulkDatabaseBase"; export type { ReactiveDeps, StorageFactory, BulkDatabase2Config, BulkFileDetails, BulkFileEntry, BulkFileInfoListing, MergeAttemptResult, MergeSkipReason, CompactionPlan, CompactionStep, CompactionStepKind, CompactionTrigger } from "./BulkDatabaseBase"; /** Per-column on-disk size info, as reported by getColumnInfo/getReaderInfo. */ export type BulkColumnInfo = { column: string; byteSize: number; }; /** A snapshot of the collection's shape (no row data), as reported by getReaderInfo. */ export type BulkReaderInfo = { rowCount: number; totalBytes: number; keyCount: number; sampleKey: string | undefined; columns: BulkColumnInfo[]; }; /** * The full public API of BulkDatabase2 (the static `clearCache()` aside). BulkDatabase2 implements * this; an application that just wants to depend on the surface can type against this interface instead * of reading the implementation. `T` is the row type and must have a string `key`. * * Reads resolve every key/column by the latest write-time across all storage tiers. Writes are * column-merges: a write/update only changes the columns it includes; columns it omits keep their * previous value (clear a column by writing it as `undefined`). */ export interface IBulkDatabase2 { /** The collection name (its folder under the storage root). */ readonly name: string; /** Write one full row (merging its columns onto any existing row for the key). */ write(entry: T): Promise; /** Write many full rows in one batch. */ writeBatch(entries: T[]): Promise; /** Update only the given columns of an existing key (key required). Warns and no-ops if the key isn't present. */ update(entry: Partial & { key: string; }): Promise; /** Update many keys' partial columns in one batch. */ updateBatch(entries: (Partial & { key: string; })[]): Promise; /** Delete a key. */ delete(key: string): Promise; /** Delete many keys in one batch. */ deleteBatch(keys: string[]): Promise; /** All live keys. */ getKeys(): Promise; /** One field's value for a key, or undefined if the key/column isn't set or the key is deleted. */ getSingleField(key: string, column: Column): Promise; /** * Like getSingleField but returns { key, value, time } (the same shape a getColumn entry has), where * time is roughly when the value last changed. undefined only when the key isn't present/live. */ getSingleFieldObj(key: string, column: Column): Promise<{ key: string; value: T[Column]; time: number; } | undefined>; /** A whole column as { key, value, time } for every live key (time ≈ when each value last changed). */ getColumn(column: Column): Promise<{ key: string; value: T[Column]; time: number; }[]>; /** * Synchronous, reactive read of one field. Returns undefined while the base value is still loading * (and re-renders once it arrives, under a mobx observer); reflects pending writes immediately. */ getSingleFieldSync(key: string, column: Column): T[Column] | undefined; /** Sync, reactive counterpart of getSingleFieldObj: { key, value, time } once loaded, else undefined. */ getSingleFieldObjSync(key: string, column: Column): { key: string; value: T[Column]; time: number; } | undefined; /** Synchronous, reactive read of a whole column ({ key, value, time }). undefined while still loading. */ getColumnSync(column: Column): { key: string; value: T[Column]; time: number; }[] | undefined; /** * Reactive: whether (key, column) is loaded yet — true once we know the answer (value, absent, or * deleted), false while it's still loading. getSingleFieldObjSync returns undefined for BOTH "loading" * and "absent", so use this to tell them apart (e.g. show a spinner only when this is false). */ isFieldLoadedSync(key: string, column: Column): boolean; /** Reactive: whether a whole column is loaded yet (see isFieldLoadedSync). */ isColumnLoadedSync(column: Column): boolean; /** * Reactive: true while a merge is rewriting this collection's files (background `maybeMerge` or * an explicit `compact`/`merge`/`tryMergeNow`). Becomes false as soon as the new index is swapped * in — the deferred-delete cleanup window is NOT counted. Use this in a UI to show a per-database * "compacting…" indicator. */ isCompactingSync(): boolean; /** * Whether a row (key) is currently being watched by some reactive observer (getSingleFieldObjSync / * getSingleFieldSync). Lets callers skip per-row work when nothing's watching. Non-reactive query; * returns true if the backend can't tell. */ isKeyWatched(key: string): boolean; /** * Drop all of this collection's in-memory loaded caches and re-trigger every watcher, which re-requests * and reloads from disk. Pending un-flushed writes are kept. Per-collection. */ reloadFromDisk(): void; /** The columns present on disk and their byte sizes (no row data read). */ getColumnInfo(): Promise; /** A cheap snapshot of the collection's shape (row/key counts, total bytes, columns) — no row data. */ getReaderInfo(): Promise; /** * Per-file breakdown of the on-disk files, read fresh from disk each call (latest sizes, including * stream files still being appended). `bytes` is the actual on-disk size. Good for showing collection * size/fragmentation and deciding whether to call tryMergeNow()/compact(). */ getFileInfo(): Promise; /** * Every compaction the files on disk currently call for, without performing any of them. This is the * same plan the background merge pass builds and then executes, so it says exactly what the database * is about to do, to which files, and (via `startTime`) when. * * A step that isn't `ready` is still listed with its `triggers`, so you can see how close it is: each * trigger carries the current `value`, the `threshold` that sets the step off, and their `fraction`. * * Not free: working out the dedup steps walks the key list of every combined file, so this is O(total * keys). Fine to call when showing collection status, not something to poll on a short timer. */ planCompaction(): Promise; /** * Consolidate on-disk files. Optional to call; the database also does this in the background. * Returns whether anything was merged, or (via skipReason) why the pass never ran — another merge * in flight, another tab/process holding the merge lock (with who holds it and when the lock * expires), or nothing on disk to compact. */ compact(): Promise; /** * Whether this collection's storage is served over the network (a remote server) rather than local * disk. Apps can branch on this to adapt to the higher latency. Note: over the network the database * skips automatic background compaction by default — call the static * `BulkDatabase2.enableNetworkCompaction()` once to opt in. */ isRemote(): Promise; /** * Flush buffered stream writes to disk now. Writes are coalesced and flushed on a ramping delay (to * avoid the browser rewriting the whole stream file per write), so a write's promise resolving means * "accepted" (in memory + cross-tab), not necessarily "on disk". Call this to force durability — it's * also run automatically on tab hide/close and before every merge. */ flush(): Promise; /** * Run one merge pass now (the same policy the database runs on a timer): consolidate recent * fragmentation and dedup a key range if it's worth it. Returns whether it merged anything and * whether it bailed because another tab/process holds the merge lock (including the lock's holder * and expiry) — so a scheduler can call this (e.g. every 30 minutes) and tell "nothing to do" from * "someone else is already merging". */ tryMergeNow(): Promise; /** Rewrite everything written in [timeLo, timeHi] into fresh key-sorted bulk file(s). Low-level; most callers want compact() or tryMergeNow(). */ merge(timeLo: number, timeHi: number): Promise; } export declare class MobxReactiveDeps implements ReactiveDeps { private boxes; private observed; private box; observe(signal: string): void; invalidate(signal: string): void; batch(fn: () => void): void; isObserved(signal: string): boolean; } export declare class BulkDatabase2 extends BulkDatabaseBase implements IBulkDatabase2 { constructor(name: string, config?: BulkDatabase2Config); } } declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseBase" { import type { FileStorage } from "../FileFolderAPI"; import { BulkFileInfo, StreamFileInfo } from "./LoadedIndex"; export declare const BULK_ROOT_FOLDER = "bulkDatabases2"; export declare const bulkDatabase2Timing: { streamSealAgeMs: number; visibleMergeIntervalMs: number; mergeSpacingMs: number; looseBulkTriggerBytes: number; looseBulkTriggerFiles: number; streamFoldTriggerBytes: number; streamFileMaxBytes: number; liveWriterProbeMs: number; streamFoldHardLimitBytes: number; writeFlushMaxDelayMs: number; fileSetPollIntervalMs: number; memoryFlushHeapBytes: number; memoryFlushMinCollectionBytes: number; memoryFlushThrottleMs: number; }; export interface ReactiveDeps { observe(signal: string): void; invalidate(signal: string): void; batch(fn: () => void): void; isObserved?(signal: string): boolean; } export declare const noopReactiveDeps: ReactiveDeps; export type StorageFactory = (path: string) => Promise; export type BulkDatabase2Config = { maxTriggerThrottleMs?: number; }; export type MergeSkipReason = "mergeInFlight" | "tabLockHeld" | "fileLockHeld" | "nothingToMerge"; export type MergeAttemptResult = { merged: boolean; skipReason?: MergeSkipReason; lockHolderId?: string; lockExpiresInMs?: number; }; /** One threshold a compaction step is measured against. `value` is where the collection stands now and `threshold` is what sets the step off, so `fraction` (value/threshold) reads as how close it is - 1 or more means met. Deliberately not clamped, so an overdue step reads as how far past due it is. */ export type CompactionTrigger = { name: string; value: number; threshold: number; fraction: number; met: boolean; /** How to render value/threshold. "fraction" values are 0..1. */ unit: "bytes" | "count" | "fraction"; }; /** streamHardLimit and streamFold are phase 1 (stream -> bulk), looseCombine is phase 2 (loose bulk -> combined bulk), dedupAll and dedupKeyGroup are phase 3. */ export type CompactionStepKind = "streamHardLimit" | "streamFold" | "looseCombine" | "dedupAll" | "dedupKeyGroup"; export type CompactionStep = { phase: 1 | 2 | 3; kind: CompactionStepKind; /** Whether this step runs on the next pass. Authoritative: on top of `requires` it accounts for inputs the step needs beyond its thresholds (e.g. two files to combine), so it can be false even with every trigger met. */ ready: boolean; /** Whether every trigger has to be met for this step, or just one of them. */ requires: "any" | "all"; triggers: CompactionTrigger[]; /** When this step's merge starts, given merges are spaced mergeSpacingMs apart. Only set when ready. */ startTime?: number; /** The files this step consumes, as of when the plan was made. */ bulkFiles: BulkFileInfo[]; streamFiles: StreamFileInfo[]; /** Total size of those inputs. */ bytes: number; /** dedupKeyGroup only - the key range the step rewrites. */ keyRange?: { lo: string; hi: string; }; }; /** Every compaction the current file set calls for, in the order a merge pass runs them, plus how close each not-yet-ready one is to its thresholds. */ export type CompactionPlan = { collection: string; /** When the plan was computed; every startTime is measured from here. */ time: number; steps: CompactionStep[]; }; export declare class BulkDatabaseBase { readonly name: string; protected deps: ReactiveDeps; private storageFactory; private config; constructor(name: string, deps: ReactiveDeps, storageFactory: StorageFactory, config?: BulkDatabase2Config); private _reader; private get reader(); private activated; private activate; private setupVisibilityMergeCheck; private subCaches; private pendingAppends; private flushTimer; private flushChain; private currentFlushDelay; private lastWriteTime; private streamFileName; private currentStreamFileName; private currentStreamFileBytes; private mergeInFlight; private lastMergeSkipLogMs; private streamBytesOnDisk; private fileSetPollTimer; private rebuildPromise; private rebuildDirty; private rebuildOptions; private static liveInstances; private static memoryWatchdogStarted; private static lastMemoryFlushMs; private static startMemoryWatchdog; static checkMemoryPressure(usedHeapBytes: number): void; static clearCache(): void; static enableNetworkCompaction(): void; storage: { (): Promise; reset(): void; set(newValue: Promise): void; }; isRemote(): Promise; private streamNeedsFold; private findAbandonedStreams; private automaticCompactionAllowed; isKeyWatched(key: string): boolean; private ensureIndex; private triggerRebuild; private doOneRebuild; reloadFromDisk(): void; private pollFileSet; private readWithRetry; private syncSetup; private applyRemote; write(entry: T): Promise; writeBatch(entries: T[]): Promise; delete(key: string): Promise; deleteBatch(keys: string[]): Promise; private streamAppend; flush(): Promise; private flushPending; private doFlush; private getStreamFileName; private foldOwnStream; update(entry: Partial & { key: string; }): Promise; updateBatch(entries: (Partial & { key: string; })[]): Promise; private listFiles; private processMarkers; private writeBulkFile; private maybeMerge; private mergeSkip; private runLockedMerge; private tryMergeThrottled; tryMergeNow(): Promise; compact(): Promise; merge(timeLo: number, timeHi: number): Promise; private readBulkHeader; private fileLogicalSize; private handleUnreadableFile; private mergeFileSet; private mergeFileSetInner; private canDeleteStream; private mergeSpacingDelay; private splitBulkTier; private analyzeDuplicates; private filesForKeyRange; /** * Every compaction the files on disk currently call for, without performing any of them. A merge pass * builds exactly this and then runs the steps whose `ready` is true, so the plan is precisely what the * database is about to do — and a step that isn't ready still reports its `triggers`, so a caller can * see how close it is (50MB of stream data out of the 64MB that would fold it, and so on). * * O(total keys): the phase 3 steps need every combined file's key list walked. */ planCompaction(): Promise; private testMergeINTERNAL_DO_NOT_CALL; getSingleField(key: string, column: C): Promise; getSingleFieldObj(key: string, column: C): Promise<{ key: string; value: T[C]; time: number; } | undefined>; getColumn(column: C): Promise<{ key: string; value: T[C]; time: number; }[]>; getKeys(): Promise; getSingleFieldSync(key: string, column: C): T[C] | undefined; getSingleFieldObjSync(key: string, column: C): { key: string; value: T[C]; time: number; } | undefined; getColumnSync(column: C): { key: string; value: T[C]; time: number; }[] | undefined; isFieldLoadedSync(key: string, column: C): boolean; isColumnLoadedSync(column: C): boolean; isCompactingSync(): boolean; getColumnInfo(): Promise<{ column: string; byteSize: number; }[]>; getKeyStats(): Promise<{ rawKeys: number; finalKeys: number; wastedKeys: number; duplication: number; readers: number; }>; getReaderInfo(): Promise<{ rowCount: number; totalBytes: number; keyCount: number; sampleKey: string | undefined; columns: { column: string; byteSize: number; }[]; }>; getFileInfo(): Promise; } export type BulkFileDetails = { keys: string[]; minTime: number; maxTime: number; }; export type BulkFileEntry = { name: string; type: "bulk" | "stream"; bytes: number; lastModified: number; getDetails: () => Promise; }; export type BulkFileInfoListing = { files: BulkFileEntry[]; count: number; totalBytes: number; }; } declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseFormat" { /// /// export declare const KEY_COLUMN = "key"; export declare const EMPTY_BUFFER: Buffer; export declare const ABSENT: unique symbol; export type RawCell = { type: number; bytes: Buffer; }; export declare const TYPE_ABSENT_TAG = 14; export type ColumnIndex = { offsets: Uint32Array; types: Uint8Array; readValueBytes: (startRow: number, endRow: number) => Promise; }; export declare function encodeValue(value: unknown): { type: number; bytes: Buffer; }; export declare const TARGET_FILE_BYTES: number; export type RawRow = { key: string; time: number; cells: Map; }; export declare function columnIndexByteLength(rowCount: number): number; export declare function assemblePlannedFile(config: { valueColumns: { name: string; blob: Buffer; }[]; keys: string[]; times: number[]; }): Buffer; export interface BuiltFile { buffer: Buffer; minKey: string; maxKey: string; rowCount: number; } export declare function buildFileBuffer(rows: Record[], times: number[], targetBytes?: number): BuiltFile[]; export declare function buildFileBufferRaw(rows: RawRow[], targetBytes?: number): BuiltFile[]; export type BaseBulkDatabaseReader = { name?: string; rowCount: number; totalBytes: number; minTime: number; maxTime: number; minKey?: string; maxKey?: string; keys: string[]; columns: { column: string; byteSize: number; }[]; keyTimes: Map; deleteTimes?: Map; getColumn: (column: string) => Promise<{ key: string; value: unknown; time: number; }[]>; getRawColumn: (column: string) => Promise>; getColumnIndex: (column: string) => Promise; rowOfKey: (key: string) => number | undefined; getSingleField: (key: string, column: string) => Promise<{ value: unknown; time: number; } | typeof ABSENT>; }; export type BulkHeaderInfo = { rowCount: number; minTime: number; maxTime: number; minKey?: string; maxKey?: string; columns: { column: string; byteSize: number; }[]; }; export declare function loadBulkHeader(getRange: (start: number, end: number) => Promise, totalBytes: number): Promise; export declare function loadBulkDatabase(config: { totalBytes: number; getRange: (start: number, end: number) => Promise; name?: string; }): Promise; } declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseMerge" { /// /// import { BaseBulkDatabaseReader } from "./BulkDatabaseFormat"; type CopyRun = { sourceIdx: number; sourceStartRow: number; sourceEndRow: number; outputByteStart: number; byteLength: number; }; type PlannedOutputColumn = { name: string; offsets: Uint32Array; types: Uint8Array; dataLength: number; runs: CopyRun[]; }; export type PlannedOutputFile = { keys: string[]; times: number[]; minKey: string; maxKey: string; columns: PlannedOutputColumn[]; estimatedFileBytes: number; sourceCounts: Map; }; export type PlannedMergeOutput = { name: string; minKey: string; maxKey: string; rowCount: number; size: number; sources: Map; }; export declare function runPlannedMerge(config: { sources: BaseBulkDatabaseReader[]; sourceNames: string[]; collectionName: string; targetFileBytes?: number; targetBatchBytes?: number; log?: (line: string) => void; writeFile: (data: Buffer) => Promise<{ name: string; size: number; }>; }): Promise<{ outputs: PlannedMergeOutput[]; carriedDeletes: Map; usedSourceNames: Set; }>; export {}; } declare module "sliftutils/storage/BulkDatabase2/BulkDatabaseReader" { import { LoadedIndex } from "./LoadedIndex"; import { WriteOverlay } from "./WriteOverlay"; import type { ReactiveDeps } from "./BulkDatabaseBase"; declare function nullJoin(a: string, b: string): string; export type ReaderConfig = { name: string; deps: ReactiveDeps; maxTriggerThrottleMs?: number; }; export declare class BulkDatabaseReader { private readonly cfg; constructor(cfg: ReaderConfig); index: LoadedIndex | undefined; readonly overlay: WriteOverlay; private dataGen; private columnCache; private pendingSignals; private triggerTimer; private currentTriggerDelay; private lastTriggerTime; get name(): string; get deps(): ReactiveDeps; get dataGeneration(): number; setIndex(newIndex: LoadedIndex, options?: { dropStaleFallback?: boolean; }): void; applyWrite(key: string, row: Record, time: number): void; applyDelete(key: string, time: number): void; isKeyWatched(key: string): boolean; isLiveNow(key: string): boolean; localTime(key: string): number; private compactingCount; beginCompaction(): void; endCompaction(): void; isCompactingSync(): boolean; private notifyOverlayMutation; getKeys(): Promise; getColumn(column: C): Promise<{ key: string; value: T[C]; time: number; }[]>; getSingleField(key: string, column: C): Promise; getSingleFieldObj(key: string, column: C): Promise<{ key: string; value: T[C]; time: number; } | undefined>; getSingleFieldSync(key: string, column: C): T[C] | undefined; getSingleFieldObjSync(key: string, column: C): { key: string; value: T[C]; time: number; } | undefined; getColumnSync(column: C): { key: string; value: T[C]; time: number; }[] | undefined; isFieldLoadedSync(key: string, column: C): boolean; isColumnLoadedSync(column: C): boolean; setEnsureIndex(fn: () => Promise>): void; private ensureIndexFn; private requireIndex; private formatInfo; private invalidateSignal; private flushSignals; } export declare const READER_SIGNALS: { LOAD: string; OVERLAY: string; }; export { nullJoin }; } declare module "sliftutils/storage/BulkDatabase2/LoadedIndex" { import type { FileStorage } from "../FileFolderAPI"; import { BaseBulkDatabaseReader } from "./BulkDatabaseFormat"; import { GetRange } from "./blockCache"; import { StreamEntry } from "./streamLog"; export type BulkFileInfo = { fileName: string; level: number; timestamp: number; }; export type StreamFileInfo = { fileName: string; timestamp: number; ownerId?: string; }; export type StreamReaderCacheEntry = { readSize: number; parsedPos: number; entries: StreamEntry[]; }; export declare class MissingFileError extends Error { } export type ResolvedReader = { rowCount: number; totalBytes: number; keys: string[]; rawKeyCount: number; readerCount: number; columns: { column: string; byteSize: number; }[]; keyTimes: Map; deleteTimes: Map; getColumn: (column: string) => Promise<{ key: string; value: unknown; time: number; }[]>; getSingleField: (key: string, column: string) => Promise<{ value: unknown; time: number; } | undefined>; }; export type SubReaderCaches = { bulk: Map; stream: Map; }; export declare class LoadedIndex { readonly name: string; readonly storage: FileStorage; readonly bulkFiles: BulkFileInfo[]; readonly streamFiles: StreamFileInfo[]; readonly reader: ResolvedReader; readonly streamTimes: Map; readonly streamSizes: Map; readonly streamRowsOnDisk: number; readonly streamBytesOnDisk: number; readonly subCaches: SubReaderCaches; private constructor(); readonly keys: Set; readonly fileSet: Set; private baseColumns; private baseColumnsLoading; private baseFields; private baseFieldsLoading; private staleBaseColumns; private staleBaseFields; get totalBytes(): number; get rowCount(): number; isLive(key: string): boolean; static build(config: { name: string; storage: FileStorage; bulkFiles: BulkFileInfo[]; streamFiles: StreamFileInfo[]; subCaches: SubReaderCaches; onUnreadableFile?: (file: BulkFileInfo, message: string) => Promise; }): Promise>; inheritStaleFrom(prev: LoadedIndex): void; getColumn(column: string): Promise<{ key: string; value: unknown; time: number; }[]>; getSingleField(key: string, column: string): Promise<{ value: unknown; time: number; } | undefined>; ensureBaseColumn(column: string, onLoaded: () => void): void; ensureBaseField(key: string, column: string, onLoaded: () => void): void; getBaseColumn(column: string): { entries: { key: string; value: unknown; time: number; }[]; fresh: boolean; } | undefined; getBaseField(key: string, column: string): { value: { value: unknown; time: number; } | undefined; fresh: boolean; loaded: boolean; }; isBaseColumnLoaded(column: string): boolean; isBaseFieldLoaded(key: string, column: string): boolean; dropLoadedValues(): void; } export declare function makeRawGetRange(storage: FileStorage, fileName: string): Promise<{ rawGetRange: GetRange; size: number; }>; export declare function loadFileReader(name: string, storage: FileStorage, f: BulkFileInfo, cache: Map): Promise; export declare function loadStreamEntries(name: string, storage: FileStorage, streamFiles: StreamFileInfo[], cache: Map): Promise<{ entries: { time: number; fileName: string; entry: StreamEntry; }[]; totalBytes: number; missing: boolean; sizes: Map; }>; export declare function orderStreamEntries(entries: { time: number; fileName: string; entry: StreamEntry; }[]): StreamEntry[]; } declare module "sliftutils/storage/BulkDatabase2/WriteOverlay" { export declare const DELETED: unique symbol; export type OverlayEntry = { time: number; value: Record | typeof DELETED; }; export declare class WriteOverlay { private entries; get size(): number; get(key: string): OverlayEntry | undefined; has(key: string): boolean; keys(): IterableIterator; [Symbol.iterator](): IterableIterator<[string, OverlayEntry]>; writeRow(key: string, row: Record, time: number, wasLive: boolean): { invalidatedColumns: Iterable | "all"; }; deleteKey(key: string, time: number, wasLive: boolean): { invalidatedColumns: Iterable | "all"; }; clear(): void; sweepCovered(authority: (key: string) => number): void; patchColumn(base: { key: string; value: unknown; time: number; }[], column: string): { key: string; value: unknown; time: number; }[]; } } declare module "sliftutils/storage/BulkDatabase2/blockCache" { /// /// export type GetRange = (start: number, end: number) => Promise; export declare function encodeCompressedBlocks(data: Buffer): Buffer; export declare class BlockCache { private blocks; private indexes; clear(): void; evict(fileId: string): void; private touch; private readIndex; open(fileId: string, fileSize: number, rawGetRange: GetRange): Promise<{ uncompressedSize: number; getRange: GetRange; }>; private makeGetRange; } export declare const blockCache: BlockCache; } declare module "sliftutils/storage/BulkDatabase2/mergeLock" { import type { FileStorage } from "../FileFolderAPI"; export type MergeLockInfo = { holderId: string; expiresInMs: number; }; export declare function tryAcquireMergeLock(collection: string, holderId: string): boolean; export declare function peekMergeLock(collection: string): MergeLockInfo | undefined; export declare function releaseMergeLock(collection: string, holderId: string): void; export declare function peekMergeFileLock(storage: FileStorage): Promise; export declare function tryAcquireMergeFileLock(storage: FileStorage, holderId: string): Promise; export declare function startMergeFileLockHeartbeat(storage: FileStorage, holderId: string): () => void; export declare function releaseMergeFileLock(storage: FileStorage, holderId: string): Promise; } declare module "sliftutils/storage/BulkDatabase2/mergeMarkers" { import type { FileStorage } from "../FileFolderAPI"; export type DeleteMarker = { fileName: string; deleteFiles: string[]; replacedBy: string[]; time: number; }; export declare function isMarkerFile(name: string): boolean; export declare function writeDeleteMarker(storage: FileStorage, config: { deleteFiles: string[]; replacedBy: string[]; }): Promise; export declare function readDeleteMarkers(storage: FileStorage, allNames: string[]): Promise; export declare function markerExclusions(markers: DeleteMarker[]): Set; export declare function processDeleteMarkers(name: string, storage: FileStorage, markers: DeleteMarker[], allNames: string[]): Promise; } declare module "sliftutils/storage/BulkDatabase2/streamLog" { /// /// import { BaseBulkDatabaseReader } from "./BulkDatabaseFormat"; export declare const STREAM_EXTENSION = ".stream"; export type StreamEntry = { time: number; row?: Record; deletedKey?: string; }; export declare function frameRows(entries: { time: number; row: Record; }[]): Buffer; export declare function frameDeletes(entries: { time: number; key: string; }[]): Buffer; export declare function parseStream(buffer: Buffer): { entries: StreamEntry[]; badBytes: number; }; export declare function streamReaderFromEntries(entries: StreamEntry[], totalBytes: number): { reader: BaseBulkDatabaseReader; times: Map; }; } declare module "sliftutils/storage/BulkDatabase2/syncClient" { export type RemoteWrite = { key: string; time: number; deleted?: boolean; value?: unknown; }; export declare function registerWriterId(id: string): void; export declare function isSyncSupported(): boolean; export declare function connect(collection: string, onWrite: (write: RemoteWrite) => void, onSeal?: () => void): Promise; export declare function broadcast(collection: string, write: RemoteWrite): void; export declare function queryLiveWriters(collection: string, timeoutMs: number): Promise | undefined>; export declare function broadcastSeal(collection: string): void; } declare module "sliftutils/storage/CBORStorage" { /// /// import { IStorage } from "./IStorage"; export declare class CBORStorage implements IStorage { private storage; constructor(storage: IStorage); get(key: string): Promise; set(key: string, value: T): Promise; remove(key: string): Promise; getKeys(): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; watchResync(callback: () => void): void; reset(): Promise; } } declare module "sliftutils/storage/CachedStorage" { import { StorageSync } from "./StorageObservable"; export declare function newCachedStrStorage(folder: string, getValue: (key: string) => Promise): StorageSync; } declare module "sliftutils/storage/DelayedStorage" { import { IStorage } from "./IStorage"; export declare class DelayedStorage implements IStorage { private storage; constructor(storage: Promise>); get(key: string): Promise; set(key: string, value: T): Promise; remove(key: string): Promise; getKeys(): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; reset(): Promise; watchResync(callback: () => void): void; } } declare module "sliftutils/storage/DiskCollection" { /// /// import { IStorage, IStorageSync } from "./IStorage"; import { StorageSync } from "./StorageObservable"; import { TransactionStorage } from "./TransactionStorage"; export declare class DiskCollection implements IStorageSync { private collectionName; private config?; static getForceNoPrompt(): boolean; static setForceNoPrompt(forceNoPrompt: boolean): void; constructor(collectionName: string, config?: { writeDelay?: number | undefined; cbor?: boolean | undefined; noPrompt?: boolean | undefined; resyncFromDisk?: boolean | undefined; freeze?: "deep" | "shallow" | undefined; beforeWrite?: ((update: { newValue: T; key: string; collection: DiskCollection; }) => void) | undefined; } | undefined); transactionStorage: TransactionStorage | undefined; initStorage(): Promise>; baseStorage: Promise>; private synced; get(key: string): T | undefined; getPromise(key: string): Promise; set(key: string, value: T): void; remove(key: string): void; getKeys(): string[]; getKeysPromise(): Promise; getEntries(): [string, T][]; getValues(): T[]; getValuesPromise(): Promise; getInfo(key: string): { size: number; lastModified: number; } | undefined; reset(): Promise; } export declare class DiskCollectionPromise implements IStorage { private collectionName; private writeDelay?; constructor(collectionName: string, writeDelay?: number | undefined); initStorage(): Promise>; private synced; get(key: string): Promise; set(key: string, value: T): Promise; remove(key: string): Promise; getKeys(): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; reset(): Promise; } export declare class DiskCollectionRaw implements IStorage { private collectionName; constructor(collectionName: string); initStorage(): Promise>; private synced; get(key: string): Promise; set(key: string, value: Buffer): Promise; remove(key: string): Promise; getKeys(): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; reset(): Promise; } export declare class DiskCollectionRawSynced { private collectionName; constructor(collectionName: string); initStorage(): Promise>; private synced; get(key: string): Buffer | undefined; getPromise(key: string): Promise; set(key: string, value: Buffer): void; getKeys(): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; reset(): Promise; } export declare class DiskCollectionRawBrowser { private collectionName; constructor(collectionName: string); initStorage(): Promise>; private synced; get(key: string): Buffer | undefined; getPromise(key: string): Promise; set(key: string, value: Buffer): void; getKeys(): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; reset(): Promise; } export declare function newFileStorageBufferSyncer(folder?: string): StorageSync; export declare function newFileStorageJSONSyncer(folder?: string): StorageSync; } declare module "sliftutils/storage/FileFolderAPI" { /// /// import { IStorageRaw } from "./IStorage"; import { RemoteOptions } from "./remoteFileStorage"; declare global { interface Window { showSaveFilePicker(config?: { types: { description: string; accept: { [mimeType: string]: string[]; }; }[]; }): Promise; showDirectoryPicker(): Promise; showOpenFilePicker(config?: { types: { description: string; accept: { [mimeType: string]: string[]; }; }[]; }): Promise; } interface FileSystemDirectoryHandle { requestPermission(config?: { mode: "read" | "readwrite"; }): Promise; } } export type FileWrapper = { readonly kind: "file"; readonly name: string; getFile(): Promise<{ size: number; lastModified: number; arrayBuffer(): Promise; slice(start: number, end: number): { arrayBuffer(): Promise; }; }>; createWritable(config?: { keepExistingData?: boolean; }): Promise<{ seek(offset: number): Promise; write(value: Buffer): Promise; close(): Promise; }>; getURL?(): Promise; }; export type DirectoryWrapper = { readonly kind: "directory"; readonly name: string; readonly fullPath?: string; readonly isRemote?: boolean; removeEntry(key: string, options?: { recursive?: boolean; }): Promise; getFileHandle(key: string, options?: { create?: boolean; }): Promise; getDirectoryHandle(key: string, options?: { create?: boolean; }): Promise; entries(): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>; [Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>; }; export declare function setFileAPIKey(key: string): void; export declare function setStorageRootOverride(handle: FileSystemDirectoryHandle | undefined): void; export declare class NodeJSFileHandleWrapper implements FileWrapper { private filePath; constructor(filePath: string); readonly kind: "file"; get name(): string; getFile(): Promise<{ size: number; lastModified: number; arrayBuffer: () => Promise; slice: (start: number, end: number) => { arrayBuffer: () => Promise; }; }>; getURL(): Promise; createWritable(config?: { keepExistingData?: boolean; }): Promise<{ seek: (offset: number) => Promise; write: (value: Buffer) => Promise; close: () => Promise; }>; } export declare class NodeJSDirectoryHandleWrapper implements DirectoryWrapper { private rootPath; constructor(rootPath: string); readonly kind: "directory"; get name(): string; get fullPath(): string; entries(): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>; removeEntry(key: string, options?: { recursive?: boolean; }): Promise; getFileHandle(key: string, options?: { create?: boolean; }): Promise; getDirectoryHandle(key: string, options?: { create?: boolean; }): Promise; [Symbol.asyncIterator](): AsyncIterableIterator<[string, FileWrapper | DirectoryWrapper]>; } export declare function usePrivateFileSystem(): void; export declare function isPrivateFileSystemActive(): boolean; export declare function listPrivateFolders(): Promise; export declare function pickPrivateFolder(name: string): void; export declare const getDirectoryHandle: { (): Promise; reset(): void; set(newValue: Promise): void; }; export declare const getFileStorageNested: { (key: string): Promise; clear(key: string): void; clearAll(): void; forceSet(key: string, value: Promise): void; getAllKeys(): string[]; get(key: string): Promise | undefined; }; export declare const getFileStorageNested2: { (key: string): Promise; clear(key: string): void; clearAll(): void; forceSet(key: string, value: Promise): void; getAllKeys(): string[]; get(key: string): Promise | undefined; }; export declare const getFileStorage: { (): Promise; reset(): void; set(newValue: Promise): void; }; export declare function resetStorageLocation(): void; export type NestedFileStorage = { hasKey(key: string): Promise; getStorage(key: string): Promise; removeStorage(key: string): Promise; getKeys(includeFolders?: boolean): Promise; }; export type FileStorage = IStorageRaw & { folder: NestedFileStorage; isRemote?: boolean; }; export declare function wrapHandle(handle: DirectoryWrapper): FileStorage; export declare function getFileURL(file: FileWrapper): Promise; export declare function disposeFileURL(url: string): void; export declare function getRemoteFileStorageFactory(url: string, password: string, options?: RemoteOptions): (pathStr: string) => Promise; export declare function tryToLoadPointer(pointer: string): Promise; } declare module "sliftutils/storage/IArchives" { /// /// export declare const MAX_LAST_MODIFIED_FUTURE: number; export declare const IMMUTABLE_CACHE_TIME: number; export declare function assertValidLastModified(lastModified: number): void; /** Every file-addressed operation checks this at its entry point, so an empty name fails right where it was passed - with the caller in the stack - instead of surfacing as a baffling backend rejection after the retry loops are done with it. */ export declare function validateFileName(fileName: string, operation: string): void; export type RemoteConfig = { version?: number; sources: RemoteConfigBase[]; }; /** string arguments will be a url, looking like: https://storage2.vidgridweb.com:4445/file/exampleaccount/examplebucket/storage/storagerouting.json https://f002.backblazeb2.com/file/querysubtest-com-public-immutable/storage/storagerouting.json - These map to { url }, with the type inferred from the url - Hosted urls are /file///..., backblaze urls are /file//... NOTE: If we do not have right access to these, then it becomes a read-only IArchives, where we solely read using the url form (which might throw due to not having access as well). UNLESS Our configuration explicitly has public: false, in which case, we don't even hit the URL and we throw on access. NOTE: If we're in the browser, we should allow downloading the files via the URL form (if it's a public bucket), however, we won't allow writing, because their servers do not allow secure browser writes. */ export type RemoteConfigBase = string | SourceConfig; /** One configured source in a routing config: a hosted (our storage server) or backblaze entry. Requests carry the exact SourceConfig they selected, and the server matches it against its own entries to pick the backing store. */ export type SourceConfig = HostedConfig | BackblazeConfig; export type CommonConfig = { /** * The storage this entry names, as opposed to the rules for using it. Every entry with the same * name (for the same account and bucket) IS the same storage: one folder on the server, one * store, one index - however many entries there are and whatever their windows and routes say. * Everything about WHEN and WHICH KEYS (validWindow, route) is policy layered on top of it, and * changing that policy never moves data. * * Letters, numbers, underscore, dash and periods, up to 64 characters - so a host or a version * can be used as-is. It is the folder name, so it must stay unique and must never be reused for * different storage: * pointing two unrelated entries at one name merges their data, and re-using a retired name * hands the new entry the retired one's files. Deciding that is the developer's job - the server * only ever does what the name says. */ name: string; /** By default a server hosting this bucket eagerly copies this source's full contents onto its own disk (on top of the lazy read-through caching). Set this to be a front end for a very large database without copying the full database - reads still down-cache individual files on demand. */ noFullSync?: boolean; /** Bytes of read-cache this server's disk may hold; least-recently-used files are deleted from disk to stay under it (only ever when another source verifiably holds the file - the only copy is never deleted). Requires noFullSync (a full copy can't be bounded). */ readerDiskLimit?: number; /** The write times ([startMs, endMs]) this source is valid for (see ArchivesSource.validWindow for the synchronization semantics). Required on object configs: configuration changes must be SCHEDULED (a new source becomes valid at a future time while the old one's window ends), not flipped instantly. Plain URL-string sources default to FULL_VALID_WINDOW - once you're writing object configs, you're doing something complicated enough to think about when things change. */ validWindow: [number, number]; /** Sharding: the fraction of the key space this source handles, as [start, end) over [0, 1) (keys are routed by getRoute in remoteConfig.ts). Defaults to FULL_ROUTE (unsharded). At every point in time the sources' routes must fully cover [0, 1), or some keys could never be read. */ route?: [number, number]; /** Set on entries injected into the in-memory config by an overlay (a deploy switchover's alternate-port window). Never written to disk: resolveIntermediateSources strips these and rejoins the windows around them, which is also how a client tells whether an update is a real configuration change or just an overlay. The VALUE is the url of the source this intermediate was split out of (its alternate-port view) - so a request naming the intermediate still resolves to the ORIGINAL source, even after the intermediate rejoins and the entry is gone. */ intermediate?: string; }; export type HostedConfig = CommonConfig & { type: "remote"; url: string; public?: boolean; fast?: boolean; writeDelay?: number; immutable?: boolean; }; export type BackblazeConfig = CommonConfig & { type: "backblaze"; url: string; public?: boolean; immutable?: boolean; allowedOrigins?: string[]; }; export declare const FULL_VALID_WINDOW: [number, number]; export type GetConfig = { range?: { start: number; end: number; }; /** Read ONLY from the primary source - the one writes would target - instead of falling back across the redundant sources. Use this when you want your reads and writes to be somewhat atomic: there will still be issues with the round trip, but without it you could talk to a completely different node and get a much older value. Most reads aren't followed by a write though, so for most cases it's better to get a value than to have to wait (or even throw) when the primary node is not available. */ noFallbacks?: boolean; /** Store-to-store call: the serving node never consults its OTHER sources - chasing its own remote holders while answering another store is how infinite get loops between stores form (A asks B, B's index points back at A, ...). That is the flag's ENTIRE meaning: no fallbacks, nothing else. The read is otherwise fully correct - the node's index still gates it (a key its index says is deleted answers as deleted, never as the history bytes still sitting on its disk). No window or route checks on reads. */ internal?: boolean; /** Also return size-0 results (tombstones - an empty file IS a missing file) instead of treating them as absent. Off by default, matching getInfo's flag of the same name. Synchronization passes this so a DELETED file (with its write time) is distinguishable from a file that never existed. */ includeTombstones?: boolean; /** Reads files that are MARKED for deletion (deleted, but with their bytes still in the deletion history - see SetConfig.undelete for restoring them). The actual content comes back, unlike includeTombstones, which only reports that a deletion happened. */ includeMarked?: boolean; /** Read from EXACTLY this source - its config url, as ArchivesChain.getFileSources lists them - with no fallback to any other. For comparing the copies different sources hold (combine with internal to read only that server's own disk, skipping its holder resolution). Multi-source archives only; throws when no configured source has the url. */ sourceUrl?: string; /** How many extra times the WHOLE operation is retried after every source in a pass failed - any error counts (the wrong-window/route markers still get their config re-resolve first). Only applies to fallback dispatch (multi-source, not noFallbacks), where it defaults to 3; the noFallbacks/write-node path already retries on its own deadline. Multi-part uploads additionally retry per part regardless of this. */ retries?: number; }; export type FindConfig = { shallow?: boolean; type?: "files" | "folders"; /** Also list files MARKED for deletion (see GetConfig.includeMarked). */ includeMarked?: boolean; /** Listings normally come ONLY from the authoritative sources (the same nodes writes go to - read-your-writes). With fallbacks, a failing shard's routes are covered by the next source holding them (e.g. a wide read replica) instead of the call failing - high availability at the cost of possibly missing just-written data. Single-source archives ignore the flag. */ fallbacks?: boolean; /** Store-to-store listing: only entries whose bytes the node ITSELF holds - never entries its index redirects to its own other sources. A peer reads with GetConfig.internal (which never chases those redirects), so listing a redirect would just make the peer flag the file missing, purge it, re-list it, and loop forever; the peer hears about such files from the source actually holding them instead. */ internal?: boolean; }; export type DelConfig = { /** Stamps the deletion (its tombstone) with this write time instead of now. Synchronization passes the ORIGINAL deletion time, so deletion ordering survives propagation exactly like any other write's ordering. */ lastModified?: number; /** See SetConfig.internal. */ internal?: boolean; /** See SetConfig.noChecks. */ noChecks?: boolean; /** See SetConfig.fallbacks. */ fallbacks?: boolean; /** See GetConfig.retries. */ retries?: number; }; export type GetInfoConfig = { /** Also report size-0 entries (tombstones - an empty file IS a missing file). Off by default, so a deleted key reports undefined, matching get. Synchronization-style callers pass this when they need a deletion's write time (e.g. to compare it against a write they are about to make). */ includeTombstones?: boolean; /** See GetConfig.noFallbacks: answer ONLY from the primary source (the one writes would target) instead of falling back across the redundant sources. */ noFallbacks?: boolean; /** See GetConfig.retries. */ retries?: number; /** See GetConfig.sourceUrl: answer from EXACTLY this source. */ sourceUrl?: string; }; export type ChangesAfterConfig = { time: number; /** Only keys routing into one of these [start, end) ranges. Only scanning passes this - it lets a store syncing a partial shard ask for just its slice. */ routes?: [number, number][]; /** See FindConfig.internal - the change feed is a listing too, and redirect entries fail a peer's internal reads the same way. Deletions are always reported (they are index-only, there are no bytes to hold). */ internal?: boolean; }; export type SetConfig = { /** The write time to stamp (see IArchives.set). ROUNDED to whole milliseconds by every implementation - the disk can't store fractional milliseconds anyway (utimes round-trips whole ms), so a fractional stamp could never be reproduced by propagation and would compare "newer" than its own copies forever. Rounded rather than floored because utimes goes through a seconds double and can read back a hair below the stamped millisecond (see ArchivesDisk.get2). */ lastModified?: number; /** Makes the write acceptable on immutable targets: an existing path is simply kept (immutability wins - nothing is overwritten) instead of the write throwing. Requires lastModified. Synchronization MUST pass this on every push - a plain set throws on immutable targets, which would abort reconciliation whenever one source in a chain is immutable. */ forceSetImmutable?: boolean; /** Skips REDUNDANT target-side safety reads around the write (backblaze: the post-upload existence poll). It does NOT skip checks that are the target's only ordering guard: backblaze's pre-write comparison stays, because b2 has no server of ours enforcing only-take-the-latest - without it a stale push lands over a newer value or tombstone and b2's self-stamped upload time launders it into the newest copy in the system (global resurrection). Hosted targets re-check server-side, so their client-side shortcuts are safe. */ noChecks?: boolean; /** Store-to-store push: the receiving node writes purely to its own disk and index, with NO downstream fan-out (the pushing store owns propagation - fanning its pushes back out is how write loops between stores form). Window and route ARE still checked: the stamp must fall inside one of the receiver's configured windows and routes, so a confused peer cannot stuff data onto a node that was never meant to hold it. Requires lastModified. */ internal?: boolean; /** Writes normally go ONLY to the write node (the first current-window source covering the key), retrying it even while it is down - consistent, but unavailable when that node is. With fallbacks, the write node is still tried first, but on failure the write lands on the next current-window source covering the key (synchronization moves it to the write node later) - availability at the cost of reads possibly missing the write until it propagates. Single-source archives ignore the flag. */ fallbacks?: boolean; /** See GetConfig.retries. */ retries?: number; /** The set is not a write at all: it RESTORES a file marked for deletion, flipping its index entry back to live (with a fresh write time, so the restore outranks the deletion everywhere it propagated) - the bytes never left the disk, so reads just work again. The data buffer is ignored (a 1-byte placeholder satisfies the empty-buffer rule); use IArchives.undelete rather than passing this yourself. Throws when the key has no marked deletion to restore (its history was dropped, or it was never deleted). */ undelete?: boolean; }; /** setLargeFile's config: a SetConfig (it IS a set - the same immutability, ordering, internal, and fallbacks rules apply) plus the stream carrying the bytes. */ export type SetLargeFileConfig = SetConfig & { path: string; getNextData(): Promise; /** Rewinds the stream to its first byte. Without it the write gets exactly ONE attempt: a retry (a fallback source, or the write node coming back) would upload whatever is left of an already-consumed stream as if it were the whole file. Callers holding the data (a buffer, or a source they can re-read) always pass it - a large set with fallbacks is only as available as this. */ restartStream?(): Promise | void; }; export type ArchiveFileInfo = { path: string; createTime: number; size: number; }; export type SyncActivity = { type: "metadataScan" | "fullSync"; sourceDebugName: string; startTime: number; doneFiles?: number; totalFiles?: number; doneBytes?: number; totalBytes?: number; }; export type ArchivesConfig = { supportsChangesAfter?: boolean; remoteConfig?: RemoteConfig; index?: { fileCount: number; byteCount: number; }; /** Files MARKED for deletion (deleted, bytes still kept as history - see SetConfig.undelete): how many, how big, and the delete time of the oldest one - which is how far back the deletion history reaches. */ markedIndex?: { fileCount: number; byteCount: number; oldestDeleteTime?: number; }; indexSources?: { debugName: string; fileCount: number; byteCount: number; }[]; readerDiskLimit?: number; syncing?: SyncActivity[]; }; export type ArchivesSource = { source: IArchives; /** The persistent identity of the endpoint: its routing URL (hosted/backblaze), or the disk folder path for the base disk source. The store persists this (via its append-only sources list) as IndexEntry.sourcesListIndex, so it must mean the same endpoint forever. */ url: string; validWindows: [number, number][]; route?: [number, number]; noFullSync?: boolean; intermediate?: string; sourceConfig?: SourceConfig; identity?: string; }; export declare const STORAGE_WRONG_VALID_WINDOW = "REMOTE_STORAGE_WRONG_VALID_WINDOW_a7c1f04e"; export declare const STORAGE_WRONG_ROUTE = "REMOTE_STORAGE_WRONG_ROUTE_c94d2e17"; export declare const STORAGE_NOT_CONFIGURED = "REMOTE_STORAGE_NOT_CONFIGURED_e51b7d92"; export declare const FULL_ROUTE: [number, number]; export declare const VARIABLE_SHARD = "VARIABLE_SHARD_f0234jfah08fgyhfgyssdds83nmp"; export declare function windowAcceptsWrites(validWindow: [number, number] | undefined): boolean; export declare function windowsAcceptWrites(validWindows: [number, number][]): boolean; export declare const LARGE_SET_THRESHOLD: number; /** The setLargeFile stream over an in-memory buffer, in LARGE_SET_THRESHOLD slices - how set transparently becomes setLargeFile for large buffers. Spread into the config: it provides both getNextData and restartStream (the buffer is still held, so a retry costs nothing). */ export declare function bufferChunkStream(data: Buffer): { getNextData(): Promise; restartStream(): void; }; export { copyArchiveFile } from "./archiveHelpers"; /** move's config. There is deliberately no lastModified: the destination is ALWAYS stamped fresh (see IArchives.move) - a move is a new write at the new path, and a preserved old stamp is how a moved file loses to a stale tombstone there and vanishes. */ export type MoveFileConfig = { fromPath: string; toPath: string; }; export type ArchivesSyncSourceStatus = { debugName: string; validWindows: [number, number][]; route?: [number, number]; noFullSync?: boolean; supportsChangesAfter: boolean; initialScanComplete: boolean; scannedCount: number; }; export type ArchivesSyncStatus = { allScansComplete: boolean; indexSize: number; sources: ArchivesSyncSourceStatus[]; }; export interface IArchives { getDebugName(): string; /** Whether writes would be accepted (credentials exist, the account trusts this machine, etc). Checked without writing anything. */ hasWriteAccess(): Promise; /** * Reads automatically fall back across the redundant sources unless config.noFallbacks is set. * A fallback copy can lag the write target, so a caller reading state in order to mutate it * (e.g. x++), where acting on previous state would cause big issues, should pass noFallbacks - * and try/catch the read, handling the catch case (a down primary is retried for a while, then * throws instead of degrading to a stale copy). */ get(fileName: string, config?: GetConfig): Promise; /** See get for the fallback semantics (and when to pass noFallbacks). url is the config URL of the source that answered - the authority the data (or the "does not exist") came from. Multi-source implementations (ArchivesChain) ALWAYS return an object: when the value doesn't exist there is still a server saying it doesn't exist, so they return { url } alone rather than undefined. Single-source backends return undefined for absent (they ARE the authority). */ get2(fileName: string, config?: GetConfig): Promise<{ data: Buffer; writeTime: number; size: number; url?: string; } | { data?: undefined; writeTime?: undefined; size?: undefined; url: string; } | undefined>; /** * lastModified stamps the write with that last-write time instead of now. If it is OLDER than * the file's current last-write time the write no-ops (so delayed / synchronized writes can * never clobber newer data). Times more than 15 minutes in the future are rejected. * * Returns the full key actually written - identical to fileName, EXCEPT for keys containing * VARIABLE_SHARD, where the shard value is materialized into the key (picked by shard latency, * see ArchivesChain) and the caller needs the returned key to ever read the value back. */ /** * THROWS on an empty buffer: an empty file IS a deletion in this system (the tombstone), so a * set-empty would read back as "the file is gone" - which is just asking for problems. If you * want the file deleted, call del; deletions take their own path. */ set(fileName: string, data: Buffer, config?: SetConfig): Promise; del(fileName: string, config?: DelConfig): Promise; /** Moves a file to a new path within THIS archives, backend-side where the backend can (backblaze copies server-side, disk renames, the storage server relocates node-side) - the bytes never travel through the caller. The destination is stamped with a FRESH write time, even when the underlying operation (a rename) would preserve the old one, so the moved file cannot immediately lose to something newer sitting at its new path (e.g. the tombstone of an earlier deletion there); the source is then deleted, exactly like del. THROWS when the source file does not exist. Optional - callers go through moveArchiveFile (archiveHelpers.ts), which falls back to copy + confirm + delete. */ move?(config: MoveFileConfig): Promise; /** Restores a deleted file whose bytes are still in the deletion history (see SetConfig.undelete, which this rides on). Only index-backed stores keep a deletion history, so only they support this. THROWS when there is nothing to restore. */ undelete?(fileName: string): Promise; /** Streams a file too large to hold in memory. getNextData returns undefined when done. This only needs to be called when you CANNOT materialize the entire file in memory - if you can, just call set: above LARGE_SET_THRESHOLD it streams through setLargeFile internally, keeping the client responsive and not overwhelming the server. The rest of the config is a plain SetConfig and means exactly what it means on set (that is what makes a large set behave like a small one instead of quietly losing immutability, ordering, internal, or fallbacks semantics as the file crosses the threshold); backends that stamp their own times (backblaze) accept and ignore lastModified. THROWS when the stream produces no data at all - same rule as set: an empty file IS a deletion and would read back as missing. */ setLargeFile(config: SetLargeFileConfig): Promise; /** writeTime is the last-write time — see ArchiveFileInfo.createTime, which is the same value. url as in get2. Size-0 entries (tombstones) report undefined unless config.includeTombstones. */ getInfo(fileName: string, config?: GetInfoConfig): Promise<{ writeTime: number; size: number; url?: string; } | undefined>; /** * Empty (size-0) files are NEVER returned by index-backed stores (BlobStore, and therefore the * chain): an empty file IS a missing file - the tombstone of a deletion. If you want a marker * file that shows up in listings, add some content to it. Raw sources (disk, backblaze) DO list * their empty files - that is how scans learn of deletions - but nothing built on the index * ever surfaces them. */ find(prefix: string, config?: FindConfig): Promise; /** See find for the empty-file (tombstone) rule. */ findInfo(prefix: string, config?: FindConfig): Promise; /** Only works for public buckets (private buckets are API-access only). */ getURL(path: string): Promise; /** The bucket's configuration, which tells whether the optional functions are supported. */ getConfig(): Promise; /** * All files changed after config.time, optionally restricted to keys routing into one of * config.routes (used by scanning, so partially-overlapping shards only receive their slice). * When getConfig().supportsChangesAfter, this is backed by an index (fast, and deletions ARE * reported, as size-0 tombstone entries). Every other backend emulates it: a full findInfo * listing filtered in memory - correct, but no cheaper than the listing itself. */ getChangesAfter2(config: ChangesAfterConfig): Promise; /** Synchronization introspection, for backends that synchronize from sources (see BlobStore). */ getSyncStatus?(): Promise; } } declare module "sliftutils/storage/IStorage" { /// /// export type IStorageSync = { get(key: string): T | undefined; set(key: string, value: T): void; remove(key: string): void; getKeys(): string[]; getValues(): T[]; getEntries(): [string, T][]; getInfo(key: string): { size: number; lastModified: number; } | undefined; reset(): Promise; }; export type IStorage = { get(key: string): Promise; set(key: string, value: T): Promise; remove(key: string): Promise; getKeys(): Promise; getInfo(key: string): Promise; reset(): Promise; watchResync?: (callback: () => void) => void; }; export type IStorageRaw = { get(key: string): Promise; getRange(key: string, config: { start: number; end: number; }): Promise; append(key: string, value: Buffer): Promise; set(key: string, value: Buffer): Promise; remove(key: string): Promise; getKeys(includeFolders?: boolean): Promise; getInfo(key: string): Promise; reset(): Promise; }; } declare module "sliftutils/storage/IndexedDBFileFolderAPI" { import { FileStorage } from "./FileFolderAPI"; export declare const getFileStorageIndexDB: { (): Promise; reset(): void; set(newValue: Promise): void; }; } declare module "sliftutils/storage/JSONStorage" { /// /// import { IStorage } from "./IStorage"; export declare class JSONStorage implements IStorage { private storage; constructor(storage: IStorage); get(key: string): Promise; set(key: string, value: T): Promise; remove(key: string): Promise; getKeys(): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; watchResync(callback: () => void): void; reset(): Promise; } } declare module "sliftutils/storage/LogMap" { /** A live value: what was stored, when it was written (the caller's ordering), and when we last changed it (ours). */ export type LogEntry = { value: T; time: number; changedAt: number; }; /** A deleted key: when it was deleted, and when we learned. The value is gone; the time is the point of a tombstone. */ export type LogTombstone = { time: number; changedAt: number; }; export declare class TransactionFile { private filePath; constructor(filePath: string); private values; private deleted; private logRecords; private pending; private flushTimer; private writeChain; /** Reads the log and replays it into memory. Every other method assumes this has finished. */ load: { (): Promise; reset(): void; set(newValue: Promise): void; }; /** The live value, or undefined when the key does not exist here (deleted included - a deletion is an absence, see getDeleted for its time). */ get(key: string): LogEntry | undefined; /** When the key was deleted, if it was. Absent both here and in get means we have never heard of it. */ getDeleted(key: string): LogTombstone | undefined; /** The time the key last changed either way, or 0 if we have never heard of it - what a new write has to beat. */ timeOf(key: string): number; /** O(1), and counts only what exists. */ get size(): number; get deletedSize(): number; /** Live values only. Live, in insertion order - deleting during iteration is safe (JS skips entries removed before they are reached), which is what the passes that walk everything and prune as they go rely on. */ entries(): IterableIterator<[string, LogEntry]>; /** The tombstones, which is a much smaller walk than the values - so expiring them, or listing what was deleted since some time, costs what it should. */ deletedEntries(): IterableIterator<[string, LogTombstone]>; /** Stores a value as of `time`. Returns false when something at least as new is already here, in which case nothing changed - an out-of-order write is not an error, it is just late. */ set(key: string, value: T, time: number): boolean; /** Deletes as of `time`, keeping the tombstone. Returns false when something at least as new is already here. */ delete(key: string, time: number): boolean; /** Forgets the key entirely, tombstone included - for a tombstone old enough that nobody needs to hear about the deletion any more, and for an entry that turned out never to have existed. Not a deletion: it leaves nothing behind to propagate. */ purge(key: string): void; private applySet; private applyDelete; private append; private scheduleFlush; /** Writes everything pending (rewriting the log first if it has grown too far past what it describes). */ flush(): Promise; private directory; private write; private appendPending; private compact; } } declare module "sliftutils/storage/PendingManager" { import preact from "preact"; export declare function setPending(group: string, message: string): void; export declare function hasPending(): boolean; export declare class PendingDisplay extends preact.Component { render(): preact.JSX.Element; } } declare module "sliftutils/storage/PendingStorage" { import { IStorage } from "./IStorage"; export declare class PendingStorage implements IStorage { private pendingGroup; private storage; pending: Map; constructor(pendingGroup: string, storage: IStorage); get(key: string): Promise; set(key: string, value: T): Promise; remove(key: string): Promise; getKeys(): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; private watchPending; private updatePending; reset(): Promise; watchResync(callback: () => void): void; } } declare module "sliftutils/storage/PrivateFileSystemStorage" { /// /// import { IStorageRaw } from "./IStorage"; export declare class PrivateFileSystemStorage implements IStorageRaw { private path; private rootHandle; constructor(path: string); private ensureInitialized; private directoryExists; private getDirectoryHandle; private resolveKey; private getFileHandle; private fileExists; get(key: string): Promise; getRange(key: string, config: { start: number; end: number; }): Promise; set(key: string, value: Buffer): Promise; append(key: string, value: Buffer): Promise; remove(key: string): Promise; getKeys(): Promise; getInfo(key: string): Promise; reset(): Promise; } } declare module "sliftutils/storage/StorageObservable" { import { IStorage, IStorageSync } from "./IStorage"; export declare const storagePendingAccesses: { value: number; }; export declare class StorageSync implements IStorageSync { storage: IStorage; private config?; cached: import("mobx").ObservableMap; infoCached: import("mobx").ObservableMap; keys: Set; synced: { keySeqNum: number; }; constructor(storage: IStorage, config?: { freeze?: "deep" | "shallow" | undefined; beforeWrite?: ((update: { newValue: T; key: string; collection: StorageSync; }) => void) | undefined; } | undefined); get(key: string): T | undefined; set(key: string, value: T): void; remove(key: string): void; private loadedKeys; getKeys(): string[]; getInfo(key: string): { size: number; lastModified: number; } | undefined; getValues(): T[]; getEntries(): [string, T][]; getPromise(key: string): Promise; private pendingGetKeys; getKeysPromise(): Promise; reload(): void; reloadKeys(): void; reloadKey(key: string): void; reset(): Promise; } export declare function waitUntilNextLoad(): Promise; } declare module "sliftutils/storage/StorageObservableAsync" { /** Reruns the code until all StorageSyncs accessed have loaded their values. Not efficient,although will usually be O(values accessed), just due to how loading works (it won't be quadratic). */ export declare function rerunCodeUntilAllLoaded(code: () => T): Promise; } declare module "sliftutils/storage/StreamingLogs" { /// /// /** Everything a log file's NAME says about it (plus its on-disk size). Times bound the entries roughly: from is when its process started writing it, to is when it was compressed. */ export type LogFileInfo = { name: string; /** Bytes on disk (compressed bytes for compressed files) */ size: number; pid: number; processStartTime: number; threadId: string; /** When the process started writing this file */ startTime: number; /** When the file was compressed - absent while it is still being streamed to */ endTime?: number; /** How many entries it holds - only counted at compression, so absent on live files */ entryCount?: number; compressed: boolean; /** Whether the writing process is still alive (always false for compressed files) */ active: boolean; }; /** * A searcher over one log file (as readFileCompressed returns it - always LZ4) that never decodes * the whole thing: JSON escapes line breaks inside strings, so the only ACTUAL line breaks in the * file are the separators between log statements - a plain substring search over the raw text, * bounded out to the surrounding line breaks, finds exactly the matching statements, and only THOSE * are decoded and returned as objects. Every search string must appear in a statement's raw JSON for * it to match (so search for values as they are encoded - e.g. quoted). */ export declare function createLogSearcher(data: Buffer): (searches: string[]) => unknown[]; /** Decodes a log file's bytes (as readFileCompressed returns them - always LZ4) back into the logged objects. A torn final line (the writer crashed mid-append) is skipped. */ export declare function decodeLogFile(data: Buffer): unknown[]; export declare class StreamingLogs { private config; constructor(config: { folder: string; /** Included in every file name - see misc/https/certs.ts getOwnThreadId. Processes without one write "none". */ threadId?: string; maxFileBytes?: number; totalLimitBytes?: number; }); private processStartTime; private threadId; private pending; private flushTimer; private maintenanceTimer; private writeChain; private currentPath; private currentBytes; private disposed; /** Queues one entry (anything JSON-serializable). Never throws - logging must not take down the caller. */ log(entry: unknown): void; private scheduleFlush; flush(): Promise; private writePending; /** * Compresses one finished log file: LZ4 into a temp SUBFOLDER (never a temp name in the log * folder itself - a crashed write must not leave a corrupt file where the maintenance scans are), * renamed into place (same drive, so the rename is atomic), verified, and only THEN is the * original deleted - at no point is the data in fewer than one complete file. */ private compressFile; listFiles(): Promise; /** One file's bytes, ALWAYS LZ4-compressed: compressed files are sent as-is, a still-streaming file is flushed and compressed in memory (smaller over the network; decodeLogFile handles both identically). */ readFileCompressed(name: string): Promise; private scheduleMaintenance; /** * Shared upkeep of the folder - every process using it runs this, so nothing depends on any one * process surviving: dead processes' uncompressed files are compressed for them; duplicate * compressions of one stream (two maintainers racing) are resolved by keeping the OLDEST copy; * an uncompressed original whose compression already exists is deleted (its compressor died * between rename and unlink); and the folder's total size is brought under its budget by * deleting the oldest files. */ runMaintenance(): Promise; private enforceTotalLimit; dispose(): void; } } declare module "sliftutils/storage/TransactionFile" { /** A live value: what was stored, when it was written (the caller's ordering), and when we last changed it (ours). */ export type LogEntry = { value: T; time: number; changedAt: number; }; /** A deleted key: when it was deleted, and when we learned. When the key had a live value at deletion time it is MARKED rather than gone - the value (and its original write time) rides along, so the underlying data can still be read and the deletion can be undone (see unmark) until the history is dropped (see dropValue). */ export type LogTombstone = { time: number; changedAt: number; value?: T; valueTime?: number; }; export declare class TransactionFile { private filePath; constructor(filePath: string); private values; private deleted; private logRecords; private pending; private flushTimer; private writeChain; /** Reads the log and replays it into memory. Every other method assumes this has finished. */ load: { (): Promise; reset(): void; set(newValue: Promise): void; }; /** The live value, or undefined when the key does not exist here (deleted included - a deletion is an absence, see getDeleted for its time). */ get(key: string): LogEntry | undefined; /** When the key was deleted, if it was (value included when the deletion is still marked - see LogTombstone). Absent both here and in get means we have never heard of it. */ getDeleted(key: string): LogTombstone | undefined; /** The time the key last changed either way, or 0 if we have never heard of it - what a new write has to beat. */ timeOf(key: string): number; /** O(1), and counts only what exists. */ get size(): number; get deletedSize(): number; /** Live values only. Live, in insertion order - deleting during iteration is safe (JS skips entries removed before they are reached), which is what the passes that walk everything and prune as they go rely on. */ entries(): IterableIterator<[string, LogEntry]>; /** The tombstones, which is a much smaller walk than the values - so expiring them, or listing what was deleted since some time, costs what it should. */ deletedEntries(): IterableIterator<[string, LogTombstone]>; /** Stores a value as of `time` (rounded to whole milliseconds - see applySet). Returns false when something at least as new is already here, in which case nothing changed - an out-of-order write is not an error, it is just late. */ set(key: string, value: T, time: number): boolean; /** Deletes as of `time`, keeping the tombstone. A key that had a live value keeps it in the tombstone as MARKED for deletion (readable and restorable until dropValue). Returns false when something at least as new is already here. */ delete(key: string, time: number): boolean; /** Undoes a marked deletion: the kept value becomes live again, as of `time` (a fresh time, so the restore outranks the deletion everywhere it propagated). Returns false when there is no marked value to restore, or something at least as new is already here. */ unmark(key: string, time: number): boolean; /** Drops a marked deletion's kept value (its history has been physically removed), leaving a plain tombstone with the same delete time. */ dropValue(key: string): void; /** Forgets the key entirely, tombstone included - for a tombstone old enough that nobody needs to hear about the deletion any more, and for an entry that turned out never to have existed. Not a deletion: it leaves nothing behind to propagate. */ purge(key: string): void; private applySet; private applyDelete; private append; private scheduleFlush; /** Writes everything pending (rewriting the log first if it has grown too far past what it describes). */ flush(): Promise; private directory; private write; private appendPending; private compact; } } declare module "sliftutils/storage/TransactionStorage" { /// /// import { IStorage, IStorageRaw } from "./IStorage"; interface TransactionEntry { key: string; value: Buffer | undefined; isZipped: boolean; time: number; } export declare class TransactionStorage implements IStorage { private rawStorage; private debugName; private writeDelay; private resyncFromDisk; cache: Map; private diskFiles; private currentChunk; private entryCount; private static allStorage; constructor(rawStorage: IStorageRaw, debugName: string, writeDelay?: number, resyncFromDisk?: boolean); static compressAll(): Promise; private resyncCallbacks; watchResync(callback: () => void): void; private init; private getCurrentChunk; private updateDiskFileTimestamp; private onAddToChunk; get(key: string): Promise; set(key: string, value: Buffer): Promise; remove(key: string): Promise; getInfo(key: string): Promise<{ size: number; lastModified: number; } | undefined>; private pendingAppends; private extraAppends; private pendingWrite; pushAppend(entry: TransactionEntry): Promise; private updatePendingAppends; getKeys(): Promise; checkDisk(): Promise; private loadAllTransactions; private parseTransactionFile; private applyTransactionEntries; private readTransactionEntry; private serializeTransactionEntry; private getHeader; private chunkBuffers; private compressing; private compressTransactionLog; reset(): Promise; } export {}; } declare module "sliftutils/storage/archiveHelpers" { import type { IArchives } from "./IArchives"; /** Copies one file between two archives. The source's CURRENT size and write time always come from getInfo right here - callers never supply them, because a stale size turns into ranged reads of a file that has changed (failing forever), and a stale write time re-orders history. Small files go as a single get2+set; past LARGE_COPY_THRESHOLD the copy streams through setLargeFile in LARGE_COPY_CHUNK ranged reads, so the whole file is never in memory. Returns the copied file's info, and undefined for every way the copy did NOT land: the source doesn't have the file, and with preserveWriteTime the two guarded cases - the destination already held something NEWER (refused up front rather than roll it back), or the destination silently dropped the write (its own only-take-latest won a race we lost - caught by confirming with getInfo afterward). The refused/dropped cases are logged as errors; a caller that treats undefined as "missing at the source" must getInfo the destination to learn the actual latest value. */ export declare function copyArchiveFile(config: { from: IArchives; to: IArchives; path: string; /** The path at the destination - defaults to path (the common case: the same key moving between two archives). */ toPath?: string; /** Stamps the destination with the SOURCE's write time instead of now, and turns on the ordering guards around it (the newer-destination refusal up front, and the getInfo confirm after). ONLY for synchronization between replicas of the same key, where the higher write time must win and ordering must survive propagation - never for a user-triggered copy: a plain copy is a NEW write, and the source's old stamp would make it LOSE to any newer write or tombstone at the destination, silently (move a file back to a folder it was deleted from and the copy is dropped, then the caller deletes the source, and the file is gone entirely). */ preserveWriteTime?: boolean; forceSetImmutable?: boolean; noChecks?: boolean; internal?: boolean; noFallbacks?: boolean; }): Promise<{ writeTime: number; size: number; } | undefined>; /** * Moves one file - between two archives, or between two paths of one. When from and to are the SAME * archives instance and it implements move, the backend moves the file itself (backblaze copies * server-side, disk renames, the storage server relocates it node-side) and the bytes never travel * through us. Everything else is the safe fallback: copy, then CONFIRM the destination actually * reports the file (getInfo), and only then delete the source - the one order in which no failure * can lose the file, only at worst leave it in both places. Throws when the source doesn't have the * file, or when the copy cannot be confirmed (the source is then left untouched). */ export declare function moveArchiveFile(config: { from: IArchives; to: IArchives; path: string; /** The path at the destination - defaults to path (moving between two archives). Required in practice when from and to are the same archives, where the same path would be a no-op. */ toPath?: string; noFallbacks?: boolean; }): Promise; } declare module "sliftutils/storage/backblaze" { /// /// import { IArchives, ArchivesConfig, ChangesAfterConfig, ArchiveFileInfo, DelConfig, FindConfig, GetConfig, GetInfoConfig, MoveFileConfig, SetConfig, SetLargeFileConfig, SourceConfig } from "./IArchives"; export declare class ArchivesBackblaze implements IArchives { private config; constructor(config: { bucketName: string; public?: boolean; immutable?: boolean; cacheTime?: number; allowedOrigins?: string[]; }); private bucketName; private bucketId; private logging; enableLogging(): void; private log; getDebugName(): string; /** Policy flags changed in the routing config. The bucket-level settings getBucketAPI applied (CORS, cache time) are NOT re-applied - those are one-time bucket setup, and re-running them on every config edit would rewrite the bucket's settings from whichever process noticed first. */ updateSourceConfig(sourceConfig: SourceConfig): void; private getBucketAPI; private currentReset; private last503Reset; private apiRetryLogic; get(fileName: string, config?: GetConfig): Promise; get2(fileName: string, config?: GetConfig): Promise<{ data: Buffer; writeTime: number; size: number; } | undefined>; getConfig(): Promise; hasWriteAccess(): Promise; /** Whether an existing file already beats this write, so it must not be sent at all. Shared by set and setLargeFile - the size a value happens to have must never change which ordering rules apply to it. */ private writeIsSuperseded; set(fileName: string, data: Buffer, config?: SetConfig): Promise; del(fileName: string, config?: DelConfig): Promise; setLargeFile(config: SetLargeFileConfig): Promise; getInfo(fileName: string, config?: GetInfoConfig): Promise<{ writeTime: number; size: number; } | undefined>; find(prefix: string, config?: FindConfig): Promise; getChangesAfter2(config: ChangesAfterConfig): Promise; findInfo(prefix: string, config?: FindConfig): Promise<{ path: string; createTime: number; size: number; }[]>; assertPathValid(path: string): Promise; move(config: MoveFileConfig): Promise; copy(config: { path: string; target: IArchives; targetPath: string; }): Promise; getURL(path: string): Promise; getDownloadAuthorization(config: { fileNamePrefix?: string; validDurationInSeconds: number; b2ContentDisposition?: string; b2ContentLanguage?: string; b2Expires?: string; b2CacheControl?: string; b2ContentEncoding?: string; b2ContentType?: string; }): Promise<{ bucketId: string; fileNamePrefix: string; authorizationToken: string; }>; } export declare const getArchivesBackblaze: { (key: string): ArchivesBackblaze; clear(key: string): void; clearAll(): void; forceSet(key: string, value: ArchivesBackblaze): void; getAllKeys(): string[]; get(key: string): ArchivesBackblaze | undefined; }; export declare const getArchivesBackblazePrivateImmutable: { (key: string): ArchivesBackblaze; clear(key: string): void; clearAll(): void; forceSet(key: string, value: ArchivesBackblaze): void; getAllKeys(): string[]; get(key: string): ArchivesBackblaze | undefined; }; export declare const getArchivesBackblazePublicImmutable: { (key: string): ArchivesBackblaze; clear(key: string): void; clearAll(): void; forceSet(key: string, value: ArchivesBackblaze): void; getAllKeys(): string[]; get(key: string): ArchivesBackblaze | undefined; }; export declare const getArchivesBackblazePublic: { (key: string): ArchivesBackblaze; clear(key: string): void; clearAll(): void; forceSet(key: string, value: ArchivesBackblaze): void; getAllKeys(): string[]; get(key: string): ArchivesBackblaze | undefined; }; } declare module "sliftutils/storage/embeddingFormats" { export type EmbeddingFormat = "q8g8_2048" | "q8_g16_2048" | "q8_g16_1024" | "float32"; export declare const EMBEDDING_FORMATS: EmbeddingFormat[]; export declare const DEFAULT_EMBEDDING_FORMAT: EmbeddingFormat; export type QuantType = "q8"; export type StoredEmbedding = { kind: "float32"; model: string; values: Float32Array; } | { kind: "quant"; model: string; type: QuantType; groupSize: number; data: Uint8Array; scales: Uint8Array; }; export declare function embeddingLength(input: Float32Array | StoredEmbedding): number; export declare function releaseFloat32(buffer: Float32Array): void; export declare function embeddingToFloat32(input: Float32Array | StoredEmbedding, usePool?: boolean): Float32Array; export declare const getCloseness: (a: Float32Array | StoredEmbedding, b: Float32Array | StoredEmbedding) => number; export declare function encodeEmbedding(config: { input: Float32Array | StoredEmbedding; format: EmbeddingFormat; model: string; }): StoredEmbedding; export declare function serializeStoredEmbedding(stored: StoredEmbedding): string; export declare function deserializeStoredEmbedding(base64: string): StoredEmbedding; export declare function averageEmbeddings(embeddings: StoredEmbedding[], config: { format: EmbeddingFormat; model: string; }): StoredEmbedding; export declare function hashEmbedding(stored: StoredEmbedding): string; } declare module "sliftutils/storage/fileSystemPointer" { export type FileSystemPointer = string; export declare function storeFileSystemPointer(config: { mode: "read" | "readwrite"; handle: FileSystemFileHandle | FileSystemDirectoryHandle; }): Promise; export declare function deleteFileSystemPointer(pointer: FileSystemPointer): Promise; export declare function findGrantedPointerHandle(mode: "read" | "readwrite"): Promise; export declare function getFileSystemPointer(config: { pointer: FileSystemPointer; }): Promise<{ onUserActivation(modeOverride?: "read" | "readwrite"): Promise; } | undefined>; } declare module "sliftutils/storage/proxydatabase/Database" { export interface Database { readData: (deref: (root: Root) => Value) => Value | undefined; writeData: (deref: (root: Root) => Value, newValue: Value) => void; deleteData: (deref: (root: Root) => unknown) => void; } export declare function namespaceDatabase(database: Database, into: (root: Root) => Sub): Database; } declare module "sliftutils/storage/proxydatabase/inMemoryDatabase" { import { Database } from "./Database"; export declare class InMemoryDatabase implements Database { readCalls: number; writeCalls: number; deleteCalls: number; bytesRead: number; bytesWritten: number; private root; constructor(initial: Root); readData(deref: (root: Root) => Value): Value | undefined; writeData(deref: (root: Root) => Value, newValue: Value): void; deleteData(deref: (root: Root) => unknown): void; } } declare module "sliftutils/storage/proxydatabase/ivfEmbeddingDatabase" { import { Database } from "./Database"; import { TransactionSetStore } from "./transactionSet"; import { StoredEmbedding, EmbeddingFormat } from "../embeddingFormats"; export type IvfConfig = { model: string; format: EmbeddingFormat; cellTargetSize: number; }; export type IvfEmbeddingRoot = { config: IvfConfig; count: number; flat: TransactionSetStore; byRef: { [ref: string]: Uint8Array; }; steps: { [step: string]: boolean; }; centroids: TransactionSetStore; cells: { [cellId: string]: TransactionSetStore; }; }; export type EmbeddingInput = { ref: string; embedding: StoredEmbedding; }; export type SearchHit = { ref: string; closeness: number; }; export declare function rebuildStructure(database: Database): void; export declare function searchEmbeddings(database: Database, query: StoredEmbedding, options: { probeBudget: number; resultCount: number; }): SearchHit[] | undefined; export declare function lookupEmbeddings(database: Database, refs: string[]): Map | undefined; export declare function insertEmbeddings(database: Database, items: EmbeddingInput[]): undefined; export declare function removeEmbeddings(database: Database, refs: string[]): void; } declare module "sliftutils/storage/proxydatabase/transactionSet" { import { Database } from "./Database"; declare const valueTag: unique symbol; export type TransactionSetStore = { [fileNumber: string]: Uint8Array; [valueTag]?: Value; }; export declare function transactionRead(database: Database>): Map | undefined; export declare function replayTransactionStore(store: TransactionSetStore | undefined): Map; export declare function transactionMutate(database: Database>, transactions: { key: string; value: Value | undefined; }[], compactAfterFiles?: number): void; export declare function transactionDelete(database: Database>): void; export {}; } declare module "sliftutils/storage/remoteFileServer" { export declare function generatePassword(wordCount: number): string; export type RemoteFileServerOptions = { root: string; port?: number; host?: string; password?: string; logAccess?: boolean; }; export type RemoteFileServerHandle = { port: number; password: string; url: string; close: () => Promise; }; export declare function startRemoteFileServer(options: RemoteFileServerOptions): Promise; export declare function autocompactBulkDatabases(root: string): Promise; export declare function runFileHoster(): Promise; } declare module "sliftutils/storage/remoteFileStorage" { import type { DirectoryWrapper } from "./FileFolderAPI"; export type RemoteOptions = { chunkBytes?: number; cacheBytes?: number; maxFetchBytes?: number; latencyMs?: number; stats?: { requestCount: number; bytesFetched: number; }; }; export declare function getRemoteDirectoryHandle(url: string, password: string, options?: RemoteOptions): DirectoryWrapper; export type RemoteConnectResult = { status: "ok"; } | { status: "unauthorized"; } | { status: "unreachable"; error: string; }; export declare function testRemoteConnection(url: string, password: string, options?: RemoteOptions): Promise; } declare module "sliftutils/storage/remoteStorage/ArchivesDelayed" { /// /// import { IArchives, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig, DelConfig, FindConfig, GetConfig, GetInfoConfig, SetConfig, SetLargeFileConfig, SourceConfig } from "../IArchives"; export declare const DEFAULT_FAST_WRITE_DELAY: number; export declare const MAX_REMOTE_FAST_BUFFER: number; export declare class ArchivesDelayed implements IArchives { inner: IArchives; private delay; private pending; private stopped; /** The instant every pending write must be on the source no matter its delay - our own valid window's end, minus the flush margin (the next window's source has to find the data on handoff). The store binds it; without it there is no deadline, only the delay. */ private flushBefore?; constructor(inner: IArchives, delay: number); /** Called by the store that owns this source: fast writes are never delayed past the store's own write window. */ bindFlushDeadline(flushBefore: () => number): void; /** The config changed the delay (fast writes turned on or off, or a different writeDelay). Anything already pending keeps the due time it was accepted with - shortening the delay must not strand it, and lengthening it must not hold it longer than promised. */ setDelay(delay: number): void; getDebugName(): string; hasWriteAccess(): Promise; set(fileName: string, data: Buffer, config?: SetConfig): Promise; del(fileName: string, config?: DelConfig): Promise; private buffer; private deadlinePassed; private write; /** Writes everything due (its delay elapsed, or the store's window deadline reached). force writes everything, however recent - shutdown and window handoffs cannot leave writes in memory. */ flush(force?: boolean): Promise; private flushDue; /** Stops the flush loop, after writing everything still pending. */ dispose(): Promise; get(fileName: string, config?: GetConfig): Promise; get2(fileName: string, config?: GetConfig): Promise<{ data: Buffer; writeTime: number; size: number; url?: string; } | { data?: undefined; writeTime?: undefined; size?: undefined; url: string; } | undefined>; getInfo(fileName: string, config?: GetInfoConfig): Promise<{ writeTime: number; size: number; url?: string; } | undefined>; find(prefix: string, config?: FindConfig): Promise; /** Pending writes are part of the listing: a scan of this source that couldn't see them would conclude the files had vanished from it, and drop them from the scanner's index. */ findInfo(prefix: string, config?: FindConfig): Promise; getChangesAfter2(config: ChangesAfterConfig): Promise; /** Never buffered: a file too large to hold in memory is exactly the file that must not sit in memory. Any pending write for the path goes first, so the two land in order. */ setLargeFile(config: SetLargeFileConfig): Promise; getURL(path: string): Promise; getConfig(): Promise; getSyncStatus(): Promise; } /** How long a source may hold a write. Our own disk and our own storage servers take the SHORT delay however large writeDelay is: they are what cross-node reads and redundancy depend on, and making those wait minutes is not a trade anyone asked for. Everything else (backblaze) takes the full delay, which is where coalescing actually saves money. Not fast -> no delay at all, and no wrapper. */ export declare function sourceWriteDelay(config: { sourceConfig?: SourceConfig; fast?: boolean; writeDelay?: number; }): number; /** The delayed wrapper around a source, when it has one - how the store reaches past the delay (to the real disk for large uploads) and flushes on shutdown. */ export declare function asDelayed(source: IArchives): ArchivesDelayed | undefined; /** The source itself, past any write delay. */ export declare function unwrapDelayed(source: IArchives): IArchives; } declare module "sliftutils/storage/remoteStorage/ArchivesRemote" { /// /// import { IArchives, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig, DelConfig, FindConfig, GetConfig, GetInfoConfig, MoveFileConfig, SourceConfig, SetConfig, SetLargeFileConfig } from "../IArchives"; export type ArchivesRemoteConfig = { url: string; waitForAccess?: boolean; /** The exact routing-config entry this connection represents, sent with every call so the server picks the matching per-route store (one server hosts one store per route). Instances built from a bare URL fabricate one - it will never match, which only works for calls that don't select a store (internal reads, ROUTING_FILE, getConfig). */ sourceConfig: SourceConfig; }; export declare function parseStorageUrl(url: string): { address: string; port: number; }; export declare function authenticateStorage(config: { address: string; port: number; nodeId: string; }): Promise<{ machineId: string; ip: string; }>; export declare class ArchivesRemote implements IArchives { private config; constructor(config: ArchivesRemoteConfig); private parsed; private account; private bucketName; private nodeId; private controller; private lastDeniedLog; getDebugName(): string; /** The config travels with every request (the server matches it against its own entries to pick the store), so a config change has to land here - otherwise we keep asking for a source description the server no longer recognizes. Only ever called with a config for the SAME endpoint (see sourceIdentity), so the connection, account, and bucket cannot change under us. */ updateSourceConfig(sourceConfig: SourceConfig): void; isConnected(): boolean; ping(): Promise<{}>; private authenticate; private callAuthed; waitingForAccess(): Promise<{ machineId: string; ip: string; reason: string; } | undefined>; hasWriteAccess(): Promise; private registerAccessRequest; private call; get(fileName: string, config?: GetConfig): Promise; get2(fileName: string, config?: GetConfig): Promise<{ data: Buffer; writeTime: number; size: number; } | undefined>; set(fileName: string, data: Buffer, config?: SetConfig): Promise; del(fileName: string, config?: DelConfig): Promise; move(config: MoveFileConfig): Promise; getInfo(fileName: string, config?: GetInfoConfig): Promise<{ writeTime: number; size: number; } | undefined>; findInfo(prefix: string, config?: FindConfig): Promise; find(prefix: string, config?: FindConfig): Promise; getChangesAfter2(config: ChangesAfterConfig): Promise; getConfig(): Promise; getSyncStatus(): Promise; setLargeFile(config: SetLargeFileConfig): Promise; getURL(path: string): Promise; } } declare module "sliftutils/storage/remoteStorage/ArchivesUrl" { /// /// import { IArchives, ArchiveFileInfo, ArchivesConfig, ChangesAfterConfig, FindConfig, GetConfig, GetInfoConfig, SetLargeFileConfig } from "../IArchives"; export declare class ArchivesUrl implements IArchives { private base; constructor(base: string); getDebugName(): string; private readOnlyError; get(fileName: string, config?: GetConfig): Promise; get2(fileName: string, config?: GetConfig): Promise<{ data: Buffer; writeTime: number; size: number; } | undefined>; getInfo(fileName: string, config?: GetInfoConfig): Promise<{ writeTime: number; size: number; } | undefined>; set(fileName: string, data: Buffer, config?: { lastModified?: number; }): Promise; del(fileName: string): Promise; setLargeFile(config: SetLargeFileConfig): Promise; find(prefix: string, config?: FindConfig): Promise; findInfo(prefix: string, config?: FindConfig): Promise; getChangesAfter2(config: ChangesAfterConfig): Promise; getURL(path: string): Promise; getConfig(): Promise; hasWriteAccess(): Promise; } } declare module "sliftutils/storage/remoteStorage/accessPage" { export {}; } declare module "sliftutils/storage/remoteStorage/accessStats" { import { SummaryEntry } from "../../treeSummary"; export type AccessSummaryState = { total: number; }; export type AccessTotals = { [operation: string]: { count: number; size: number; }; }; /** Counts one storage access, in memory only. size is the bytes involved (0 when the target does not exist); omit it entirely for operations that only count calls, which then only get a count tree. */ export declare function trackAccess(config: { account: string; operation: string; path: string; size?: number; }): void; /** Method decorator factory, for API methods whose single config-object argument has account and bucketName: tracks the access (as `bucketName/path`) after the method succeeds. Sizes come from the config's data (writes) or the result's data (reads); operations without either are count-only. Array results (listings - findInfo, getChangesAfter) are tracked as two breakdowns: " queries" - one access per CALL, at the query prefix, sized by the number of results (so the tree shows which QUERY returns the most) - and " results" - one count-only access per returned path (so the tree shows which PATHS come back most). */ export declare function trackAccessCall(operation: string): (target: unknown, key: string, descriptor: PropertyDescriptor) => void; export declare function getAccessTotals(account: string): AccessTotals; export declare function readAccessSummaries(config: { account: string; operation: string; maxCount: number; weightBySize?: boolean; }): SummaryEntry[]; export declare function clearAccountAccessStats(account: string): void; export type BucketWriteStats = { /** Every set call the bucket accepted */ originalWrites: number; originalBytes: number; /** What actually reached the sources. Fast writes coalesce repeated writes to the same key, so this is lower than the original counts (and is what the disk actually did). */ flushedWrites: number; flushedBytes: number; }; export declare function countBucketWrite(key: string, kind: "original" | "flushed", bytes: number): void; export declare function getBucketWriteStats(key: string): BucketWriteStats; /** Zeroes the write statistics of every bucket in the account. */ export declare function debugClearAccountWriteStats(account: string): number; } declare module "sliftutils/storage/remoteStorage/blobStore" { /// /// import { IArchives, ArchiveFileInfo, ArchivesSource, ArchivesSyncStatus, ChangesAfterConfig, FindConfig, RemoteConfig, SourceConfig, SyncActivity } from "../IArchives"; import { StoreSync } from "./storeSync"; import { StoreConfig } from "./storeConfig"; export declare const WINDOW_END_FLUSH_MARGIN: number; export declare const HISTORY_MIN_BYTES: number; /** The multiple of a store's live bytes its deletion history may grow to. Async so it can later become dynamic and user-configurable; for now it is a constant. */ export declare function getHistoryFactor(): Promise; /** What we store about a file. Its times are not in here: the index keeps those for every key, deleted ones included (see TransactionFile). */ type IndexValue = { size: number; sourcesListIndex: number; }; /** One file we hold, as everything outside the index sees it. */ export type IndexEntry = IndexValue & { writeTime: number; changedAt: number; }; export type BlobSourceSpec = { identity: string; url: string; validWindows: [number, number][]; route?: [number, number]; noFullSync?: boolean; intermediate?: string; sourceConfig?: SourceConfig; create: () => IArchives; applyConfig?: (source: IArchives) => void; }; export declare class BlobStore { folder: string; /** The name this store answers to (see CommonConfig.name) - the entries of the routing config that carry it are the ones that configure it, and the rest are its peers. */ storeName: string; private config?; stopped: { stop: boolean; }; syncStarted: boolean; /** Its sources, in config order: slot 0 is always its own disk folder, the rest are the peers it synchronizes with. Filled by updateSources, which is also how they change. The store OWNS them - writes pick among them, reads resolve holders through them - and StoreSync only scans whatever is in here at the time. */ sources: ArchivesSource[]; private discardedUploads; private nextDiscardedUpload; private sourcesList; private slotSourcesListIndexes; private slotRegistrations; private index; /** Keeping the index in agreement with the sources: scanning, pulling, pushing, and the maintenance that follows from holding an index (disk-limit eviction, tombstone expiry). It reads and writes this store's index and sources - it does not own them. */ sync: StoreSync; constructor(folder: string, /** The name this store answers to (see CommonConfig.name) - the entries of the routing config that carry it are the ones that configure it, and the rest are its peers. */ storeName: string, config?: { /** Whether a config entry is THIS SERVER (same account, same bucket, our own address). Injected because a store knows nothing about servers - it only needs to tell its own entries apart from its peers'. Absent means nothing is us, which is what a bare store (no server around it) wants. */ isSelf?: ((source: SourceConfig) => boolean) | undefined; /** Builds one of its sources. Injected for the same reason, and because the delay it is created with is this store's policy. */ createSource?: ((config: { sourceConfig?: SourceConfig; writeDelay: number; }) => IArchives) | undefined; /** Hands a running source a changed config, so an endpoint we already talk to is never rebuilt just because a flag moved. */ applySource?: ((source: IArchives, sourceConfig: SourceConfig | undefined, writeDelay: number) => void) | undefined; onIndexChanged?: ((key: string) => void) | undefined; /** Called every time this store applies a routing config to itself (startup, an operator's write, a peer's copy arriving) - the store is the one that knows when a config landed, and the server arms window-boundary scans from it. */ onRoutingApplied?: ((routing: RemoteConfig) => void) | undefined; /** Asks the client whose request created this store what routing config it intended for our name. Only used when init finds NO configuration in our folder: a store only ever exists because a config names it, so the requester has that config - asking for it lazily is the same information as passing the config on every call, without the per-call kilobytes. */ requestRoutingConfig?: (() => Promise) | undefined; onWriteCounted?: ((kind: "original" | "flushed", bytes: number) => void) | undefined; /** A synchronization transfer: "sync get" is bytes pulled off a source (the backblaze download bill), "sync set" is bytes pushed to one. Injected because sync traffic never passes through the API controller, so nothing else can count it. */ onSyncTransfer?: ((operation: "sync get" | "sync set", path: string, bytes: number) => void) | undefined; resolveSourceUrl?: ((url: string) => IArchives) | undefined; } | undefined); /** This store's folder, unwrapped: the same bytes slot 0 serves, but reached without its write delay. Used for the two things that cannot go through a buffered source - reading our own routing config before we have any sources, and streaming a large upload that must not sit in memory. */ private ownDisk; /** What this store is configured to be. It owns this: the routing config is a file IN the store, so the store reads it, applies it to itself, and re-applies it whenever the file changes - by our own write, or by a peer's copy arriving through synchronization. */ storeConfig: StoreConfig; private appliedRoutingVersion; private appliedRouting; init: { (): Promise; reset(): void; set(newValue: Promise): void; }; /** * Re-reads the routing config out of this store and applies it to itself. Called at startup and * whenever that file changes here - which is the ONE mechanism: a config written by an operator * and a config pulled off a peer are the same event, a write of that path into this store. * * A store with no routing config configures itself as its own disk, valid always, for the whole * key space. That is a complete, working store - it just has nobody to synchronize with - and it * is what lets a store exist before it has ever heard of a configuration. */ applyRoutingConfig(): Promise; private readRoutingConfig; /** The version of the routing config this store is running, so a copy found on a peer is only taken when it is genuinely newer. -1 means it has none. */ routingVersion(): number; private routingApplies; reapplyRoutingConfig(): void; private planSources; dispose(): Promise; get2(config: { path: string; range?: { start: number; end: number; }; internal?: boolean; includeTombstones?: boolean; includeMarked?: boolean; }): Promise<{ data: Buffer; writeTime: number; size: number; } | undefined>; set(config: { path: string; data: Buffer; lastModified?: number; forceSetImmutable?: boolean; internal?: boolean; undelete?: boolean; }): Promise; del(config: { path: string; lastModified?: number; internal?: boolean; }): Promise; /** A node-side move: the bytes never travel through the client. Deliberately just get2 + set + del rather than a disk rename, so the destination write passes EVERY rule a set passes (windows, routes, immutability, only-take-latest, index, fan-out to peers) and the deletion propagates as a normal tombstone - a rename would bypass all of it. The set stamps fresh, so the moved file beats any tombstone at its new path. */ move(config: { fromPath: string; toPath: string; }): Promise; getInfo(config: { path: string; includeTombstones?: boolean; }): Promise<{ writeTime: number; size: number; } | undefined>; findInfo(config: FindConfig & { prefix: string; }): Promise; getChangesAfter2(config: ChangesAfterConfig): Promise; getSyncStatus(): Promise; /** The index's totals plus any in-progress background synchronization. */ getSyncProgress(): { index: { fileCount: number; byteCount: number; }; marked: { fileCount: number; byteCount: number; oldestDeleteTime?: number; }; sources: { debugName: string; fileCount: number; byteCount: number; }[]; readerDiskLimit?: number; syncing: SyncActivity[]; }; /** getSyncProgress's totals, but loading the index first, so they are never the zeroes of a store nothing has touched yet. */ computeIndexTotals(): Promise<{ fileCount: number; byteCount: number; sources: { debugName: string; fileCount: number; byteCount: number; }[]; }>; private namedIndexTotals; /** * The store's sources, as the current routing config says they should be. This is the ONLY way * they are ever set: the first call populates an empty store, every later one applies a change to * the running one. Windows, routes and flags move in place, genuinely new endpoints are added and * start scanning, and endpoints that are gone go dead (their scans stop, their index entries * drop). * * A store is never rebuilt for a config change. Its name decides its folder and its identity, and * a config change cannot change either - so there is nothing a change can do to a store except * this. */ updateSources(specs: BlobSourceSpec[]): void; /** Rescans our own disk's metadata into the index - used around valid window handoffs, where another process wrote files to the shared folder that our index hasn't seen. */ rescanBase(): Promise; /** A boundary scan of the node that owned (part of) our route in the valid window before ours, when that node is different storage (a disk rescan can't see its writes). */ boundaryScanRemote(source: IArchives, config: { since: number; route?: [number, number]; }): Promise; startLargeUpload(config?: { path?: string; lastModified?: number; forceSetImmutable?: boolean; noChecks?: boolean; internal?: boolean; }): Promise; appendLargeUpload(config: { id: string; data: Buffer; offset?: number; }): Promise; finishLargeUpload(config: { id: string; path: string; lastModified?: number; forceSetImmutable?: boolean; noChecks?: boolean; internal?: boolean; }): Promise; cancelLargeUpload(config: { id: string; }): Promise; /** Bytes of read cache the disk may hold; see CommonConfig.readerDiskLimit (StoreSync enforces it). Read from the config in effect, so raising or removing the limit takes effect on the next eviction pass. */ get readerDiskLimit(): number | undefined; /** The write time a new write has to beat, or 0 when we have never heard of the key. Counts DELETIONS too: a write older than the deletion that removed it must not bring it back. The index is authoritative even for a write still buffered in a delayed source, since the entry is recorded when the write is accepted rather than when it reaches storage. */ currentWriteTime(key: string): number; private isLive; registerSlot(slot: number): Promise; /** The persistent sourcesListIndex of a slot, or undefined when the slot never got that far (a source removed before its registration resolved). */ slotSourcesListIndex(slot: number): number | undefined; sourcesListIndexOfSlot(slot: number): number; slotForSourcesListIndex(sourcesListIndex: number): number | undefined; getEntryHolder(entry: IndexEntry): Promise; private loadIndex; /** A file we hold. A deleted one is not one: it is a tombstone, and only getDeletedEntry knows about it. */ getIndexEntry(key: string): IndexEntry | undefined; /** When a key was deleted, if it was. A deletion is an absence with a time attached - that time is what makes it propagate and what expires it. */ getDeletedEntry(key: string): { writeTime: number; changedAt: number; } | undefined; /** Every file we hold, for the passes that walk them all (listings, scans, reconciliation, eviction). Deletions are not in here - see deletedEntries. Live: deleting entries while iterating is expected here, and safe. */ indexEntries(): IterableIterator<[string, IndexEntry]>; /** Every deletion we know of. A much smaller walk than the files, which is what makes expiring them cheap. */ deletedEntries(): IterableIterator<[string, { writeTime: number; changedAt: number; }]>; /** A file MARKED for deletion: its kept index value plus when it was deleted. Undefined when the key is live, never existed, or its history was already dropped. */ getMarkedEntry(key: string): (IndexEntry & { deleteTime: number; }) | undefined; /** Every file marked for deletion - the deletion history, walked by retention and by includeMarked listings. */ markedEntries(): IterableIterator<[string, IndexEntry & { deleteTime: number; }]>; /** The deletion history's totals: how many marked files, their bytes, and the delete time of the OLDEST one - which is how far back the history reaches. */ markedTotals(): { fileCount: number; byteCount: number; oldestDeleteTime?: number; }; /** Physically removes a marked file's bytes from our disk and drops its kept value, leaving a plain tombstone that ages out normally - retention calling time on the oldest history. */ dropMarkedHistory(key: string): Promise; /** See SetConfig.undelete: flips a marked deletion back to live (fresh write time, so the restore outranks the deletion everywhere it propagated) - the bytes never left the disk, so reads just work again. Internal restores are a peer's propagation and tolerate having nothing to restore (this node may never have held the file); a caller's restore throws instead. */ private undeleteKey; /** How many files we hold, deletions excluded. */ indexSize(): number; /** Totals over the files we hold, broken down by the slot holding each (entries can name a source that is no longer configured, which counts towards the total but no slot). */ indexTotals(): { fileCount: number; byteCount: number; slots: { fileCount: number; byteCount: number; }[]; }; /** Records a file, as of its write time. Returns false, having changed nothing, when we already know something at least as new - the index cannot be made to go backwards, whichever path the write came in by. */ setIndexEntry(key: string, entry: { writeTime: number; size: number; sourcesListIndex: number; }): boolean; /** Records a DELETION, as of its time: the key stops existing here, and the tombstone is what makes that fact propagate and reconcile like any other write. Same ordering rule as setIndexEntry. */ setIndexDeleted(key: string, writeTime: number): boolean; /** Forgets a key entirely, tombstone included. NOT a deletion: it says nothing happened to the file, only that we no longer know anything about it - for an entry whose holder turned out not to have it, and for a tombstone old enough that everyone has heard. */ purgeIndexEntry(key: string): void; /** Counts a synchronization transfer in the server's access statistics (see getStore's wiring): "sync get" for bytes pulled off a source, "sync set" for bytes pushed to one. */ noteSyncTransfer(operation: "sync get" | "sync set", path: string, bytes: number): void; /** * Every write, however it is stamped, has to be one we are actually meant to hold - because the * alternative is not a smaller problem, it is a silent one. A write that lands on a store that * does not serve its route (or on a server that is not in the bucket's config at all) goes into a * folder nothing scans and no peer reconciles: it succeeds, and then it is gone. The markers make * the client re-read the routing config and retry, which is exactly the right outcome when the * reason it aimed here is that its config was stale. */ private assertWriteTarget; /** Exactly why this store has no configuration entries - which of the three possible reasons it is, with the values that decided it, because "not configured" alone is undiagnosable. */ private unconfiguredDetail; /** * Whether a routing config may be written here. Two rules, and this is the one place either is * applied - a config only ever enters the system through a write, so a config that got in is a * config that passed, and reading one back never judges it again. * * The config has to be valid as a whole (see assertValidRemoteConfig), and it has to outrank what * we are running: the same version means the same config, so re-writing it is harmless, but a * lower one is an older config arriving late and must never undo a newer one. */ private assertRoutingConfigWritable; private assertFreshWriteTarget; private assertMutable; private assertInternalWriteAccepted; /** Internal (store-to-store) read: never goes to OTHER sources - the caller is another store, and chasing OUR remote holders while answering it is how infinite get loops between stores form - but the INDEX still gates, because it is the source of truth: a marked deletion keeps its bytes on disk as history (see writeToSources), so the disk alone would happily serve a DELETED file as live. Index says live -> the disk provides the bytes (past any write delay, so a fast write still buffered in memory is invisible here; the caller re-finds it once it flushes). Index says deleted -> the tombstone is the answer, never the disk. No window or route checks. */ private getInternal2; /** Internal (store-to-store) write: the local disk plus our index, with NO downstream fan-out - the pushing store owns propagation, and fanning its pushes back out is how write loops between stores form. Only-take-latest still applies here. */ private setInternal; private cacheRead; private setOrDelete; /** The instant every delayed write must be on its source: the end of our own write window that contains now, minus the flush margin (so the next window's source finds the data on handoff). The LATEST end among covering windows - overlapping windows hand off at the last one. No window contains now (an inert store, or a moment between our windows) -> 0, i.e. nothing may be delayed at all. */ writeFlushDeadline(): number; private getWritableSources; private writeToSources; private getDiskSource; /** Writes everything still held by a delayed source (see ArchivesDelayed). force also writes what isn't due yet - shutdown cannot leave writes in memory. */ private flushDelayedWrites; } export {}; } declare module "sliftutils/storage/remoteStorage/bucketDisk" { /// /// import { RemoteConfig } from "../IArchives"; /** A store's folder, from the only three things that identify it. The name is the config entry's name and nothing else: the same name is the same storage, whatever its window or route say, and a different name is different storage even for the same URL. Nothing about a folder changes when the routing does. */ export declare function getBucketFolder(name: string, account: string, bucketName: string): string; export type StoreFolder = { account: string; name: string; bucketName: string; folder: string; }; /** Every store this server holds for an account, found by walking the disk. */ export declare function listAccountStoreFolders(account: string): Promise; /** Every store this server holds for ONE bucket - one per name it has ever been given. */ export declare function listBucketStoreFolders(account: string, bucketName: string): Promise; export declare function readRoutingFile(folder: string): Promise<{ data: Buffer; writeTime: number; } | undefined>; /** The bucket's routing config as this server holds it: the newest copy among its stores. Each store keeps its own, and they converge - so when they disagree, the one written most recently is the one that has heard the most. */ export declare function readNewestRoutingFile(account: string, bucketName: string): Promise<{ data: Buffer; writeTime: number; size: number; name: string; } | undefined>; export declare function readRoutingFromDisk(account: string, bucketName: string): Promise; /** What an anonymous URL read of the routing file gets: the same newest copy. */ export declare function getRoutingFileResult(account: string, bucketName: string): Promise<{ data: Buffer; writeTime: number; size: number; } | undefined>; export type BucketDiskInfo = { totalBytes: number; freeBytes: number; usedBytes: number; }; export declare function getDiskInfo(folder: string): Promise; } declare module "sliftutils/storage/remoteStorage/certTrustModal" { export declare function showCertTrustModal(serverUrl: string): void; } declare module "sliftutils/storage/remoteStorage/chainStartup" { import { RemoteConfig, SourceConfig } from "../IArchives"; import { SourceWrapper } from "./sourceWrapper"; export declare const CONFIG_WRITE_RETRY_INTERVAL: number; export declare const CONFIG_WRITE_REFRESH_INTERVAL: number; export type ChainState = { config: RemoteConfig; sources: SourceWrapper[]; }; /** The chain's state and everything about HAVING one: init (with its retry), the config poll, availability rechecks, and the routing rewrite loop. The chain constructs one, asks it getState() on every call, and disposes it. */ export declare class ChainStateManager { private config; readonly configured: RemoteConfig; /** The newest adopted config - what getDebugName and dispatch decisions read. Starts as the configured one and moves with every adoption. */ activeConfig: RemoteConfig; private statePromise; private latestState; private initRetryDelay; private initRetryTimer; private pollTimer; private disposed; private unsubscribeRoutingPush; private routingRewriter; constructor(config: { configured: RemoteConfig; debugName: () => string; /** See ArchivesChainOptions.directConnect. */ directConnect?: boolean; }); private untrackConfig; /** The newest adopted state, synchronously - undefined until the first init finishes. */ latest(): ChainState | undefined; getState(): Promise; private init; /** Clientside, a config with public sources is served entirely over plain URL downloads - no API connection, no access grant, and no writing. directConnect opts out of that. */ private isReadOnly; private createChainSource; private buildSources; private startConfigPoll; private configRefreshInFlight; refreshActiveConfig(): Promise; private fetchLatestConfig; private checkForNewConfig; private adoptNewConfig; private lastAvailabilityRecheck; private availabilityRecheckInFlight; /** Every source failed: re-contact all of them (routing re-read + connection re-attempt) and adopt whatever config comes back. Throttled, and deduplicated across concurrent callers. */ recheckAvailability(): Promise; private recheckAvailabilityNow; dispose(): void; } export type SourceProbe = { probe: SourceWrapper; sourceConfig: SourceConfig; responded: boolean; latency: number; existing: RemoteConfig | undefined; error: string; }; /** One throwaway SourceWrapper per configured source, each asked for its stored routing config - which also measures first-contact latency, seeded into the real sources afterwards. The probes MUST be disposed (disposeProbes) once the caller is done with them. */ export declare function probeSources(configs: SourceConfig[], readOnly: boolean): Promise; export declare function disposeProbes(probes: SourceProbe[]): void; /** * Which routing config the chain should RUN: the newest of ours and every stored one. needsWrite * when ours is strictly the newest, meaning the stores have to be told about it. A stored config * with our exact version but DIFFERENT content wins without a write, loudly: config updates must * bump the version, or they are ignored - silently taking the changed one would make "what is the * bucket running" depend on which process started last. * * Throws when no source answered at all: with nothing stored and nobody to write to, there is no * config to run. */ export declare function chooseStartupConfig(config: { configured: RemoteConfig; probes: SourceProbe[]; debugName: string; }): { active: RemoteConfig; needsWrite: boolean; existing: RemoteConfig | undefined; }; /** * Writes the given routing config to every configured store, one write per url+name: the write * lands in the store the entry NAMES, so two entries sharing a URL but naming different stores are * two separate deliveries - deduping by URL alone leaves the second store unconfigured forever. All * in parallel, every failure tolerated (no-write-access included - the attempt classifies it, * nothing pre-checks it): a down node must not stop the others from getting the config - they would * then reject writes as unconfigured precisely BECAUSE it never arrived - and it must never stop * the chain from starting. A store that missed it pulls it off its peers, and the rewrite loop * tries again (see RoutingRewriteLoop). */ export declare function writeRoutingToAllStores(config: { configured: RemoteConfig; sources: SourceWrapper[]; debugName: string; }): Promise<{ failures: string[]; total: number; }>; /** * The periodic re-write of the chain's in-code config: failures retried on the short interval - a * store without the config rejects every write aimed at it, so this not landing is a big deal, * logged on every attempt - and even success repeated hourly, in case a store lost it. The failure * this exists for: a server whose trust was only granted AFTER startup, so the startup write was * rejected and nothing else would ever retry it. */ export declare class RoutingRewriteLoop { private config; constructor(config: { configured: RemoteConfig; debugName: () => string; /** The chain's newest adopted state: what decides whether ours is still the config to write, and the sources it is written through. Undefined until the first init finishes. */ latest: () => { config: RemoteConfig; sources: SourceWrapper[]; } | undefined; }); private timer; private disposed; /** (Re)arms the loop - called at the end of every init, with whether that init's write failed (which picks the short retry interval). */ start(failedAtStartup: boolean): void; dispose(): void; private schedule; private rewrite; } } declare module "sliftutils/storage/remoteStorage/cliArgs" { export declare function getArg(name: string): string | undefined; /** A valueless boolean flag: true when --name is present (with nothing, or a following flag). */ export declare function getFlag(name: string): boolean; } declare module "sliftutils/storage/remoteStorage/createArchives" { /// /// import { IArchives, RemoteConfig, RemoteConfigBase, SourceConfig, ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, ChangesAfterConfig, DelConfig, FindConfig, GetConfig, GetInfoConfig, MoveFileConfig, SetConfig, SetLargeFileConfig } from "../IArchives"; import { ServerBucketInfo, ActiveBucketInfo } from "./storageServerState"; import { LogFileInfo } from "../StreamingLogs"; /** The address, port, account, and bucket name a bucket routing URL addresses. Throws when the URL isn't a hosted bucket routing URL (https://host:port/file///storage/storagerouting.json). */ export { parseHostedUrl, parseBackblazeUrl, getBucketBaseUrl } from "./remoteConfig"; /** A client for ONE source - see storeSources.ts. Re-exported here because a chain is built out of them. */ export { createApiArchives } from "./storeSources"; export type ArchivesChainOptions = { /** Outside of node we default to read-only downloads over the public URLs (no API connection) when the config has public sources. Set this to connect to the API anyway - needed for writing, listing, and any other operation the plain URL form cannot serve. */ directConnect?: boolean; }; export declare class ArchivesChain implements IArchives { private state; constructor(config: RemoteConfig | RemoteConfigBase, options?: ArchivesChainOptions); getDebugName(): string; private run; private runPrimary; /** Races call against a size-based deadline. Uploads know their size upfront; gets are given SMART_TIMEOUT_PROBE to produce anything, and only then is the file's info fetched (from the same source, itself time-limited) to size the deadline - measured from the call's start, so a source that was slow before the probe doesn't get the full allowance again. Timed-out calls keep running in the background (they cannot be cancelled) but their eventual result is ignored. */ private applySmartTimeout; /** Runs one call under a window that can be pushed back while it runs. The window covers the next piece of work rather than the whole call, so nothing has to guess how long a transfer "should" take from its size: as long as pieces keep landing, the call keeps its time. The waiting is a loop rather than one race, because a refresh that arrives while we are already waiting has to move the deadline we are waiting on. */ private applyRefreshableTimeout; private lastConfigRefresh; private prepareWrongTargetRetry; private request; waitingForAccess(): Promise<{ machineId: string; ip: string; reason: string; } | undefined>; /** The sources that can serve a file right now, in dispatch order - the first is the write node, the one a plain read asks first. Each entry's url is what GetConfig.sourceUrl / GetInfoConfig.sourceUrl accept, so listing these and then reading with sourceUrl compares the copies the sources actually hold. */ getFileSources(fileName: string): Promise; private runOnSource; get(fileName: string, config?: GetConfig): Promise; /** get2, but trying sources in latency order (fastest first) instead of config order. While this is much faster, it might miss immediate writes: the write node is no longer tried first, so a lagging replica may answer with a slightly older value. Exclusive with noFallbacks (which only considers one source - the write node - so there is no order to speed up); passing both throws. */ getFast(fileName: string, config?: GetConfig): Promise<{ data: Buffer; writeTime: number; size: number; url: string; } | { data?: undefined; writeTime?: undefined; size?: undefined; url: string; }>; /** Always resolves with a url - the authority that answered. A value that doesn't exist is still an answer FROM a server, so it comes back as { url } with no data (never plain undefined); errors from every source throw instead. */ get2(fileName: string, config?: GetConfig): Promise<{ data: Buffer; writeTime: number; size: number; url: string; } | { data?: undefined; writeTime?: undefined; size?: undefined; url: string; }>; /** Reads a whole file as a series of ranged reads, so a big one arrives in pieces instead of as one request nobody can see inside of. The first read asks for CHUNK_FIRST_SIZE. Less than that coming back IS the whole file, so a small file costs exactly one request - and because every backend reports the file's FULL size alongside a ranged read, a big one already knows its size from that same answer and never needs a getInfo to find out. Every chunk after the first goes to the source that served the first, so the pieces cannot be assembled out of two different versions living on two replicas. Finishing a chunk pushes the timeout back, so the deadline covers one chunk rather than the whole transfer. */ private readInChunks; getInfo(fileName: string, config?: GetInfoConfig): Promise<{ writeTime: number; size: number; url: string; } | undefined>; private selectCoveringSources; private runOnCovering; find(prefix: string, config?: FindConfig): Promise; findInfo(prefix: string, config?: FindConfig): Promise; getChangesAfter2(config: ChangesAfterConfig): Promise; getSyncStatus(): Promise; getConfig(): Promise; hasWriteAccess(): Promise; set(fileName: string, data: Buffer, config?: SetConfig): Promise; private setRoutingConfig; del(fileName: string, config?: DelConfig): Promise; /** See IArchives.undelete: restores a file marked for deletion, dispatched to the write node as SetConfig.undelete (the write node propagates the restore to its peers itself). */ undelete(fileName: string): Promise; /** See IArchives.move. When one node is the write target for BOTH paths, that node moves the file itself - the bytes never come through us - with the same wrong-window/route re-resolution as any write. When the paths route to different shards no single node holds both, so the move degrades to a copy through us plus a delete, CONFIRMED at the destination before the source is touched. No smart timeout on the node-side move: it can be a big file's worth of node-side work, which the upload-sized deadlines would misjudge. */ move(config: MoveFileConfig): Promise; private getVariableShardTargets; /** The key setVariableShard would materialize for this VARIABLE_SHARD key (a value in the preferred shard's route range), without writing anything. */ getShardKey(key: string): Promise; private setVariableShard; /** A large file is written exactly like a small one - same write node, same wrong-window/route re-resolution, same fallbacks - so a value's SIZE never decides its write semantics (set streams through here past LARGE_SET_THRESHOLD, and a file that grew past it must not suddenly lose the availability its caller asked for). The one difference: every attempt after the first has to rewind the stream, so a config without restartStream gets a single attempt. */ setLargeFile(config: SetLargeFileConfig): Promise; private setLargeFileOnce; getURL(path: string): Promise; /** Every URL that could serve this path: public sources matching both the path's route and the current valid window. The first is the write node's (first matching source in config order, see runPrimary - the one guaranteed current); the rest are ranked fastest-first by measured latency. Empty when none qualify. */ getURLs(path: string): Promise; /** getURLs, but after the one await (initialization) the returned function is synchronous: everything underneath - route hashing, window checks, latencies, URL building - is synchronous, and the closure always reads the newest adopted config, so it stays correct across config refreshes. */ getGetURLs(): Promise<(path: string) => string[]>; /** getGetURLs, but sorted purely by latency - the write node gets no special first position. For read-only consumers that just want the fastest host. */ getGetFastURLs(): Promise<(path: string) => string[]>; private makeGetURLs; dispose(): void; } export declare function createArchives(config: RemoteConfig | RemoteConfigBase, options?: ArchivesChainOptions): ArchivesChain; export declare function listServerBuckets(config: { url: string; account: string; }): Promise; /** The live, in-memory state of one bucket on a server (routing config included), or a string saying why it is unavailable. Cheap - it never touches the server's disk - but only works while that bucket is loaded there. */ export declare function getServerActiveBucket(config: { url: string; account: string; bucketName: string; }): Promise; /** The buckets a server currently has loaded. Admin only, so in practice this is our own machine's other process - a deploy successor asking its predecessor what is actually in use. */ export declare function listServerActiveBucketKeys(config: { url: string; }): Promise<{ account: string; bucketName: string; }[]>; /** Tells a server to load one of its buckets into memory (starting its synchronization) and returns its live state, or a string saying why it could not be loaded. Only touches that server - nothing is written and no other source is contacted. */ export declare function activateServerBucket(config: { url: string; account: string; bucketName: string; }): Promise; /** Zeroes the write statistics listServerBuckets reports, for every bucket in the account. */ export declare function clearServerWriteStats(config: { url: string; account: string; }): Promise<{ clearedBuckets: number; }>; /** The operation-log files ONE storage server holds (every server logs only its own operations - see listAllServerLogFiles for the whole fleet). The names carry pid/thread/time-range/entry-count metadata; see LogFileInfo. */ export declare function listServerLogFiles(config: { url: string; account: string; }): Promise; /** Every server's log files at once, one entry per url - a server that cannot answer reports its error instead of failing the rest. */ export declare function listAllServerLogFiles(config: { urls: string[]; account: string; }): Promise<{ url: string; files?: LogFileInfo[]; error?: string; }[]>; /** Downloads the named log files off one server and decodes them into the logged objects (the wire always carries them LZ4-compressed - live files are compressed in memory server-side). */ export declare function getServerLogs(config: { url: string; account: string; names: string[]; }): Promise<{ name: string; entries: unknown[]; }[]>; /** getServerLogs, but SEARCHED instead of fully decoded: the raw JSON text is substring-matched (every search string must appear in a statement - see createLogSearcher), and only the matching statements are decoded into objects. Far cheaper than decoding whole files to look for one path or caller. */ export declare function searchServerLogs(config: { url: string; account: string; names: string[]; searches: string[]; }): Promise<{ name: string; entries: unknown[]; }[]>; export declare function getBucketInfo(config: { url: string; }): Promise; } declare module "sliftutils/storage/remoteStorage/deployTakeover" { type DeployTakeover = { releaseTime: number; overlapTime: number; altPort?: number; intermediateStart?: number; }; /** Called when the main port is already in use, which on a healthy machine only happens while our predecessor is still running a deploy overlap. Confirms that against the deploy timeline; if no deploy is in progress we are in a bad state (someone else holds our port) and the process must not keep running. */ export declare function detectDeployTakeover(): Promise; export declare function setAltPort(port: number): void; /** The window in which writes belong to our alternate port: from partway through the overlap (giving the predecessor notice to flush) until safely past its kill (giving us time to actually take the main port). */ export declare function getTakeoverIntermediate(): { start: number; end: number; altPort: number; } | undefined; /** We never stop listening on the alternate port while its window is still valid, and hold it well past that for clients that have not caught up yet. */ export declare function getAltPortListenEnd(): number; /** How long to wait between main-port acquisition attempts: tight around our predecessor's scheduled death (when the port actually frees), relaxed otherwise. */ export declare function getMainPortAcquireDelay(): number; export {}; } declare module "sliftutils/storage/remoteStorage/intermediateManagement" { import { RemoteConfig } from "../IArchives"; /** Called every time a store applies a routing config to itself (see BlobStore's onRoutingApplied): arms the scans the config's upcoming window boundaries need. Each scan is scheduled once - the key includes the boundary it is for - so re-arming on every config application is harmless. */ export declare function scheduleBoundaryWork(account: string, bucketName: string, routing: RemoteConfig): void; /** An operator's config knows nothing about a switchover that is in flight right now, so writing it as-is would cancel one mid-handover. The in-flight windows are put back into it first. */ export declare function reinjectIntermediates(current: RemoteConfig | undefined, incoming: RemoteConfig): RemoteConfig; /** Started by deployTakeover once we are actually a deploy successor listening on an alternate port. Until then there are no switchover windows to write or expire, so nothing polls. */ export declare const startIntermediateMaintenance: { (): void; reset(): void; set(newValue: void): void; }; } declare module "sliftutils/storage/remoteStorage/intermediateSources" { import { RemoteConfig, SourceConfig } from "../IArchives"; export declare const INTERMEDIATE_EXPIRE_GRACE: number; /** Adding or removing intermediates is a real config update, so it takes a real version increment - but a proportional one, so it stays far below whatever the author's next version would be (whether they count 1, 2, 3 or use timestamps), and a million of them still fit under it. */ export declare function nextIntermediateVersion(version: number): number; export declare function getIntermediateSources(config: RemoteConfig): SourceConfig[]; export declare function hasIntermediateSources(config: RemoteConfig): boolean; /** Removes every intermediate entry and rejoins the windows it split, giving back the underlying configuration. Two configs that resolve equal differ only by intermediates. */ export declare function resolveIntermediateSources(config: RemoteConfig): RemoteConfig; /** Splits every source at splitUrl covering [start, end) so that middle window points at intermediateUrl instead, flagged as intermediate. Idempotent: a config that already contains the exact intermediate comes back unchanged. */ export declare function injectIntermediateSource(config: RemoteConfig, inject: { splitUrl: string; intermediateUrl: string; start: number; end: number; }): RemoteConfig; /** Intermediates whose window ended more than INTERMEDIATE_EXPIRE_GRACE ago are removed, and the windows they split are rejoined. */ export declare function expireIntermediateSources(config: RemoteConfig, now: number): RemoteConfig; } declare module "sliftutils/storage/remoteStorage/productionEnv" { } declare module "sliftutils/storage/remoteStorage/remoteConfig" { /// /// import { RemoteConfig, RemoteConfigBase, SourceConfig, ArchiveFileInfo, ChangesAfterConfig } from "../IArchives"; export declare const ROUTING_FILE = "storage/storagerouting.json"; /** The variable-shard route override embedded in the key ("_", see VARIABLE_SHARD), or undefined when the key has no sentinel or the sentinel has no value yet. */ export declare function parseVariableRoute(key: string): number | undefined; /** Where a key routes in [0, 1). A materialized variable-shard suffix completely overrides the hash. */ export declare function getRoute(key: string): number; /** The in-memory getChangesAfter2 emulation, for backends without a native change feed: a full listing filtered down to files written after config.time whose keys route into config.routes. */ export declare function filterChanges(files: ArchiveFileInfo[], config: ChangesAfterConfig): ArchiveFileInfo[]; export declare function routeContains(route: [number, number] | undefined, value: number): boolean; export declare function routesOverlap(a: [number, number] | undefined, b: [number, number] | undefined): boolean; /** The overlap of two route ranges, or undefined when they don't overlap. */ export declare function routeIntersection(a: [number, number] | undefined, b: [number, number] | undefined): [number, number] | undefined; export declare function getConfigVersion(config: RemoteConfig): number; /** Strips the routing-file suffix, leaving the bucket's public base URL (file paths append to it). */ export declare function getBucketBaseUrl(url: string): string; export declare function buildFileUrl(baseUrl: string, filePath: string): string; export declare function parseHostedUrl(url: string): { address: string; port: number; account: string; bucketName: string; }; export declare function parseBackblazeUrl(url: string): { bucketName: string; }; export declare function replaceHostedUrlPort(url: string, port: number): string; /** * Puts a source into the shape the code expects. It does NOT judge it: this runs every time a config * is READ, and a config that is already on disk has to keep working - a server that cannot parse its * own routing file is a server that cannot serve, and it would stay that way forever. Anything * missing or unusable is filled in with the safest equivalent instead, loudly where it matters. * * Judging happens on the way IN, in assertValidRemoteConfig. */ export declare function normalizeSource(source: RemoteConfigBase): SourceConfig; /** Puts a whole config into the shape the code expects, without judging it - see normalizeSource, and see assertValidRemoteConfig for the judging. */ export declare function normalizeRemoteConfig(config: RemoteConfig | RemoteConfigBase): RemoteConfig; /** * Whether a config may be WRITTEN. Everything here is a rule about the config as a whole, which is * exactly why it cannot run on read: a config that is already stored somewhere has to keep being * readable, or a server that once accepted a bad one could never start again. Rejecting it at the * point it is introduced is what keeps a bad one from ever being stored in the first place. */ export declare function assertValidRemoteConfig(config: RemoteConfig): void; /** * The identity of one of a store's SOURCE SLOTS - which is the endpoint it talks to, and not the same * question as which store this is (that is CommonConfig.name). A switchover's alternate port is a * distinct slot even though it names the same storage, because a slot holds a connection to a port. * * ONLY the type, the url, and the intermediate's alternate port are part of it: everything else is * policy about how we USE the endpoint, and changing policy must never make a store believe it is * looking at a NEW source - that would drop every index entry the old one held and rescan it from * scratch, so the files it holds go missing from listings until the rescan finishes, for a flag flip. * * Built by hand rather than by serializing the config, so it cannot change just because the routing * file was written with its keys in a different order. */ export declare function sourceIdentity(sourceConfig: SourceConfig | undefined): string; /** What an index entry records as the holder of its bytes (see ArchivesSource.url), so it must name the endpoint FOREVER. An intermediate is a switchover's temporary alternate port onto another source, and that port is gone for good once its window passes - so it is recorded as the source it was split out of, which holds the same bucket and outlives it. */ export declare function sourcePersistentUrl(sourceConfig: SourceConfig | undefined, folder: string): string; /** Reads a stored routing config. NEVER throws: this runs on every READ of a stored config, and a torn/corrupt file must not brick the paths that would fix it - above all writeRoutingConfig, where throwing while reading the OLD config blocks the write of the NEW one forever. Unreadable data is logged and read as undefined - the same as the file not existing. Judging a config on its way IN is the writer's job (see assertValidRemoteConfig at the write entry points), where rejecting bad data with a throw is correct. */ export declare function parseRoutingData(data: Buffer): RemoteConfig | undefined; export declare function serializeRemoteConfig(config: RemoteConfig): Buffer; } declare module "sliftutils/storage/remoteStorage/serverConfig" { export type StorageServerConfig = { domain: string; port: number; rootDomain: string; folder: string; }; export declare function setStorageServerConfig(value: StorageServerConfig): void; export declare function getStorageServerConfig(): StorageServerConfig; export declare function getStorageServerConfigOptional(): StorageServerConfig | undefined; export declare function setWritesRejectedReason(reason: string | undefined): void; export declare function getWritesRejectedReason(): string | undefined; export declare function assertWritesAllowed(): void; export declare function getStorageFolder(): string; export declare function addExtraListenPort(port: number): void; export declare function removeExtraListenPort(port: number): void; /** Whether address:port is this server process, including its extra listen ports (a deploy switchover's alternate port is still us). Used to tell which config entries are OUR copy of a bucket - the stores we run - as opposed to peers we synchronize with. Talking to ourselves is not one of the things it prevents: a source that happens to be us is reached over the API like any other. */ export declare function isOwnAddress(address: string, port: number): boolean; } declare module "sliftutils/storage/remoteStorage/sourceWrapper" { import { IArchives, SourceConfig, RemoteConfig } from "../IArchives"; import { ArchivesUrl } from "./ArchivesUrl"; export declare const RETRY_START_DELAY: number; export declare const RETRY_MAX_DELAY: number; export declare const RETRY_GROWTH = 1.5; export declare class SourceWrapper { config: SourceConfig; private background; api?: IArchives; url?: ArchivesUrl; writeBlocked?: string; private remote?; private disposed; private reconnectRunning; private accessCache?; private constructor(); /** Config updates routinely just move a source's valid window (the last window extends forever, then gets reduced when a new entry is appended). The wrapper survives that: only the window changes, keeping the connection, pings, and latency history. */ updateValidWindow(validWindow: [number, number]): void; static create(config: SourceConfig, options?: { background?: boolean; readOnly?: boolean; }): Promise; getDebugName(): string; isConnected(): boolean; /** A source whose window has passed is never read from or written to (see the window checks in ArchivesChain), and an intermediate only exists for the minutes of a deploy switchover - on either, being unreachable is expected, not a problem to report. */ private isConnectionProblemWorthReporting; private cooldownUntil; /** A source that failed while disconnected is skipped by callers for SOURCE_FAILURE_COOLDOWN - but only while some other source can serve the request. When nothing else is usable, callers ignore this and try it anyway, so a total outage still retries every time. */ isOnCooldown(): boolean; /** Call after a request failed while isConnected() was false: puts the source on cooldown and starts (if not already running) the background reconnect loop. Never blocks - the failed request still throws. */ noteFailure(): void; private reconnectLoop; private checkAccess; read(run: (archives: IArchives) => Promise): Promise; readRoutingConfig(): Promise; hasWriteAccess(): Promise; private pings; private pingTimer; private loggedConnected; /** Starts measuring this source's latency, which decides which hosts reads and variable-shard writes prefer. Hosted remotes ping over their API connection; URL-only sources (read-only mode, backblaze without credentials) probe over plain HTTPS instead - the client NEEDS their latency too, or it cannot rank the hosts it reads from. Our own local server counts as 0; only sources with neither form stay Infinity. */ startPinging(): void; /** Seeds the latency estimate before the first ping lands (e.g. from the initial routing fetch), so variable-shard picking has something immediately. Real pings take over from the first measurement on. */ seedLatency(ms: number): void; /** Median of the recent pings (API or URL-form, whichever this source measures), plus DISCONNECTED_LATENCY_PENALTY while the source is disconnected - so a down source still sorts and can still be picked, just after every connected one. Sources with no measurements yet sort last (Infinity), except our own in-process server, which is the best possible target (0). */ getLatency(): number; /** writeBlocked from missing backblaze credentials is re-checked on every write attempt: the secret load can fail TRANSIENTLY (early startup, a file-read hiccup), and one failed load must never permanently stop this process from writing to the bucket. The other writeBlocked reasons (read-only mode, browsers) are static properties of the process, so there is nothing to re-check. */ recheckWriteBlocked(): Promise; /** Writes always go through the API, so a permission error throws to the caller on every write (and access granted in the meantime is picked up automatically). */ write(run: (archives: IArchives) => Promise): Promise; dispose(): void; } } declare module "sliftutils/storage/remoteStorage/sourcesList" { export declare class SourcesList { private filePath; constructor(filePath: string); private urls; private indexes; private endsClean; private lastReload; private appendQueue; private load; getUrl(sourcesListIndex: number): string | undefined; getUrlReloading(sourcesListIndex: number): Promise; ensure(url: string): Promise; } } declare module "sliftutils/storage/remoteStorage/storageClientController" { import { RemoteConfig } from "../IArchives"; /** Subscribe to server-pushed routing change notifications. Returns the unsubscribe function. */ export declare function onServerRoutingChanged(listener: () => void): () => void; /** One chain's configs, as getRoutingConfigForName answers from them: the initial in-code config, and a getter for the synchronized one (adopted from the stored routing files, so it changes over time). */ type TrackedChainConfig = { configured: RemoteConfig; active: () => RemoteConfig; }; /** Every chain tracks its configs here (see ChainStateManager), so a server can ask what config was intended for a store name. Returns the untrack function. */ export declare function trackChainConfig(entry: TrackedChainConfig): () => void; export declare const StorageClientController: import("socket-function/SocketFunctionTypes").SocketRegistered<{ routingConfigChanged: () => Promise; getRoutingConfigForName: (config: { account: string; bucketName: string; name: string; }) => Promise; }>; export {}; } declare module "sliftutils/storage/remoteStorage/storageController" { /// /// import { ArchiveFileInfo, ArchivesConfig, ArchivesSyncStatus, FindConfig, SourceConfig } from "../IArchives"; import { ActiveBucketInfo, ServerBucketInfo } from "./storageServerState"; import { MachineState } from "../../security/machines/machines"; import { AccessTotals, AccessSummaryState } from "./accessStats"; import { LogFileInfo } from "../StreamingLogs"; import type { SummaryEntry } from "../../treeSummary"; export declare const REMOTE_STORAGE_CLASS_GUID = "RemoteStorageController-b7e42a91"; export declare const STORAGE_AUTH_PURPOSE = "remoteStorage-auth-1"; export declare const STORAGE_NOT_AUTHENTICATED = "REMOTE_STORAGE_NOT_AUTHENTICATED_cf2f7b1e"; export declare const STORAGE_ACCESS_DENIED = "REMOTE_STORAGE_ACCESS_DENIED_9d81a4c0"; export type AuthTokenData = { purpose: string; time: number; server: string; }; export type AuthToken = { certPem: string; issuerPem: string; signature: string; data: AuthTokenData; }; export type AccessState = { machineId: string; ip: string; hasAccess: boolean; reason?: string; trustedMachines?: MachineState[]; }; export declare function broadcastRoutingChanged(): void; export declare const RemoteStorageController: import("socket-function/SocketFunctionTypes").SocketRegistered<{ ping: () => Promise<{}>; authenticate: (token: AuthToken) => Promise<{ machineId: string; ip: string; }>; getAccessState: (config: { account: string; }) => Promise; adminListActiveBuckets: () => Promise<{ account: string; bucketName: string; }[]>; get2: (config: { account: string; bucketName: string; path: string; sourceConfig: SourceConfig; range?: { start: number; end: number; }; internal?: boolean; includeTombstones?: boolean; includeMarked?: boolean; }) => Promise<{ data: Buffer; writeTime: number; size: number; } | undefined>; set: (config: { account: string; bucketName: string; path: string; data: Buffer; sourceConfig: SourceConfig; lastModified?: number; forceSetImmutable?: boolean; internal?: boolean; undelete?: boolean; }) => Promise; del: (config: { account: string; bucketName: string; path: string; sourceConfig: SourceConfig; lastModified?: number; internal?: boolean; }) => Promise; move: (config: { account: string; bucketName: string; fromPath: string; toPath: string; sourceConfig: SourceConfig; }) => Promise; getInfo: (config: { account: string; bucketName: string; path: string; sourceConfig: SourceConfig; includeTombstones?: boolean; }) => Promise<{ writeTime: number; size: number; } | undefined>; findInfo: (config: FindConfig & { account: string; bucketName: string; prefix: string; sourceConfig: SourceConfig; }) => Promise; getChangesAfter2: (config: { account: string; bucketName: string; sourceConfig: SourceConfig; time: number; routes?: [number, number][]; internal?: boolean; }) => Promise; getArchivesConfig: (config: { account: string; bucketName: string; }) => Promise; listBuckets: (config: { account: string; }) => Promise; getActiveBucket: (config: { account: string; bucketName: string; }) => Promise; activateBucket: (config: { account: string; bucketName: string; }) => Promise; clearWriteStats: (config: { account: string; }) => Promise<{ clearedBuckets: number; }>; getAccessStats: (config: { account: string; }) => Promise; getAccessSummaries: (config: { account: string; operation: string; maxCount: number; weightBySize?: boolean; }) => Promise[]>; getIndexInfo: (config: { account: string; bucketName: string; }) => Promise<{ fileCount: number; byteCount: number; sources: { debugName: string; fileCount: number; byteCount: number; }[]; } | undefined>; getSyncStatus: (config: { account: string; bucketName: string; }) => Promise; listLogFiles: (config: { account: string; }) => Promise; getLogFile: (config: { account: string; name: string; }) => Promise; startLargeFile: (config: { account: string; bucketName: string; path: string; sourceConfig: SourceConfig; lastModified?: number; forceSetImmutable?: boolean; noChecks?: boolean; internal?: boolean; }) => Promise; uploadPart: (config: { uploadId: string; data: Buffer; offset?: number; }) => Promise; finishLargeFile: (config: { uploadId: string; }) => Promise; cancelLargeFile: (config: { uploadId: string; }) => Promise; httpEntry: (config?: { requireCalls?: string[]; cacheTime?: number; }) => Promise; }>; } declare module "sliftutils/storage/remoteStorage/storageLogs" { /// /// import { LogFileInfo } from "../StreamingLogs"; export declare const LOGS_FOLDER_NAME = "logs"; /** One mutation the server performed: set/del/move/undelete/setLarge/routingConfig, plus the per-file synchronization writes ("sync get"/"sync set"). Sizes and times, never the data. internal marks writes pushed by a peer's synchronization rather than a client. Logged DELIBERATELY at two layers: the controller (which knows the account/bucket and the caller) AND BlobStore itself (which knows the folder, and sees the writes that never pass through the controller) - the redundancy is the point, because a write that only one layer saw is exactly the kind of masked issue these logs exist to expose. Stream-only - one entry per write is exactly what the console does NOT need. */ export declare function logMutation(entry: { op: string; account?: string; bucketName?: string; store?: string; folder?: string; path: string; toPath?: string; size?: number; writeTime?: number; callerId?: string; internal?: boolean; }): void; /** A synchronization key point: scans and full syncs starting/finishing, reconciles, boundary scans - what an operator greps for to see whether the fleet is converging. Also printed to the console. */ export declare function logSyncEvent(entry: { event: string; store: string; source?: string; [key: string]: unknown; }): void; /** One console.error is all an error takes (console.error and console.warn are HOOKED to feed the stream) - this just guarantees the hook is installed first, for very-early callers. */ export declare function logStorageError(message: string): void; /** logStorageError at warn level: guarantees the console.warn hook is installed before warning, so warnings from before the first logged mutation still reach the stream. */ export declare function logStorageWarn(message: string): void; /** The log files this server holds - see StreamingLogs.listFiles. Empty on processes with no storage folder. */ export declare function listStorageLogFiles(): Promise; /** One log file's bytes, always LZ4-compressed - see StreamingLogs.readFileCompressed (decode with decodeLogFile). */ export declare function readStorageLogFile(name: string): Promise; } declare module "sliftutils/storage/remoteStorage/storageServer" { import "./accessPage"; export type HostStorageServerConfig = { url: string; folder: string; lowSpaceThresholdBytes?: number; internal?: boolean; selfSigned?: boolean; }; export declare function hostStorageServer(config: HostStorageServerConfig): Promise; } declare module "sliftutils/storage/remoteStorage/storageServerCli" { export {}; } declare module "sliftutils/storage/remoteStorage/storageServerState" { /// /// import { BlobStore } from "./blobStore"; import { RemoteConfig, SourceConfig, IArchives, ArchivesConfig, ArchivesSyncStatus } from "../IArchives"; import { BucketDiskInfo } from "./bucketDisk"; import { BucketWriteStats } from "./accessStats"; export declare function getStore(account: string, bucketName: string, name: string, callerNodeId?: string): BlobStore; /** The store serving a request: the one the client's selected entry NAMES. Account, name, and bucket ARE the folder, so this is a direct lookup, never a search of what exists - and a name this server has never seen is CREATED, never rejected, because asking for a name is the instruction to have that store (one name, one folder, one index; it configures itself once the routing config lands in it). Nothing else about the request is compared - not the window, not the route, not the flags - which is the whole point of naming it: a client a config version behind on some flag still reaches the right store. */ export declare function findBucketStore(account: string, bucketName: string, sourceConfig: SourceConfig | undefined): BlobStore; /** The stores of a bucket as the DISK records them (a bucket is nothing more than the store folders sharing its name), opened - so the ones that weren't running yet start synchronizing. Empty when the bucket does not exist here. */ export declare function getBucketStores(account: string, bucketName: string): Promise<{ name: string; store: BlobStore; }[]>; /** Internal (store-to-store) reads skip store selection entirely: the caller is another store whose index says this MACHINE holds the bytes - the persisted holder identity is just a URL, which cannot name a store. Whichever store's folder has the newest copy answers. */ export declare function readBucketInternal(account: string, bucketName: string, config: { path: string; range?: { start: number; end: number; }; includeTombstones?: boolean; }): Promise<{ data: Buffer; writeTime: number; size: number; } | undefined>; export declare function getBucketArchivesConfig(account: string, bucketName: string): Promise; export declare function bucketSyncStatus(account: string, bucketName: string): Promise; export declare function debugBucketIndexTotals(account: string, bucketName: string): Promise<{ fileCount: number; byteCount: number; sources: { debugName: string; fileCount: number; byteCount: number; }[]; }>; /** A cached IArchives for a persisted source identity: a routing URL (hosted/backblaze) or a disk folder path - the form BlobStore's sources list stores. Configuration (valid windows, routes) decides WHEN a source should be used; for reading bytes the index says a source holds, the URL alone is enough - even for sources no longer in any config. */ export declare function resolveSourceArchives(url: string): IArchives; /** * Writing the routing config is a write like any other: it goes into a store, and the store applies * it to itself and lets its peers pull it. The only thing that happens here is picking WHICH store, * because the writer names a source and a source names a store. * * In-flight switchover windows are re-injected first (see intermediateManagement): an operator's * config knows nothing about a switchover that is happening right now, and writing it as-is would * cancel it mid-flight. */ export declare function writeRoutingConfig(account: string, bucketName: string, name: string, data: Buffer, config?: { lastModified?: number; }): Promise; /** Which buckets this process currently has active (some store of theirs was opened) - what a deploy successor asks its predecessor for, so it activates exactly the buckets that are actually in use. */ export declare function getActiveBucketKeys(): { account: string; bucketName: string; }[]; export type ServerBucketInfo = { bucketName: string; active: boolean; /** Where the bucket's data lives on this server */ folder: string; /** The drive that folder is on. Buckets sharing a drive report the same numbers. */ disk?: BucketDiskInfo; diskError?: string; writeStats?: BucketWriteStats; config?: ArchivesConfig; error?: string; }; export type ActiveBucketInfo = { folder: string; /** The bucket's routing config, the newest copy among its stores. Absent when none of them has one yet. */ routing?: RemoteConfig; config: ArchivesConfig; }; /** The state of ONE active bucket. Returns an error string when the bucket is not active here, which is the normal state for a bucket nothing has accessed since startup. */ export declare function debugGetActiveBucket(account: string, bucketName: string): Promise; /** Loads every store of a bucket that exists on this server's disk into memory, which starts their synchronization and window timers, and returns the bucket's state. Nothing is written and no other server is contacted - unlike building an ArchivesChain for it, which would probe every source and could write the routing config. Already-active buckets just return their state. */ export declare function activateBucket(account: string, bucketName: string): Promise; export declare function debugListAccountBuckets(account: string): Promise; } declare module "sliftutils/storage/remoteStorage/storeConfig" { import { HostedConfig } from "../IArchives"; export declare class StoreConfig { readonly name: string; constructor(name: string, entries: HostedConfig[]); private entries; /** The routing config changed. Same store either way: its name did not change, so neither did its folder, its index, or the data in it. */ /** No entries is a real state, not an error: a store exists as soon as its folder does, and it may never have heard of a configuration (see StorePolicy). */ update(entries: HostedConfig[]): void; all(): HostedConfig[]; /** * What this store is configured to be right now: the entry whose window contains this moment; * failing that the next one due to start; failing that the last one to have ended. * * There is always an answer, and it matters that there is: a store between windows still holds * data and still has to answer for it, so it needs a route and flags even when nothing is * currently pointing writes at it. Which of the three cases produced the answer is deliberately * not exposed - the valid window itself is what says whether writes belong here, and every write * is checked against it separately. */ current(): StorePolicy; } /** The parts of a config entry that describe what a store IS - as opposed to which entry said so. Kept separate because a store with no entries still has all of them. */ export type StorePolicy = { validWindow: [number, number]; route?: [number, number]; public?: boolean; immutable?: boolean; fast?: boolean; writeDelay?: number; noFullSync?: boolean; readerDiskLimit?: number; }; } declare module "sliftutils/storage/remoteStorage/storePlan" { import { RemoteConfig, HostedConfig, SourceConfig } from "../IArchives"; /** Whether a config entry is THIS server's copy of this bucket - the same account and bucket, at an address this process answers on. */ export declare function isSelfSource(source: SourceConfig, account: string, bucketName: string): boolean; export declare function findSelfIndexes(routing: RemoteConfig, account: string, bucketName: string): number[]; export declare function selectEntryAt(entries: HostedConfig[], time: number, route?: number): HostedConfig | undefined; /** What one of our stores has to pull in at a valid-window boundary, so the writes that landed just before the handover are not missed. */ export type BoundaryHandover = { name: string; route: [number, number]; scanOwnDisk: boolean; remotes: Map; }; /** * Who held each slice of our route in the window before windowStart, for every self entry whose * window starts exactly then. This is the whole of "who do we take over from": a store taking over a * route may be taking it from several previous owners at once (their shards need not line up with * ours), and from itself for the parts it already held. * * A self entry is skipped when an EARLIER entry valid at the boundary already covers its whole route: * config order is priority, so that entry is the write target and we are not the one taking over. * Owners are then resolved in config order too, each claiming the part of our route still unclaimed - * the same first-match-wins rule that picks a write target at any other moment. * * Pure: config in, plan out. Nothing here reads a store, a clock, or the network. */ export declare function previousWindowOwners(config: RemoteConfig, windowStart: number, selfIndexes: number[]): BoundaryHandover[]; } declare module "sliftutils/storage/remoteStorage/storeSources" { import { IArchives, SourceConfig } from "../IArchives"; /** The client for one configured source: backblaze, or a storage server - including this one. */ export declare function createApiArchives(source: SourceConfig): IArchives; /** The ONE place a store's source is built. Every source a store synchronizes with is one of exactly two things: a configured peer, or the store's own disk folder (no sourceConfig). writeDelay wraps it so its writes are buffered in memory for that long (see ArchivesDelayed) - the whole of "fast writes", per source, decided here. */ export declare function createStoreSource(config: { sourceConfig?: SourceConfig; folder: string; writeDelay?: number; }): IArchives; /** Applies a changed config to an ALREADY RUNNING source (same endpoint, see sourceIdentity - only policy moved). Sources that carry their config into every request MUST be updated in place, or they keep sending the old one: the server matches the config it is handed against its own entries, so a source left holding a stale config eventually stops resolving to a store at all. The write delay is policy too, so it moves here as well. */ export declare function applySourceConfig(source: IArchives, sourceConfig: SourceConfig | undefined, writeDelay?: number): void; } declare module "sliftutils/storage/remoteStorage/storeSync" { import { IArchives, ArchivesSyncStatus, SyncActivity } from "../IArchives"; import type { BlobStore } from "./blobStore"; export declare class StoreSync { private store; private states; private activities; private evicting; private lastAccess; constructor(store: BlobStore); /** Starts every live source's synchronization, plus the maintenance loops. Called once, by the store's init - the store's index must already be loaded, since scans write straight into it. */ start(): void; /** * Asks every peer for the routing config, and takes it if it is newer than ours. This is the whole * of configuration propagation: it rides beside the scans rather than being part of them, because * one small read is worth doing every few minutes while a full listing is not, and because a * config we are missing is what stops everything else from being right. * * The copy is stored the same way a scan stores anything it pulls - an internal write into our own * store - and the store notices that file landing and re-configures itself. Nothing here knows what * a config means; it only knows this one file is worth asking for often. */ private pollRoutingConfig; /** Stops every source's loops (the store's own stop token stops the maintenance loops). */ stop(): void; /** A slot the store just appended to its sources array: it starts from nothing, so it gets a full scan. */ addSource(slot: number): void; /** The slot stays in the store's arrays forever (running loops hold slot numbers); it just goes dead - loops stop, and its index entries drop (other sources' scans re-find any copy that's still reachable through the new config). */ removeSource(slot: number): Promise; /** Whether a slot is still configured. Dead slots are never scanned, written, or read. */ isLive(slot: number): boolean; getActivities(): SyncActivity[]; /** A key was just served, so it goes to the back of the eviction queue. */ noteAccess(key: string): void; private entryUnchanged; getStatus(): ArchivesSyncStatus; /** Listings come straight from the index, so they must wait for our own base source's initial scan (which might lag minutes) before they are trustworthy. The base (local disk) is implicitly required - remote sources are not, they come and go. */ waitForRequiredScans(): Promise; /** Rescans our own disk's metadata into the index - used around valid window handoffs, where another process wrote files to the shared folder that our index hasn't seen. */ rescanBase(): Promise; /** One synchronization round of a source: the PULL direction always (its listing, applied to our index), and with "push" the push direction too (what our index says the source is missing, written to it). Push is an argument rather than a separate call because it cannot run without the pull's listing - the index alone cannot say what the source already holds. Listings unblock (initialScan) between the halves, so they never wait behind a push. Only one round per SOURCE runs at a time (cache keys the serializer by source index). */ private syncSource; /** A boundary scan of the node that owned (part of) our route in the valid window before ours, when that node is different storage (a disk rescan can't see its writes): just its changes since the boundary neighborhood, with matching values pulled onto our own disk. */ boundaryScanRemote(source: IArchives, config: { since: number; route?: [number, number]; }): Promise; private windowsAllowScanning; private startSourceSyncLoops; private pullSource; private pushSource; private updateScanIndex; private pollChanges; private copySourceFiles; private enforceDiskLimit; private cleanupTombstones; private enforceHistoryLimit; } } declare module "sliftutils/storage/remoteStorage/validation" { export declare function assertValidName(value: string, kind: string): void; /** A store's name (see CommonConfig.name), which also allows dots - a name is often a host or a version, and both read wrong without them. It is one path segment of the store's folder, so the two names that would mean a different folder entirely are rejected: everything else containing dots is just a name. */ export declare function assertValidSourceName(value: string): void; export declare function assertValidPath(path: string): void; /** Method decorator: validates the well-known fields of the method's single config-object argument - account/bucketName as names, path as a path - before the method runs. Fields the config doesn't have are skipped, so it applies to every API method uniformly. prefix is deliberately NOT validated: prefixes may be empty or end with "/", both invalid for paths. */ export declare function assertValidArgs(target: unknown, key: string, descriptor: PropertyDescriptor): void; } declare module "sliftutils/storage/storage" { declare module "node-forge" { declare type Ed25519PublicKey = { publicKeyBytes: Buffer; } & Buffer; declare type Ed25519PrivateKey = { privateKeyBytes: Buffer; } & Buffer; class ed25519 { static generateKeyPair(): { publicKey: Ed25519PublicKey, privateKey: Ed25519PrivateKey }; static privateKeyToPem(key: Ed25519PrivateKey): string; static privateKeyFromPem(pem: string): Ed25519PrivateKey; static publicKeyToPem(key: Ed25519PublicKey): string; static publicKeyFromPem(pem: string): Ed25519PublicKey; } } interface FileSystemDirectoryHandle { [Symbol.asyncIterator](): AsyncIterator<[string, FileSystemFileHandle | FileSystemDirectoryHandle]>; requestPermission(config: { mode: "read" | "readwrite" }): Promise; } interface FileSystemFileHandle { getFile(): File; createWritable(): FileSystemWritableFileStream; } interface Window { showSaveFilePicker(config?: { types: { description: string; accept: { [mimeType: string]: string[] } }[]; }): Promise; showDirectoryPicker(): Promise; showOpenFilePicker(config?: { types: { description: string; accept: { [mimeType: string]: string[] } }[]; }): Promise; } }