@cloudbitmaps/core 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +4 -4
- package/README.md +33 -16
- package/dist/chunk-5OMC5U7U.js +508 -0
- package/dist/chunk-5OMC5U7U.js.map +7 -0
- package/dist/core/audit.d.ts +128 -0
- package/dist/core/bit-route.d.ts +21 -0
- package/dist/core/blob.d.ts +34 -0
- package/dist/core/budget.d.ts +68 -0
- package/dist/core/codec.d.ts +122 -0
- package/dist/core/concurrency.d.ts +15 -0
- package/dist/core/consistency.d.ts +73 -0
- package/dist/core/cooperative.d.ts +78 -0
- package/dist/core/cost.d.ts +254 -0
- package/dist/core/crbm/crc32c.d.ts +10 -0
- package/dist/core/crbm/format.d.ts +114 -0
- package/dist/core/crbm/reader.d.ts +86 -0
- package/dist/core/crbm/varint.d.ts +13 -0
- package/dist/core/crbm/writer.d.ts +39 -0
- package/dist/core/crbm-storage-source.d.ts +522 -0
- package/dist/core/crypto.d.ts +80 -0
- package/dist/core/determinism.d.ts +42 -0
- package/dist/core/discover.d.ts +77 -0
- package/dist/core/due-index.d.ts +53 -0
- package/dist/core/engine.d.ts +246 -0
- package/dist/core/erase-id.d.ts +176 -0
- package/dist/core/erasure.d.ts +185 -0
- package/dist/core/errors.d.ts +113 -0
- package/dist/core/generation-gc.d.ts +56 -0
- package/dist/core/keys.d.ts +23 -0
- package/dist/core/load.d.ts +121 -0
- package/dist/core/lru.d.ts +69 -0
- package/dist/core/metrics.d.ts +136 -0
- package/dist/core/name-codec.d.ts +69 -0
- package/dist/core/pinned-storage-source.d.ts +90 -0
- package/dist/core/ports.d.ts +423 -0
- package/dist/core/reader-defaults.d.ts +18 -0
- package/dist/core/registry-scan.d.ts +47 -0
- package/dist/core/retention-sweep.d.ts +198 -0
- package/dist/core/retention.d.ts +67 -0
- package/dist/core/retry.d.ts +69 -0
- package/dist/core/rollback.d.ts +75 -0
- package/dist/core/validate.d.ts +44 -0
- package/dist/driver-kit.d.ts +9 -0
- package/dist/driver-kit.js +275 -0
- package/dist/driver-kit.js.map +7 -0
- package/dist/drivers/_shared/keys.d.ts +26 -0
- package/dist/drivers/_shared/object-registry-keys.d.ts +28 -0
- package/dist/drivers/_shared/object-registry.d.ts +89 -0
- package/dist/drivers/_shared/registry.d.ts +66 -0
- package/dist/drivers/backends.d.ts +43 -0
- package/dist/drivers/crypto.d.ts +38 -0
- package/dist/drivers/localfs/fs-util.d.ts +21 -0
- package/dist/drivers/localfs/paths.d.ts +33 -0
- package/dist/drivers/localfs/registry.d.ts +28 -0
- package/dist/drivers/localfs/storage.d.ts +19 -0
- package/dist/drivers/memory.d.ts +49 -0
- package/dist/drivers/retry/retrying-chunk-source.d.ts +59 -0
- package/dist/export/index.d.ts +93 -0
- package/dist/index.d.ts +76 -2760
- package/dist/index.js +3527 -3133
- package/dist/index.js.map +7 -1
- package/dist/testing/fuzz-core.d.ts +14 -0
- package/package.json +19 -115
- package/dist/azure/index.cjs +0 -368
- package/dist/azure/index.cjs.map +0 -1
- package/dist/azure/index.d.cts +0 -48
- package/dist/azure/index.d.ts +0 -48
- package/dist/azure/index.js +0 -304
- package/dist/azure/index.js.map +0 -1
- package/dist/cassandra/index.cjs +0 -265
- package/dist/cassandra/index.cjs.map +0 -1
- package/dist/cassandra/index.d.cts +0 -52
- package/dist/cassandra/index.d.ts +0 -52
- package/dist/cassandra/index.js +0 -204
- package/dist/cassandra/index.js.map +0 -1
- package/dist/chunk-7LMLYSVJ.js +0 -43
- package/dist/chunk-7LMLYSVJ.js.map +0 -1
- package/dist/chunk-BD22WDAN.js +0 -230
- package/dist/chunk-BD22WDAN.js.map +0 -1
- package/dist/chunk-NUIDEEFZ.js +0 -91
- package/dist/chunk-NUIDEEFZ.js.map +0 -1
- package/dist/chunk-OZ6LAFV4.js +0 -6
- package/dist/chunk-OZ6LAFV4.js.map +0 -1
- package/dist/chunk-SNJVZ227.js +0 -35
- package/dist/chunk-SNJVZ227.js.map +0 -1
- package/dist/dynamodb/index.cjs +0 -767
- package/dist/dynamodb/index.cjs.map +0 -1
- package/dist/dynamodb/index.d.cts +0 -106
- package/dist/dynamodb/index.d.ts +0 -106
- package/dist/dynamodb/index.js +0 -469
- package/dist/dynamodb/index.js.map +0 -1
- package/dist/gcs/index.cjs +0 -343
- package/dist/gcs/index.cjs.map +0 -1
- package/dist/gcs/index.d.cts +0 -46
- package/dist/gcs/index.d.ts +0 -46
- package/dist/gcs/index.js +0 -279
- package/dist/gcs/index.js.map +0 -1
- package/dist/index.cjs +0 -4990
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -2761
- package/dist/mongodb/index.cjs +0 -260
- package/dist/mongodb/index.cjs.map +0 -1
- package/dist/mongodb/index.d.cts +0 -45
- package/dist/mongodb/index.d.ts +0 -45
- package/dist/mongodb/index.js +0 -199
- package/dist/mongodb/index.js.map +0 -1
- package/dist/mysql/index.cjs +0 -281
- package/dist/mysql/index.cjs.map +0 -1
- package/dist/mysql/index.d.cts +0 -56
- package/dist/mysql/index.d.ts +0 -56
- package/dist/mysql/index.js +0 -211
- package/dist/mysql/index.js.map +0 -1
- package/dist/ports-CvcvJWf6.d.cts +0 -395
- package/dist/ports-CvcvJWf6.d.ts +0 -395
- package/dist/postgres/index.cjs +0 -273
- package/dist/postgres/index.cjs.map +0 -1
- package/dist/postgres/index.d.cts +0 -54
- package/dist/postgres/index.d.ts +0 -54
- package/dist/postgres/index.js +0 -203
- package/dist/postgres/index.js.map +0 -1
- package/dist/redis/index.cjs +0 -257
- package/dist/redis/index.cjs.map +0 -1
- package/dist/redis/index.d.cts +0 -42
- package/dist/redis/index.d.ts +0 -42
- package/dist/redis/index.js +0 -197
- package/dist/redis/index.js.map +0 -1
- package/dist/s3/index.cjs +0 -918
- package/dist/s3/index.cjs.map +0 -1
- package/dist/s3/index.d.cts +0 -112
- package/dist/s3/index.d.ts +0 -112
- package/dist/s3/index.js +0 -555
- package/dist/s3/index.js.map +0 -1
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metrics seam — an injected sink the engine pushes typed events to, exactly like the `Clock`/driver seams
|
|
3
|
+
* keep `core/` pure and storage-agnostic.
|
|
4
|
+
*
|
|
5
|
+
* Design: the library emits **neutral domain events** and stays vendor-agnostic — you (or a ~12-line
|
|
6
|
+
* adapter) map them to OpenTelemetry / Datadog / a log line. The default is a **no-op**, and emission is
|
|
7
|
+
* skipped entirely when no sink is wired (near-zero overhead, no telemetry dependency). Events carry **raw
|
|
8
|
+
* observations** (bytes, counts, ms); turning those into billable units / dollars is the cost model's job,
|
|
9
|
+
* which keeps rates pluggable.
|
|
10
|
+
*
|
|
11
|
+
* Two caveats for sink authors: `onEvent` runs **synchronously on the I/O path**, so keep it cheap and
|
|
12
|
+
* non-blocking (offload batching/network to your own async queue); and `segment`/`namespace` are
|
|
13
|
+
* caller-controlled strings that may be PII and are **unbounded-cardinality** — do not map them to
|
|
14
|
+
* per-series metric labels unless they're known low-cardinality and PII-free. See the observability
|
|
15
|
+
* guide.
|
|
16
|
+
*/
|
|
17
|
+
/** The segment operations that emit an `op` latency event (timed with the injected clock, at the facade). */
|
|
18
|
+
export type MetricOpName = 'has' | 'count' | 'intersectInto' | 'unionInto' | 'andNotInto';
|
|
19
|
+
/**
|
|
20
|
+
* One observability event. A discriminated union on `kind` — new variants can be added over time without
|
|
21
|
+
* breaking consumers (they switch on the kinds they care about). `namespace` is the segment's namespace
|
|
22
|
+
* (absent ⇒ the default namespace). Segment names are identifiers, never bitmap contents (threat model:
|
|
23
|
+
* never log PII / bitmap bits).
|
|
24
|
+
*/
|
|
25
|
+
export type MetricEvent = {
|
|
26
|
+
readonly kind: 'storage.get';
|
|
27
|
+
readonly namespace?: string;
|
|
28
|
+
readonly segment: string;
|
|
29
|
+
/** Bytes returned (0 if the chunk was absent — a GET still happened). */
|
|
30
|
+
readonly bytes: number;
|
|
31
|
+
/**
|
|
32
|
+
* Elapsed wall time of the read — includes any transient-retry backoff on the storage call. From the
|
|
33
|
+
* injected clock, clamped to ≥ 0 (0 under the no-wait test clock).
|
|
34
|
+
*/
|
|
35
|
+
readonly ms: number;
|
|
36
|
+
} | {
|
|
37
|
+
readonly kind: 'cache';
|
|
38
|
+
readonly hit: boolean;
|
|
39
|
+
} | {
|
|
40
|
+
readonly kind: 'retry';
|
|
41
|
+
/** Infrastructure-fault backoff (throttling, 5xx, a dropped connection) — the one kind of retry the store does. */
|
|
42
|
+
readonly reason: 'transient';
|
|
43
|
+
/** 1-based number of the attempt about to be retried. */
|
|
44
|
+
readonly attempt: number;
|
|
45
|
+
readonly delayMs: number;
|
|
46
|
+
} | {
|
|
47
|
+
readonly kind: 'intersect';
|
|
48
|
+
/**
|
|
49
|
+
* Which chunk-aligned combine this was. **Optional** — absent means `'intersect'`.
|
|
50
|
+
*
|
|
51
|
+
* For `'union'` over its *include* operands alone, `skippedChunks` is 0 by construction — union reads
|
|
52
|
+
* every chunk of every operand, so there is nothing to prune. It can still be non-zero when a union
|
|
53
|
+
* carries `exclude` operands, because a suppression list may hold keys no include does and those are
|
|
54
|
+
* legitimately never fetched. So: 0 is the expected shape for a plain union, not a guarantee.
|
|
55
|
+
*/
|
|
56
|
+
readonly op?: 'intersect' | 'union' | 'andNot';
|
|
57
|
+
/** Operand count, including any `exclude` (suppression) operands. */
|
|
58
|
+
readonly operands: number;
|
|
59
|
+
/**
|
|
60
|
+
* Distinct chunk **keys** selected to fetch — for `'intersect'`, those present in every operand.
|
|
61
|
+
*
|
|
62
|
+
* **Keys, not requests.** The actual storage GETs are roughly `fetchedChunks × operands`, plus one per
|
|
63
|
+
* `exclude` that holds a given key, which is why this number is smaller than what the per-op budget
|
|
64
|
+
* charges for the same call. The two are different units by design; if you are reconciling a bill, the
|
|
65
|
+
* budget's accounting is the one that models requests.
|
|
66
|
+
*/
|
|
67
|
+
readonly fetchedChunks: number;
|
|
68
|
+
/**
|
|
69
|
+
* **Distinct** chunk-keys pruned — never fetched (the chunk-skipping saving). Counts distinct keys,
|
|
70
|
+
* not per-operand GETs, so it under-states the true GET saving when 3+ operands partially overlap.
|
|
71
|
+
*
|
|
72
|
+
* On a read bounded by `after` / `through`, this and `fetchedChunks` count only the keys inside the range:
|
|
73
|
+
* the keys outside it are never considered, so they are not counted as pruned either. Both count the keys the
|
|
74
|
+
* call would fetch if read to the end, so a page that stops early reports every key up to `through`, or to the
|
|
75
|
+
* end of the segment when `through` is left out.
|
|
76
|
+
*/
|
|
77
|
+
readonly skippedChunks: number;
|
|
78
|
+
} | {
|
|
79
|
+
readonly kind: 'op';
|
|
80
|
+
readonly name: MetricOpName;
|
|
81
|
+
readonly ms: number;
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* The sink you plug in. One method, synchronous, fire-and-forget. It must never throw back into the
|
|
85
|
+
* caller — `CloudRoaring` wraps a user sink with {@link safeMetrics} so a buggy sink can't break I/O.
|
|
86
|
+
*/
|
|
87
|
+
export interface IMetricsSink {
|
|
88
|
+
onEvent(event: MetricEvent): void;
|
|
89
|
+
}
|
|
90
|
+
/** The default sink: discards everything, zero overhead. Used whenever no sink is wired. */
|
|
91
|
+
export declare const NOOP_METRICS: IMetricsSink;
|
|
92
|
+
/**
|
|
93
|
+
* Wrap a sink so a throwing/buggy `onEvent` can never break the data path — observability is strictly
|
|
94
|
+
* best-effort. `CloudRoaring` applies this to the user-supplied sink at construction.
|
|
95
|
+
*/
|
|
96
|
+
export declare function safeMetrics(sink: IMetricsSink): IMetricsSink;
|
|
97
|
+
/** Accumulated totals — the shape returned by {@link CountingMetricsSink.snapshot}. */
|
|
98
|
+
export interface MetricsSnapshot {
|
|
99
|
+
readonly storage: {
|
|
100
|
+
readonly gets: number;
|
|
101
|
+
readonly bytes: number;
|
|
102
|
+
readonly totalMs: number;
|
|
103
|
+
};
|
|
104
|
+
readonly cache: {
|
|
105
|
+
readonly hits: number;
|
|
106
|
+
readonly misses: number;
|
|
107
|
+
};
|
|
108
|
+
readonly retries: {
|
|
109
|
+
readonly transient: number;
|
|
110
|
+
};
|
|
111
|
+
readonly intersect: {
|
|
112
|
+
readonly calls: number;
|
|
113
|
+
readonly fetchedChunks: number;
|
|
114
|
+
readonly skippedChunks: number;
|
|
115
|
+
};
|
|
116
|
+
readonly ops: Readonly<Record<MetricOpName, {
|
|
117
|
+
readonly count: number;
|
|
118
|
+
readonly totalMs: number;
|
|
119
|
+
}>>;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* A ready-made sink that tallies events into a {@link MetricsSnapshot} — handy for tests, quick scripts,
|
|
123
|
+
* and as the grounded-request-cost source behind `costReport()`. `snapshot()` returns an independent copy;
|
|
124
|
+
* `reset()` zeroes the counters.
|
|
125
|
+
*/
|
|
126
|
+
export declare class CountingMetricsSink implements IMetricsSink {
|
|
127
|
+
private storage;
|
|
128
|
+
private cache;
|
|
129
|
+
private retries;
|
|
130
|
+
private intersect;
|
|
131
|
+
private ops;
|
|
132
|
+
private static zeroOps;
|
|
133
|
+
onEvent(event: MetricEvent): void;
|
|
134
|
+
snapshot(): MetricsSnapshot;
|
|
135
|
+
reset(): void;
|
|
136
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Name → physical-key encoding. The library validates *length*, and encodes everything else.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `core/` rather than beside the drivers because it is pure string logic — no I/O, no SDK, no
|
|
5
|
+
* `node:` builtin — and because `validate.ts` needs it to measure a name against the key budget. Core may
|
|
6
|
+
* not import a driver (invariant 7), and a driver may import core, so this is the only layer both can see.
|
|
7
|
+
*
|
|
8
|
+
* A name is **any non-empty string**, and each physical boundary escapes what *it* cannot take literally. An
|
|
9
|
+
* allowlist of characters would reject names for the storage layer's convenience, and the storage layer's
|
|
10
|
+
* convenience is the library's job, not the caller's. It would also let through the names that are actually
|
|
11
|
+
* dangerous: `con`, `nul` and `com1` are Windows device names made only of letters and digits, which pass any
|
|
12
|
+
* allowlist and fail only on a user's machine. A grammar can be both too narrow and too permissive at once,
|
|
13
|
+
* which is the argument against having one.
|
|
14
|
+
*
|
|
15
|
+
* Percent-encoding, because it is the one escape everybody already reads. `%` escapes itself as `%25` and is
|
|
16
|
+
* encoded **first**, which is what makes the transform injective: every `%` in an encoded string starts an
|
|
17
|
+
* escape, so decoding is unambiguous and two distinct names can never collide on one key.
|
|
18
|
+
*
|
|
19
|
+
* There are two alphabets, because the boundaries differ:
|
|
20
|
+
*
|
|
21
|
+
* - **Object keys** (S3, GCS, Azure Blob) take almost anything. They need `/` escaped so a name cannot
|
|
22
|
+
* invent hierarchy or break a key parser that splits on it, and control characters because they are not
|
|
23
|
+
* legal in the XML an S3 LIST returns. Every other character outside `[A-Za-z0-9._:-]` is escaped as well.
|
|
24
|
+
* - **Filesystem paths** need all of that plus `:` (an NTFS alternate-data-stream separator, where a write can
|
|
25
|
+
* *succeed* while `readdir` never lists the result), plus three hazards that are about the component as a
|
|
26
|
+
* whole rather than its characters — see {@link encodeNameForPath}.
|
|
27
|
+
*
|
|
28
|
+
* **The key alphabet is fixed for good.** Adding a character to it would change what `encodeNameForKey` emits
|
|
29
|
+
* for every name containing that character, silently moving those segments to keys nothing looks up. A test pins
|
|
30
|
+
* it, one printable ASCII character at a time.
|
|
31
|
+
*
|
|
32
|
+
* **A plain name encodes to itself on the OBJECT-KEY alphabet** — one made only of `[A-Za-z0-9._:-]` that
|
|
33
|
+
* starts with a letter or digit — so S3, GCS and Azure keys read as the names that made them. That is the
|
|
34
|
+
* property to preserve if this file is ever edited, and a property test asserts it over every plain name.
|
|
35
|
+
*
|
|
36
|
+
* The **path** alphabet escapes more. Besides `:`, two classes of plain name are escaped on a path: a Windows
|
|
37
|
+
* device-name stem (`con`, `nul`, `com1`, `con.backup`) and a trailing dot (`a.`), since both silently alias
|
|
38
|
+
* or fail on Windows.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Encode a name for use inside an **object key** (S3, GCS, Azure Blob).
|
|
42
|
+
*
|
|
43
|
+
* Leaves `[A-Za-z0-9._:-]` literal, except a leading `_` and a name that is only `.` or `..`, so a plain name
|
|
44
|
+
* reads as itself, and escapes everything else — including `/` (which would invent hierarchy and break a parser
|
|
45
|
+
* that splits on it) and control characters (not legal in S3's XML responses).
|
|
46
|
+
*/
|
|
47
|
+
export declare function encodeNameForKey(name: string): string;
|
|
48
|
+
/** Inverse of {@link encodeNameForKey}. */
|
|
49
|
+
export declare function decodeNameFromKey(encoded: string): string;
|
|
50
|
+
/**
|
|
51
|
+
* Encode a name for use as a **filesystem path component**.
|
|
52
|
+
*
|
|
53
|
+
* Everything {@link encodeNameForKey} escapes, plus `:`, plus three hazards that are properties of the whole
|
|
54
|
+
* component rather than of any character in it. All three are silent corruption rather than errors, which is
|
|
55
|
+
* why they are handled here instead of being left to the OS:
|
|
56
|
+
*
|
|
57
|
+
* 1. **`.` and `..`** are traversal. `join(root, '..')` leaves the storage root.
|
|
58
|
+
* 2. **Windows reserved device names** — `CON`, `PRN`, `AUX`, `NUL`, `COM1`–`COM9`, `LPT1`–`LPT9`, case
|
|
59
|
+
* insensitive and *with or without an extension*, so `con.0.crbm` is reserved too. Opening one addresses a
|
|
60
|
+
* device rather than a file, so `store.segment('con')` would break only on a user's Windows box.
|
|
61
|
+
* 3. **A trailing `.` or space**, which Windows silently strips — so `a.` and `a` would become the same
|
|
62
|
+
* directory, quietly merging two segments.
|
|
63
|
+
*
|
|
64
|
+
* Each is fixed by escaping the one character that defuses it, which keeps the encoding injective: a name that
|
|
65
|
+
* needed defusing produces a `%XX` no other name produces.
|
|
66
|
+
*/
|
|
67
|
+
export declare function encodeNameForPath(name: string): string;
|
|
68
|
+
/** Inverse of {@link encodeNameForPath}. */
|
|
69
|
+
export declare function decodeNameFromPath(encoded: string): string;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A {@link StorageChunkSource} view that holds **one** segment at **one** generation, and passes everything else
|
|
3
|
+
* through to the live source.
|
|
4
|
+
*
|
|
5
|
+
* A pin exists so a long job describes a single instant. An ordinary handle re-resolves on `cache.genTtlMs`, so a
|
|
6
|
+
* publish part-way through an export, a send or a reconciliation means the second half of the job describes a
|
|
7
|
+
* different instant than the first — and nothing in the result says so.
|
|
8
|
+
*
|
|
9
|
+
* **A pin is a generation number and the object it named, not a captured reader.** The reader behind it can live
|
|
10
|
+
* in the shared bounded LRU and be evicted freely: re-opening at the same number reopens the same object, or, if
|
|
11
|
+
* the name was purged and loaded again since, finds another one, which the pin's fingerprint refuses. Holding the
|
|
12
|
+
* reader instead put every live pin outside the library's memory ceiling.
|
|
13
|
+
*
|
|
14
|
+
* **Other segments are not pinned, and that is the contract, not an omission.** `snap.intersect([other])` reads
|
|
15
|
+
* `snap` at its pinned generation and `other` at whatever is current. Pinning the whole query means pinning
|
|
16
|
+
* each segment. The alternative shapes are both worse: refusing the combine outright makes a pin useless to the
|
|
17
|
+
* export and reconciliation jobs it exists for, and silently pinning the operands would quietly change what
|
|
18
|
+
* `other` means to a caller who did not ask.
|
|
19
|
+
*
|
|
20
|
+
* What this must never do is the inverse — read a pinned handle's segment **live** because the call was made
|
|
21
|
+
* on some other handle. That is why the routing is by segment identity here rather than by which object the
|
|
22
|
+
* caller happened to start from.
|
|
23
|
+
*/
|
|
24
|
+
import type { ChunkRef, StorageChunkSource, SegmentRef, SegmentSize } from './ports.js';
|
|
25
|
+
import type { CrbmStorageChunkSource } from './crbm-storage-source.js';
|
|
26
|
+
/**
|
|
27
|
+
* What a pin holds for one segment: the generation, the version identifying those exact bytes, and the pinned
|
|
28
|
+
* object's fingerprint, so a read can tell that the generation it opens is still that object.
|
|
29
|
+
*
|
|
30
|
+
* `fingerprint` is optional so that a pin built by hand, before there was one, still compiles. Nothing checks the
|
|
31
|
+
* object such a pin reads, so it cannot tell a name purged and loaded again from the segment it pinned.
|
|
32
|
+
* `seg.pin()` always records it.
|
|
33
|
+
*/
|
|
34
|
+
export interface PinnedAt {
|
|
35
|
+
readonly generation: number | null;
|
|
36
|
+
readonly version: string | null;
|
|
37
|
+
readonly fingerprint?: string | null;
|
|
38
|
+
}
|
|
39
|
+
export declare class PinnedStorageChunkSource implements StorageChunkSource {
|
|
40
|
+
private readonly inner;
|
|
41
|
+
/**
|
|
42
|
+
* The pinned segments, keyed by {@link segmentKey}. A **set** rather than one entry because a combine may
|
|
43
|
+
* involve several pinned handles, and each must be read at its own pin — including when the call was made
|
|
44
|
+
* on a different handle. Reading a pinned handle live because the caller started elsewhere is the silent
|
|
45
|
+
* wrong answer this exists to prevent.
|
|
46
|
+
*/
|
|
47
|
+
private readonly pins;
|
|
48
|
+
constructor(inner: CrbmStorageChunkSource,
|
|
49
|
+
/**
|
|
50
|
+
* The pinned segments, keyed by {@link segmentKey}. A **set** rather than one entry because a combine may
|
|
51
|
+
* involve several pinned handles, and each must be read at its own pin — including when the call was made
|
|
52
|
+
* on a different handle. Reading a pinned handle live because the caller started elsewhere is the silent
|
|
53
|
+
* wrong answer this exists to prevent.
|
|
54
|
+
*/
|
|
55
|
+
pins: ReadonlyMap<string, PinnedAt>);
|
|
56
|
+
/** The unpinned segments refresh as the store's own source does; a pinned one never does. */
|
|
57
|
+
get pointerRefreshMs(): number;
|
|
58
|
+
/**
|
|
59
|
+
* The pin for this segment, or `undefined` if it is not pinned here.
|
|
60
|
+
*
|
|
61
|
+
* A pin whose `generation` is `null` is a segment that had none when it was pinned: that handle reads empty
|
|
62
|
+
* for its lifetime, which is exactly what an unpinned read of it does. That is **not** the same as "this
|
|
63
|
+
* source cannot pin" — a source that genuinely cannot is a capability gap and fails fast at `pin()`.
|
|
64
|
+
*/
|
|
65
|
+
private pinFor;
|
|
66
|
+
getChunk(ref: ChunkRef): Promise<Uint8Array | null>;
|
|
67
|
+
listChunkKeys(ref: SegmentRef): Promise<number[]>;
|
|
68
|
+
sizeOf(ref: SegmentRef): Promise<SegmentSize | null>;
|
|
69
|
+
cardinalities(ref: SegmentRef): Promise<ReadonlyMap<number, number> | null>;
|
|
70
|
+
currentGeneration(ref: SegmentRef): Promise<number | null>;
|
|
71
|
+
/**
|
|
72
|
+
* A pinned segment reports the version captured when it was pinned, marked as a pin's, so its decoded chunks
|
|
73
|
+
* are cached under keys that no live read writes. A live read keys its fetches by the version it resolved when
|
|
74
|
+
* it began, but is served whatever generation the live source holds when each fetch lands — after a publish
|
|
75
|
+
* and a lapsed `cache.genTtlMs`, a reader-cache eviction, a sweep that heals the read forward, or an
|
|
76
|
+
* invalidation, a different one — so an entry under a live version can hold another generation's chunk.
|
|
77
|
+
* Invariant 3 lets that live call see it, and later live reads resolve the current version and never look the
|
|
78
|
+
* entry up; a pin sharing the key would be handed it, and return a read that mixed two generations. A pinned
|
|
79
|
+
* read fetches exactly its own generation, so the entries it fills are always that generation's.
|
|
80
|
+
*
|
|
81
|
+
* The pin still shares the store's chunk cache, and its memory ceiling; what it gives up is a hit on a chunk a
|
|
82
|
+
* live read of the same version cached, which costs a pin one GET per such chunk. Live reads make no call for it,
|
|
83
|
+
* though a pin's entries share the cache's bound with theirs, so under a small `cache.maxChunks` each can evict
|
|
84
|
+
* the other. The version, not the bare generation, is still what the key carries: sharing on a generation-only
|
|
85
|
+
* key was how a pinned read could resurrect an id that `eraseIdFromSegment` had reported physically gone.
|
|
86
|
+
*/
|
|
87
|
+
currentVersion(ref: SegmentRef): Promise<string | null>;
|
|
88
|
+
exists(ref: SegmentRef): Promise<boolean>;
|
|
89
|
+
invalidate(ref: SegmentRef): void;
|
|
90
|
+
}
|